UI: обновить thread/counters, вкладку Каналы и сценарий просмотра+подписки

This commit is contained in:
AidarKC
2026-05-14 12:46:22 +03:00
parent a2954071bd
commit c0b0c99f53
6 changed files with 129 additions and 91 deletions
+20 -60
View File
@@ -16,7 +16,6 @@ export const pageMeta = { id: 'channel-thread-view', title: 'Тред' };
const pendingReactionActions = new Set();
const pendingThreadScroll = new Map();
const revealedCountersByRoute = new Map();
function logThreadRuntimeError(stage, error, context = {}) {
const message = String(error?.message || error || 'thread runtime error');
@@ -67,29 +66,6 @@ function messageRefKey(messageRef) {
return `${blockchainName}:${blockNumber}:${blockHash}`;
}
function getRevealedCounterSet(routeKey) {
const key = String(routeKey || '').trim();
if (!key) return new Set();
let bucket = revealedCountersByRoute.get(key);
if (!bucket) {
bucket = new Set();
revealedCountersByRoute.set(key, bucket);
}
return bucket;
}
function isCounterVisible(routeKey, counterKey) {
const key = String(counterKey || '').trim();
if (!key) return false;
return getRevealedCounterSet(routeKey).has(key);
}
function revealCounter(routeKey, counterKey) {
const key = String(counterKey || '').trim();
if (!key) return;
getRevealedCounterSet(routeKey).add(key);
}
function buildAbsoluteRouteUrl(routePath = '') {
const cleanRoute = String(routePath || '').replace(/^#?\/?/, '');
const url = new URL(window.location.href);
@@ -299,7 +275,7 @@ function openReplyModal({ onSubmit, navigate }) {
if (textEl) textEl.focus();
}
function renderNodeCard(node, heading, handlers, localNumber, routeKey, options = {}) {
function renderNodeCard(node, heading, handlers, localNumber) {
const card = document.createElement('article');
card.className = 'card stack thread-node-card';
@@ -308,6 +284,7 @@ function renderNodeCard(node, heading, handlers, localNumber, routeKey, options
const likes = Number(node?.likesCount || 0);
const replies = Number(node?.repliesCount || 0);
const versions = Number(node?.versionsTotal || 1);
const changes = Math.max(0, versions - 1);
const headingText = String(heading || '').trim();
if (headingText) {
@@ -328,31 +305,10 @@ function renderNodeCard(node, heading, handlers, localNumber, routeKey, options
body.className = 'thread-node-body';
body.textContent = text;
const stats = document.createElement('p');
stats.className = 'thread-node-stats';
stats.textContent = `Лайки: ${likes}, ответы: ${replies}, версий: ${versions}`;
card.append(meta, body, stats);
card.append(meta, body);
const target = buildTargetFromNode(node);
const refKey = messageRefKey(target);
const countersVisible = refKey ? isCounterVisible(routeKey, refKey) : true;
if (!countersVisible) {
card.classList.remove('is-counters-visible');
stats.classList.add('is-hidden');
} else {
card.classList.add('is-counters-visible');
stats.classList.remove('is-hidden');
}
const revealCounters = () => {
if (!refKey) return;
revealCounter(routeKey, refKey);
card.classList.add('is-counters-visible');
stats.classList.remove('is-hidden');
};
card.addEventListener('click', revealCounters);
if (!target || !handlers) return card;
if (refKey) card.dataset.messageKey = refKey;
@@ -371,7 +327,7 @@ 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 ? '❤️ Лайк...' : (isLiked ? '❤️ Лайк' : '🤍 Лайк');
likeButton.textContent = isPending ? `❤️ ${likes}...` : `${isLiked ? '❤️' : '🤍'} ${likes}`;
likeButton.disabled = isPending;
likeButton.addEventListener('click', async (event) => {
animatePress(event.currentTarget);
@@ -380,10 +336,9 @@ function renderNodeCard(node, heading, handlers, localNumber, routeKey, options
const ok = window.confirm('Поставить лайк?');
if (!ok) return;
}
revealCounters();
await longPressFeel(event.currentTarget, 130);
likeButton.disabled = true;
likeButton.textContent = 'Лайк...';
likeButton.textContent = `❤️ ${likes}...`;
try {
await handlers.onToggleLike(target, isLiked ? 'unlike' : 'like');
} catch (error) {
@@ -399,16 +354,22 @@ 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 = `💬 ${replies}`;
replyButton.addEventListener('click', (event) => {
animatePress(event.currentTarget);
revealCounters();
openReplyModal({
navigate: handlers.navigate,
onSubmit: async (textValue) => handlers.onReply(target, textValue),
});
});
const changedButton = document.createElement('button');
changedButton.type = 'button';
changedButton.className = 'secondary-btn thread-version-btn';
changedButton.textContent = `✏️ ${changes}`;
changedButton.disabled = true;
changedButton.style.display = changes > 0 ? '' : 'none';
const shareButton = document.createElement('button');
shareButton.type = 'button';
shareButton.className = 'secondary-btn thread-share-btn';
@@ -416,16 +377,15 @@ function renderNodeCard(node, heading, handlers, localNumber, routeKey, options
shareButton.addEventListener('click', async (event) => {
event.stopPropagation();
animatePress(event.currentTarget);
revealCounters();
await handlers.onShare(target);
});
actions.append(likeButton, replyButton, shareButton);
actions.append(likeButton, replyButton, changedButton, shareButton);
card.append(actions);
return card;
}
function renderDescendants(items, handlers, nextNumber, routeKey, depth = 0) {
function renderDescendants(items, handlers, nextNumber, depth = 0) {
const wrap = document.createElement('div');
wrap.className = 'stack';
@@ -433,13 +393,13 @@ function renderDescendants(items, handlers, nextNumber, routeKey, depth = 0) {
normalized.forEach((branch, index) => {
try {
const nodeNumber = nextNumber();
const row = renderNodeCard(branch?.node, `Ответ ${index + 1}`, handlers, nodeNumber, routeKey);
const row = renderNodeCard(branch?.node, `Ответ ${index + 1}`, handlers, nodeNumber);
row.classList.add('thread-node-level');
row.style.setProperty('--depth', String(Math.min(depth, 4)));
wrap.append(row);
if (Array.isArray(branch?.children) && branch.children.length) {
wrap.append(renderDescendants(branch.children, handlers, nextNumber, routeKey, depth + 1));
wrap.append(renderDescendants(branch.children, handlers, nextNumber, depth + 1));
}
} catch (error) {
logThreadRuntimeError('render_descendants_branch', error, { depth, index });
@@ -655,7 +615,7 @@ export function render({ navigate, route }) {
title.textContent = 'Предыдущие сообщения';
ancestorsWrap.append(title);
ancestors.forEach((node, index) => {
ancestorsWrap.append(renderNodeCard(node, `Предок ${index + 1}`, handlers, nextNumber(), routeKey));
ancestorsWrap.append(renderNodeCard(node, `Предок ${index + 1}`, handlers, nextNumber()));
});
screen.append(ancestorsWrap);
}
@@ -663,7 +623,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: false }));
focusWrap.append(renderNodeCard(focus, '', handlers, nextNumber()));
screen.append(focusWrap);
}
@@ -675,7 +635,7 @@ export function render({ navigate, route }) {
descendantsWrap.append(descendantsTitle);
if (descendants.length) {
descendantsWrap.append(renderDescendants(descendants, handlers, nextNumber, routeKey));
descendantsWrap.append(renderDescendants(descendants, handlers, nextNumber));
} else {
const empty = document.createElement('div');
empty.className = 'card meta-muted';