mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
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
This commit is contained in:
@@ -147,7 +147,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
|||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{!showOriginal && <Markdown content={displayContent} postCid={postCid} />}
|
{!showOriginal && <Markdown content={displayContent} postCid={postCid} subplebbitAddress={subplebbitAddress} />}
|
||||||
{pendingApproval && (
|
{pendingApproval && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
@@ -182,7 +182,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
|||||||
)}
|
)}
|
||||||
{edit && original?.content !== content && (
|
{edit && original?.content !== content && (
|
||||||
<span className={styles.editedInfo}>
|
<span className={styles.editedInfo}>
|
||||||
{showOriginal && <Markdown content={original?.content} postCid={postCid} />}
|
{showOriginal && <Markdown content={original?.content} postCid={postCid} subplebbitAddress={subplebbitAddress} />}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<Trans
|
<Trans
|
||||||
|
|||||||
@@ -186,12 +186,13 @@ interface MarkdownProps {
|
|||||||
content: string;
|
content: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
postCid?: string;
|
postCid?: string;
|
||||||
|
subplebbitAddress?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NUMBER_QUOTE_HREF_REGEX = /^#q-(\d+)$/;
|
const NUMBER_QUOTE_HREF_REGEX = /^#q-(\d+)$/;
|
||||||
|
|
||||||
const NumberQuoteLink = ({ number, threadPostCid }: { number: number; threadPostCid?: string }) => {
|
const NumberQuoteLink = ({ number, threadPostCid, subplebbitAddress }: { number: number; threadPostCid?: string; subplebbitAddress?: string }) => {
|
||||||
const cid = usePostNumberStore((state) => state.numberToCid[number]);
|
const cid = usePostNumberStore((state) => (subplebbitAddress ? state.numberToCid[subplebbitAddress]?.[number] : undefined));
|
||||||
const commentFromStore = useSubplebbitsPagesStore((state) => (cid ? state.comments[cid] : undefined));
|
const commentFromStore = useSubplebbitsPagesStore((state) => (cid ? state.comments[cid] : undefined));
|
||||||
const commentFromHook = useComment({ commentCid: cid, onlyIfCached: true });
|
const commentFromHook = useComment({ commentCid: cid, onlyIfCached: true });
|
||||||
const comment = commentFromHook?.number !== undefined ? commentFromHook : commentFromStore;
|
const comment = commentFromHook?.number !== undefined ? commentFromHook : commentFromStore;
|
||||||
@@ -204,7 +205,7 @@ const NumberQuoteLink = ({ number, threadPostCid }: { number: number; threadPost
|
|||||||
return <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={comment} isOP={isOP} showTrailingBreak={false} />;
|
return <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={comment} isOP={isOP} showTrailingBreak={false} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderAnchorLink = (children: React.ReactNode, href: string, threadPostCid?: string) => {
|
const renderAnchorLink = (children: React.ReactNode, href: string, threadPostCid?: string, subplebbitAddress?: string) => {
|
||||||
if (!href) {
|
if (!href) {
|
||||||
return <span>{children}</span>;
|
return <span>{children}</span>;
|
||||||
}
|
}
|
||||||
@@ -214,7 +215,7 @@ const renderAnchorLink = (children: React.ReactNode, href: string, threadPostCid
|
|||||||
const number = parseInt(numberQuoteMatch[1], 10);
|
const number = parseInt(numberQuoteMatch[1], 10);
|
||||||
return (
|
return (
|
||||||
<span className={styles.inlineQuoteLink}>
|
<span className={styles.inlineQuoteLink}>
|
||||||
<NumberQuoteLink number={number} threadPostCid={threadPostCid} />
|
<NumberQuoteLink number={number} threadPostCid={threadPostCid} subplebbitAddress={subplebbitAddress} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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 remarkPlugins = useMemo(() => {
|
||||||
const plugins: any[] = [[supersub]];
|
const plugins: any[] = [[supersub]];
|
||||||
|
|
||||||
@@ -338,13 +339,13 @@ const Markdown = ({ content, title, postCid }: MarkdownProps) => {
|
|||||||
console.debug('Invalid URL:', href);
|
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,
|
}) as ExtendedComponents,
|
||||||
[isInCatalogView, postCid],
|
[isInCatalogView, postCid, subplebbitAddress],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -849,7 +849,7 @@ const PostDesktop = ({
|
|||||||
return map;
|
return map;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const quotedByMap = useQuotedByMap(filteredReplies);
|
const quotedByMap = useQuotedByMap(filteredReplies, subplebbitAddress);
|
||||||
|
|
||||||
const visibleReplies = useProgressiveRender(filteredReplies, {
|
const visibleReplies = useProgressiveRender(filteredReplies, {
|
||||||
batchSize: 50,
|
batchSize: 50,
|
||||||
|
|||||||
@@ -606,7 +606,7 @@ const PostMobile = ({
|
|||||||
return map;
|
return map;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const quotedByMap = useQuotedByMap(filteredReplies);
|
const quotedByMap = useQuotedByMap(filteredReplies, subplebbitAddress);
|
||||||
|
|
||||||
const visibleReplies = useProgressiveRender(filteredReplies, {
|
const visibleReplies = useProgressiveRender(filteredReplies, {
|
||||||
batchSize: 50,
|
batchSize: 50,
|
||||||
|
|||||||
@@ -131,8 +131,12 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
|
|||||||
if (cid && subplebbitAddress) {
|
if (cid && subplebbitAddress) {
|
||||||
const boardPath = getBoardPath(subplebbitAddress, directories);
|
const boardPath = getBoardPath(subplebbitAddress, directories);
|
||||||
const threadRoute = `/${boardPath}/thread/${cid}`;
|
const threadRoute = `/${boardPath}/thread/${cid}`;
|
||||||
if (isOpQuote && location.pathname === threadRoute) {
|
if (isOpQuote) {
|
||||||
scrollToThreadCardTop(cid);
|
if (location.pathname === threadRoute) {
|
||||||
|
scrollToThreadCardTop(cid);
|
||||||
|
} else {
|
||||||
|
navigate(threadRoute);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (scrollToReplyOnPage(cid)) return;
|
if (scrollToReplyOnPage(cid)) return;
|
||||||
@@ -248,8 +252,12 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
|
|||||||
if (cid && subplebbitAddress) {
|
if (cid && subplebbitAddress) {
|
||||||
const boardPath = getBoardPath(subplebbitAddress, directories);
|
const boardPath = getBoardPath(subplebbitAddress, directories);
|
||||||
const threadRoute = `/${boardPath}/thread/${cid}`;
|
const threadRoute = `/${boardPath}/thread/${cid}`;
|
||||||
if (isOpQuote && location.pathname === threadRoute) {
|
if (isOpQuote) {
|
||||||
scrollToThreadCardTop(cid);
|
if (location.pathname === threadRoute) {
|
||||||
|
scrollToThreadCardTop(cid);
|
||||||
|
} else {
|
||||||
|
navigate(threadRoute);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (scrollToReplyOnPage(cid)) return;
|
if (scrollToReplyOnPage(cid)) return;
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ const usePublishReply = ({ cid, subplebbitAddress, postCid }: { cid: string; sub
|
|||||||
|
|
||||||
const resetPublishReplyOptions = useCallback(() => resetPublishReplyStore(parentCid), [parentCid, resetPublishReplyStore]);
|
const resetPublishReplyOptions = useCallback(() => resetPublishReplyStore(parentCid), [parentCid, resetPublishReplyStore]);
|
||||||
|
|
||||||
const numberToCid = usePostNumberStore((state) => state.numberToCid);
|
const scopedNumberToCid = usePostNumberStore((state) => (subplebbitAddress ? state.numberToCid[subplebbitAddress] : undefined));
|
||||||
const quotedCids = useMemo(() => getQuotedCidsFromContent(content, numberToCid), [content, numberToCid]);
|
const quotedCids = useMemo(() => getQuotedCidsFromContent(content, scopedNumberToCid), [content, scopedNumberToCid]);
|
||||||
|
|
||||||
const publishOptions = useMemo(() => mergeQuotedCids(publishCommentOptions, quotedCids), [publishCommentOptions, quotedCids]);
|
const publishOptions = useMemo(() => mergeQuotedCids(publishCommentOptions, quotedCids), [publishCommentOptions, quotedCids]);
|
||||||
|
|
||||||
|
|||||||
@@ -61,32 +61,40 @@ const extractReplyQuoteTargets = (replies: Comment[]) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const useQuotedByMap = (replies: Comment[] = []) => {
|
const useQuotedByMap = (replies: Comment[] = [], subplebbitAddress?: string) => {
|
||||||
const stableQuotedByMapRef = useRef<Map<string, Comment[]>>(new Map());
|
const stableQuotedByMapRef = useRef<Map<string, Comment[]>>(new Map());
|
||||||
const { replyQuoteTargets, quotedPostNumbers } = useMemo(() => extractReplyQuoteTargets(replies), [replies]);
|
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(
|
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(() => {
|
const quotedNumberToCid = useMemo(() => {
|
||||||
if (quotedPostNumbers.length === 0) {
|
if (quotedPostNumbers.length === 0 || !subplebbitAddress) {
|
||||||
return {} as Record<number, string>;
|
return {} as Record<number, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { numberToCid } = usePostNumberStore.getState();
|
const { numberToCid } = usePostNumberStore.getState();
|
||||||
|
const scoped = numberToCid[subplebbitAddress];
|
||||||
|
if (!scoped) return {} as Record<number, string>;
|
||||||
|
|
||||||
const nextQuotedNumberToCid: Record<number, string> = {};
|
const nextQuotedNumberToCid: Record<number, string> = {};
|
||||||
|
|
||||||
for (const postNumber of quotedPostNumbers) {
|
for (const postNumber of quotedPostNumbers) {
|
||||||
const quotedCid = numberToCid[postNumber];
|
const quotedCid = scoped[postNumber];
|
||||||
if (quotedCid) {
|
if (quotedCid) {
|
||||||
nextQuotedNumberToCid[postNumber] = quotedCid;
|
nextQuotedNumberToCid[postNumber] = quotedCid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextQuotedNumberToCid;
|
return nextQuotedNumberToCid;
|
||||||
}, [quotedPostNumbers, quotedNumbersSignature]);
|
}, [quotedPostNumbers, subplebbitAddress, quotedNumbersSignature]);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const map = new Map<string, Comment[]>();
|
const map = new Map<string, Comment[]>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { QUOTE_NUMBER_REGEX } from './url-utils';
|
import { QUOTE_NUMBER_REGEX } from './url-utils';
|
||||||
|
|
||||||
export const getQuotedCidsFromContent = (content: string | undefined, numberToCid: Record<number, string>) => {
|
export const getQuotedCidsFromContent = (content: string | undefined, numberToCid: Record<number, string> | undefined) => {
|
||||||
if (!content) return undefined;
|
if (!content || !numberToCid) return undefined;
|
||||||
const cids = new Set<string>();
|
const cids = new Set<string>();
|
||||||
for (const match of content.matchAll(new RegExp(QUOTE_NUMBER_REGEX.source, 'g'))) {
|
for (const match of content.matchAll(new RegExp(QUOTE_NUMBER_REGEX.source, 'g'))) {
|
||||||
const num = parseInt(match[1], 10);
|
const num = parseInt(match[1], 10);
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { create } from 'zustand';
|
|||||||
import type { Comment } from '@plebbit/plebbit-react-hooks';
|
import type { Comment } from '@plebbit/plebbit-react-hooks';
|
||||||
|
|
||||||
interface PostNumberState {
|
interface PostNumberState {
|
||||||
numberToCid: Record<number, string>;
|
// 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<string, Record<number, string>>;
|
||||||
cidToNumber: Record<string, number>;
|
cidToNumber: Record<string, number>;
|
||||||
registerComments: (comments: Comment[]) => void;
|
registerComments: (comments: Comment[]) => void;
|
||||||
}
|
}
|
||||||
@@ -21,13 +23,20 @@ const usePostNumberStore = create<PostNumberState>((set) => ({
|
|||||||
for (const c of comments) {
|
for (const c of comments) {
|
||||||
const num = c?.number;
|
const num = c?.number;
|
||||||
const cid = c?.cid;
|
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) {
|
if (!hasUpdates) {
|
||||||
nextNumberToCid = { ...state.numberToCid };
|
nextNumberToCid = { ...state.numberToCid };
|
||||||
nextCidToNumber = { ...state.cidToNumber };
|
nextCidToNumber = { ...state.cidToNumber };
|
||||||
hasUpdates = true;
|
hasUpdates = true;
|
||||||
}
|
}
|
||||||
nextNumberToCid[num] = cid;
|
if (!nextNumberToCid[addr] || nextNumberToCid[addr] === state.numberToCid[addr]) {
|
||||||
|
nextNumberToCid[addr] = { ...nextNumberToCid[addr] };
|
||||||
|
}
|
||||||
|
nextNumberToCid[addr][num] = cid;
|
||||||
nextCidToNumber[cid] = num;
|
nextCidToNumber[cid] = num;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user