SHA256
UI: отправка UI-ошибок, персональный публичный чат, русские pending-файлы
This commit is contained in:
@@ -22,7 +22,6 @@ export const pageMeta = { id: 'channel-view', title: 'Канал' };
|
||||
|
||||
const pendingReactionActions = new Set();
|
||||
const pendingScrollByRoute = new Map();
|
||||
const revealedCountersByRoute = new Map();
|
||||
|
||||
function isChannelsDemoMode() {
|
||||
try {
|
||||
@@ -95,29 +94,6 @@ function blockRefToMessageKey(blockRef, fallbackBch = '') {
|
||||
return `${blockchainName}:${blockNumber}:${blockHash}`;
|
||||
}
|
||||
|
||||
function getRevealedCounterSet(routeKey) {
|
||||
const key = String(routeKey || '').trim();
|
||||
if (!key) return new Set();
|
||||
let bucket = revealedCountersByRoute.get(key);
|
||||
if (!bucket) {
|
||||
bucket = new Set();
|
||||
revealedCountersByRoute.set(key, bucket);
|
||||
}
|
||||
return bucket;
|
||||
}
|
||||
|
||||
function isCounterVisible(routeKey, counterKey) {
|
||||
const key = String(counterKey || '').trim();
|
||||
if (!key) return false;
|
||||
return getRevealedCounterSet(routeKey).has(key);
|
||||
}
|
||||
|
||||
function revealCounter(routeKey, counterKey) {
|
||||
const key = String(counterKey || '').trim();
|
||||
if (!key) return;
|
||||
getRevealedCounterSet(routeKey).add(key);
|
||||
}
|
||||
|
||||
function buildAbsoluteRouteUrl(routePath = '') {
|
||||
const cleanRoute = String(routePath || '').replace(/^#?\/?/, '');
|
||||
const url = new URL(window.location.href);
|
||||
@@ -128,9 +104,9 @@ function buildAbsoluteRouteUrl(routePath = '') {
|
||||
function buildSelectorFromRoute(route, channelId) {
|
||||
const params = route?.params || {};
|
||||
|
||||
if (params.ownerLogin && params.channelName) {
|
||||
if (params.ownerBlockchainName && params.channelName) {
|
||||
return {
|
||||
ownerLogin: String(params.ownerLogin || '').trim(),
|
||||
ownerBlockchainName: String(params.ownerBlockchainName || '').trim(),
|
||||
channelName: String(params.channelName || '').trim(),
|
||||
};
|
||||
}
|
||||
@@ -157,15 +133,14 @@ function buildSelectorFromRoute(route, channelId) {
|
||||
|
||||
function buildThreadRoute(messageRef, selector) {
|
||||
if (!messageRef || !selector) return '';
|
||||
const ownerLogin = String(selector.ownerLogin || '').trim();
|
||||
const ownerBlockchainName = String(selector.ownerBlockchainName || '').trim();
|
||||
const channelName = String(selector.channelName || '').trim();
|
||||
if (ownerLogin && channelName) {
|
||||
if (ownerBlockchainName && channelName) {
|
||||
return [
|
||||
'channel',
|
||||
encodeRoutePart(ownerLogin),
|
||||
encodeRoutePart(ownerBlockchainName),
|
||||
encodeRoutePart(channelName),
|
||||
messageRef.blockNumber,
|
||||
normalizeRouteHash(messageRef.blockHash),
|
||||
].join('/');
|
||||
}
|
||||
return [
|
||||
@@ -434,11 +409,16 @@ function mapApiMessageToPost(message, selector, localNumber) {
|
||||
|
||||
async function loadFromApi(route, channelId) {
|
||||
let selector = buildSelectorFromRoute(route, channelId);
|
||||
if (selector?.ownerLogin && selector?.channelName) {
|
||||
const ownerFeed = await authService.listSubscriptionsFeed(selector.ownerLogin, 1000);
|
||||
const ownChannels = Array.isArray(ownerFeed?.ownedChannels) ? ownerFeed.ownedChannels : [];
|
||||
const channel = ownChannels.find((item) => (
|
||||
String(item?.channel?.channelName || '').trim().toLowerCase() === selector.channelName.toLowerCase()
|
||||
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
|
||||
&& String(item?.channel?.channelName || '').trim().toLowerCase() === selector.channelName.toLowerCase()
|
||||
));
|
||||
if (!channel?.channel?.ownerBlockchainName || channel?.channel?.channelRoot?.blockNumber == null) {
|
||||
throw new Error('Канал не найден.');
|
||||
@@ -447,7 +427,6 @@ async function loadFromApi(route, channelId) {
|
||||
ownerBlockchainName: String(channel.channel.ownerBlockchainName),
|
||||
channelRootBlockNumber: Number(channel.channel.channelRoot.blockNumber),
|
||||
channelRootBlockHash: normalizeRouteHash(channel.channel.channelRoot.blockHash),
|
||||
ownerLogin: selector.ownerLogin,
|
||||
channelName: selector.channelName,
|
||||
};
|
||||
}
|
||||
@@ -550,9 +529,7 @@ function applyPendingScroll(screen, routeKey) {
|
||||
|
||||
function renderPostCard(post, {
|
||||
navigate,
|
||||
routeKey,
|
||||
selector,
|
||||
canWrite,
|
||||
onToggleLike,
|
||||
onReply,
|
||||
onShare,
|
||||
@@ -598,72 +575,57 @@ function renderPostCard(post, {
|
||||
if (refKey) {
|
||||
card.dataset.messageKey = refKey;
|
||||
}
|
||||
const countersVisible = refKey ? isCounterVisible(routeKey, refKey) : true;
|
||||
if (!countersVisible) {
|
||||
card.classList.remove('is-counters-visible');
|
||||
} else {
|
||||
card.classList.add('is-counters-visible');
|
||||
}
|
||||
|
||||
const revealCounters = () => {
|
||||
if (!refKey) return;
|
||||
revealCounter(routeKey, refKey);
|
||||
card.classList.add('is-counters-visible');
|
||||
};
|
||||
card.addEventListener('click', revealCounters);
|
||||
card.classList.add('is-counters-visible');
|
||||
|
||||
if (!post.messageRef || !selector) return card;
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'channel-message-actions';
|
||||
|
||||
if (canWrite) {
|
||||
const actionKey = makeReactionActionKey(post.messageRef);
|
||||
const isPending = actionKey ? pendingReactionActions.has(actionKey) : false;
|
||||
const actionKey = makeReactionActionKey(post.messageRef);
|
||||
const isPending = actionKey ? pendingReactionActions.has(actionKey) : false;
|
||||
|
||||
const likeButton = document.createElement('button');
|
||||
likeButton.type = 'button';
|
||||
likeButton.className = 'channel-action-item channel-action-like';
|
||||
const isLiked = post.reactionState === 'liked';
|
||||
if (isLiked) likeButton.classList.add('is-liked');
|
||||
likeButton.innerHTML = `
|
||||
<span class="channel-action-icon" aria-hidden="true">${isLiked ? '❤️' : '🤍'}</span>
|
||||
<span class="channel-action-label">${isPending ? 'Лайк...' : 'Лайк'}</span>
|
||||
<span class="channel-action-counter">${post.likesCount || 0}</span>
|
||||
`;
|
||||
likeButton.disabled = isPending;
|
||||
likeButton.addEventListener('click', async (event) => {
|
||||
animatePress(event.currentTarget);
|
||||
if (isPending) return;
|
||||
if (!isLiked) {
|
||||
const ok = window.confirm('Поставить лайк?');
|
||||
if (!ok) return;
|
||||
}
|
||||
revealCounters();
|
||||
await longPressFeel(event.currentTarget, 130);
|
||||
likeButton.disabled = true;
|
||||
const labelEl = likeButton.querySelector('.channel-action-label');
|
||||
if (labelEl) labelEl.textContent = 'Лайк...';
|
||||
await onToggleLike(post.messageRef, isLiked ? 'unlike' : 'like', { likeButton });
|
||||
});
|
||||
const likeButton = document.createElement('button');
|
||||
likeButton.type = 'button';
|
||||
likeButton.className = 'channel-action-item channel-action-like';
|
||||
const isLiked = post.reactionState === 'liked';
|
||||
if (isLiked) likeButton.classList.add('is-liked');
|
||||
likeButton.innerHTML = `
|
||||
<span class="channel-action-icon" aria-hidden="true">${isLiked ? '❤️' : '🤍'}</span>
|
||||
<span class="channel-action-label">${isPending ? 'Лайк...' : 'Лайк'}</span>
|
||||
<span class="channel-action-counter">${post.likesCount || 0}</span>
|
||||
`;
|
||||
likeButton.disabled = isPending;
|
||||
likeButton.addEventListener('click', async (event) => {
|
||||
animatePress(event.currentTarget);
|
||||
if (isPending) return;
|
||||
if (!isLiked) {
|
||||
const ok = window.confirm('Поставить лайк?');
|
||||
if (!ok) return;
|
||||
}
|
||||
await longPressFeel(event.currentTarget, 130);
|
||||
likeButton.disabled = true;
|
||||
const labelEl = likeButton.querySelector('.channel-action-label');
|
||||
if (labelEl) labelEl.textContent = 'Лайк...';
|
||||
await onToggleLike(post.messageRef, isLiked ? 'unlike' : 'like', { likeButton });
|
||||
});
|
||||
|
||||
const replyButton = document.createElement('button');
|
||||
replyButton.type = 'button';
|
||||
replyButton.className = 'channel-action-item channel-action-reply';
|
||||
replyButton.innerHTML = `
|
||||
<span class="channel-action-icon" aria-hidden="true">⟳</span>
|
||||
<span class="channel-action-label">Ответить</span>
|
||||
`;
|
||||
replyButton.addEventListener('click', (event) => {
|
||||
animatePress(event.currentTarget);
|
||||
revealCounters();
|
||||
openReplyModal({
|
||||
navigate,
|
||||
onSubmit: async (text) => onReply(post.messageRef, text),
|
||||
});
|
||||
const replyButton = document.createElement('button');
|
||||
replyButton.type = 'button';
|
||||
replyButton.className = 'channel-action-item channel-action-reply';
|
||||
replyButton.innerHTML = `
|
||||
<span class="channel-action-icon" aria-hidden="true">💬</span>
|
||||
<span class="channel-action-label">Ответить</span>
|
||||
<span class="channel-action-counter">${post.repliesCount || 0}</span>
|
||||
`;
|
||||
replyButton.addEventListener('click', (event) => {
|
||||
animatePress(event.currentTarget);
|
||||
openReplyModal({
|
||||
navigate,
|
||||
onSubmit: async (text) => onReply(post.messageRef, text),
|
||||
});
|
||||
actions.append(likeButton, replyButton);
|
||||
}
|
||||
});
|
||||
actions.append(likeButton, replyButton);
|
||||
|
||||
const openThreadButton = document.createElement('button');
|
||||
openThreadButton.type = 'button';
|
||||
@@ -671,11 +633,9 @@ function renderPostCard(post, {
|
||||
openThreadButton.innerHTML = `
|
||||
<span class="channel-action-icon" aria-hidden="true">#</span>
|
||||
<span class="channel-action-label">Тред</span>
|
||||
<span class="channel-action-counter">${post.repliesCount || 0}</span>
|
||||
`;
|
||||
openThreadButton.addEventListener('click', (event) => {
|
||||
animatePress(event.currentTarget);
|
||||
revealCounters();
|
||||
const route = buildThreadRoute(post.messageRef, selector);
|
||||
if (route) navigate(route);
|
||||
});
|
||||
@@ -690,7 +650,6 @@ function renderPostCard(post, {
|
||||
shareButton.addEventListener('click', async (event) => {
|
||||
event.stopPropagation();
|
||||
animatePress(event.currentTarget);
|
||||
revealCounters();
|
||||
const route = buildThreadRoute(post.messageRef, selector);
|
||||
await onShare(route);
|
||||
});
|
||||
@@ -741,9 +700,7 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
||||
channelData.posts.forEach((post) => {
|
||||
const row = renderPostCard(post, {
|
||||
navigate,
|
||||
routeKey,
|
||||
selector: channelData.selector,
|
||||
canWrite: channelData.isOwnChannel,
|
||||
onToggleLike: handlers.onToggleLike,
|
||||
onReply: handlers.onReply,
|
||||
onShare: handlers.onShare,
|
||||
|
||||
Reference in New Issue
Block a user