diff --git a/components/drops/view/part/DropPartMarkdown.tsx b/components/drops/view/part/DropPartMarkdown.tsx index c85017f7ec..76499e310b 100644 --- a/components/drops/view/part/DropPartMarkdown.tsx +++ b/components/drops/view/part/DropPartMarkdown.tsx @@ -216,6 +216,7 @@ export interface DropPartMarkdownProps { readonly onQuoteClick: (drop: ApiDrop) => void; readonly textSize?: "sm" | "md"; readonly currentDropId?: string; + readonly currentSerialNo?: number; } function DropPartMarkdown({ @@ -225,6 +226,7 @@ function DropPartMarkdown({ onQuoteClick, textSize, currentDropId, + currentSerialNo, }: DropPartMarkdownProps) { const isMobile = useIsMobileScreen(); const { emojiMap, findNativeEmoji } = useEmoji(); @@ -243,8 +245,9 @@ function DropPartMarkdown({ createLinkRenderer({ onQuoteClick, currentDropId, + currentSerialNo, }), - [onQuoteClick, currentDropId] + [onQuoteClick, currentDropId, currentSerialNo] ); const { customRenderer, renderParagraph, processContent } = useMemo( diff --git a/components/drops/view/part/DropPartMarkdownWithPropLogger.tsx b/components/drops/view/part/DropPartMarkdownWithPropLogger.tsx index d6129a24d9..14033ccc79 100644 --- a/components/drops/view/part/DropPartMarkdownWithPropLogger.tsx +++ b/components/drops/view/part/DropPartMarkdownWithPropLogger.tsx @@ -23,6 +23,7 @@ function areEqual( "partContent", "textSize", "currentDropId", + "currentSerialNo", ]; for (const key of propsToCheck) { diff --git a/components/drops/view/part/dropPartMarkdown/handlers/seize.tsx b/components/drops/view/part/dropPartMarkdown/handlers/seize.tsx index 80be3f265f..e050b3cccb 100644 --- a/components/drops/view/part/dropPartMarkdown/handlers/seize.tsx +++ b/components/drops/view/part/dropPartMarkdown/handlers/seize.tsx @@ -17,6 +17,7 @@ import { renderSeizeQuote } from "../renderers"; interface CreateSeizeHandlersConfig { readonly onQuoteClick: (drop: ApiDrop) => void; readonly currentDropId?: string; + readonly currentSerialNo?: number; } const ensureSeizeQuote = (href: string): SeizeQuoteLinkInfo => { @@ -29,11 +30,19 @@ const ensureSeizeQuote = (href: string): SeizeQuoteLinkInfo => { }; const createSeizeQuoteHandler = ( - onQuoteClick: (drop: ApiDrop) => void + onQuoteClick: (drop: ApiDrop) => void, + currentSerialNo?: number ): LinkHandler => ({ match: (href) => Boolean(parseSeizeQuoteLink(href)), render: (href) => { - const content = renderSeizeQuote(ensureSeizeQuote(href), onQuoteClick, href); + const quoteInfo = ensureSeizeQuote(href); + + // Prevent infinite recursion when a drop references itself by serialNo + if (currentSerialNo && quoteInfo.serialNo && Number.parseInt(quoteInfo.serialNo, 10) === currentSerialNo) { + throw new Error("Seize quote link matches current drop"); + } + + const content = renderSeizeQuote(quoteInfo, onQuoteClick, href); if (!content) { throw new Error("Unable to render seize quote link"); } @@ -126,8 +135,9 @@ const createSeizeDropHandler = (currentDropId?: string): LinkHandler => export const createSeizeHandlers = ({ onQuoteClick, currentDropId, + currentSerialNo, }: CreateSeizeHandlersConfig): LinkHandler[] => [ - createSeizeQuoteHandler(onQuoteClick), + createSeizeQuoteHandler(onQuoteClick, currentSerialNo), createSeizeGroupHandler(), createSeizeWaveHandler(), createSeizeDropHandler(currentDropId), diff --git a/components/drops/view/part/dropPartMarkdown/linkHandlers.tsx b/components/drops/view/part/dropPartMarkdown/linkHandlers.tsx index 0253158984..8b32caf6b8 100644 --- a/components/drops/view/part/dropPartMarkdown/linkHandlers.tsx +++ b/components/drops/view/part/dropPartMarkdown/linkHandlers.tsx @@ -25,6 +25,7 @@ import { interface LinkRendererConfig { readonly onQuoteClick: (drop: ApiDrop) => void; readonly currentDropId?: string; + readonly currentSerialNo?: number; } interface LinkRenderer { @@ -57,8 +58,9 @@ const findMatch = ( export const createLinkRenderer = ({ onQuoteClick, currentDropId, + currentSerialNo, }: LinkRendererConfig): LinkRenderer => { - const seizeHandlers = createSeizeHandlers({ onQuoteClick, currentDropId }); + const seizeHandlers = createSeizeHandlers({ onQuoteClick, currentDropId, currentSerialNo }); const handlers = createLinkHandlers(); const renderImage: LinkRenderer["renderImage"] = ({ src }) => { diff --git a/components/waves/drops/WaveDropPartContentMarkdown.tsx b/components/waves/drops/WaveDropPartContentMarkdown.tsx index 3af3ed54ac..4843ea1607 100644 --- a/components/waves/drops/WaveDropPartContentMarkdown.tsx +++ b/components/waves/drops/WaveDropPartContentMarkdown.tsx @@ -65,6 +65,7 @@ const WaveDropPartContentMarkdown: React.FC< partContent={part.content} onQuoteClick={onQuoteClick} currentDropId={drop?.id} + currentSerialNo={drop?.serial_no} /> {drop?.updated_at && drop.updated_at !== drop.created_at && (