feat(archive): implement comment.archived and add archive page (#1074)

* feat(archive): implement comment.archived and add archive page

Add isCommentArchived() utility, /board/:boardIdentifier/archive route,
archive view with filtered feed, and UI integration (board buttons, edit
menu, catalog row, post). Archived indicator on catalog and posts.

* fix(archive): address review feedback from Bugbot and CodeRabbit

Add missing i18n keys (archived, thread_archived, view, loading_archive),
add /archive/settings route, replace hardcoded English in archive.tsx, and
fix mobile test state.
This commit is contained in:
Tommaso Casaburi
2026-03-13 16:06:36 +08:00
committed by GitHub
parent d7703953fb
commit aedee9fb83
65 changed files with 1384 additions and 62 deletions
@@ -127,6 +127,9 @@ const basePost = {
removed: false,
spoiler: false,
communityAddress: 'music-posting.eth',
commentModeration: {
archived: false,
},
} as Record<string, any>;
const renderMenu = async (post = basePost) => {
@@ -276,6 +279,7 @@ describe('EditMenu', () => {
await click(getCheckbox('removed'));
await click(getCheckbox('purged'));
await click(getCheckbox('locked'));
await click(getCheckbox('archived'));
await click(getCheckbox('spoiler'));
await click(getCheckbox('pinned'));
await click(getCheckbox('banUser'));
@@ -302,6 +306,7 @@ describe('EditMenu', () => {
});
expect(testState.modOptions?.commentModeration).toMatchObject({
reason: 'rule violation',
archived: true,
locked: true,
pinned: true,
purged: true,
@@ -329,6 +334,26 @@ describe('EditMenu', () => {
expect(testState.modOptions?.commentModeration?.purged).toBe(false);
});
it('shows archive control only for top-level comments', async () => {
testState.privileges = {
isAccountCommentAuthor: false,
isAccountMod: true,
isCommentAuthorMod: false,
};
await renderMenu(basePost);
await openMenu();
expect(getCheckbox('archived')).not.toBeNull();
await renderMenu({
...basePost,
cid: 'reply-comment',
parentCid: 'parent-cid',
});
await openMenu();
expect(getCheckbox('archived')).toBeNull();
});
it('runs both the author edit and moderation publication paths when the user has both privileges', async () => {
testState.privileges = {
isAccountCommentAuthor: true,
+31 -1
View File
@@ -11,6 +11,7 @@ import {
} from '@bitsocialnet/bitsocial-react-hooks';
import styles from './edit-menu.module.css';
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
import useChallengesStore from '../../stores/use-challenges-store';
import capitalize from 'lodash/capitalize';
import useIsMobile from '../../hooks/use-is-mobile';
@@ -36,6 +37,7 @@ const EditMenu = ({ post }: { post: Comment }) => {
const resolvedPost = withResolvedCommentCommunityAddress(post);
const { author, cid, content, deleted, locked, parentCid, pinned, postCid, reason, removed, spoiler } = resolvedPost || {};
const communityAddress = getCommentCommunityAddress(resolvedPost);
const archived = isCommentArchived(resolvedPost);
const authorDisplayName = resolvedPost?.author?.displayName;
const modBanExpiresAt = resolvedPost?.commentModeration?.author?.banExpiresAt;
const purged = resolvedPost?.commentModeration?.purged ?? false;
@@ -67,6 +69,7 @@ const EditMenu = ({ post }: { post: Comment }) => {
commentModeration: isAccountMod
? {
locked: locked ?? false,
archived: parentCid === undefined ? (archived ?? false) : undefined,
pinned: pinned ?? false,
removed: removed ?? false,
purged: purged ?? false,
@@ -82,7 +85,23 @@ const EditMenu = ({ post }: { post: Comment }) => {
alert('Comment edit failed. ' + error.message);
},
};
}, [isAccountMod, isAccountCommentAuthor, cid, content, deleted, locked, pinned, reason, removed, purged, spoiler, communityAddress, modBanExpiresAt, onChallenge]);
}, [
isAccountMod,
isAccountCommentAuthor,
archived,
cid,
content,
deleted,
locked,
pinned,
reason,
removed,
purged,
spoiler,
communityAddress,
modBanExpiresAt,
onChallenge,
]);
const [publishCommentEditOptions, setPublishCommentEditOptions] = useState<PublishCommentEditOptions>(defaultPublishEditOptions);
@@ -112,6 +131,7 @@ const EditMenu = ({ post }: { post: Comment }) => {
communityAddress,
commentModeration: {
locked: parentCid === undefined ? publishCommentEditOptions.commentModeration?.locked : undefined,
archived: parentCid === undefined ? publishCommentEditOptions.commentModeration?.archived : undefined,
pinned: publishCommentEditOptions.commentModeration?.pinned,
removed: publishCommentEditOptions.commentModeration?.removed,
purged: publishCommentEditOptions.commentModeration?.purged,
@@ -337,6 +357,16 @@ const EditMenu = ({ post }: { post: Comment }) => {
</label>
]
</div>
{!parentCid && (
<div className={styles.menuItem}>
[
<label>
<input onChange={onCheckbox} checked={publishCommentEditOptions.commentModeration?.archived ?? false} type='checkbox' id='archived' />
{capitalize(t('archived'))}?
</label>
]
</div>
)}
<div className={styles.menuItem}>
[
<label>