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);