npm i @rtdui/md-editor
tip: 记得安装
@rtdui/hooks和@rtdui/core配对依赖, 如果还未安装的话.
@rtdui/md-editor 包导出了两个组件: MdEditor 和 MdViewer.
功能:
unified生态系统中的 remark 和 rehype 生态locale属性设置.import { MdEditor, UploadResult } from "@rtdui/md-editor";
// 需要额外导入外部依赖包中的样式.
import "allotment/dist/style.css";
import "katex/dist/katex.min.css";
const handleImageUpload = async (files: File[]) => {
const formdata = new FormData();
files.forEach((d) => formdata.append("upload", d));
const res = await fetch("<your_upload_server>", {
method: "POST",
body: formdata,
});
const result = await res.json();
return result as UploadResult[];
};
const md = `# 目录
# 语法示例
...
`;
export default function MdEditorDemo() {
const [value, setValue] = useState(md);
return (
<MdEditor
handleImageUpload={handleImageUpload}
value={value}
onChange={setValue}
/>
);
}
在新窗口打开
MdViewer应该和MdEditor使用一致的插件集, 这样才能保证编辑时的预览和阅读时保持一致.
import { MdViewer } from "@rtdui/md-editor";
// 需要额外导入外部依赖包中的样式.
import "katex/dist/katex.min.css";
const md = `# 目录
# 语法示例
...
`;
export default function MdEditorDemo() {
return <MdViewer value={md} />;
}
在新窗口打开
@rtdui/md-editor 包已附带了英文和简体中文
默认使用英文, 使用英文无需额外的操作.
使用内置的简体中文:
import { MdEditor, MdViewer, zhLocale } from "@rtdui/md-editor";
<MdEditor locale={zhLocale} />
<MdViewer locale={zhLocale} />
使用自定义的本地化:
import { MdEditor, MdViewer, type Locale } from "@rtdui/md-editor";
const myLocale: Locale = {
// ...
}
<MdEditor locale={myLocale} />
<MdViewer locale={myLocale} />
Plugin接口类型:
interface Plugin {
/**
* 使用Remark生态系统中的扩展
*
* https://github.com/remarkjs/remark/blob/main/doc/plugins.md
*/
remark?: (p: Processor) => Processor;
/**
* 使用Rehype生态系统中的扩展
*
* https://github.com/rehypejs/rehype/blob/main/doc/plugins.md
*/
rehype?: (p: Processor) => Processor;
/**
* 用于工具栏中的图标及处理
*/
toolbar?: ToolbarItem[];
/**
* 编辑器预览和查看器中的副作用函数. 当不能在rehype扩展中处理时的自定义渲染.
*/
viewerEffect?(ctx: ViewerContext): void | (() => void);
}
插件使用unified系统中的Remark生态和Rehype生态, 示意图如下:
每个红框表示的就是插件钩子生效的位置.
tips: 应该优先使用rehype生态中的扩展插件, 因为使用rehype生态中的扩展插件可以支持SSR, 只有当rehype生态扩展无法满足时, 才使用viewerEffect作 为最后的选择, 使用viewerEffect只支持CSR(客户端渲染).
tip: 组件已集成了该功能, 此处仅作为例子
import { wrapText, appendBlock, type Plugin } from "@rtdui/md-editor";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import { IconSum } from "@tabler/icons-react";
export default function mathPlugin(): Plugin {
return {
remark: (processor) => processor.use(remarkMath),
rehype: (processor) => processor.use(rehypeKatex),
toolbar: [
{
type: "multiple",
icon: <IconSum size={iconSize} stroke={iconStroke} />,
title: locale.math,
actions: [
{
title: locale.inline,
icon: "$",
cheatsheet: `$${locale.inlineText}$`,
click: (e, { editor }) => {
wrapText(editor, "$");
},
},
{
title: locale.block,
icon: "$$",
cheatsheet: `$$↵${locale.blockText}↵$$`,
click: (e, { editor }) => {
appendBlock(editor, "\\TeX", {
prefix: "$$\n",
suffix: "\n$$",
});
},
},
],
},
],
};
}
如果不使用rehype生态扩展, 则可以使用viewerEffect:
import type { Plugin } from "@rtdui/md-editor";
import remarkMath from "remark-math";
- import rehypeKatex from "rehype-katex";
export default function mathPlugin(): Plugin {
return {
remark: (processor) => processor.use(remarkMath),
- rehype: (processor) => processor.use(rehypeKatex),
+ viewerEffect({ markdownBody }) {
+ const renderMath = async (selector: string, displayMode: boolean) => {
+ const katex = await import('katex').then((m) => m.default)
+
+ const els = markdownBody.querySelectorAll<HTMLElement>(selector)
+ els.forEach((el) => {
+ katex.render(el.innerText, el, { displayMode })
+ })
+ }
+
+ renderMath('.math.math-inline', false)
+ renderMath('.math.math-display', true)
+ },
};
}
import { MdEditor, MdViewer } from "@rtdui/md-editor";
import mathPlugin from "./mathPlugin";
export default function EditorDemo() {
return <MdEditor plugins={[mathPlugin()]} />;
}
// editor 和 viewer 应同时应用插件
export default function ViewerDemo() {
return <MdViewer plugins={[mathPlugin()]} />;
}| 属性名 | 类型 | 是否必须 | 默认值 | 说明 |
|---|---|---|---|---|
| defaultValue | string | undefined | no | ||
| handleImageUpload | ((files: File[], ev: EditorView) => Promise<UploadResult[]>) | undefined | no | ||
| locale | Locale | undefined | no | ||
| mode | Mode | undefined | no | "auto" | responsive width when auto mode editor layout mode |
| onChange | ((val: string) => void) | undefined | no | ||
| plugins | Plugin[] | undefined | no | Plugin list | |
| remarkRehypeOptions | Options | undefined | no | custom remark-rehype options: Defaults value { allowDangerousHtml: true } https://github.com/remarkjs/remark-rehype | |
| responsiveWidth | number | undefined | no | 640 | responsive width when auto mode |
| sanitize | ((schema: Schema) => Schema) | undefined | no | Sanitize strategy: Defaults to GitHub style sanitation with class names allowed https://github.com/syntax-tree/hast-util-sanitize/blob/main/lib/github.json If you want further customization, pass a function to mutate sanitize schema. | |
| slots | { toolbar?: string | undefined; editorWrapper?: string | undefined; editor?: string | undefined; preview?: string | undefined; } | undefined | no | ||
| value | string | undefined | no |