从 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-kitTiptap 支持所有现代前端 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>
</>
)
}