refactor: migrate 5chan to the community hooks API (#1073)

* refactor(community-api): migrate 5chan to community hooks

* fix(review): address PR feedback

* fix(review): preserve legacy board context fallbacks

* fix(review): address latest bot feedback

* fix(review): use communityAddress in edit menu privileges
This commit is contained in:
Tommaso Casaburi
2026-03-13 13:25:10 +08:00
committed by GitHub
parent 9dc4d96d27
commit d7703953fb
105 changed files with 2659 additions and 1491 deletions
+52 -46
View File
@@ -52,6 +52,7 @@ import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT,
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount } from '../../lib/utils/replies-preview-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';
const { addChallenge } = useChallengesStore.getState();
@@ -97,7 +98,7 @@ const PostInfo = ({
directRepliesByParentCid,
}: PostProps & { directRepliesByParentCid?: Map<string, Comment[]> }) => {
const { t } = useTranslation();
const { author, cid, deleted, locked, pinned, parentCid, postCid, reason, removed, state, subplebbitAddress, timestamp } = post || {};
const { author, cid, deleted, locked, pinned, parentCid, postCid, reason, removed, state, communityAddress, timestamp } = post || {};
const purged = post?.commentModeration?.purged;
const title = post?.title?.trim();
const { address, shortAddress } = author || {};
@@ -107,7 +108,7 @@ const PostInfo = ({
const isReply = parentCid;
const { showOmittedReplies } = useShowOmittedReplies();
const directories = useDirectories();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, directories) : undefined;
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
const postMenuProps = selectPostMenuProps(post);
const params = useParams();
@@ -125,7 +126,7 @@ const PostInfo = ({
// Check if post is pending approval and user is mod (for post page view)
const pendingApproval = post?.pendingApproval;
const shouldShowPendingApprovalButtons = isInPostPageView && !isInModQueueView && pendingApproval && isAccountMod && subplebbitAddress;
const shouldShowPendingApprovalButtons = isInPostPageView && !isInModQueueView && pendingApproval && isAccountMod && communityAddress;
// Moderation actions for pending approval posts
const {
@@ -134,7 +135,7 @@ const PostInfo = ({
error: approvePendingError,
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
communityAddress: shouldShowPendingApprovalButtons ? communityAddress : undefined,
commentModeration: approvePendingCommentModeration,
onChallenge: async (...args: any) => {
addChallenge([...args, post]);
@@ -153,7 +154,7 @@ const PostInfo = ({
error: rejectPendingError,
} = usePublishCommentModeration({
commentCid: cid,
subplebbitAddress: shouldShowPendingApprovalButtons ? subplebbitAddress : undefined,
communityAddress: shouldShowPendingApprovalButtons ? communityAddress : undefined,
commentModeration: rejectPendingCommentModeration,
onChallenge: async (...args: any) => {
addChallenge([...args, post]);
@@ -220,7 +221,7 @@ const PostInfo = ({
const userIDBackgroundColor = hashStringToColor(userID);
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);
const pseudonymityMode = useBoardPseudonymityMode(subplebbitAddress);
const pseudonymityMode = useBoardPseudonymityMode(communityAddress);
const showUserID = pseudonymityMode === 'per-post';
const handleUserAddressClick = useAuthorAddressClick();
@@ -246,7 +247,7 @@ const PostInfo = ({
? isReply
? alert(t('this_reply_was_removed'))
: alert(t('this_thread_was_removed'))
: openReplyModal && openReplyModal(cid, post?.number, postCid, threadNumber, subplebbitAddress);
: openReplyModal && openReplyModal(cid, post?.number, postCid, threadNumber, communityAddress);
};
const threadRoute = cid ? (boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`) : undefined;
@@ -552,7 +553,7 @@ interface PostMediaProps {
linkHeight: number;
linkWidth: number;
parentCid: string;
subplebbitAddress: string;
communityAddress?: string;
isInAllView: boolean;
isInSubscriptionsView: boolean;
isInModView: boolean;
@@ -568,7 +569,7 @@ const PostMedia = ({
linkHeight,
linkWidth,
parentCid,
subplebbitAddress,
communityAddress,
isInAllView,
isInSubscriptionsView,
isInModView,
@@ -589,20 +590,22 @@ const PostMedia = ({
const [showThumbnail, setShowThumbnail] = useState(true);
const mediaDimensions = getMediaDimensions(commentMediaInfo);
const directoryEntry = findDirectoryByAddress(directories, subplebbitAddress);
const directoryEntry = findDirectoryByAddress(directories, communityAddress);
const requirePostLinkIsMedia = directoryEntry?.features?.requirePostLinkIsMedia === true;
const boardPath = getBoardPath(subplebbitAddress, directories);
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
const displayBoardPath =
boardPath !== subplebbitAddress
boardPath && communityAddress && boardPath !== communityAddress
? boardPath
: subplebbitAddress.endsWith('.eth') || subplebbitAddress.endsWith('.sol')
? subplebbitAddress
: getShortAddress(subplebbitAddress);
: communityAddress && (communityAddress.endsWith('.eth') || communityAddress.endsWith('.sol'))
? communityAddress
: communityAddress
? getShortAddress(communityAddress)
: undefined;
return (
<div className={styles.file}>
<div className={styles.fileText}>
{subplebbitAddress && (isInAllView || isInSubscriptionsView || isInModView) && boardPath && !parentCid && (
{communityAddress && (isInAllView || isInSubscriptionsView || isInModView) && boardPath && !parentCid && (
<>
{t('board')}: <Link to={`/${boardPath}`}>{displayBoardPath}</Link>{' '}
</>
@@ -701,11 +704,12 @@ const Reply = ({
if (editedComment) {
post = editedComment;
}
post = withResolvedCommentCommunityAddress(post);
const { author, cid, deleted, link, linkHeight, linkWidth, postCid, reason, removed, spoiler, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
const { author, cid, deleted, link, linkHeight, linkWidth, postCid, reason, removed, spoiler, communityAddress, thumbnailUrl, parentCid } = post || {};
const purged = post?.commentModeration?.purged;
const directories = useDirectories();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, directories) : undefined;
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
const location = useLocation();
const route = boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`;
@@ -746,7 +750,7 @@ const Reply = ({
linkHeight={linkHeight}
linkWidth={linkWidth}
parentCid={parentCid}
subplebbitAddress={subplebbitAddress}
communityAddress={communityAddress}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
@@ -774,8 +778,10 @@ const PostDesktop = ({
onReject,
}: PostProps) => {
const { t } = useTranslation();
const { author, cid, content, deleted, link, linkHeight, linkWidth, pinned, postCid, removed, spoiler, state, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
const purged = post?.commentModeration?.purged;
const resolvedPost = withResolvedCommentCommunityAddress(post);
const { author, cid, content, deleted, link, linkHeight, linkWidth, pinned, postCid, removed, spoiler, state, communityAddress, thumbnailUrl, parentCid } =
resolvedPost || {};
const purged = resolvedPost?.commentModeration?.purged;
const params = useParams();
const location = useLocation();
const navigationType = useNavigationType();
@@ -786,14 +792,14 @@ const PostDesktop = ({
const isInModView = isModView(location.pathname);
const isMultiboardView = isInAllView || isInSubscriptionsView || isInModView;
const directories = useDirectories();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, directories) : undefined;
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
const displayBoardPath =
boardPath && subplebbitAddress
? boardPath !== subplebbitAddress
boardPath && communityAddress
? boardPath !== communityAddress
? boardPath
: subplebbitAddress.endsWith('.eth') || subplebbitAddress.endsWith('.sol')
? subplebbitAddress
: getShortAddress(subplebbitAddress)
: communityAddress.endsWith('.eth') || communityAddress.endsWith('.sol')
? communityAddress
: getShortAddress(communityAddress)
: undefined;
const { hidden, unhide, hide } = useHide({ cid });
@@ -805,14 +811,14 @@ const PostDesktop = ({
const shouldFetchFull = showReplies && !isModQueue && (showAllReplies || showOmittedReplies[cid]);
const previewRepliesResult = useReplies({
comment: shouldFetchPreview ? post : undefined,
comment: shouldFetchPreview ? resolvedPost : undefined,
sortType: 'new',
flat: true,
repliesPerPage: BOARD_REPLIES_PREVIEW_FETCH_SIZE,
accountComments: { newerThan: Infinity, append: true },
});
const fullRepliesResult = useReplies({
comment: shouldFetchFull ? post : undefined,
comment: shouldFetchFull ? resolvedPost : undefined,
sortType: 'old',
flat: true,
repliesPerPage: REPLIES_PER_PAGE,
@@ -839,7 +845,7 @@ const PostDesktop = ({
: previewReplies
: getPreviewDisplayReplies(previewReplies, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT);
const freshRepliesForRender = useFreshReplies(repliesForRender);
useRegisterFreshReplies(post, freshRepliesForRender);
useRegisterFreshReplies(resolvedPost, freshRepliesForRender);
const setResetFunction = useFeedResetStore((s) => s.setResetFunction);
useEffect(() => {
if ((isInPostPageView || isInPendingPostView) && reset) {
@@ -848,12 +854,12 @@ const PostDesktop = ({
});
}
}, [isInPostPageView, isInPendingPostView, reset, setResetFunction]);
const visiblelinksCount = useCountLinksInReplies(post, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT);
const totalLinksCount = useCountLinksInReplies(post);
const visiblelinksCount = useCountLinksInReplies(resolvedPost, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT);
const totalLinksCount = useCountLinksInReplies(resolvedPost);
const replyCount = freshRepliesForRender.length;
const totalReplyCount = getTotalReplyCount({
replyCount: post?.replyCount,
replyCount: resolvedPost?.replyCount,
fullLoadedCount: fullReplies.length,
previewLoadedCount: previewReplies.length,
});
@@ -863,9 +869,9 @@ const PostDesktop = ({
});
const linksCount = totalLinksCount - visiblelinksCount;
const stateString = useStateString(post) || t('downloading_board');
const stateString = useStateString(resolvedPost) || t('downloading_board');
const hasFailedState = state === 'failed';
const { canDeleteFailedPost, isDeletingFailedPost, onDeleteFailedPost } = useDeleteFailedPost(post);
const { canDeleteFailedPost, isDeletingFailedPost, onDeleteFailedPost } = useDeleteFailedPost(resolvedPost);
const failedPublishNotice = canDeleteFailedPost ? <FailedPublishNotice isDeleting={isDeletingFailedPost} onDelete={onDeleteFailedPost} /> : undefined;
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
@@ -890,7 +896,7 @@ const PostDesktop = ({
return map;
})();
const quotedByMap = useQuotedByMap(filteredReplies, subplebbitAddress);
const quotedByMap = useQuotedByMap(filteredReplies, communityAddress);
const visibleReplies = useProgressiveRender(filteredReplies, {
batchSize: 50,
@@ -966,7 +972,7 @@ const PostDesktop = ({
className={`${styles.opContainer} ${shouldShowSnow() && hasThumbnail ? styles.xmasHatWrapper : ''}`}
>
{shouldShowSnow() && hasThumbnail && <img src='assets/xmashat.gif' className={styles.xmasHat} alt='' />}
{!link && !parentCid && subplebbitAddress && isMultiboardView && boardPath && (
{!link && !parentCid && communityAddress && isMultiboardView && boardPath && (
<div className={styles.file}>
<div className={styles.fileText}>
{t('board')}: <Link to={`/${boardPath}`}>{displayBoardPath}</Link>
@@ -984,7 +990,7 @@ const PostDesktop = ({
linkHeight={linkHeight}
linkWidth={linkWidth}
parentCid={parentCid}
subplebbitAddress={subplebbitAddress}
communityAddress={communityAddress}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
@@ -992,10 +998,10 @@ const PostDesktop = ({
)}
<PostInfo
isHidden={hidden}
post={post}
post={resolvedPost}
postReplyCount={replyCount}
roles={roles}
threadNumber={post?.number}
threadNumber={resolvedPost?.number}
isModQueue={isModQueue}
modQueueStatus={modQueueStatus}
modQueueError={modQueueError}
@@ -1006,7 +1012,7 @@ const PostDesktop = ({
directRepliesByParentCid={directRepliesByParentCid}
/>
{!isHidden && !content && !(deleted || removed || purged) && <div className={styles.spacer} />}
{!isHidden && <CommentContent comment={post} prependContent={failedPublishNotice} />}
{!isHidden && <CommentContent comment={resolvedPost} prependContent={failedPublishNotice} />}
</div>
{!isHidden && !isInPendingPostView && showReplies && repliesCount > 0 && !isInPostPageView && (
<span className={styles.summary}>
@@ -1042,7 +1048,7 @@ const PostDesktop = ({
</span>
)}
{/* Virtuoso infinite scroll for post page view when there's more content to paginate */}
{!isHidden && showAllReplies && !isInPendingPostView && showReplies && hasMore && !!post?.replyCount && (
{!isHidden && showAllReplies && !isInPendingPostView && showReplies && hasMore && !!resolvedPost?.replyCount && (
<Virtuoso
increaseViewportBy={{ bottom: 1200, top: 1200 }}
totalCount={filteredReplies.length}
@@ -1053,7 +1059,7 @@ const PostDesktop = ({
reply={reply}
roles={roles}
postReplyCount={replyCount}
threadNumber={post?.number}
threadNumber={resolvedPost?.number}
quotedByMap={quotedByMap}
directRepliesByParentCid={directRepliesByParentCid}
/>
@@ -1079,7 +1085,7 @@ const PostDesktop = ({
reply={reply}
roles={roles}
postReplyCount={replyCount}
threadNumber={post?.number}
threadNumber={resolvedPost?.number}
quotedByMap={quotedByMap}
directRepliesByParentCid={directRepliesByParentCid}
/>
@@ -1097,7 +1103,7 @@ const PostDesktop = ({
reply={reply}
roles={roles}
postReplyCount={replyCount}
threadNumber={post?.number}
threadNumber={resolvedPost?.number}
quotedByMap={quotedByMap}
directRepliesByParentCid={directRepliesByParentCid}
/>
@@ -1113,7 +1119,7 @@ const PostDesktop = ({
stateString &&
!hasFailedState &&
state !== 'succeeded' &&
!(post?.timestamp && !post?.updatedAt) &&
!(resolvedPost?.timestamp && !resolvedPost?.updatedAt) &&
isInPostPageView &&
!(!showReplies && !showAllReplies) ? (
<div className={styles.stateString}>
@@ -106,7 +106,7 @@ const basePostMenu = {
linkHeight: 0,
linkWidth: 0,
postCid: 'cid-1',
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
thumbnailUrl: undefined as string | undefined,
};
@@ -39,13 +39,13 @@ const safeCopyToClipboard = async (text: string, label: string): Promise<boolean
};
type CopyLinkButtonProps =
| { cid: string; subplebbitAddress: string; linkType: 'thread'; onClose: () => void }
| { subplebbitAddress: string; linkType: Exclude<ShareLinkType, 'thread'>; onClose: () => void; cid?: undefined };
| { cid: string; communityAddress: string; linkType: 'thread'; onClose: () => void }
| { communityAddress: string; linkType: Exclude<ShareLinkType, 'thread'>; onClose: () => void; cid?: undefined };
const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: CopyLinkButtonProps) => {
const CopyLinkButton = ({ cid, communityAddress, linkType, onClose }: CopyLinkButtonProps) => {
const { t } = useTranslation();
const directories = useDirectories();
const boardIdentifier = getBoardPath(subplebbitAddress, directories);
const boardIdentifier = getBoardPath(communityAddress, directories);
const handleClick = async () => {
await safeCopyShareLink(boardIdentifier, linkType, linkType === 'thread' ? cid : undefined);
onClose();
@@ -163,9 +163,13 @@ type PostMenuDesktopProps = {
postMenu: PostMenuProps;
};
type PostMenuLegacyAddress = Pick<PostMenuProps, 'subplebbitAddress'> & { communityAddress?: string };
const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
const { t } = useTranslation();
const { authorAddress, cid, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = postMenu || {};
const { authorAddress, cid, link, thumbnailUrl, linkWidth, linkHeight, postCid } = postMenu || {};
const postMenuLegacyAddress = (postMenu as PostMenuLegacyAddress) || {};
const resolvedCommunityAddress = postMenuLegacyAddress.communityAddress || postMenuLegacyAddress.subplebbitAddress;
const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth ?? 0, linkHeight ?? 0);
const { thumbnail, type, url } = commentMediaInfo || {};
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
@@ -225,7 +229,7 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
createPortal(
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
{cid && resolvedCommunityAddress && <CopyLinkButton cid={cid} communityAddress={resolvedCommunityAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
{!(isInPostPageView && postCid === cid) && (