Добавил гостевой режим, единые shine-ссылки и пометку о нестабильности мнений

This commit is contained in:
AidarKC
2026-05-20 16:14:59 +03:00
parent aa35d87885
commit 21413268f3
46 changed files with 1125 additions and 310 deletions
+74 -29
View File
@@ -12,11 +12,66 @@ import {
} from '../services/channels-ux.js';
import { openSpeechInputModal } from '../components/speech-input-modal.js';
import { navigateBack } from '../router.js';
import { renderUserAvatar } from '../components/avatar-image.js';
import { loadProfileSnapshot } from '../services/user-profile-params.js';
import { extractLoginFromBlockchainName, makeProfileRoute, makeShineChannelRoute, makeShineMessageRoute } from '../services/shine-routes.js';
export const pageMeta = { id: 'channel-thread-view', title: 'Тред' };
const pendingReactionActions = new Set();
const pendingThreadScroll = new Map();
const threadAvatarSnapshotCache = new Map();
const threadAvatarPendingByLogin = new Map();
async function loadThreadAvatarSnapshot(login) {
const cleanLogin = String(login || '').trim();
if (!cleanLogin) return null;
const key = cleanLogin.toLowerCase();
if (threadAvatarSnapshotCache.has(key)) return threadAvatarSnapshotCache.get(key);
if (threadAvatarPendingByLogin.has(key)) return threadAvatarPendingByLogin.get(key);
const pending = loadProfileSnapshot(cleanLogin)
.then((snapshot) => {
threadAvatarSnapshotCache.set(key, snapshot || null);
threadAvatarPendingByLogin.delete(key);
return snapshot || null;
})
.catch(() => {
threadAvatarSnapshotCache.set(key, null);
threadAvatarPendingByLogin.delete(key);
return null;
});
threadAvatarPendingByLogin.set(key, pending);
return pending;
}
function createThreadAvatar(login) {
const cleanLogin = String(login || '').trim();
const title = cleanLogin ? `Профиль ${cleanLogin}` : '';
const avatarEl = renderUserAvatar({
login: cleanLogin || 'unknown',
size: 'small',
className: 'channel-message-avatar',
title,
});
if (!cleanLogin) return avatarEl;
void loadThreadAvatarSnapshot(cleanLogin).then((snapshot) => {
if (!avatarEl.isConnected) return;
const upgraded = renderUserAvatar({
login: cleanLogin,
avatar: snapshot?.avatar?.txId
? {
ar: String(snapshot.avatar.txId || '').trim(),
sha256Hex: String(snapshot?.avatar?.sha256Hex || '').trim().toLowerCase(),
}
: null,
size: 'small',
className: 'channel-message-avatar',
title,
});
avatarEl.replaceWith(upgraded);
});
return avatarEl;
}
function logThreadRuntimeError(stage, error, context = {}) {
const message = String(error?.message || error || 'thread runtime error');
@@ -55,13 +110,6 @@ function looksLikeBlockchainName(value) {
return /^[^-]+-\d+$/.test(raw);
}
function extractLoginFromBlockchainName(value) {
const raw = String(value || '').trim();
const match = raw.match(/^(.+)-\d+$/);
if (!match) return '';
return String(match[1] || '').trim();
}
function makeReactionActionKey(messageRef) {
const login = String(state.session.login || '').trim().toLowerCase();
const blockchainName = String(messageRef?.blockchainName || '').trim();
@@ -200,32 +248,31 @@ async function resolveChannelDisplayNameFromServer(channelSelector) {
function buildThreadRouteFromTarget(target, selector) {
if (!target) return '';
return [
'm',
encodeRoutePart(target.blockchainName),
target.blockNumber,
].join('/');
const ownerBch = String(selector?.short?.ownerBlockchainName || selector?.channel?.ownerBlockchainName || '').trim();
return makeShineMessageRoute({
ownerLogin: extractLoginFromBlockchainName(ownerBch) || extractLoginFromBlockchainName(target.blockchainName),
messageBlockchainName: target.blockchainName,
messageBlockNumber: target.blockNumber,
});
}
function buildChannelRouteFromThread(selector, resolvedChannelLabel = '') {
const ownerBch = String(selector?.short?.ownerBlockchainName || selector?.channel?.ownerBlockchainName || '').trim();
if (selector?.short?.ownerBlockchainName && selector?.short?.channelName) {
return [
'channel',
encodeRoutePart(selector.short.ownerBlockchainName),
encodeRoutePart(selector.short.channelName),
].join('/');
return makeShineChannelRoute({
ownerLogin: extractLoginFromBlockchainName(ownerBch),
ownerBlockchainName: ownerBch,
channelName: selector.short.channelName,
});
}
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('/');
return makeShineChannelRoute({
ownerLogin: extractLoginFromBlockchainName(ownerBch),
ownerBlockchainName: ownerBch,
channelName,
});
}
function buildTargetFromNode(node) {
@@ -443,9 +490,7 @@ function renderNodeCard(node, heading, handlers, localNumber) {
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 avatar = createThreadAvatar(author);
const authorBlock = document.createElement('div');
authorBlock.className = 'channel-message-author';
@@ -591,7 +636,7 @@ function renderNodeCard(node, heading, handlers, localNumber) {
event.stopPropagation();
const login = String(node?.authorLogin || '').trim();
if (!login) return;
handlers.navigate(`user/${encodeRoutePart(login)}`);
handlers.navigate(makeProfileRoute(login));
});
card.addEventListener('click', () => {
handlers.onOpenThread(target);