HTML

将Plate内容转换为HTML及反向转换。

本指南涵盖将Plate编辑器内容转换为HTML(serializeHtml)以及将HTML解析回Plate格式(editor.api.html.deserialize)的操作。

Loading...
Files
components/demo.tsx
'use client';

import * as React from 'react';

import { Plate, usePlateEditor } from 'platejs/react';

import { EditorKit } from '@/components/editor/editor-kit';
import { Editor, EditorContainer } from '@/components/ui/editor';

import { DEMO_VALUES } from './values/demo-values';

export default function Demo({ id }: { id: string }) {
  const editor = usePlateEditor({
    plugins: EditorKit,
    value: DEMO_VALUES[id],
  });

  return (
    <Plate editor={editor}>
      <EditorContainer variant="demo">
        <Editor />
      </EditorContainer>
    </Plate>
  );
}

套件使用

安装

启用HTML序列化的最快方式是使用BaseEditorKit,它包含预配置的基础插件,支持大多数常见元素和标记的HTML转换。

import { BaseAlignKit } from './plugins/align-base-kit';
import { BaseBasicBlocksKit } from './plugins/basic-blocks-base-kit';
import { BaseBasicMarksKit } from './plugins/basic-marks-base-kit';
import { BaseCalloutKit } from './plugins/callout-base-kit';
import { BaseCodeBlockKit } from './plugins/code-block-base-kit';
import { BaseColumnKit } from './plugins/column-base-kit';
import { BaseCommentKit } from './plugins/comment-base-kit';
import { BaseDateKit } from './plugins/date-base-kit';
import { BaseFontKit } from './plugins/font-base-kit';
import { BaseLineHeightKit } from './plugins/line-height-base-kit';
import { BaseLinkKit } from './plugins/link-base-kit';
import { BaseListKit } from './plugins/list-base-kit';
import { MarkdownKit } from './plugins/markdown-kit';
import { BaseMathKit } from './plugins/math-base-kit';
import { BaseMediaKit } from './plugins/media-base-kit';
import { BaseMentionKit } from './plugins/mention-base-kit';
import { BaseSuggestionKit } from './plugins/suggestion-base-kit';
import { BaseTableKit } from './plugins/table-base-kit';
import { BaseTocKit } from './plugins/toc-base-kit';
import { BaseToggleKit } from './plugins/toggle-base-kit';
 
export const BaseEditorKit = [
  ...BaseBasicBlocksKit,
  ...BaseCodeBlockKit,
  ...BaseTableKit,
  ...BaseToggleKit,
  ...BaseTocKit,
  ...BaseMediaKit,
  ...BaseCalloutKit,
  ...BaseColumnKit,
  ...BaseMathKit,
  ...BaseDateKit,
  ...BaseLinkKit,
  ...BaseMentionKit,
  ...BaseBasicMarksKit,
  ...BaseFontKit,
  ...BaseListKit,
  ...BaseAlignKit,
  ...BaseLineHeightKit,
  ...BaseCommentKit,
  ...BaseSuggestionKit,
  ...MarkdownKit,
];

添加套件

import { createSlateEditor, serializeHtml } from 'platejs';
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
 
const editor = createSlateEditor({
  plugins: BaseEditorKit,
  value: [
    { type: 'h1', children: [{ text: 'Hello World' }] },
    { type: 'p', children: [{ text: '此内容将被序列化为HTML。' }] },
  ],
});
 
// 序列化为HTML
const html = await serializeHtml(editor);

示例

查看完整的服务端HTML生成示例:

import * as React from 'react';
 
import { cva } from 'class-variance-authority';
import fs from 'node:fs/promises';
import path from 'node:path';
import { type Value, normalizeNodeId } from 'platejs';
import { createStaticEditor, serializeHtml } from 'platejs/static';
 
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
import {
  EditorClient,
  EditorViewClient,
  ExportHtmlButton,
  HtmlIframe,
} from '@/components/editor/slate-to-html';
import { alignValue } from '@/registry/examples/values/align-value';
import { basicBlocksValue } from '@/registry/examples/values/basic-blocks-value';
import { basicMarksValue } from '@/registry/examples/values/basic-marks-value';
import { columnValue } from '@/registry/examples/values/column-value';
import { dateValue } from '@/registry/examples/values/date-value';
import { discussionValue } from '@/registry/examples/values/discussion-value';
import { equationValue } from '@/registry/examples/values/equation-value';
import { fontValue } from '@/registry/examples/values/font-value';
import { indentValue } from '@/registry/examples/values/indent-value';
import { lineHeightValue } from '@/registry/examples/values/line-height-value';
import { linkValue } from '@/registry/examples/values/link-value';
import { listValue } from '@/registry/examples/values/list-value';
import { mediaValue } from '@/registry/examples/values/media-value';
import { mentionValue } from '@/registry/examples/values/mention-value';
import { tableValue } from '@/registry/examples/values/table-value';
import { tocPlaygroundValue } from '@/registry/examples/values/toc-value';
import { createHtmlDocument } from '@/lib/create-html-document';
import { EditorStatic } from '@/components/ui/editor-static';
 
const getCachedTailwindCss = React.cache(async () => {
  const cssPath = path.join(process.cwd(), 'public', 'tailwind.css');
 
  return await fs.readFile(cssPath, 'utf8');
});
 
export default async function SlateToHtmlBlock() {
  const createValue = (): Value =>
    normalizeNodeId([
      ...basicBlocksValue,
      ...basicMarksValue,
      ...tocPlaygroundValue,
      ...linkValue,
      ...tableValue,
      ...equationValue,
      ...columnValue,
      ...mentionValue,
      ...dateValue,
      ...fontValue,
      ...discussionValue,
      ...alignValue,
      ...lineHeightValue,
      ...indentValue,
      ...listValue,
      ...mediaValue,
    ]);
 
  const editor = createStaticEditor({
    plugins: BaseEditorKit,
    value: createValue(),
  });
 
  const tailwindCss = await getCachedTailwindCss();
  const katexCDN = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.css" integrity="sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV" crossorigin="anonymous">`;
 
  // const cookieStore = await cookies();
  // const theme = cookieStore.get('theme')?.value;
  const theme = 'light';
 
  // Get the editor content HTML using EditorStatic
  const editorHtml = await serializeHtml(editor, {
    editorComponent: EditorStatic,
    props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },
  });
 
  // Create the full HTML document
  const html = createHtmlDocument({
    editorHtml,
    katexCDN,
    tailwindCss,
    theme,
  });
 
  return (
    <div className="grid grid-cols-3 px-4">
      <div className="p-2">
        <h3 className={headingVariants()}>Editor</h3>
        <EditorClient value={createValue()} />
      </div>
 
      <div className="p-2">
        <h3 className={headingVariants()}>EditorView</h3>
        <EditorViewClient value={createValue()} />
      </div>
 
      <div className="relative p-2">
        <h3 className={headingVariants()}>HTML Iframe</h3>
        <ExportHtmlButton
          className="absolute top-10 right-0"
          html={html}
          serverTheme={theme}
        />
        <HtmlIframe
          className="h-[7500px] w-full"
          html={html}
          serverTheme={theme}
        />
      </div>
    </div>
  );
}
 
const headingVariants = cva(
  'group mt-8 scroll-m-20 font-heading font-semibold text-xl tracking-tight'
);

Plate转HTML

将Plate编辑器内容(Plate节点)转换为HTML字符串。这通常在服务端完成。

查看服务端示例

关键服务端限制

在服务端环境(Node.js, RSC)中使用serializeHtml或其他Plate工具时,不得从任何platejs*包的/react子路径导入。始终使用基础导入(例如使用@platejs/basic-nodes而非@platejs/basic-nodes/react)。

这意味着服务端编辑器实例应使用platejs中的createSlateEditor,而非platejs/react中的usePlateEditorcreatePlateEditor

基础用法

提供服务端编辑器实例并在编辑器创建时配置Plate组件。

lib/generate-html.ts
import { createSlateEditor, serializeHtml } from 'platejs'; // 基础导入
// 导入基础插件(不从/react路径导入)
import { BaseHeadingPlugin } from '@platejs/basic-nodes';
// 导入用于渲染的静态组件
import { ParagraphElementStatic } from '@/components/ui/paragraph-node-static';
import { HeadingElementStatic } from '@/components/ui/heading-node-static';
// 对于带样式的静态输出,可以使用像EditorStatic这样的包装器
import { EditorStatic } from '@/components/ui/editor-static';
 
// 将插件键映射到其静态渲染组件
const components = {
  p: ParagraphElementStatic, // 'p'是段落的默认键
  h1: HeadingElementStatic,
  // ... 为所有元素和标记添加映射
};
 
// 创建带组件的服务端编辑器实例
const editor = createSlateEditor({
  plugins: [
    BaseHeadingPlugin,   // 标题基础插件
    // ... 添加与内容相关的所有其他基础插件
  ],
  components,
});
 
async function getMyHtml() {
  // 示例:在服务端编辑器上设置内容
  editor.children = [
    { type: 'h1', children: [{text: '我的标题'}] },
    { type: 'p', children: [{text: '我的内容。'}] }
  ];
 
  const html = await serializeHtml(editor, {
    // 可选:使用像EditorStatic这样的自定义包装器进行样式设置
    // editorComponent: EditorStatic,
    // props: { variant: 'none', className: 'p-4 m-4 border' },
  });
 
  return html;
}

序列化HTML的样式设置

serializeHtml仅返回编辑器内容本身的HTML。如果使用带样式的组件(如EditorStatic或具有特定类的自定义静态组件),必须确保最终显示HTML的上下文中包含必要的CSS。

这通常意味着将序列化的HTML包装在包含样式表的完整HTML文档中:

lib/generate-full-html-document.ts
// ...(来自generate-html.ts的先前设置)
 
async function getFullHtmlDocument() {
  const editorHtmlContent = await getMyHtml(); // 来自之前的示例
 
  const fullHtml = `<!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="stylesheet" href="/path/to/your-global-styles.css" />
      <link rel="stylesheet" href="/path/to/tailwind-or-component-styles.css" />
      <title>序列化内容</title>
    </head>
    <body>
      <div class="my-document-wrapper prose dark:prose-invert">
        ${editorHtmlContent}
      </div>
    </body>
  </html>`;
  return fullHtml;
}

仅静态输出

序列化过程将Plate节点转换为静态HTML。交互功能(React事件处理程序、客户端钩子)或依赖浏览器API的组件在序列化输出中将无法工作。

使用静态组件

对于服务端序列化,必须使用组件的静态版本(无仅客户端代码,无React钩子如useEffectuseState)。

参考静态渲染指南获取为Plate元素和标记创建服务端安全静态组件的详细说明。

components/ui/paragraph-node-static.tsx
import React from 'react';
import type { SlateElementProps } from 'platejs';
 
// 示例静态段落组件
export function ParagraphElementStatic(props: SlateElementProps) {
  return (
    <SlateElement {...props} className={cn('m-0 px-0 py-1')}>
      {props.children}
    </SlateElement>
  );
}

HTML转Plate

HTML反序列化器允许将HTML内容(字符串或DOM元素)转换回Plate格式。这支持往返转换,在存在对应插件规则的情况下保留结构、格式和属性。

基础用法

在客户端Plate编辑器上下文中使用editor.api.html.deserialize

components/my-html-importer.tsx
import { PlateEditor, usePlateEditor } from 'platejs/react'; // 客户端专用的React导入
// 导入表示HTML内容所需的所有Plate插件
import { HeadingPlugin } from '@platejs/basic-nodes/react';
// ... 以及粗体、斜体、表格、列表等的插件
 
function MyHtmlImporter({ htmlString }: { htmlString: string }) {
  const editor = usePlateEditor({
    plugins: [
      HeadingPlugin,     // 用于<h1>, <h2>等
      // ... 包含与预期解析的HTML对应的所有插件
    ],
  });
 
  const handleImport = () => {
    const slateValue = editor.api.html.deserialize(htmlString);
    editor.tf.setValue(slateValue);
  };
 
  // ... 渲染编辑器及触发handleImport的按钮 ...
  return <button onClick={handleImport}>导入HTML</button>;
}

客户端操作

使用editor.api.html.deserialize的HTML反序列化通常是客户端操作,因为它与配置了React组件和插件的实时Plate编辑器实例交互。

插件反序列化规则概览

每个Plate插件可以定义规则,说明在反序列化期间如何解释特定的HTML标签、样式和属性。下表是常见HTML结构及通常负责它们的Plate插件的摘要。

HTML元素/样式Plate插件(典型)备注
<strong>, <b>, font-weight: 600,700,boldBoldPlugin转换为bold: true标记。
<em>, <i>, font-style: italicItalicPlugin转换为italic: true标记。
<u>, text-decoration: underlineUnderlinePlugin转换为underline: true标记。
<s>, <del>, <strike>, text-decoration: line-throughStrikethroughPlugin转换为strikethrough: true标记。
<sub>, vertical-align: subSubscriptPlugin转换为subscript: true标记。
<sup>, vertical-align: superSuperscriptPlugin转换为superscript: true标记。
<code> (不在<pre>中), font-family: ConsolasCodePlugin转换为code: true标记(内联代码)。
<kbd>KbdPlugin转换为kbd: true标记。
<p>ParagraphPlugin转换为段落元素。
<h1> - <h6>HeadingPlugin转换为对应的标题元素(h1 - h6)。
<ul>ListPlugin (经典)转换为无序列表(ul类型)。项目变为li
<ol>ListPlugin (经典)转换为有序列表(ol类型)。项目变为li
<li> (在<ul><ol>中)ListPlugin (经典)转换为列表项(li类型),带有lic(列表项内容)子项。
<li> (带aria-level缩进)ListPlugin转换为带indentlistStyleType属性的段落。
<blockquote>BlockquotePlugin转换为blockquote元素。
<pre> (通常内部有<code>)CodeBlockPlugin转换为code_block元素。内容分割为code_line
<hr>HorizontalRulePlugin转换为水平线元素。
<a>LinkPlugin转换为链接元素(a类型)带url属性。
<img>ImagePlugin转换为图像元素(img类型)带url属性。
<iframe>MediaEmbedPlugin转换为媒体嵌入元素,尝试解析URL。
<table>TablePlugin转换为table元素。
<tr>TablePlugin转换为tr(表格行)元素。
<td>TablePlugin转换为td(表格单元格)元素。
<th>TablePlugin转换为th(表格标题单元格)元素。
style="background-color: ..."FontColorPlugin转换为backgroundColor标记。(插件名称可能看似相反)
style="color: ..."FontColorPlugin转换为color标记。
style="font-family: ..."FontFamilyPlugin转换为fontFamily标记。
style="font-size: ..."FontSizePlugin转换为fontSize标记。
style="font-weight: ..." (非粗体值)FontWeightPlugin为非标准粗体值转换为fontWeight标记。
<mark>HighlightPlugin转换为highlight: true标记。
style="text-align: ..."TextAlignPlugin在块元素上设置align属性。
style="line-height: ..."LineHeightPlugin在块元素上设置lineHeight属性。

插件配置

确切的Plate类型(例如ParagraphPlugin.key'p')取决于插件的配置方式。表格显示典型关联。确保编辑器包含对应的Plate插件以使这些规则生效。

插件中的反序列化属性

插件可以在其parsers.html.deserializer配置中定义如何处理HTML反序列化:

  • parse: 函数({ editor, element, getOptions, ... }) => Partial<SlateNode>,接收HTML元素并返回部分Plate节点。这是主要转换逻辑所在。
  • query: 可选函数({ element, getOptions }) => boolean,确定是否应考虑当前HTML元素的反序列化规则。
  • rules: 规则对象数组,每个定义匹配HTML元素的条件:
    • validNodeName: 字符串或字符串数组,用于匹配HTML标签名(例如'P', ['STRONG', 'B'])。
    • validAttribute: 对象或对象数组,指定必需的属性名和/或值(例如{ align: ['left', 'center'] })。
    • validClassName: 字符串或字符串数组,用于匹配CSS类名。
    • validStyle: 对象或对象数组,指定必需的CSS样式属性和/或值(例如{ fontWeight: ['600', '700', 'bold'] })。
  • isElement: 布尔值,true表示插件将HTML元素反序列化为Plate Element节点。
  • isLeaf: 布尔值,true表示插件将HTML元素或样式反序列化为Text节点上的Plate Leaf(标记)。
  • attributeNames: HTML属性名数组,其值应保留在结果Plate节点的node.attributes属性上。
  • withoutChildren: 布尔值,如果true,HTML元素的子节点不会被convertHtmlAttributes处理。

自定义反序列化行为

可以扩展插件以修改其HTML解析逻辑。这对于支持非标准HTML属性或结构非常有用。

lib/custom-code-block-plugin.ts
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { CodeLinePlugin } from '@platejs/code-block'; // 如果需要基础
 
const MyCustomCodeBlockPlugin = CodeBlockPlugin.configure({
  parsers: {
    html: {
      deserializer: {
        // 继承大多数规则和属性,然后覆盖或添加
        ...CodeBlockPlugin.parsers.html.deserializer,
        parse: ({ element, editor }) => { // 可能需要editor用于getType
          const language = element.getAttribute('data-custom-lang') || element.className.match(/language-(?<lang>[^\s]+)/)?.groups?.lang;
          const textContent = element.textContent || '';
          const lines = textContent.split('\n');
 
          return {
            type: CodeBlockPlugin.key, // 或editor.getType(CodeBlockPlugin.key)
            lang: language,
            code: textContent, // 示例:存储完整代码字符串
            children: lines.map((line) => ({
              type: editor.getType(CodeLinePlugin.key),
              children: [{ text: line }],
            })),
          };
        },
        rules: [
          // 如果需要,继承现有规则
          ...(CodeBlockPlugin.parsers.html.deserializer.rules || []),
          // 添加基于自定义属性的新规则
          { validAttribute: { 'data-custom-lang': true } },
        ],
      },
    },
  },
});
 
// 然后在编辑器配置中使用MyCustomCodeBlockPlugin。

此示例自定义CodeBlockPlugin以查找data-custom-lang属性或language-*类来确定代码语言。

高级反序列化示例(ListPlugin

ListPlugin展示了一个更复杂的反序列化场景,它将HTML列表结构(<li>元素)转换为Plate中的缩进段落,使用aria-level确定缩进。

以下是其反序列化逻辑的概念性展示:

// 来自ListPlugin的简化概念
export const ListPluginConfig = {
  // ... 其他配置 ...
  parsers: {
    html: {
      deserializer: {
        isElement: true,
        // query: ({ element }) => hasListAncestor(element), // 示例条件
        parse: ({ editor, element }) => ({
          type: 'p', // 将<li>转换为<p>
          indent: Number(element.getAttribute('aria-level') || '1'),
          listStyleType: element.style.listStyleType || undefined,
          // 子节点在此节点创建后由Plate的默认反序列化器处理
        }),
        rules: [
          { validNodeName: 'LI' }, // 仅适用于<li>元素
        ],
      },
    },
  },
};

这展示了插件如何完全将HTML结构重新解释为不同的Plate表示。

API 参考

serializeHtml(editor, options)

editor.children(或提供的 value)中的 Plate 节点转换为 HTML 字符串。此函数通常在服务端使用。

Parameters

    通过 createSlateEditor 创建的服务端 Plate 编辑器实例,已配置组件。

    序列化选项。

OptionsSerializeHtmlOptions<P = PlateStaticProps>

    用于在静态渲染期间包装整个编辑器内容的 React 组件。默认为 PlateStatic。 该组件接收 editorvalue 以及此处传递的任何 props

    传递给 editorComponent 的 props。P 默认为 PlateStaticProps

    要序列化的 Plate 节点。如果未提供,将使用 editor.children

    如果 stripClassNames 为 true,则保留的类名前缀数组。如果 stripClassNames 为 true,null 将保留所有类名。默认值:['slate-', 'line-clamp']

    如果为 true,则从输出 HTML 中删除所有类名,但保留 preserveClassNames 中列出的前缀的类名。默认值:true

    如果为 true,则从输出 HTML 中删除所有 data-* 属性。默认值:true

Returns

    解析为序列化 HTML 字符串的 Promise。


api.html.deserialize(options)

将 HTML 字符串或 HTMLElement 解析为 Plate ValueDescendant 节点数组)。这通常在客户端与完全配置的 Plate 编辑器一起使用。

Parameters

    客户端 Plate 编辑器实例。

    反序列化选项。

OptionsDeserializeHtmlOptions

    要反序列化的 HTML 字符串或 HTMLElement

    如果为 true(默认值),则像浏览器处理 HTML 中的空白一样折叠文本节点中的空白。设置为 false 以保留所有空白。默认值:true

    已弃用。 使用 collapseWhiteSpace。如果为 true,则修剪前导/尾随空白并折叠空白序列。默认值:true

Returns

    反序列化的 Plate Value

下一步

HTML - Plate