fix(mod-queue): preserve pending reject state after refresh

This commit is contained in:
plebeius
2026-03-09 19:05:29 +08:00
parent 4806f48094
commit a20f131201
5 changed files with 99 additions and 21 deletions
+4 -3
View File
@@ -8,6 +8,7 @@ import styles from '../../views/post/post.module.css';
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { approvePendingCommentModeration, isPendingApprovalRejected, rejectPendingCommentModeration } from '../../lib/utils/pending-approval-moderation';
import { isValidURL } from '../../lib/utils/url-utils';
import { isAllView, isModQueueView, isModView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { formatUserIDForDisplay, truncateWithEllipsisInMiddle } from '../../lib/utils/string-utils';
@@ -131,7 +132,7 @@ const PostInfo = ({
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
commentModeration: { approved: true },
commentModeration: approvePendingCommentModeration,
onChallenge: async (...args: any) => {
addChallenge([...args, post]);
},
@@ -150,7 +151,7 @@ const PostInfo = ({
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
commentModeration: { removed: true },
commentModeration: rejectPendingCommentModeration,
onChallenge: async (...args: any) => {
addChallenge([...args, post]);
},
@@ -208,7 +209,7 @@ const PostInfo = ({
// Check if post is awaiting approval and over threshold (for mod queue view)
const approved = post?.approved;
const alreadyApproved = approved === true;
const alreadyRejected = removed === true;
const alreadyRejected = isPendingApprovalRejected(post);
const isAwaitingApproval = isInModQueueView && !alreadyApproved && !alreadyRejected;
const timeWaiting = timestamp ? currentTime - timestamp : 0;
const alertThresholdSeconds = getAlertThresholdSeconds();
+4 -3
View File
@@ -9,6 +9,7 @@ import { shouldShowSnow } from '../../lib/snow';
import { getHasThumbnail } from '../../lib/utils/media-utils';
import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { approvePendingCommentModeration, isPendingApprovalRejected, rejectPendingCommentModeration } from '../../lib/utils/pending-approval-moderation';
import { isAllView, isModQueueView, isModView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { formatUserIDForDisplay } from '../../lib/utils/string-utils';
import useModQueueStore from '../../stores/use-mod-queue-store';
@@ -103,7 +104,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
commentModeration: { approved: true },
commentModeration: approvePendingCommentModeration,
onChallenge: async (...args: any) => {
addChallenge([...args, post]);
},
@@ -122,7 +123,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
commentModeration: { removed: true },
commentModeration: rejectPendingCommentModeration,
onChallenge: async (...args: any) => {
addChallenge([...args, post]);
},
@@ -182,7 +183,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
// Check if post is awaiting approval and over threshold (for mod queue view)
const approved = post?.approved;
const alreadyApproved = approved === true;
const alreadyRejected = removed === true;
const alreadyRejected = isPendingApprovalRejected(post);
const isAwaitingApproval = isInModQueueView && !alreadyApproved && !alreadyRejected;
const timeWaiting = timestamp ? currentTime - timestamp : 0;
const alertThresholdSeconds = getAlertThresholdSeconds();
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest';
import { approvePendingCommentModeration, isPendingApprovalAwaiting, isPendingApprovalRejected, rejectPendingCommentModeration } from '../pending-approval-moderation';
describe('pending approval moderation utils', () => {
it('publishes approval with approved=true', () => {
expect(approvePendingCommentModeration).toEqual({ approved: true });
});
it('publishes rejection with approved=false and no removed flag', () => {
expect(rejectPendingCommentModeration).toEqual({ approved: false });
expect('removed' in rejectPendingCommentModeration).toBe(false);
});
it('treats pending approved=false as rejected for display', () => {
expect(isPendingApprovalRejected({ pendingApproval: true, approved: false })).toBe(true);
expect(isPendingApprovalAwaiting({ pendingApproval: true, approved: false })).toBe(false);
});
it('keeps pending approvals awaiting when no decision has been made', () => {
expect(isPendingApprovalRejected({ pendingApproval: true })).toBe(false);
expect(isPendingApprovalAwaiting({ pendingApproval: true })).toBe(true);
});
});
@@ -0,0 +1,17 @@
export const approvePendingCommentModeration = { approved: true } as const;
// plebbit-js clears pendingApproval only when rejection is published as approved:false.
// Sending removed:true marks the comment removed but can leave it in the mod queue.
export const rejectPendingCommentModeration = { approved: false } as const;
type PendingApprovalDisplayState = {
approved?: boolean;
removed?: boolean;
pendingApproval?: boolean;
};
export const isPendingApprovalRejected = (comment?: PendingApprovalDisplayState) =>
comment?.removed === true || (comment?.pendingApproval === true && comment?.approved === false);
export const isPendingApprovalAwaiting = (comment?: PendingApprovalDisplayState) =>
comment?.pendingApproval === true && comment?.approved !== true && !isPendingApprovalRejected(comment);
+51 -15
View File
@@ -13,6 +13,12 @@ import { useDirectories, DirectoryCommunity } from '../../hooks/use-directories'
import getShortAddress from '../../lib/get-short-address';
import { BOARD_CODE_GROUPS } from '../../constants/board-codes';
import { getHasThumbnail, getCommentMediaInfo } from '../../lib/utils/media-utils';
import {
approvePendingCommentModeration,
isPendingApprovalAwaiting,
isPendingApprovalRejected,
rejectPendingCommentModeration,
} from '../../lib/utils/pending-approval-moderation';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useChallengesStore from '../../stores/use-challenges-store';
@@ -148,11 +154,11 @@ const ModQueueActions = ({ status, errorMessage, isPublishing, handleApprove, ha
const useModQueueActions = (comment: Comment): ModQueueActionState => {
const { t } = useTranslation();
const { cid, subplebbitAddress, approved, removed } = comment || {};
const { cid, subplebbitAddress, approved, removed, pendingApproval } = comment || {};
const [initiatedAction, setInitiatedAction] = useState<ModerationAction>(null);
const alreadyApproved = approved === true;
const alreadyRejected = removed === true;
const alreadyRejected = isPendingApprovalRejected({ approved, removed, pendingApproval });
const {
publishCommentModeration: approve,
@@ -161,7 +167,7 @@ const useModQueueActions = (comment: Comment): ModQueueActionState => {
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress,
commentModeration: { approved: true },
commentModeration: approvePendingCommentModeration,
onChallenge: async (...args: any) => {
addChallenge([...args, comment]);
},
@@ -180,7 +186,7 @@ const useModQueueActions = (comment: Comment): ModQueueActionState => {
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress,
commentModeration: { removed: true },
commentModeration: rejectPendingCommentModeration,
onChallenge: async (...args: any) => {
addChallenge([...args, comment]);
},
@@ -245,21 +251,36 @@ const ModQueueRow = memo(({ comment, isOdd = false, showBoard = false, boardPath
const { editedComment } = useEditedComment({ comment });
const displayComment = editedComment || comment;
const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number, parentCid } =
displayComment;
const {
content,
title,
timestamp,
subplebbitAddress,
cid,
threadCid,
link,
thumbnailUrl,
linkWidth,
linkHeight,
removed,
approved,
pendingApproval,
number,
parentCid,
} = displayComment;
// Check if already moderated (from previous session or API update)
// Note: `approved` and `removed` are direct fields on the comment from CommentUpdate,
// not nested under commentModeration (which is the options object for publishing moderation actions)
const alreadyApproved = approved === true;
const alreadyRejected = removed === true;
const alreadyRejected = isPendingApprovalRejected({ approved, removed, pendingApproval });
const timeWaiting = currentTime - timestamp;
const alertThresholdSeconds = getAlertThresholdSeconds();
const isOverThreshold = timeWaiting > alertThresholdSeconds;
// Only show alert animation for comments awaiting approval (not approved or rejected)
const isAwaitingApproval = !alreadyApproved && !alreadyRejected;
const isAwaitingApproval = isPendingApprovalAwaiting(displayComment);
const { status, errorMessage, isPublishing, handleApprove, handleReject } = useModQueueActions(displayComment);
const hasTitle = title && title.trim().length > 0;
@@ -357,16 +378,31 @@ const ModQueueCard = memo(({ comment, showBoard = false, boardPath, boardDisplay
const { editedComment } = useEditedComment({ comment });
const displayComment = editedComment || comment;
const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number, parentCid } =
displayComment;
const {
content,
title,
timestamp,
subplebbitAddress,
cid,
threadCid,
link,
thumbnailUrl,
linkWidth,
linkHeight,
removed,
approved,
pendingApproval,
number,
parentCid,
} = displayComment;
const alreadyApproved = approved === true;
const alreadyRejected = removed === true;
const alreadyRejected = isPendingApprovalRejected({ approved, removed, pendingApproval });
const timeWaiting = currentTime - timestamp;
const alertThresholdSeconds = getAlertThresholdSeconds();
const isOverThreshold = timeWaiting > alertThresholdSeconds;
const isAwaitingApproval = !alreadyApproved && !alreadyRejected;
const isAwaitingApproval = isPendingApprovalAwaiting(displayComment);
const { status, errorMessage, isPublishing, handleApprove, handleReject } = useModQueueActions(displayComment);
const hasTitle = title && title.trim().length > 0;
@@ -480,7 +516,7 @@ const ModQueueBoardSummary = ({ feed, directories, accountSubplebbitAddresses }:
if (!addr) continue;
const entry = counts.get(addr);
if (!entry) continue;
const isAwaiting = item.approved !== true && item.removed !== true;
const isAwaiting = isPendingApprovalAwaiting(item);
if (!isAwaiting) continue;
const timeWaiting = currentTime - (item.timestamp ?? 0);
const isUrgent = timeWaiting > alertThresholdSeconds;
@@ -606,8 +642,8 @@ const ModQueueCountItem = ({ comment, alertThresholdSeconds, onStatusChange }: M
const displayComment = editedComment || comment;
const currentTime = useCurrentTime();
const { cid, approved, removed, timestamp } = displayComment;
const isAwaiting = approved !== true && removed !== true;
const { cid, timestamp } = displayComment;
const isAwaiting = isPendingApprovalAwaiting(displayComment);
const timeWaiting = currentTime - timestamp;
const isUrgent = isAwaiting && timeWaiting > alertThresholdSeconds;