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
+24 -20
View File
@@ -1,13 +1,14 @@
import { memo, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@bitsocialnet/bitsocial-react-hooks';
import useSubplebbitsPagesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/subplebbits-pages';
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
import { Comment, Role, useComment, useEditedComment, useCommunity } from '@bitsocialnet/bitsocial-react-hooks';
import useCommunitiesPagesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/communities-pages';
import { useCommunityField } from '../../hooks/use-stable-community';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { isAllView } from '../../lib/utils/view-utils';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import { useDirectories } from '../../hooks/use-directories';
import { areSameBoardAddress, isDirectoryBoard } from '../../lib/utils/route-utils';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import useIsMobile from '../../hooks/use-is-mobile';
import ErrorDisplay from '../../components/error-display/error-display';
import { PageFooterDesktop, ThreadFooterFirstRow, ThreadFooterStyleRow, ThreadFooterMobile } from '../../components/footer';
@@ -17,11 +18,11 @@ import { getRequestedThreadTopCid, scrollThreadContainerToTop } from '../../lib/
import styles from './post.module.css';
// useComment may not return cached feed data immediately due to its updatedAt comparison logic.
// This hook falls back to the subplebbit pages store (populated by useFeed) so content
// This hook falls back to the communities pages store (populated by useFeed) so content
// from the catalog appears instantly instead of going through a loading phase.
const useCommentWithFeedCache = (options: { commentCid: string | undefined }) => {
const comment = useComment(options);
const cachedComment = useSubplebbitsPagesStore((state) => state.comments[options?.commentCid || '']);
const cachedComment = useCommunitiesPagesStore((state) => state.comments[options?.commentCid || '']);
return useMemo(() => {
if (!cachedComment || comment?.timestamp) return comment;
@@ -53,7 +54,8 @@ export interface PostProps {
export const Post = memo(
({ post, showAllReplies = false, showReplies = true, targetReplyCid, isModQueue, modQueueStatus, modQueueError, isPublishing, onApprove, onReject }: PostProps) => {
// Only subscribe to roles field to avoid rerenders from updatingState changes
const roles = useSubplebbitField(post?.subplebbitAddress, (subplebbit) => subplebbit?.roles);
const communityAddress = post?.communityAddress || post?.subplebbitAddress;
const roles = useCommunityField(communityAddress, (community) => community?.roles);
const isMobile = useIsMobile();
let comment = post;
@@ -130,21 +132,23 @@ const PostPage = () => {
const params = useParams();
const location = useLocation();
const { commentCid } = params;
const subplebbitAddress = useResolvedSubplebbitAddress();
const resolvedCommunityAddress = useResolvedCommunityAddress();
const isInAllView = isAllView(location.pathname);
const comment = useCommentWithFeedCache({ commentCid });
const commentCommunityAddress = getCommentCommunityAddress(comment);
const communityAddress = resolvedCommunityAddress ?? commentCommunityAddress;
const consumedThreadTopScrollRef = useRef<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
if (comment?.subplebbitAddress && subplebbitAddress && !areSameBoardAddress(comment.subplebbitAddress, subplebbitAddress)) {
if (commentCommunityAddress && resolvedCommunityAddress && !areSameBoardAddress(commentCommunityAddress, resolvedCommunityAddress)) {
navigate('/not-found', { replace: true });
}
}, [comment?.subplebbitAddress, subplebbitAddress, navigate]);
}, [commentCommunityAddress, resolvedCommunityAddress, navigate]);
const subplebbit = useSubplebbit({ subplebbitAddress });
const { error: subplebbitError, shortAddress, title } = subplebbit || {};
const community = useCommunity({ communityAddress });
const { error: communityError, shortAddress, title } = community || {};
const directories = useDirectories();
// if the comment is a reply, return the post comment instead, then the reply will be highlighted in the thread
@@ -192,17 +196,17 @@ const PostPage = () => {
} else if (isDirectory) {
boardTitle = `/${boardIdentifier}/`;
} else {
boardTitle = title ? title : shortAddress || subplebbitAddress || '';
boardTitle = title ? title : shortAddress || communityAddress || '';
}
const postTitle = post?.title?.slice(0, 30) || post?.content?.slice(0, 30);
const postTitlePart = postTitle ? ` - ${postTitle.trim()}...` : '';
document.title = `${boardTitle}${postTitlePart} - 5chan`;
}, [title, shortAddress, subplebbitAddress, post?.title, post?.content, isInAllView, t, params.boardIdentifier, directories]);
}, [title, shortAddress, communityAddress, post?.title, post?.content, isInAllView, t, params.boardIdentifier, directories]);
const shouldShowCommentError = comment?.error?.message && !comment?.cid;
const shouldShowPostError = post?.error && post?.replyCount > 0 && post?.replies?.length === 0;
const shouldShowSubplebbitError = subplebbitError?.message && !post?.cid;
const shouldShowCommunityError = communityError?.message && !post?.cid;
const targetReplyCid = comment?.parentCid ? comment?.cid : undefined;
@@ -214,9 +218,9 @@ const PostPage = () => {
</div>
)}
<Post post={post} showAllReplies={true} targetReplyCid={targetReplyCid} />
{shouldShowSubplebbitError && (
{shouldShowCommunityError && (
<div className={styles.error}>
<ErrorDisplay error={subplebbitError} />
<ErrorDisplay error={communityError} />
</div>
)}
{shouldShowCommentError && (
@@ -224,13 +228,13 @@ const PostPage = () => {
<ErrorDisplay error={comment?.error} />
</div>
)}
{post?.cid && subplebbitAddress ? (
{post?.cid && communityAddress ? (
<>
<PageFooterDesktop
firstRow={<ThreadFooterFirstRow postCid={post.cid} threadNumber={post?.number} subplebbitAddress={subplebbitAddress} isThreadClosed={!!post?.locked} />}
firstRow={<ThreadFooterFirstRow postCid={post.cid} threadNumber={post?.number} communityAddress={communityAddress} isThreadClosed={!!post?.locked} />}
styleRow={<ThreadFooterStyleRow />}
/>
<ThreadFooterMobile postCid={post.cid} threadNumber={post?.number} subplebbitAddress={subplebbitAddress} isThreadClosed={!!post?.locked} />
<ThreadFooterMobile postCid={post.cid} threadNumber={post?.number} communityAddress={communityAddress} isThreadClosed={!!post?.locked} />
</>
) : null}
</div>