import type { ReactNode } from "react"; const SAFE_HTTP_URL = /^https?:\/\//i; type InlineToken = | { type: "text"; value: string } | { type: "link"; label: string; href: string }; function parseLinks(text: string): InlineToken[] { const tokens: InlineToken[] = []; const pattern = /\[([^\]]+)\]\(([^)\s]+)\)/g; let lastIndex = 0; let match: RegExpExecArray | null; while ((match = pattern.exec(text)) !== null) { if (match.index > lastIndex) { tokens.push({ type: "text", value: text.slice(lastIndex, match.index) }); } const label = match[1] ?? ""; const href = match[2] ?? ""; if (SAFE_HTTP_URL.test(href)) { tokens.push({ type: "link", label, href }); } else { tokens.push({ type: "text", value: match[0] }); } lastIndex = pattern.lastIndex; } if (lastIndex < text.length) { tokens.push({ type: "text", value: text.slice(lastIndex) }); } return tokens; } function renderEmphasis(text: string, keyPrefix: string): ReactNode[] { const nodes: ReactNode[] = []; const pattern = /(\*\*\*[^*]+?\*\*\*|\*\*[^*]+?\*\*|\*[^*]+?\*)/g; let lastIndex = 0; let match: RegExpExecArray | null; let idx = 0; while ((match = pattern.exec(text)) !== null) { if (match.index > lastIndex) { nodes.push(text.slice(lastIndex, match.index)); } const token = match[0]; const key = `${keyPrefix}-em-${idx++}`; if (token.startsWith("***")) { nodes.push( {token.slice(3, -3)} ); } else if (token.startsWith("**")) { nodes.push({token.slice(2, -2)}); } else { nodes.push({token.slice(1, -1)}); } lastIndex = pattern.lastIndex; } if (lastIndex < text.length) { nodes.push(text.slice(lastIndex)); } return nodes; } export function renderInlineMarkdown(text: string): ReactNode[] { const withLines = text.split("\n"); const nodes: ReactNode[] = []; withLines.forEach((line, lineIndex) => { const tokens = parseLinks(line); tokens.forEach((token, tokenIndex) => { const keyBase = `ln-${lineIndex}-tok-${tokenIndex}`; if (token.type === "text") { nodes.push(...renderEmphasis(token.value, keyBase)); return; } nodes.push( {renderEmphasis(token.label, `${keyBase}-label`)} ); }); if (lineIndex < withLines.length - 1) { nodes.push(
); } }); return nodes; }