Slate Editor

默认的 Payload 编辑器目前基于 Lexical。本文档 是关于我们旧的基于 Slate 的编辑器。你可以继续使用它,因为它仍然受支持,或者你可以 查看可选的迁移指南从 Slate 迁移到 Lexical(推荐)。

要使用 Slate 编辑器,首先需要安装它:

npm install --save @payloadcms/richtext-slate

安装完成后,你可以将其传递给你的顶层 Payload 配置:

import { buildConfig } from 'payload'
import { slateEditor } from '@payloadcms/richtext-slate'

export default buildConfig({
  collections: [
    // 你的 collections 配置
  ],
  // 将 Slate 编辑器传递给根配置
  editor: slateEditor({}),
})

以下是如何逐个字段安装 Slate 编辑器并自定义其选项的示例:

import type { CollectionConfig } from 'payload'
import { slateEditor } from '@payloadcms/richtext-slate'

export const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'content',
      type: 'richText',
      // 在此处传递 Slate 编辑器并进行相应配置
      editor: slateEditor({
        admin: {
          elements: [
            // 在此处自定义 Slate 编辑器中允许的元素
          ],
          leaves: [
            // 在此处自定义 Slate 编辑器中允许的叶子节点
          ],
        },
      }),
    },
  ],
}

管理选项

elements

elements 属性用于指定在管理面板中应为字段提供哪些内置或自定义的 SlateJS 元素

Payload 中默认可用的 elements 包括:

leaves

leaves 属性用于指定在 Admin Panel 中启用的内置或自定义 SlateJS leaves

Payload 中默认可用的 leaves 包括:

  • bold(加粗)
  • code(代码)
  • italic(斜体)
  • strikethrough(删除线)
  • underline(下划线)

link.fields

该属性允许将字段作为富文本编辑器中链接的额外字段保存。当设置此属性时,这些字段会显示在一个模态框中,可以通过点击链接元素上的"编辑"按钮打开。

link.fields 可以是一个字段数组(在这种情况下,所有定义的字段将被追加到默认字段下方),也可以是一个函数,该函数接受默认字段作为唯一参数,并返回定义要使用的全部字段的数组(从而提供覆盖默认字段的机制)。

RichText 链接字段 带有自定义字段的 RichText 链接

upload.collections[collection-name].fields

该属性允许将字段作为富文本编辑器中上传字段的元数据保存。当设置此属性时,这些字段会显示在一个模态框中,可以通过点击上传元素上的"编辑"按钮打开。

RichText 上传元素 使用上传元素的 RichText 字段

RichText 上传元素模态框 显示配置字段的 RichText 上传元素模态框

关系元素

内置的 relationship 元素是一种强大的方式,可以直接在你的富文本编辑器中引用其他文档。

上传元素

relationship 元素类似,upload 元素是一种用户友好的方式来引用支持上传的集合,其 UI 专门为媒体/基于图片的上传设计。

提示:

默认情况下,集合会自动允许在富文本关系型和上传元素中被选择。如果你想禁止某个集合在富文本字段中被引用,可以将集合的管理选项 enableRichTextLinkenableRichTextRelationship 设置为 false。

关系型和上传元素会动态填充到你的富文本字段内容中。在 REST 和 Local API 中,任何存在的 RichText relationshipupload 元素都会遵循你传递的 depth 选项,并相应地被填充。在 GraphQL 中,每个 richText 字段都接受一个 depth 参数供你使用。

文本对齐元素

文本对齐默认不包含在内,可以通过将 textAlign 添加到元素列表中来为富文本编辑器添加该功能。TextAlign 会修改现有元素,在生成的 JSON 中包含一个新的 textAlign 字段。该字段可以与其他元素和叶子节点结合使用,将内容定位到左侧、居中或右侧。

指定允许的元素和叶子节点

要指定该字段允许使用的默认元素或叶子节点,可以定义包含你希望启用的每个元素或叶子节点名称的字符串数组。要指定自定义元素或叶子节点,可以传递一个包含所有相应属性的对象,如下所述。查看示例了解所有这些如何工作。

构建自定义元素和叶子节点

你可以设计和构建自己的 Slate 元素和叶子节点,通过自定义功能来扩展编辑器。为此,首先请阅读 SlateJS 文档 并查看 Slate 示例,全面熟悉 SlateJS 编辑器。

一旦你掌握了相关的基本概念,就可以将自定义的元素和叶子节点传递给你的字段管理配置。

自定义元素和叶子节点都通过以下配置定义:

PropertyDescription
name *作为该元素默认 type 使用的名称。
Button *在富文本工具栏中渲染的 React 组件。
plugins提供给富文本编辑器的插件数组。
type覆盖 name 默认类型的自定义类型

自定义 Element 还需要设置 Element 属性为一个 React 组件,该组件将在富文本编辑器内部作为 Element 渲染。

自定义 Leaf 对象遵循类似的模式,但需要你定义 Leaf 属性而非 Element

指定自定义 Type 允许你通过向 JSON 对象添加额外字段来扩展自定义元素。

示例

collections/ExampleCollection.ts

import type { CollectionConfig } from 'payload'

import { slateEditor } from '@payloadcms/richtext-slate'

export const ExampleCollection: CollectionConfig = {
  slug: 'example-collection',
  fields: [
    {
      name: 'content', // 必填
      type: 'richText', // 必填
      defaultValue: [
        {
          children: [{ text: '这是该字段的默认内容' }],
        },
      ],
      required: true,
      editor: slateEditor({
        admin: {
          elements: [
            'h2',
            'h3',
            'h4',
            'link',
            'blockquote',
            {
              name: 'cta',
              Button: CustomCallToActionButton,
              Element: CustomCallToActionElement,
              plugins: [
                // 该元素所需的任何插件放在这里
              ],
            },
          ],
          leaves: [
            'bold',
            'italic',
            {
              name: 'highlight',
              Button: CustomHighlightButton,
              Leaf: CustomHighlightLeaf,
              plugins: [
                // 该叶子节点所需的任何插件放在这里
              ],
            },
          ],
          link: {
            // 向 Link 元素注入自定义字段
            fields: [
              {
                name: 'rel',
                label: 'Rel 属性',
                type: 'select',
                hasMany: true,
                options: ['noopener', 'noreferrer', 'nofollow'],
              },
            ],
          },
          upload: {
            collections: {
              media: {
                fields: [
                  // 你想在 `media` 集合的上传元素上保存的任何字段
                ],
              },
            },
          },
        },
      }),
    },
  ],
}

生成 HTML

由于 Rich Text 字段以 JSON 格式保存内容,你需要自行将其渲染为 HTML。以下是一个将 Rich Text 内容生成 JSX/HTML 的示例:

import React, { Fragment } from "react";
import escapeHTML from "escape-html";
import { Text } from "slate";

const serialize = (children) =>
  children.map((node, i) => {
    if (Text.isText(node)) {
      let text = (
        <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
      );

      if (node.bold) {
        text = <strong key={i}>{text}</strong>;
      }

      if (node.code) {
        text = <code key={i}>{text}</code>;
      }

      if (node.italic) {
        text = <em key={i}>{text}</em>;
      }

      // 在这里处理其他叶子节点类型...

      return <Fragment key={i}>{text}</Fragment>;
    }

    if (!node) {
      return null;
    }

    switch (node.type) {
      case "h1":
        return <h1 key={i}>{serialize(node.children)}</h1>;
      // 在这里遍历所有标题...
      case "h6":
        return <h6 key={i}>{serialize(node.children)}</h6>;
      case "blockquote":
        return <blockquote key={i}>{serialize(node.children)}</blockquote>;
      case "ul":
        return <ul key={i}>{serialize(node.children)}</ul>;
      case "ol":
        return <ol key={i}>{serialize(node.children)}</ol>;
      case "li":
        return <li key={i}>{serialize(node.children)}</li>;
      case "link":
        return (
          <a href={escapeHTML(node.url)} key={i}>
            {serialize(node.children)}
          </a>
        );

      default:
        return <p key={i}>{serialize(node.children)}</p>;
    }
  });

注意:

上述示例展示的是如何渲染为 JSX,虽然对于纯 HTML 模式也类似。 只需移除 JSX 并返回 HTML 字符串即可!

内置 SlateJS 插件

Payload 提供了一些内置的 SlateJS 插件,可以通过扩展这些插件来更轻松地开发你自己的元素(elements)和叶子节点(leaves)。

shouldBreakOutOnEnter

Payload 内置的所有标题元素都允许通过"硬回车"来"跳出"当前激活的元素。例如,当你在 h1 元素中按下 enter 键时,将会"跳出" h1 元素,然后你可以继续以默认的段落元素进行输入。

如果你想在自己的自定义元素中使用这个功能,可以通过向你的 element 添加自定义插件来实现,如下面的"大号正文"元素示例:

customLargeBodyElement.js:

import Button from './Button'
import Element from './Element'
import withLargeBody from './plugin'

export default {
  name: 'large-body',
  Button,
  Element,
  plugins: [
    (incomingEditor) => {
      const editor = incomingEditor
      const { shouldBreakOutOnEnter } = editor

      editor.shouldBreakOutOnEnter = (element) =>
        element.type === 'large-body' ? true : shouldBreakOutOnEnter(element)

      return editor
    },
  ],
}

在上面的代码中,我们创建了一个名为 large-body 的自定义 SlateJS 元素。这个元素可能会在你的应用前端渲染稍大一些的正文内容。我们为它传递了名称、按钮和元素——此外,我们还传递了一个包含单个 SlateJS 插件的 plugins 数组。

该插件本身扩展了 Payload 内置的 shouldBreakOutOnEnter Slate 函数,将自己的元素名称添加到按下 enter 键时应该"跳出"的元素列表中。

TypeScript

如果你正在构建自己的自定义富文本元素或叶子节点,可能会用到以下类型:

import type {
  RichTextCustomElement,
  RichTextCustomLeaf,
} from '@payloadcms/richtext-slate'