UI: шапка channel owner/name и унификация карточек треда

This commit is contained in:
AidarKC
2026-05-19 14:22:28 +03:00
parent 3e62a2a01c
commit 1e1cdd9e76
5 changed files with 134 additions and 66 deletions
+62 -29
View File
@@ -314,14 +314,12 @@ function openReplyModal({ onSubmit, navigate }) {
function renderNodeCard(node, heading, handlers, localNumber) {
const card = document.createElement('article');
card.className = 'card stack thread-node-card';
card.className = 'card stack thread-node-card channel-message-card';
const author = node?.authorLogin || 'автор';
const text = resolveNodeText(node) || '(пусто)';
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) {
@@ -331,18 +329,36 @@ function renderNodeCard(node, heading, handlers, localNumber) {
card.append(headingEl);
}
const meta = document.createElement('p');
meta.className = 'thread-node-meta';
meta.innerHTML = `
<span class="author-line-login">${author}</span>
<span class="author-line-num">· #${localNumber}</span>
`;
const authorTile = document.createElement('button');
authorTile.type = 'button';
authorTile.className = 'channel-message-author-tile';
const avatar = document.createElement('div');
avatar.className = 'channel-message-avatar';
avatar.textContent = String(author || 'A').trim().charAt(0).toUpperCase() || 'A';
const authorBlock = document.createElement('div');
authorBlock.className = 'channel-message-author';
const title = document.createElement('div');
title.className = 'channel-message-title author-line';
const loginEl = document.createElement('span');
loginEl.className = 'author-line-login';
loginEl.textContent = author;
const numberEl = document.createElement('span');
numberEl.className = 'author-line-num';
numberEl.textContent = `· #${localNumber}`;
const timestamp = document.createElement('div');
timestamp.className = 'channel-message-time';
timestamp.textContent = node?.createdAtMs ? new Date(node.createdAtMs).toLocaleString() : '—';
title.append(loginEl, numberEl);
authorBlock.append(title, timestamp);
authorTile.append(avatar, authorBlock);
const body = document.createElement('p');
body.className = 'thread-node-body';
body.className = 'channel-message-body';
body.textContent = text;
card.append(meta, body);
card.append(authorTile, body);
const target = buildTargetFromNode(node);
const refKey = messageRefKey(target);
@@ -358,15 +374,20 @@ function renderNodeCard(node, heading, handlers, localNumber) {
const isLiked = getMessageReactionState(target) === 'liked';
const actions = document.createElement('div');
actions.className = 'thread-node-actions';
actions.className = 'thread-node-actions channel-message-actions';
const likeButton = document.createElement('button');
likeButton.type = 'button';
likeButton.className = 'secondary-btn thread-like-btn';
likeButton.className = 'channel-action-item thread-like-btn';
if (isLiked) likeButton.classList.add('is-liked');
likeButton.textContent = isPending ? `❤️ ${likes}...` : `${isLiked ? '❤️' : '🤍'} ${likes}`;
likeButton.innerHTML = `
<span class="channel-action-icon" aria-hidden="true">${isLiked ? '❤️' : '🤍'}</span>
<span class="channel-action-label">${isPending ? 'Лайк...' : 'Лайк'}</span>
<span class="channel-action-counter">${likes}</span>
`;
likeButton.disabled = isPending;
likeButton.addEventListener('click', async (event) => {
event.stopPropagation();
animatePress(event.currentTarget);
if (isPending) return;
if (!isLiked) {
@@ -375,7 +396,6 @@ function renderNodeCard(node, heading, handlers, localNumber) {
}
await longPressFeel(event.currentTarget, 130);
likeButton.disabled = true;
likeButton.textContent = `❤️ ${likes}...`;
try {
await handlers.onToggleLike(target, isLiked ? 'unlike' : 'like');
} catch (error) {
@@ -390,9 +410,14 @@ function renderNodeCard(node, heading, handlers, localNumber) {
const replyButton = document.createElement('button');
replyButton.type = 'button';
replyButton.className = 'secondary-btn thread-reply-btn';
replyButton.textContent = `💬 ${replies}`;
replyButton.className = 'channel-action-item thread-reply-btn';
replyButton.innerHTML = `
<span class="channel-action-icon" aria-hidden="true">💬</span>
<span class="channel-action-label">Ответить</span>
<span class="channel-action-counter">${replies}</span>
`;
replyButton.addEventListener('click', (event) => {
event.stopPropagation();
animatePress(event.currentTarget);
openReplyModal({
navigate: handlers.navigate,
@@ -400,17 +425,13 @@ function renderNodeCard(node, heading, handlers, localNumber) {
});
});
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';
shareButton.textContent = '↗ Отправить';
shareButton.className = 'channel-action-item thread-share-btn';
shareButton.innerHTML = `
<span class="channel-action-icon" aria-hidden="true">↗</span>
<span class="channel-action-label">Отправить</span>
`;
shareButton.addEventListener('click', async (event) => {
event.stopPropagation();
animatePress(event.currentTarget);
@@ -419,16 +440,28 @@ function renderNodeCard(node, heading, handlers, localNumber) {
const openThreadButton = document.createElement('button');
openThreadButton.type = 'button';
openThreadButton.className = 'secondary-btn thread-open-btn';
openThreadButton.textContent = '🧵 В тред';
openThreadButton.className = 'channel-action-item thread-open-btn';
openThreadButton.innerHTML = `
<span class="channel-action-icon" aria-hidden="true">#</span>
<span class="channel-action-label">Тред</span>
`;
openThreadButton.addEventListener('click', (event) => {
event.stopPropagation();
animatePress(event.currentTarget);
handlers.onOpenThread(target);
});
actions.append(likeButton, replyButton, changedButton, shareButton, openThreadButton);
actions.append(likeButton, replyButton, openThreadButton, shareButton);
card.append(actions);
authorTile.addEventListener('click', (event) => {
event.stopPropagation();
const login = String(node?.authorLogin || '').trim();
if (!login) return;
handlers.navigate(`user/${encodeRoutePart(login)}`);
});
card.addEventListener('click', () => {
handlers.onOpenThread(target);
});
return card;
}