fix(mod-queue): rework excerpt hover preview, scope pending-age alerts (#1152)

Render the compact mod queue excerpt hover preview like the quote-link previews (clean read-only Post via useFloating, anchored to the right of the excerpt on desktop / below on mobile), cap its content at 350 chars, and scope the red pending-age timestamp to posts actually awaiting approval. Adds keyboard handlers to the span fallback.
This commit is contained in:
Tommaso Casaburi
2026-06-05 17:29:04 +07:00
committed by GitHub
parent ae16ae3dd3
commit 12977fa24a
5 changed files with 242 additions and 28 deletions
@@ -25,8 +25,10 @@ type TestComment = {
linkWidth?: number;
number?: number;
parentCid?: string;
pendingApproval?: boolean;
pinned?: boolean;
postCid?: string;
approved?: boolean;
removed?: boolean;
replyCount?: number;
replies?: {
@@ -167,6 +169,8 @@ vi.mock('../../lib/utils/time-utils', () => ({
vi.mock('../../lib/utils/pending-approval-moderation', () => ({
approvePendingCommentModeration: {},
isPendingApprovalAwaiting: (comment?: TestComment) =>
comment?.pendingApproval === true && comment?.approved !== true && comment?.approved !== false && !comment?.removed,
isPendingApprovalRejected: () => false,
rejectPendingCommentModeration: {},
}));
@@ -178,7 +182,7 @@ vi.mock('../../lib/utils/url-utils', () => ({
vi.mock('../../lib/utils/view-utils', () => ({
isAllView: (pathname: string) => pathname === '/all',
isModQueueView: () => false,
isModQueueView: (pathname: string) => pathname === '/mod/queue' || pathname.endsWith('/mod/queue'),
isModView: () => false,
isPendingPostView: () => false,
isPostPageView: (pathname: string) => pathname.includes('/thread/'),
@@ -535,4 +539,34 @@ describe('post community address compatibility', () => {
await renderWithRoute(createElement(PostMobile, { post: makeLegacyThreadWithoutReplies() }));
expect(container.querySelector('.postMobile')?.getAttribute('data-pretext-height')).toBeTruthy();
});
it('does not show a mod queue age alert for published preview posts on the mod queue route', async () => {
const publishedPost = {
...makeLegacyThread(),
pendingApproval: false,
timestamp: 1_700_000_000,
};
await renderWithRoute(createElement(PostDesktop, { post: publishedPost, showReplies: false }), '/mod/queue');
expect(container.textContent).toContain('2026-03-13');
expect(container.textContent).not.toContain('moments ago');
await renderWithRoute(createElement(PostMobile, { post: publishedPost, showReplies: false }), '/mod/queue');
expect(container.textContent).toContain('2026-03-13');
expect(container.textContent).not.toContain('moments ago');
});
it('keeps the mod queue age alert for pending preview posts on the mod queue route', async () => {
const pendingPost = {
...makeLegacyThread(),
pendingApproval: true,
timestamp: 1_700_000_000,
};
await renderWithRoute(createElement(PostDesktop, { post: pendingPost, showReplies: false }), '/mod/queue');
expect(container.textContent).toContain('moments ago');
await renderWithRoute(createElement(PostMobile, { post: pendingPost, showReplies: false }), '/mod/queue');
expect(container.textContent).toContain('moments ago');
});
});
+2 -5
View File
@@ -8,7 +8,7 @@ import styles from '../../views/post/post.module.css';
import { CommentMediaInfo, getHasThumbnail, getMediaDimensions, getPostMediaTypeLabel, getYouTubeEmbedPostMediaFileLink } 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 { approvePendingCommentModeration, isPendingApprovalAwaiting, rejectPendingCommentModeration } from '../../lib/utils/pending-approval-moderation';
import { isValidURL, parseHttpUrl } 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';
@@ -263,10 +263,7 @@ const PostInfo = ({
const shouldShowPendingApprovalButtons = isInPostPageView && !isInModQueueView && pendingApproval && isAccountMod && communityAddress;
// Check if post is awaiting approval and over threshold (for mod queue view)
const approved = post?.approved;
const alreadyApproved = approved === true;
const alreadyRejected = isPendingApprovalRejected(post);
const isAwaitingApproval = isInModQueueView && !alreadyApproved && !alreadyRejected;
const isAwaitingApproval = isInModQueueView && isPendingApprovalAwaiting(post);
const timeWaiting = timestamp ? currentTime - timestamp : 0;
const alertThresholdSeconds = getAlertThresholdSeconds();
const isOverThreshold = isAwaitingApproval && timeWaiting > alertThresholdSeconds;
+2 -5
View File
@@ -9,7 +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 { approvePendingCommentModeration, isPendingApprovalAwaiting, 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';
@@ -210,10 +210,7 @@ const PostInfoAndMedia = ({
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
// Check if post is awaiting approval and over threshold (for mod queue view)
const approved = resolvedPost?.approved;
const alreadyApproved = approved === true;
const alreadyRejected = isPendingApprovalRejected(post);
const isAwaitingApproval = isInModQueueView && !alreadyApproved && !alreadyRejected;
const isAwaitingApproval = isInModQueueView && isPendingApprovalAwaiting(resolvedPost);
const timeWaiting = timestamp ? currentTime - timestamp : 0;
const alertThresholdSeconds = getAlertThresholdSeconds();
const isOverThreshold = isAwaitingApproval && timeWaiting > alertThresholdSeconds;