mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Merge branch 'codex/fix/mod-queue-approved-stuck'
# Conflicts: # package.json # yarn.lock
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatErrorForDisplay } from '../error-utils';
|
||||
|
||||
describe('error utils', () => {
|
||||
it('returns plain string errors unchanged', () => {
|
||||
expect(formatErrorForDisplay('plain failure')).toBe('plain failure');
|
||||
});
|
||||
|
||||
it('appends structured details to the message', () => {
|
||||
expect(
|
||||
formatErrorForDisplay({
|
||||
details: {
|
||||
plebpubsub: 'timeout',
|
||||
pubsubprovider: 'connection refused',
|
||||
},
|
||||
message: 'All pubsub providers throw an error and unable to publish or subscribe',
|
||||
}),
|
||||
).toBe('All pubsub providers throw an error and unable to publish or subscribe: plebpubsub: timeout; pubsubprovider: connection refused');
|
||||
});
|
||||
|
||||
it('falls back to cause when details are missing', () => {
|
||||
expect(
|
||||
formatErrorForDisplay({
|
||||
cause: {
|
||||
provider: 'plebpubsub',
|
||||
reason: 'timeout',
|
||||
},
|
||||
message: 'publish failed',
|
||||
}),
|
||||
).toBe('publish failed: provider: plebpubsub; reason: timeout');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { filterVisibleModQueueFeed, getModQueueCommentRoute, getQueuedCommentRouteState } from '../mod-queue-utils';
|
||||
|
||||
describe('mod queue utils', () => {
|
||||
it('keeps only comments still awaiting approval', () => {
|
||||
const feed = [
|
||||
{ cid: 'pending', communityAddress: 'tech.eth', pendingApproval: true },
|
||||
{ cid: 'approved', approved: true, communityAddress: 'tech.eth', pendingApproval: true },
|
||||
{ cid: 'rejected', approved: false, communityAddress: 'tech.eth', pendingApproval: true },
|
||||
{ cid: 'removed', communityAddress: 'tech.eth', pendingApproval: true, removed: true },
|
||||
{ cid: 'published', communityAddress: 'tech.eth', pendingApproval: false },
|
||||
];
|
||||
|
||||
expect(filterVisibleModQueueFeed(feed, null)).toEqual([{ cid: 'pending', communityAddress: 'tech.eth', pendingApproval: true }]);
|
||||
});
|
||||
|
||||
it('applies the selected board filter after removing non-pending items', () => {
|
||||
const feed = [
|
||||
{ cid: 'tech-pending', communityAddress: 'tech.eth', pendingApproval: true },
|
||||
{ cid: 'g-pending', communityAddress: 'g.eth', pendingApproval: true },
|
||||
{ cid: 'tech-approved', approved: true, communityAddress: 'tech.eth', pendingApproval: true },
|
||||
];
|
||||
|
||||
expect(filterVisibleModQueueFeed(feed, 'tech.eth')).toEqual([{ cid: 'tech-pending', communityAddress: 'tech.eth', pendingApproval: true }]);
|
||||
});
|
||||
|
||||
it('builds excerpt routes from the comment permalink cid', () => {
|
||||
expect(getModQueueCommentRoute('g', 'reply-cid')).toBe('/g/thread/reply-cid');
|
||||
expect(getModQueueCommentRoute('g', undefined)).toBeUndefined();
|
||||
expect(getModQueueCommentRoute(undefined, 'reply-cid')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('serializes queued comment route state for reply links', () => {
|
||||
expect(
|
||||
getQueuedCommentRouteState({
|
||||
cid: 'reply-cid',
|
||||
communityAddress: 'g.eth',
|
||||
content: 'pending reply body',
|
||||
parentCid: 'thread-cid',
|
||||
pendingApproval: true,
|
||||
postCid: 'thread-cid',
|
||||
}),
|
||||
).toEqual({
|
||||
queuedComment: {
|
||||
approved: undefined,
|
||||
author: undefined,
|
||||
cid: 'reply-cid',
|
||||
commentModeration: undefined,
|
||||
communityAddress: 'g.eth',
|
||||
content: 'pending reply body',
|
||||
deleted: undefined,
|
||||
link: undefined,
|
||||
linkHeight: undefined,
|
||||
linkWidth: undefined,
|
||||
number: undefined,
|
||||
parentCid: 'thread-cid',
|
||||
pendingApproval: true,
|
||||
postCid: 'thread-cid',
|
||||
reason: undefined,
|
||||
removed: undefined,
|
||||
replyCount: undefined,
|
||||
threadCid: undefined,
|
||||
thumbnailUrl: undefined,
|
||||
timestamp: undefined,
|
||||
title: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
type ErrorLike = {
|
||||
cause?: unknown;
|
||||
details?: unknown;
|
||||
message?: unknown;
|
||||
};
|
||||
|
||||
const normalizeUnknownErrorPart = (value: unknown): string | undefined => {
|
||||
if (value === null || value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value.trim() || undefined;
|
||||
}
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return String(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const parts = value.map(normalizeUnknownErrorPart).filter(Boolean);
|
||||
return parts.length ? parts.join('; ') : undefined;
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
const entries = Object.entries(value as Record<string, unknown>)
|
||||
.map(([key, entryValue]) => {
|
||||
const normalizedValue = normalizeUnknownErrorPart(entryValue);
|
||||
return normalizedValue ? `${key}: ${normalizedValue}` : undefined;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (entries.length) {
|
||||
return entries.join('; ');
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const formatErrorForDisplay = (error: unknown): string | undefined => {
|
||||
if (!error) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalizedString = normalizeUnknownErrorPart(error);
|
||||
if (typeof error === 'string') {
|
||||
return normalizedString;
|
||||
}
|
||||
|
||||
const { cause, details, message } = error as ErrorLike;
|
||||
const normalizedMessage = normalizeUnknownErrorPart(message);
|
||||
const normalizedDetails = normalizeUnknownErrorPart(details);
|
||||
const normalizedCause = normalizeUnknownErrorPart(cause);
|
||||
|
||||
const detailParts = [normalizedDetails, normalizedCause].filter(Boolean);
|
||||
if (normalizedMessage && detailParts.length) {
|
||||
const detailText = detailParts.join('; ');
|
||||
return normalizedMessage.includes(detailText) ? normalizedMessage : `${normalizedMessage}: ${detailText}`;
|
||||
}
|
||||
|
||||
return normalizedMessage || detailParts[0] || normalizedString;
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Comment } from '@bitsocialnet/bitsocial-react-hooks';
|
||||
import { getCommentCommunityAddress } from './comment-utils';
|
||||
import { isPendingApprovalAwaiting } from './pending-approval-moderation';
|
||||
import { getThreadTopNavigationState } from './thread-scroll-utils';
|
||||
|
||||
type ModQueueCommentLike = {
|
||||
approved?: boolean;
|
||||
author?: Comment['author'];
|
||||
cid?: string;
|
||||
commentModeration?: Comment['commentModeration'];
|
||||
communityAddress?: string;
|
||||
content?: Comment['content'];
|
||||
deleted?: Comment['deleted'];
|
||||
link?: Comment['link'];
|
||||
linkHeight?: Comment['linkHeight'];
|
||||
linkWidth?: Comment['linkWidth'];
|
||||
number?: Comment['number'];
|
||||
parentCid?: Comment['parentCid'];
|
||||
pendingApproval?: boolean;
|
||||
postCid?: Comment['postCid'];
|
||||
reason?: Comment['reason'];
|
||||
removed?: boolean;
|
||||
replyCount?: Comment['replyCount'];
|
||||
threadCid?: Comment['threadCid'];
|
||||
thumbnailUrl?: Comment['thumbnailUrl'];
|
||||
timestamp?: Comment['timestamp'];
|
||||
title?: Comment['title'];
|
||||
};
|
||||
|
||||
export type QueuedCommentRouteState = {
|
||||
scrollThreadContainerCid?: string;
|
||||
queuedComment?: {
|
||||
approved?: Comment['approved'];
|
||||
author?: Comment['author'];
|
||||
cid?: Comment['cid'];
|
||||
commentModeration?: Comment['commentModeration'];
|
||||
communityAddress?: string;
|
||||
content?: Comment['content'];
|
||||
deleted?: Comment['deleted'];
|
||||
link?: Comment['link'];
|
||||
linkHeight?: Comment['linkHeight'];
|
||||
linkWidth?: Comment['linkWidth'];
|
||||
number?: Comment['number'];
|
||||
parentCid?: Comment['parentCid'];
|
||||
pendingApproval?: Comment['pendingApproval'];
|
||||
postCid?: Comment['postCid'];
|
||||
reason?: Comment['reason'];
|
||||
removed?: Comment['removed'];
|
||||
replyCount?: Comment['replyCount'];
|
||||
threadCid?: Comment['threadCid'];
|
||||
thumbnailUrl?: Comment['thumbnailUrl'];
|
||||
timestamp?: Comment['timestamp'];
|
||||
title?: Comment['title'];
|
||||
};
|
||||
};
|
||||
|
||||
export const getModQueueCommentRoute = (boardPath: string | undefined, commentCid: string | undefined): string | undefined =>
|
||||
boardPath && commentCid ? `/${boardPath}/thread/${commentCid}` : undefined;
|
||||
|
||||
export const getQueuedCommentRouteState = (comment: ModQueueCommentLike | undefined): QueuedCommentRouteState | undefined => {
|
||||
if (!comment?.cid) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...(comment.parentCid ? {} : getThreadTopNavigationState(comment.cid)),
|
||||
queuedComment: {
|
||||
approved: comment.approved,
|
||||
author: comment.author,
|
||||
cid: comment.cid,
|
||||
commentModeration: comment.commentModeration,
|
||||
communityAddress: getCommentCommunityAddress(comment),
|
||||
content: comment.content,
|
||||
deleted: comment.deleted,
|
||||
link: comment.link,
|
||||
linkHeight: comment.linkHeight,
|
||||
linkWidth: comment.linkWidth,
|
||||
number: comment.number,
|
||||
parentCid: comment.parentCid,
|
||||
pendingApproval: comment.pendingApproval,
|
||||
postCid: comment.postCid,
|
||||
reason: comment.reason,
|
||||
removed: comment.removed,
|
||||
replyCount: comment.replyCount,
|
||||
threadCid: comment.threadCid,
|
||||
thumbnailUrl: comment.thumbnailUrl,
|
||||
timestamp: comment.timestamp,
|
||||
title: comment.title,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const filterVisibleModQueueFeed = <T extends ModQueueCommentLike>(feed: T[], selectedBoardFilter: string | null): T[] =>
|
||||
feed.filter((comment) => {
|
||||
if (!isPendingApprovalAwaiting(comment)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!selectedBoardFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return getCommentCommunityAddress(comment) === selectedBoardFilter;
|
||||
});
|
||||
Reference in New Issue
Block a user