SHA256
Add channels IT coverage, live UI loading, and runbook
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { renderHeader } from '../components/header.js?v=20260330001044';
|
||||
import { channels } from '../mock-data.js?v=20260330001044';
|
||||
import { channels as mockChannels } from '../mock-data.js?v=20260330001044';
|
||||
import { authService, setChannelsFeed, state } from '../state.js?v=20260330001044';
|
||||
|
||||
export const pageMeta = { id: 'channels-list', title: 'Каналы' };
|
||||
|
||||
@@ -27,6 +28,53 @@ function openSimpleSubscribeModal(kindLabel) {
|
||||
root.querySelector('#sub-submit').addEventListener('click', close);
|
||||
}
|
||||
|
||||
function initialsFromName(name = '') {
|
||||
const parts = name.split(/\s+/).filter(Boolean);
|
||||
return (parts[0]?.[0] || '#') + (parts[1]?.[0] || '');
|
||||
}
|
||||
|
||||
function mapMockGroups() {
|
||||
const ownChannels = mockChannels.filter((channel) => channel.kind === 'own-personal' || channel.kind === 'own');
|
||||
const followedUserChannels = mockChannels.filter((channel) => channel.kind === 'followed-user-channel');
|
||||
const subscribedChannels = mockChannels.filter((channel) => channel.kind === 'subscribed');
|
||||
return { ownChannels, followedUserChannels, subscribedChannels, index: {} };
|
||||
}
|
||||
|
||||
function mapApiChannelRow(summary, bucketKey, idx, index) {
|
||||
const rowId = `${bucketKey}-${idx}`;
|
||||
index[rowId] = summary;
|
||||
|
||||
return {
|
||||
id: rowId,
|
||||
source: 'api',
|
||||
ownerName: summary.channel?.ownerLogin || 'unknown',
|
||||
initials: initialsFromName(summary.channel?.channelName || summary.channel?.ownerLogin || '?'),
|
||||
name: summary.channel?.channelName || '(без имени)',
|
||||
description: `owner=${summary.channel?.ownerLogin || '-'} / bch=${summary.channel?.ownerBlockchainName || '-'}`,
|
||||
lastMessage: summary.lastMessage?.text || 'Сообщений пока нет',
|
||||
time: summary.lastMessage?.createdAtMs ? new Date(summary.lastMessage.createdAtMs).toLocaleString('ru-RU') : '—',
|
||||
messagesCount: summary.messagesCount || 0,
|
||||
};
|
||||
}
|
||||
|
||||
function mapApiFeed(feed) {
|
||||
const index = {};
|
||||
|
||||
const ownChannels = (feed?.ownedChannels || []).map((it, idx) => mapApiChannelRow(it, 'own', idx, index));
|
||||
const followedUserChannels = (feed?.followedUsersChannels || []).map((it, idx) => mapApiChannelRow(it, 'followedUsers', idx, index));
|
||||
const subscribedChannels = (feed?.followedChannels || []).map((it, idx) => mapApiChannelRow(it, 'followedChannels', idx, index));
|
||||
|
||||
ownChannels.sort((a, b) => {
|
||||
const ap = index[a.id]?.channel?.personal === true;
|
||||
const bp = index[b.id]?.channel?.personal === true;
|
||||
if (ap && !bp) return -1;
|
||||
if (!ap && bp) return 1;
|
||||
return a.name.localeCompare(b.name, 'ru');
|
||||
});
|
||||
|
||||
return { ownChannels, followedUserChannels, subscribedChannels, index };
|
||||
}
|
||||
|
||||
function renderChannelRow(channel, navigate) {
|
||||
const row = document.createElement('article');
|
||||
row.className = 'list-item';
|
||||
@@ -57,14 +105,66 @@ function renderSection(title, items, navigate) {
|
||||
header.textContent = title;
|
||||
|
||||
wrap.append(header);
|
||||
|
||||
items.forEach((channel) => {
|
||||
wrap.append(renderChannelRow(channel, navigate));
|
||||
});
|
||||
items.forEach((channel) => wrap.append(renderChannelRow(channel, navigate)));
|
||||
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function renderGroupedList(screen, navigate, groups) {
|
||||
const listWrap = document.createElement('div');
|
||||
listWrap.className = 'channels-scroll-wrap';
|
||||
|
||||
const list = document.createElement('div');
|
||||
list.className = 'stack channels-groups';
|
||||
|
||||
list.append(renderSection('Мои каналы', groups.ownChannels, navigate));
|
||||
|
||||
const dividerOne = document.createElement('hr');
|
||||
dividerOne.className = 'channels-divider';
|
||||
list.append(dividerOne);
|
||||
|
||||
list.append(renderSection('Каналы пользователей, на кого вы подписаны', groups.followedUserChannels, navigate));
|
||||
|
||||
const dividerTwo = document.createElement('hr');
|
||||
dividerTwo.className = 'channels-divider';
|
||||
list.append(dividerTwo);
|
||||
|
||||
list.append(renderSection('Каналы, на которые вы подписаны', groups.subscribedChannels, navigate));
|
||||
|
||||
const addChannelButton = document.createElement('button');
|
||||
addChannelButton.className = 'primary-btn';
|
||||
addChannelButton.textContent = 'Добавить канал';
|
||||
addChannelButton.addEventListener('click', () => navigate('add-channel-view'));
|
||||
|
||||
list.append(addChannelButton);
|
||||
|
||||
const scrollHint = document.createElement('div');
|
||||
scrollHint.className = 'channels-scroll-hint';
|
||||
|
||||
listWrap.append(list, scrollHint);
|
||||
screen.append(listWrap);
|
||||
}
|
||||
|
||||
async function loadFeedAndRender(screen, navigate) {
|
||||
const status = document.createElement('div');
|
||||
status.className = 'card meta-muted';
|
||||
status.textContent = 'Загрузка каналов с сервера...';
|
||||
screen.append(status);
|
||||
|
||||
try {
|
||||
if (!state.session.login) throw new Error('not_authorized');
|
||||
const feed = await authService.listSubscriptionsFeed(state.session.login, 200);
|
||||
const groups = mapApiFeed(feed);
|
||||
setChannelsFeed(feed, groups.index);
|
||||
status.remove();
|
||||
renderGroupedList(screen, navigate, groups);
|
||||
} catch {
|
||||
setChannelsFeed(null, {});
|
||||
status.textContent = 'Сервер недоступен или нет данных. Показаны демо-каналы.';
|
||||
renderGroupedList(screen, navigate, mapMockGroups());
|
||||
}
|
||||
}
|
||||
|
||||
export function render({ navigate }) {
|
||||
const screen = document.createElement('section');
|
||||
screen.className = 'stack';
|
||||
@@ -79,48 +179,6 @@ export function render({ navigate }) {
|
||||
})
|
||||
);
|
||||
|
||||
const ownChannels = channels
|
||||
.filter((channel) => channel.kind === 'own-personal' || channel.kind === 'own')
|
||||
.sort((a, b) => {
|
||||
if (a.kind === 'own-personal') return -1;
|
||||
if (b.kind === 'own-personal') return 1;
|
||||
return a.name.localeCompare(b.name, 'ru');
|
||||
});
|
||||
|
||||
const followedUserChannels = channels.filter((channel) => channel.kind === 'followed-user-channel');
|
||||
const subscribedChannels = channels.filter((channel) => channel.kind === 'subscribed');
|
||||
|
||||
const listWrap = document.createElement('div');
|
||||
listWrap.className = 'channels-scroll-wrap';
|
||||
|
||||
const list = document.createElement('div');
|
||||
list.className = 'stack channels-groups';
|
||||
|
||||
list.append(renderSection('Мои каналы', ownChannels, navigate));
|
||||
|
||||
const dividerOne = document.createElement('hr');
|
||||
dividerOne.className = 'channels-divider';
|
||||
list.append(dividerOne);
|
||||
|
||||
list.append(renderSection('Каналы пользователей, на кого вы подписаны', followedUserChannels, navigate));
|
||||
|
||||
const dividerTwo = document.createElement('hr');
|
||||
dividerTwo.className = 'channels-divider';
|
||||
list.append(dividerTwo);
|
||||
|
||||
list.append(renderSection('Каналы, на которые вы подписаны', subscribedChannels, navigate));
|
||||
|
||||
const addChannelButton = document.createElement('button');
|
||||
addChannelButton.className = 'primary-btn';
|
||||
addChannelButton.textContent = 'Добавить канал';
|
||||
addChannelButton.addEventListener('click', () => navigate('add-channel-view'));
|
||||
|
||||
list.append(addChannelButton);
|
||||
|
||||
const scrollHint = document.createElement('div');
|
||||
scrollHint.className = 'channels-scroll-hint';
|
||||
|
||||
listWrap.append(list, scrollHint);
|
||||
screen.append(listWrap);
|
||||
loadFeedAndRender(screen, navigate);
|
||||
return screen;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user