Files
SHiNE-server/shine-UI/js/pages/channel-about-view.js
T

183 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { renderHeader } from '../components/header.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('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
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 = renderHeader({
title: 'О канале',
leftAction: {
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>
`;
const footer = document.createElement('div');
footer.className = 'meta-muted screen-footer';
footer.textContent = 'О канале (channel-about-view)';
screen.append(card, footer);
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;
}