【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 transaction
    tr.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/920742.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

window的wsl(Ubuntu)安装kafka步骤

环境&#xff1a;Win11 WSL(Linux子系统Ubuntu) apache-zookeeper-3.9.3-bin kafka_2.12-3.8.1 思路&#xff1a;apache上分别下载zookeeper和kafka&#xff0c;在wsl环境安装。在kafka上创建消息的topic&#xff0c;发送消息&#xff0c;接受消息&#xff0c;验证是否安…

Notepad++--在开头快速添加行号

原文网址&#xff1a;Notepad--在开头快速添加行号_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Notepad怎样在开头快速添加行号。 需求 原文件 想要的效果 方法 1.添加点号 Alt鼠标左键&#xff0c;从首行选中首列下拉&#xff0c;选中需要添加序号的所有行的首列&#xff…

机器学习基础06_梯度下降

目录 一、为什么使用梯度下降 二、什么是梯度下降 三、为什么要用梯度下降 四、怎么进行梯度下降 1、微分 1.单变量的微分 2.多变量的微分 2、梯度 3、步骤 (1)学习率α (2)梯度(导数)前的负号 4、实例实现 五、sklearn梯度下降 一、为什么使用梯度下降 前面利用正…

《Vue零基础入门教程》第二课:搭建开发环境

往期内容&#xff1a; 《Vue零基础入门教程》第一课&#xff1a;Vue简介 1 搭建开发环境 Vue环境分为两种 不使用构建工具使用构建丁具 首先&#xff0c;我们会介绍 不使用构建工具 的环境,在组件化章节中介绍 使用构建工具 的方式 1) 初始化 使用如下指令初始化 npm i…

【IDEA】解决总是自动导入全部类(.*)问题

文章目录 问题描述解决方法 我是一名立志把细节说清楚的博主&#xff0c;欢迎【关注】&#x1f389; ~ 原创不易&#xff0c; 如果有帮助 &#xff0c;记得【点赞】【收藏】 哦~ ❥(^_-)~ 如有错误、疑惑&#xff0c;欢迎【评论】指正探讨&#xff0c;我会尽可能第一时间回复…

Acme PHP - Let‘s Encrypt

Lets Encrypt是一个于2015年三季度推出的数字证书认证机构&#xff0c;旨在以自动化流程消除手动创建和安装证书的复杂流程&#xff0c;并推广使万维网服务器的加密连接无所不在&#xff0c;为安全网站提供免费的SSL/TLS证书。 使用PHP来更新证书&#xff1a; Acme PHP | Rob…

【Linux清空显存占用】Linux 系统中清理 GPU 显存

操作指令 # 查看NVIDIA GPU状态和进程 nvidia-smi # 查找所有包含"python"的进程 ps -ef grep python # 强制结束进程号为3023的进程 kill -9 3023截图演示 在 Linux 系统中清理 GPU 显存可以采用以下方法&#xff1a; 1. 终止特定进程&#xff08;常用方法&#x…

【网络】网络抓包与协议分析

网络抓包与协议分析 一. 以太网帧格式分析 这是以太网数据帧的基本格式&#xff0c;包含目的地址(6 Byte)、源地址(6 Byte)、类型(2 Byte)、数据(46~1500 Byte)、FCS(4 Byte)。 Mac 地址类型 分为单播地址、组播地址、广播地址。 单播地址&#xff1a;是指第一个字节的最低位…

IC脚本之perl

Perl 是一种功能丰富的计算机程序语言&#xff0c;运行在超过100种计算机平台上。IC flow 的 流传的古老版本大多是也是使用这种语言&#xff0c;这里会对Perl的常用知识点进行总结。 Note: 所有的语句必须以 “ &#xff1b;”结尾&#xff1b;所有的数据必须先定义才可以使…

MEMS硅麦克风应用电子烟雾化产业稳步爬升,耐高温、 防油、防酸、防腐蚀等性能优势和可实现自动化贴片及极高的一致性等特性使其必将成为主流

全球范围内&#xff0c;电子烟行业正处于快速发展的阶段。随着消费者健康意识的提升和对传统烟草制品替代品需求的增加&#xff0c;电子烟市场获得了显著的增长。然而&#xff0c;伴随而来的监管挑战和消费者期待的变化&#xff0c;也促使行业不断进行技术创新和产品优化。特别…

双因子认证:统一运维平台安全管理策略

01双因子认证概述 双因子认证&#xff08;Two-Factor Authentication&#xff0c;简称2FA&#xff09;是一种身份验证机制&#xff0c;它要求用户提供两种不同类型的证据来证明自己的身份。这通常包括用户所知道的&#xff08;如密码&#xff09;、用户所拥有的&#xff08;如…

快慢指针应用---环型链表的应用

1.题目--判断链表是否成环 已经了解了快慢指针的应用原理&#xff0c;引申&#xff1a;用快慢指针去判断链表是否成环。 题解 简而言之&#xff0c;在fast和slow指针遍历的这种情况下&#xff0c;如果链表是成环的&#xff0c;那么在循环遍历了两次后&#xff0c;fast指针就会…

三、计算机视觉_06YOLO基础知识

1、YOLO概述 1.1 定义 YOLO&#xff08;You Only Look Once&#xff09;是一种流行的对象检测和图像分割模型&#xff0c;由华盛顿大学的 Joseph Redmon 和 Ali Farhadi 于 2015 年推出&#xff0c;因其高速和准确性而迅速受到欢迎 在目标检测领域&#xff0c;传统方法&…

Python Matplotlib 数据可视化全面解析:选择它的七大理由与入门简介

Python Matplotlib数据可视化全面解析&#xff1a;选择它的七大理由与入门简介 本文介绍了Matplotlib这一强大而灵活的数据可视化工具&#xff0c;涵盖其基本概念、独特优势以及为何在众多Python绘图库中脱颖而出。Matplotlib具有广泛的社区支持、高度自定义能力、多样的绘图类…

【Spring Boot】用 MyBatis 实现数据的 CRUD

用 MyBatis 实现数据的 CRUD 1.创建项目 & 引入依赖2.实现数据表的自动初始化3.实现实体对象建模4.实现实体和数据表的映射关系5.实现增加、删除、修改和查询功能6.配置分页功能6.1 增加分页支持6.2 创建分页配置类 7.实现分页控制器8.创建分页视图 本篇博客将通过 MyBatis…

数据结构-二叉树_堆

目录 1.二叉树的概念 ​编辑1.1树的概念与结构 1.2树的相关语 1.3 树的表示 2. ⼆叉树 2.1 概念与结构 2.2 特殊的⼆叉树 2.2.2 完全⼆叉树 2.3 ⼆叉树存储结构 2.3.1 顺序结构 2.3.2 链式结构 3. 实现顺序结构⼆叉树 3.2 堆的实现 3.2.2 向下调整算法 1.二叉树的概…

独家原创 | SCI 1区 高创新预测模型!

往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较 全是干货 | 数据集、学习资料、建模资源分享&#xff01; EMD变体分解效果最好算法——CEEMDAN&#xff08;五&#xff09;-CSDN博客 拒绝信息泄露&#xff01;VMD滚动分…

IDEA+Docker一键部署项目SpringBoot项目

文章目录 1. 部署项目的传统方式2. 前置工作3. SSH配置4. 连接Docker守护进程5. 创建简单的SpringBoot应用程序6. 编写Dockerfile文件7. 配置远程部署7.1 创建配置7.2 绑定端口7.3 添加执行前要运行的任务 8. 部署项目9. 开放防火墙的 11020 端口10. 访问项目11. 可能遇到的问题…

Arcgis 地图制作

地图如下,不同历史时期&#xff1a;

【K8S系列】Kubernetes 中如何调试imagePullSecrets配置详细步骤介绍

调试 imagePullSecrets 配置是确保 Kubernetes 能够成功拉取私有镜像所需的关键步骤。以下是详细的调试步骤和建议。 1. 确认 imagePullSecrets 配置 首先&#xff0c;确保在 Pod 的 YAML 配置中正确引用了 imagePullSecrets。其基本结构如下&#xff1a; apiVersion: v1 kin…