---
title: "添加到现有扩展"
description: "在 Tiptap 中扩展已存在的扩展，以添加新功能和特性。文档中有更多信息！"
canonical_url: "https://tiptap.zhcndoc.com/editor/extensions/custom-extensions/extend-existing"
---

# 添加到现有扩展

在 Tiptap 中扩展已存在的扩展，以添加新功能和特性。文档中有更多信息！

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

假设您想更改项目符号列表的键盘快捷键。您应该首先查看扩展的源代码，在本例中是 [`BulletList` 节点](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-list/src/bullet-list/bullet-list.ts)。对于覆盖键盘快捷键的示例，您的代码可以如下所示：

```js
// 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 存储内容](https://tiptap.zhcndoc.com/guides/output-json-html.md#option-1-json)，您也需要在这里更改名称。

## 设置

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

```js
import Heading from '@tiptap/extension-heading'

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

## 存储

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

```js
import { Extension } from '@tiptap/core'

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

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

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

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

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

const awesomeness = editor.storage.customExtension.awesomeness
```

## 架构

Tiptap 使用严格的架构，配置内容可以如何结构化、嵌套、行为及更多。您可以 [更改现有扩展的所有架构方面](https://tiptap.zhcndoc.com/editor/core-concepts/schema.md)。让我们通过一些常见用例进行介绍。

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

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

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

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

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

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

这只是两个小示例，但 [底层的 ProseMirror 架构](https://prosemirror.net/docs/ref/#model.SchemaSpec) 实际上是非常强大的。

## 属性

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

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

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

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

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

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

```js
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`），以下是这样做的方式：

```js
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?.()`

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

## 全局属性

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

仔细查看 [`TextAlign`](https://tiptap.zhcndoc.com/editor/extensions/functionality/textalign.md) 扩展的 [完整源代码](https://github.com/ueberdosis/tiptap/tree/main/packages/extension-text-align)，以查看更复杂的示例。但这里是其工作的简单介绍：

```js
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',
          },
        },
      },
    ]
  },
})
```

### 将全局属性应用到所有扩展

除了应用属性到特定类型，您还可以为 `types` 属性使用简写字符串选项以更广泛地应用属性。这些提供了一个明确且自解释的 API。

#### 应用到所有节点和标记

使用 `types: '*'` 将属性应用于所有节点类型（不包含内置文本节点）和所有标记类型：

```js
import { Extension } from '@tiptap/core'

const UniversalAttribute = Extension.create({
  addGlobalAttributes() {
    return [
      {
        types: '*',
        attributes: {
          customAttribute: {
            default: 'default-value',
            renderHTML: (attributes) => ({
              'data-custom': attributes.customAttribute,
            }),
            parseHTML: (element) => element.getAttribute('data-custom') || 'default-value',
          },
        },
      },
    ]
  },
})
```

#### 应用到所有节点

使用 `types: 'nodes'` 将属性应用于所有节点类型（不包括内置文本节点）：

```js
const NodeAttribute = Extension.create({
  addGlobalAttributes() {
    return [
      {
        types: 'nodes',
        attributes: {
          nodeClass: {
            default: null,
            renderHTML: (attributes) => ({
              class: attributes.nodeClass,
            }),
            parseHTML: (element) => element.className || null,
          },
        },
      },
    ]
  },
})
```

#### 应用到所有标记

使用 `types: 'marks'` 将属性应用于所有标记类型：

```js
const MarkAttribute = Extension.create({
  addGlobalAttributes() {
    return [
      {
        types: 'marks',
        attributes: {
          emphasis: {
            default: false,
            renderHTML: (attributes) => {
              if (!attributes.emphasis) return {}
              return { style: 'font-style: italic' }
            },
            parseHTML: (element) => element.style.fontStyle === 'italic',
          },
        },
      },
    ]
  },
})
```

#### 应用到特定类型

如果想有更多控制，您仍然可以指定具体的类型名称数组：

```js
const TextAlignAttribute = Extension.create({
  addGlobalAttributes() {
    return [
      {
        types: ['heading', 'paragraph'],
        attributes: {
          textAlign: {
            default: 'left',
            renderHTML: (attributes) => ({
              style: `text-align: ${attributes.textAlign}`,
            }),
            parseHTML: (element) => element.style.textAlign || 'left',
          },
        },
      },
    ]
  },
})
```

> **提示:**
>
> 字符串简写选项（`'*'`、`'nodes'`、`'marks'`）比省略 `types` 属性提供了更明确且自解释的 API。当您想让其他开发者立即清楚全局属性的作用范围时，请使用它们。

## 渲染 HTML

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

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

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

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

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

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

```js
import { mergeAttributes } from '@tiptap/core'

// ...

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

## 解析 HTML

`parseHTML()` 函数尝试从 HTML 加载编辑器文档。该函数将 HTML DOM 元素作为参数传递，并且预计返回一个包含属性及其值的对象。这是来自 [`Bold`](https://tiptap.zhcndoc.com/editor/extensions/marks/bold.md) 标记的简化示例：

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

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

```js
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 期望在检查成功时返回 null 或 undefined。](https://prosemirror.net/docs/ref/version/0.18.0.html#model.ParseRule.getAttrs)

[将 `priority` 传递给规则](https://prosemirror.net/docs/ref/version/0.18.0.html#model.ParseRule.priority) 以解决与其他扩展的冲突，例如，如果您构建了一个自定义扩展，该扩展搜索具有类属性的段落，但您已经使用了默认段落扩展。

### 使用 getAttrs

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

1. 检查 HTML 属性以确定规则是否匹配（以及是否从该 HTML 创建标记或节点）。当函数返回 `false` 时，它不匹配。
2. 获取 DOM 元素并使用 HTML 属性相应地设置您的标记或节点属性：

```js
parseHTML() {
  return [
    {
      tag: 'span',
      getAttrs: element => {
        // 检查元素是否具有属性
        element.hasAttribute('style')
        // 获取内联样式
        element.style.color
        // 获取特定属性
        element.getAttribute('data-color')
      },
    },
  ]
},
```

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

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

在 [ProseMirror 参考](https://prosemirror.net/docs/ref/#model.ParseRule) 中了解有关 `getAttrs` 和所有其他 `ParseRule` 属性的更多信息。
