From c7c8c46557f09decee5aadaf0e65a087bd7c725b Mon Sep 17 00:00:00 2001 From: plebeius Date: Mon, 9 Feb 2026 19:56:07 +0800 Subject: [PATCH] feat(post): add backlinks for quotedCids --- src/components/post-desktop/post-desktop.tsx | 37 ++++++++++++++++--- src/components/post-mobile/post-mobile.tsx | 38 ++++++++++++++++---- src/views/post/post.tsx | 1 + 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index e5f821bc..f3bc86f3 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -73,6 +73,7 @@ const PostInfo = ({ isPublishing, onApprove, onReject, + quotedByMap, }: PostProps) => { const { t } = useTranslation(); const { author, cid, deleted, locked, pinned, parentCid, postCid, reason, removed, state, subplebbitAddress, timestamp } = post || {}; @@ -425,6 +426,16 @@ const PostInfo = ({ reply?.cid && !(reply?.deleted || reply?.removed) && , )} + {cid && + parentCid && + quotedByMap + ?.get(cid) + ?.map( + (reply: Comment, index: number) => + reply?.parentCid !== cid && + reply?.cid && + !(reply?.deleted || reply?.removed) && , + )} ); @@ -527,7 +538,7 @@ const PostMedia = ({ ); }; -const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => { +const Reply = ({ postReplyCount, reply, roles, threadNumber, quotedByMap }: PostProps) => { let post = reply; // handle pending mod or author edit const { editedComment } = useEditedComment({ comment: reply }); @@ -555,7 +566,7 @@ const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => {
{'>>'}
- + {link && !hidden && !(deleted || removed) && isValidURL(link) && ( (replies || []).filter((reply) => !(reply.deleted && (reply.replyCount === 0 || !reply.replyCount))), [replies]); + // Compute quotedByMap: map each quoted CID to array of replies that quote it + const quotedByMap = useMemo(() => { + const map = new Map(); + for (const reply of filteredReplies) { + const quotedCids = reply.quotedCids; + if (quotedCids?.length) { + for (const quotedCid of quotedCids) { + const arr = map.get(quotedCid); + if (arr) arr.push(reply); + else map.set(quotedCid, [reply]); + } + } + } + return map; + }, [filteredReplies]); + // Virtuoso scroll position management for infinite replies const virtuosoRef = useRef(null); const virtuosoStateKey = `replies-desktop-${cid}`; @@ -747,7 +774,7 @@ const PostDesktop = ({ data={filteredReplies} itemContent={(index, reply) => (
- +
)} useWindowScroll={true} @@ -766,7 +793,7 @@ const PostDesktop = ({ !hasMore && filteredReplies.map((reply, index) => (
- +
))} {/* Non-virtualized rendering for board view (last 5 replies or show omitted) */} @@ -778,7 +805,7 @@ const PostDesktop = ({ showReplies && (showOmittedReplies[cid] ? filteredReplies : filteredReplies.slice(-5)).map((reply, index) => (
- +
))}
diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index 9a0fc77c..4a8d5dee 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -377,14 +377,14 @@ const PostMediaContent = ({ post, link }: { post: any; link: string }) => { ); }; -const ReplyBacklinks = ({ post }: PostProps) => { +const ReplyBacklinks = ({ post, quotedByMap }: PostProps) => { const { cid, parentCid } = post || {}; const { replies } = useReplies({ comment: post, flat: true, accountComments: { newerThan: Infinity } }); return ( cid && parentCid && - replies.length > 0 && ( + (replies.length > 0 || quotedByMap?.get(cid)?.length) && (
{replies.map( (reply: Comment, index: number) => @@ -392,12 +392,20 @@ const ReplyBacklinks = ({ post }: PostProps) => { reply?.cid && !(reply?.deleted || reply?.removed) && , )} + {quotedByMap + ?.get(cid) + ?.map( + (reply: Comment, index: number) => + reply?.parentCid !== cid && + reply?.cid && + !(reply?.deleted || reply?.removed) && , + )}
) ); }; -const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => { +const Reply = ({ postReplyCount, reply, roles, threadNumber, quotedByMap }: PostProps) => { let post = reply; // handle pending mod or author edit const { editedComment } = useEditedComment({ comment: reply }); @@ -423,7 +431,7 @@ const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => { > {!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && } - +
@@ -463,6 +471,22 @@ const PostMobile = ({ // Filter out deleted replies with no children for both virtuoso and non-virtuoso rendering const filteredReplies = useMemo(() => (replies || []).filter((reply) => !(reply.deleted && (reply.replyCount === 0 || !reply.replyCount))), [replies]); + // Compute quotedByMap: map each quoted CID to array of replies that quote it + const quotedByMap = useMemo(() => { + const map = new Map(); + for (const reply of filteredReplies) { + const quotedCids = reply.quotedCids; + if (quotedCids?.length) { + for (const quotedCid of quotedCids) { + const arr = map.get(quotedCid); + if (arr) arr.push(reply); + else map.set(quotedCid, [reply]); + } + } + } + return map; + }, [filteredReplies]); + // Virtuoso scroll position management for infinite replies const virtuosoRef = useRef(null); const virtuosoStateKey = `replies-mobile-${cid}`; @@ -578,7 +602,7 @@ const PostMobile = ({ data={filteredReplies} itemContent={(index, reply) => (
- +
)} useWindowScroll={true} @@ -597,7 +621,7 @@ const PostMobile = ({ !hasMore && filteredReplies.map((reply, index) => (
- +
))} {/* Non-virtualized rendering for board view (last 5 replies) */} @@ -608,7 +632,7 @@ const PostMobile = ({ showReplies && filteredReplies.slice(-5).map((reply, index) => (
- +
))} diff --git a/src/views/post/post.tsx b/src/views/post/post.tsx index e1416051..cebcea9b 100644 --- a/src/views/post/post.tsx +++ b/src/views/post/post.tsx @@ -31,6 +31,7 @@ export interface PostProps { isPublishing?: boolean; onApprove?: () => void; onReject?: () => void; + quotedByMap?: Map; } export const Post = ({