Обновить список каналов и кнопку сообщения
This commit is contained in:
parent
77f5759d60
commit
f9a15ab192
@ -0,0 +1,28 @@
|
||||
# Общий список каналов без stories
|
||||
|
||||
- Краткое описание:
|
||||
вкладка `Каналы` переведена на единый список без разделения на "мои" и "подписки".
|
||||
Название канала в списке теперь показывается как `login_владельца/название_канала`.
|
||||
Служебный канал `stories` скрыт из списка каналов, поиска, подписки и связанных UI-сценариев.
|
||||
|
||||
- Что проверять:
|
||||
1. Открыть вкладку `Каналы`.
|
||||
2. Убедиться, что сразу показывается один общий список.
|
||||
3. Проверить, что свои и чужие каналы отображаются вместе.
|
||||
4. Проверить формат названий: `ownerLogin/channelName`.
|
||||
5. Открыть свой канал и убедиться, что внутри сохраняется UI владельца.
|
||||
6. Открыть чужой канал и убедиться, что внутри сохраняется UI подписчика.
|
||||
7. Проверить, что `stories` не отображается:
|
||||
- в общем списке;
|
||||
- в поиске каналов;
|
||||
- в подписке на канал;
|
||||
- в списках выбора канала для репоста.
|
||||
|
||||
- Ожидаемый результат:
|
||||
- вкладка `Каналы` больше не делится на два режима;
|
||||
- все видимые каналы идут единым списком;
|
||||
- `stories` нигде не виден и не предлагается пользователю;
|
||||
- переход в канал сохраняет корректный UI в зависимости от владельца.
|
||||
|
||||
- Статус:
|
||||
`pending`
|
||||
@ -1,2 +1,2 @@
|
||||
client.version=1.2.260
|
||||
server.version=1.2.245
|
||||
client.version=1.2.261
|
||||
server.version=1.2.246
|
||||
|
||||
@ -94,7 +94,7 @@ export function renderToolbar(currentPageId, navigate) {
|
||||
btn.append(badge);
|
||||
}
|
||||
if (item.pageId === 'channels-list') {
|
||||
btn.addEventListener('click', () => navigate('channels-list/feed'));
|
||||
btn.addEventListener('click', () => navigate('channels-list'));
|
||||
} else {
|
||||
btn.addEventListener('click', () => navigateWithGuestRules(item.pageId, navigate));
|
||||
}
|
||||
|
||||
@ -178,7 +178,11 @@ function allFeedSummaries() {
|
||||
...(feed.ownedChannels || []),
|
||||
...(feed.followedUsersChannels || []),
|
||||
...(feed.followedChannels || []),
|
||||
];
|
||||
].filter((summary) => {
|
||||
const typeCode = Number(summary?.channel?.channelTypeCode ?? 1);
|
||||
const channelName = String(summary?.channel?.channelName || '').trim().toLowerCase();
|
||||
return typeCode !== 0 && channelName !== 'stories';
|
||||
});
|
||||
}
|
||||
|
||||
function resolveChannelDisplayName(channelSelector) {
|
||||
@ -394,7 +398,7 @@ function openRepostModal({ navigate, channels = [], onSubmit }) {
|
||||
.map((item, index) => {
|
||||
const owner = String(item?.ownerLogin || '').trim();
|
||||
const name = String(item?.channelName || '').trim();
|
||||
const label = `${owner || 'my'} / ${name || 'stories'}`;
|
||||
const label = `${owner || 'my'}/${name || 'channel'}`;
|
||||
return `<option value="${index}">${label}</option>`;
|
||||
})
|
||||
.join('');
|
||||
@ -936,10 +940,12 @@ export function render({ navigate, route }) {
|
||||
return {
|
||||
ownerLogin: String(row?.channel?.ownerLogin || '').trim(),
|
||||
channelName: String(row?.channel?.channelName || '').trim(),
|
||||
channelTypeCode: Number(row?.channel?.channelTypeCode ?? 1),
|
||||
selector: selectorRow,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
.filter(Boolean)
|
||||
.filter((item) => Number(item.channelTypeCode) !== 0 && String(item.channelName || '').trim().toLowerCase() !== 'stories');
|
||||
if (!channels.length) throw new Error('У вас пока нет каналов для репоста.');
|
||||
|
||||
openRepostModal({
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
} from '../services/shine-routes.js';
|
||||
|
||||
export const pageMeta = { id: 'channel-view', title: 'Канал' };
|
||||
const CHANNEL_TYPE_STORIES = 0;
|
||||
const CHANNEL_TYPE_PERSONAL = 100;
|
||||
|
||||
const pendingReactionActions = new Set();
|
||||
@ -226,6 +227,12 @@ function latestVersionText(versions) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function isStoriesChannel(channel = null) {
|
||||
const typeCode = Number(channel?.channelTypeCode ?? channel?.channel?.channelTypeCode ?? 1);
|
||||
const name = String(channel?.channelName || channel?.channel?.channelName || '').trim().toLowerCase();
|
||||
return typeCode === CHANNEL_TYPE_STORIES || name === 'stories';
|
||||
}
|
||||
|
||||
function resolveMessageText(message) {
|
||||
return firstNonEmptyText(
|
||||
message?.text,
|
||||
@ -363,10 +370,11 @@ function openRepostModal({ navigate, channels = [], onSubmit }) {
|
||||
const root = document.getElementById('modal-root');
|
||||
const options = (Array.isArray(channels) ? channels : [])
|
||||
.filter((item) => item?.selector?.ownerBlockchainName && Number.isFinite(Number(item?.selector?.channelRootBlockNumber)))
|
||||
.filter((item) => !isStoriesChannel(item))
|
||||
.map((item, index) => {
|
||||
const owner = String(item?.ownerLogin || '').trim();
|
||||
const name = String(item?.channelName || '').trim();
|
||||
const label = `${owner || 'my'} / ${name || 'stories'}`;
|
||||
const label = `${owner || 'my'}/${name || 'channel'}`;
|
||||
return `<option value="${index}">${label}</option>`;
|
||||
})
|
||||
.join('');
|
||||
@ -451,9 +459,6 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
|
||||
<h3 class="modal-title">Новое сообщение в канале</h3>
|
||||
<p class="meta-muted">${channelName}</p>
|
||||
<textarea id="channel-message-text" class="input" rows="6" maxlength="2000" placeholder="Текст сообщения"></textarea>
|
||||
<div class="row wrap-row">
|
||||
<button class="ghost-btn" id="channel-message-voice" type="button">🎤 Голосом</button>
|
||||
</div>
|
||||
<div class="meta-muted inline-error" id="channel-message-error"></div>
|
||||
<div class="form-actions-grid">
|
||||
<button class="secondary-btn" id="channel-message-cancel" type="button">Отмена</button>
|
||||
@ -480,15 +485,6 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
|
||||
};
|
||||
|
||||
root.querySelector('#channel-message-cancel')?.addEventListener('click', close);
|
||||
root.querySelector('#channel-message-voice')?.addEventListener('click', async () => {
|
||||
await openSpeechInputModal({
|
||||
navigate,
|
||||
onTextReady: (text) => {
|
||||
const prev = String(textEl?.value || '').trim();
|
||||
if (textEl) textEl.value = prev ? `${prev} ${text}` : text;
|
||||
},
|
||||
});
|
||||
});
|
||||
submitEl?.addEventListener('click', async () => {
|
||||
if (inFlight) return;
|
||||
|
||||
@ -735,6 +731,9 @@ async function loadFromApi(route, channelId) {
|
||||
const ownerLogin = String(payload.channel?.ownerLogin || '').trim();
|
||||
const channelName = String(payload.channel?.channelName || '').trim();
|
||||
const channelTypeCode = Number(payload.channel?.channelTypeCode ?? 1);
|
||||
if (channelTypeCode === CHANNEL_TYPE_STORIES) {
|
||||
throw new Error('Канал stories скрыт из пользовательского интерфейса.');
|
||||
}
|
||||
const canResolveReverse = (
|
||||
channelTypeCode === CHANNEL_TYPE_PERSONAL
|
||||
&& !!currentLogin
|
||||
@ -1085,6 +1084,15 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
||||
actionButton.className = 'destructive-btn channel-main-action';
|
||||
actionButton.textContent = 'Подписаться на канал';
|
||||
|
||||
const addMessageButton = document.createElement('button');
|
||||
addMessageButton.type = 'button';
|
||||
addMessageButton.className = 'primary-btn channel-main-action channel-main-action--compose';
|
||||
addMessageButton.textContent = 'Добавить сообщение';
|
||||
addMessageButton.addEventListener('click', (event) => {
|
||||
animatePress(event.currentTarget);
|
||||
handlers.onAddMessage();
|
||||
});
|
||||
|
||||
const feed = document.createElement('div');
|
||||
feed.className = 'stack channel-feed';
|
||||
const postsByKey = new Map();
|
||||
@ -1124,8 +1132,8 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
||||
backButton.addEventListener('click', () => navigate('channels-list'));
|
||||
|
||||
if (channelData.isOwnChannel) {
|
||||
screen.append(feed);
|
||||
} else if (!channelData.isSubscribed) {
|
||||
screen.append(feed, addMessageButton);
|
||||
} else if (!channelData.isSubscribed && !isStoriesChannel(channelData.channel)) {
|
||||
screen.append(actionButton, feed, backButton);
|
||||
} else {
|
||||
screen.append(feed, backButton);
|
||||
@ -1257,10 +1265,12 @@ export function render({ navigate, route }) {
|
||||
return {
|
||||
ownerLogin: String(row?.channel?.ownerLogin || '').trim(),
|
||||
channelName: String(row?.channel?.channelName || '').trim(),
|
||||
channelTypeCode: Number(row?.channel?.channelTypeCode ?? 1),
|
||||
selector,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
.filter(Boolean)
|
||||
.filter((item) => !isStoriesChannel(item));
|
||||
};
|
||||
|
||||
const isSameChannelSelector = (a, b) => (
|
||||
@ -1359,7 +1369,7 @@ export function render({ navigate, route }) {
|
||||
try {
|
||||
const apiData = await loadFromApi(route, channelId);
|
||||
activeSelector = apiData?.selector || null;
|
||||
const channelRouteLabel = `Канал: ${apiData?.channel?.ownerName || 'owner'}/${apiData?.channel?.name || 'channel'}`;
|
||||
const channelRouteLabel = `Канал: ${apiData?.channel?.ownerName || 'owner'} / ${apiData?.channel?.name || 'channel'}`;
|
||||
const ownChannelLabel = `Ваш канал: ${apiData?.channel?.name || 'channel'}`;
|
||||
if (channelHeaderButton) {
|
||||
channelHeaderButton.textContent = apiData?.isOwnChannel ? ownChannelLabel : channelRouteLabel;
|
||||
@ -1369,15 +1379,9 @@ export function render({ navigate, route }) {
|
||||
openAboutChannelModal(apiData.channel);
|
||||
};
|
||||
}
|
||||
if (apiData?.isOwnChannel) {
|
||||
const headerActions = header.querySelector('.header-actions');
|
||||
if (headerActions) {
|
||||
const addBtn = document.createElement('button');
|
||||
addBtn.type = 'button';
|
||||
addBtn.className = 'icon-btn channel-header-add-btn';
|
||||
addBtn.textContent = 'Добавить сообщение';
|
||||
addBtn.addEventListener('click', (event) => {
|
||||
animatePress(event.currentTarget);
|
||||
skeleton.remove();
|
||||
cleanupSeenTracking = renderBody(screen, navigate, routeKey, apiData, {
|
||||
onAddMessage: () => {
|
||||
openAddMessageModal({
|
||||
channelName: apiData?.channel?.name || '',
|
||||
navigate,
|
||||
@ -1390,12 +1394,7 @@ export function render({ navigate, route }) {
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
headerActions.append(addBtn);
|
||||
}
|
||||
}
|
||||
skeleton.remove();
|
||||
cleanupSeenTracking = renderBody(screen, navigate, routeKey, apiData, {
|
||||
},
|
||||
onToggleLike: async (messageRef, action) => {
|
||||
try {
|
||||
await onToggleLike(messageRef, action);
|
||||
|
||||
@ -19,7 +19,6 @@ const CREATE_CHANNEL_FLASH_KEY = 'shine-channels-create-success';
|
||||
const MENU_OVERLAY_ID = 'channels-context-menu-overlay';
|
||||
const CHANNEL_TYPE_STORIES = 0;
|
||||
const CHANNEL_TYPE_PERSONAL = 100;
|
||||
const TAB_ORDER = ['feed', 'my'];
|
||||
|
||||
function isChannelsDemoMode() {
|
||||
try {
|
||||
@ -55,6 +54,20 @@ function buildChannelRouteFromSummary(summary, fallbackId) {
|
||||
});
|
||||
}
|
||||
|
||||
function isStoriesChannel(summaryOrChannel = null) {
|
||||
const typeCode = Number(summaryOrChannel?.channel?.channelTypeCode ?? summaryOrChannel?.channelTypeCode ?? 1);
|
||||
const channelName = String(summaryOrChannel?.channel?.channelName || summaryOrChannel?.channelName || '').trim().toLowerCase();
|
||||
return typeCode === CHANNEL_TYPE_STORIES || channelName === 'stories';
|
||||
}
|
||||
|
||||
function isVisibleChannelSummary(summary) {
|
||||
if (!summary?.channel) return false;
|
||||
if (isStoriesChannel(summary)) return false;
|
||||
const ownerLogin = String(summary.channel.ownerLogin || '').trim();
|
||||
const channelName = String(summary.channel.channelName || '').trim();
|
||||
return !!ownerLogin && !!channelName;
|
||||
}
|
||||
|
||||
function avatarLetterFromName(name = '') {
|
||||
const first = Array.from(String(name || '').trim())[0] || '#';
|
||||
return first.toUpperCase();
|
||||
@ -66,7 +79,7 @@ function allFeedSummaries() {
|
||||
...(feed.ownedChannels || []),
|
||||
...(feed.followedUsersChannels || []),
|
||||
...(feed.followedChannels || []),
|
||||
];
|
||||
].filter(isVisibleChannelSummary);
|
||||
}
|
||||
|
||||
function uniqueBy(items, keySelector) {
|
||||
@ -110,7 +123,7 @@ async function resolveChannelTargetFromInput(rawInput) {
|
||||
}
|
||||
|
||||
const ownerFeed = await authService.listSubscriptionsFeed(ownerLogin, 500);
|
||||
const ownChannels = Array.isArray(ownerFeed?.ownedChannels) ? ownerFeed.ownedChannels : [];
|
||||
const ownChannels = Array.isArray(ownerFeed?.ownedChannels) ? ownerFeed.ownedChannels.filter(isVisibleChannelSummary) : [];
|
||||
const matches = ownChannels.filter((item) => (
|
||||
String(item?.channel?.channelName || '').trim().toLowerCase() === channelName
|
||||
));
|
||||
@ -209,7 +222,9 @@ function isFollowedUserVisible(targetLogin) {
|
||||
const rows = Array.isArray(state.channelsFeed?.followedUsersChannels)
|
||||
? state.channelsFeed.followedUsersChannels
|
||||
: [];
|
||||
return rows.some((row) => normalizeComparableLogin(row?.channel?.ownerLogin) === expected);
|
||||
return rows
|
||||
.filter(isVisibleChannelSummary)
|
||||
.some((row) => normalizeComparableLogin(row?.channel?.ownerLogin) === expected);
|
||||
}
|
||||
|
||||
function isFollowedChannelVisible(target) {
|
||||
@ -221,7 +236,7 @@ function isFollowedChannelVisible(target) {
|
||||
const expectedHash = normalizeHash(target?.rootBlockHash);
|
||||
if (!expectedBch || !Number.isFinite(expectedNo)) return false;
|
||||
|
||||
return rows.some((row) => {
|
||||
return rows.filter(isVisibleChannelSummary).some((row) => {
|
||||
const rowBch = String(row?.channel?.ownerBlockchainName || '');
|
||||
const rowNo = Number(row?.channel?.channelRoot?.blockNumber);
|
||||
const rowHash = normalizeHash(row?.channel?.channelRoot?.blockHash);
|
||||
@ -488,7 +503,7 @@ function openChannelFinderModal({ navigate }) {
|
||||
const ownerLogin = normalizeLoginInput(login);
|
||||
if (!ownerLogin) return;
|
||||
const feed = await authService.listSubscriptionsFeed(ownerLogin, 1000);
|
||||
const rows = Array.isArray(feed?.ownedChannels) ? feed.ownedChannels : [];
|
||||
const rows = Array.isArray(feed?.ownedChannels) ? feed.ownedChannels.filter(isVisibleChannelSummary) : [];
|
||||
const needle = String(filterChannel || '').trim().toLowerCase();
|
||||
const channels = rows
|
||||
.map((item) => ({
|
||||
@ -499,7 +514,7 @@ function openChannelFinderModal({ navigate }) {
|
||||
.filter((item) => !needle || item.channelName.toLowerCase().includes(needle))
|
||||
.slice(0, 200)
|
||||
.map((item) => ({
|
||||
label: `${ownerLogin}/${item.channelName}`,
|
||||
label: `${ownerLogin} / ${item.channelName}`,
|
||||
ownerLogin,
|
||||
ownerBlockchainName: item.ownerBlockchainName,
|
||||
channelName: item.channelName,
|
||||
@ -595,9 +610,6 @@ function mapMockGroups() {
|
||||
ownerBlockchainName: String(channel.ownerName || ''),
|
||||
channelName: String(channel.channelName || channel.title || channel.id),
|
||||
}),
|
||||
tabCategory: channel.kind === 'own' || channel.kind === 'own-personal'
|
||||
? 'my'
|
||||
: 'feed',
|
||||
messagePreview: channel.lastMessage || 'Ждем ваших начинаний',
|
||||
isSubscribed: channel.kind !== 'own' && channel.kind !== 'own-personal',
|
||||
isOwnChannel: channel.kind === 'own' || channel.kind === 'own-personal',
|
||||
@ -612,15 +624,18 @@ function mapMockGroups() {
|
||||
|
||||
const ownChannels = mockChannels
|
||||
.filter((channel) => channel.kind === 'own-personal' || channel.kind === 'own')
|
||||
.map((item) => ({ ...mapRow(item), tabCategory: 'my' }));
|
||||
.map((item) => mapRow(item))
|
||||
.filter((item) => !isStoriesChannel(item));
|
||||
|
||||
const followedUserChannels = mockChannels
|
||||
.filter((channel) => channel.kind === 'followed-user-channel')
|
||||
.map((item) => ({ ...mapRow(item), tabCategory: 'feed' }));
|
||||
.map((item) => mapRow(item))
|
||||
.filter((item) => !isStoriesChannel(item));
|
||||
|
||||
const subscribedChannels = mockChannels
|
||||
.filter((channel) => channel.kind === 'subscribed')
|
||||
.map((item) => ({ ...mapRow(item), tabCategory: 'feed' }));
|
||||
.map((item) => mapRow(item))
|
||||
.filter((item) => !isStoriesChannel(item));
|
||||
|
||||
return { ownChannels, followedUserChannels, subscribedChannels, index: {} };
|
||||
}
|
||||
@ -635,9 +650,7 @@ function mapApiChannelRow(summary, bucketKey, idx, index, notificationsState) {
|
||||
const channelTypeCode = Number(summary?.channel?.channelTypeCode ?? 1);
|
||||
const channelTypeVersion = Number(summary?.channel?.channelTypeVersion ?? 1);
|
||||
const isOwn = bucketKey === 'own';
|
||||
const tabCategory = isOwn ? 'my' : 'feed';
|
||||
|
||||
const title = isOwn ? channelName : `${ownerLogin}/${channelName}`;
|
||||
const title = `${ownerLogin} / ${channelName}`;
|
||||
|
||||
return {
|
||||
id: rowId,
|
||||
@ -656,7 +669,6 @@ function mapApiChannelRow(summary, bucketKey, idx, index, notificationsState) {
|
||||
messagesCount: Number(summary?.messagesCount || 0),
|
||||
unreadCount: Number(summary?.unreadCount || 0),
|
||||
lastMessageAt: Number(summary?.lastMessage?.createdAtMs || 0),
|
||||
tabCategory,
|
||||
isOwnChannel: isOwn,
|
||||
isSubscribed: !isOwn,
|
||||
notificationsEnabled: notificationsState[rowId] === true,
|
||||
@ -677,9 +689,14 @@ function pullCreateSuccessFlash() {
|
||||
function mapApiFeed(feed, notificationsState) {
|
||||
const index = {};
|
||||
const ownChannels = (feed?.ownedChannels || [])
|
||||
.filter(isVisibleChannelSummary)
|
||||
.map((it, idx) => mapApiChannelRow(it, 'own', idx, index, notificationsState));
|
||||
const followedUserChannels = (feed?.followedUsersChannels || []).map((it, idx) => mapApiChannelRow(it, 'followedUsers', idx, index, notificationsState));
|
||||
const subscribedChannels = (feed?.followedChannels || []).map((it, idx) => mapApiChannelRow(it, 'followedChannels', idx, index, notificationsState));
|
||||
const followedUserChannels = (feed?.followedUsersChannels || [])
|
||||
.filter(isVisibleChannelSummary)
|
||||
.map((it, idx) => mapApiChannelRow(it, 'followedUsers', idx, index, notificationsState));
|
||||
const subscribedChannels = (feed?.followedChannels || [])
|
||||
.filter(isVisibleChannelSummary)
|
||||
.map((it, idx) => mapApiChannelRow(it, 'followedChannels', idx, index, notificationsState));
|
||||
|
||||
return { ownChannels, followedUserChannels, subscribedChannels, index };
|
||||
}
|
||||
@ -692,7 +709,7 @@ function toListModel(groups) {
|
||||
];
|
||||
}
|
||||
|
||||
function renderEmptyState(activeTab, navigate) {
|
||||
function renderEmptyState() {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'channels-empty-state channels-empty-state--compact channels-empty-state--silent';
|
||||
if (!state.session.isAuthorized) {
|
||||
@ -700,13 +717,7 @@ function renderEmptyState(activeTab, navigate) {
|
||||
}
|
||||
const text = document.createElement('p');
|
||||
text.className = 'meta-muted';
|
||||
if (activeTab === 'feed') {
|
||||
text.textContent = 'Нет подписок и найденных каналов.';
|
||||
} else if (activeTab === 'my') {
|
||||
text.textContent = 'У вас пока нет каналов.';
|
||||
} else {
|
||||
text.textContent = 'Пусто.';
|
||||
}
|
||||
text.textContent = 'У вас пока нет доступных каналов.';
|
||||
wrap.append(text);
|
||||
|
||||
return wrap;
|
||||
@ -940,36 +951,15 @@ function openChannelMenu({ listState, channel, anchorEl, refreshFeed, rerenderLi
|
||||
};
|
||||
}
|
||||
|
||||
function renderChannelMain(channel, activeTab) {
|
||||
function renderChannelMain(channel) {
|
||||
const main = document.createElement('div');
|
||||
main.className = 'channel-row-main';
|
||||
|
||||
if (activeTab === 'feed') {
|
||||
const author = document.createElement('p');
|
||||
author.className = 'channel-row-author';
|
||||
author.textContent = `@${channel.ownerName}`;
|
||||
|
||||
const title = document.createElement('strong');
|
||||
title.className = 'channel-row-title';
|
||||
title.textContent = channel.channelName ? `#${channel.channelName}` : channel.title;
|
||||
title.textContent = channel.title;
|
||||
|
||||
const preview = document.createElement('p');
|
||||
preview.className = 'channel-row-message';
|
||||
preview.textContent = channel.messagePreview || 'Ждем ваших начинаний';
|
||||
|
||||
const meta = document.createElement('p');
|
||||
meta.className = 'channel-row-owner channel-counter-meta';
|
||||
meta.textContent = `Сообщений: ${channel.messagesCount || 0}`;
|
||||
|
||||
main.append(author, title, preview, meta);
|
||||
return main;
|
||||
}
|
||||
|
||||
const title = document.createElement('strong');
|
||||
title.className = 'channel-row-title';
|
||||
title.textContent = activeTab === 'my' ? channel.channelName : channel.title;
|
||||
|
||||
if (activeTab === 'my' && channel.channelDescription) {
|
||||
if (channel.channelDescription) {
|
||||
const desc = document.createElement('p');
|
||||
desc.className = 'channel-row-description';
|
||||
desc.textContent = channel.channelDescription;
|
||||
@ -993,11 +983,10 @@ function renderListContent({ screen, container, listState, navigate, refreshFeed
|
||||
container.innerHTML = '';
|
||||
|
||||
const allChannels = listState.channels || [];
|
||||
const activeTab = listState.activeTab;
|
||||
const filtered = allChannels.filter((channel) => channel.tabCategory === activeTab);
|
||||
const filtered = allChannels;
|
||||
|
||||
if (!filtered.length) {
|
||||
container.append(renderEmptyState(activeTab, navigate));
|
||||
container.append(renderEmptyState());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1016,7 +1005,7 @@ function renderListContent({ screen, container, listState, navigate, refreshFeed
|
||||
avatar.className = 'avatar';
|
||||
avatar.textContent = channel.avatar;
|
||||
|
||||
const main = renderChannelMain(channel, activeTab);
|
||||
const main = renderChannelMain(channel);
|
||||
|
||||
const isGuest = !state.session.isAuthorized;
|
||||
const controls = document.createElement('div');
|
||||
@ -1078,23 +1067,7 @@ function renderListContent({ screen, container, listState, navigate, refreshFeed
|
||||
}
|
||||
|
||||
function updateBottomCta({ button, listState, navigate, isTabEmpty = false }) {
|
||||
const tab = listState.activeTab;
|
||||
const baseClass = `primary-btn channels-bottom-action${isTabEmpty && tab !== 'my' ? ' is-empty-lift' : ''}`;
|
||||
|
||||
if (tab === 'feed') {
|
||||
button.textContent = 'Найти канал';
|
||||
button.className = baseClass;
|
||||
button.onclick = () => openChannelFinderModal({ navigate });
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab === 'my') {
|
||||
button.textContent = 'Найти канал';
|
||||
button.className = baseClass;
|
||||
button.onclick = () => openChannelFinderModal({ navigate });
|
||||
return;
|
||||
}
|
||||
|
||||
const baseClass = `primary-btn channels-bottom-action${isTabEmpty ? ' is-empty-lift' : ''}`;
|
||||
button.textContent = 'Поиск каналов';
|
||||
button.className = baseClass;
|
||||
button.onclick = () => openChannelFinderModal({ navigate });
|
||||
@ -1155,18 +1128,12 @@ export function render({ navigate, route }) {
|
||||
|
||||
const isGuest = !state.session.isAuthorized;
|
||||
const listState = {
|
||||
activeTab: TAB_ORDER.includes(String(route?.params?.mode || '').trim())
|
||||
? String(route?.params?.mode).trim()
|
||||
: 'feed',
|
||||
openMenuId: null,
|
||||
notificationsState,
|
||||
revealedCounters: new Set(),
|
||||
channels: [],
|
||||
menuCleanup: null,
|
||||
};
|
||||
if (isGuest && listState.activeTab === 'my') {
|
||||
listState.activeTab = 'feed';
|
||||
}
|
||||
|
||||
const contentEl = document.createElement('div');
|
||||
contentEl.className = 'channels-list-content';
|
||||
@ -1187,16 +1154,6 @@ export function render({ navigate, route }) {
|
||||
const topTitle = document.createElement('strong');
|
||||
topTitle.className = 'channels-top-title';
|
||||
|
||||
const myChannelsBtn = document.createElement('button');
|
||||
myChannelsBtn.type = 'button';
|
||||
myChannelsBtn.className = 'secondary-btn channels-top-switch-btn';
|
||||
myChannelsBtn.textContent = 'Мои каналы';
|
||||
myChannelsBtn.addEventListener('click', () => {
|
||||
if (listState.activeTab === 'my') return;
|
||||
listState.activeTab = 'my';
|
||||
rerenderList();
|
||||
});
|
||||
|
||||
const topBarRight = document.createElement('div');
|
||||
topBarRight.className = 'channels-top-right';
|
||||
|
||||
@ -1214,18 +1171,8 @@ export function render({ navigate, route }) {
|
||||
createInMyBtn.setAttribute('aria-label', 'Создать канал');
|
||||
createInMyBtn.addEventListener('click', () => navigate('add-channel-view'));
|
||||
|
||||
const switchToAllBtn = document.createElement('button');
|
||||
switchToAllBtn.type = 'button';
|
||||
switchToAllBtn.className = 'secondary-btn channels-top-switch-btn';
|
||||
switchToAllBtn.textContent = 'Все каналы';
|
||||
switchToAllBtn.addEventListener('click', () => {
|
||||
if (listState.activeTab === 'feed') return;
|
||||
listState.activeTab = 'feed';
|
||||
rerenderList();
|
||||
});
|
||||
|
||||
topBarLeft.append(backBtn, topTitle);
|
||||
topBarRight.append(myChannelsBtn, findChannelBtn, createInMyBtn);
|
||||
topBarRight.append(findChannelBtn, createInMyBtn);
|
||||
topBarEl.append(topBarLeft, topBarRight);
|
||||
|
||||
const bottomCta = document.createElement('button');
|
||||
@ -1235,7 +1182,7 @@ export function render({ navigate, route }) {
|
||||
|
||||
const rerenderList = () => {
|
||||
try {
|
||||
const expectedPath = `/channels-list/${listState.activeTab}`;
|
||||
const expectedPath = '/channels-list';
|
||||
if (window.location.pathname !== expectedPath) {
|
||||
window.history.replaceState({}, '', expectedPath);
|
||||
}
|
||||
@ -1243,7 +1190,7 @@ export function render({ navigate, route }) {
|
||||
// ignore history errors
|
||||
}
|
||||
|
||||
const isTabEmpty = !(listState.channels || []).some((channel) => channel.tabCategory === listState.activeTab);
|
||||
const isTabEmpty = !(listState.channels || []).length;
|
||||
|
||||
closeChannelMenu(listState);
|
||||
|
||||
@ -1255,23 +1202,10 @@ export function render({ navigate, route }) {
|
||||
refreshFeed: reloadFeed,
|
||||
});
|
||||
|
||||
if (listState.activeTab === 'my' && !isGuest) {
|
||||
myChannelsBtn.style.display = 'none';
|
||||
topTitle.textContent = 'Мои каналы';
|
||||
findChannelBtn.style.display = 'none';
|
||||
switchToAllBtn.style.display = '';
|
||||
createInMyBtn.style.display = '';
|
||||
if (!switchToAllBtn.isConnected) topBarLeft.append(switchToAllBtn);
|
||||
if (topTitle.parentElement !== topBarRight) topBarRight.prepend(topTitle);
|
||||
} else {
|
||||
myChannelsBtn.style.display = isGuest ? 'none' : '';
|
||||
topTitle.textContent = 'Все каналы';
|
||||
topTitle.textContent = 'Каналы';
|
||||
findChannelBtn.style.display = '';
|
||||
switchToAllBtn.style.display = 'none';
|
||||
createInMyBtn.style.display = 'none';
|
||||
if (switchToAllBtn.isConnected) switchToAllBtn.remove();
|
||||
createInMyBtn.style.display = '';
|
||||
if (topTitle.parentElement !== topBarLeft) topBarLeft.append(topTitle);
|
||||
}
|
||||
|
||||
updateBottomCta({
|
||||
button: bottomCta,
|
||||
@ -1281,28 +1215,6 @@ export function render({ navigate, route }) {
|
||||
});
|
||||
};
|
||||
|
||||
let touchStartX = 0;
|
||||
let touchStartY = 0;
|
||||
contentEl.addEventListener('touchstart', (event) => {
|
||||
const p = event.changedTouches?.[0];
|
||||
if (!p) return;
|
||||
touchStartX = p.clientX;
|
||||
touchStartY = p.clientY;
|
||||
}, { passive: true });
|
||||
contentEl.addEventListener('touchend', (event) => {
|
||||
const p = event.changedTouches?.[0];
|
||||
if (!p) return;
|
||||
const dx = p.clientX - touchStartX;
|
||||
const dy = p.clientY - touchStartY;
|
||||
if (Math.abs(dx) < 45 || Math.abs(dx) < Math.abs(dy)) return;
|
||||
const index = TAB_ORDER.indexOf(listState.activeTab);
|
||||
if (index < 0) return;
|
||||
if (dx < 0 && index < TAB_ORDER.length - 1) listState.activeTab = TAB_ORDER[index + 1];
|
||||
if (dx > 0 && index > 0) listState.activeTab = TAB_ORDER[index - 1];
|
||||
if (isGuest && listState.activeTab === 'my') listState.activeTab = 'feed';
|
||||
rerenderList();
|
||||
}, { passive: true });
|
||||
|
||||
screen.append(topBarEl, contentEl, bottomCta);
|
||||
|
||||
if (createSuccessFlash) {
|
||||
|
||||
@ -4861,6 +4861,19 @@ html, body { overflow-x: hidden; }
|
||||
margin-top: 6px !important;
|
||||
}
|
||||
|
||||
.channels-screen--channel .channel-main-action--compose {
|
||||
position: sticky;
|
||||
bottom: -12px;
|
||||
margin: 16px 20px 0;
|
||||
width: calc(100% - 40px);
|
||||
background: linear-gradient(135deg, #f5cf4f, #e2ad1f);
|
||||
border: 1px solid rgba(255, 215, 97, 0.85);
|
||||
color: #2f2200;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 14px 28px rgba(226, 173, 31, 0.24);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
background: rgba(18, 24, 38, 0.4);
|
||||
backdrop-filter: blur(25px);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user