title: Markdown description: 将 Plate 内容转换为 Markdown 及其逆过程。 toc: true
@platejs/markdown 提供了强大的 Plate 内容结构与 Markdown 之间的双向转换能力。
特性
- Markdown 转 Plate JSON: 将 Markdown 字符串转换为 Plate 可编辑格式(
deserialize方法)。 - Plate JSON 转 Markdown: 将 Plate 内容重新序列化为 Markdown 字符串(
serialize方法)。 - 默认安全: 转换过程不使用
dangerouslySetInnerHTML,安全可靠。 - 自定义规则: 可通过
rules配置自定义特定 Markdown 语法或 Plate 元素的转换方式,支持 MDX。 - 高度可扩展: 可通过
remarkPlugins选项集成 remark 插件。 - 标准兼容: 支持 CommonMark,支持通过
remark-gfm启用 GFM(GitHub 风格 Markdown 扩展)。 - 循环序列化: 通过 MDX 语法保证自定义元素可逆转换。
为什么选择 Plate Markdown?
与 react-markdown 等库仅将 Markdown 渲染成 React 元素不同,@platejs/markdown 深度集成 Plate 体系,带来如下优势:
- 富文本编辑能力: 支持将 Markdown 转为结构化 Plate 内容,实现高级编辑能力。
- 所见即所得(WYSIWYG): 支持富文本视图编辑,一键回转为 Markdown。
- 自定义元素与数据支持: 复杂自定义元素(如提及、嵌入等)可通过 MDX 双向转换。
- 可扩展性: 充分利用 Plate 插件系统与 unified/remark 生态的能力。
如果你只需将 Markdown 显示为 HTML,无需自定义元素和富编辑,react-markdown 可能已足够。但如需 Markdown 导入/导出、富文本及自定义内容,@platejs/markdown 则是最佳集成方案。
套件方式使用
安装
最快速增加 Markdown 支持的方法是使用 MarkdownKit,它已预配置好带必要 remark 插件的 MarkdownPlugin,兼容 Plate UI。
import {
BaseFootnoteDefinitionPlugin,
BaseFootnoteReferencePlugin,
} from '@platejs/footnote';
import { MarkdownPlugin, remarkMdx, remarkMention } from '@platejs/markdown';
import { KEYS } from 'platejs';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
export const MarkdownKit = [
BaseFootnoteReferencePlugin,
BaseFootnoteDefinitionPlugin,
MarkdownPlugin.configure({
options: {
plainMarks: [KEYS.suggestion, KEYS.comment],
remarkPlugins: [
remarkMath,
remarkGfm,
remarkEmoji as any,
remarkMdx,
remarkMention,
],
},
}),
];import {
BaseFootnoteDefinitionPlugin,
BaseFootnoteReferencePlugin,
} from '@platejs/footnote';
import { MarkdownPlugin, remarkMdx, remarkMention } from '@platejs/markdown';
import { KEYS } from 'platejs';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
export const MarkdownKit = [
BaseFootnoteReferencePlugin,
BaseFootnoteDefinitionPlugin,
MarkdownPlugin.configure({
options: {
plainMarks: [KEYS.suggestion, KEYS.comment],
remarkPlugins: [
remarkMath,
remarkGfm,
remarkEmoji as any,
remarkMdx,
remarkMention,
],
},
}),
];挂载套件
import { createPlateEditor } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
const editor = createPlateEditor({
plugins: [
// ...其它插件,
...MarkdownKit,
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
const editor = createPlateEditor({
plugins: [
// ...其它插件,
...MarkdownKit,
],
});手动集成用法
安装
pnpm add platejs @platejs/markdownpnpm add platejs @platejs/markdown引用插件
import { MarkdownPlugin } from '@platejs/markdown';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...其它插件,
MarkdownPlugin,
],
});import { MarkdownPlugin } from '@platejs/markdown';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...其它插件,
MarkdownPlugin,
],
});配置插件
推荐通过 MarkdownPlugin 的 configure 方法配置粘贴支持与自定义转换规则。
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMention, remarkMdx } from '@platejs/markdown';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
const editor = createPlateEditor({
plugins: [
// ...其它 Plate 插件
MarkdownPlugin.configure({
options: {
// 通过 remarkPlugins 添加语法扩展(如 GFM、数学、MDX)
remarkPlugins: [remarkMath, remarkGfm, remarkMdx, remarkMention],
// 如需自定义转换规则可配置 rules
rules: {
// date: { /* ... 规则对象 ... */ },
},
},
}),
],
});
// 如需禁用 Markdown 粘贴处理:
const editorWithoutPaste = createPlateEditor({
plugins: [
// ...其它 Plate 插件
MarkdownPlugin.configure(() => ({ parser: null })),
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMention, remarkMdx } from '@platejs/markdown';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
const editor = createPlateEditor({
plugins: [
// ...其它 Plate 插件
MarkdownPlugin.configure({
options: {
// 通过 remarkPlugins 添加语法扩展(如 GFM、数学、MDX)
remarkPlugins: [remarkMath, remarkGfm, remarkMdx, remarkMention],
// 如需自定义转换规则可配置 rules
rules: {
// date: { /* ... 规则对象 ... */ },
},
},
}),
],
});
// 如需禁用 Markdown 粘贴处理:
const editorWithoutPaste = createPlateEditor({
plugins: [
// ...其它 Plate 插件
MarkdownPlugin.configure(() => ({ parser: null })),
],
});如果未用 configure 配置 MarkdownPlugin,可直接用 editor.api.markdown.deserialize 和 serialize,但无法享受默认规则和粘贴功能。
Markdown 转 Plate(反序列化)
用 editor.api.markdown.deserialize 方法将 Markdown 字符串转换为 Plate Value(节点数组),常用于设置编辑器初始内容。
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
// ...还需引入其它用于渲染的 Plate 插件
const markdownString = '# Hello, *Plate*!';
const editor = createPlateEditor({
plugins: [
// 必须包含 MarkdownPlugin
MarkdownPlugin,
// ...渲染反序列化元素所需的其它插件(如 HeadingPlugin、ItalicPlugin)
],
// 初始内容通过反序列化生成
value: (editor) =>
editor.getApi(MarkdownPlugin).markdown.deserialize(markdownString),
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
// ...还需引入其它用于渲染的 Plate 插件
const markdownString = '# Hello, *Plate*!';
const editor = createPlateEditor({
plugins: [
// 必须包含 MarkdownPlugin
MarkdownPlugin,
// ...渲染反序列化元素所需的其它插件(如 HeadingPlugin、ItalicPlugin)
],
// 初始内容通过反序列化生成
value: (editor) =>
editor.getApi(MarkdownPlugin).markdown.deserialize(markdownString),
});你需要在 plugins 中包含渲染 Markdown 反序列化结果所需的所有 Plate 插件(例如渲染 # 需 HeadingPlugin,渲染表格需 TablePlugin)。
Plate 转 Markdown(序列化)
用 editor.api.markdown.serialize 将当前编辑器内容或指定节点数组转回 Markdown 字符串。
序列化当前编辑器内容:
// 假设 editor 已有内容
const markdownOutput = editor.api.markdown.serialize();
console.info(markdownOutput);// 假设 editor 已有内容
const markdownOutput = editor.api.markdown.serialize();
console.info(markdownOutput);序列化指定节点数组:
const specificNodes = [
{ type: 'p', children: [{ text: '仅序列化这一段。' }] },
{ type: 'h1', children: [{ text: '以及这个标题。' }] },
];
// 假设 editor 是你的 Plate 实例
const partialMarkdownOutput = editor.api.markdown.serialize({
value: specificNodes,
});
console.info(partialMarkdownOutput);const specificNodes = [
{ type: 'p', children: [{ text: '仅序列化这一段。' }] },
{ type: 'h1', children: [{ text: '以及这个标题。' }] },
];
// 假设 editor 是你的 Plate 实例
const partialMarkdownOutput = editor.api.markdown.serialize({
value: specificNodes,
});
console.info(partialMarkdownOutput);循环序列化:自定义元素(MDX)
核心特性之一是支持无标准 Markdown 语法的自定义 Plate 元素(如下划线、提及等),@platejs/markdown 会在序列化时转为 MDX 元素,并可无损还原。
示例:处理自定义 date 元素
Plate 节点结构:
{
type: 'p',
children: [
{ text: 'Today is ' },
{ type: 'date', date: '2025-03-31', children: [{ text: '' }] } // 叶节点需有 text
],
}{
type: 'p',
children: [
{ text: 'Today is ' },
{ type: 'date', date: '2025-03-31', children: [{ text: '' }] } // 叶节点需有 text
],
}通过 rules 配置插件:
import type { MdMdxJsxTextElement } from '@platejs/markdown';
import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
// ... 其它导入
MarkdownPlugin.configure({
options: {
rules: {
// 键名匹配规则:
// 1. Plate 插件的 key 或 type
// 2. mdast 节点类型
// 3. MDX tag 名
date: {
// Markdown -> Plate
deserialize(mdastNode: MdMdxJsxTextElement, deco, options) {
const dateValue = (mdastNode.children?.[0] as any)?.value || '';
return {
type: 'date', // 你的 Plate 节点类型
date: dateValue,
children: [{ text: '' }], // 合法 Plate 结构
};
},
// Plate -> Markdown(MDX)
serialize: (slateNode): MdMdxJsxTextElement => {
return {
type: 'mdxJsxTextElement',
name: 'date', // MDX 标签名
attributes: [], // 可选,如 [{ type: 'mdxJsxAttribute', name: 'date', value: slateNode.date }]
children: [{ type: 'text', value: slateNode.date || '1999-01-01' }],
};
},
},
// ...其它自定义元素规则
},
remarkPlugins: [remarkMdx /*, 其它如 remarkGfm 的插件 */],
},
});import type { MdMdxJsxTextElement } from '@platejs/markdown';
import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
// ... 其它导入
MarkdownPlugin.configure({
options: {
rules: {
// 键名匹配规则:
// 1. Plate 插件的 key 或 type
// 2. mdast 节点类型
// 3. MDX tag 名
date: {
// Markdown -> Plate
deserialize(mdastNode: MdMdxJsxTextElement, deco, options) {
const dateValue = (mdastNode.children?.[0] as any)?.value || '';
return {
type: 'date', // 你的 Plate 节点类型
date: dateValue,
children: [{ text: '' }], // 合法 Plate 结构
};
},
// Plate -> Markdown(MDX)
serialize: (slateNode): MdMdxJsxTextElement => {
return {
type: 'mdxJsxTextElement',
name: 'date', // MDX 标签名
attributes: [], // 可选,如 [{ type: 'mdxJsxAttribute', name: 'date', value: slateNode.date }]
children: [{ type: 'text', value: slateNode.date || '1999-01-01' }],
};
},
},
// ...其它自定义元素规则
},
remarkPlugins: [remarkMdx /*, 其它如 remarkGfm 的插件 */],
},
});转换流程说明:
- 序列化(Plate → Markdown): Plate 的
date节点会转为<date>2025-03-31</date>。 - 反序列化(Markdown → Plate): MDX 标签
<date>2025-03-31</date>可还原对应 Platedate节点。
API 参考
MarkdownPlugin
核心插件配置对象。使用 MarkdownPlugin.configure({ options: {} }) 可以设置全局的 Markdown 处理选项。
白名单方式,指定允许哪些节点类型(包括 Plate 元素类型及 Markdown AST 类型,如 strong)。不能与 disallowedNodes 一起使用。如果设置,仅处理列表内的类型。默认值:null(全部允许)。
黑名单方式,指定过滤掉哪些节点类型。不能和 allowedNodes 一起使用。默认值:null。
使用自定义函数进行更细粒度的节点过滤,该函数会在 allowedNodes/disallowedNodes 之后应用。- deserialize?: (mdastNode: any) => boolean:Markdown → Plate 的过滤,返回 true 保留节点。- serialize?: (slateNode: any) => boolean:Plate → Markdown 的过滤,返回 true 保留节点。默认值:null。
remark 插件数组
(如 remark-gfm、remark-math、remark-mdx 等)。对 Markdown AST (mdast) 生效。默认值:[]。
粘贴内容处理相关配置。设置为 null 可禁止 Markdown 粘贴。默认情况下会启用将 text/plain 视为 Markdown 粘贴。详见 PlatePlugin API > parser。
api.markdown.deserialize
将 Markdown 字符串转换为 Plate Value(Descendant[])。
api.markdown.serialize
将 Plate Value(Descendant[])序列化为 Markdown 字符串。
parseMarkdownBlocks
工具函数:将 Markdown 字符串切分为块级 token(deserialize 内部使用,memoize 时也有用)。
示例
使用 Remark 插件(GFM)
为编辑器增加 GitHub Flavored Markdown(GFM)支持,包括表格、删除线、任务列表和自动链接。
插件配置:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkGfm from 'remark-gfm';
// 导入 GFM 相关 Plate 插件
import { TablePlugin } from '@platejs/table/react';
import { TodoListPlugin } from '@platejs/list-classic/react'; // 对应任务列表的 List 插件
import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
import { LinkPlugin } from '@platejs/link/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件
TablePlugin,
TodoListPlugin, // 或你当前使用的任务列表插件
StrikethroughPlugin,
LinkPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkGfm],
},
}),
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkGfm from 'remark-gfm';
// 导入 GFM 相关 Plate 插件
import { TablePlugin } from '@platejs/table/react';
import { TodoListPlugin } from '@platejs/list-classic/react'; // 对应任务列表的 List 插件
import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
import { LinkPlugin } from '@platejs/link/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件
TablePlugin,
TodoListPlugin, // 或你当前使用的任务列表插件
StrikethroughPlugin,
LinkPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkGfm],
},
}),
],
});用法示例:
const markdown = `
A table:
| a | b |
| - | - |
~~Strikethrough~~
- [x] Task list item
Visit https://platejs.org
`;
// 假设 `editor` 是已配置好的 Plate 编辑器实例
const slateValue = editor.api.markdown.deserialize(markdown);
// editor.tf.setValue(slateValue); // 可以设置编辑器内容
const markdownOutput = editor.api.markdown.serialize();
// markdownOutput 将包含 GFM 语法const markdown = `
A table:
| a | b |
| - | - |
~~Strikethrough~~
- [x] Task list item
Visit https://platejs.org
`;
// 假设 `editor` 是已配置好的 Plate 编辑器实例
const slateValue = editor.api.markdown.deserialize(markdown);
// editor.tf.setValue(slateValue); // 可以设置编辑器内容
const markdownOutput = editor.api.markdown.serialize();
// markdownOutput 将包含 GFM 语法自定义渲染(语法高亮)
本例展示两种方式:一种是自定义渲染组件(适合仅 UI 层变更),另一种是自定义转换规则(适合改变 Plate 数据结构)。
背景说明:
@platejs/markdown会将 Markdown 的代码块(如 ```js ... ```)转换为 Plate 的code_block元素,子元素为code_line。- Plate 的
CodeBlockElement(通常来自@platejs/code-block/react)负责渲染这一结构。 - 语法高亮通常在
CodeBlockElement渲染中通过lowlight(由CodeBlockPlugin提供)实现。详见 代码块插件文档。
方式一:自定义渲染组件(推荐 UI 层改动时使用)
自定义 code_block 插件的渲染组件即可更改代码块的外观。
import { createPlateEditor } from 'platejs/react';
import {
CodeBlockPlugin,
CodeLinePlugin,
CodeSyntaxPlugin,
} from '@platejs/code-block/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { MyCustomCodeBlockElement } from './my-custom-code-block'; // 你的自定义组件
const editor = createPlateEditor({
plugins: [
CodeBlockPlugin.withComponent(MyCustomCodeBlockElement), // 基础插件 + 自定义渲染
CodeLinePlugin.withComponent(MyCustomCodeLineElement),
CodeSyntaxPlugin.withComponent(MyCustomCodeSyntaxElement),
MarkdownPlugin, // 用于 Markdown 转换
// ... 其他插件
],
});
// MyCustomCodeBlockElement.tsx 内实现自定义渲染(如用 react-syntax-highlighter),并消费 PlateElement 的 props。import { createPlateEditor } from 'platejs/react';
import {
CodeBlockPlugin,
CodeLinePlugin,
CodeSyntaxPlugin,
} from '@platejs/code-block/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { MyCustomCodeBlockElement } from './my-custom-code-block'; // 你的自定义组件
const editor = createPlateEditor({
plugins: [
CodeBlockPlugin.withComponent(MyCustomCodeBlockElement), // 基础插件 + 自定义渲染
CodeLinePlugin.withComponent(MyCustomCodeLineElement),
CodeSyntaxPlugin.withComponent(MyCustomCodeSyntaxElement),
MarkdownPlugin, // 用于 Markdown 转换
// ... 其他插件
],
});
// MyCustomCodeBlockElement.tsx 内实现自定义渲染(如用 react-syntax-highlighter),并消费 PlateElement 的 props。完整用法详见 代码块插件文档。
方式二:自定义转换规则(高级用法 - Plate 数据结构变更)
如果希望代码块以单独字符串属性存储(非 code_line 拆分),可重写 deserialize 规则。
import { MarkdownPlugin } from '@platejs/markdown';
import { CodeBlockPlugin } from '@platejs/code-block/react';
MarkdownPlugin.configure({
options: {
rules: {
// 自定义 mdast 的 'code' 类型反序列化方式
code: {
deserialize: (mdastNode, deco, options) => {
return {
type: KEYS.codeBlock, // Plate 的 type
lang: mdastNode.lang ?? undefined,
rawCode: mdastNode.value || '', // 直接存原始 code 文本
children: [{ text: '' }], // Plate 元素必须要有子文本节点
};
},
},
// 还需要为 `code_block` 自定义 serialize 规则,将 `rawCode` 转回 mdast 'code' 节点
[KEYS.codeBlock]: {
serialize: (slateNode, options) => {
return {
// mdast 'code' 节点
type: 'code',
lang: slateNode.lang,
value: slateNode.rawCode,
};
},
},
},
// remarkPlugins: [...]
},
});
// 你的自定义渲染组件(MyCustomCodeBlockElement)应读取 `rawCode` 属性import { MarkdownPlugin } from '@platejs/markdown';
import { CodeBlockPlugin } from '@platejs/code-block/react';
MarkdownPlugin.configure({
options: {
rules: {
// 自定义 mdast 的 'code' 类型反序列化方式
code: {
deserialize: (mdastNode, deco, options) => {
return {
type: KEYS.codeBlock, // Plate 的 type
lang: mdastNode.lang ?? undefined,
rawCode: mdastNode.value || '', // 直接存原始 code 文本
children: [{ text: '' }], // Plate 元素必须要有子文本节点
};
},
},
// 还需要为 `code_block` 自定义 serialize 规则,将 `rawCode` 转回 mdast 'code' 节点
[KEYS.codeBlock]: {
serialize: (slateNode, options) => {
return {
// mdast 'code' 节点
type: 'code',
lang: slateNode.lang,
value: slateNode.rawCode,
};
},
},
},
// remarkPlugins: [...]
},
});
// 你的自定义渲染组件(MyCustomCodeBlockElement)应读取 `rawCode` 属性可根据需求选择 UI 层调整(方式一)或底层数据结构调整(方式二)。
支持数学(remark-math)
支持 TeX 数学语法(如 $行内$、$$块级$$)。
插件配置:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkMath from 'remark-math';
// Plate 数学渲染相关插件
import { MathPlugin } from '@platejs/math/react'; // 主 Math 插件
const editor = createPlateEditor({
plugins: [
// ...其他插件
MathPlugin, // 渲染块级与行内公式
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMath],
// 内置规则已支持 remark-math 产生的 'math' / 'inlineMath' mdast 节点到 Plate 的 'equation' 和 'inline_equation'
},
}),
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkMath from 'remark-math';
// Plate 数学渲染相关插件
import { MathPlugin } from '@platejs/math/react'; // 主 Math 插件
const editor = createPlateEditor({
plugins: [
// ...其他插件
MathPlugin, // 渲染块级与行内公式
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMath],
// 内置规则已支持 remark-math 产生的 'math' / 'inlineMath' mdast 节点到 Plate 的 'equation' 和 'inline_equation'
},
}),
],
});用法示例:
const markdown = `
Inline math: $E=mc^2$
Block math:
$$
\\int_a^b f(x) dx = F(b) - F(a)
$$
`;
// 假设 `editor` 为已配置 Plate 编辑器实例
const slateValue = editor.api.markdown.deserialize(markdown);
// slateValue 包含 'inline_equation' 及 'equation' 节点
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// 输出 Markdown 字符串包含 $...$ 和 $$...$$ 语法const markdown = `
Inline math: $E=mc^2$
Block math:
$$
\\int_a^b f(x) dx = F(b) - F(a)
$$
`;
// 假设 `editor` 为已配置 Plate 编辑器实例
const slateValue = editor.api.markdown.deserialize(markdown);
// slateValue 包含 'inline_equation' 及 'equation' 节点
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// 输出 Markdown 字符串包含 $...$ 和 $$...$$ 语法Mentions 支持(remarkMention)
使用链接语法风格的 mention,兼容多语言和特殊字符,序列化时格式始终如 [显示文本](mention:id)。
插件配置:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMention } from '@platejs/markdown';
import { MentionPlugin } from '@platejs/mention/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件
MentionPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMention],
},
}),
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMention } from '@platejs/markdown';
import { MentionPlugin } from '@platejs/mention/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件
MentionPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMention],
},
}),
],
});支持的 Markdown 格式:
const markdown = `
Mention: [Alice](mention:alice)
Mention with spaces: [John Doe](mention:john_doe)
Full name with ID: [Jane Smith](mention:user_123)
`;
// 假设 `editor` 是已配置好的 Plate 编辑器
const slateValue = editor.api.markdown.deserialize(markdown);
// 自动生成合适值与显示文本的 mention 节点
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// mention 序列化后始终用链接格式: [Alice](mention:alice), [John Doe](mention:john_doe) 等const markdown = `
Mention: [Alice](mention:alice)
Mention with spaces: [John Doe](mention:john_doe)
Full name with ID: [Jane Smith](mention:user_123)
`;
// 假设 `editor` 是已配置好的 Plate 编辑器
const slateValue = editor.api.markdown.deserialize(markdown);
// 自动生成合适值与显示文本的 mention 节点
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// mention 序列化后始终用链接格式: [Alice](mention:alice), [John Doe](mention:john_doe) 等remarkMention 插件采用 显示文本 的链接语法,支持带空格与自定义显示文本。
序列化时所有 mention 都用同一链接风格,以充分支持特殊字符与一致性。
使用多列布局(Columns)
支持多列文档,可通过 MDX 语法定义多列结构。
插件配置:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件
ColumnPlugin,
ColumnItemPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMdx], // 多列 MDX 语法需要
},
}),
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件
ColumnPlugin,
ColumnItemPlugin,
MarkdownPlugin.configure({
options: {
remarkPlugins: [remarkMdx], // 多列 MDX 语法需要
},
}),
],
});支持的 Markdown 格式:
const markdown = `
<column_group>
<column width="50%">
左侧内容,宽度 50%
</column>
<column width="50%">
右侧内容,宽度 50%
</column>
</column_group>
<column_group>
<column width="33%">第一列</column>
<column width="33%">第二列</column>
<column width="34%">第三列</column>
</column_group>
`;
// 假设 `editor` 为已配置好的 Plate 编辑器
const slateValue = editor.api.markdown.deserialize(markdown);
// 生成包含嵌套 column 元素的 column_group 节点
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// 输出的 Markdown 保持列结构和宽度属性const markdown = `
<column_group>
<column width="50%">
左侧内容,宽度 50%
</column>
<column width="50%">
右侧内容,宽度 50%
</column>
</column_group>
<column_group>
<column width="33%">第一列</column>
<column width="33%">第二列</column>
<column width="34%">第三列</column>
</column_group>
`;
// 假设 `editor` 为已配置好的 Plate 编辑器
const slateValue = editor.api.markdown.deserialize(markdown);
// 生成包含嵌套 column 元素的 column_group 节点
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// 输出的 Markdown 保持列结构和宽度属性多列特性:
- 支持任意数量的列
- 宽度属性可省略(默认为等分)
- 列内可嵌套任意内容
- 自动归一化各列宽度,总和为 100%
Remark 插件生态(Remark Plugins)
@platejs/markdown 基于 unified 与 remark 生态。通过在 MarkdownPlugin.configure 的 remarkPlugins 选项中添加 remark 插件可扩展能力。这些插件对 mdast(Markdown 抽象语法树) 进行操作。
插件查找指南:
常见用途:
- 语法扩展: 如
remark-gfm(表格等)、remark-math(TeX)、remark-frontmatter、remark-mdx - Lint/格式化: 如
remark-lint(通常作为独立工具链) - 自定义转换: 编写自定义插件以变更 mdast
Plate 组件(如 TableElement、CodeBlockElement)渲染 Plate JSON。
remarkPlugins 作用于 Markdown AST。与部分渲染器不同,
Plate 一般不需要 rehypePlugins(HTML AST 处理),除非需要更复杂的 HTML 处理(如 rehype-raw)。
语法支持
@platejs/markdown 使用 remark-parse,遵循 CommonMark 标准。可通过 remarkPlugins 开启 GFM 或其它语法扩展。
- Markdown 教程: CommonMark 入门
- GFM 规范: GitHub Flavored Markdown 规范
架构概览
@platejs/markdown 基于 unified/remark,将 Markdown 字符串与 Plate 编辑器格式进行互转。
@platejs/markdown
+--------------------------------------------------------------------------------------------+
| |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| | | | | | | | | |
markdown-+->+ remark +-mdast->+ remark plugins +-mdast->+ mdast-to-slate+----->+ nodes +-plate-+->react elements
| | | | | | | | | |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| ^ | |
| | v |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| | | | | | | | | |
| | stringify |<-mdast-+ remark plugins |<-mdast-+ slate-to-mdast+<-----+ serialize | |
| | | | | | | | | |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| |
+--------------------------------------------------------------------------------------------+
关键流程:
- 解析(反序列化):
- Markdown 字符串 →
remark-parse→ mdast remarkPlugins变换 mdast(如remark-gfm)mdast-to-slate利用rules转化为 Plate 节点- Plate 组件系统渲染节点
- Markdown 字符串 →
- 序列化:
- Plate 节点 →
slate-to-mdast(用rules)→ mdast remarkPlugins变换 mdastremark-stringify输出为 Markdown 字符串
- Plate 节点 →
- 节点直出渲染: Plate 直接渲染自己的节点组件,而
react-markdown往往转为 HTML AST 后再生成 React 元素 - 双向处理: Plate 的 Markdown 处理是完整双向的
- 富文本集成: 节点与 Plate 编辑功能紧密集成
- 插件系统: 节点组件受插件管理
从 react-markdown 迁移
迁移时需将 react-markdown 的概念映射到 Plate 体系。
主要区别:
- 渲染流程:
react-markdown(MD → mdast → hast → React) VS@platejs/markdown(MD ↔ mdast ↔ Plate JSON,Plate 组件直接渲染 Plate 节点) - 组件定制方式:
react-markdown:components属性替换 HTML 节点渲染- Plate:
MarkdownPlugin提供rules定制 mdast↔Plate JSON 互转createPlateEditor的components配置 Plate 节点的渲染组件(详见附录C)
- 插件生态系统:
@platejs/markdown主要以remarkPlugins为核心,rehypePlugins使用较少
对照表:
react-markdown 属性 | @platejs/markdown 等价项/概念 | 备注 |
|---|---|---|
children (字符串) | 传递给 editor.api.markdown.deserialize(string) | 通常在 createPlateEditor 的 value 配置 |
remarkPlugins | MarkdownPlugin.configure({ options: { remarkPlugins: [...] }}) | 直接映射;在 mdast 层操作 |
rehypePlugins | 通常不需要,如有需求通过 remarkPlugins 完成语法扩展 | Plate 组件自行渲染。原生 HTML 需求通过 rehype-raw 加入 |
components={{ h1: MyH1 }} | createPlateEditor({ components: { h1: MyH1 } }) | 设置对应渲染组件,h1 等键依赖 HeadingPlugin 配置 |
components={{ code: MyCode }} | ① 转换规则:MarkdownPlugin > rules > code ② 渲染:components: { [KEYS.codeBlock]: MyCode } | rules 处理 mdast(code)到 Plate(code_block);自定义渲染组件 |
allowedElements | MarkdownPlugin.configure({ options: { allowedNodes: [...] }}) | 转换时过滤 mdast/Plate 节点 |
disallowedElements | MarkdownPlugin.configure({ options: { disallowedNodes: [...] }}) | 转换时过滤不支持节点 |
unwrapDisallowed | 无直接等价项,默认过滤节点 | 可通过自定义 rules 实现展开逻辑 |
skipHtml | 默认会移除大部分 HTML | 如需保留,使用 remarkPlugins 引入 rehype-raw |
urlTransform | 在 rules 对 link 处理,或按插件类型序列化处理 | 在转换规则中自定义实现 |
allowElement | MarkdownPlugin.configure({ options: { allowNode: { ... } } }) | 转换时自定义函数过滤节点 |
附录A:Markdown中的HTML
默认情况下,@platejs/markdown 出于安全考虑不会处理原始的 HTML 标签。标准 Markdown 生成的 HTML(如 *emphasis* → <em>)会被正常转换。
默认回退行为是保守的:
- 未知的内联 MDX / 类 HTML 节点会按源码文本原样保留。
- 未知的块级 MDX / 原始 HTML 块会保留为可编辑的源码文本段落,而不是被丢弃或静默转换成其它块类型。
- 这让源码保持原样,也更适合流式 / AI 生成的 Markdown,这类 HTML 往往会分片到达,或者没有对应的 Plate 规则。
如果需要比“保留源码”更丰富的 HTML 行为,可为目标标签显式添加 rules。
如需在受信任环境下处理原始 HTML:
- 引入
remark-mdx: 添加到remarkPlugins。 - 使用
rehype-raw: 将rehype-raw添加进remarkPlugins。 - 配置Rules: 可能需要针对解析后的 HTML
hast节点自定义rules,将其映射到 Plate 结构。
import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
import rehypeRaw from 'rehype-raw'; // 可能需要 VFile,确保兼容性
// import { VFile } from 'vfile'; // 如果 rehype-raw 需要 VFile
MarkdownPlugin.configure({
options: {
remarkPlugins: [
remarkMdx,
// 在 remark 流水线中使用 rehype 插件会比较复杂
[
rehypeRaw,
{
/* 配置项,如传递 vfile */
},
],
],
rules: {
// 示例:对 rehype-raw 解析出来的 HTML 标签设置规则
// mdastNode 结构取决于 rehype-raw 的输出
element: {
// 针对 rehype-raw 产出的“element”节点的通用处理
deserialize: (mdastNode, deco, options) => {
// 简化示例:请根据 mdastNode.tagName 及其属性做完整处理
// 实际上常需要针对每一种 HTML 标签做独立规则
if (mdastNode.tagName === 'div') {
return {
type: 'html_div', // 例如:映射到 Plate 的自定义元素 'html_div'
children: convertChildrenDeserialize(
mdastNode.children,
deco,
options
),
};
}
// 其他标签回退处理
return convertChildrenDeserialize(mdastNode.children, deco, options);
},
},
// 如需要将Plate结构重新序列化为原始HTML,请补充相应规则
},
},
});import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
import rehypeRaw from 'rehype-raw'; // 可能需要 VFile,确保兼容性
// import { VFile } from 'vfile'; // 如果 rehype-raw 需要 VFile
MarkdownPlugin.configure({
options: {
remarkPlugins: [
remarkMdx,
// 在 remark 流水线中使用 rehype 插件会比较复杂
[
rehypeRaw,
{
/* 配置项,如传递 vfile */
},
],
],
rules: {
// 示例:对 rehype-raw 解析出来的 HTML 标签设置规则
// mdastNode 结构取决于 rehype-raw 的输出
element: {
// 针对 rehype-raw 产出的“element”节点的通用处理
deserialize: (mdastNode, deco, options) => {
// 简化示例:请根据 mdastNode.tagName 及其属性做完整处理
// 实际上常需要针对每一种 HTML 标签做独立规则
if (mdastNode.tagName === 'div') {
return {
type: 'html_div', // 例如:映射到 Plate 的自定义元素 'html_div'
children: convertChildrenDeserialize(
mdastNode.children,
deco,
options
),
};
}
// 其他标签回退处理
return convertChildrenDeserialize(mdastNode.children, deco, options);
},
},
// 如需要将Plate结构重新序列化为原始HTML,请补充相应规则
},
},
});启用原始 HTML 渲染后,如果 Markdown 来源不受信任,会显著增加 XSS 风险。应在 rehype-raw 后使用 rehype-sanitize,白名单允许的 HTML 元素/属性。
附录B:自定义转换规则(rules)
在 MarkdownPlugin.configure 中设置的 rules 选项,提供了 mdast ↔ Plate JSON 转换的精细控制。rules 对象中的字段需与各节点类型对应。
- 反序列化(Markdown → Plate): 键为
mdast节点类型(如paragraph、heading、strong、link,以及 MDX 节点如mdxJsxTextElement)。deserialize函数接收(mdastNode, deco, options),返回 Plate 的Descendant或Descendant[]。 - 序列化(Plate → Markdown): 键为 Plate 的元素/文本类型(如
p、h1、a、code_block、bold)。serialize函数接收(slateNode, options),返回mdast节点。
示例:自定义链接反序列化规则
MarkdownPlugin.configure({
options: {
rules: {
// 针对 mdast 的 'link' 类型的规则
link: {
deserialize: (mdastNode, deco, options) => {
// 默认会生成 { type: 'a', url: ..., children: [...] }
// 这里添加一个自定义属性
return {
type: 'a', // Plate 链接节点类型
url: mdastNode.url,
title: mdastNode.title,
customProp: 'added-during-deserialize',
children: convertChildrenDeserialize(
mdastNode.children,
deco,
options
),
};
},
},
// 如序列化时需处理 customProp,可对 Plate 的 'a' 类型覆写规则
a: {
// 假定 'a' 就是 Plate 链接类型
serialize: (slateNode, options) => {
// 默认输出 mdast 的 'link'
// 如需将 customProp 输出为 MDX 属性等,可自定义处理
return {
type: 'link', // mdast 类型
url: slateNode.url,
title: slateNode.title,
// customProp: slateNode.customProp, // MDX 属性占位?
children: convertNodesSerialize(slateNode.children, options),
};
},
},
},
// ... 其他 remarkPlugins 配置 ...
},
});MarkdownPlugin.configure({
options: {
rules: {
// 针对 mdast 的 'link' 类型的规则
link: {
deserialize: (mdastNode, deco, options) => {
// 默认会生成 { type: 'a', url: ..., children: [...] }
// 这里添加一个自定义属性
return {
type: 'a', // Plate 链接节点类型
url: mdastNode.url,
title: mdastNode.title,
customProp: 'added-during-deserialize',
children: convertChildrenDeserialize(
mdastNode.children,
deco,
options
),
};
},
},
// 如序列化时需处理 customProp,可对 Plate 的 'a' 类型覆写规则
a: {
// 假定 'a' 就是 Plate 链接类型
serialize: (slateNode, options) => {
// 默认输出 mdast 的 'link'
// 如需将 customProp 输出为 MDX 属性等,可自定义处理
return {
type: 'link', // mdast 类型
url: slateNode.url,
title: slateNode.title,
// customProp: slateNode.customProp, // MDX 属性占位?
children: convertNodesSerialize(slateNode.children, options),
};
},
},
},
// ... 其他 remarkPlugins 配置 ...
},
});默认规则概览
完整内容可查看 defaultRules.ts。主要规则概览如下:
| Markdown(mdast) | Plate 类型 | 备注 |
|---|---|---|
paragraph | p | |
heading (depth) | h1 - h6 | 根据 depth 自动映射 |
blockquote | blockquote | |
有序 list | ol / p* | ol/li/lic;或通过 p+列表缩进属性 |
无序 list | ul / p* | ul/li/lic;或通过 p+列表缩进属性 |
code (fenced) | code_block | 包裹 code_line 子节点 |
inlineCode | code (mark) | 应用于文本 |
strong | bold (mark) | 应用于文本 |
emphasis | italic (mark) | 应用于文本 |
delete | strikethrough(mark) | 应用于文本 |
link | a | |
image | img | 序列化时会包裹在段落 |
thematicBreak | hr | |
table | table | 子节点为 tr |
math (block) | equation | 需配合 remark-math 使用 |
inlineMath | inline_equation | 需配合 remark-math 使用 |
mdxJsxFlowElement | 自定义 | 需配合 remark-mdx,并补充自定义规则 |
mdxJsxTextElement | 自定义 | 需配合 remark-mdx,并补充自定义规则 |
* 列表类型的转换依赖于是否启用 ListPlugin
默认 MDX 转换(配合 remark-mdx):
| MDX(mdast) | Plate 类型 | 备注 |
|---|---|---|
<del>...</del> | strikethrough (mark) | 另一种写法 ~~strikethrough~~ |
<sub>...</sub> | subscript (mark) | H2O |
<sup>...</sup> | superscript (mark) | E=mc2 |
<u>...</u> | underline (mark) | 下划线文本 |
<mark>...</mark> | highlight (mark) | 高亮文本 |
<span style="font-family: ..."> | fontFamily (mark) | |
<span style="font-size: ..."> | fontSize (mark) | |
<span style="font-weight: ..."> | fontWeight (mark) | |
<span style="color: ..."> | color (mark) | |
<span style="background-color: ..."> | backgroundColor (mark) | |
<date>...</date> | date | 自定义日期元素 |
[text](mention:id) | mention | 自定义提及元素 |
<file name="..." /> | file | 自定义文件元素 |
<audio src="..." /> | audio | 自定义音频元素 |
<video src="..." /> | video | 自定义视频元素 |
<toc /> | toc | 目录 |
<callout>...</callout> | callout | 提示块 |
<column_group>...</column_group> | column_group | 多列布局容器 |
<column width="50%">...</column> | column | 单列,可指定宽度属性 |
附录C:渲染 Plate 节点的组件配置
rules 主要负责 MD ↔ Plate 的数据转换,而 Plate 实际渲染节点时,使用 React 组件。可以通过 createPlateEditor 的 components 选项或插件的 withComponent 方法配置。
示例:
import { createPlateEditor, ParagraphPlugin, PlateLeaf } from 'platejs/react';
import { BoldPlugin } from '@platejs/basic-nodes/react';
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { ParagraphElement } from '@/components/ui/paragraph-node'; // UI组件示例
import { CodeBlockElement } from '@/components/ui/code-block-node'; // UI组件示例
const editor = createPlateEditor({
plugins: [
ParagraphPlugin.withComponent(ParagraphElement),
CodeBlockPlugin.withComponent(CodeBlockElement),
BoldPlugin,
/* ... 其他插件 ... */
],
});import { createPlateEditor, ParagraphPlugin, PlateLeaf } from 'platejs/react';
import { BoldPlugin } from '@platejs/basic-nodes/react';
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { ParagraphElement } from '@/components/ui/paragraph-node'; // UI组件示例
import { CodeBlockElement } from '@/components/ui/code-block-node'; // UI组件示例
const editor = createPlateEditor({
plugins: [
ParagraphPlugin.withComponent(ParagraphElement),
CodeBlockPlugin.withComponent(CodeBlockElement),
BoldPlugin,
/* ... 其他插件 ... */
],
});更多自定义与注册方式请参考 插件组件相关文档。
附录D:PlateMarkdown 只读展示组件
如果需要类似 react-markdown 的只读渲染组件,可以参考下方示例:
import React, { useEffect } from 'react';
import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
// 导入各类常用的 Plate 插件
import { HeadingPlugin } from '@platejs/basic-nodes/react';
// ... 还可以按需引入 BlockquotePlugin、CodeBlockPlugin、ListPlugin等
// ... 以及粗体、斜体等标记插件
export interface PlateMarkdownProps {
children: string; // Markdown 内容
remarkPlugins?: any[];
components?: Record<string, React.ComponentType<any>>; // Plate 渲染组件(可选)
className?: string;
}
export function PlateMarkdown({
children,
remarkPlugins = [],
components = {},
className,
}: PlateMarkdownProps) {
const editor = usePlateEditor({
plugins: [
// 加入渲染 Markdown 所需的所有插件
HeadingPlugin /* ... 其他插件 ... */,
MarkdownPlugin.configure({ options: { remarkPlugins } }),
],
components, // 传递自定义渲染组件
});
useEffect(() => {
editor.tf.reset(); // 清空先前内容
editor.tf.setValue(
editor.getApi(MarkdownPlugin).markdown.deserialize(children)
);
}, [children, editor, remarkPlugins]); // 当 Markdown 内容或插件更改时重新反序列化
return (
<Plate editor={editor}>
<PlateContent readOnly className={className} />
</Plate>
);
}
// 使用示例:
// const markdownString = "# Hello\nThis is *Markdown*.";
// <PlateMarkdown className="prose dark:prose-invert">
// {markdownString}
// </PlateMarkdown>import React, { useEffect } from 'react';
import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
// 导入各类常用的 Plate 插件
import { HeadingPlugin } from '@platejs/basic-nodes/react';
// ... 还可以按需引入 BlockquotePlugin、CodeBlockPlugin、ListPlugin等
// ... 以及粗体、斜体等标记插件
export interface PlateMarkdownProps {
children: string; // Markdown 内容
remarkPlugins?: any[];
components?: Record<string, React.ComponentType<any>>; // Plate 渲染组件(可选)
className?: string;
}
export function PlateMarkdown({
children,
remarkPlugins = [],
components = {},
className,
}: PlateMarkdownProps) {
const editor = usePlateEditor({
plugins: [
// 加入渲染 Markdown 所需的所有插件
HeadingPlugin /* ... 其他插件 ... */,
MarkdownPlugin.configure({ options: { remarkPlugins } }),
],
components, // 传递自定义渲染组件
});
useEffect(() => {
editor.tf.reset(); // 清空先前内容
editor.tf.setValue(
editor.getApi(MarkdownPlugin).markdown.deserialize(children)
);
}, [children, editor, remarkPlugins]); // 当 Markdown 内容或插件更改时重新反序列化
return (
<Plate editor={editor}>
<PlateContent readOnly className={className} />
</Plate>
);
}
// 使用示例:
// const markdownString = "# Hello\nThis is *Markdown*.";
// <PlateMarkdown className="prose dark:prose-invert">
// {markdownString}
// </PlateMarkdown>该 PlateMarkdown 组件用于只读展示。如果需要完整的编辑体验,请参阅安装指南。
安全性注意事项
@platejs/markdown 优先保证渲染安全,会把 Markdown 转换成结构化的 Plate 格式,避免直接渲染 HTML。但安全性依赖于:
- 自定义
rules: 保证自定义的deserialize规则不会引入不安全数据。 remarkPlugins: 谨慎甄别使用的第三方 remark 插件,注意其安全性隐患。- 原始 HTML 处理: 如使用
rehype-raw,必须在来源不可信时联合rehype-sanitize进行清洗。 - 插件安全责任: 如链接元素需进行有效性验证,可参见 LinkPlugin 的
isUrl说明,或媒体插件的parseMediaUrl。
建议: 对于不可信的 Markdown 输入务必格外谨慎。如果开放复杂扩展或原始 HTML 功能,请做好输入清洗。
相关链接
- remark: Markdown 处理器
- unified: 统一处理流程引擎
- MDX: Markdown 文件中支持 JSX
- react-markdown: 另一种 React Markdown 组件
- remark-slate-transformer: inokawa 起初开发的 mdast ↔ Plate 转换方案
On This Page
title: Markdown description: 将 Plate 内容转换为 Markdown 及其逆过程。 toc: true特性为什么选择 Plate Markdown?套件方式使用安装挂载套件手动集成用法安装引用插件配置插件Markdown 转 Plate(反序列化)Plate 转 Markdown(序列化)循环序列化:自定义元素(MDX)API 参考MarkdownPluginapi.markdown.deserializeapi.markdown.serializeparseMarkdownBlocks示例使用 Remark 插件(GFM)自定义渲染(语法高亮)支持数学(remark-math)Mentions 支持(remarkMention)使用多列布局(Columns)Remark 插件生态(Remark Plugins)语法支持架构概览从 react-markdown 迁移附录A:Markdown中的HTML附录B:自定义转换规则(rules)附录C:渲染 Plate 节点的组件配置附录D:PlateMarkdown 只读展示组件安全性注意事项相关链接