/** * Parse [N] and [N,M] citation markers in text and replace with React anchor links. * * Citations are 1-based indices into the key_moments array. * Each marker becomes a superscript link to #km-{momentId}. */ import React from "react"; import type { KeyMomentSummary } from "../api/public-client"; // Matches [1], [2,3], [1,2,3], etc. const CITATION_RE = /\[(\d+(?:,\s*\d+)*)\]/g; /** * Convert a text string containing [N] or [N,M] markers into an array of * React nodes — plain strings interleaved with citation anchor elements. */ export function parseCitations( text: string, keyMoments: KeyMomentSummary[], ): React.ReactNode[] { const nodes: React.ReactNode[] = []; let lastIndex = 0; for (const match of text.matchAll(CITATION_RE)) { const matchStart = match.index ?? 0; // Push text before this match if (matchStart > lastIndex) { nodes.push(text.slice(lastIndex, matchStart)); } // Parse the indices from the match group const rawGroup = match[1]; if (!rawGroup) continue; const indices = rawGroup.split(",").map((s) => parseInt(s.trim(), 10)); const links: React.ReactNode[] = []; for (let i = 0; i < indices.length; i++) { const idx = indices[i]!; // Citation indices are 1-based; key_moments array is 0-based const moment = keyMoments[idx - 1]; if (moment) { if (i > 0) links.push(", "); links.push( {idx} , ); } else { // Invalid index — render as plain text if (i > 0) links.push(", "); links.push(String(idx)); } } nodes.push( [{links}] , ); lastIndex = matchStart + match[0].length; } // Push remaining text after the last match if (lastIndex < text.length) { nodes.push(text.slice(lastIndex)); } return nodes.length > 0 ? nodes : [text]; }