mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
perf(replies): prefer cached board preview replies (#1101)
* perf(replies): prefer cached board preview replies * fix(replies): address review feedback
This commit is contained in:
@@ -326,6 +326,8 @@ vi.mock('../../lib/utils/replies-preview-utils', () => ({
|
|||||||
filterRepliesForDisplay: (replies: TestComment[]) => replies,
|
filterRepliesForDisplay: (replies: TestComment[]) => replies,
|
||||||
getPreviewDisplayReplies: (replies: TestComment[]) => replies,
|
getPreviewDisplayReplies: (replies: TestComment[]) => replies,
|
||||||
getTotalReplyCount: ({ replyCount }: { replyCount?: number }) => replyCount ?? 0,
|
getTotalReplyCount: ({ replyCount }: { replyCount?: number }) => replyCount ?? 0,
|
||||||
|
hasEnoughPreviewReplies: ({ replyCount, loadedCount, visibleCount }: { replyCount?: number; loadedCount: number; visibleCount: number }) =>
|
||||||
|
loadedCount >= Math.min(visibleCount, replyCount ?? visibleCount),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../lib/utils/thread-scroll-utils', () => ({
|
vi.mock('../../lib/utils/thread-scroll-utils', () => ({
|
||||||
|
|||||||
@@ -49,7 +49,13 @@ import useQuotedByMap from '../../hooks/use-quoted-by-map';
|
|||||||
import useProgressiveRender from '../../hooks/use-progressive-render';
|
import useProgressiveRender from '../../hooks/use-progressive-render';
|
||||||
import useFreshReplies from '../../hooks/use-fresh-replies';
|
import useFreshReplies from '../../hooks/use-fresh-replies';
|
||||||
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
||||||
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount } from '../../lib/utils/replies-preview-utils';
|
import {
|
||||||
|
computeOmittedCount,
|
||||||
|
filterRepliesForDisplay,
|
||||||
|
getPreviewDisplayReplies,
|
||||||
|
getTotalReplyCount,
|
||||||
|
hasEnoughPreviewReplies,
|
||||||
|
} from '../../lib/utils/replies-preview-utils';
|
||||||
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
||||||
import { getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
import { getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
||||||
import useDeleteFailedPost from '../../hooks/use-delete-failed-post';
|
import useDeleteFailedPost from '../../hooks/use-delete-failed-post';
|
||||||
@@ -821,11 +827,27 @@ const PostDesktop = ({
|
|||||||
|
|
||||||
const { showOmittedReplies, setShowOmittedReplies } = useShowOmittedReplies();
|
const { showOmittedReplies, setShowOmittedReplies } = useShowOmittedReplies();
|
||||||
|
|
||||||
const shouldFetchPreview = showReplies && !isModQueue && !showAllReplies && showOmittedReplies[cid] !== true;
|
const shouldUsePreview = showReplies && !isModQueue && !showAllReplies;
|
||||||
const shouldFetchFull = showReplies && !isModQueue && (showAllReplies || showOmittedReplies[cid]);
|
const shouldFetchFull = showReplies && !isModQueue && (showAllReplies || showOmittedReplies[cid]);
|
||||||
|
|
||||||
|
const cachedPreviewRepliesResult = useReplies({
|
||||||
|
comment: shouldUsePreview ? resolvedPost : undefined,
|
||||||
|
onlyIfCached: true,
|
||||||
|
sortType: 'new',
|
||||||
|
flat: true,
|
||||||
|
repliesPerPage: BOARD_REPLIES_PREVIEW_FETCH_SIZE,
|
||||||
|
accountComments: { newerThan: Infinity, append: true },
|
||||||
|
});
|
||||||
|
const cachedPreviewReplies = (cachedPreviewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
||||||
|
? (cachedPreviewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
||||||
|
: cachedPreviewRepliesResult.replies || [];
|
||||||
|
const hasEnoughCachedPreview = hasEnoughPreviewReplies({
|
||||||
|
replyCount: resolvedPost?.replyCount,
|
||||||
|
loadedCount: cachedPreviewReplies.length,
|
||||||
|
visibleCount: BOARD_REPLIES_PREVIEW_VISIBLE_COUNT,
|
||||||
|
});
|
||||||
const previewRepliesResult = useReplies({
|
const previewRepliesResult = useReplies({
|
||||||
comment: shouldFetchPreview ? resolvedPost : undefined,
|
comment: shouldUsePreview && !showOmittedReplies[cid] && !hasEnoughCachedPreview ? resolvedPost : undefined,
|
||||||
sortType: 'new',
|
sortType: 'new',
|
||||||
flat: true,
|
flat: true,
|
||||||
repliesPerPage: BOARD_REPLIES_PREVIEW_FETCH_SIZE,
|
repliesPerPage: BOARD_REPLIES_PREVIEW_FETCH_SIZE,
|
||||||
@@ -839,9 +861,10 @@ const PostDesktop = ({
|
|||||||
accountComments: { newerThan: Infinity, append: true },
|
accountComments: { newerThan: Infinity, append: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const previewReplies = (previewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
const livePreviewReplies = (previewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
||||||
? (previewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
? (previewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
||||||
: previewRepliesResult.replies || [];
|
: previewRepliesResult.replies || [];
|
||||||
|
const previewReplies = hasEnoughCachedPreview ? cachedPreviewReplies : livePreviewReplies;
|
||||||
const fullReplies = (fullRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
const fullReplies = (fullRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
||||||
? (fullRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
? (fullRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
||||||
: fullRepliesResult.replies || [];
|
: fullRepliesResult.replies || [];
|
||||||
@@ -875,7 +898,7 @@ const PostDesktop = ({
|
|||||||
const totalReplyCount = getTotalReplyCount({
|
const totalReplyCount = getTotalReplyCount({
|
||||||
replyCount: resolvedPost?.replyCount,
|
replyCount: resolvedPost?.replyCount,
|
||||||
fullLoadedCount: fullReplies.length,
|
fullLoadedCount: fullReplies.length,
|
||||||
previewLoadedCount: previewReplies.length,
|
previewLoadedCount: Math.max(cachedPreviewReplies.length, livePreviewReplies.length),
|
||||||
});
|
});
|
||||||
const repliesCount = computeOmittedCount({
|
const repliesCount = computeOmittedCount({
|
||||||
totalReplyCount,
|
totalReplyCount,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import useProgressiveRender from '../../hooks/use-progressive-render';
|
|||||||
import useFreshReplies from '../../hooks/use-fresh-replies';
|
import useFreshReplies from '../../hooks/use-fresh-replies';
|
||||||
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
||||||
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
||||||
import { filterRepliesForDisplay, getPreviewDisplayReplies } from '../../lib/utils/replies-preview-utils';
|
import { filterRepliesForDisplay, getPreviewDisplayReplies, hasEnoughPreviewReplies } from '../../lib/utils/replies-preview-utils';
|
||||||
import { getRenderableMobileBacklinks } from '../../lib/utils/reply-backlink-utils';
|
import { getRenderableMobileBacklinks } from '../../lib/utils/reply-backlink-utils';
|
||||||
import { getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
import { getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
||||||
import useDeleteFailedPost from '../../hooks/use-delete-failed-post';
|
import useDeleteFailedPost from '../../hooks/use-delete-failed-post';
|
||||||
@@ -564,8 +564,26 @@ const PostMobile = ({
|
|||||||
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
|
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
|
||||||
const linksCount = useCountLinksInReplies(resolvedPost);
|
const linksCount = useCountLinksInReplies(resolvedPost);
|
||||||
const shouldFetchReplies = showReplies && !isModQueue;
|
const shouldFetchReplies = showReplies && !isModQueue;
|
||||||
|
const shouldUsePreview = shouldFetchReplies && !showAllReplies;
|
||||||
|
const cachedPreviewRepliesResult = useReplies({
|
||||||
|
comment: shouldUsePreview ? resolvedPost : undefined,
|
||||||
|
onlyIfCached: true,
|
||||||
|
sortType: 'new',
|
||||||
|
flat: true,
|
||||||
|
repliesPerPage: BOARD_REPLIES_PREVIEW_FETCH_SIZE,
|
||||||
|
accountComments: { newerThan: Infinity, append: true },
|
||||||
|
});
|
||||||
|
const cachedPreviewReplies = (cachedPreviewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
||||||
|
? (cachedPreviewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
||||||
|
: cachedPreviewRepliesResult.replies || [];
|
||||||
|
const cachedPreviewDisplayCount = filterRepliesForDisplay(cachedPreviewReplies).length;
|
||||||
|
const hasEnoughCachedPreview = hasEnoughPreviewReplies({
|
||||||
|
replyCount: resolvedPost?.replyCount,
|
||||||
|
loadedCount: cachedPreviewDisplayCount,
|
||||||
|
visibleCount: BOARD_REPLIES_PREVIEW_VISIBLE_COUNT,
|
||||||
|
});
|
||||||
const previewRepliesResult = useReplies({
|
const previewRepliesResult = useReplies({
|
||||||
comment: shouldFetchReplies && !showAllReplies ? resolvedPost : undefined,
|
comment: shouldUsePreview && !hasEnoughCachedPreview ? resolvedPost : undefined,
|
||||||
sortType: 'new',
|
sortType: 'new',
|
||||||
flat: true,
|
flat: true,
|
||||||
repliesPerPage: BOARD_REPLIES_PREVIEW_FETCH_SIZE,
|
repliesPerPage: BOARD_REPLIES_PREVIEW_FETCH_SIZE,
|
||||||
@@ -578,7 +596,11 @@ const PostMobile = ({
|
|||||||
repliesPerPage: REPLIES_PER_PAGE,
|
repliesPerPage: REPLIES_PER_PAGE,
|
||||||
accountComments: { newerThan: Infinity, append: true },
|
accountComments: { newerThan: Infinity, append: true },
|
||||||
});
|
});
|
||||||
const repliesResult = showAllReplies ? fullRepliesResult : previewRepliesResult;
|
const livePreviewReplies = (previewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
||||||
|
? (previewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
||||||
|
: previewRepliesResult.replies || [];
|
||||||
|
const previewReplies = hasEnoughCachedPreview ? cachedPreviewReplies : livePreviewReplies;
|
||||||
|
const repliesResult = showAllReplies ? fullRepliesResult : { ...previewRepliesResult, replies: previewReplies, updatedReplies: previewReplies };
|
||||||
const { replies, hasMore, loadMore } = repliesResult;
|
const { replies, hasMore, loadMore } = repliesResult;
|
||||||
const updatedReplies = (repliesResult as { updatedReplies?: Comment[] }).updatedReplies;
|
const updatedReplies = (repliesResult as { updatedReplies?: Comment[] }).updatedReplies;
|
||||||
const repliesForRender = updatedReplies?.length ? updatedReplies : replies || [];
|
const repliesForRender = updatedReplies?.length ? updatedReplies : replies || [];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||||||
import { copyToClipboard } from '../clipboard-utils';
|
import { copyToClipboard } from '../clipboard-utils';
|
||||||
import { hashStringToColor, getTextColorForBackground, removeMarkdown } from '../post-utils';
|
import { hashStringToColor, getTextColorForBackground, removeMarkdown } from '../post-utils';
|
||||||
import { preloadReplyModal, preloadThemeAssets, resolveAssetUrl } from '../preload-utils';
|
import { preloadReplyModal, preloadThemeAssets, resolveAssetUrl } from '../preload-utils';
|
||||||
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount } from '../replies-preview-utils';
|
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount, hasEnoughPreviewReplies } from '../replies-preview-utils';
|
||||||
import { getQuotedCidsFromContent, mergeQuotedCids } from '../reply-quote-utils';
|
import { getQuotedCidsFromContent, mergeQuotedCids } from '../reply-quote-utils';
|
||||||
import { formatUserIDForDisplay, truncateWithEllipsisInMiddle } from '../string-utils';
|
import { formatUserIDForDisplay, truncateWithEllipsisInMiddle } from '../string-utils';
|
||||||
import { getFormattedDate, getFormattedTimeAgo, isChristmas } from '../time-utils';
|
import { getFormattedDate, getFormattedTimeAgo, isChristmas } from '../time-utils';
|
||||||
@@ -186,6 +186,10 @@ describe('misc utils', () => {
|
|||||||
|
|
||||||
expect(computeOmittedCount({ totalReplyCount: 2, visibleCount: 5 })).toBe(0);
|
expect(computeOmittedCount({ totalReplyCount: 2, visibleCount: 5 })).toBe(0);
|
||||||
expect(computeOmittedCount({ totalReplyCount: 9, visibleCount: 5 })).toBe(4);
|
expect(computeOmittedCount({ totalReplyCount: 9, visibleCount: 5 })).toBe(4);
|
||||||
|
expect(hasEnoughPreviewReplies({ replyCount: 2, loadedCount: 2, visibleCount: 5 })).toBe(true);
|
||||||
|
expect(hasEnoughPreviewReplies({ replyCount: 9, loadedCount: 4, visibleCount: 5 })).toBe(false);
|
||||||
|
expect(hasEnoughPreviewReplies({ replyCount: undefined, loadedCount: 5, visibleCount: 5 })).toBe(true);
|
||||||
|
expect(hasEnoughPreviewReplies({ replyCount: undefined, loadedCount: 4, visibleCount: 5 })).toBe(false);
|
||||||
expect(getTotalReplyCount({ replyCount: undefined, fullLoadedCount: 7, previewLoadedCount: 5 })).toBe(7);
|
expect(getTotalReplyCount({ replyCount: undefined, fullLoadedCount: 7, previewLoadedCount: 5 })).toBe(7);
|
||||||
expect(getTotalReplyCount({ replyCount: 12, fullLoadedCount: 7, previewLoadedCount: 5 })).toBe(12);
|
expect(getTotalReplyCount({ replyCount: 12, fullLoadedCount: 7, previewLoadedCount: 5 })).toBe(12);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,6 +54,22 @@ export function computeOmittedCount({ totalReplyCount, visibleCount }: ComputeOm
|
|||||||
return Math.max(0, totalReplyCount - visibleCount);
|
return Math.max(0, totalReplyCount - visibleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HasEnoughPreviewRepliesParams {
|
||||||
|
replyCount: number | undefined;
|
||||||
|
loadedCount: number;
|
||||||
|
visibleCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Board previews can skip a live fetch when cached replies already cover all
|
||||||
|
* replies that could be shown. If total reply count is unknown, require a full
|
||||||
|
* visible slice so UX matches the current live-preview behavior.
|
||||||
|
*/
|
||||||
|
export function hasEnoughPreviewReplies({ replyCount, loadedCount, visibleCount }: HasEnoughPreviewRepliesParams): boolean {
|
||||||
|
const requiredCount = typeof replyCount === 'number' && replyCount >= 0 ? Math.min(visibleCount, replyCount) : visibleCount;
|
||||||
|
return requiredCount === 0 || loadedCount >= requiredCount;
|
||||||
|
}
|
||||||
|
|
||||||
interface GetTotalReplyCountParams {
|
interface GetTotalReplyCountParams {
|
||||||
replyCount: number | undefined;
|
replyCount: number | undefined;
|
||||||
fullLoadedCount: number;
|
fullLoadedCount: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user