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
@@ -15,12 +15,12 @@ const testState = vi.hoisted(() => ({
},
accountComment: undefined as { communityAddress?: string } | undefined,
accountCommunityAddresses: ['mod.eth'] as string[],
comments: {} as Record<string, { deleted?: boolean; locked?: boolean; postCid?: string; removed?: boolean }>,
comments: {} as Record<string, { commentModeration?: { archived?: boolean }; deleted?: boolean; locked?: boolean; postCid?: string; removed?: boolean }>,
directories: [
{ address: 'music-posting.eth', features: {}, title: '/mu/ - Music' },
{ address: 'mod.eth', features: {}, title: '/mod/ - Moderation' },
] as Array<{ address: string; features?: Record<string, unknown>; title?: string }>,
editedComment: undefined as { deleted?: boolean; locked?: boolean; postCid?: string; removed?: boolean } | undefined,
editedComment: undefined as { commentModeration?: { archived?: boolean }; deleted?: boolean; locked?: boolean; postCid?: string; removed?: boolean } | undefined,
gifFrameStatus: 'idle' as 'idle' | 'ready',
handleUploadMock: vi.fn(),
isOffline: false,
@@ -332,6 +332,22 @@ describe('PostForm', () => {
expect(container.textContent).toContain('may_not_reply');
});
it('shows the closed-thread notice for archived threads', async () => {
testState.comments = {
'thread-cid': {
postCid: 'thread-cid',
commentModeration: {
archived: true,
},
},
};
await renderPostForm('/mu/thread/thread-cid');
expect(container.textContent).toContain('thread_archived');
expect(container.textContent).toContain('may_not_reply');
});
it('opens the new-thread form, validates all-view requirements, and publishes a board post', async () => {
await renderPostForm('/all');
await clickByText(container, 'start_new_thread');
+6 -3
View File
@@ -16,6 +16,7 @@ import usePublishPost from '../../hooks/use-publish-post';
import usePublishReply from '../../hooks/use-publish-reply';
import { useFileUpload } from '../../hooks/use-file-upload';
import { getShowUploadControls, isWebRuntime } from '../../lib/media-hosting/show-upload-controls';
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
import useMediaHostingStore from '../../stores/use-media-hosting-store';
import BoardOfflineAlert from '../board-offline-alert/board-offline-alert';
import styles from './post-form.module.css';
@@ -525,7 +526,9 @@ const PostForm = () => {
}
const { deleted, locked, removed, postCid } = comment || {};
const isThreadClosed = deleted || locked || removed;
const archived = isCommentArchived(comment);
const isThreadClosed = deleted || locked || removed || archived;
const threadStateKey = archived ? 'thread_archived' : 'thread_closed';
const [showForm, setShowForm] = useState(false);
@@ -543,7 +546,7 @@ const PostForm = () => {
<div className={styles.modQueueTitle}>{t('moderation_queue')}</div>
) : isThreadClosed ? (
<div className={styles.closed}>
{t('thread_closed')}
{t(threadStateKey)}
<br />
{t('may_not_reply')}
</div>
@@ -567,7 +570,7 @@ const PostForm = () => {
<div className={styles.modQueueTitle}>{t('moderation_queue')}</div>
) : isThreadClosed ? (
<div className={styles.closed}>
{t('thread_closed')}
{t(threadStateKey)}
<br />
{t('may_not_reply')}
</div>