Slate文档编辑器-Decorator装饰器渲染调度
Slate文档编辑器-Decorator装饰器渲染调度
在之前我们聊到了基于文档编辑器的数据结构设计,聊了聊基于slate实现的文档编辑器类型系统,那么当前我们来研究一下slate编辑器中的装饰器实现。装饰器在slate中是非常重要的实现,可以为我们方便地在编辑器渲染调度时处理range的渲染。
关于slate文档编辑器项目的相关文章:
- 基于Slate构建文档编辑器
- Slate文档编辑器-WrapNode数据结构与操作变换
- Slate文档编辑器-TS类型扩展与节点类型检查
- Slate文档编辑器-Decorator装饰器渲染调度
- Slate文档编辑器-Node节点与Path路径映射
Decorate
在slate中decoration是比较有趣的功能,设想一个场景,当需要实现代码块的高亮时,我们可以有几种方案来实现: 第一种方案是我们可以通过直接将代码块的内容解析的方式,解析出的关键字类别直接写入数据结构中,这样就可以直接在渲染时将高亮信息渲染出来,缺点就是会增加数据结构存储数据的大小;那么第二种方式我们就可以只存储代码信息,当需要数据高亮时也就是前端渲染时我们再将其解析出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: 0到end: 5这部分代码截取range并渲染。
跨节点问题
然而我们在下一个text range范围内继续查找6这部分就没有那么简单了,因为前边我们实际上查找的range是path: [0], offset: [0-6],而第二个text的基本range是path: [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为基准,例如当在渲染mark和bold样式产生重叠时,这两种节点都需要以leaf为基准。
在这里的原因是,decorations在slate实现中是以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,然后分别进行处理,并且还分析了源码来探究了相关问题的实现。那么在后边的文章中我们就主要聊一聊在slate中Path的表达,以及在React中是如何控制其内容表达与正确维护Path路径与Element内容渲染的方案。
Blog
https://github.com/WindRunnerMax/EveryDay
Slate文档编辑器-Decorator装饰器渲染调度的更多相关文章
- 基于slate构建文档编辑器
基于slate构建文档编辑器 slate.js是一个完全可定制的框架,用于构建富文本编辑器,在这里我们使用slate.js构建专注于文档编辑的富文本编辑器. 描述 Github | Editor DE ...
- [Qt及Qt Quick开发实战精解] 第1章 多文档编辑器
这一章的例子是对<Qt Creator快速人门>基础应用篇各章节知识的综合应用, 也是一个规范的实例程序.之所以说其规范,是因为在这个程序中,我们对菜单什么时候可用/什么时候不可用.关 ...
- Linux_文档编辑器_简介
1. vi 2. vim 3. ubuntu 有一个 自己的图形化的 文档编辑器,用起来比较方便: gedit 4. 5.
- PowerDesigner(九)-模型文档编辑器(生成项目文档)(转)
模型文档编辑器 PowerDesigner的模型文档(Model Report)是基于模型的,面向项目的概览文档,提供了灵活,丰富的模型文档编辑界面,实现了设计,修改和输出模型文档的全过程. 模型文 ...
- Python进阶之decorator装饰器
decorator装饰器 .note-content {font-family: "Helvetica Neue",Arial,"Hiragino Sans GB&quo ...
- Web页面引入文档编辑器报风险
Web页面引入文档编辑器会报风险,则需要以下操作: <system.web> <httpRuntime requestValidationMode="2.0" / ...
- 使用Swing实现简易而不简单的文档编辑器
本文通过Swing来实现文档简易而不简单的文档编辑器,该文档编辑器的功能包括: 设置字体样式:粗体,斜体,下划线,可扩展 设置字体:宋体,黑体,可扩展 设置字号:12,14,18,20,30,40, ...
- 谈谈Python中的decorator装饰器,如何更优雅的重用代码
众所周知,Python本身有很多优雅的语法,让你能用一行代码写出其他语言很多行代码才能做的事情,比如: 最常用的迭代(eg: for i in range(1,10)), 列表生成式(eg: [ x* ...
- 在线HTML文档编辑器使用入门之图片上传与图片管理的实现
在线HTML文档编辑器使用入门之图片上传与图片管理的实现: 官方网址: http://kindeditor.net/demo.php 开发步骤: 1.开发中只需要导入选中的文件(通常在 webapp ...
- Python的程序结构[8] -> 装饰器/Decorator -> 装饰器浅析
装饰器 / Decorator 目录 关于闭包 装饰器的本质 语法糖 装饰器传入参数 1 关于闭包 / About Closure 装饰器其本质是一个闭包函数,为此首先理解闭包的含义. 闭包(Clos ...
随机推荐
- 《使用Gin框架构建分布式应用》阅读笔记:p108-p126
<用Gin框架构建分布式应用>学习第8天,p108-p126总结,总计18页. 一.技术总结 1.Redis eviction policy (1)什么是 eviction policy? ...
- 使用 VSCode 远程连接的 SSH 权限问题及解决方案
在使用 VSCode 远程 SSH 连接时,可能会遇到文件权限问题导致连接失败的情况.本文将详细记录如何为 SSH 配置文件(config)和私钥文件(id_rsa)正确设置权限,从而解决 VSCod ...
- 一文彻底弄懂MySQL的各个存储引擎,InnoDB、MyISAM、Memory、CSV、Archive、Merge、Federated、NDB
MySQL 中的存储引擎是其数据库管理系统的核心模块,用于处理不同类型的数据存储和检索操作.每种存储引擎都有自己的特点,适用于不同类型的应用场景.MySQL 最常用的存储引擎包括 InnoDB.MyI ...
- 号码变换配置对接运营商IMS
概述 freeswitch是一款简单好用的VOIP开源软交换平台. fs直接对接运营商,调试过程中的号码变换规则比较容易出问题. 本文档记录一个较为通用的对接IMS配置方案. 环境 CentOS 7. ...
- Httprunner生成Allure格式HTML报告
一.httprunner v2.x版本的报告 最近组内其他同学使用httprunner做接口自动化,之前没有接触过httprunner,发现httprunner相比pytest和unittest有自己 ...
- 基于Hadoop实现的对历年四级单词的词频分析(入门级Hadoop项目)
前情提要:飞物作者屡次四级考试未能通过,进而恼羞成怒,制作了基于Hadoop实现的对历年四级单词的词频分析项目,希望督促自己尽快通过四级(然而并没有什么卵用) 项目需求:Pycharm.IDEA.Li ...
- UE5笔记:虚幻引擎反射系统和对象
虚幻引擎反射系统 使用宏提供引擎和编辑器各种功能,封装你的类.使用虚幻时,可以使用标准的C++类,函数和变量 虚幻中对象的基类是UObject,UCALSS宏的作用是标记UObject的子类,以便UO ...
- 搭建离线yum源
HTTP方式 安装步骤 系统:CentOS 7.6 yum install -y httpd vi /etc/httpd/conf/httpd.conf <Directory /> Opt ...
- 2023NOIP A层联测26 T3 tour
2023NOIP A层联测26 T3 tour 有意思的树上主席树. 思路 首先考虑一个点 \(p\) 能计入答案的情况,就是 \(dis(x,p)-a_p \ge a_p\). 我们把 \(x \t ...
- (Redis基础教程之十) 如何在Redis中运行事务
介绍 Redis是一个开源的内存中键值数据存储.Redis允许您计划一系列命令,然后一个接一个地运行它们,这一过程称为_transaction_.每个事务都被视为不间断且隔离的操作,以确保数据完整性. ...