探索 Tiptap V3 的最新功能

扩展 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,
})

扩展加载的顺序影响两个方面:

  1. 插件顺序(优先级更高的扩展的 ProseMirror 插件将先运行。)
  2. 架构顺序

例如,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() {
    // 编辑器正在被销毁。
  },
})

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('Custom plugin updated')
            },
          }
        },
      }),
    ]
  },
})

要了解有关 ProseMirror 插件的更多信息,请查阅 ProseMirror 文档

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