富文本编辑器

基于 Lexical 的 Payload 编辑器,能够以无与伦比的便捷性实现高度自定义。

本文档介绍我们基于 Lexical(Meta 的富文本编辑器)的新编辑器。之前的默认编辑器基于 Slate 仍然受支持。你可以阅读 其文档,或可选的 迁移指南 从 Slate 迁移到 Lexical(推荐)。

编辑器是 富文本字段 最重要的属性。

作为 Payload 的核心部分,我们自豪地为您提供所能想象的最佳编辑体验。不仅开箱即用提供合理的默认设置,还能灵活定制每个细节:从 "/" 菜单和工具栏(无论是内联还是固定)到插入任何你能想象的组件或子字段。

要使用富文本编辑器,首先需要安装它:

pnpm install @payloadcms/richtext-lexical

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

import { buildConfig } from 'payload'
import { lexicalEditor } from '@payloadcms/richtext-lexical'

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

你也可以按字段逐个覆盖 Lexical 设置:

import type { CollectionConfig } from 'payload'
import { lexicalEditor } from '@payloadcms/richtext-lexical'

export const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'content',
      type: 'richText',
      // 在此处传递 Lexical 编辑器并根据需要覆盖基础设置
      editor: lexicalEditor({}),
    },
  ],
}

通过 Features 扩展 Lexical 编辑器

Lexical 在设计时就考虑了可扩展性。无论你是想引入新功能还是调整现有功能,Lexical 都能让你轻松实现这些变更。

功能特性:构建模块

Lexical 定制能力的核心在于"features"(功能特性)。虽然 Lexical 自带了一系列我们认为对大多数用例都至关重要的默认功能,但其真正强大之处在于你可以根据需要重新定义、扩展或精简这些功能。

如果移除所有默认功能,你将得到一个空白编辑器。然后你可以只添加所需的功能,或者从头开始构建自己的自定义功能。

集成新功能

要集成自定义功能,可以在初始化 Lexical Editor 时使用 features 属性。以下是一个基本示例:

import {
  BlocksFeature,
  LinkFeature,
  UploadFeature,
  lexicalEditor,
} from '@payloadcms/richtext-lexical'
import { Banner } from '../blocks/Banner'
import { CallToAction } from '../blocks/CallToAction'

{
  editor: lexicalEditor({
    features: ({ defaultFeatures, rootFeatures }) => [
      ...defaultFeatures,
      LinkFeature({
        // 示例展示如何自定义 Link 功能的内置字段
        fields: ({ defaultFields }) => [
          ...defaultFields,
          {
            name: 'rel',
            label: 'Rel 属性',
            type: 'select',
            hasMany: true,
            options: ['noopener', 'noreferrer', 'nofollow'],
            admin: {
              description:
                'rel 属性定义了链接资源与当前文档之间的关系。这是一个自定义链接字段。',
            },
          },
        ],
      }),
      UploadFeature({
        collections: {
          uploads: {
            // 示例展示如何自定义 Upload 功能的内置字段
            fields: [
              {
                name: 'caption',
                type: 'richText',
                editor: lexicalEditor(),
              },
            ],
          },
        },
      }),
      // 这个功能非常强大。你可以直接在 Lexical 编辑器中重用 Payload 的 blocks:
      BlocksFeature({
        blocks: [Banner, CallToAction],
      }),
    ],
  })
}

features 可以是一个功能数组,也可以是一个返回功能数组的函数。该函数提供以下属性:

属性描述
defaultFeatures这个预设数组包含所有"推荐"的默认功能。你可以在下表中查看默认功能包含哪些内容。
rootFeatures这个数组包含在根 richText 编辑器(在 payload.config.ts 中定义)中启用的所有功能。如果当前字段是根 richText 编辑器,或者根 richText 编辑器不是 lexical 编辑器,这个数组将为空。

功能概述

以下是所有包含功能的概览:

功能名称默认包含描述
BoldFeature处理粗体文本格式
ItalicFeature处理斜体文本格式
UnderlineFeature处理下划线文本格式
StrikethroughFeature处理删除线文本格式
SubscriptFeature处理下标文本格式
SuperscriptFeature处理上标文本格式
InlineCodeFeature处理行内代码文本格式
ParagraphFeature处理段落。由于段落已经是 lexical 的核心功能,此功能主要处理 Slash 菜单和添加块菜单中的段落选项
HeadingFeature添加标题节点(默认支持 H1 - H6,但可自定义)
AlignFeature允许文本左对齐、居中对齐和右对齐
IndentFeature允许使用 Tab 键缩进文本
UnorderedListFeature添加无序列表 (ul)
OrderedListFeature添加有序列表 (ol)
ChecklistFeature添加复选框列表
LinkFeature允许创建内部和外部链接
RelationshipFeature允许创建块级(非行内)文档关联
BlockquoteFeature允许创建块级引用
UploadFeature允许创建块级上传节点 - 支持所有类型的上传,不仅仅是图片
HorizontalRuleFeature水平分隔线。基本上显示一个 <hr> 元素
InlineToolbarFeature行内工具栏是选中文本时出现的浮动工具栏。此工具栏仅包含与选中文本相关的操作
FixedToolbarFeature此经典工具栏固定在顶部并始终可见。行内工具栏和固定工具栏可以同时启用
BlocksFeature允许直接在编辑器中使用 Payload 的 Blocks Field。在功能属性中,你可以指定允许的块 - 就像在 Blocks 字段中一样。
TreeViewFeature在编辑器下方添加调试框,可以实时查看当前编辑器状态、DOM 以及时间旅行。对调试非常有用
EXPERIMENTAL_TableFeature添加表格支持。此功能可能会在未来被移除或发生破坏性变更 - 即使在稳定的 lexical 版本中,也不需要主版本更新。
EXPERIMENTAL_TextStateFeature允许在 TextNodes 中存储键值属性并为其分配行内样式。

注意到连工具栏都是功能吗?这就是我们的 lexical 编辑器的可扩展性 - 理论上你可以创建自己的工具栏!

创建自定义功能

你可以在我们的构建自定义功能文档中找到更多关于创建自定义功能的信息。

TypeScript

在 lexical 中,所有保存的数据都完全类型化。它为每个节点提供了类型,这些类型可以从 @payloadcms/richtext-lexical 导入 - 每个类型都以 Serialized 为前缀,例如 SerializedUploadNode

要为整个编辑器 JSON 提供完整类型,可以使用我们的 TypedEditorState 辅助类型,它接受所有可能的节点类型的联合作为泛型。我们不提供已经包含所有可能节点类型的类型,因为这些类型取决于你在编辑器中启用了哪些功能。以下是一个示例:

import type {
  SerializedAutoLinkNode,
  SerializedBlockNode,
  SerializedHorizontalRuleNode,
  SerializedLinkNode,
  SerializedListItemNode,
  SerializedListNode,
  SerializedParagraphNode,
  SerializedQuoteNode,
  SerializedRelationshipNode,
  SerializedTextNode,
  SerializedUploadNode,
  TypedEditorState,
  SerializedHeadingNode,
} from '@payloadcms/richtext-lexical'

const editorState: TypedEditorState<
  | SerializedAutoLinkNode
  | SerializedBlockNode
  | SerializedHorizontalRuleNode
  | SerializedLinkNode
  | SerializedListItemNode
  | SerializedListNode
  | SerializedParagraphNode
  | SerializedQuoteNode
  | SerializedRelationshipNode
  | SerializedTextNode
  | SerializedUploadNode
  | SerializedHeadingNode
> = {
  root: {
    type: 'root',
    direction: 'ltr',
    format: '',
    indent: 0,
    version: 1,
    children: [
      {
        children: [
          {
            detail: 0,
            format: 0,
            mode: 'normal',
            style: '',
            text: 'Some text. Every property here is fully-typed',
            type: 'text',
            version: 1,
          },
        ],
        direction: 'ltr',
        format: '',
        indent: 0,
        type: 'paragraph',
        textFormat: 0,
        version: 1,
      },
    ],
  },
}

或者,你也可以使用 DefaultTypedEditorState 类型,它包含了 defaultFeatures 中所有节点的类型:

import type { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'

const editorState: DefaultTypedEditorState = {
  root: {
    type: 'root',
    direction: 'ltr',
    format: '',
    indent: 0,
    version: 1,
    children: [
      {
        children: [
          {
            detail: 0,
            format: 0,
            mode: 'normal',
            style: '',
            text: 'Some text. Every property here is fully-typed',
            type: 'text',
            version: 1,
          },
        ],
        direction: 'ltr',
        format: '',
        indent: 0,
        type: 'paragraph',
        textFormat: 0,
        version: 1,
      },
    ],
  },
}

TypedEditorState 类似,DefaultTypedEditorState 也接受一个可选的节点类型联合作为泛型。这里,这将添加指定的节点类型到默认类型中。示例:

DefaultTypedEditorState<SerializedBlockNode | YourCustomSerializedNode>

这是编辑器状态的类型安全表示。如果你查看节点 type 属性的自动建议,你会看到所有可以使用的节点类型。

确保只使用从 @payloadcms/richtext-lexical 导出的类型,而不是从 lexical 核心包导出的类型。我们只能控制我们导出的类型,并确保它们是正确的,尽管 lexical 核心可能会导出名称相同的类型。

自动类型生成

目前 Lexical 还不能为你的 richText 字段生成精确的类型定义——这一功能将在未来改进。当前它只会输出编辑器 JSON 的大致结构,你可以通过类型断言来增强这些类型。

管理员界面自定义

富文本字段编辑器配置有一个 admin 属性,包含以下选项:

PropertyDescription
placeholder设置此属性可定义字段的占位文本。
hideGutter将此属性设为 true 可隐藏管理面板中该字段的装订线。
hideInsertParagraphAtEnd将此属性设为 true 可隐藏编辑器末尾出现的"+"按钮

禁用装订线

通过将 hideGutter 属性设为 true 可以禁用装订线(编辑器与屏幕左边缘之间的垂直间距线):

{
  name: 'richText',
  type: 'richText',
  editor: lexicalEditor({
    admin: {
      hideGutter: true
    },
  }),
}

自定义占位文本

通过设置 placeholder 属性可以自定义占位文本(编辑器为空时显示的文本):

{
  name: 'richText',
  type: 'richText',
  editor: lexicalEditor({
    admin: {
      placeholder: '在此输入内容...'
    },
  }),
}