UniqueID 扩展

版本下载量

UniqueID 扩展为你配置的节点类型添加唯一 ID。
该扩展会跟踪你的节点,即使你拆分它们、合并它们、撤销/重做更改、裁剪内容、粘贴内容……它都能正常工作。
此外,你可以配置哪些节点类型获得唯一 ID,哪些不获得,且可以自定义这些 ID 的生成方式。

安装

npm install @tiptap/extension-unique-id

设置

attributeName

附加到 HTML 标签上的属性名称(前缀会是 data-)。

默认值:'id'

UniqueID.configure({
  attributeName: 'uid',
})

types

所有应获得唯一 ID 的节点类型,例如 ['heading', 'paragraph']
你也可以传入 'all',为文档中除 doctext 之外的所有节点类型添加 ID。

默认值:[]

UniqueID.configure({
  types: ['heading', 'paragraph'],
})
UniqueID.configure({
  types: 'all',
})

generateID

用于生成并返回唯一 ID 的函数。该函数接收一个上下文对象(例如 { node, pos }),因此你可以根据节点的类型或其位置自定义 ID 的生成方式。

默认值:({ node, pos }) => uuidv4()

UniqueID.configure({
  generateID: ({ node }) => `${node.type.name}-${uuidv4()}`,
})

filterTransaction

忽略某些事务,例如通过协作插件由其他用户应用的事务。

默认值:null

import { isChangeOrigin } from '@tiptap/extension-collaboration'

// 添加对协同编辑的支持
UniqueID.configure({
  filterTransaction: (transaction) => !isChangeOrigin(transaction),
})

updateDocument

是否通过为节点添加唯一 ID 来更新文档。如果文档处于只读模式、不可变,或者你不希望它被修改,可以将此属性设置为 false

默认值:true

UniqueID.configure({
  updateDocument: false,
})

与协作(Collaboration)一起使用

Important

When using the UniqueID extension together with the Collaboration extension, ensure that the editor is only mounted after the collaboration provider has synced. Mounting the editor on an unsynced Y.Doc can lead to unintended document state (e.g., persistent empty paragraphs), which the UniqueID extension will then preserve.

不正确的设置

In this example, the editor is mounted immediately with the Collaboration extension before the provider has synced. This causes the editor to initialize against an empty Y.Doc, which leads to unintended document mutations.

import Collaboration from '@tiptap/extension-collaboration'
import { isChangeOrigin } from '@tiptap/extension-collaboration'
import { EditorContent, useEditor } from '@tiptap/react'
import { TiptapCollabProvider } from '@tiptap-pro/provider'
import { useEffect } from 'react'
import * as Y from 'yjs'

const doc = new Y.Doc()

export default () => {
  // ❌ Editor is mounted before the provider has synced
  const editor = useEditor({
    extensions: [
      // ...other extensions
      UniqueID.configure({
        types: ['paragraph', 'heading'],
        filterTransaction: (transaction) => !isChangeOrigin(transaction),
      }),
      Collaboration.configure({
        document: doc,
      }),
    ],
    content: '<p>Initial content</p>', // This will be duplicated on every sync
  })

  useEffect(() => {
    // Provider connects and syncs after the editor is already mounted
    const provider = new TiptapCollabProvider({
      name: 'your-document-name',
      appId: 'YOUR_APP_ID',
      token: 'YOUR_JWT',
      document: doc,
    })
  }, [])

  return <EditorContent editor={editor} />
}

正确的设置

To avoid this issue, follow this initialization order:

  1. Initialize the Y.Doc
  2. Start the collaboration provider
  3. Wait for the synced event
  4. Check if the collaborative document is empty
  5. Mount the editor with the correct content
import Collaboration from '@tiptap/extension-collaboration'
import { isChangeOrigin } from '@tiptap/extension-collaboration'
import { EditorContent, useEditor } from '@tiptap/react'
import { TiptapCollabProvider } from '@tiptap-pro/provider'
import { useEffect } from 'react'
import * as Y from 'yjs'

const doc = new Y.Doc()

export default () => {
  const editor = useEditor({
    extensions: [
      // ...other extensions
      UniqueID.configure({
        types: ['paragraph', 'heading'],
        filterTransaction: (transaction) => !isChangeOrigin(transaction),
      }),
      Collaboration.configure({
        document: doc,
      }),
    ],
    // Do not set content here — wait for sync
  })

  useEffect(() => {
    const provider = new TiptapCollabProvider({
      name: 'your-document-name',
      appId: 'YOUR_APP_ID',
      token: 'YOUR_JWT',
      document: doc,

      onSynced() {
        // Only seed content if the collaborative document is empty
        const isEmpty = doc.getXmlFragment('default').length === 0

        if (isEmpty && editor) {
          editor.commands.setContent(yourInitialContent)
        }
      },
    })
  }, [])

  return <EditorContent editor={editor} />
}

关键要点

  • 不要在同步之前初始化编辑器内容。 使用协作时不要依赖默认编辑器状态。
  • 使用 filterTransaction 来忽略来自协作的变更。查看 filterTransaction 设置
  • 避免在开发过程中复用损坏的文档名称。 如果某个错误状态已经被持久化,请使用新的文档名称以干净地开始。

有关完整的协作设置指南,请参考 安装协作(Install Collaboration) 文档。

服务器端唯一 ID 工具

generateUniqueIds 函数允许你在服务端为 Tiptap 文档添加唯一 ID,无需创建 Editor 实例。这对于在服务端处理文档或需要为现有内容添加 ID 非常有用。

参数

  • doc (JSONContent):要添加唯一 ID 的 Tiptap JSON 文档
  • extensions (Extensions):要使用的扩展列表。必须包含 UniqueID 扩展。若要自定义 ID 生成方式,可以向 UniqueID 扩展传入选项。

返回类型

返回添加了唯一 ID 后的新 Tiptap 文档(一个 JSONContent 对象)。

示例

import { generateUniqueIds, UniqueID } from '@tiptap/extension-unique-id'
import { StarterKit } from '@tiptap/starter-kit'

const doc = {
  type: 'doc',
  content: [
    { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }] },
    { type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'My Heading' }] },
  ],
}

const newDoc = generateUniqueIds(doc, [
  StarterKit,
  UniqueID.configure({ types: ['paragraph', 'heading'] }),
])

// 结果:
// {
//   type: 'doc',
//   content: [
//     { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }], attrs: { id: 'd4590f81-52e8-45ec-b317-2e9a805b03e3' } },
//     { type: 'heading', content: [{ type: 'text', text: 'My Heading' }], attrs: { level: 1, id: 'c88f9b5f-7b91-442f-b4d9-ee0d04104827' } }
//   ]
// }

该函数会自动获取 UniqueID 扩展中的配置,包括 typesattributeNamegenerateID 等选项。