diff --git a/Dev_Docs/Pending_Features/2026-06-24_1825_unified_channels_list_without_stories.md b/Dev_Docs/Pending_Features/2026-06-24_1825_unified_channels_list_without_stories.md new file mode 100644 index 0000000..7e4eac9 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-06-24_1825_unified_channels_list_without_stories.md @@ -0,0 +1,28 @@ +# Общий список каналов без stories + +- Краткое описание: + вкладка `Каналы` переведена на единый список без разделения на "мои" и "подписки". + Название канала в списке теперь показывается как `login_владельца/название_канала`. + Служебный канал `stories` скрыт из списка каналов, поиска, подписки и связанных UI-сценариев. + +- Что проверять: + 1. Открыть вкладку `Каналы`. + 2. Убедиться, что сразу показывается один общий список. + 3. Проверить, что свои и чужие каналы отображаются вместе. + 4. Проверить формат названий: `ownerLogin/channelName`. + 5. Открыть свой канал и убедиться, что внутри сохраняется UI владельца. + 6. Открыть чужой канал и убедиться, что внутри сохраняется UI подписчика. + 7. Проверить, что `stories` не отображается: + - в общем списке; + - в поиске каналов; + - в подписке на канал; + - в списках выбора канала для репоста. + +- Ожидаемый результат: + - вкладка `Каналы` больше не делится на два режима; + - все видимые каналы идут единым списком; + - `stories` нигде не виден и не предлагается пользователю; + - переход в канал сохраняет корректный UI в зависимости от владельца. + +- Статус: + `pending` diff --git a/VERSION.properties b/VERSION.properties index 893c286..320fbca 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.260 -server.version=1.2.245 +client.version=1.2.261 +server.version=1.2.246 diff --git a/shine-UI/js/components/toolbar.js b/shine-UI/js/components/toolbar.js index ddaed6c..da954c2 100644 --- a/shine-UI/js/components/toolbar.js +++ b/shine-UI/js/components/toolbar.js @@ -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)); } diff --git a/shine-UI/js/pages/channel-thread-view.js b/shine-UI/js/pages/channel-thread-view.js index b610c61..a3aca91 100644 --- a/shine-UI/js/pages/channel-thread-view.js +++ b/shine-UI/js/pages/channel-thread-view.js @@ -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 ``; }) .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({ diff --git a/shine-UI/js/pages/channel-view.js b/shine-UI/js/pages/channel-view.js index faeb42b..cd82608 100644 --- a/shine-UI/js/pages/channel-view.js +++ b/shine-UI/js/pages/channel-view.js @@ -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 ``; }) .join(''); @@ -451,9 +459,6 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {

${channelName}

-
- -
@@ -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,33 +1379,22 @@ 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); - openAddMessageModal({ - channelName: apiData?.channel?.name || '', - navigate, - onSubmit: async (bodyText) => { - try { - await onAddPost(bodyText); - showStatus(''); - } catch (error) { - throw new Error(toUserMessage(error, 'Не удалось добавить сообщение.')); - } - }, - }); - }); - headerActions.append(addBtn); - } - } skeleton.remove(); cleanupSeenTracking = renderBody(screen, navigate, routeKey, apiData, { + onAddMessage: () => { + openAddMessageModal({ + channelName: apiData?.channel?.name || '', + navigate, + onSubmit: async (bodyText) => { + try { + await onAddPost(bodyText); + showStatus(''); + } catch (error) { + throw new Error(toUserMessage(error, 'Не удалось добавить сообщение.')); + } + }, + }); + }, onToggleLike: async (messageRef, action) => { try { await onToggleLike(messageRef, action); diff --git a/shine-UI/js/pages/channels-list.js b/shine-UI/js/pages/channels-list.js index 0121bd6..fdd2776 100644 --- a/shine-UI/js/pages/channels-list.js +++ b/shine-UI/js/pages/channels-list.js @@ -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; - - 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; + title.textContent = 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 = 'Все каналы'; - findChannelBtn.style.display = ''; - switchToAllBtn.style.display = 'none'; - createInMyBtn.style.display = 'none'; - if (switchToAllBtn.isConnected) switchToAllBtn.remove(); - if (topTitle.parentElement !== topBarLeft) topBarLeft.append(topTitle); - } + topTitle.textContent = 'Каналы'; + findChannelBtn.style.display = ''; + 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) { diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 885e618..7b128db 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -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);