探索 Tiptap V3 的最新功能

从 CKEditor 5 迁移至 Tiptap

Editor

如果你正在弃用 CKEditor,Tiptap 是一个很好的替代方案。从 CKEditor 5 迁移到 Tiptap 非常简单。本文档涵盖了顺利过渡所需了解的所有内容。

内容迁移

HTML 内容兼容性

CKEditor 5 通常输出 HTML 内容,Tiptap 可直接使用,无需任何转换:

// 你现有的 CKEditor 5 内容
const existingContent =
  '<p>Hello <strong>world</strong>!</p><ul><li>Item 1</li><li>Item 2</li></ul>'

// 直接在 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。请参阅我们针对具体框架的安装指南

基础编辑器配置

用 Tiptap 替换你的 CKEditor 5 初始化:

// CKEditor 5 (之前)
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials'
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic'

ClassicEditor.create(document.querySelector('#editor'), {
  plugins: [Essentials, Bold, Italic],
  toolbar: ['bold', 'italic'],
})

// 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 使用类似于 CKEditor 5 插件架构的模块化扩展系统。每个功能都是独立的扩展,可进行配置和自定义。

StarterKit 是所有基础扩展的集合,你可以根据需要添加或删除其他扩展。

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

常见 CKEditor 5 插件对应关系

CKEditor 5 插件Tiptap 扩展备注
BoldBold包含在 StarterKit 中
ItalicItalic包含在 StarterKit 中
ListBulletList, OrderedList, ListItem包含在 StarterKit 中
LinkLink包含在 StarterKit 中
ImageImage单独提供
TableTable单独提供
CodeBlockCodeBlock包含在 StarterKit 中
HeadingHeading包含在 StarterKit 中
BlockquoteBlockquote包含在 StarterKit 中
AlignmentTextAlign单独提供
FontColorTextStyle, Color单独提供
FontSizeTextStyle, FontSize单独提供

扩展配置

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
import TextAlign from '@tiptap/extension-text-align'

const editor = new Editor({
  extensions: [
    StarterKit,
    Image.configure({
      inline: true,
      allowBase64: true,
    }),
    Table.configure({
      resizable: true,
    }),
    TableRow,
    TableHeader,
    TableCell,
    TextAlign.configure({
      types: ['heading', 'paragraph'],
    }),
  ],
})

自定义扩展

针对 CKEditor 5 的自定义插件,可创建自定义 Tiptap 扩展。详见我们的自定义扩展指南

UI 实现

工具栏实现

CKEditor 5 的工具栏配置可转为 Tiptap 的自定义 UI 组件:

// CKEditor 5 工具栏配置
toolbar: ['heading', 'bold', 'italic', 'link', 'bulletedList', 'numberedList']

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

  return (
    <div className="toolbar">
      <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>

      <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>

      <button
        onClick={() => editor.chain().focus().toggleBulletList().run()}
        className={editor.isActive('bulletList') ? 'active' : ''}
      >
        项目符号列表
      </button>

      <button
        onClick={() => editor.chain().focus().toggleOrderedList().run()}
        className={editor.isActive('orderedList') ? 'active' : ''}
      >
        编号列表
      </button>
    </div>
  )
}

预构建 UI 组件

为了加快开发,可使用 Tiptap 的预构建 UI 组件:

气泡编辑器等价

CKEditor 5 的气泡编辑器可用 Tiptap 的 BubbleMenu 实现:

import { BubbleMenu } 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>
    </>
  )
}

迁移清单

下一步