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
+24
View File
@@ -12,6 +12,9 @@ type TestComment = {
cid?: string;
content?: string;
error?: Error;
commentModeration?: {
archived?: boolean;
};
locked?: boolean;
number?: number;
parentCid?: string;
@@ -302,6 +305,27 @@ describe('Post', () => {
expect(HTMLElement.prototype.scrollIntoView).not.toHaveBeenCalled();
});
it('passes archived OP state through to thread footers as closed', async () => {
testState.commentsByCid = {
'archived-thread': {
cid: 'archived-thread',
content: 'thread',
commentModeration: {
archived: true,
},
number: 777,
replyCount: 0,
subplebbitAddress: 'music-posting.eth',
title: 'Archived thread',
},
};
await renderPostPage('/mu/thread/archived-thread');
expect(container.querySelector('[data-testid="thread-footer-first-row"]')?.textContent).toBe('archived-thread:777:music-posting.eth:true');
expect(container.querySelector('[data-testid="thread-footer-mobile"]')?.textContent).toBe('archived-thread:777:music-posting.eth:true');
});
it('only aligns the OP container when navigation explicitly requests it', async () => {
testState.commentsByCid = {
'thread-cid': {
+16 -2
View File
@@ -7,6 +7,7 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { isAllView } from '../../lib/utils/view-utils';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import { useDirectories } from '../../hooks/use-directories';
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
import { areSameBoardAddress, isDirectoryBoard } from '../../lib/utils/route-utils';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import useIsMobile from '../../hooks/use-is-mobile';
@@ -111,6 +112,7 @@ export const Post = memo(
prev?.updatedAt === next?.updatedAt &&
prev?.locked === next?.locked &&
prev?.pinned === next?.pinned &&
isCommentArchived(prev) === isCommentArchived(next) &&
prev?.removed === next?.removed &&
prev?.deleted === next?.deleted &&
prev?.commentModeration?.purged === next?.commentModeration?.purged &&
@@ -231,10 +233,22 @@ const PostPage = () => {
{post?.cid && communityAddress ? (
<>
<PageFooterDesktop
firstRow={<ThreadFooterFirstRow postCid={post.cid} threadNumber={post?.number} communityAddress={communityAddress} isThreadClosed={!!post?.locked} />}
firstRow={
<ThreadFooterFirstRow
postCid={post.cid}
threadNumber={post?.number}
communityAddress={communityAddress}
isThreadClosed={!!(post?.locked || isCommentArchived(post))}
/>
}
styleRow={<ThreadFooterStyleRow />}
/>
<ThreadFooterMobile postCid={post.cid} threadNumber={post?.number} communityAddress={communityAddress} isThreadClosed={!!post?.locked} />
<ThreadFooterMobile
postCid={post.cid}
threadNumber={post?.number}
communityAddress={communityAddress}
isThreadClosed={!!(post?.locked || isCommentArchived(post))}
/>
</>
) : null}
</div>