UI: упростить профиль и обновить UX чатов/шапок

This commit is contained in:
AidarKC
2026-05-19 15:34:46 +03:00
parent 83892d5093
commit c6d310184b
8 changed files with 414 additions and 131 deletions
+64 -15
View File
@@ -207,6 +207,27 @@ function buildThreadRouteFromTarget(target, selector) {
].join('/');
}
function buildChannelRouteFromThread(selector, resolvedChannelLabel = '') {
if (selector?.short?.ownerBlockchainName && selector?.short?.channelName) {
return [
'channel',
encodeRoutePart(selector.short.ownerBlockchainName),
encodeRoutePart(selector.short.channelName),
].join('/');
}
const ownerBch = String(selector?.channel?.ownerBlockchainName || '').trim();
const label = String(resolvedChannelLabel || '').trim();
const slashIndex = label.indexOf('/');
const channelName = slashIndex >= 0 ? label.slice(slashIndex + 1).trim() : '';
if (!ownerBch || !channelName) return '';
return [
'channel',
encodeRoutePart(ownerBch),
encodeRoutePart(channelName),
].join('/');
}
function buildTargetFromNode(node) {
const blockchainName = String(node?.authorBlockchainName || '').trim();
const blockNumber = Number(node?.messageRef?.blockNumber);
@@ -315,6 +336,7 @@ function openReplyModal({ onSubmit, navigate }) {
function renderNodeCard(node, heading, handlers, localNumber) {
const card = document.createElement('article');
card.className = 'card stack thread-node-card channel-message-card';
card.classList.add('is-counters-visible');
const author = node?.authorLogin || 'автор';
const text = resolveNodeText(node) || '(пусто)';
@@ -532,9 +554,16 @@ export function render({ navigate, route }) {
const appScreen = document.getElementById('app-screen');
appScreen?.classList.add('channels-scroll-clean');
const channelIndicator = document.createElement('div');
channelIndicator.className = 'card channels-user-chip';
channelIndicator.textContent = `Канал: ${channelDisplayName || selector?.channel?.ownerBlockchainName || 'неизвестно'}`;
const header = renderHeader({
title: '',
leftAction: { label: '<', onClick: () => navigateBack() },
rightActions: [{ label: 'Тред в канале: ...', onClick: () => {} }],
});
const threadHeaderButton = header.querySelector('.header-actions .icon-btn');
if (threadHeaderButton) {
threadHeaderButton.classList.add('channel-header-route-btn');
threadHeaderButton.disabled = true;
}
const statusBox = document.createElement('div');
statusBox.className = 'card status-line is-unavailable channels-status';
@@ -643,13 +672,7 @@ export function render({ navigate, route }) {
},
};
screen.append(
renderHeader({
title: 'Тред',
leftAction: { label: '<', onClick: () => navigateBack() },
}),
);
screen.append(channelIndicator, statusBox);
screen.append(header, statusBox);
if (!selector) {
const invalid = document.createElement('div');
@@ -758,7 +781,18 @@ export function render({ navigate, route }) {
resolvedChannelLabel = await resolveChannelDisplayNameFromServer(selector.channel);
}
const fallbackChannel = String(selector?.channel?.ownerBlockchainName || '').trim() || 'неизвестно';
channelIndicator.textContent = `Канал: ${resolvedChannelLabel || fallbackChannel}`;
const resolvedChannelTitle = resolvedChannelLabel || fallbackChannel;
if (threadHeaderButton) {
threadHeaderButton.textContent = `Тред в канале: ${resolvedChannelTitle}`;
threadHeaderButton.disabled = false;
threadHeaderButton.onclick = (event) => {
event.preventDefault();
animatePress(event.currentTarget);
const routeToChannel = buildChannelRouteFromThread(selector, resolvedChannelLabel);
if (routeToChannel) navigate(routeToChannel);
else navigate('channels-list');
};
}
let seq = 0;
const nextNumber = () => {
@@ -766,8 +800,9 @@ export function render({ navigate, route }) {
return seq;
};
let ancestorsWrap = null;
if (ancestors.length) {
const ancestorsWrap = document.createElement('div');
ancestorsWrap = document.createElement('div');
ancestorsWrap.className = 'stack thread-block thread-block--ancestors';
const title = document.createElement('h3');
title.className = 'section-title';
@@ -776,18 +811,17 @@ export function render({ navigate, route }) {
ancestors.forEach((node, index) => {
ancestorsWrap.append(renderNodeCard(node, `Предок ${index + 1}`, handlers, nextNumber()));
});
screen.append(ancestorsWrap);
}
let focusWrap = null;
if (focus) {
const focusWrap = document.createElement('div');
focusWrap = document.createElement('div');
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()));
screen.append(focusWrap);
}
const descendantsWrap = document.createElement('div');
@@ -806,8 +840,23 @@ export function render({ navigate, route }) {
descendantsWrap.append(empty);
}
if (ancestorsWrap) {
screen.append(ancestorsWrap);
const divider = document.createElement('div');
divider.className = 'thread-history-divider';
screen.append(divider);
}
if (focusWrap) screen.append(focusWrap);
screen.append(descendantsWrap);
applyPendingScroll(screen, routeKey);
const hasPendingScroll = pendingThreadScroll.has(routeKey);
if (!hasPendingScroll && focusWrap) {
setTimeout(() => {
focusWrap.scrollIntoView({ behavior: 'auto', block: 'start' });
}, 20);
}
} catch (error) {
skeleton.remove();
const failed = document.createElement('div');