Slate文档编辑器-Decorator装饰器渲染调度

news2025/1/4 6:37:26

Slate文档编辑器-Decorator装饰器渲染调度

在之前我们聊到了基于文档编辑器的数据结构设计,聊了聊基于slate实现的文档编辑器类型系统,那么当前我们来研究一下slate编辑器中的装饰器实现。装饰器在slate中是非常重要的实现,可以为我们方便地在编辑器渲染调度时处理range的渲染。

  • 在线编辑: https://windrunnermax.github.io/DocEditor
  • 开源地址: https://github.com/WindRunnerMax/DocEditor

关于slate文档编辑器项目的相关文章:

  • 基于Slate构建文档编辑器
  • Slate文档编辑器-WrapNode数据结构与操作变换
  • Slate文档编辑器-TS类型扩展与节点类型检查
  • Slate文档编辑器-Decorator装饰器渲染调度
  • Slate文档编辑器-Node节点与Path路径映射

Decorate

slatedecoration是比较有趣的功能,设想一个场景,当需要实现代码块的高亮时,我们可以有几种方案来实现: 第一种方案是我们可以通过直接将代码块的内容解析的方式,解析出的关键字类别直接写入数据结构中,这样就可以直接在渲染时将高亮信息渲染出来,缺点就是会增加数据结构存储数据的大小;那么第二种方式我们就可以只存储代码信息,当需要数据高亮时也就是前端渲染时我们再将其解析出Marks进行渲染,但是这样的话如果存在协同我们还需要为其标记为非协同操作以及无需服务端存储的纯客户端Op,会稍微增加一些复杂度;那么第三种方法就是使用decoration,实际上可以说这里只是slate帮我们把第二种方法的事情做好了,可以在不改变数据结构的情况下将额外的Marks内容渲染出来。

当然,我们使用装饰器的场景自然不只是代码块高亮,凡是涉及到不希望在数据结构中表达却要在渲染时表现的内容,都需要使用decoration来实现。还有比较明显的例子就是查找能力,如果在编辑器中实现查找功能,那么我们需要将查找到的内容标记出来,这个时候我们就可以使用decoration来实现,否则就需要绘制虚拟的图层来完成。而如果需要实现用户态的超链接解析功能,即直接贴入连接的时候,我们希望将其自动转为超链接的节点,也可以利用装饰器来实现。

在前段时间测试slate官网的search-highlighting example时,当我搜索adds时,搜索的效果很好,但是当我执行跨节点的搜索时,就不能非常有效地突出显示内容了,具体信息可以查看https://github.com/ianstormtaylor/slate/pull/5670。这也就是说当decoration执行跨节点处理的时候,是存在一些问题的。例如下面的例子,当我们搜索123或者12345时,我们能够正常将标记出的decoration渲染出来,然而当我们搜索123456时,此时我们构造的range会是path: [0], offset: [0-6],此时我们跨越了[0]节点进行标记,就无法正常标记内容了。

[
    { text: "12345" },
    { text: "67890" }
]

通过调用查找相关代码,我们可以看到上级的decorate结果会被传递到后续的渲染中,那么在本级同样会调度传递的decorate函数来生成新的decorations,并且这里需要判断如果父级的decorations与当前节点的range存在交集的话,那么内容会被继续传递下去。那么重点就在这里了,试想一下我们的场景,依旧以上述的例子中的内容为例,如果我们此时想要获取123456的索引,那么在text: 12345这个节点中肯定是不够的,我们必须要在上层数组中将所有文本节点的内容拼接起来,然后再查找才可以找到准确的索引位置。

// https://github.com/ianstormtaylor/slate/blob/25be3b/packages/slate-react/src/hooks/use-children.tsx#L21
const useChildren = (props: {
  decorations: Range[]
  // ...
}) => {
  // ...

  for (let i = 0; i < node.children.length; i++) {
    // ...
    const ds = decorate([n, p])
    for (const dec of decorations) {
      const d = Range.intersection(dec, range)
      if (d) {
        ds.push(d)
      }
    }
    // ...
  }
  // ...
}

那么此时我们就明确需要我们调用decorate的节点是父级元素,而父级节点传递到我们需要处理的text节点时,就需要Range.intersection来判断是否存在交集,实际上这里判断交集的策略很简单,在下面我们举了两个例子,分别是存在交集和不存在交集的情况,我们实际上只需要判断两个节点的最终状态即可。

// https://github.com/ianstormtaylor/slate/blob/25be3b/packages/slate/src/interfaces/range.ts#L118

// start1          end1          start2          end2
// end1          start2
// end1 < start2 ===> 无交集

// start1          start2          end1          end2
// start2          end1
// start2 < end1 ===> 有交集 [start2, end1]

那么我们可以通过修改在decorate这部分代码中的Range.intersection逻辑部分来解决这个问题吗,具体来说就是当我们查找出的内容超出原本range的内容,则截取其需要装饰的部分,而其他部分舍弃掉,实际上这个逻辑在上边我们分析的时候已经发觉是没有问题的,也就是当我们查找123456的时候是能够将12345这部分完全展示出来的。根据前边的分析,本次循环我们的节点都在path: [0],这部分代码会将start: 0end: 5这部分代码截取range并渲染。

然而我们在下一个text range范围内继续查找6这部分就没有那么简单了,因为前边我们实际上查找的rangepath: [0], offset: [0-6],而第二个text的基本rangepath: [1], offset: [0-5],基于上述判断条件的话我们是发现是不会存在交集的。因此如果需要在这里进行处理的话,我们就需要取得前一个range甚至在跨越多个节点的情况下我们需要向前遍历很多节点,当decorations数量比较多的情况下我们需要检查所有的节点,因为在此节点我们并不知道前一个节点是否超越了本身节点的长度,这种情况下在此处的计算量可能比较大,或许会造成性能问题。

因此我们还是从解析时构造range入手,当跨越节点时我们就需要将当前查找出来的内容分割为多个range,然后为每个range分别置入标记,还是以上边的数据为例,此时我们查找的结果就是path: [0], offset: [0, 5]path: [1], offset: [0, 1]两部分,这种情况下我们在Range.intersection时就可以正常处理交集了,此时我们的path是完全对齐的,而即使完全将内容跨越,也就是搜索内容跨越不止一个节点时,我们也可以通过这种方式来处理。

// https://github.com/ianstormtaylor/slate/pull/5670
const texts = node.children.map(it => it.text)
const str = texts.join('')
const length = search.length
let start = str.indexOf(search)
let index = 0
let iterated = 0
while (start !== -1) {
  while (index < texts.length && start >= iterated + texts[index].length) {
    iterated = iterated + texts[index].length
    index++
  }
  let offset = start - iterated
  let remaining = length
  while (index < texts.length && remaining > 0) {
    const currentText = texts[index]
    const currentPath = [...path, index]
    const taken = Math.min(remaining, currentText.length - offset)
    ranges.push(/* 构造新的`range` */)
    remaining = remaining - taken
    if (remaining > 0) {
      iterated = iterated + currentText.length
      offset = 0
      index++
    }
  }
  start = str.indexOf(search, start + search.length)
}

此外,我们在调度装饰器的时候,需要关注在renderLeaf参数RenderLeafProps的值,因为在这里存在两种类型的文本内容,即leaf: Text;以及text: Text;基本TextInterface类型。而在我们通过renderLeaf渲染内容时,以高亮的代码块内值mark节点的渲染为例,我们实际渲染节点需要以leaf为基准而不是以text为基准,例如当在渲染markbold样式产生重叠时,这两种节点都需要以leaf为基准。

在这里的原因是,decorationsslate实现中是以text节点为基准将其拆分为多个leaves,然后再将leaves传递到renderLeaf中进行渲染。因此实际上在这里可以这么理解,text属性为原始值,而leaf属性为更细粒度的节点,调度renderLeaf的时候本身也是以leaf为粒度进行渲染的,当然在不使用装饰器的情况下,这两种属性的节点类型是等同的。

// https://github.com/ianstormtaylor/slate/blob/25be3b/packages/slate-react/src/components/text.tsx#L39
const leaves = SlateText.decorations(text, decorations)
const key = ReactEditor.findKey(editor, text)
const children = []

for (let i = 0; i < leaves.length; i++) {
  const leaf = leaves[i]

  children.push(
    <Leaf
    isLast={isLast && i === leaves.length - 1}
    key={`${key.id}-${i}`}
    renderPlaceholder={renderPlaceholder}
    leaf={leaf}
    text={text}
    parent={parent}
    renderLeaf={renderLeaf}
    />
  )
}

最后

在这里我们主要讨论了slate中的decoration装饰器的实现,以及在实际使用中可能会遇到的问题,主要是在跨节点的情况下,我们需要将range拆分为多个range,然后分别进行处理,并且还分析了源码来探究了相关问题的实现。那么在后边的文章中我们就主要聊一聊在slatePath的表达,以及在React中是如何控制其内容表达与正确维护Path路径与Element内容渲染的方案。

Blog

https://github.com/WindRunnerMax/EveryDay

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

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

相关文章

[2474].第04节:Activiti官方画流程图方式

我的后端学习大纲 Activiti大纲 1.安装位置&#xff1a; 2.启动&#xff1a;

按照乘法分解10点结构

在行列可自由变换的平面上9点结构有1430个&#xff0c;10点结构有3908个。其中可被分解为2*5的有102个&#xff0c; 5a1*2a110a28 5a1*2a210a689 5a1*2a310a1722 5a2*2a110a172 5a2*2a210a1081 5a2*2a310a2006 5a3*2a110a275 5a3*2a210a1561 5a3*2a310a2381 5a4*2a110…

JVM实战—6.频繁YGC和频繁FGC的后果

大纲 1.JVM GC导致系统突然卡死无法访问 2.什么是Young GC什么是Full GC 3.Young GC、Old GC和Full GC的发生情况 4.频繁YGC的案例(G1解决大内存YGC过慢) 5.频繁FGC的案例(YGC存活对象S区放不下) 6.问题汇总 1.JVM GC导致系统突然卡死无法访问 (1)基于JVM运行的系统最怕…

word运行时错误‘-2147221164(80040154)’ 没有注册类的解决办法

目录 问题描述解决方案 问题描述 解决方案 打开C盘找到路径C:\Users\Administrator\AppData\Roaming\Microsoft\Word\STARTUP或者在everything中搜索“Microsoft\Word\STARTUP”删除NEWebWordAddin.dotm文件即可正确打开word。

微服务保护—Sentinel快速入门+微服务整合 示例: 黑马商城

1.微服务保护 微服务保护是确保微服务架构可靠、稳定和安全的策略与技术。 在可靠性上&#xff0c;限流是控制进入微服务的请求数量&#xff0c;防止流量过大导致服务崩溃。比如电商促销时对商品详情服务进行流量限制。熔断是当被调用的微服务故障过多或响应过慢时&#xff0c;…

屏幕时序参数详解

屏幕时序参数详解 作者&#xff1a;&#xff08;Witheart&#xff09;更新时间&#xff1a;20241231 本文详细介绍了屏幕显示时序的基本参数&#xff0c;包括水平和垂直方向的有效像素、同步信号、前肩、后肩及其总周期的定义与计算公式。同时&#xff0c;通过公式和图示&…

2024年RAG:回顾与展望

2024年&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff09;技术经历了从狂热到理性的蜕变&#xff0c;成为大模型应用领域不可忽视的关键力量。年初&#xff0c;AI的“无所不能”让市场充满乐观情绪&#xff0c;RAG被视为解决复杂问题的万能钥匙&#xff1…

webpack01

webpack是一个前端工程化的打包工具 webpack在打包的时候&#xff0c;会形成一个依赖关系图&#xff0c;关联要打包的模块&#xff0c;&#xff0c;&#xff0c;不同的模块通过不同的loader去解析&#xff0c;&#xff0c;&#xff0c;比如解析css使用 css-loader,解析js使用b…

牛客网最新1129道 Java 面试题及答案整理

前言 面试&#xff0c;跳槽&#xff0c;每天都在发生&#xff0c;而对程序员来说"金三银四"更是面试和跳槽的高峰期&#xff0c;跳槽&#xff0c;更是很常见的&#xff0c;对于每个人来说&#xff0c;跳槽的意义也各不相同&#xff0c;可能是一个人更向往一个更大的…

python版本的Selenium的下载及chrome环境搭建和简单使用

针对Python版本的Selenium下载及Chrome环境搭建和使用&#xff0c;以下将详细阐述具体步骤&#xff1a; 一、Python版本的Selenium下载 安装Python环境&#xff1a; 确保系统上已经安装了Python 3.8及以上版本。可以从[Python官方网站]下载并安装最新版本的Python&#xff0c;…

突破管理困局,驾驭变革浪潮

在瞬息万变的商业环境中&#xff0c;变革已成为企业生存和发展的必经之路。许多企业在面对激烈竞争、技术进步和市场变化时&#xff0c;都会选择或被迫进行各种形式的变革。本文将深入探讨变革管理的重要性&#xff0c;介绍常见的变革模型&#xff0c;并提供实用的策略和建议&a…

WPF编程excel表格操作

WPF编程excel表格操作 摘要NPOI安装封装代码测试代码 摘要 Excel操作几种方式 使用开源库NPOI(常用&#xff0c;操作丰富)使用Microsoft.Office.Interop.Excel COM组件(兼容性问题)使用OpenXml(效率高)使用OleDb(过时) NPOI安装 封装代码 using System; using System.IO; u…

【论文阅读笔记】SCI算法与代码 | 低照度图像增强 | 2022.4.21

目录 一 SCI 1 SCI网络结构 核心代码&#xff08;model.py&#xff09; 2 SCI损失函数 核心代码&#xff08;loss.py&#xff09; 3 实验 二 SCI效果 1 下载代码 2 运行 一 SCI &#x1f49c;论文题目&#xff1a;Toward Fast, Flexible, and Robust Low-Light Image …

wps透视数据表

1、操作 首先选中你要的行字段表格 -> 插入 -> 透视数据表 -> 拖动行值&#xff08;部门&#xff09;到下方&#xff0c;拖动值&#xff08;包裹数量、运费&#xff09;到下方 2、删除 选中整个透视数据表 -> delete 如图&#xff1a;

STM32配合可编程加密芯片SMEC88ST的防抄板加密方案设计

SMEC88ST SDK开发包下载 目前市场上很多嵌入式产品方案都是可以破解复制的&#xff0c;主要是因为方案主芯片不具备防破解的功能&#xff0c;这就导致开发者投入大量精力、财力开发的新产品一上市就被别人复制&#xff0c;到市场上的只能以价格竞争&#xff0c;最后工厂复制的产…

【电路理论四】正弦电流电路

正弦电流 正弦量是随时间按正弦规律变动的电路变量。 随时间按正弦规律变动的电流称为正弦电流。 正弦电流的瞬时值表达式&#xff1a; 称为正弦电流的三要素。 分别为振幅/幅值&#xff0c;角频率&#xff0c;初相。 幅值为正弦电流的最大值&#xff0c;恒为正。 为正弦电…

多模态论文笔记——Coca(副)

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍多模态模型Coca&#xff0c;在DALLE 3中使用其作为captioner基准模型的原因和优势。 文章目录 ALBEF论文模型结构组成训练目标 CoCa​论文模型结构CoCa…

【Python系列】处理空请求体Body

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【毕业设计选题】目标检测方向毕业设计选题推荐 2025

目录 前言 毕设选题 开题指导建议 更多精选选题 选题帮助 最后 前言 大家好,这里是海浪学长毕设专题! 大四是整个大学期间最忙碌的时光&#xff0c;一边要忙着准备考研、考公、考教资或者实习为毕业后面临的升学就业做准备,一边要为毕业设计耗费大量精力。学长给大家整…

从2024看2025前端发展趋势

前言 又至年关&#xff0c;回顾整个2024年&#xff0c;前端行业仍旧百废待兴&#xff0c;IT业界同样也未见有所起色&#xff0c;AI风潮也从狂热兴奋逐步走向了冷静稳定阶段&#xff0c;造成此形势感观并非单一行业或者某一企业之特例&#xff0c;实为政经等综合影响之结果。因…