探索 Tiptap V3 的最新功能

从 Quill 迁移到 Tiptap

Editor

Tiptap 是一个流行的 Quill 替代方案,从 Quill 迁移到 Tiptap 十分简便。本指南将帮助你顺利完成从 Quill 的 Delta 格式到 Tiptap 扩展系统的过渡。

内容迁移

HTML 内容兼容性

Quill 使用 Delta 格式存储内容,需先转换为 HTML 以供 Tiptap 使用:

// 先将 Quill Delta 转换为 HTML
const quill = new Quill('#temp-editor')
quill.setContents(existingDeltaContent)
const htmlContent = quill.root.innerHTML

// 在 Tiptap 中使用 HTML 内容
const editor = new Editor({
  content: htmlContent,
  extensions: [StarterKit],
})

如果你已有 Quill 输出的 HTML,可以直接使用:

// 你的现有 Quill HTML 内容
const existingContent = '<p>Hello <strong>world</strong>!</p>'

// 直接在 Tiptap 中使用
const editor = new Editor({
  content: existingContent,
  extensions: [StarterKit],
})

虽然 HTML 已完全兼容,我们建议转换为 Tiptap 的 JSON 格式以获得更好的性能和可读性。对于批量转换现有内容,可使用HTML 工具程序化地将 HTML 转为 JSON。

编辑器设置

安装

首先,安装 Tiptap 及其依赖:

npm install @tiptap/core @tiptap/starter-kit

Tiptap 支持所有现代前端 UI 框架,如 React 和 Vue。请参阅我们的安装指南中针对具体框架的安装说明。

基础编辑器设置

将 Quill 的初始化替换为 Tiptap:

// Quill(之前)
const quill = new Quill('#editor', {
  theme: 'snow',
  modules: {
    toolbar: [
      ['bold', 'italic', 'underline'],
      ['link', 'image'],
      [{ list: 'ordered' }, { list: 'bullet' }],
      [{ header: [1, 2, 3, false] }],
    ],
  },
})

// Tiptap(之后)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

扩展

了解 Tiptap 的扩展系统

Tiptap 使用模块化扩展系统,类似于 Quill 的模块系统。每个功能都是独立的扩展,可以进行配置和自定义。

StarterKit 包含了所有基本扩展,你也可以根据需要增删其他扩展。

在我们的扩展指南中探索所有可用扩展,或者创建自定义扩展以支持自定义功能和 HTML 元素。

常见 Quill 模块对应关系

Quill 功能Tiptap 扩展备注
加粗Bold含于 StarterKit
斜体Italic含于 StarterKit
下划线Underline含于 StarterKit
链接Link含于 StarterKit
图片Image单独提供
列表(项目符号/有序)BulletList, OrderedList, ListItem含于 StarterKit
标题Heading含于 StarterKit
引用块Blockquote含于 StarterKit
代码块CodeBlock含于 StarterKit
删除线Strike含于 StarterKit
字体颜色TextStyle, Color单独提供
背景色TextStyle, Highlight单独提供
对齐TextAlign单独提供

扩展配置

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import TextAlign from '@tiptap/extension-text-align'
import { Color } from '@tiptap/extension-color'
import TextStyle from '@tiptap/extension-text-style'

const editor = new Editor({
  extensions: [
    StarterKit,
    Image.configure({
      inline: true,
      allowBase64: true,
    }),
    TextAlign.configure({
      types: ['heading', 'paragraph'],
    }),
    TextStyle,
    Color.configure({
      types: [TextStyle.name, ListItem.name],
    }),
  ],
})

自定义扩展

针对 Quill 自定义模块或 blot,请创建对应的 Tiptap 自定义扩展。详见我们的自定义扩展指南

UI 实现

工具栏实现

Quill 的工具栏配置需要转换为 Tiptap 中的自定义 UI 组件:

// Quill 工具栏配置
toolbar: [
  ['bold', 'italic', 'underline'],
  ['link', 'image'],
  [{ list: 'ordered' }, { list: 'bullet' }],
  [{ header: [1, 2, 3, false] }],
]

// Tiptap 等效实现(React 示例)
function Toolbar({ editor }) {
  if (!editor) return null

  return (
    <div className="toolbar">
      <div className="toolbar-group">
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          className={editor.isActive('bold') ? 'active' : ''}
        >
          加粗
        </button>
        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          className={editor.isActive('italic') ? 'active' : ''}
        >
          斜体
        </button>
        <button
          onClick={() => editor.chain().focus().toggleUnderline().run()}
          className={editor.isActive('underline') ? 'active' : ''}
        >
          下划线
        </button>
      </div>

      <div className="toolbar-group">
        <button
          onClick={() => {
            const url = window.prompt('输入链接 URL')
            if (url) {
              editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
            }
          }}
          className={editor.isActive('link') ? 'active' : ''}
        >
          链接
        </button>
        <button
          onClick={() => {
            const url = window.prompt('输入图片 URL')
            if (url) {
              editor.chain().focus().setImage({ src: url }).run()
            }
          }}
        >
          图片
        </button>
      </div>

      <div className="toolbar-group">
        <button
          onClick={() => editor.chain().focus().toggleOrderedList().run()}
          className={editor.isActive('orderedList') ? 'active' : ''}
        >
          有序列表
        </button>
        <button
          onClick={() => editor.chain().focus().toggleBulletList().run()}
          className={editor.isActive('bulletList') ? 'active' : ''}
        >
          项目符号列表
        </button>
      </div>

      <div className="toolbar-group">
        <select
          onChange={(e) => {
            const level = parseInt(e.target.value)
            if (level === 0) {
              editor.chain().focus().setParagraph().run()
            } else {
              editor.chain().focus().toggleHeading({ level }).run()
            }
          }}
          value={
            editor.isActive('heading', { level: 1 })
              ? 1
              : editor.isActive('heading', { level: 2 })
                ? 2
                : editor.isActive('heading', { level: 3 })
                  ? 3
                  : 0
          }
        >
          <option value={0}>普通文本</option>
          <option value={1}>标题 1</option>
          <option value={2}>标题 2</option>
          <option value={3}>标题 3</option>
        </select>
      </div>
    </div>
  )
}

预构建 UI 组件

为了加快开发速度,可以使用 Tiptap 提供的预构建 UI 组件:

气泡工具栏(Quill 气泡主题)

使用 Tiptap 的 BubbleMenu 模拟 Quill 的气泡主题:

import { BubbleMenu, useEditor } from '@tiptap/react'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
  })

  return (
    <>
      <EditorContent editor={editor} />
      <BubbleMenu editor={editor}>
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          className={editor.isActive('bold') ? 'active' : ''}
        >
          加粗
        </button>
        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          className={editor.isActive('italic') ? 'active' : ''}
        >
          斜体
        </button>
        <button
          onClick={() => {
            const url = window.prompt('输入链接 URL')
            if (url) {
              editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
            }
          }}
          className={editor.isActive('link') ? 'active' : ''}
        >
          链接
        </button>
      </BubbleMenu>
    </>
  )
}

迁移清单

后续步骤