39 lines
1.7 KiB
TypeScript
39 lines
1.7 KiB
TypeScript
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) => <code key={key}>{m[1]}</code> },
|
|
{ re: /\*\*(.+?)\*\*(?!\*)/y, render: (m, key) => <strong key={key}>{parse(m[1]!, key)}</strong> },
|
|
{ re: /~~(.+?)~~/y, render: (m, key) => <s key={key}>{parse(m[1]!, key)}</s> },
|
|
{ re: /\*(?!\s)(.+?)\*/y, render: (m, key) => <em key={key}>{parse(m[1]!, key)}</em> },
|
|
{ re: /\b_(?!\s)(.+?)_\b/y, render: (m, key) => <em key={key}>{parse(m[1]!, key)}</em> },
|
|
{ re: /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/y, render: (m, key) => <a key={key} href={m[2]} target="_blank" rel="noreferrer noopener">{parse(m[1]!, key)}</a> },
|
|
];
|
|
|
|
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')}</>;
|
|
}
|