feat(ui): короткий роут m для тредов и восстановление заголовка канала
This commit is contained in:
parent
d13c60fca1
commit
3a0899bcfe
@ -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 для открытия треда не воспроизводится.
|
||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.64
|
client.version=1.2.65
|
||||||
server.version=1.2.58
|
server.version=1.2.59
|
||||||
|
|||||||
@ -148,6 +148,54 @@ function resolveChannelDisplayName(channelSelector) {
|
|||||||
return `${found.channel?.ownerLogin || 'неизвестно'}/${found.channel?.channelName || 'канал'}`;
|
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) {
|
function buildBackRoute(selector) {
|
||||||
if (selector?.short?.ownerBlockchainName && selector?.short?.channelName) {
|
if (selector?.short?.ownerBlockchainName && selector?.short?.channelName) {
|
||||||
return [
|
return [
|
||||||
@ -161,25 +209,12 @@ function buildBackRoute(selector) {
|
|||||||
|
|
||||||
function buildThreadRouteFromTarget(target, selector) {
|
function buildThreadRouteFromTarget(target, selector) {
|
||||||
if (!target) return '';
|
if (!target) return '';
|
||||||
const ownerBch = String(selector?.channel?.ownerBlockchainName || '').trim();
|
return [
|
||||||
const rootNo = Number(selector?.channel?.channelRootBlockNumber);
|
'm',
|
||||||
const rootHash = normalizeRouteHash(selector?.channel?.channelRootBlockHash);
|
|
||||||
const base = [
|
|
||||||
'channel-thread-view',
|
|
||||||
encodeRoutePart(target.blockchainName),
|
encodeRoutePart(target.blockchainName),
|
||||||
target.blockNumber,
|
target.blockNumber,
|
||||||
normalizeRouteHash(target.blockHash),
|
normalizeRouteHash(target.blockHash),
|
||||||
];
|
].join('/');
|
||||||
|
|
||||||
if (ownerBch && Number.isFinite(rootNo) && rootNo >= 0) {
|
|
||||||
base.push(
|
|
||||||
encodeRoutePart(ownerBch),
|
|
||||||
String(rootNo),
|
|
||||||
normalizeRouteHash(rootHash),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.join('/');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTargetFromNode(node) {
|
function buildTargetFromNode(node) {
|
||||||
@ -666,21 +701,10 @@ export function render({ navigate, route }) {
|
|||||||
channelRootBlockHash: rootHash,
|
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 = {
|
resolvedMessage = {
|
||||||
blockchainName: ownerBch,
|
blockchainName: ownerBch,
|
||||||
blockNumber: resolvedMessage.blockNumber,
|
blockNumber: resolvedMessage.blockNumber,
|
||||||
blockHash: resolvedHash,
|
blockHash: normalizeMessageHash(resolvedMessage?.blockHash),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,6 +715,29 @@ export function render({ navigate, route }) {
|
|||||||
const focus = payload?.focus || null;
|
const focus = payload?.focus || null;
|
||||||
const descendants = Array.isArray(payload?.descendants) ? payload.descendants : [];
|
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;
|
let seq = 0;
|
||||||
const nextNumber = () => {
|
const nextNumber = () => {
|
||||||
seq += 1;
|
seq += 1;
|
||||||
|
|||||||
@ -146,24 +146,11 @@ function buildSelectorFromRoute(route, channelId) {
|
|||||||
|
|
||||||
function buildThreadRoute(messageRef, selector) {
|
function buildThreadRoute(messageRef, selector) {
|
||||||
if (!messageRef || !selector) return '';
|
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 [
|
return [
|
||||||
'channel-thread-view',
|
'm',
|
||||||
encodeRoutePart(messageRef.blockchainName),
|
encodeRoutePart(messageRef.blockchainName),
|
||||||
messageRef.blockNumber,
|
messageRef.blockNumber,
|
||||||
normalizeRouteHash(messageRef.blockHash),
|
normalizeRouteHash(messageRef.blockHash),
|
||||||
encodeRoutePart(selector.ownerBlockchainName),
|
|
||||||
selector.channelRootBlockNumber,
|
|
||||||
normalizeRouteHash(selector.channelRootBlockHash),
|
|
||||||
].join('/');
|
].join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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') {
|
if (pageId === 'device-session-view') {
|
||||||
return { pageId, params: { sessionId: dynamicId ? decodeURIComponent(dynamicId) : '' } };
|
return { pageId, params: { sessionId: dynamicId ? decodeURIComponent(dynamicId) : '' } };
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user