From 216073aee39396d6a8ce6a36fe4c3d70c453df29 Mon Sep 17 00:00:00 2001 From: plebeius Date: Thu, 26 Feb 2026 15:08:33 +0800 Subject: [PATCH] fix(quotes): scope post-number lookup by subplebbit, OP quote always navigates to thread - numberToCid scoped by subplebbit address to fix wrong preview in /all/ - OP quote click navigates to thread page instead of highlighting card --- .../comment-content/comment-content.tsx | 4 ++-- src/components/markdown/markdown.tsx | 17 ++++++++-------- src/components/post-desktop/post-desktop.tsx | 2 +- src/components/post-mobile/post-mobile.tsx | 2 +- .../reply-quote-preview.tsx | 16 +++++++++++---- src/hooks/use-publish-reply.ts | 4 ++-- src/hooks/use-quoted-by-map.ts | 20 +++++++++++++------ src/lib/utils/reply-quote-utils.ts | 4 ++-- src/stores/use-post-number-store.ts | 15 +++++++++++--- 9 files changed, 55 insertions(+), 29 deletions(-) diff --git a/src/components/comment-content/comment-content.tsx b/src/components/comment-content/comment-content.tsx index 25e1736c..d0d58f8d 100644 --- a/src/components/comment-content/comment-content.tsx +++ b/src/components/comment-content/comment-content.tsx @@ -147,7 +147,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => { ) ) : ( <> - {!showOriginal && } + {!showOriginal && } {pendingApproval && ( <>
@@ -182,7 +182,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => { )} {edit && original?.content !== content && ( - {showOriginal && } + {showOriginal && }

{ - const cid = usePostNumberStore((state) => state.numberToCid[number]); +const NumberQuoteLink = ({ number, threadPostCid, subplebbitAddress }: { number: number; threadPostCid?: string; subplebbitAddress?: string }) => { + const cid = usePostNumberStore((state) => (subplebbitAddress ? state.numberToCid[subplebbitAddress]?.[number] : undefined)); const commentFromStore = useSubplebbitsPagesStore((state) => (cid ? state.comments[cid] : undefined)); const commentFromHook = useComment({ commentCid: cid, onlyIfCached: true }); const comment = commentFromHook?.number !== undefined ? commentFromHook : commentFromStore; @@ -204,7 +205,7 @@ const NumberQuoteLink = ({ number, threadPostCid }: { number: number; threadPost return ; }; -const renderAnchorLink = (children: React.ReactNode, href: string, threadPostCid?: string) => { +const renderAnchorLink = (children: React.ReactNode, href: string, threadPostCid?: string, subplebbitAddress?: string) => { if (!href) { return {children}; } @@ -214,7 +215,7 @@ const renderAnchorLink = (children: React.ReactNode, href: string, threadPostCid const number = parseInt(numberQuoteMatch[1], 10); return ( - + ); } @@ -268,7 +269,7 @@ const renderAnchorLink = (children: React.ReactNode, href: string, threadPostCid ); }; -const Markdown = ({ content, title, postCid }: MarkdownProps) => { +const Markdown = ({ content, title, postCid, subplebbitAddress }: MarkdownProps) => { const remarkPlugins = useMemo(() => { const plugins: any[] = [[supersub]]; @@ -338,13 +339,13 @@ const Markdown = ({ content, title, postCid }: MarkdownProps) => { console.debug('Invalid URL:', href); } - return renderAnchorLink(children, href, postCid); + return renderAnchorLink(children, href, postCid, subplebbitAddress); } - return renderAnchorLink(children, href || '', postCid); + return renderAnchorLink(children, href || '', postCid, subplebbitAddress); }, }) as ExtendedComponents, - [isInCatalogView, postCid], + [isInCatalogView, postCid, subplebbitAddress], ); return ( diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index d8db269a..6ab2fba8 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -849,7 +849,7 @@ const PostDesktop = ({ return map; })(); - const quotedByMap = useQuotedByMap(filteredReplies); + const quotedByMap = useQuotedByMap(filteredReplies, subplebbitAddress); const visibleReplies = useProgressiveRender(filteredReplies, { batchSize: 50, diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index 3c219084..86a9b539 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -606,7 +606,7 @@ const PostMobile = ({ return map; })(); - const quotedByMap = useQuotedByMap(filteredReplies); + const quotedByMap = useQuotedByMap(filteredReplies, subplebbitAddress); const visibleReplies = useProgressiveRender(filteredReplies, { batchSize: 50, diff --git a/src/components/reply-quote-preview/reply-quote-preview.tsx b/src/components/reply-quote-preview/reply-quote-preview.tsx index 19932b00..ea7df6bb 100644 --- a/src/components/reply-quote-preview/reply-quote-preview.tsx +++ b/src/components/reply-quote-preview/reply-quote-preview.tsx @@ -131,8 +131,12 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i if (cid && subplebbitAddress) { const boardPath = getBoardPath(subplebbitAddress, directories); const threadRoute = `/${boardPath}/thread/${cid}`; - if (isOpQuote && location.pathname === threadRoute) { - scrollToThreadCardTop(cid); + if (isOpQuote) { + if (location.pathname === threadRoute) { + scrollToThreadCardTop(cid); + } else { + navigate(threadRoute); + } return; } if (scrollToReplyOnPage(cid)) return; @@ -248,8 +252,12 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is if (cid && subplebbitAddress) { const boardPath = getBoardPath(subplebbitAddress, directories); const threadRoute = `/${boardPath}/thread/${cid}`; - if (isOpQuote && location.pathname === threadRoute) { - scrollToThreadCardTop(cid); + if (isOpQuote) { + if (location.pathname === threadRoute) { + scrollToThreadCardTop(cid); + } else { + navigate(threadRoute); + } return; } if (scrollToReplyOnPage(cid)) return; diff --git a/src/hooks/use-publish-reply.ts b/src/hooks/use-publish-reply.ts index 229421a1..c04aef8c 100644 --- a/src/hooks/use-publish-reply.ts +++ b/src/hooks/use-publish-reply.ts @@ -56,8 +56,8 @@ const usePublishReply = ({ cid, subplebbitAddress, postCid }: { cid: string; sub const resetPublishReplyOptions = useCallback(() => resetPublishReplyStore(parentCid), [parentCid, resetPublishReplyStore]); - const numberToCid = usePostNumberStore((state) => state.numberToCid); - const quotedCids = useMemo(() => getQuotedCidsFromContent(content, numberToCid), [content, numberToCid]); + const scopedNumberToCid = usePostNumberStore((state) => (subplebbitAddress ? state.numberToCid[subplebbitAddress] : undefined)); + const quotedCids = useMemo(() => getQuotedCidsFromContent(content, scopedNumberToCid), [content, scopedNumberToCid]); const publishOptions = useMemo(() => mergeQuotedCids(publishCommentOptions, quotedCids), [publishCommentOptions, quotedCids]); diff --git a/src/hooks/use-quoted-by-map.ts b/src/hooks/use-quoted-by-map.ts index a6e9da25..2f622756 100644 --- a/src/hooks/use-quoted-by-map.ts +++ b/src/hooks/use-quoted-by-map.ts @@ -61,32 +61,40 @@ const extractReplyQuoteTargets = (replies: Comment[]) => { }; }; -const useQuotedByMap = (replies: Comment[] = []) => { +const useQuotedByMap = (replies: Comment[] = [], subplebbitAddress?: string) => { const stableQuotedByMapRef = useRef>(new Map()); const { replyQuoteTargets, quotedPostNumbers } = useMemo(() => extractReplyQuoteTargets(replies), [replies]); - // Subscribe only to post numbers referenced in this thread to avoid unrelated global store churn. const quotedNumbersSignature = usePostNumberStore( - useCallback((state) => quotedPostNumbers.map((postNumber) => `${postNumber}:${state.numberToCid[postNumber] ?? ''}`).join('|'), [quotedPostNumbers]), + useCallback( + (state) => { + const scoped = subplebbitAddress ? state.numberToCid[subplebbitAddress] : undefined; + return quotedPostNumbers.map((postNumber) => `${postNumber}:${scoped?.[postNumber] ?? ''}`).join('|'); + }, + [quotedPostNumbers, subplebbitAddress], + ), ); const quotedNumberToCid = useMemo(() => { - if (quotedPostNumbers.length === 0) { + if (quotedPostNumbers.length === 0 || !subplebbitAddress) { return {} as Record; } const { numberToCid } = usePostNumberStore.getState(); + const scoped = numberToCid[subplebbitAddress]; + if (!scoped) return {} as Record; + const nextQuotedNumberToCid: Record = {}; for (const postNumber of quotedPostNumbers) { - const quotedCid = numberToCid[postNumber]; + const quotedCid = scoped[postNumber]; if (quotedCid) { nextQuotedNumberToCid[postNumber] = quotedCid; } } return nextQuotedNumberToCid; - }, [quotedPostNumbers, quotedNumbersSignature]); + }, [quotedPostNumbers, subplebbitAddress, quotedNumbersSignature]); return useMemo(() => { const map = new Map(); diff --git a/src/lib/utils/reply-quote-utils.ts b/src/lib/utils/reply-quote-utils.ts index b00fa991..a8dc4e4c 100644 --- a/src/lib/utils/reply-quote-utils.ts +++ b/src/lib/utils/reply-quote-utils.ts @@ -1,7 +1,7 @@ import { QUOTE_NUMBER_REGEX } from './url-utils'; -export const getQuotedCidsFromContent = (content: string | undefined, numberToCid: Record) => { - if (!content) return undefined; +export const getQuotedCidsFromContent = (content: string | undefined, numberToCid: Record | undefined) => { + if (!content || !numberToCid) return undefined; const cids = new Set(); for (const match of content.matchAll(new RegExp(QUOTE_NUMBER_REGEX.source, 'g'))) { const num = parseInt(match[1], 10); diff --git a/src/stores/use-post-number-store.ts b/src/stores/use-post-number-store.ts index 5a494bf0..df540e88 100644 --- a/src/stores/use-post-number-store.ts +++ b/src/stores/use-post-number-store.ts @@ -2,7 +2,9 @@ import { create } from 'zustand'; import type { Comment } from '@plebbit/plebbit-react-hooks'; interface PostNumberState { - numberToCid: Record; + // Post numbers are only unique within a subplebbit, so scope by address + // to avoid collisions in /all/ where multiple boards are shown together. + numberToCid: Record>; cidToNumber: Record; registerComments: (comments: Comment[]) => void; } @@ -21,13 +23,20 @@ const usePostNumberStore = create((set) => ({ for (const c of comments) { const num = c?.number; const cid = c?.cid; - if (typeof num === 'number' && cid && (nextNumberToCid[num] !== cid || nextCidToNumber[cid] !== num)) { + const addr = c?.subplebbitAddress; + if (typeof num !== 'number' || !cid || !addr) continue; + + const existingCid = nextNumberToCid[addr]?.[num]; + if (existingCid !== cid || nextCidToNumber[cid] !== num) { if (!hasUpdates) { nextNumberToCid = { ...state.numberToCid }; nextCidToNumber = { ...state.cidToNumber }; hasUpdates = true; } - nextNumberToCid[num] = cid; + if (!nextNumberToCid[addr] || nextNumberToCid[addr] === state.numberToCid[addr]) { + nextNumberToCid[addr] = { ...nextNumberToCid[addr] }; + } + nextNumberToCid[addr][num] = cid; nextCidToNumber[cid] = num; } }