From 3a0899bcfe083fd1d2e1956c48e46d4f54eb760f45deb63256801be897d76924 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 19 May 2026 01:05:25 +0300 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=D0=BA=D0=BE=D1=80=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=D0=B9=20=D1=80=D0=BE=D1=83=D1=82=20m=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=82=D1=80=D0=B5=D0=B4=D0=BE=D0=B2=20=D0=B8=20?= =?UTF-8?q?=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...откая-ссылка-на-сообщение-m-блокчейн-номер.md | 19 ++++ VERSION.properties | 4 +- shine-UI/js/pages/channel-thread-view.js | 103 +++++++++++++----- shine-UI/js/pages/channel-view.js | 15 +-- shine-UI/js/router.js | 14 +++ 5 files changed, 111 insertions(+), 44 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-05-19_0112_короткая-ссылка-на-сообщение-m-блокчейн-номер.md diff --git a/Dev_Docs/Pending_Features/2026-05-19_0112_короткая-ссылка-на-сообщение-m-блокчейн-номер.md b/Dev_Docs/Pending_Features/2026-05-19_0112_короткая-ссылка-на-сообщение-m-блокчейн-номер.md new file mode 100644 index 0000000..73de89b --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-05-19_0112_короткая-ссылка-на-сообщение-m-блокчейн-номер.md @@ -0,0 +1,19 @@ +# Короткая ссылка на сообщение `#/m/{blockchainName}/{blockNumber}` + +Статус: `pending` + +## Краткое описание +Добавлен короткий роут сообщения `#/m/{blockchainName}/{blockNumber}` (поддерживает и вариант с hash). +Переходы в тред из канала и из треда теперь формируются через `#/m/...`, а не через длинный путь канала. + +## Что проверять +1. Открыть сообщение в канале и перейти в тред — адрес должен быть формата `#/m/...`. +2. Скопировать ссылку на тред сообщения и открыть в новой вкладке. +3. Для ответа (reply) нажать `🧵 В тред` и убедиться, что тред открывается без ошибок `BAD_FIELDS`/`Не удалось определить hash`. +4. Проверить шапку треда: UI должен попытаться восстановить красивый заголовок канала (`owner/channel`). +5. Проверить, что старый маршрут `#/channel-thread-view/...` тоже продолжает работать. + +## Ожидаемый результат +- Короткий роут работает стабильно для постов и ответов. +- Тред открывается даже если в URL нет hash (опциональный случай). +- Ошибка про невозможность определить hash для открытия треда не воспроизводится. diff --git a/VERSION.properties b/VERSION.properties index 41e009a..773849c 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.64 -server.version=1.2.58 +client.version=1.2.65 +server.version=1.2.59 diff --git a/shine-UI/js/pages/channel-thread-view.js b/shine-UI/js/pages/channel-thread-view.js index 02b8a19..6840bec 100644 --- a/shine-UI/js/pages/channel-thread-view.js +++ b/shine-UI/js/pages/channel-thread-view.js @@ -148,6 +148,54 @@ function resolveChannelDisplayName(channelSelector) { return `${found.channel?.ownerLogin || 'неизвестно'}/${found.channel?.channelName || 'канал'}`; } +function extractChannelContextFromThreadPayload(payload) { + const focusInfo = payload?.focus?.channelInfo; + if (focusInfo?.ownerBlockchainName && focusInfo?.channelRoot?.blockNumber != null) { + return { + ownerBlockchainName: String(focusInfo.ownerBlockchainName || '').trim(), + channelRootBlockNumber: Number(focusInfo.channelRoot.blockNumber), + channelRootBlockHash: '0', + }; + } + + const ancestors = Array.isArray(payload?.ancestors) ? payload.ancestors : []; + for (let i = ancestors.length - 1; i >= 0; i -= 1) { + const info = ancestors[i]?.channelInfo; + if (info?.ownerBlockchainName && info?.channelRoot?.blockNumber != null) { + return { + ownerBlockchainName: String(info.ownerBlockchainName || '').trim(), + channelRootBlockNumber: Number(info.channelRoot.blockNumber), + channelRootBlockHash: '0', + }; + } + } + return null; +} + +async function resolveChannelDisplayNameFromServer(channelSelector) { + const ownerBch = String(channelSelector?.ownerBlockchainName || '').trim(); + const rootNo = Number(channelSelector?.channelRootBlockNumber); + if (!ownerBch || !Number.isFinite(rootNo) || rootNo < 0) return ''; + + const ownerLogin = extractLoginFromBlockchainName(ownerBch); + if (!ownerLogin) return ''; + + try { + const feed = await authService.listSubscriptionsFeed(ownerLogin, 1000); + const rows = Array.isArray(feed?.ownedChannels) ? feed.ownedChannels : []; + const row = rows.find((item) => ( + String(item?.channel?.ownerBlockchainName || '').trim().toLowerCase() === ownerBch.toLowerCase() + && Number(item?.channel?.channelRoot?.blockNumber) === rootNo + )); + if (!row?.channel?.channelName) return ''; + + channelSelector.channelRootBlockHash = normalizeRouteHash(row?.channel?.channelRoot?.blockHash); + return `${row.channel.ownerLogin || ownerLogin}/${row.channel.channelName}`; + } catch { + return ''; + } +} + function buildBackRoute(selector) { if (selector?.short?.ownerBlockchainName && selector?.short?.channelName) { return [ @@ -161,25 +209,12 @@ function buildBackRoute(selector) { function buildThreadRouteFromTarget(target, selector) { if (!target) return ''; - const ownerBch = String(selector?.channel?.ownerBlockchainName || '').trim(); - const rootNo = Number(selector?.channel?.channelRootBlockNumber); - const rootHash = normalizeRouteHash(selector?.channel?.channelRootBlockHash); - const base = [ - 'channel-thread-view', + return [ + 'm', encodeRoutePart(target.blockchainName), target.blockNumber, normalizeRouteHash(target.blockHash), - ]; - - if (ownerBch && Number.isFinite(rootNo) && rootNo >= 0) { - base.push( - encodeRoutePart(ownerBch), - String(rootNo), - normalizeRouteHash(rootHash), - ); - } - - return base.join('/'); + ].join('/'); } function buildTargetFromNode(node) { @@ -666,21 +701,10 @@ export function render({ navigate, route }) { channelRootBlockHash: rootHash, }; - let resolvedHash = normalizeMessageHash(resolvedMessage?.blockHash); - if (!resolvedHash) { - const channelPayload = await authService.getChannelMessages(selector.channel, 400, 'asc', state.session.login); - const messages = Array.isArray(channelPayload?.messages) ? channelPayload.messages : []; - const foundMessage = messages.find((item) => Number(item?.messageRef?.blockNumber) === Number(resolvedMessage.blockNumber)); - const foundHash = normalizeMessageHash(foundMessage?.messageRef?.blockHash); - if (!foundHash) { - throw new Error('Не удалось определить hash сообщения для открытия треда.'); - } - resolvedHash = foundHash; - } resolvedMessage = { blockchainName: ownerBch, blockNumber: resolvedMessage.blockNumber, - blockHash: resolvedHash, + blockHash: normalizeMessageHash(resolvedMessage?.blockHash), }; } @@ -691,6 +715,29 @@ export function render({ navigate, route }) { const focus = payload?.focus || null; const descendants = Array.isArray(payload?.descendants) ? payload.descendants : []; + const focusHash = normalizeMessageHash(focus?.messageRef?.blockHash); + if (focusHash && selector?.message) { + selector.message.blockHash = focusHash; + } + + if ((!selector?.channel?.ownerBlockchainName || selector?.channel?.channelRootBlockNumber == null) && payload) { + const context = extractChannelContextFromThreadPayload(payload); + if (context) { + selector.channel = { + ownerBlockchainName: context.ownerBlockchainName, + channelRootBlockNumber: context.channelRootBlockNumber, + channelRootBlockHash: normalizeRouteHash(context.channelRootBlockHash), + }; + } + } + + let resolvedChannelLabel = resolveChannelDisplayName(selector?.channel); + if (!resolvedChannelLabel && selector?.channel?.ownerBlockchainName && selector?.channel?.channelRootBlockNumber != null) { + resolvedChannelLabel = await resolveChannelDisplayNameFromServer(selector.channel); + } + const fallbackChannel = String(selector?.channel?.ownerBlockchainName || '').trim() || 'неизвестно'; + channelIndicator.textContent = `Канал: ${resolvedChannelLabel || fallbackChannel}`; + let seq = 0; const nextNumber = () => { seq += 1; diff --git a/shine-UI/js/pages/channel-view.js b/shine-UI/js/pages/channel-view.js index b187165..21c7b1e 100644 --- a/shine-UI/js/pages/channel-view.js +++ b/shine-UI/js/pages/channel-view.js @@ -146,24 +146,11 @@ function buildSelectorFromRoute(route, channelId) { function buildThreadRoute(messageRef, selector) { if (!messageRef || !selector) return ''; - const ownerBlockchainName = String(selector.ownerBlockchainName || '').trim(); - const channelName = String(selector.channelName || '').trim(); - if (ownerBlockchainName && channelName) { - return [ - 'channel', - encodeRoutePart(ownerBlockchainName), - encodeRoutePart(channelName), - messageRef.blockNumber, - ].join('/'); - } return [ - 'channel-thread-view', + 'm', encodeRoutePart(messageRef.blockchainName), messageRef.blockNumber, normalizeRouteHash(messageRef.blockHash), - encodeRoutePart(selector.ownerBlockchainName), - selector.channelRootBlockNumber, - normalizeRouteHash(selector.channelRootBlockHash), ].join('/'); } diff --git a/shine-UI/js/router.js b/shine-UI/js/router.js index 9378a0b..d457dcd 100644 --- a/shine-UI/js/router.js +++ b/shine-UI/js/router.js @@ -99,6 +99,20 @@ export function getRoute() { }; } + if (pageId === 'm') { + return { + pageId: 'channel-thread-view', + params: { + messageBlockchainName: decodePart(segments[1]), + messageBlockNumber: segments[2] || '', + messageBlockHash: segments[3] || '', + channelOwnerBlockchainName: '', + channelRootBlockNumber: '', + channelRootBlockHash: '', + }, + }; + } + if (pageId === 'device-session-view') { return { pageId, params: { sessionId: dynamicId ? decodeURIComponent(dynamicId) : '' } }; }