【element-tiptap】Tiptap编辑器核心概念----内容、扩展与词汇

news/2024/11/25 0:20:16/

前言:本篇文章继续来讲Tiptap编辑器的核心概念,主要是内容、扩展、词汇相关的概念

(一)内容

文档内容被存储在编辑器实例的 state 属性中。所有的修改都会以事务 transaction 的形式应用于 state。state 详细介绍了当前的内容、光标的位置和选区等内容。Tiptap 提供了很多可以挂载的事件,例如可以用于在应用事务之前改变事务。

可挂载的事件列表
事件名描述
beforeCreate编辑器视图创建之前
create编辑器初始化完成
update内容有修改
selectionUpdate编辑器的选区有修改
transaction创建和执行事务
focus监听编辑器聚焦
blur监听编辑器失焦
destroy监听编辑器实例销毁
onPaste监听粘贴事件
onDrop监听内容拖拽到编辑器
contentError内容不符合 schema 制定的规则时触发
注册事件监听器

有三个方式注册事件监听器
① 通过配置项的方式
新创建的编辑器可以使用配置项的方式增加监听函数

const editor = new Editor({onBeforeCreate({ editor }) {// Before the view is created.},onCreate({ editor }) {// The editor is ready.},
})

② 通过绑定的方式
正在运行的编辑器可以通过 on() 方法监听

editor.on('beforeCreate', ({ editor }) => {// Before the view is created.
})editor.on('create', ({ editor }) => {// The editor is ready.
})editor.on('update', ({ editor }) => {// The content has changed.
})

如果后续要解绑的话,需要使用命名函数

const onUpdate = () => {// The content has changed.
}// Bind …
editor.on('update', onUpdate)// … and unbind.
editor.off('update', onUpdate)

③ 给扩展增加监听器

import { Extension } from '@tiptap/core'const CustomExtension = Extension.create({onBeforeCreate({ editor }) {// Before the view is created.},onCreate({ editor }) {// The editor is ready.},
})

(二)扩展

扩展向编辑器中添加节点、标记、功能等。
扩展里面的内容有一丢丢多哇,等我后面专门写几篇文章介绍吧👻👻👻
这里先介绍一下创建扩展的方法

1、扩展现有的 extension

每一个 extension 都有一个 extends 方法,这个方法接收一个配置对象,可以向其中设置你想修改或者新增的功能。
下面的例子,重写了切换列表的快捷键

// 1. Import the extension
import BulletList from '@tiptap/extension-bullet-list'// 2. Overwrite the keyboard shortcuts
const CustomBulletList = BulletList.extend({addKeyboardShortcuts() {return {'Mod-l': () => this.editor.commands.toggleBulletList(),}},
})// 3. Add the custom extension to your editor
new Editor({extensions: [CustomBulletList(),// …],
})

用这种方法,你可以修改现有的扩展的除了 name 以外的所有属性。下面我们来挨个看看扩展都有哪些属性

Name

扩展的名字是它的唯一标识符,一般不会修改它。在文档数据源JSON中也会存储扩展的名字。如果想修改它只能创建一个新的扩展。

Priority

这个属性定义扩展被注册的顺序。默认的 priority 是 100,大部分扩展都使用的是默认值。如果设置的大一些的话,可以早一些加载扩展。

import Link from '@tiptap/extension-link'const CustomLink = Link.extend({priority: 1000,
})

扩展的加载顺序影响两个事情:

  • 插件的顺序
    扩展的ProseMirror插件会优先运行
  • Schema 顺序
    在上面的例子中,提升了 Link 的顺序,那么渲染的时候,Link 标记就会先渲染,意味着一个链接之前可能是 <strong><a href="…">Example</a></strong>,但是提升优先级之后Link的层级也会提升,会变成 <strong><a href="…">Example</a></strong>
Settings

所有设置都可以通过扩展来配置,但是如果你想要修改默认设置,比如为其他开发者提供一个基于 Tiptap 的库,你可以这样做:

import Heading from '@tiptap/extension-heading'const CustomHeading = Heading.extend({addOptions() {return {...this.parent?.(),levels: [1, 2, 3],}},
})
Storage

在某些情况下你可能想在 extension 实例中存储一些可变数据,此时就可以使用 storage

import { Extension } from '@tiptap/core'const CustomExtension = Extension.create({name: 'customExtension',addStorage() {return {awesomeness: 100,}},onUpdate() {this.storage.awesomeness += 1},
})

在扩展之外访问扩展中定义的 storage 的话,使用 editor.storage 访问,需要确保每一个扩展都有独一无二的 name。

const editor = new Editor({extensions: [CustomExtension],
})const awesomeness = editor.storage.customExtension.awesomeness
Schema

Tiptap 需要定义非常严格的数据模式,来指定文档的结构和节点之间的嵌套方式。你可以通过下面的几个属性自定义extension 的数据模式。

// 1. 解析自定义格式
const CustomFormat = Mark.create({name: 'customFormat',parseDOM: [{// 样式匹配style: 'color',getAttrs: (value) => ({color: value})},{// 类名匹配tag: 'span.custom',getAttrs: (dom) => ({color: dom.style.color})}]
})// 2. 解析外部粘贴的内容
const ExternalContent = Node.create({name: 'external',parseDOM: [{tag: '[data-source="external"]',getAttrs: (dom) => ({source: dom.getAttribute('data-source'),id: dom.getAttribute('data-id')})}]
})
  • toDebugString⁠ fn(node: Node) → string 定义该节点在调试的时候显示的信息
  • leafText⁠?: fn(node: Node) → string 定义将此类型的叶节点序列化为字符串的默认方式(如Node.textBetween和Node.textContent所使用的)
  • linebreakReplacement 布尔值 表示该节点是否能起到换行的作用,但不使用换行符
    实际应用:
// 1. 在富文本和纯文本间转换
const convertToPlainText = () => {// <br> 节点会被转换为 \neditor.commands.setBlockType('plain')
}// 2. 在预格式化和普通文本间转换
const togglePreformatted = () => {// 自动处理换行符的转换editor.commands.toggleBlockType('preformatted')
}
Attributes

Attributes 可以用来存储内容的附加信息。例如下面,扩展段落增加不同的颜色,渲染段落的时候就会自动加上 color 属性

const CustomParagraph = Paragraph.extend({addAttributes() {// Return an object with attribute configurationreturn {color: {default: 'pink',},},},
})// Result:
// <p color="pink">Example Text</p>

默认情况下,所有的属性都会在初始化节点的时候解析并且渲染成 HTML 属性。
不过要想给文字设置颜色,需要使用style属性,像下面的写法:

const CustomParagraph = Paragraph.extend({addAttributes() {return {color: {default: null,// Take the attribute valuesrenderHTML: (attributes) => {// … and return an object with HTML attributes.return {style: `color: ${attributes.color}`,}},},}},
})// Result:
// <p style="color: pink">Example Text</p>

你也可以控制如何从HTML中转换成数据。例如下面的例子,如果你想将 color 的属性存储成 data-color,可以使用 psrseHTML 自定义转换规则

const CustomParagraph = Paragraph.extend({addAttributes() {return {color: {default: null,// Customize the HTML parsing (for example, to load the initial content)parseHTML: (element) => element.getAttribute('data-color'),// … and customize the HTML rendering.renderHTML: (attributes) => {return {'data-color': attributes.color,style: `color: ${attributes.color}`,}},},}},
})// Result:
// <p data-color="pink" style="color: pink">Example Text</p>

可以使用 rendered: false 完全禁用属性的渲染
如果你想保持现有的属性,可以通过 this.parent() 继承

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

Attributes 还能一次性给多个扩展设置。例如文本对齐方式、行高、文字样式、或者其他的样式相关的属性就很适合一次性设置。例如 TextAlign 扩展

import { Extension } from '@tiptap/core'const TextAlign = Extension.create({addGlobalAttributes() {return [{// Extend the following extensionstypes: ['heading', 'paragraph'],// … with those attributesattributes: {textAlign: {default: 'left',renderHTML: (attributes) => ({style: `text-align: ${attributes.textAlign}`,}),parseHTML: (element) => element.style.textAlign || 'left',},},},]},
})
Render HTML

renderHTML 方法可以用来控制扩展如何转换为 HTML。它接收一个属性对象作为参数,其中包含所有自持有的属性、全局的属性以及配置的CSS类。例如下面的 Bold 扩展:

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

返回的数组中的第一个元素是HTML的标签。如果第二个参数是一个对象,它就是属性的集合;如果是一个嵌套的数组,那么就是子元素。最后的数字表示元素要插入的位置。
下面是放子元素的示例:

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

如果还想在这里添加具体的属性,可以使用 mergeAttributes

import { mergeAttributes } from '@tiptap/core'// ...renderHTML({ HTMLAttributes }) {return ['a', mergeAttributes(HTMLAttributes, { rel: this.options.rel }), 0]
},
Parse HTML

parseHTML() 方法定义从 HTML 转换为编辑器文档的方式。这个方法可以获取 HTML DOM 元素,返回一个包含属性、标签等信息的对象。
下面是一个简单的 Bold 标记的例子:

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

我们定义了一个规则,将所有的 strong 标签转为 Bold 标记。下面是一个更复杂的转换规则,将所有的 strong 标签和 b 标签,以及行内设置 font-weight 为bold或者700的标签都识别成 Bold 标记。getAttrs 方法用来匹配更复杂的规则,如果检查成功需要返回 null,所以这个方法的最后是 && null

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

这个属性是用于向后端发送数据的时候,或者是在控制台打印 editor.editor.getHTML() 的时候,当前节点怎么展现。因为比如说像 latex 公式,在网页中它需要很复杂的结构来展示成 MathML,但是存储文档的话存一个 <latex>2^1=2</latex> 类似的latex公式就可以了。

Commands

给扩展增加命令,

import Paragraph from '@tiptap/extension-paragraph'const CustomParagraph = Paragraph.extend({addCommands() {return {paragraph:() =>({ commands }) => {return commands.setNode('paragraph')},}},
})

增加后就可以通过editor.commands.paragraph(); 访问

Keyboard shortcuts

大多数核心的扩展都带有默认的快捷键,你可以使用 addKeyboardShortcuts() 方法对其进行修改

// Change the bullet list keyboard shortcut
import BulletList from '@tiptap/extension-bullet-list'const CustomBulletList = BulletList.extend({addKeyboardShortcuts() {return {'Mod-l': () => this.editor.commands.toggleBulletList(),}},
})
Input rules

输入规则是用定义用正则表达式监听用户输入的规则,常用于匹配 markdown 输入法。
例如下面的例子,输入 ~文字~ 的时候,会转换成 “ 文字

// Use the ~single tilde~ markdown shortcut
import Strike from '@tiptap/extension-strike'
import { markInputRule } from '@tiptap/core'// Default:
// const inputRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))$/// New:
const inputRegex = /(?:^|\s)((?:~)((?:[^~]+))(?:~))$/const CustomStrike = Strike.extend({addInputRules() {return [markInputRule({find: inputRegex,type: this.type,}),]},
})
Paste rules

粘贴规则类似于上面的输入规则,监听用户的粘贴的内容,如果有匹配上的字符串就进行转换。
不过在写正则表达式的时候有些不一样,输入规则通常要以指定的符号为开头和结尾,分别使用 ^$ 表示。但是粘贴只需要找字符串中成对出现的所有符号,不用考虑是否在一定要以某符号开头和结尾,例如 文本~~删除线1~~文本~~删除线2~~ 这种形式也可以转换成 “文本删除线1文本删除线2”,因此正则表达式会更灵活

// Check pasted content for the ~single tilde~ markdown syntax
import Strike from '@tiptap/extension-strike'
import { markPasteRule } from '@tiptap/core'// Default:
// const pasteRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))/g// New:
const pasteRegex = /(~~([^~]+)~~)/g;const CustomStrike = Strike.extend({addPasteRules() {return [markPasteRule({find: pasteRegex,type: this.type,}),]},
})
Events

编辑器的生命周期函数以及监听器可以放在扩展中

import { Extension } from '@tiptap/core'const CustomExtension = Extension.create({onCreate() {// The editor is ready.},onUpdate() {// The content has changed.},onSelectionUpdate({ editor }) {// The selection has changed.},onTransaction({ transaction }) {// The editor state has changed.},onFocus({ event }) {// The editor is focused.},onBlur({ event }) {// The editor isn’t focused anymore.},onDestroy() {// The editor is being destroyed.},
})
可以通过this访问的属性

在扩展中,有几个属性可以通过 this 来访问

// extension 的名字,例如 'bulletList'
this.name// Editor 实例
this.editor// ProseMirror 类型
this.type// 配置项
this.options// 被继承的 extension 的所有信息
this.parent
ProseMirror Plugins

Tiptap 是在 ProseMirror 的基础上开发的,ProseMirror提供了强大的插件 API。使用 addProseMirrorPlugins() 向扩展中添加插件。

  • 添加现成的插件
import { history } from '@tiptap/pm/history'const History = Extension.create({addProseMirrorPlugins() {return [history(),// …]},
})
  • 使用插件 API 创建新的插件,例如下面代码创建一个事件处理的插件
import { Extension } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'export const EventHandler = Extension.create({name: 'eventHandler',addProseMirrorPlugins() {return [new Plugin({key: new PluginKey('eventHandler'),props: {handleClick(view, pos, event) {/* … */},handleDoubleClick(view, pos, event) {/* … */},handlePaste(view, event, slice) {/* … */},// … and many, many more.// Here is the full list: https://prosemirror.net/docs/ref/#view.EditorProps},}),]},
})
Node views

在某些情况下,你需要动态运行 JavaScript 来创建节点,例如给图片渲染一个外框框,此时就需要使用 addNodeView 方法。这个方法需要返回父节点和当前节点

import Image from '@tiptap/extension-image'const CustomImage = Image.extend({addNodeView() {return () => {const container = document.createElement('div')container.addEventListener('click', (event) => {alert('clicked on the container')})const content = document.createElement('div')container.append(content)return {dom: container,contentDOM: content,}}},
})

(三)词汇

下面是 ProseMirror 中常见的词汇的描述

词汇描述
Schema配置内容可以具有的结构
Document编辑器中实际的内容
State描述编辑器文档内容和选区的所有的东西
Transactionstate的修改
Extension注册新功能
Node内容的类型,例如段落、标题
Mark可以应用于节点,例如用于内联格式设置
Command编辑器中执行一个动作,以某种方式改变state
Decoration在文档顶部设置样式,例如突出显示错误

http://www.ppmy.cn/news/1549670.html

相关文章

java编程开发基础,正则表达式的使用案例Demo

java编程开发基础,正则表达式的使用案例Demo!实际开发中&#xff0c;经常遇到一些字符串&#xff0c;信息的裁剪和提取操作&#xff0c;正则表达式是经常使用的&#xff0c;下面的案例&#xff0c;可以帮助大家快速的了解和熟悉&#xff0c;正则表达式的使用技巧。 package com…

小红书/小绿书笔记保存攻略:图片去水印保存,文案一键复制

家人们&#xff0c;原来这么简单啊 上教程&#xff0c; 这样保存的图片无水印&#xff0c; 这样轻松可以复制文案&#xff0c; 完全免费呀 #免费去水印 #干货分享 #视频去水印 #图片去水印 #文案复制 #文案保存 #保存笔记 #视频下载 #小红书笔记保存 #去水印教程 #新媒体运营工…

使用脚本判断网络连接状态,并且添加对应路由

这个脚本通过不断检测有线网络和4G网络的连通性来动态调整默认路由。如果两个网络都可用&#xff0c;则优先使用4G网络。如果只有一个网络可用&#xff0c;则使用该网络。如果两个网络都不可用&#xff0c;则每秒钟检测一次&#xff0c;连续30次检测失败后重启设备。 #!/bin/b…

Cargo Rust 的包管理器

Cargo->Rust 的包管理器 Cargi简介Cargo 的主要功能1. 创建项目2. 管理依赖3. 构建项目4. 运行项目5. 测试代码6. 检查代码7. 生成文档8. 发布和分享包 Cargo 的核心文件1. Cargo.toml2. Cargo.lock **Cargo 的生态系统** 常用命令总结Hello, Cargo! 示例 Cargi简介 Cargo …

计算机网络(14)ip地址超详解

先看图&#xff1a; 注意看第三列蓝色标注的点不会改变&#xff0c;A类地址第一个比特只会是0&#xff0c;B类是10&#xff0c;C类是110&#xff0c;D类是1110&#xff0c;E类是1111. IPv4地址根据其用途和网络规模的不同&#xff0c;分为五个主要类别&#xff08;A、B、C、D、…

R语言 | 宽数据变成一列,保留对应的行名和列名

对应稀疏矩阵 转为 宽数据框&#xff0c;见 数据格式转换 | 稀疏矩阵3列还原为原始矩阵/数据框&#xff0c;自定义函数 df3toMatrix() 目的&#xff1a;比如查看鸢尾花整体的指标分布&#xff0c;4个指标分开&#xff0c;画到一个图中。每个品种画一个图。 1.数据整理&#…

vscode remote-ssh直连docker容器

1、想要通过本地vscode远程开发&#xff0c;需要下载vscode的remote-ssh插件 2、docker创建 查看了本地有的镜像&#xff0c;使用ubuntu:20.04版本 docker run -it --name janice1119 -v /home/janice:/var/janice ubuntu:20.04 -p3333:22 /bin/bash docker run -it会在创…

蓝桥杯疑似例题解答方案(打印任意阶杨辉三角)

题目&#xff1a;输入n&#xff0c;打印n阶的杨辉三角 杨辉三角是一种特殊的由数字构成的三角形&#xff0c;边缘上的数字都是1&#xff0c;内部的数字则是左上角和右上角数字的加和。它本质上其实是二项展开的系数序列&#xff08;我们通过这个性质可以给出一种与本篇文章的方…