SHA256
UI: исправить каналы и добавить MCP-док по чтению/дозаписи
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
import { openSpeechInputModal } from '../components/speech-input-modal.js';
|
||||
|
||||
export const pageMeta = { id: 'channel-view', title: 'Канал' };
|
||||
const CHANNEL_TYPE_PERSONAL = 100;
|
||||
|
||||
const pendingReactionActions = new Set();
|
||||
const pendingScrollByRoute = new Map();
|
||||
@@ -408,18 +409,50 @@ function mapApiMessageToPost(message, selector, localNumber) {
|
||||
}
|
||||
|
||||
async function loadFromApi(route, channelId) {
|
||||
let cachedFeed = null;
|
||||
const ensureFeed = async () => {
|
||||
if (cachedFeed) return cachedFeed;
|
||||
cachedFeed = await authService.listSubscriptionsFeed(state.session.login, 1000);
|
||||
return cachedFeed;
|
||||
};
|
||||
const getAllRows = async () => {
|
||||
const feed = await ensureFeed();
|
||||
return [
|
||||
...(Array.isArray(feed?.ownedChannels) ? feed.ownedChannels : []),
|
||||
...(Array.isArray(feed?.followedUsersChannels) ? feed.followedUsersChannels : []),
|
||||
...(Array.isArray(feed?.followedChannels) ? feed.followedChannels : []),
|
||||
];
|
||||
};
|
||||
|
||||
let selector = buildSelectorFromRoute(route, channelId);
|
||||
if (selector?.ownerBlockchainName && selector?.channelName) {
|
||||
const ownFeed = await authService.listSubscriptionsFeed(state.session.login, 1000);
|
||||
const allRows = [
|
||||
...(Array.isArray(ownFeed?.ownedChannels) ? ownFeed.ownedChannels : []),
|
||||
...(Array.isArray(ownFeed?.followedUsersChannels) ? ownFeed.followedUsersChannels : []),
|
||||
...(Array.isArray(ownFeed?.followedChannels) ? ownFeed.followedChannels : []),
|
||||
];
|
||||
const channel = allRows.find((item) => (
|
||||
String(item?.channel?.ownerBlockchainName || '').trim() === selector.ownerBlockchainName
|
||||
const routeOwnerRaw = String(selector.ownerBlockchainName || '').trim();
|
||||
const routeOwnerNormalized = routeOwnerRaw.toLowerCase();
|
||||
const allRows = await getAllRows();
|
||||
let channel = allRows.find((item) => (
|
||||
String(item?.channel?.ownerBlockchainName || '').trim().toLowerCase() === routeOwnerNormalized
|
||||
&& String(item?.channel?.channelName || '').trim().toLowerCase() === selector.channelName.toLowerCase()
|
||||
));
|
||||
if (!channel) {
|
||||
channel = allRows.find((item) => (
|
||||
String(item?.channel?.ownerLogin || '').trim().toLowerCase() === routeOwnerNormalized
|
||||
&& String(item?.channel?.channelName || '').trim().toLowerCase() === selector.channelName.toLowerCase()
|
||||
));
|
||||
}
|
||||
if (!channel) {
|
||||
try {
|
||||
const ownerUser = await authService.getUser(routeOwnerRaw);
|
||||
const ownerBch = String(ownerUser?.blockchainName || '').trim().toLowerCase();
|
||||
if (ownerBch) {
|
||||
channel = allRows.find((item) => (
|
||||
String(item?.channel?.ownerBlockchainName || '').trim().toLowerCase() === ownerBch
|
||||
&& String(item?.channel?.channelName || '').trim().toLowerCase() === selector.channelName.toLowerCase()
|
||||
));
|
||||
}
|
||||
} catch {
|
||||
// ignore fallback lookup failures
|
||||
}
|
||||
}
|
||||
if (!channel?.channel?.ownerBlockchainName || channel?.channel?.channelRoot?.blockNumber == null) {
|
||||
throw new Error('Канал не найден.');
|
||||
}
|
||||
@@ -437,8 +470,53 @@ async function loadFromApi(route, channelId) {
|
||||
|
||||
const payload = await authService.getChannelMessages(selector, 200, 'asc', state.session.login);
|
||||
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
||||
const posts = messages.map((message, index) => mapApiMessageToPost(message, selector, index + 1));
|
||||
let reverseChannelMissingWarning = '';
|
||||
let mergedMessages = [...messages];
|
||||
|
||||
const currentLogin = String(state.session.login || '').trim();
|
||||
const ownerLogin = String(payload.channel?.ownerLogin || '').trim();
|
||||
const channelName = String(payload.channel?.channelName || '').trim();
|
||||
const channelTypeCode = Number(payload.channel?.channelTypeCode ?? 1);
|
||||
const canResolveReverse = (
|
||||
channelTypeCode === CHANNEL_TYPE_PERSONAL
|
||||
&& !!currentLogin
|
||||
&& !!ownerLogin
|
||||
&& !!channelName
|
||||
&& ownerLogin.toLowerCase() === currentLogin.toLowerCase()
|
||||
);
|
||||
|
||||
if (canResolveReverse) {
|
||||
const allRows = await getAllRows();
|
||||
const reverseSummary = allRows.find((item) => (
|
||||
Number(item?.channel?.channelTypeCode ?? 1) === CHANNEL_TYPE_PERSONAL
|
||||
&& String(item?.channel?.ownerLogin || '').trim().toLowerCase() === channelName.toLowerCase()
|
||||
&& String(item?.channel?.channelName || '').trim().toLowerCase() === currentLogin.toLowerCase()
|
||||
));
|
||||
|
||||
if (reverseSummary?.channel?.ownerBlockchainName && reverseSummary?.channel?.channelRoot?.blockNumber != null) {
|
||||
const reverseSelector = {
|
||||
ownerBlockchainName: String(reverseSummary.channel.ownerBlockchainName),
|
||||
channelRootBlockNumber: Number(reverseSummary.channel.channelRoot.blockNumber),
|
||||
channelRootBlockHash: normalizeRouteHash(reverseSummary.channel.channelRoot.blockHash),
|
||||
};
|
||||
const reversePayload = await authService.getChannelMessages(reverseSelector, 200, 'asc', state.session.login);
|
||||
const reverseMessages = Array.isArray(reversePayload?.messages) ? reversePayload.messages : [];
|
||||
mergedMessages = mergedMessages.concat(reverseMessages);
|
||||
} else {
|
||||
reverseChannelMissingWarning = `У собеседника ${channelName} пока не создан ответный персональный чат.`;
|
||||
}
|
||||
}
|
||||
|
||||
const posts = mergedMessages
|
||||
.map((message, index) => mapApiMessageToPost(message, selector, index + 1))
|
||||
.sort((a, b) => {
|
||||
const byTime = Number(a?.timestampMs || 0) - Number(b?.timestampMs || 0);
|
||||
if (byTime !== 0) return byTime;
|
||||
const aNum = Number(a?.messageRef?.blockNumber || 0);
|
||||
const bNum = Number(b?.messageRef?.blockNumber || 0);
|
||||
return aNum - bNum;
|
||||
})
|
||||
.map((post, index) => ({ ...post, localNumber: index + 1 }));
|
||||
const isOwnChannel = ownerLogin.toLowerCase() === (state.session.login || '').toLowerCase();
|
||||
const followedRows = Array.isArray(state.channelsFeed?.followedChannels) ? state.channelsFeed.followedChannels : [];
|
||||
const isSubscribed = followedRows.some((row) => (
|
||||
@@ -455,6 +533,7 @@ async function loadFromApi(route, channelId) {
|
||||
ownerName: ownerLogin || 'неизвестно',
|
||||
},
|
||||
posts,
|
||||
reverseChannelMissingWarning,
|
||||
isOwnChannel,
|
||||
isSubscribed,
|
||||
selector,
|
||||
@@ -685,6 +764,12 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
||||
|
||||
head.append(title);
|
||||
head.append(owner, headActions);
|
||||
if (channelData.reverseChannelMissingWarning) {
|
||||
const reverseWarning = document.createElement('p');
|
||||
reverseWarning.className = 'channel-head-meta';
|
||||
reverseWarning.textContent = channelData.reverseChannelMissingWarning;
|
||||
head.append(reverseWarning);
|
||||
}
|
||||
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.className = channelData.isOwnChannel
|
||||
@@ -787,6 +872,7 @@ export function render({ navigate, route }) {
|
||||
const next = render({ navigate, route });
|
||||
current.replaceWith(next);
|
||||
};
|
||||
let activeSelector = null;
|
||||
|
||||
const requireSigningSession = () => {
|
||||
const login = state.session.login;
|
||||
@@ -860,14 +946,14 @@ export function render({ navigate, route }) {
|
||||
|
||||
const onAddPost = async (bodyText) => {
|
||||
const { login, storagePwd } = requireSigningSession();
|
||||
if (!routeSelector?.ownerBlockchainName || routeSelector.channelRootBlockNumber == null) {
|
||||
if (!activeSelector?.ownerBlockchainName || activeSelector.channelRootBlockNumber == null) {
|
||||
throw new Error('Идентификатор канала не готов.');
|
||||
}
|
||||
|
||||
await authService.addBlockTextPost({
|
||||
login,
|
||||
storagePwd,
|
||||
channel: routeSelector,
|
||||
channel: activeSelector,
|
||||
text: bodyText,
|
||||
});
|
||||
|
||||
@@ -892,6 +978,7 @@ export function render({ navigate, route }) {
|
||||
(async () => {
|
||||
try {
|
||||
const apiData = await loadFromApi(route, channelId);
|
||||
activeSelector = apiData?.selector || null;
|
||||
skeleton.remove();
|
||||
cleanupSeenTracking = renderBody(screen, navigate, routeKey, apiData, {
|
||||
onToggleLike: async (messageRef, action) => {
|
||||
|
||||
Reference in New Issue
Block a user