Markdown

PreviousNext

将 Plate 内容转换为 Markdown,反之亦然。


title: Markdown description: 将 Plate 内容转换为 Markdown 及其逆过程。 toc: true

@platejs/markdown 提供了强大的 Plate 内容结构与 Markdown 之间的双向转换能力。

Loading…
Loading…

特性

  • 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 支持的方法是使用 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/markdown
pnpm 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 方法配置粘贴支持与自定义转换规则。

lib/plate-editor.ts
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 })),
  ],
});
lib/plate-editor.ts
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 })),
  ],
});

Markdown 转 Plate(反序列化)

editor.api.markdown.deserialize 方法将 Markdown 字符串转换为 Plate Value(节点数组),常用于设置编辑器初始内容。

components/my-editor.tsx
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),
});
components/my-editor.tsx
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),
});

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 配置插件:

lib/plate-editor.ts
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 的插件 */],
  },
});
lib/plate-editor.ts
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 的插件 */],
  },
});

转换流程说明:

  1. 序列化(Plate → Markdown): Plate 的 date 节点会转为 <date>2025-03-31</date>
  2. 反序列化(Markdown → Plate): MDX 标签 <date>2025-03-31</date> 可还原对应 Plate date 节点。

API 参考

MarkdownPlugin

核心插件配置对象。使用 MarkdownPlugin.configure({ options: {} }) 可以设置全局的 Markdown 处理选项。

Options

    白名单方式,指定允许哪些节点类型(包括 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

    自定义 Markdown AST 与 Plate 元素之间的转换规则。详见 轮转序列化自定义转换规则。对于 mark/leaf 类型,rule 对象需带上 mark: true。默认值:null(使用内部 defaultRules)。

    remark 插件数组 (如 remark-gfmremark-mathremark-mdx 等)。对 Markdown AST (mdast) 生效。默认值:[]

Attributes

    粘贴内容处理相关配置。设置为 null 可禁止 Markdown 粘贴。默认情况下会启用将 text/plain 视为 Markdown 粘贴。详见 PlatePlugin API > parser


api.markdown.deserialize

将 Markdown 字符串转换为 Plate ValueDescendant[])。

Parameters

    要反序列化的 Markdown 字符串。

    本次调用的配置选项,将覆盖插件默认值。

OptionsDeserializeMdOptions

    覆盖插件的 allowedNodes 配置。

    覆盖插件的 disallowedNodes 配置。

    覆盖插件的 allowNode 配置。

    为顶级 Block 节点添加 _memo 属性,记录原始 Markdown,用于如 PlateStatic 之类的记忆场景。默认值:false

    覆盖插件的 rules 配置。

    Markdown 块级解析器 (parseMarkdownBlocks) 的相关选项,详见下文。

    覆盖插件的 remarkPlugins

    若设置为 true,段落中的单个换行符(\n)会被视为段落分隔。默认值:false

ReturnsDescendant[]

    返回 Plate 节点数组。

api.markdown.serialize

将 Plate ValueDescendant[])序列化为 Markdown 字符串。

Parameters

    本次调用的序列化选项,覆盖插件默认值。

OptionsSerializeMdOptions

    需要序列化为 Markdown 的 Plate 节点,默认为 editor.children

    覆盖插件的 allowedNodes 配置。

    覆盖插件的 disallowedNodes 配置。

    覆盖插件的 allowNode 配置。

    覆盖插件的 rules 配置。

    覆盖插件的 remarkPlugins(影响 Markdown 字符串化过程)。

    若为 true,序列化时会保留区块 ID,用于如 AI 评论跟踪。会以 <block id="...">内容</block> 语法包裹。默认值false

Returnsstring

    返回 Markdown 字符串。

parseMarkdownBlocks

工具函数:将 Markdown 字符串切分为块级 token(deserialize 内部使用,memoize 时也有用)。

Parameters

    输入的 Markdown 字符串。

    解析选项。

OptionsParseMarkdownBlocksOptions

    需要排除的标记类型(如 'space')。默认值:['space']

    是否移除输入末尾的空白字符。默认值:true

ReturnsToken[]

    返回带原始 Markdown 的标记(Token)对象数组。

示例

使用 Remark 插件(GFM)

为编辑器增加 GitHub Flavored Markdown(GFM)支持,包括表格、删除线、任务列表和自动链接。

插件配置:

lib/plate-editor.ts
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],
      },
    }),
  ],
});
lib/plate-editor.ts
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 插件的渲染组件即可更改代码块的外观。

components/my-editor.tsx
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。
components/my-editor.tsx
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 规则。

lib/plate-editor.ts
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` 属性
lib/plate-editor.ts
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 数学语法(如 $行内$$$块级$$)。

插件配置:

lib/plate-editor.ts
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'
      },
    }),
  ],
});
lib/plate-editor.ts
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)

插件配置:

lib/plate-editor.ts
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],
      },
    }),
  ],
});
lib/plate-editor.ts
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 语法定义多列结构。

插件配置:

lib/plate-editor.ts
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 语法需要
      },
    }),
  ],
});
lib/plate-editor.ts
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 基于 unifiedremark 生态。通过在 MarkdownPlugin.configureremarkPlugins 选项中添加 remark 插件可扩展能力。这些插件对 mdast(Markdown 抽象语法树) 进行操作。

插件查找指南:

常见用途:

  • 语法扩展:remark-gfm(表格等)、remark-math(TeX)、remark-frontmatterremark-mdx
  • Lint/格式化:remark-lint(通常作为独立工具链)
  • 自定义转换: 编写自定义插件以变更 mdast

语法支持

@platejs/markdown 使用 remark-parse,遵循 CommonMark 标准。可通过 remarkPlugins 开启 GFM 或其它语法扩展。

架构概览

@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 |       |
          |  |           |        |                |        |               |      |           |       |
          |  +-----------+        +----------------+        +---------------+      +-----------+       |
          |                                                                                            |
          +--------------------------------------------------------------------------------------------+

关键流程:

  1. 解析(反序列化):
    • Markdown 字符串 → remark-parse → mdast
    • remarkPlugins 变换 mdast(如 remark-gfm
    • mdast-to-slate 利用 rules 转化为 Plate 节点
    • Plate 组件系统渲染节点
  2. 序列化:
    • Plate 节点 → slate-to-mdast(用 rules)→ mdast
    • remarkPlugins 变换 mdast
    • remark-stringify 输出为 Markdown 字符串

react-markdown 迁移

迁移时需将 react-markdown 的概念映射到 Plate 体系。

主要区别:

  1. 渲染流程: react-markdown(MD → mdast → hast → React) VS @platejs/markdown(MD ↔ mdast ↔ Plate JSON,Plate 组件直接渲染 Plate 节点)
  2. 组件定制方式:
    • react-markdowncomponents 属性替换 HTML 节点渲染
    • Plate:
      • MarkdownPlugin 提供 rules 定制 mdast↔Plate JSON 互转
      • createPlateEditorcomponents 配置 Plate 节点的渲染组件(详见附录C
  3. 插件生态系统: @platejs/markdown 主要以 remarkPlugins 为核心,rehypePlugins 使用较少

对照表:

react-markdown 属性@platejs/markdown 等价项/概念备注
children (字符串)传递给 editor.api.markdown.deserialize(string)通常在 createPlateEditorvalue 配置
remarkPluginsMarkdownPlugin.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);自定义渲染组件
allowedElementsMarkdownPlugin.configure({ options: { allowedNodes: [...] }})转换时过滤 mdast/Plate 节点
disallowedElementsMarkdownPlugin.configure({ options: { disallowedNodes: [...] }})转换时过滤不支持节点
unwrapDisallowed无直接等价项,默认过滤节点可通过自定义 rules 实现展开逻辑
skipHtml默认会移除大部分 HTML如需保留,使用 remarkPlugins 引入 rehype-raw
urlTransformruleslink 处理,或按插件类型序列化处理在转换规则中自定义实现
allowElementMarkdownPlugin.configure({ options: { allowNode: { ... } } })转换时自定义函数过滤节点

附录A:Markdown中的HTML

默认情况下,@platejs/markdown 出于安全考虑不会处理原始的 HTML 标签。标准 Markdown 生成的 HTML(如 *emphasis*<em>)会被正常转换。

默认回退行为是保守的:

  • 未知的内联 MDX / 类 HTML 节点会按源码文本原样保留。
  • 未知的块级 MDX / 原始 HTML 块会保留为可编辑的源码文本段落,而不是被丢弃或静默转换成其它块类型。
  • 这让源码保持原样,也更适合流式 / AI 生成的 Markdown,这类 HTML 往往会分片到达,或者没有对应的 Plate 规则。

如果需要比“保留源码”更丰富的 HTML 行为,可为目标标签显式添加 rules

如需在受信任环境下处理原始 HTML:

  1. 引入 remark-mdx 添加到 remarkPlugins
  2. 使用 rehype-rawrehype-raw添加进remarkPlugins
  3. 配置Rules: 可能需要针对解析后的 HTML hast 节点自定义rules,将其映射到 Plate 结构。
lib/plate-editor.ts
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,请补充相应规则
    },
  },
});
lib/plate-editor.ts
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,请补充相应规则
    },
  },
});

附录B:自定义转换规则(rules

MarkdownPlugin.configure 中设置的 rules 选项,提供了 mdast ↔ Plate JSON 转换的精细控制。rules 对象中的字段需与各节点类型对应。

  • 反序列化(Markdown → Plate): 键为 mdast 节点类型(如 paragraphheadingstronglink,以及 MDX 节点如 mdxJsxTextElement)。deserialize 函数接收 (mdastNode, deco, options),返回 Plate 的 DescendantDescendant[]
  • 序列化(Plate → Markdown): 键为 Plate 的元素/文本类型(如 ph1acode_blockbold)。serialize 函数接收 (slateNode, options),返回 mdast 节点。

示例:自定义链接反序列化规则

lib/plate-editor.ts
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 配置 ...
  },
});
lib/plate-editor.ts
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 类型备注
paragraphp
heading (depth)h1 - h6根据 depth 自动映射
blockquoteblockquote
有序 listol / p*ol/li/lic;或通过 p+列表缩进属性
无序 listul / p*ul/li/lic;或通过 p+列表缩进属性
code (fenced)code_block包裹 code_line 子节点
inlineCodecode (mark)应用于文本
strongbold (mark)应用于文本
emphasisitalic (mark)应用于文本
deletestrikethrough(mark)应用于文本
linka
imageimg序列化时会包裹在段落
thematicBreakhr
tabletable子节点为 tr
math (block)equation需配合 remark-math 使用
inlineMathinline_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 组件。可以通过 createPlateEditorcomponents 选项或插件的 withComponent 方法配置。

示例:

components/my-editor.tsx
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,
    /* ... 其他插件 ... */
  ],
});
components/my-editor.tsx
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 的只读渲染组件,可以参考下方示例:

components/plate-markdown.tsx
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>
components/plate-markdown.tsx
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>

安全性注意事项

@platejs/markdown 优先保证渲染安全,会把 Markdown 转换成结构化的 Plate 格式,避免直接渲染 HTML。但安全性依赖于:

  • 自定义 rules 保证自定义的 deserialize 规则不会引入不安全数据。
  • remarkPlugins 谨慎甄别使用的第三方 remark 插件,注意其安全性隐患。
  • 原始 HTML 处理: 如使用 rehype-raw,必须在来源不可信时联合 rehype-sanitize 进行清洗。
  • 插件安全责任: 如链接元素需进行有效性验证,可参见 LinkPlugin 的 isUrl 说明,或媒体插件的 parseMediaUrl

建议: 对于不可信的 Markdown 输入务必格外谨慎。如果开放复杂扩展或原始 HTML 功能,请做好输入清洗。

相关链接