Lexical 迁移指南

从 Slate 迁移

虽然 Slate 和 Lexical 都将编辑器状态保存为 JSON,但它们的 JSON 结构有所不同。

通过迁移脚本迁移(推荐)

只需导入我们提供的 migrateSlateToLexical 函数,传入 payload 对象并运行它。根据集合数量,这可能需要一些时间。

重要提示:这将覆盖所有 Slate 数据。我们建议先执行以下操作:

  1. 备份整个数据库。如果出现问题且没有备份,你将无法获得任何支持。
  2. 将所有 richText 字段转换为 lexical 编辑器。此脚本只会转换带有旧 Slate 数据的 lexical richText 字段。
  3. 首先添加 SlateToLexicalFeature(如下所示),并通过加载 Admin Panel 进行测试,查看迁移器是否按预期工作。对于某些字段,可能需要先构建一些自定义转换器来转换自定义 Slate 节点。SlateToLexicalFeature 是存储这些转换器的地方。只有添加了此功能的字段才会被迁移。
  4. 如果一切正常,在所有初始化 SlateToLexicalFeature 的地方添加 disableHooks: true 属性。例如:SlateToLexicalFeature({ disableHooks: true })。完成后,即可运行迁移脚本。
import { migrateSlateToLexical } from '@payloadcms/richtext-lexical/migrate'

await migrateSlateToLexical({ payload })

通过 SlateToLexicalFeature 进行迁移

一种处理方式是让你的 lexical 编辑器能够读取 slate JSON 数据。

只需将 SlateToLexicalFeature 添加到你的编辑器中:

import type { CollectionConfig } from 'payload'

import { SlateToLexicalFeature } from '@payloadcms/richtext-lexical/migrate'
import { lexicalEditor } from '@payloadcms/richtext-lexical'

const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'nameOfYourRichTextField',
      type: 'richText',
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [
          ...defaultFeatures,
          SlateToLexicalFeature({}),
        ],
      }),
    },
  ],
}

这样就完成了!现在,每当这个 lexical 编辑器初始化时,它都会实时将 slate 数据转换为 lexical 格式。如果数据已经是 lexical 格式,则会直接传递。

这是目前从 Slate 迁移到 Lexical 最简单的方法,但需要注意以下几点:

  • 初始化 lexical 编辑器时会有性能损耗
  • 编辑器在输出 JSON 时仍会保留 Slate 数据,因为实时转换器仅针对 Admin Panel 运行

简单的解决方案是:编辑 richText 字段并保存文档!这会用 lexical 数据覆盖 slate 数据,下次加载文档时将使用 lexical 数据。这既解决了性能问题,也解决了该特定文档的输出问题。然而,这是一个缓慢渐进式的迁移过程,因此你需要同时支持两种 API 格式。特别是对于大量文档,我们建议运行上述提到的迁移脚本。

转换自定义 Slate 节点

如果你有自定义的 Slate 节点,需要为它们创建自定义转换器。以下是 Upload 转换器的示例:

import type { SerializedUploadNode } from '../uploadNode'
import type { SlateNodeConverter } from '@payloadcms/richtext-lexical'

export const SlateUploadConverter: SlateNodeConverter = {
  converter({ slateNode }) {
    return {
      fields: {
        ...slateNode.fields,
      },
      format: '',
      relationTo: slateNode.relationTo,
      type: 'upload',
      value: {
        id: slateNode.value?.id || '',
      },
      version: 1,
    } as const as SerializedUploadNode
  },
  nodeTypes: ['upload'],
}

原理很简单:你接收一个 Slate 节点作为输入,然后返回 lexical 节点。nodeTypes 数组用于确定该转换器能处理哪些 Slate 节点。

在使用迁移脚本时,你可以将自定义转换器添加到 convertSlateToLexical 属性的 converters 中,如上例所示。

当使用 SlateToLexicalFeature 时,你可以将自定义转换器添加到 SlateToLexicalFeature 属性的 converters 中:

import type { CollectionConfig } from 'payload'

import { lexicalEditor } from '@payloadcms/richtext-lexical'
import {
  SlateToLexicalFeature,
  defaultSlateConverters,
} from '@payloadcms/richtext-lexical'

import { YourCustomConverter } from '../converters/YourCustomConverter'

const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'nameOfYourRichTextField',
      type: 'richText',
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [
          ...defaultFeatures,
          SlateToLexicalFeature({
            converters: [...defaultSlateConverters, YourCustomConverter],
          }),
        ],
      }),
    },
  ],
}

从 payload-plugin-lexical 迁移

payload-plugin-lexical 迁移的过程与从 Slate 迁移类似。

你可以使用 LexicalPluginToLexicalFeature 替代 SlateToLexicalFeature,并使用 convertLexicalPluginToLexical 替代 convertSlateToLexical

将旧版本的 lexical 数据迁移到新版本

每个 lexical 节点都有一个保存在数据库中的 version 属性。每当我们对节点数据进行破坏性变更时,都会递增版本号。这样,我们可以检测到旧版本数据,并在你打开编辑器时自动将旧数据转换为新格式。

问题是,这种迁移只会在你打开编辑器、修改 richText 字段(从而调用字段的 setValue 函数)并保存文档时发生。在你对所有文档执行此操作之前,部分文档仍将保留旧数据。

为解决这个问题,我们导出了一个 upgradeLexicalData 函数,它会遍历 Payload 应用中的每个文档,如果文档包含 lexical 编辑器,则重新保存它。这样,数据会自动转换为新格式,并且这种自动转换会应用到应用中的所有文档。

重要提示:请备份整个数据库。如果出现任何问题且你没有备份,你将无法获得任何支持。

import { upgradeLexicalData } from '@payloadcms/richtext-lexical'

await upgradeLexicalData({ payload })