feat(thread): переход в тред ответа и явная история сверху

This commit is contained in:
AidarKC 2026-05-19 00:47:12 +03:00
parent 49ebf1605a
commit 580bd6fbeb
4 changed files with 50 additions and 5 deletions

View File

@ -0,0 +1,19 @@
# Навигация по тредам и история сообщения
Статус: `pending`
## Краткое описание
В экране треда добавлен явный переход `🧵 В тред` для каждого сообщения (включая ответы), чтобы можно было углубляться в любую ветку обсуждения.
Также уточнены заголовки блоков: сверху история сообщений, отдельно текущее сообщение.
## Что проверять
1. Открыть любой канал и перейти в тред сообщения.
2. Нажать `🧵 В тред` у одного из ответов.
3. Убедиться, что открывается тред выбранного ответа, а не исходного сообщения.
4. Проверить, что в новом треде сверху показывается блок истории (`История выше...`), затем блок `Текущее сообщение`, затем `Ответы`.
5. Проверить на мобильной ширине, что кнопки действий в карточке не ломают верстку.
## Ожидаемый результат
- Переход в тред ответа работает стабильно для всех узлов дерева.
- Пользователь видит структуру треда в логичном порядке: предки → текущее сообщение → потомки.
- UI остаётся читаемым на мобильных экранах.

View File

@ -1,2 +1,2 @@
client.version=1.2.62 client.version=1.2.63
server.version=1.2.56 server.version=1.2.57

View File

@ -391,7 +391,17 @@ function renderNodeCard(node, heading, handlers, localNumber) {
await handlers.onShare(target); await handlers.onShare(target);
}); });
actions.append(likeButton, replyButton, changedButton, shareButton); const openThreadButton = document.createElement('button');
openThreadButton.type = 'button';
openThreadButton.className = 'secondary-btn thread-open-btn';
openThreadButton.textContent = '🧵 В тред';
openThreadButton.addEventListener('click', (event) => {
event.stopPropagation();
animatePress(event.currentTarget);
handlers.onOpenThread(target);
});
actions.append(likeButton, replyButton, changedButton, shareButton, openThreadButton);
card.append(actions); card.append(actions);
return card; return card;
} }
@ -559,6 +569,14 @@ export function render({ navigate, route }) {
showStatus(toUserMessage(error, 'Не удалось транслировать ссылку.')); showStatus(toUserMessage(error, 'Не удалось транслировать ссылку.'));
} }
}, },
onOpenThread: (target) => {
const routePath = buildThreadRouteFromTarget(target, selector);
if (!routePath) {
showStatus('Не удалось определить путь до треда.');
return;
}
navigate(routePath);
},
onActionError: (error, action) => { onActionError: (error, action) => {
const fallback = action === 'unlike' const fallback = action === 'unlike'
? 'Не удалось убрать лайк.' ? 'Не удалось убрать лайк.'
@ -683,7 +701,7 @@ export function render({ navigate, route }) {
ancestorsWrap.className = 'stack thread-block thread-block--ancestors'; ancestorsWrap.className = 'stack thread-block thread-block--ancestors';
const title = document.createElement('h3'); const title = document.createElement('h3');
title.className = 'section-title'; title.className = 'section-title';
title.textContent = 'Предыдущие сообщения'; title.textContent = 'История выше (на что это ответ)';
ancestorsWrap.append(title); ancestorsWrap.append(title);
ancestors.forEach((node, index) => { ancestors.forEach((node, index) => {
ancestorsWrap.append(renderNodeCard(node, `Предок ${index + 1}`, handlers, nextNumber())); ancestorsWrap.append(renderNodeCard(node, `Предок ${index + 1}`, handlers, nextNumber()));
@ -694,6 +712,10 @@ export function render({ navigate, route }) {
if (focus) { if (focus) {
const focusWrap = document.createElement('div'); const focusWrap = document.createElement('div');
focusWrap.className = 'stack thread-block thread-block--focus'; focusWrap.className = 'stack thread-block thread-block--focus';
const focusTitle = document.createElement('h3');
focusTitle.className = 'section-title';
focusTitle.textContent = 'Текущее сообщение';
focusWrap.append(focusTitle);
focusWrap.append(renderNodeCard(focus, '', handlers, nextNumber())); focusWrap.append(renderNodeCard(focus, '', handlers, nextNumber()));
screen.append(focusWrap); screen.append(focusWrap);
} }

View File

@ -2356,7 +2356,7 @@ textarea.input {
.thread-node-actions { .thread-node-actions {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px; gap: 8px;
} }
@ -2439,6 +2439,10 @@ textarea.input {
color: rgba(255, 255, 255, 0.55); color: rgba(255, 255, 255, 0.55);
} }
.thread-open-btn {
color: rgba(255, 255, 255, 0.62);
}
@media (max-width: 430px) { @media (max-width: 430px) {
.channels-screen .page-title { .channels-screen .page-title {
font-size: 26px; font-size: 26px;