添加到现有扩展
每个扩展都有一个 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 期望在检查成功时返回 null 或 undefined
将 priority 传递给规则 以解决与其他扩展的冲突,例如,如果您构建了一个自定义扩展,该扩展搜索具有类属性的段落,但您已经使用了默认段落扩展。
使用 getAttrs
在示例中您可能注意到的 getAttrs 函数有两个目的:
- 检查 HTML 属性以确定规则是否匹配(以及是否从该 HTML 创建标记或节点)。当函数返回
false时,它不匹配。 - 获取 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 属性的更多信息。