【element-tiptap】Tiptap编辑器核心概念----结构篇
core-concepts
前言:这篇文章来介绍一下 Tiptap 编辑器的一些核心概念
(一)结构
1、 Schemas
定义文档组成方式。一个文档就是标题、段落以及其他的节点组成的一棵树。
每一个 ProseMirror 的文档都有一个与之相关联的 schema,它规定了可以出现在文档中的节点的类型,以及它们的嵌套方式。例如,它可能规定,顶层节点可以包含一个或多个块(block),段落节点可以包括任意数量的拥有各种标记的行内节点。ProseMirror 给出了基本的 schema
,并且允许自定义 schema
。
一个简单的 schema
示例:
// 底层 ProseMirror schema
{nodes: {doc: {content: 'block+',},paragraph: {content: 'inline*',group: 'block',parseDOM: [{ tag: 'p' }],toDOM: () => ['p', 0],},text: {group: 'inline',},},
}
在上面的 schema
的定义中,我们定义了三个节点
doc
,它是文档的根节点,它的内容由content
属性定义,可以是一个或多个块级元素,block+
是正则表达式的写法,小加号表示一个或多个。paragraph
节点,它自身是块级元素,由group
属性指定,由于定义的节点只有它是块级元素,所以根节点的子节点只允许出现paragraph
节点。它的内容inline*
指的是0个或多个行内节点,所以它只能包含text
节点。parseDOM
定义了如何从粘贴的 HTML 中解析节点。toDOM
定义了paragraph
节点如何在 DOM 中渲染。text
节点,这个节点就很简单了,行内,纯文本
在 Tiptap 中,节点、标记和扩展都在各自的文件中独立定义,便于拆分逻辑,最终引擎会将所有的定义合并在一起。
schema 是严格的,文档中不能出现任何 schema 不允许的节点。
例如,如果你想编辑器中粘贴了 This is <strong>important</strong>
,但是没有扩展可以解析 strong
标签,那么文档中会直接显示 This is important
可以通过监听器可以监听解析失败的事件:
contentError
在这个事件中还会接收到一个参数 disableCollaboration
,它是一个函数,调用这个函数可以重新初始化编辑器,并且不会将出错的内容同步给其他用户。不过协同编辑的功能是收费的,大概率还是需要自己的团队重新开发。
这个方法的调用有以下两种:
- 直接在创建编辑器的时候设置监听器
new Editor({enableContentCheck: true,content: invalidContent,onContentError({ editor, error, disableCollaboration }) {// your handler here},...options,
})
- 通过
on()
方法监听
const editor = new Editor({enableContentCheck: true,content: invalidContent,...options,
})editor.on('contentError', ({ editor, error, disableCollaboration }) => {// your handler here
})
2、Marks
标记可以附加到每一个节点上,用来给节点的某些特殊的部分增加样式或者注释文本。
在 schema 中,必须指定可以允许的 marks 的类型,和上面的 schema 中指定 nodes 的写法类似。
默认情况下,带有行内内容的节点会允许所有的在 schema 中定义的 marks 应用于它们的子节点,但是可以在节点的 marks 属性中进行自定义。
例如下面这个简单的 schema,允许 strong 和 em 标记应用于 paragraphs 中的文本,但是不允许应用于 headings 中的文本:
const markSchema = new Schema({nodes: {doc: {content: "block+"},paragraph: {group: "block", content: "text*", marks: "_"},heading: {group: "block", content: "text*", marks: ""},text: {inline: true}},marks: {strong: {},em: {}}
})
标记集合会被解析成标记的所有名称以空格分隔的字符串的形式,_
会作为一个通配符,匹配所有的标记;而空字符串表示不允许任何的标记。
Tiptap 中提供了一系列 marks 扩展
3、commands
Commands
是以编程的方式改变编辑器内容。编辑器提供了很多很多的命令,以编程的方式添加、改变编辑器内容或者更改选区。
① 执行 command
通过编辑器实例上的 commands 属性,调用 command
editor.commands.setBold()
像酱紫执行命令就可以让文本加粗
② 链式执行 command
大多数的命令都可以合并到一次调用中,这会比单独调用函数更加高效。下面的例子是让选中的文本加粗
editor.chain().focus().toggleBold().run()
chain()
用来开启新的执行链,run()
方法用来实际执行所有的命令。这些命令很可能都是通过点击按钮触发的,但是按钮通常不在编辑器内部,所有可能会需要先执行 focus
聚焦于编辑器,然后再执行 toggleBold
方法加粗选中的文本。所以大部分命令在执行前都会先链式调用 focus()
,这样用户就可以继续进行编辑操作。
执行链上的方法是排着队执行的。一个执行链上的方法会被合并到一个 transaction 中,一个执行链只会触发一次更新监听器。
默认情况下,ProseMirror 是不支持链式操作的,我们需要通过 Transaction mapping 在命令执行链中更新位置。
下面是一个例子,链式执行删除和插入命令:
// 添加两个自定义命令演示两个 transaction 步骤之间的映射
addCommands() {return {delete: () => ({ tr }) => {const { $from, $to } = tr.selection// 使用 tr.mapping.map 在 transaction 步骤之间映射位置const from = tr.mapping.map($from.pos)const to = tr.mapping.map($to.pos)tr.delete(from, to)return true},insert: (content: string) => ({ tr }) => {const { $from } = tr.selection// 使用 tr.mapping.map 在 transaction 步骤之间映射位置const pos = tr.mapping.map($from.pos)tr.insertText(content, pos)return true},}
}
现在就可以执行下面的操作,确保插入内容时位置不会错误
editor.chain().delete().insert('foo').run()
③ 自定义命令中的链
当链接到一个命令的时候,事务会处于保留状态。如果你想链式调用自定义命令,你需要使用当前的事务,并且将你的自定义命令添加到当前的调用链上,如以下代码:
addCommands() {return {customCommand: attributes => ({ chain }) => {// Doesn’t work:// return editor.chain() …// Does work:return chain().insertContent('foo!').insertContent('bar!').run()},}
}
④ 行内命令
如果命令中执行的代码比较简单,可以直接写成行内命令的形式:
editor.chain().focus().command(({ tr }) => {// manipulate the transactiontr.insertText('hey, that’s cool!')return true}).run()
⑤ 空运行命令
在执行某些命令之前,可以使用 can()
方法,来判断这个命令能不能执行,例如在菜单中的按钮能不能显示等。这个方法不会执行任何修改而只是会判断后面跟的命令是否可以执行。
editor.can().toggleBold()
can()
方法也可以和 chain()
一起使用,来判断是否执行链上所有的方法都可以执行
editor.can().chain().toggleBold().toggleItalic().run()
如果链式操作中的所有命令都能执行,can() 方法才会返回 true。如果其中有自定义命令,切记要返回布尔值。
就是说上面一连串的方法,最后返回的是个布尔值,不会有任何的修改。
⑥ 尝试命令
如果有一连串的命令,运行一个命令成功后,就不再往后执行,就可以使用 first
命令。好像有 if~else~
的作用,或者是替代 can()
判断的作用。
例如下面的例子,backspace 键会首先去尝试撤销一个输入规则;如果成功的话就执行这个操作,如果失败的话就执行下一个命令,删除选区内容
editor.first(({ commands }) => [() => commands.undoInputRule(),() => commands.deleteSelection(),// …
])
下面的写法作用相同:
export default () =>({ commands }) => {return commands.first([() => commands.undoInputRule(),() => commands.deleteSelection(),// …])}
就是说如果当前焦点是一个列表,点击删除键,会列表输入规则删除,变成普通的文本输入
⑦ 关键命令列表
内容
命令 | 描述 |
---|---|
clearContent() | 删除整个文档 |
insertContent() | 在当前位置插入一个节点或者HTML字符串 |
insertContentAt() | 在指定位置插入一个节点或者HTML字符串 |
setContent() | 用新内容替代整篇文档的内容 |
节点&标记
命令 | 描述 |
---|---|
clearNodes() | 将节点变成简单的段落 |
createParagraphNear() | 在当前位置的附近创建一个段落 |
deleteNode() | 删除节点 |
extendMarkRange() | 将文本选择范围扩展到当前标记。 |
exitCode() | 停止代码编辑 |
joinBackward() | 和后一个节点合并 |
joinForward() | 和前一个节点合并 |
lift() | 提升当前的选区到上一个层级,例如将二层的列表项变成一层的列表项 |
liftEmptyBlock() | 提升空块的层,例如空的列表项点回车时会将当前列表项提升为单独的一行;空引用点击回车会退出引用 |
newlineInCode() | 在代码中添加换行符 |
resetAttributes() | 将一些节点或标记的属性重置为默认值 |
setMark() | 给标记添加一个新属性 |
setNode() | 将一个指定范围的内容替换为新节点 |
splitBlock() | 在光标处分割当前元素,派生一个新节点 |
toggleMark() | 切换标记 |
toggleWrap() | 切换指定的包裹标签,例如 toggleWrap(‘bulletList’) 切换当前元素是否放在列表中 |
undoInputRule() | 撤销输入规则,即变成普通文本 |
unsetAllMarks() | 删除当前选区的所有标记 |
unsetMark() | 删除当前选区的指定的标记 |
updateAttributes() | 更新节点或标记的属性 |
列表
命令 | 描述 |
---|---|
liftListItem() | 提升列表项等级 |
sinkListItem() | 降低列表项等级 |
splitListItem() | 将一个列表项拆分为两个列表项。 |
toggleList() | 切换列表类型;切换普通文本和列表 |
wrapInList() | 将一个节点包装在一个列表中 |
选区
命令 | 描述 |
---|---|
blur() | 从编辑器中移除焦点 |
deleteRange() | 删除指定 range |
deleteSelection() | 删除 selection |
enter() | 触发回车行为 例如分割p标签、创建新的一行等 |
focus() | 聚焦编辑器到指定的位置 |
keyboardShortcut() | 触发指定的键盘快捷键 |
scrollIntoView() | 滚动视图到选区位置 |
selectAll() | 选中整个文档 |
selectNodeBackward() | 向后选中一个节点 |
selectNodeForward() | 向前选中一个节点 |
selectParentNode() | 选中父节点 |
setNodeSelection() | 创建一个 NodeSelection |
setTextSelection() | 创建一个 TextSelection |
相关文章:

【element-tiptap】Tiptap编辑器核心概念----结构篇
core-concepts 前言:这篇文章来介绍一下 Tiptap 编辑器的一些核心概念 (一)结构 1、 Schemas 定义文档组成方式。一个文档就是标题、段落以及其他的节点组成的一棵树。 每一个 ProseMirror 的文档都有一个与之相关联的 schema,…...
半导体工艺与制造篇3 离子注入
离子注入工艺 一般掺杂的杂质类别,包括:提供载流子的施主杂质和受主杂质;产生复合中心的重金属杂质 离子注入往往需要生成井well,其中井的定义:晶圆与杂质之间形成的扩散层或杂质与杂质之间形成的扩散层 离子注入的目的:用掺杂改…...

利用开源的低代码表单设计器FcDesigner高效管理和渲染复杂表单结构
FcDesigner 是一个强大的开源低代码表单设计器组件,支持快速拖拽生成表单。提供丰富的自定义及扩展功能,FcDesigner支持多语言环境,并允许开发者进行二次开发。通过将表单设计输出为JSON格式,再通过渲染器进行加载,实现…...
淘宝 NPM 镜像源
npm i vant/weapp -S --production npm config set registry https://registry.npmmirror.com 要在淘宝 NPM 镜像站下载项目或依赖,你可以按照以下步骤操作: 1. 设置淘宝 NPM 镜像源 首先,你需要设置淘宝 NPM 镜像源以加速下载。可以通过…...

i春秋-GetFlag(md5加密,字符串比较绕过)
练习平台地址 竞赛中心 题目描述 题目内容 你好,单身狗,这是一个迷你文件管理器,你可以登录和下载文件,甚至得到旗帜 点击登录 发现capture需要满足条件substr(md5(captcha), 0, 6)xxxxxx 编写python脚本破解验证码 import has…...

SpringBoot中设置超时30分钟自动删除元素的List和Map
简介 在 Spring Boot 中,你可以使用多种方法来实现自动删除超时元素的 List 或 Map。以下是两种常见的方式: 如果你需要简单的功能并且不介意引入外部依赖,可以选择 Guava Cache。如果你想要更灵活的控制,使用 Spring 的调度功能…...

入门车载以太网(6) -- XCP on Ethernet
目录 1.寻址方式 2.数据帧格式 3.特殊指令 4.使用实例 了解了SOME/IP之后,继续来看看车载以太网在汽车标定领域的应用。 在汽车标定领域XCP是非常重要的协议,咱们先来回顾下基础概念。 XCP全称Universal Measurement and Calibration Protocol&a…...

DAY4 网络编程(广播和多线程并发)
作业1: 1、将广播发送和接收端实现一遍,完成一个发送端发送信息,对应多个接收端接收信息实验。 send.c代码: #include <myhead.h> #define IP "192.168.61.255"//广播IP #define PORT 7777 int main(int argc, …...
C++个人复习(4)
C中为什么要引入make_shared,它有什么优点 1. 减少内存分配次数 使用 make_shared 时,内存分配只发生一次,它同时分配了对象和控制块(用于管理引用计数等信息)。而如果直接使用 new 创建对象并传递给 shared_ptr,则会…...

Dockerhub镜像加速
一、背景 dockerhub由于被封锁和站点处于国外的原因,docker pull拉取镜像非常慢,有时候直接都无法拉取。严重妨碍了我们的学习进度以及日常使用。 总结了一些proxy代理的镜像站点,配置之后速度会有明显提升,大家可以参考使用。 二…...
11.20讲座笔记
信息门户 -------- 人才培养方案(重要) 结构化矛盾------需求方(企业) ------供给方(高校) 电子方向职业 -------- 基建、基础算力 -------中国 1st (已经相对完善饱和) 网…...
网络协议之UDP
一、UDP协议定义 UDP(User Datagram Protocol,用户数据报协议)是一种面向无连接的、不可靠的、基于数据报的传输层通信协议。UDP在传输数据时不需要建立连接,直接将数据包发送出去。这种特性使得UDP在实时性要求较高的应用场景中…...
Elasticsearch面试内容整理-常见问题和解决方案
在使用 Elasticsearch 的过程中,可能会遇到各种常见问题,如集群状态异常、分片未分配、查询性能低下等。这些问题往往影响系统的可用性和性能,因此理解这些问题的成因和解决方案非常重要。以下是 Elasticsearch 常见问题及其解决方案的整理。 集群状态问题 Elasticsearch 集…...
React 表单Form 中的 useForm
1、介绍 useForm 是 React Hook Form 中的核心 Hook,用于管理表单的状态和行为。它提供了处理表单验证、数据收集、状态管理等功能的简便方法。useForm 本质上是用于创建和配置表单,并允许你在组件中与表单字段交互。 2、基本用法 useForm 是一个函数…...
用指针函数寻找数组中的最大值与次大值
#include <stdio.h>// 函数用于找出数组中的最大值和次大值 void LargestTow(int a[], int n, int *pfirst, int *psecond) {*pfirst a[0];*psecond a[1];if (*psecond > *pfirst) {// 如果初始的次大值大于最大值,交换它们int temp *pfirst;*pfirst *…...
人工智能在金融领域的创新与应用
引言:AI如何重塑金融行业? 金融行业是人工智能(AI)技术的最佳应用场景之一。通过数据分析、模式识别和自动化处理,AI正为金融行业提供高效、安全和智能化的解决方案。从反欺诈到投资决策,AI正逐步改变金融服…...
shell脚本(4)一文解决比较运算符用户交互
免责声明 学习视频来自B 站up主泷羽sec,如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识,以下代码、网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负。 比较运算符 在Shell脚本中…...

windows 操作系统下载 Android源码教程
前言 开始我是装了hyber-v 虚拟机ubuntu 的,然而非常的卡顿且难用。因此我尝试在windows上使用repo,因此有了这篇文章 补充 第二天发现编译源码也需要linux命令因为源码中的很多脚本都是.sh的 因此最终通过安装WSL解决(在window应用商店就…...

【AIGC】如何使用高价值提示词Prompt提升ChatGPT响应质量
博客主页: [小ᶻZ࿆] 本文专栏: AIGC | 提示词Prompt应用实例 文章目录 💯前言💯提示词英文模板💯提示词中文解析1. 明确需求2. 建议额外角色3. 角色确认与修改4. 逐步完善提示5. 确定参考资料6. 生成和优化提示7. 生成最终响…...

vue3-input 搜索框
第一种 实现效果 实现代码 <template><div class="input-box mb20"><input type="text" class="input" /><span class="span">搜</span></div> </template><script setup> import …...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...