import type { ReactNode } from 'react'; /** * Renders the small markdown subset allowed in config strings (banner content, * error page descriptions): links, bold, italic, and inline code. Custom * components are deliberately not supported — these strings come from * docs.json, not MDX. */ interface InlinePattern { regex: RegExp; render: (match: RegExpExecArray, key: number) => ReactNode; } const PATTERNS: InlinePattern[] = [ { // [label](https://example.com) regex: /\[([^\]]+)\]\(([^)\s]+)\)/, render: (match, key) => { const external = /^https?:\/\//.test(match[2]); return ( {renderInlineMarkdown(match[1])} ); }, }, { // **bold** regex: /\*\*([^*]+)\*\*/, render: (match, key) => {renderInlineMarkdown(match[1])}, }, { // *italic* (single asterisk, not part of **) regex: /(? {renderInlineMarkdown(match[1])}, }, { // _italic_ regex: /_([^_]+)_/, render: (match, key) => {renderInlineMarkdown(match[1])}, }, { // `code` — contents render verbatim regex: /`([^`]+)`/, render: (match, key) => {match[1]}, }, ]; export function renderInlineMarkdown(text: string): ReactNode[] { const nodes: ReactNode[] = []; let remaining = text; let key = 0; while (remaining) { let earliest: { index: number; match: RegExpExecArray; pattern: InlinePattern } | null = null; for (const pattern of PATTERNS) { const match = pattern.regex.exec(remaining); if (match && (!earliest || match.index < earliest.index)) { earliest = { index: match.index, match, pattern }; } } if (!earliest) { nodes.push(remaining); break; } if (earliest.index > 0) { nodes.push(remaining.slice(0, earliest.index)); } nodes.push(earliest.pattern.render(earliest.match, key++)); remaining = remaining.slice(earliest.index + earliest.match[0].length); } return nodes; }