feat(web): role colors with picker, Material 3 draw, effects and inline markdown
This commit is contained in:
1 parent
bef8898cd8
commit
3b062687cd
20 files changed
+688
-83
No files matched your search
@@ -0,0 +1,38 @@
|
||||
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')}</>;
|
||||
}
|
||||
Reference in new issue
Block a user