mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat: render >>{number} as interactive quote links with hover preview
Added bidirectional `usePostNumberStore` for post number ↔ CID mapping, populated from thread views. Preprocesses `>>{number}` into markdown links, renders them via `NumberQuoteLink` and `ReplyQuotePreview`, and deduplicates against `quotedCids` when both refer to the same post.
This commit is contained in:
@@ -38,6 +38,7 @@ import { shouldShowSnow } from '../../lib/snow';
|
||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
|
||||
import useChallengesStore from '../../stores/use-challenges-store';
|
||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||
import { usePublishCommentModeration } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
@@ -618,6 +619,20 @@ const PostDesktop = ({
|
||||
const isHidden = hidden && !isInPostPageView;
|
||||
|
||||
const { replies, hasMore, loadMore } = useReplies({ comment: post, flat: true, accountComments: { newerThan: Infinity } });
|
||||
const registerComments = usePostNumberStore((s) => s.registerComments);
|
||||
const prevCidsRef = useRef<string>('');
|
||||
useEffect(() => {
|
||||
const all = post ? [post, ...(replies || [])] : replies || [];
|
||||
if (!all.length) return;
|
||||
const cidsKey = all
|
||||
.map((c) => c?.cid)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.join(',');
|
||||
if (cidsKey === prevCidsRef.current) return;
|
||||
prevCidsRef.current = cidsKey;
|
||||
registerComments(all);
|
||||
}, [post, replies, registerComments]);
|
||||
const visiblelinksCount = useCountLinksInReplies(post, 5);
|
||||
const totalLinksCount = useCountLinksInReplies(post);
|
||||
const replyCount = replies?.length;
|
||||
|
||||
@@ -33,6 +33,7 @@ import _ from 'lodash';
|
||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
|
||||
import useChallengesStore from '../../stores/use-challenges-store';
|
||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||
|
||||
const { addChallenge } = useChallengesStore.getState();
|
||||
@@ -463,6 +464,20 @@ const PostMobile = ({
|
||||
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, directories) : undefined;
|
||||
const linksCount = useCountLinksInReplies(post);
|
||||
const { replies, hasMore, loadMore } = useReplies({ comment: post, accountComments: { newerThan: Infinity } });
|
||||
const registerComments = usePostNumberStore((s) => s.registerComments);
|
||||
const prevCidsRef = useRef<string>('');
|
||||
useEffect(() => {
|
||||
const all = post ? [post, ...(replies || [])] : replies || [];
|
||||
if (!all.length) return;
|
||||
const cidsKey = all
|
||||
.map((c) => c?.cid)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.join(',');
|
||||
if (cidsKey === prevCidsRef.current) return;
|
||||
prevCidsRef.current = cidsKey;
|
||||
registerComments(all);
|
||||
}, [post, replies, registerComments]);
|
||||
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
const { hidden, unhide } = useHide({ cid });
|
||||
|
||||
@@ -180,14 +180,22 @@ export const isValidCrossboardPattern = (pattern: string): boolean => {
|
||||
return isValidDomain(pathPart) || isValidIPNSKey(pathPart);
|
||||
};
|
||||
|
||||
// Transform >>{number} post number patterns to markdown links with special anchor
|
||||
const preprocessPostNumberPatterns = (content: string): string => {
|
||||
// Match >> followed by digits, avoid overlap with greentext (>>>), cross-board (>>>/), URLs, CID-like patterns
|
||||
const pattern = /(?<![>/\w])>>(\d+)(?![\d/])/g;
|
||||
return content.replace(pattern, (_, num) => `[>>${num}](#q-${num})`);
|
||||
};
|
||||
|
||||
// Preprocess content to convert plain text 5chan cross-board patterns to markdown links
|
||||
export const preprocess5chanPatterns = (content: string): string => {
|
||||
const withPostNumbers = preprocessPostNumberPatterns(content);
|
||||
// Pattern to match ">>>/something" or ">>>/something/cid"
|
||||
// Negative lookbehind prevents matching patterns that are already part of URLs
|
||||
// Matches: >>>/directory/, >>>/directory/cid (46 chars), >>>/address, >>>/address/cid (46 chars)
|
||||
const pattern = /(?<!https?:\/\/[^\s]*)>>>\/([a-zA-Z0-9]{1,10}\/(?:[a-zA-Z0-9]{46})?|[a-zA-Z0-9\-.]+(?:\/[a-zA-Z0-9]{46})?)[.,:;!?]*/g;
|
||||
|
||||
return content.replace(pattern, (match, capturedPath) => {
|
||||
return withPostNumbers.replace(pattern, (match, capturedPath) => {
|
||||
// Remove any trailing punctuation from the captured path
|
||||
const cleanPath = capturedPath.replace(/[.,:;!?]+$/, '');
|
||||
const fullPattern = `>>>/${cleanPath}`;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { create } from 'zustand';
|
||||
import type { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
interface PostNumberState {
|
||||
numberToCid: Record<number, string>;
|
||||
cidToNumber: Record<string, number>;
|
||||
registerComments: (comments: Comment[]) => void;
|
||||
}
|
||||
|
||||
const usePostNumberStore = create<PostNumberState>((set) => ({
|
||||
numberToCid: {},
|
||||
cidToNumber: {},
|
||||
registerComments: (comments: Comment[]) => {
|
||||
if (!comments?.length) return;
|
||||
|
||||
set((state) => {
|
||||
const nextNumberToCid = { ...state.numberToCid };
|
||||
const nextCidToNumber = { ...state.cidToNumber };
|
||||
|
||||
for (const c of comments) {
|
||||
const num = c?.number;
|
||||
const cid = c?.cid;
|
||||
if (typeof num === 'number' && cid) {
|
||||
nextNumberToCid[num] = cid;
|
||||
nextCidToNumber[cid] = num;
|
||||
}
|
||||
}
|
||||
|
||||
return { numberToCid: nextNumberToCid, cidToNumber: nextCidToNumber };
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
export default usePostNumberStore;
|
||||
Reference in New Issue
Block a user