Каналы: новый роутинг, поиск, вход-возврат, удаление просмотров и документация

This commit is contained in:
AidarKC
2026-05-08 01:15:54 +03:00
parent 6774c26ea1
commit acdd6c928b
18 changed files with 657 additions and 458 deletions
+63 -20
View File
@@ -98,6 +98,24 @@ function buildAbsoluteRouteUrl(routePath = '') {
function parseThreadSelector(route) {
const params = route?.params || {};
if (params.ownerLogin && params.channelName && params.messageBlockNumber && params.messageBlockHash) {
return {
short: {
ownerLogin: String(params.ownerLogin || '').trim(),
channelName: String(params.channelName || '').trim(),
},
message: {
blockchainName: '',
blockNumber: toSafeInt(params.messageBlockNumber),
blockHash: normalizeRouteHash(params.messageBlockHash),
},
channel: {
ownerBlockchainName: '',
rootBlockNumber: null,
rootBlockHash: '0',
},
};
}
const blockNumber = toSafeInt(params.messageBlockNumber);
if (!params.messageBlockchainName || blockNumber == null) return null;
@@ -153,7 +171,17 @@ function buildBackRoute(selector) {
}
function buildThreadRouteFromTarget(target, selector) {
if (!target || !selector?.channel?.ownerBlockchainName || selector.channel.rootBlockNumber == null) return '';
if (!target) return '';
if (selector?.short?.ownerLogin && selector?.short?.channelName) {
return [
'channel',
encodeRoutePart(selector.short.ownerLogin),
encodeRoutePart(selector.short.channelName),
target.blockNumber,
normalizeRouteHash(target.blockHash),
].join('/');
}
if (!selector?.channel?.ownerBlockchainName || selector.channel.rootBlockNumber == null) return '';
return [
'channel-thread-view',
encodeRoutePart(target.blockchainName),
@@ -293,13 +321,6 @@ function renderNodeCard(node, heading, handlers, localNumber, routeKey, options
card.append(meta, body, stats);
if (options.showViews === true) {
const views = document.createElement('p');
views.className = 'thread-node-views';
views.textContent = `Просмотры: ${Number(node?.viewCount || 0)}`;
card.append(views);
}
const target = buildTargetFromNode(node);
const refKey = messageRefKey(target);
const countersVisible = refKey ? isCounterVisible(routeKey, refKey) : true;
@@ -337,15 +358,19 @@ function renderNodeCard(node, heading, handlers, localNumber, routeKey, options
likeButton.type = 'button';
likeButton.className = 'secondary-btn thread-like-btn';
if (isLiked) likeButton.classList.add('is-liked');
likeButton.textContent = isPending ? '✦ Сияние...' : '✦ Сияние';
likeButton.textContent = isPending ? '❤️ Лайк...' : (isLiked ? '❤️ Лайк' : '🤍 Лайк');
likeButton.disabled = isPending;
likeButton.addEventListener('click', async (event) => {
animatePress(event.currentTarget);
if (isPending) return;
if (!isLiked) {
const ok = window.confirm('Поставить лайк?');
if (!ok) return;
}
revealCounters();
await longPressFeel(event.currentTarget, 130);
likeButton.disabled = true;
likeButton.textContent = 'Сияние...';
likeButton.textContent = 'Лайк...';
try {
await handlers.onToggleLike(target, isLiked ? 'unlike' : 'like');
} catch (error) {
@@ -361,7 +386,7 @@ function renderNodeCard(node, heading, handlers, localNumber, routeKey, options
const replyButton = document.createElement('button');
replyButton.type = 'button';
replyButton.className = 'secondary-btn thread-reply-btn';
replyButton.textContent = ' Отразить';
replyButton.textContent = '💬 Ответить';
replyButton.addEventListener('click', (event) => {
animatePress(event.currentTarget);
revealCounters();
@@ -373,7 +398,7 @@ function renderNodeCard(node, heading, handlers, localNumber, routeKey, options
const shareButton = document.createElement('button');
shareButton.type = 'button';
shareButton.className = 'secondary-btn thread-share-btn';
shareButton.textContent = '↗ Транслировать';
shareButton.textContent = '↗ Отправить';
shareButton.addEventListener('click', async (event) => {
event.stopPropagation();
animatePress(event.currentTarget);
@@ -454,10 +479,6 @@ export function render({ navigate, route }) {
const appScreen = document.getElementById('app-screen');
appScreen?.classList.add('channels-scroll-clean');
const userIndicator = document.createElement('div');
userIndicator.className = 'card channels-user-chip';
userIndicator.textContent = `Вы вошли как @${state.session.login || 'неизвестно'}`;
const channelIndicator = document.createElement('div');
channelIndicator.className = 'card channels-user-chip';
channelIndicator.textContent = `Канал: ${channelDisplayName || selector?.channel?.ownerBlockchainName || 'неизвестно'}`;
@@ -490,7 +511,11 @@ export function render({ navigate, route }) {
const requireSigningSession = () => {
const login = state.session.login;
const storagePwd = state.session.storagePwdInMemory;
if (!login || !storagePwd) throw new Error('Сессия недействительна. Выполните вход заново.');
if (!login || !storagePwd) {
state.authReturnHash = window.location.hash || '#/channels-list';
navigate('login-view');
throw new Error('Для этого действия нужно войти');
}
return { login, storagePwd };
};
@@ -562,7 +587,7 @@ export function render({ navigate, route }) {
leftAction: { label: '<', onClick: () => navigate(backRoute) },
}),
);
screen.append(userIndicator, channelIndicator, statusBox);
screen.append(channelIndicator, statusBox);
if (!selector) {
const invalid = document.createElement('div');
@@ -576,7 +601,25 @@ export function render({ navigate, route }) {
(async () => {
try {
const payload = await authService.getMessageThread(selector.message, 20, 2, 50, state.session.login);
let resolvedMessage = selector.message;
if (selector.short?.ownerLogin && selector.short?.channelName) {
const ownerFeed = await authService.listSubscriptionsFeed(selector.short.ownerLogin, 1000);
const ownChannels = Array.isArray(ownerFeed?.ownedChannels) ? ownerFeed.ownedChannels : [];
const channel = ownChannels.find((item) => (
String(item?.channel?.channelName || '').trim().toLowerCase() === selector.short.channelName.toLowerCase()
));
const ownerBch = String(channel?.channel?.ownerBlockchainName || '').trim();
if (!ownerBch || !Number.isFinite(resolvedMessage?.blockNumber)) {
throw new Error('Канал или сообщение не найдено.');
}
resolvedMessage = {
blockchainName: ownerBch,
blockNumber: resolvedMessage.blockNumber,
blockHash: normalizeRouteHash(resolvedMessage.blockHash),
};
}
const payload = await authService.getMessageThread(resolvedMessage, 20, 2, 50, state.session.login);
skeleton.remove();
const ancestors = Array.isArray(payload?.ancestors) ? payload.ancestors : [];
@@ -605,7 +648,7 @@ export function render({ navigate, route }) {
if (focus) {
const focusWrap = document.createElement('div');
focusWrap.className = 'stack thread-block thread-block--focus';
focusWrap.append(renderNodeCard(focus, '', handlers, nextNumber(), routeKey, { showViews: true }));
focusWrap.append(renderNodeCard(focus, '', handlers, nextNumber(), routeKey, { showViews: false }));
screen.append(focusWrap);
}