SHA256
Добавить состояние чтения каналов
This commit is contained in:
@@ -234,13 +234,6 @@ function buildThreadRoute(messageRef, selector) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildChannelSettingsKey(ownerBlockchainName, channelName) {
|
||||
const ownerBch = String(ownerBlockchainName || '').trim();
|
||||
const name = String(channelName || '').trim();
|
||||
if (!ownerBch || !name) return '';
|
||||
return `${ownerBch}/${name}`;
|
||||
}
|
||||
|
||||
function getChannelScrollRoot() {
|
||||
return document.getElementById('app-screen');
|
||||
}
|
||||
@@ -286,7 +279,9 @@ function scrollChannelToUnreadLine(screen, unreadCount = 0, smooth = false) {
|
||||
function createChannelReadTracker({
|
||||
screen,
|
||||
routeKey,
|
||||
settingKey,
|
||||
ownerBlockchainName,
|
||||
channelName,
|
||||
initializeIfMissing = false,
|
||||
unreadCount,
|
||||
messagesCount,
|
||||
initialSeenCount,
|
||||
@@ -295,7 +290,9 @@ function createChannelReadTracker({
|
||||
}) {
|
||||
const login = String(state.session.login || '').trim();
|
||||
const storagePwd = state.session.storagePwdInMemory;
|
||||
const canWrite = !!(settingKey && login && storagePwd);
|
||||
const cleanOwnerBlockchainName = String(ownerBlockchainName || '').trim();
|
||||
const cleanChannelName = String(channelName || '').trim();
|
||||
const canWrite = !!(cleanOwnerBlockchainName && cleanChannelName && login && storagePwd);
|
||||
const safeMessagesCount = Math.max(0, Number(messagesCount || 0));
|
||||
const safeInitialSeenCount = Math.max(0, Math.min(Number(initialSeenCount || 0), safeMessagesCount));
|
||||
const unreadLine = unreadCount > 0 ? screen.querySelector('.channel-unread-line') : null;
|
||||
@@ -303,6 +300,7 @@ function createChannelReadTracker({
|
||||
|
||||
let desiredSeenCount = safeInitialSeenCount;
|
||||
let persistedSeenCount = safeInitialSeenCount;
|
||||
let initialPersistPending = !!initializeIfMissing;
|
||||
let inFlight = false;
|
||||
let disposed = false;
|
||||
let rafId = 0;
|
||||
@@ -327,7 +325,7 @@ function createChannelReadTracker({
|
||||
const flush = async () => {
|
||||
if (disposed || !canWrite) return;
|
||||
const next = Math.max(safeInitialSeenCount, Math.min(desiredSeenCount, safeMessagesCount));
|
||||
if (next <= persistedSeenCount) return;
|
||||
if (next <= persistedSeenCount && !initialPersistPending) return;
|
||||
if (inFlight) {
|
||||
queueFlush(120);
|
||||
return;
|
||||
@@ -335,16 +333,16 @@ function createChannelReadTracker({
|
||||
|
||||
inFlight = true;
|
||||
try {
|
||||
await authService.upsertUserSetting({
|
||||
await authService.setChannelReadState({
|
||||
login,
|
||||
settingType: 1,
|
||||
settingKey,
|
||||
ownerBlockchainName: cleanOwnerBlockchainName,
|
||||
channelName: cleanChannelName,
|
||||
readCount: next,
|
||||
timeMs: Date.now(),
|
||||
valueText: '',
|
||||
valueNum: next,
|
||||
storagePwd,
|
||||
});
|
||||
persistedSeenCount = next;
|
||||
initialPersistPending = false;
|
||||
if (typeof onPersistSuccess === 'function') onPersistSuccess(persistedSeenCount);
|
||||
} catch (error) {
|
||||
if (typeof onPersistError === 'function') onPersistError(error);
|
||||
@@ -399,6 +397,8 @@ function createChannelReadTracker({
|
||||
|
||||
// Opening a channel must NOT mark the whole channel as read.
|
||||
// Only cards actually crossed by the viewport tracker advance desiredSeenCount.
|
||||
// Exception: if the server has no row yet, persist the current client baseline once.
|
||||
if (initialPersistPending) queueFlush(80);
|
||||
window.setTimeout(() => measure(), 120);
|
||||
|
||||
const cleanup = () => {
|
||||
@@ -1457,6 +1457,7 @@ async function loadFromApi(route, channelId) {
|
||||
const isAuthorized = !!currentSessionLogin;
|
||||
let unreadCount = 0;
|
||||
let messagesCount = 0;
|
||||
let readStateInitialized = false;
|
||||
let cachedFeed = null;
|
||||
const ensureFeed = async () => {
|
||||
if (cachedFeed) return cachedFeed;
|
||||
@@ -1571,6 +1572,7 @@ async function loadFromApi(route, channelId) {
|
||||
}
|
||||
unreadCount = Number(channel?.unreadCount || 0);
|
||||
messagesCount = Number(channel?.messagesCount || 0);
|
||||
readStateInitialized = !!channel?.readStateInitialized;
|
||||
selector = {
|
||||
ownerBlockchainName: String(channel.channel.ownerBlockchainName),
|
||||
channelRootBlockNumber: Number(channel.channel.channelRoot.blockNumber),
|
||||
@@ -1664,6 +1666,7 @@ async function loadFromApi(route, channelId) {
|
||||
messagesCount,
|
||||
isOwnChannel,
|
||||
isSubscribed,
|
||||
readStateInitialized,
|
||||
selector,
|
||||
};
|
||||
}
|
||||
@@ -1793,12 +1796,12 @@ function renderChannelMetaEventCard(event) {
|
||||
|
||||
function likeCategoryCounts(post) {
|
||||
const total = Math.max(0, Number(post?.likesCount || 0));
|
||||
const primary = Math.max(0, Math.min(total, Number(post?.primaryLikesCount || 0)));
|
||||
const shining = Math.max(0, Math.min(primary, Number(post?.shiningLikesCount || 0)));
|
||||
const official = Math.max(0, Math.min(total, Number(post?.primaryLikesCount || 0)));
|
||||
const shining = Math.max(0, Math.min(official, Number(post?.shiningLikesCount || 0)));
|
||||
return {
|
||||
shining,
|
||||
official: Math.max(0, primary - shining),
|
||||
others: Math.max(0, total - primary),
|
||||
official,
|
||||
all: total,
|
||||
total,
|
||||
};
|
||||
}
|
||||
@@ -1815,7 +1818,7 @@ function openMessageLikesListModal({ navigate, messageRef, initialTab = 'shining
|
||||
<div class="channel-likes-tabs" role="tablist">
|
||||
<button type="button" class="ui-button" data-like-tab="shining">Сияющие <span data-like-tab-count="shining"></span></button>
|
||||
<button type="button" class="ui-button" data-like-tab="official">Официальные <span data-like-tab-count="official"></span></button>
|
||||
<button type="button" class="ui-button" data-like-tab="others">Остальные <span data-like-tab-count="others"></span></button>
|
||||
<button type="button" class="ui-button" data-like-tab="all">Все <span data-like-tab-count="all"></span></button>
|
||||
</div>
|
||||
<div class="channel-likes-modal__status">Загрузка...</div>
|
||||
<div class="channel-likes-user-list"></div>
|
||||
@@ -1831,7 +1834,7 @@ function openMessageLikesListModal({ navigate, messageRef, initialTab = 'shining
|
||||
overlay.addEventListener('click', (event) => { if (event.target === overlay) close(); });
|
||||
modal?.addEventListener('click', (event) => event.stopPropagation());
|
||||
|
||||
let activeTab = ['shining', 'official', 'others'].includes(initialTab) ? initialTab : 'shining';
|
||||
let activeTab = ['shining', 'official', 'all'].includes(initialTab) ? initialTab : 'shining';
|
||||
let payload = null;
|
||||
|
||||
const renderTab = () => {
|
||||
@@ -1841,7 +1844,14 @@ function openMessageLikesListModal({ navigate, messageRef, initialTab = 'shining
|
||||
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||
});
|
||||
if (!payload) return;
|
||||
const rows = Array.isArray(payload?.[activeTab]) ? payload[activeTab] : [];
|
||||
const shiningRows = Array.isArray(payload?.shining) ? payload.shining : [];
|
||||
const officialOnlyRows = Array.isArray(payload?.official) ? payload.official : [];
|
||||
const otherRows = Array.isArray(payload?.others) ? payload.others : [];
|
||||
const rows = activeTab === 'shining'
|
||||
? shiningRows
|
||||
: activeTab === 'official'
|
||||
? [...shiningRows, ...officialOnlyRows]
|
||||
: [...shiningRows, ...officialOnlyRows, ...otherRows];
|
||||
list.innerHTML = '';
|
||||
rows.forEach((row) => {
|
||||
const userButton = document.createElement('button');
|
||||
@@ -1869,7 +1879,7 @@ function openMessageLikesListModal({ navigate, messageRef, initialTab = 'shining
|
||||
});
|
||||
list.append(userButton);
|
||||
});
|
||||
status.textContent = rows.length ? (payload?.truncated ? 'Показаны первые 1000 лайков.' : '') : 'В этом списке пока никого нет.';
|
||||
status.textContent = rows.length ? '' : 'В этом списке пока никого нет.';
|
||||
};
|
||||
|
||||
overlay.querySelectorAll('[data-like-tab]').forEach((button) => {
|
||||
@@ -1882,11 +1892,19 @@ function openMessageLikesListModal({ navigate, messageRef, initialTab = 'shining
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
payload = await authService.getMessageLikes(messageRef, 1000);
|
||||
payload = await authService.getMessageLikes(messageRef);
|
||||
if (!overlay.isConnected) return;
|
||||
['shining', 'official', 'others'].forEach((key) => {
|
||||
const shiningCount = Array.isArray(payload?.shining) ? payload.shining.length : 0;
|
||||
const officialOnlyCount = Array.isArray(payload?.official) ? payload.official.length : 0;
|
||||
const otherCount = Array.isArray(payload?.others) ? payload.others.length : 0;
|
||||
const tabCounts = {
|
||||
shining: shiningCount,
|
||||
official: shiningCount + officialOnlyCount,
|
||||
all: shiningCount + officialOnlyCount + otherCount,
|
||||
};
|
||||
Object.entries(tabCounts).forEach(([key, value]) => {
|
||||
const countEl = overlay.querySelector(`[data-like-tab-count="${key}"]`);
|
||||
if (countEl) countEl.textContent = String(Array.isArray(payload?.[key]) ? payload[key].length : 0);
|
||||
if (countEl) countEl.textContent = String(value);
|
||||
});
|
||||
renderTab();
|
||||
} catch (error) {
|
||||
@@ -1909,25 +1927,16 @@ function openMessageLikePopup({ anchor, post, navigate, onToggleLike }) {
|
||||
<div class="channel-like-popup__counts">
|
||||
<button type="button" class="ui-button channel-like-count" data-like-list="shining"><b>${counts.shining}</b><span>Сияющие</span></button>
|
||||
<button type="button" class="ui-button channel-like-count" data-like-list="official"><b>${counts.official}</b><span>Официальные</span></button>
|
||||
<button type="button" class="ui-button channel-like-count" data-like-list="others"><b>${counts.others}</b><span>Остальные</span></button>
|
||||
<button type="button" class="ui-button channel-like-count" data-like-list="all"><b>${counts.all}</b><span>Все</span></button>
|
||||
</div>
|
||||
<div class="channel-like-popup__total">Всего лайков: <b>${counts.total}</b></div>
|
||||
<button type="button" class="ui-button channel-like-popup__action">${post.reactionState === 'liked' ? 'Убрать свой лайк' : 'Добавить свой лайк'}</button>
|
||||
<button type="button" class="ui-button channel-like-popup__close">Закрыть</button>
|
||||
</section>
|
||||
`;
|
||||
document.body.append(layer);
|
||||
const popup = layer.querySelector('.channel-like-popup');
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
const width = Math.min(330, Math.max(270, window.innerWidth - 24));
|
||||
const left = Math.max(12, Math.min(window.innerWidth - width - 12, rect.left + rect.width / 2 - width / 2));
|
||||
const estimatedHeight = 255;
|
||||
const top = rect.bottom + estimatedHeight < window.innerHeight - 8
|
||||
? rect.bottom + 8
|
||||
: Math.max(8, rect.top - estimatedHeight - 8);
|
||||
popup.style.width = `${width}px`;
|
||||
popup.style.left = `${left}px`;
|
||||
popup.style.top = `${top}px`;
|
||||
|
||||
const close = () => layer.remove();
|
||||
layer.addEventListener('click', (event) => { if (event.target === layer) close(); });
|
||||
@@ -2378,10 +2387,9 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
||||
const tracker = createChannelReadTracker({
|
||||
screen,
|
||||
routeKey,
|
||||
settingKey: buildChannelSettingsKey(
|
||||
channelData.channel?.ownerBlockchainName || channelData.selector?.ownerBlockchainName,
|
||||
channelData.channel?.name || channelData.channel?.channelName,
|
||||
),
|
||||
ownerBlockchainName: channelData.channel?.ownerBlockchainName || channelData.selector?.ownerBlockchainName,
|
||||
channelName: channelData.channel?.name || channelData.channel?.channelName,
|
||||
initializeIfMissing: !!(channelData.isSubscribed && !channelData.readStateInitialized),
|
||||
unreadCount,
|
||||
messagesCount,
|
||||
initialSeenCount: readCount,
|
||||
@@ -2532,18 +2540,13 @@ export function render({ navigate, route, chrome }) {
|
||||
});
|
||||
if (disposed) return;
|
||||
|
||||
const readSettingKey = buildChannelSettingsKey(
|
||||
apiData.channel?.ownerBlockchainName || apiData.selector?.ownerBlockchainName,
|
||||
apiData.channel?.name || apiData.channel?.channelName,
|
||||
);
|
||||
try {
|
||||
await authService.upsertUserSetting({
|
||||
await authService.setChannelReadState({
|
||||
login,
|
||||
settingType: 1,
|
||||
settingKey: readSettingKey,
|
||||
ownerBlockchainName: apiData.channel?.ownerBlockchainName || apiData.selector?.ownerBlockchainName,
|
||||
channelName: apiData.channel?.name || apiData.channel?.channelName,
|
||||
readCount: Math.max(0, Number(apiData.messagesCount || 0)),
|
||||
timeMs: Date.now(),
|
||||
valueText: '',
|
||||
valueNum: Math.max(0, Number(apiData.messagesCount || 0)),
|
||||
storagePwd,
|
||||
});
|
||||
} catch (readStateError) {
|
||||
|
||||
Reference in New Issue
Block a user