mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
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:
@@ -185,6 +185,7 @@ const renderWithRoute = async (element: React.ReactElement, initialEntry: string
|
||||
createElement(Route, { path: '/all/catalog', element }),
|
||||
createElement(Route, { path: '/mod/queue', element }),
|
||||
createElement(Route, { path: '/:boardIdentifier/catalog', element }),
|
||||
createElement(Route, { path: '/:boardIdentifier/archive', element }),
|
||||
createElement(Route, { path: '/:boardIdentifier/thread/:commentCid', element }),
|
||||
createElement(Route, { path: '/:boardIdentifier', element }),
|
||||
),
|
||||
@@ -288,8 +289,9 @@ describe('BoardButtons', () => {
|
||||
|
||||
expect(testState.resetMock).toHaveBeenCalledTimes(1);
|
||||
expect(testState.subscribeMock).toHaveBeenCalledTimes(1);
|
||||
expect(testState.navigateMock).toHaveBeenCalledWith('/mu/archive');
|
||||
expect(globalThis.alert).toHaveBeenNthCalledWith(1, 'vote_button_unavailable_intro\n\nvote_button_unavailable_outro');
|
||||
expect(globalThis.alert).toHaveBeenNthCalledWith(2, 'Work in progress');
|
||||
expect(globalThis.alert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('renders desktop catalog controls and wires sort, style, filter, and refresh updates', async () => {
|
||||
@@ -299,6 +301,7 @@ describe('BoardButtons', () => {
|
||||
|
||||
expect(container.textContent).toContain('filtered_threads');
|
||||
expect(container.textContent).toContain('4');
|
||||
expect(container.textContent).not.toContain('archive');
|
||||
expect(container.querySelector('[data-testid="catalog-filters"]')?.textContent).toBe('catalog-filters');
|
||||
expect(container.querySelector('[data-testid="catalog-search"]')?.textContent).toBe('catalog-search');
|
||||
|
||||
@@ -322,6 +325,7 @@ describe('BoardButtons', () => {
|
||||
testState.commentsByCid = {
|
||||
'comment-1': {
|
||||
cid: 'comment-1',
|
||||
archived: true,
|
||||
closed: true,
|
||||
number: 99,
|
||||
pinned: true,
|
||||
@@ -335,6 +339,7 @@ describe('BoardButtons', () => {
|
||||
const tooltips = Array.from(container.querySelectorAll<HTMLElement>('[data-testid="tooltip"]'));
|
||||
expect(tooltips.map((tooltip) => tooltip.dataset.content)).toEqual(['Replies', 'Links', 'pagination.pageLabel']);
|
||||
expect(tooltips.map((tooltip) => tooltip.textContent)).toEqual(['9', '3', '7']);
|
||||
expect(container.textContent).toContain('Archived /');
|
||||
expect(container.textContent).toContain('Sticky /');
|
||||
expect(container.textContent).toContain('Closed /');
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import CatalogFilters from '../catalog-filters';
|
||||
import CatalogSearch from '../catalog-search';
|
||||
import Tooltip from '../tooltip';
|
||||
import { ModQueueButton } from '../../views/mod-queue/mod-queue';
|
||||
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
||||
import styles from './board-buttons.module.css';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
|
||||
@@ -59,13 +60,21 @@ export const CatalogButton = ({ address, isInAllView, isInSubscriptionsView, isI
|
||||
|
||||
export const ArchiveButton = ({ address, isInAllView, isInSubscriptionsView, isInModView }: BoardButtonsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const directories = useDirectories();
|
||||
|
||||
const handleClick = () => {
|
||||
window.alert('Work in progress');
|
||||
};
|
||||
const isInvalidArchiveContext = isInAllView || isInSubscriptionsView || isInModView;
|
||||
const boardIdentifier = params.boardIdentifier || params.subplebbitAddress;
|
||||
const archiveBoardIdentifier = address ? getBoardPath(address, directories) : boardIdentifier ? getBoardPath(boardIdentifier, directories) : '';
|
||||
const archivePath = archiveBoardIdentifier ? `/${archiveBoardIdentifier}/archive` : '';
|
||||
|
||||
if (isInvalidArchiveContext || !archivePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button className='button' onClick={handleClick}>
|
||||
<button className='button' onClick={() => navigate(archivePath)}>
|
||||
{t('archive')}
|
||||
</button>
|
||||
);
|
||||
@@ -183,7 +192,7 @@ export const AutoButton = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const BottomButton = () => {
|
||||
export const BottomButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const handleClick = () => {
|
||||
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'instant' });
|
||||
@@ -495,6 +504,7 @@ export const PostPageStats = () => {
|
||||
const postCid = comment?.postCid ?? commentCid;
|
||||
const post = useComment({ commentCid: postCid });
|
||||
|
||||
const archived = isCommentArchived(post);
|
||||
const { closed, pinned, replyCount } = post || {};
|
||||
const linkCount = useCountLinksInReplies(post);
|
||||
const directoryEntry = useDirectoryByAddress(communityAddress);
|
||||
@@ -513,6 +523,7 @@ export const PostPageStats = () => {
|
||||
return (
|
||||
<span>
|
||||
{pinned && `${capitalize(t('sticky'))} / `}
|
||||
{archived && `${capitalize(t('archived'))} / `}
|
||||
{closed && `${capitalize(t('closed'))} / `}
|
||||
<Tooltip content={replyCountTooltip}>{displayReplyCount}</Tooltip> /{' '}
|
||||
<Tooltip content={capitalize(requirePostLinkIsMedia ? t('images') : t('links'))}>{linkCount?.toString()}</Tooltip>
|
||||
|
||||
@@ -7,6 +7,7 @@ import useCommunitiesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores
|
||||
import getShortAddress from '../../lib/get-short-address';
|
||||
import { useStableCommunity } from '../../hooks/use-stable-community';
|
||||
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
|
||||
import { isArchiveRoute } from '../../lib/utils/route-utils';
|
||||
import styles from './board-header.module.css';
|
||||
import { useDirectoriesMetadata, useDirectories } from '../../hooks/use-directories';
|
||||
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
|
||||
@@ -51,6 +52,7 @@ const BoardHeader = () => {
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
||||
const isInModView = isModView(location.pathname);
|
||||
const isInArchiveView = isArchiveRoute(location.pathname);
|
||||
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
||||
const resolvedAddress = useResolvedCommunityAddress();
|
||||
const communityAddress = resolvedAddress || accountComment?.communityAddress;
|
||||
@@ -120,7 +122,7 @@ const BoardHeader = () => {
|
||||
subtitle
|
||||
)}
|
||||
</div>
|
||||
<hr />
|
||||
{!isInArchiveView && <hr />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,9 @@ type TestComment = {
|
||||
};
|
||||
cid: string;
|
||||
content?: string;
|
||||
commentModeration?: {
|
||||
archived?: boolean;
|
||||
};
|
||||
link?: string;
|
||||
linkHeight?: number;
|
||||
linkWidth?: number;
|
||||
@@ -276,6 +279,25 @@ describe('CatalogRow', () => {
|
||||
expect(container.querySelector<HTMLImageElement>('img[src="assets/filedeleted-res.gif"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders the archived icon for archived threads', async () => {
|
||||
const post: TestComment = {
|
||||
author: { address: 'author-1', displayName: 'Alice' },
|
||||
cid: 'post-archived',
|
||||
commentModeration: {
|
||||
archived: true,
|
||||
},
|
||||
content: 'Archived thread',
|
||||
replyCount: 3,
|
||||
subplebbitAddress: 'music-posting.eth',
|
||||
title: 'Old thread',
|
||||
};
|
||||
|
||||
testState.mediaInfoByLink['https://example.com/media.png'] = { type: 'image', url: 'https://example.com/media.png' };
|
||||
await renderWithRouter(createElement(CatalogRow, { row: [post] }));
|
||||
|
||||
expect(container.querySelector('[title="archived"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders audio players and video first-frame fallbacks for media without thumbnails', async () => {
|
||||
await act(async () => {
|
||||
root.render(
|
||||
|
||||
@@ -66,6 +66,10 @@
|
||||
background-image: url("/assets/icons/closed.gif");
|
||||
}
|
||||
|
||||
.threadIcons .archivedIcon {
|
||||
background-image: url("/assets/icons/archived.gif");
|
||||
}
|
||||
|
||||
.mediaWrapper {
|
||||
background-color: var(--media-thumbnail-background-color);
|
||||
width: var(--width);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
|
||||
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
||||
import useHide from '../../hooks/use-hide';
|
||||
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
||||
import { removeMarkdown } from '../../lib/utils/post-utils';
|
||||
import PostMenuDesktop from '../post-desktop/post-menu-desktop';
|
||||
import styles from './catalog-row.module.css';
|
||||
@@ -121,6 +122,7 @@ const CatalogPost = memo(
|
||||
const resolvedPost = useMemo(() => withResolvedCommentCommunityAddress(post), [post]);
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, spoiler, communityAddress, timestamp, title, thumbnailUrl } =
|
||||
resolvedPost || {};
|
||||
const archived = isCommentArchived(resolvedPost);
|
||||
const linkCount = useCountLinksInReplies(resolvedPost);
|
||||
|
||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
@@ -144,6 +146,7 @@ const CatalogPost = memo(
|
||||
<div className={styles.threadIcons}>
|
||||
{pinned && <span className={styles.stickyIcon} title={t('sticky')} />}
|
||||
{locked && <span className={styles.closedIcon} title={t('closed')} />}
|
||||
{archived && <span className={styles.archivedIcon} title={t('archived')} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -324,6 +327,7 @@ const CatalogPost = memo(
|
||||
prev?.replyCount === next?.replyCount &&
|
||||
prev?.locked === next?.locked &&
|
||||
prev?.pinned === next?.pinned &&
|
||||
isCommentArchived(prev) === isCommentArchived(next) &&
|
||||
prev?.title === next?.title &&
|
||||
prev?.content === next?.content &&
|
||||
prev?.spoiler === next?.spoiler &&
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -50,6 +50,7 @@ import useProgressiveRender from '../../hooks/use-progressive-render';
|
||||
import useFreshReplies from '../../hooks/use-fresh-replies';
|
||||
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
||||
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount } from '../../lib/utils/replies-preview-utils';
|
||||
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
||||
import { getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
||||
import useDeleteFailedPost from '../../hooks/use-delete-failed-post';
|
||||
import { withResolvedCommentCommunityAddress } from '../../lib/utils/comment-utils';
|
||||
@@ -99,6 +100,7 @@ const PostInfo = ({
|
||||
}: PostProps & { directRepliesByParentCid?: Map<string, Comment[]> }) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, deleted, locked, pinned, parentCid, postCid, reason, removed, state, communityAddress, timestamp } = post || {};
|
||||
const archived = isCommentArchived(post);
|
||||
const purged = post?.commentModeration?.purged;
|
||||
const title = post?.title?.trim();
|
||||
const { address, shortAddress } = author || {};
|
||||
@@ -247,7 +249,9 @@ const PostInfo = ({
|
||||
? isReply
|
||||
? alert(t('this_reply_was_removed'))
|
||||
: alert(t('this_thread_was_removed'))
|
||||
: openReplyModal && openReplyModal(cid, post?.number, postCid, threadNumber, communityAddress);
|
||||
: archived && !isReply
|
||||
? alert(t('thread_archived'))
|
||||
: openReplyModal && openReplyModal(cid, post?.number, postCid, threadNumber, communityAddress);
|
||||
};
|
||||
|
||||
const threadRoute = cid ? (boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`) : undefined;
|
||||
@@ -397,6 +401,13 @@ const PostInfo = ({
|
||||
<img src='assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
|
||||
</span>
|
||||
)}
|
||||
{archived && (
|
||||
<span
|
||||
className={`${styles.closedIconWrapper} ${!locked && !pinned ? styles.addPaddingBeforeReply : ''} ${pinned || locked ? styles.addPaddingInBetween : ''}`}
|
||||
>
|
||||
<img src='assets/icons/archived.gif' alt='' className={styles.closedIcon} title={t('archived')} />
|
||||
</span>
|
||||
)}
|
||||
{!isInPostPageView && !isReply && !isHidden && !isModQueue && (
|
||||
<span className={styles.replyButton}>
|
||||
[
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -43,6 +43,7 @@ import useQuotedByMap from '../../hooks/use-quoted-by-map';
|
||||
import useProgressiveRender from '../../hooks/use-progressive-render';
|
||||
import useFreshReplies from '../../hooks/use-fresh-replies';
|
||||
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
||||
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
||||
import { filterRepliesForDisplay, getPreviewDisplayReplies } from '../../lib/utils/replies-preview-utils';
|
||||
import { getRenderableMobileBacklinks } from '../../lib/utils/reply-backlink-utils';
|
||||
import { getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
||||
@@ -67,6 +68,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
||||
const resolvedPost = withResolvedCommentCommunityAddress(post);
|
||||
const { author, cid, deleted, link, linkHeight, linkWidth, locked, parentCid, pinned, postCid, reason, removed, state, communityAddress, timestamp, thumbnailUrl } =
|
||||
resolvedPost || {};
|
||||
const archived = isCommentArchived(resolvedPost);
|
||||
const purged = resolvedPost?.commentModeration?.purged;
|
||||
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
|
||||
const displayBoardPath =
|
||||
@@ -229,7 +231,9 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
||||
? isReply
|
||||
? alert(t('this_reply_was_removed'))
|
||||
: alert(t('this_thread_was_removed'))
|
||||
: openReplyModal && openReplyModal(cid, resolvedPost?.number, postCid, threadNumber, communityAddress);
|
||||
: archived && !isReply
|
||||
? alert(t('thread_archived'))
|
||||
: openReplyModal && openReplyModal(cid, resolvedPost?.number, postCid, threadNumber, communityAddress);
|
||||
};
|
||||
|
||||
const threadRoute = cid ? (boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`) : undefined;
|
||||
@@ -332,6 +336,13 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
||||
<img src='assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
|
||||
</span>
|
||||
)}
|
||||
{archived && (
|
||||
<span
|
||||
className={`${styles.closedIconWrapper} ${!locked && !pinned ? styles.addPaddingBeforeReply : ''} ${pinned || locked ? styles.addPaddingInBetween : ''}`}
|
||||
>
|
||||
<img src='assets/icons/archived.gif' alt='' className={styles.closedIcon} title={t('archived')} />
|
||||
</span>
|
||||
)}
|
||||
{title && (
|
||||
<span className={styles.subjectWrapper}>
|
||||
{title.length <= 30 ? (
|
||||
|
||||
Reference in New Issue
Block a user