fix(replies): order approved queue replies

This commit is contained in:
Tommaso Casaburi
2026-04-27 15:12:52 +07:00
parent 5dc5408a15
commit 463509e105
4 changed files with 168 additions and 14 deletions
+35 -1
View File
@@ -2,7 +2,14 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { copyToClipboard } from '../clipboard-utils';
import { hashStringToColor, getTextColorForBackground, removeMarkdown } from '../post-utils';
import { preloadReplyModal, preloadThemeAssets, resolveAssetUrl } from '../preload-utils';
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount, hasEnoughPreviewReplies } from '../replies-preview-utils';
import {
computeOmittedCount,
filterRepliesForDisplay,
getPreviewDisplayReplies,
getTotalReplyCount,
hasEnoughPreviewReplies,
sortRepliesForDisplay,
} from '../replies-preview-utils';
import { getQuotedCidsFromContent, mergeQuotedCids } from '../reply-quote-utils';
import { formatUserIDForDisplay, truncateWithEllipsisInMiddle } from '../string-utils';
import { getFormattedDate, getFormattedTimeAgo, isChristmas } from '../time-utils';
@@ -184,6 +191,33 @@ describe('misc utils', () => {
{ cid: 'pending', pendingApproval: true },
]);
expect(
sortRepliesForDisplay([
{ cid: 'reply-8', number: 8 },
{ cid: 'reply-12', number: 12 },
{ cid: 'reply-11', number: 11 },
]),
).toEqual([
{ cid: 'reply-8', number: 8 },
{ cid: 'reply-11', number: 11 },
{ cid: 'reply-12', number: 12 },
]);
expect(
getPreviewDisplayReplies(
[
{ cid: 'reply-8', number: 8, timestamp: 8 },
{ cid: 'reply-12', number: 12, timestamp: 12 },
{ cid: 'reply-11', number: 11, timestamp: 99 },
],
3,
),
).toEqual([
{ cid: 'reply-8', number: 8, timestamp: 8 },
{ cid: 'reply-11', number: 11, timestamp: 99 },
{ cid: 'reply-12', number: 12, timestamp: 12 },
]);
expect(computeOmittedCount({ totalReplyCount: 2, visibleCount: 5 })).toBe(0);
expect(computeOmittedCount({ totalReplyCount: 9, visibleCount: 5 })).toBe(4);
expect(hasEnoughPreviewReplies({ replyCount: 2, loadedCount: 2, visibleCount: 5 })).toBe(true);
+63 -10
View File
@@ -4,11 +4,45 @@ interface CommentLike {
cid?: string | null;
deleted?: boolean;
index?: number;
number?: number;
pendingApproval?: boolean;
state?: string;
timestamp?: number;
}
const getReplyNumber = (reply: CommentLike): number | undefined => (typeof reply?.number === 'number' && Number.isFinite(reply.number) ? reply.number : undefined);
export function sortRepliesForDisplay<T extends CommentLike>(replies: T[]): T[] {
if (replies.length < 2) {
return replies;
}
const taggedReplies = replies.map((reply, index) => ({
index,
number: getReplyNumber(reply),
reply,
}));
taggedReplies.sort((a, b) => {
if (a.number !== undefined && b.number !== undefined) {
return a.number === b.number ? a.index - b.index : a.number - b.number;
}
if (a.number !== undefined) {
return -1;
}
if (b.number !== undefined) {
return 1;
}
return a.index - b.index;
});
const sortedReplies = taggedReplies.map((taggedReply) => taggedReply.reply);
return sortedReplies.every((reply, index) => reply === replies[index]) ? replies : sortedReplies;
}
export function filterRepliesForDisplay<T extends CommentLike>(replies: T[]): T[] {
return replies.filter((reply) => !reply.deleted);
}
@@ -21,23 +55,42 @@ export function filterRepliesForDisplay<T extends CommentLike>(replies: T[]): T[
* board previews.
*/
export function getPreviewDisplayReplies<T extends CommentLike>(replies: T[], visibleCount: number = BOARD_REPLIES_PREVIEW_VISIBLE_COUNT): T[] {
const getRecency = (reply: T): number => {
if (typeof reply?.timestamp === 'number') {
return reply.timestamp;
const getRecency = (reply: T): { group: number; value: number } => {
const number = getReplyNumber(reply);
if (number !== undefined) {
return { group: 2, value: number };
}
// Pending/local account replies can be missing timestamp early on.
if (typeof reply?.index === 'number' || reply?.pendingApproval || (reply?.state && reply.state !== 'succeeded')) {
return Number.POSITIVE_INFINITY;
return { group: 3, value: 0 };
}
return Number.NEGATIVE_INFINITY;
if (typeof reply?.timestamp === 'number') {
return { group: 1, value: reply.timestamp };
}
return { group: 0, value: 0 };
};
const tagged = replies.map((reply, i) => ({ reply, recency: getRecency(reply), i }));
tagged.sort((a, b) => (a.recency !== b.recency ? b.recency - a.recency : a.i - b.i));
return tagged
.slice(0, visibleCount)
.reverse()
.map((t) => t.reply);
tagged.sort((a, b) => {
if (a.recency.group !== b.recency.group) {
return b.recency.group - a.recency.group;
}
if (a.recency.value !== b.recency.value) {
return b.recency.value - a.recency.value;
}
return a.i - b.i;
});
return sortRepliesForDisplay(
tagged
.slice(0, visibleCount)
.reverse()
.map((t) => t.reply),
);
}
interface ComputeOmittedParams {