SHA256
179 lines
7.2 KiB
JavaScript
179 lines
7.2 KiB
JavaScript
import { createTopBar } from '../components/topbar.js';
|
||
import { authService, state } from '../state.js';
|
||
import { navigateBack } from '../router.js';
|
||
import { makeShineChannelRootRoute, makeShineChannelShortRoute } from '../services/shine-routes.js';
|
||
import { showToast } from '../services/channels-ux.js';
|
||
import { toUserMessage } from '../services/ui-error-texts.js';
|
||
|
||
export const pageMeta = { id: 'channel-about-view', title: 'О канале' };
|
||
|
||
function escapeHtml(text) {
|
||
return String(text || '')
|
||
.replaceAll('&', '&')
|
||
.replaceAll('<', '<')
|
||
.replaceAll('>', '>')
|
||
.replaceAll('"', '"')
|
||
.replaceAll("'", ''');
|
||
}
|
||
|
||
function normalizeHash(hash) {
|
||
const normalized = String(hash || '').trim().toLowerCase();
|
||
return normalized || '0';
|
||
}
|
||
|
||
function toSafeInt(value) {
|
||
const parsed = Number(value);
|
||
return Number.isFinite(parsed) ? parsed : null;
|
||
}
|
||
|
||
function findCachedChannel(ownerBlockchainName, channelRootBlockNumber, channelRootBlockHash) {
|
||
const ownerBch = String(ownerBlockchainName || '').trim().toLowerCase();
|
||
const rootNo = Number(channelRootBlockNumber);
|
||
const rootHash = normalizeHash(channelRootBlockHash);
|
||
const rows = Object.values(state.channelsIndex || {});
|
||
return rows.find((row) => (
|
||
String(row?.channel?.ownerBlockchainName || '').trim().toLowerCase() === ownerBch
|
||
&& Number(row?.channel?.channelRoot?.blockNumber) === rootNo
|
||
&& normalizeHash(row?.channel?.channelRoot?.blockHash) === rootHash
|
||
)) || null;
|
||
}
|
||
|
||
function buildChannelLink(route) {
|
||
if (!route) return '';
|
||
const url = new URL(window.location.href);
|
||
url.pathname = `/${String(route).replace(/^\/+/, '')}`;
|
||
url.hash = '';
|
||
return url.toString();
|
||
}
|
||
|
||
function statsText(value) {
|
||
return Number.isFinite(Number(value)) ? String(Math.max(0, Number(value))) : '0';
|
||
}
|
||
|
||
export function render({ navigate, route, chrome }) {
|
||
const ownerBlockchainName = String(route?.params?.ownerBlockchainName || '').trim();
|
||
const channelRootBlockNumber = toSafeInt(route?.params?.channelRootBlockNumber);
|
||
const channelRootBlockHash = normalizeHash(route?.params?.channelRootBlockHash);
|
||
const channelRoute = makeShineChannelRootRoute({
|
||
ownerBlockchainName,
|
||
channelRootBlockNumber,
|
||
channelRootBlockHash,
|
||
});
|
||
|
||
const screen = document.createElement('section');
|
||
screen.className = 'stack channels-screen channels-screen--channel-about';
|
||
|
||
const topbar = createTopBar({
|
||
title: 'О канале',
|
||
back: {
|
||
label: '←',
|
||
onClick: () => {
|
||
if (window.history.length > 1) {
|
||
navigateBack();
|
||
return;
|
||
}
|
||
if (channelRoute) navigate(channelRoute);
|
||
},
|
||
ariaLabel: 'Назад',
|
||
title: 'Назад',
|
||
},
|
||
});
|
||
chrome?.setTopbar(topbar);
|
||
|
||
const card = document.createElement('div');
|
||
card.className = 'card stack channel-about-card';
|
||
card.innerHTML = `
|
||
<div class="stack" id="channel-about-content">
|
||
<div class="meta-muted">Загрузка данных канала…</div>
|
||
</div>
|
||
`;
|
||
|
||
screen.append(card);
|
||
|
||
const renderContent = (channel) => {
|
||
const cleanName = String(channel?.displayTitle || channel?.displayName || channel?.channelName || 'Канал').trim();
|
||
const ownerName = String(channel?.ownerLogin || channel?.ownerName || 'автор').trim();
|
||
const description = String(channel?.channelDescription || channel?.description || '').trim();
|
||
const subscribersCount = Number(channel?.subscribersCount || 0);
|
||
const aboutRoute = makeShineChannelRootRoute({
|
||
ownerBlockchainName: channel?.ownerBlockchainName || ownerBlockchainName,
|
||
channelRootBlockNumber: channel?.channelRoot?.blockNumber ?? channelRootBlockNumber,
|
||
channelRootBlockHash: channel?.channelRoot?.blockHash ?? channelRootBlockHash,
|
||
});
|
||
const channelLinkRoute = makeShineChannelShortRoute({
|
||
ownerBlockchainName: channel?.ownerBlockchainName || ownerBlockchainName,
|
||
channelName: channel?.channelName || '',
|
||
});
|
||
const channelLink = buildChannelLink(channelLinkRoute);
|
||
const changedAtMs = Number(channel?.metaUpdatedAtMs || 0);
|
||
const changedAtLabel = changedAtMs ? new Date(changedAtMs).toLocaleString('ru-RU') : '—';
|
||
const avatarState = String(channel?.avaAr || '').trim() ? 'Установлен' : 'Не установлен';
|
||
|
||
const content = card.querySelector('#channel-about-content');
|
||
if (!content) return;
|
||
content.innerHTML = `
|
||
<div class="channel-profile-modal-head">
|
||
<h2 class="modal-title">${escapeHtml(cleanName)}</h2>
|
||
</div>
|
||
<div class="channel-meta-details-grid">
|
||
<span>Дата</span><strong>${escapeHtml(changedAtLabel)}</strong>
|
||
<span>Владелец</span><strong>${escapeHtml(ownerName)}</strong>
|
||
<span>Подписчиков</span><strong>${escapeHtml(statsText(subscribersCount))}</strong>
|
||
<span>Системное имя</span><code>${escapeHtml(String(channel?.channelName || '').trim() || 'channel')}</code>
|
||
<span>Название</span><strong>${escapeHtml(cleanName)}</strong>
|
||
<span>Описание</span><span style="white-space: pre-wrap;">${escapeHtml(description || 'Описание не задано.')}</span>
|
||
<span>Аватар</span><span>${escapeHtml(avatarState)}</span>
|
||
<span>Ссылка</span><span><a href="${escapeHtml(channelLink)}">${escapeHtml(channelLink)}</a></span>
|
||
</div>
|
||
<div class="form-actions-grid">
|
||
<button class="secondary-btn" type="button" id="channel-about-open">Открыть канал</button>
|
||
<button class="secondary-btn" type="button" id="channel-about-copy">Скопировать ссылку</button>
|
||
</div>
|
||
`;
|
||
|
||
content.querySelector('#channel-about-open')?.addEventListener('click', () => {
|
||
if (!channelLinkRoute) return;
|
||
navigate(channelLinkRoute);
|
||
});
|
||
content.querySelector('#channel-about-copy')?.addEventListener('click', async () => {
|
||
if (!channelLink) return;
|
||
try {
|
||
await navigator.clipboard.writeText(channelLink);
|
||
showToast('Ссылка скопирована');
|
||
} catch (error) {
|
||
showToast(toUserMessage(error, 'Не удалось скопировать ссылку'), { kind: 'error' });
|
||
}
|
||
});
|
||
};
|
||
|
||
const cached = findCachedChannel(ownerBlockchainName, channelRootBlockNumber, channelRootBlockHash);
|
||
if (cached?.channel) {
|
||
renderContent({
|
||
...cached.channel,
|
||
subscribersCount: cached.channel.subscribersCount ?? cached.subscribersCount ?? 0,
|
||
});
|
||
return screen;
|
||
}
|
||
|
||
void (async () => {
|
||
try {
|
||
const payload = await authService.getChannelMessages({
|
||
ownerBlockchainName,
|
||
channelRootBlockNumber,
|
||
channelRootBlockHash,
|
||
}, 1, 'asc', String(state.session.login || '').trim());
|
||
renderContent(payload?.channel || {});
|
||
} catch (error) {
|
||
const content = card.querySelector('#channel-about-content');
|
||
if (content) {
|
||
content.innerHTML = `
|
||
<div class="meta-muted">Не удалось загрузить данные канала.</div>
|
||
<div class="meta-muted">${escapeHtml(toUserMessage(error, 'Проверьте подключение к серверу и повторите попытку.'))}</div>
|
||
`;
|
||
}
|
||
}
|
||
})();
|
||
|
||
return screen;
|
||
}
|