扩展 API
Tiptap 的强大之处在于其灵活性。您可以从头开始创建自己的扩展,并构建一个独特的编辑器体验,以满足您的需求。
所有扩展的基本结构都是相同的,无论您是创建节点、标记还是功能更改。而且,Tiptap 中的一切都是基于扩展的。
创建扩展
扩展为 Tiptap 添加了新功能。在文档中,您会经常看到“扩展”这个词,甚至在节点和标记中也是如此。但也有字面意义上的扩展。这些扩展不能添加到架构中(就像标记和节点那样),但它们可以添加功能或改变编辑器的行为。
一个很好的例子是监听编辑器事件并处理它们的内容。像这样:
import { Extension } from '@tiptap/core'
const CustomExtension = Extension.create({
name: 'customExtension',
onUpdate() {
console.log(this.editor.getJSON())
},
})您也可以使用回调函数创建扩展。这在您想封装扩展逻辑时非常有用,例如,当您想定义事件处理程序或其他自定义逻辑时。
import { Extension } from '@tiptap/core'
const CustomExtension = Extension.create(() => {
// 定义扩展内部使用的变量或函数
const customVariable = 'foo'
function onCreate() {}
function onUpdate() {}
return {
name: 'customExtension',
onCreate,
onUpdate,
// 在这里编写您的代码。
}
})此扩展监听编辑器的 update 事件,并将编辑器当前的 JSON 表示日志输出到控制台。
它这样安装到编辑器中:
import { Editor } from '@tiptap/core'
const editor = new Editor({
extensions: [CustomExtension],
})
// 或者在使用 React 或 Vue 时
const editor = useEditor({
extensions: [CustomExtension],
})此扩展数组可以包含任意数量的扩展。它们将按列出的顺序安装,或按它们的 priority 属性排序。
现在我们已经看到扩展的基本结构,让我们深入了解您可以用来创建自己扩展的所有扩展选项。
扩展选项
在创建扩展时,您可以定义一组用户可配置的选项。这些选项可以用于自定义扩展的行为,或提供额外的功能。
name
扩展的名称。这用于在编辑器的扩展管理器中识别扩展。
const CustomExtension = Extension.create({
name: 'customExtension',
})如果扩展是节点或标记,则名称用于在编辑器的架构中识别节点或标记,并因此保存在编辑器内容的 JSON 表示中。有关更多信息,请参见 存储您的内容为 JSON。
priority
优先级定义扩展注册的顺序。默认优先级为 100,大多数扩展都是这个值。具有更高优先级的扩展将更早加载。
import Link from '@tiptap/extension-link'
const CustomLink = Link.extend({
priority: 1000,
})扩展加载的顺序影响两个方面:
- 插件顺序(优先级更高的扩展的 ProseMirror 插件将先运行。)
- 架构顺序
例如,Link 标记具有更高的优先级,这意味着它将被渲染为 <a href="…"><strong>Example</strong></a> 而不是 <strong><a href="…">Example</a></strong>。
addOptions
addOptions 方法用于定义扩展的选项。此方法应返回一个对象,其中包含用户可配置的选项。
type CustomExtensionOptions = {
customOption: string
}
declare module '@tiptap/core' {
interface ExtensionOptions {
customOption: CustomExtensionOptions
}
}
const CustomExtension = Extension.create<CustomExtensionOptions>({
name: 'customExtension',
addOptions() {
return {
customOption: 'default value',
}
},
})这会暴露在安装扩展时可以设置的配置:
const editor = new Editor({
extensions: [CustomExtension.configure({ customOption: 'new value' })],
})addStorage
addStorage 方法用于定义扩展的存储(本质上是一个简单的状态管理器)。此方法应返回一个对象,其中包含扩展可以使用的存储。
type CustomExtensionStorage = {
customValue: string
}
declare module '@tiptap/core' {
interface ExtensionStorage {
customExtension: CustomExtensionStorage
}
}
const CustomExtension = Extension.create<any, CustomExtensionStorage>({
name: 'customExtension',
addStorage() {
return {
customValue: 'default value',
}
},
})这会暴露在扩展内部可以访问的存储:
const CustomExtension = Extension.create<any, CustomExtensionStorage>({
name: 'customExtension',
addStorage() {
return {
customValue: 'default value',
}
},
onUpdate() {
console.log(this.storage.customValue) // 'default value'
},
})或者通过编辑器:
const editor = new Editor({
extensions: [CustomExtension],
})
editor.storage.customExtension.customValue // 'default value'注意
editor.storage 由扩展的名称命名空间。
addCommands
addCommands 方法用于定义扩展的命令。此方法应返回一个对象,其中包含用户可以执行的命令。
declare module '@tiptap/core' {
interface Commands<ReturnType> {
customExtension: {
customCommand: () => ReturnType
}
}
}
const CustomExtension = Extension.create({
name: 'customExtension',
addCommands() {
return {
customCommand:
() =>
({ commands }) => {
return commands.setContent('Custom command executed')
},
}
},
})在 addCommands 内部使用 commands 参数
要在 addCommands 内部访问其他命令,请使用传递给它的 commands 参数。
这会暴露用户可以执行的命令:
const editor = new Editor({
extensions: [CustomExtension],
})
editor.commands.customCommand() // 'Custom command executed'
editor.chain().customCommand().run() // 'Custom command executed'addKeyboardShortcuts
addKeyboardShortcuts 方法用于定义键盘快捷键。此方法应返回一个对象,其中包含用户可以使用的键盘快捷键。
const CustomExtension = Extension.create({
name: 'customExtension',
addKeyboardShortcuts() {
return {
'Mod-k': () => {
console.log('Keyboard shortcut executed')
},
}
},
})这会暴露可供用户使用的键盘快捷键。
addInputRules
使用输入规则,您可以定义正则表达式以监听用户输入。它们用于 Markdown 快捷方式,例如将文本 (c) 转换为 ©(还有许多其他用途)与 Typography 扩展一起使用。对于标记,使用 markInputRule 辅助函数,对于节点,使用 nodeInputRule。
默认情况下,位于两侧的波浪号之间的文本会转换为 删除线文本。如果您认为在每边只需一个波浪号就足够了,可以像这样覆盖输入规则:
import { Extension } from '@tiptap/core'
import { markInputRule } from '@tiptap/core'
const CustomExtension = Extension.create({
name: 'customExtension',
addInputRules() {
return [
markInputRule({
find: /(?:~)((?:[^~]+))(?:~)$/,
type: this.editor.schema.marks.strike,
}),
]
},
})现在,当您输入 ~striked text~ 时,它将被转换为 删除线文本。
想了解更多关于输入规则的信息?请查看 输入规则 文档。
addPasteRules
粘贴规则的工作方式与输入规则(见上文)相似。但不同之处在于,它们应用于粘贴的内容,而不是监听用户输入。
正则表达式有一个微小的区别。输入规则通常以 $ 符号结束(这意味着“在行尾断言位置”),而粘贴规则通常浏览所有内容,不带该 $ 符号。
将上述示例应用于粘贴规则将如下所示:
import { Extension } from '@tiptap/core'
import { markPasteRule } from '@tiptap/core'
const CustomExtension = Extension.create({
name: 'customExtension',
addPasteRules() {
return [
markPasteRule({
find: /(?:~)((?:[^~]+))(?:~)/g,
type: this.editor.schema.marks.strike,
}),
]
},
})想了解更多关于粘贴规则的信息?请查看 粘贴规则 文档。
事件
您甚至可以将您的 事件监听器 移动到一个单独的扩展。以下是一个涉及所有事件的示例:
import { Extension } from '@tiptap/core'
const CustomExtension = Extension.create({
onBeforeCreate() {
// 编辑器即将被创建。
},
onCreate() {
// 编辑器已准备就绪。
},
onUpdate() {
// 内容已更改。
},
onSelectionUpdate({ editor }) {
// 选择已更改。
},
onTransaction({ transaction }) {
// 编辑器状态已更改。
},
onFocus({ event }) {
// 编辑器已获得焦点。
},
onBlur({ event }) {
// 编辑器不再获得焦点。
},
onDestroy() {
// 编辑器正在被销毁。
},
})dispatchTransaction
该钩子允许您在事务分发之前拦截和修改事务。它采用类似 Koa 或 Express 的中间件模式,每个扩展都会接收到 transaction 和 next 函数。
工作原理
- 中间件链:当事务被分发时,Tiptap 会创建一个包含所有定义了
dispatchTransaction钩子的扩展的链。 - 按优先级排序:该链按扩展优先级排序。具有更高优先级(如
1000)的扩展会更早调用,并且包装优先级较低的扩展。 - 传递调用:您必须调用
next(transaction)才能将事务传递给链中的下一个扩展。最终,最后的next()调用会将事务传递给编辑器的基础分发函数(或者如果定义了自定义的editorProps.dispatchTransaction,则调用自定义函数)。 - 阻止事务:如果您不调用
next(transaction),事务将被阻止,不会传递给编辑器。
import { Extension } from '@tiptap/core'
const LoggingExtension = Extension.create({
name: 'loggingExtension',
priority: 1000, // 提前运行
dispatchTransaction({ transaction, next }) {
console.log('在其他扩展之前拦截事务...')
// 您可以在此修改事务
// transaction.setMeta('customMeta', true)
// 调用 next 将其传递给下一个扩展
next(transaction)
console.log('事务已由后续链处理。')
},
})中间件生命周期
因为每个扩展都会“包裹”下一个扩展,您可以在事务被后续链和编辑器处理之前和之后执行代码。
dispatchTransaction({ transaction, next }) {
// 1. 预处理:在调用 next() 之前执行的代码
const start = performance.now()
next(transaction)
// 2. 后处理:在调用 next() 之后执行的代码
const end = performance.now()
console.log(`分发耗时 ${end - start} 毫秒`)
}addProseMirrorPlugins
您可以向扩展添加 ProseMirror 插件。如果您想使用 ProseMirror 插件扩展编辑器,这非常有用。
使用现有 ProseMirror 插件
您可以将现有的 ProseMirror 插件包装在 Tiptap 扩展中,如下面的示例所示。
import { history } from '@tiptap/pm/history'
const History = Extension.create({
addProseMirrorPlugins() {
return [
history(),
// …
]
},
})创建 ProseMirror 插件
您也可以创建自定义的 ProseMirror 插件。以下是一个将消息记录到控制台的自定义 ProseMirror 插件示例。
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Extension } from '@tiptap/core'
const CustomExtension = Extension.create({
name: 'customExtension',
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('customPlugin'),
view() {
return {
update() {
console.log('自定义插件已更新')
},
}
},
}),
]
},
})要了解有关 ProseMirror 插件的更多信息,请查阅 ProseMirror 文档。
addGlobalAttributes
addGlobalAttributes 方法用于为多个扩展同时添加属性。这对于诸如文本对齐、行高或其他应在许多节点和标记类型上可用的样式相关属性非常有用。
您可以通过 types 属性指定哪些扩展将接收该属性:
types: ['heading', 'paragraph']- 应用于特定类型名称types: '*'- 应用于所有节点类型(不包括内置的文本节点)和所有标记类型types: 'nodes'- 应用于所有节点类型(不包括内置的文本节点)types: 'marks'- 应用于所有标记类型
以下示例演示了如何将属性应用于特定类型:
const TextAlign = Extension.create({
name: 'textAlign',
addGlobalAttributes() {
return [
{
types: ['heading', 'paragraph'],
attributes: {
textAlign: {
default: 'left',
renderHTML: (attributes) => ({
style: `text-align: ${attributes.textAlign}`,
}),
parseHTML: (element) => element.style.textAlign || 'left',
},
},
},
]
},
})更多选项(包括字符串简写语法)请参阅应用全局属性 指南。
addExtensions
您可以向扩展添加更多扩展。如果您想创建一组属于同一类别的扩展,这非常有用。
import { Extension } from '@tiptap/core'
import CustomExtension1 from './CustomExtension1'
const CustomExtension = Extension.create({
name: 'customExtension',
addExtensions() {
return [
CustomExtension1.configure({
name: 'customExtension1',
}),
]
},
})extendNodeSchema
您可以使用 extendNodeSchema 方法扩展编辑器的 NodeConfig。如果您想向节点架构添加额外属性,这将非常有用。
import { Extension } from '@tiptap/core'
declare module '@tiptap/core' {
// 这将向 NodeConfig 添加一个新的配置选项
interface NodeConfig {
customAttribute: {
default: null
}
}
}
const CustomExtension = Extension.create({
name: 'customExtension',
extendNodeSchema() {
return {
customAttribute: {
default: null,
},
}
},
})extendMarkSchema
您可以使用 extendMarkSchema 方法扩展编辑器的 MarkConfig。如果您想向标记架构添加额外属性,这将非常有用。
import { Extension } from '@tiptap/core'
declare module '@tiptap/core' {
// 这将向 MarkConfig 添加一个新的配置选项
interface MarkConfig {
customAttribute: {
default: null
}
}
}
const CustomExtension = Extension.create({
name: 'customExtension',
extendMarkSchema() {
return {
customAttribute: {
default: null,
},
}
},
})这有什么可用的?
这些扩展不是类,但您在扩展中的 this 处仍然可以访问一些重要内容。
// 扩展的名称,例如 'bulletList'
this.name
// 编辑器实例
this.editor
// ProseMirror 类型(如果是节点或标记)
this.type
// 包含所有设置的对象
this.options
// 所有扩展的内容
this.parent
// 存储对象
this.storage