【element-tiptap】Tiptap编辑器核心概念----结构篇

news2024/11/19 5:49:18

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LC69---219存在重复元素(滑动窗口)---Java版

1.题目描述 2.思路 3.代码实现 public class Solution { public boolean containsNearbyDuplicate(int[] nums, int k) {Map<Integer,Integer> m1new HashMap<>();// 1:0, 2:1,3:2,1:3 key存数组的值&#xff0c;value存索引&#xff0c;为getnum[i]做准备&am…

【C++】了解map和set及平衡二叉树和红黑树的原理

目录 ​编辑 一、关联式容器 二、 键值对 三、pair介绍 四、树形结构的关联式容器 4.1 set 4.2 map 4.3 multiset 4.4 multimaps 五、底层结构&#xff08;重点&#xff09; 5.1 AVL 树 5.1.1 AVL树的概念 5.1.2 AVL树节点的定义 5.1.3 AVL树的旋转 5.1.4 AVL树的…

LeetCode 力扣 热题 100道(五)最长回文子串(C++)

最长回文子串 给你一个字符串 s&#xff0c;找到 s 中最长的 回文子串。 回文性 如果字符串向前和向后读都相同&#xff0c;则它满足 回文性 子字符串子字符串 是字符串中连续的 非空 字符序列。 动态规划法 class Solution { public:string longestPalindrome(string s) {i…

Spring Boot汽车资讯:科技与汽车的新融合

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了汽车资讯网站的开发全过程。通过分析汽车资讯网站管理的不足&#xff0c;创建了一个计算机管理汽车资讯网站的方案。文章介绍了汽车资讯网站的系统分析部分&…

逆向攻防世界CTF系列41-EASYHOOK

逆向攻防世界CTF系列41-EASYHOOK 看题目是一个Hook类型的&#xff0c;第一次接触&#xff0c;虽然学过相关理论&#xff0c;可以看我的文章 Hook入门(逆向)-CSDN博客 题解参考&#xff1a;https://www.cnblogs.com/c10udlnk/p/14214057.html和攻防世界逆向高手题之EASYHOOK-…

【网络】HTTP 协议

目录 基本概念基于 HTTP 的系统组成HTTP 的基本性质 HTTP 请求头 & 响应头HTTP 的请求方法HTTP 的返回码HTTP 的 CookieHTTP 缓存 Cache-Control会话HTTP/1.x 的连接管理 基本概念 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一…

执行flink sql连接clickhouse库

手把手教学&#xff0c;flink connector打通clickhouse大数据库&#xff0c;通过下发flink sql&#xff0c;来使用ck。 组件版本jdk1.8flink1.17.2clickhouse23.12.2.59 1.背景 flink官方不支持clickhouse连接器&#xff0c;工作中难免会用到。 2.方案 利用GitHub大佬提供…

笔记|M芯片MAC (arm64) docker上使用 export / import / commit 构建amd64镜像

很简单的起因&#xff0c;我的东西最终需要跑在amd64上&#xff0c;但是因为mac的架构师arm64&#xff0c;所以直接构建好的代码是没办法跨平台运行的。直接在arm64上pull下来的docker镜像也都是arm64架构。 检查镜像架构&#xff1a; docker inspect 8135f475e221 | grep Arc…

免费送源码:Java+Springboot+MySQL Springboot多租户博客网站的设计 计算机毕业设计原创定制

Springboot多租户博客网站的设计 摘 要 博客网站是当今网络的热点&#xff0c;博客技术的出现使得每个人可以零成本、零维护地创建自己的网络媒体&#xff0c;Blog站点所形成的网状结构促成了不同于以往社区的Blog文化&#xff0c;Blog技术缔造了“博客”文化。本文课题研究的“…

代码随想录第46期 单调栈

这道题主要是单调栈的简单应用 class Solution { public:vector<int> dailyTemperatures(vector<int>& T) {vector<int> result(T.size(),0);stack<int> st;st.push(0);for(int i1;i<T.size();i){if(T[i]<T[st.top()]){st.push(i);}else{wh…

【Three.js基础学习】24. shader patterns

前言 课程回顾: ShaderMaterial 这里用的是着色器材质 所以顶点和片段着色器就不需要像原始着色器那样添加需要的属性 然后写 片段着色器需要属性 &#xff1a; 顶点 属性 -》变化 -》 片段中 顶点中的属性不需要声明 只需要声明传送的变量 例如 varying vec vUv; vUv uv; 补充…

力扣整理版七:二叉树(待更新)

满二叉树&#xff1a;如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。深度为k&#xff0c;有2^k-1个节点的二叉树。 完全二叉树&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&am…

173. 二叉搜索树迭代器【 力扣(LeetCode) 】

文章目录 零、原题链接一、题目描述二、测试用例三、解题思路四、参考代码 零、原题链接 173. 二叉搜索树迭代器 一、题目描述 实现一个二叉搜索树迭代器类BSTIterator &#xff0c;表示一个按中序遍历二叉搜索树&#xff08;BST&#xff09;的迭代器&#xff1a; BSTIterato…

自动驾驶系列—深入解析自动驾驶车联网技术及其应用场景

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

STL序列式容器之list

相较于vector的连续性空间&#xff0c;list相对比较复杂&#xff1b;list内部使用了双向环形链表的方式对数据进行存储&#xff1b;list在增加元素时&#xff0c;采用了精准的方式分配一片空间对数据及附加指针等信息进行存储&#xff1b; list节点定义如下 template<clas…

Element-ui Select选择器自定义搜索方法

效果图 具体实现 <template><div class"home"><el-selectref"currencySelect"v-model"currency"filterable:spellcheck"false"placeholder"请选择":filter-method"handleCurrencyFilter"change&q…

Linux debian系统安装ClamTk开源图形用户界面(GUI)杀毒软件

一、ClamTk简介 ClamTk 是一个基于 ClamAV 的开源图形用户界面&#xff08;GUI&#xff09;杀毒软件。它使用 GTK2-Perl 脚本构建而成&#xff0c;支持32位与64位操作系统。ClamTk 提供了一个直观的用户界面&#xff0c;使得用户无需深入了解命令行即可完成大部分操作。它具备…

MIT6.5840 Lab 1: MapReduce(6.824)

结果 介绍 在本实验中&#xff0c;您将构建一个MapReduce系统。您将实现一个调用应用程序Map和Reduce函数并处理文件读写的工作进程&#xff0c;以及一个将任务分发给工作进程并处理失败的工作进程的协调进程。您将构建类似于MapReduce论文的东西。&#xff08;注意&#xff1a…

Kafka进阶_1.生产消息

文章目录 一、Controller选举二、生产消息2.1、创建待发送数据2.2、创建生产者对象&#xff0c;发送数据2.3、发送回调2.3.1、异步发送2.3.2、同步发送 2.4、拦截器2.5、序列化器2.6、分区器2.7、消息可靠性2.7.1、acks 02.7.2、acks 1(默认)2.7.3、acks -1或all 2.8、部分重…

SpringBoot多环境配置的实现

前言 开发过程中必然使用到的多环境案例&#xff0c;通过简单的案例分析多环境配置的实现过程。 一、案例 1.1主配置文件 spring:profiles:active: prod server:port: 80801.2多环境配置文件 开发环境 blog:domain: http://localhost:8080测试环境 blog:domain: https:/…