import type { ReactNode } from 'react'; /** * 身份组前后缀用的行内 Markdown:`code`、**粗体**、~~删除线~~、*斜体* / _斜体_、[文字](http(s) 链接)。 * 只生成 React 元素,不接受任何 HTML,链接只允许 http(s),所以别人写的前后缀不会注入脚本或样式。 * 没有闭合的符号按普通文字显示。 */ const PATTERNS: { re: RegExp; render: (match: RegExpExecArray, key: string) => ReactNode }[] = [ { re: /`([^`]+)`/y, render: (m, key) => {m[1]} }, { re: /\*\*(.+?)\*\*(?!\*)/y, render: (m, key) => {parse(m[1]!, key)} }, { re: /~~(.+?)~~/y, render: (m, key) => {parse(m[1]!, key)} }, { re: /\*(?!\s)(.+?)\*/y, render: (m, key) => {parse(m[1]!, key)} }, { re: /\b_(?!\s)(.+?)_\b/y, render: (m, key) => {parse(m[1]!, key)} }, { re: /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/y, render: (m, key) => {parse(m[1]!, key)} }, ]; function parse(source: string, prefix: string): ReactNode[] { const out: ReactNode[] = []; let plain = ''; let at = 0; let n = 0; const flush = () => { if (plain) { out.push(plain); plain = ''; } }; while (at < source.length) { let hit = false; for (const { re, render } of PATTERNS) { re.lastIndex = at; const match = re.exec(source); if (!match) continue; flush(); out.push(render(match, `${prefix}.${n++}`)); at += match[0].length; hit = true; break; } if (!hit) { plain += source[at]; at += 1; } } flush(); return out; } export function InlineMarkdown({ text }: { text: string }) { return <>{parse(text, 'm')}; }