import { memo, useMemo, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Comment, Subplebbit, useAccount } from '@plebbit/plebbit-react-hooks';
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
import styles from '../home.module.css';
import usePopularPosts from '../../../hooks/use-popular-posts';
import usePopularThreadsOptionsStore from '../../../stores/use-popular-threads-options-store';
import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
import { CatalogPostMedia } from '../../../components/catalog-row';
import LoadingEllipsis from '../../../components/loading-ellipsis';
import BoxModal from '../box-modal';
import { MultisubSubplebbit, useDefaultSubplebbits } from '../../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../../lib/utils/route-utils';
import { removeMarkdown } from '../../../lib/utils/post-utils';
import { useStableSubplebbits } from '../../../hooks/use-stable-subplebbit';
interface PopularThreadProps {
post: Comment;
multisub: MultisubSubplebbit[];
}
export const ContentPreview = ({ content, maxLength = 99 }: { content: string; maxLength?: number }) => {
const plainText = removeMarkdown(content).trim().replaceAll(' ', '').replace(/\n\n/g, '\n').replaceAll('\n\n', '');
const truncatedText = plainText.length > maxLength ? `${plainText.substring(0, maxLength).trim()}...` : plainText;
return truncatedText;
};
// Memoize to prevent rerenders when parent rerenders due to updatingState
const PopularThreadCard = memo(
({ post, multisub }: PopularThreadProps) => {
const { cid, content, link, linkHeight, linkWidth, subplebbitAddress, thumbnailUrl, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
const defaultSubplebbits = useDefaultSubplebbits();
// Find the matching MultisubSubplebbit entry and get its title
const multisubEntry = multisub.find((ms) => ms?.address === subplebbitAddress);
const boardTitle = multisubEntry?.title?.replace(/^\/[^/]+\/\s*-\s*/, '') || '';
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
return (
{boardTitle}
{title && (
<>
{title.trim()}
{content && ': '}
>
)}
{content && }
);
},
// Custom equality: rerender if post.cid or multisub entry title changes
(prevProps, nextProps) => {
if (prevProps.post?.cid !== nextProps.post?.cid) return false;
// Compare the relevant multisub entry for this post's subplebbitAddress
const prevEntry = prevProps.multisub.find((ms) => ms?.address === prevProps.post?.subplebbitAddress);
const nextEntry = nextProps.multisub.find((ms) => ms?.address === nextProps.post?.subplebbitAddress);
return prevEntry?.title === nextEntry?.title;
},
);
// Uses stable subplebbits hook to avoid re-renders from updatingState changes
const PopularThreadsBox = ({ multisub, subplebbitAddresses }: { multisub: MultisubSubplebbit[]; subplebbitAddresses: string[] }) => {
const { t } = useTranslation();
const { showWorksafeContentOnly, showNsfwContentOnly } = usePopularThreadsOptionsStore();
const account = useAccount();
const addSubplebbitToStore = useSubplebbitsStore((state) => state.addSubplebbitToStore);
// Trigger fetching subplebbits (same as useSubplebbits does internally)
useEffect(() => {
if (!account || !subplebbitAddresses) return;
for (const address of subplebbitAddresses) {
addSubplebbitToStore(address, account).catch(() => {});
}
}, [subplebbitAddresses?.toString(), account?.id]);
// Use stable hook that only re-renders when actual content changes
const subplebbits = useStableSubplebbits(subplebbitAddresses);
const getFilteredSubplebbits = () => {
if (showWorksafeContentOnly) {
return subplebbits.filter((sub: Subplebbit) => {
const multisubEntry = multisub.find((ms) => ms?.address === sub?.address);
return multisubEntry ? !multisubEntry.nsfw : true;
});
}
if (showNsfwContentOnly) {
return subplebbits.filter((sub: Subplebbit) => {
const multisubEntry = multisub.find((ms) => ms?.address === sub?.address);
return multisubEntry ? multisubEntry.nsfw : false;
});
}
return subplebbits;
};
const filteredSubplebbits = useMemo(getFilteredSubplebbits, [subplebbits, showWorksafeContentOnly, showNsfwContentOnly, multisub]);
const { popularPosts } = usePopularPosts(filteredSubplebbits);
const isLoading = popularPosts.length === 0;
return (
{t('popular_threads')}
{isLoading ?
: popularPosts.map((post: any) =>
)}
);
};
export default PopularThreadsBox;