探索 Tiptap V3 的最新功能

添加到现有扩展

每个扩展都有一个 extend() 方法,该方法接受一个对象,包含您希望更改或添加的所有内容。

假设您想更改项目符号列表的键盘快捷键。您应该首先查看扩展的源代码,在本例中是 BulletList 节点。对于覆盖键盘快捷键的示例,您的代码可以如下所示:

// 1. 导入扩展
import BulletList from '@tiptap/extension-bullet-list'

// 2. 重写键盘快捷键
const CustomBulletList = BulletList.extend({
  addKeyboardShortcuts() {
    return {
      'Mod-l': () => this.editor.commands.toggleBulletList(),
    }
  },
})

// 3. 将自定义扩展添加到您的编辑器
new Editor({
  extensions: [
    CustomBulletList(),
    // …
  ],
})

对现有扩展的每个方面都适用相同的原则,除了名称。让我们看看您可以通过扩展方法更改的所有内容。我们在每个示例中重点关注一个方面,但您也可以结合所有这些示例,并在一个 extend() 调用中更改多个方面。

名称

扩展名称在很多地方被使用,改变它并不是很容易。如果您想更改现有扩展的名称,您可以复制整个扩展并在所有出现的地方更改名称。

扩展名称也是 JSON 的一部分。如果您 以 JSON 存储内容,您也需要在这里更改名称。

设置

所有设置都可以通过扩展进行配置,但是如果您想更改默认设置,例如为其他开发人员在 Tiptap 上提供一个库,您可以这样做:

import Heading from '@tiptap/extension-heading'

const CustomHeading = Heading.extend({
  addOptions() {
    return {
      ...this.parent?.(),
      levels: [1, 2, 3],
    }
  },
})

存储

在某些时候,您可能想在扩展实例中保存一些数据。该数据是可变的。您可以在扩展中通过 this.storage 访问它。

import { Extension } from '@tiptap/core'

const CustomExtension = Extension.create({
  name: 'customExtension',

  addStorage() {
    return {
      awesomeness: 100,
    }
  },

  onUpdate() {
    this.storage.awesomeness += 1
  },
})

在扩展外部,您可以通过 editor.storage 访问。确保每个扩展都有一个唯一的名称。

const editor = new Editor({
  extensions: [CustomExtension],
})

const awesomeness = editor.storage.customExtension.awesomeness

架构

Tiptap 使用严格的架构,配置内容可以如何结构化、嵌套、行为及更多。您可以 更改现有扩展的所有架构方面。让我们通过一些常见用例进行介绍。

默认的 Blockquote 扩展可以包装其他节点,如标题。如果您只想允许段落在您的引用块中,请相应地设置 content 属性:

// 引用块只能包含段落
import Blockquote from '@tiptap/extension-blockquote'

const CustomBlockquote = Blockquote.extend({
  content: 'paragraph*',
})

架构甚至允许您使节点可拖动,这就是 draggable 选项的用途。它默认为 false,但您可以覆盖它。

// 可拖动的段落
import Paragraph from '@tiptap/extension-paragraph'

const CustomParagraph = Paragraph.extend({
  draggable: true,
})

这只是两个小示例,但 底层的 ProseMirror 架构 实际上是非常强大的。

属性

您可以使用属性在内容中存储附加信息。假设您想扩展默认的 Paragraph 节点以具有不同的颜色:

const CustomParagraph = Paragraph.extend({
  addAttributes() {
    // 返回一个包含属性配置的对象
    return {
      color: {
        default: 'pink',
      },
    },
  },
})

// 结果:
// <p color="pink">示例文本</p>

仅此就足以让 Tiptap 知道新的属性,并将 'pink' 设置为默认值。所有属性默认将呈现为 HTML 属性,并在初始化时从内容中解析。

让我们继续使用颜色示例,假设您想添加一个内联样式以实际改变文本的颜色。使用 renderHTML 函数,您可以返回将在输出中呈现的 HTML 属性。

此示例根据 color 的值添加一个样式 HTML 属性:

const CustomParagraph = Paragraph.extend({
  addAttributes() {
    return {
      color: {
        default: null,
        // 获取属性值
        renderHTML: (attributes) => {
          // …并返回一个包含 HTML 属性的对象。
          return {
            style: `color: ${attributes.color}`,
          }
        },
      },
    }
  },
})

// 结果:
// <p style="color: pink">示例文本</p>

您还可以控制如何从 HTML 中解析属性。也许您想将颜色存储在名为 data-color 的属性中(而不仅仅是 color),以下是这样做的方式:

const CustomParagraph = Paragraph.extend({
  addAttributes() {
    return {
      color: {
        default: null,
        // 自定义 HTML 解析(例如,加载初始内容)
        parseHTML: (element) => element.getAttribute('data-color'),
        // …并自定义 HTML 呈现。
        renderHTML: (attributes) => {
          return {
            'data-color': attributes.color,
            style: `color: ${attributes.color}`,
          }
        },
      },
    }
  },
})

// 结果:
// <p data-color="pink" style="color: pink">示例文本</p>

您可以完全禁用属性的渲染,使用 rendered: false

扩展现有属性

如果您想向扩展添加一个属性并保留现有属性,您可以通过 this.parent() 访问它们。

在某些情况下,它是未定义的,因此请确保检查这种情况,或使用可选链 this.parent?.()

const CustomTableCell = TableCell.extend({
  addAttributes() {
    return {
      ...this.parent?.(),
      myCustomAttribute: {
        // …
      },
    }
  },
})

全局属性

属性可以同时应用于多个扩展。这对于文本对齐、行高、颜色、字体系列和其他样式相关属性非常有用。

仔细查看 TextAlign 扩展的 完整源代码,以查看更复杂的示例。但这里是其工作的简单介绍:

import { Extension } from '@tiptap/core'

const TextAlign = Extension.create({
  addGlobalAttributes() {
    return [
      {
        // 扩展以下扩展
        types: ['heading', 'paragraph'],
        // …添加这些属性
        attributes: {
          textAlign: {
            default: 'left',
            renderHTML: (attributes) => ({
              style: `text-align: ${attributes.textAlign}`,
            }),
            parseHTML: (element) => element.style.textAlign || 'left',
          },
        },
      },
    ]
  },
})

渲染 HTML

使用 renderHTML 函数,您可以控制扩展如何渲染为 HTML。我们向其传递一个包含所有本地属性、全局属性和配置的 CSS 类的属性对象。以下是来自 Bold 扩展的示例:

renderHTML({ HTMLAttributes }) {
  return ['strong', HTMLAttributes, 0]
},

数组中的第一个值应为 HTML 标签的名称。如果第二个元素是一个对象,则将其解释为一组属性。后面的任何元素都将作为子元素渲染。

数字零(表示空洞)用于指示内容应插入的位置。让我们看看 CodeBlock 扩展的渲染,其中包含两个嵌套标签:

renderHTML({ HTMLAttributes }) {
  return ['pre', ['code', HTMLAttributes, 0]]
},

如果您想在此处添加一些特定属性,请从 @tiptap/core 中导入 mergeAttributes 辅助函数:

import { mergeAttributes } from '@tiptap/core'

// ...

renderHTML({ HTMLAttributes }) {
  return ['a', mergeAttributes(HTMLAttributes, { rel: this.options.rel }), 0]
},

解析 HTML

parseHTML() 函数尝试从 HTML 加载编辑器文档。该函数将 HTML DOM 元素作为参数传递,并且预计返回一个包含属性及其值的对象。这是来自 Bold 标记的简化示例:

parseHTML() {
  return [
    {
      tag: 'strong',
    },
  ]
},

这定义了一条规则,将所有 <strong> 标签转换为 Bold 标记。但您可以更高级,这里是该扩展的完整示例:

parseHTML() {
  return [
    // <strong>
    {
      tag: 'strong',
    },
    // <b>
    {
      tag: 'b',
      getAttrs: node => node.style.fontWeight !== 'normal' && null,
    },
    // <span style="font-weight: bold"> 和 <span style="font-weight: 700">
    {
      style: 'font-weight',
      getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value as string) && null,
    },
  ]
},

这检查 <strong><b> 标签,以及任何具有设置为粗体的内联样式的 HTML 标签。

如您所见,您可以选择性地传递一个 getAttrs 回调,以添加更复杂的检查,例如针对特定的 HTML 属性。回调会传递 HTML DOM 节点,检查 style 属性时再传递值。

您可能会想知道 && null 是干什么的? ProseMirror 期望在检查成功时返回 nullundefined

priority 传递给规则 以解决与其他扩展的冲突,例如,如果您构建了一个自定义扩展,该扩展搜索具有类属性的段落,但您已经使用了默认段落扩展。

使用 getAttrs

在示例中您可能注意到的 getAttrs 函数有两个目的:

  1. 检查 HTML 属性以确定规则是否匹配(以及是否从该 HTML 创建标记或节点)。当函数返回 false 时,它不匹配。
  2. 获取 DOM 元素并使用 HTML 属性相应地设置您的标记或节点属性:
parseHTML() {
  return [
    {
      tag: 'span',
      getAttrs: element => {
        // 检查元素是否具有属性
        element.hasAttribute('style')
        // 获取内联样式
        element.style.color
        // 获取特定属性
        element.getAttribute('data-color')
      },
    },
  ]
},

您可以返回一个对象,属性作为键,并且解析值以设置您的标记或节点属性。我们建议将 parseHTML 使用在 addAttributes() 内,这样可以使代码更清晰。

addAttributes() {
  return {
    color: {
      // 根据 `data-color` 属性的值设置颜色属性
      parseHTML: element => element.getAttribute('data-color'),
    }
  }
},

ProseMirror 参考 中了解有关 getAttrs 和所有其他 ParseRule 属性的更多信息。