Slate文档编辑器-Decorator装饰器渲染调度
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
在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].lengthindex++}let offset = start - iteratedlet remaining = lengthwhile (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 - takenif (remaining > 0) {iterated = iterated + currentText.lengthoffset = 0index++}}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(<LeafisLast={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文档编辑器-Decorator装饰器渲染调度 在之前我们聊到了基于文档编辑器的数据结构设计,聊了聊基于slate实现的文档编辑器类型系统,那么当前我们来研究一下slate编辑器中的装饰器实现。装饰器在slate中是非常重要的实现,可以为我们方便地…...
本地Docker部署Flowise并实现远程构建LLM应用程序原型高效开发
文章目录 前言1. Docker安装Flowise2. Ubuntu安装Cpolar3. 配置Flowise公网地址4. 远程访问Flowise5. 固定Cpolar公网地址6. 固定地址访问 前言 相信很多对AI感兴趣的小伙伴都会觉得正在逐渐流行的工作流自动化和AI集成特别酷炫,没错,这些技术像“秘密武…...
多点通信、流式域套接字
一、广播 1.1广播的发送端模型: #include<myhead.h>#define BEN_IP "192.168.191.129" #define BEN_PORT 8888#define PORT 6666int main(int argc, const char *argv[]) {int oldfd socket(AF_INET,SOCK_DGRAM,0);if(oldfd -1){perror("soc…...
vue3使用video-player实现视频播放(可拖动视频窗口、调整大小)
1.安装video-player npm install video.js videojs-player/vue --save在main.js中配置全局引入 // 导入视频播放组件 import VueVideoPlayer from videojs-player/vue import video.js/dist/video-js.cssconst app createApp(App) // 视频播放组件 app.use(VueVideoPlayer)2…...
模块化和面向接口的设计:深入理解和应用
模块化和面向接口的设计:深入理解和应用 在面向对象编程中,模块化 和 面向接口设计 是两种非常重要的编程理念。它们能帮助开发人员构建更加清晰、可维护和易于扩展的系统。接下来,我们将详细解释这两种设计思想,并结合 Python 中…...
《SwiftUI 实现点击按钮播放 MP3 音频》
功能介绍 点击按钮时,应用会播放名为 yinpin.mp3 的音频文件。使用 AVAudioPlayer 来加载和播放音频。 关键点: 按钮触发:点击按钮会调用 playAudio() 播放音频。音频加载:通过 Bundle.main.url(forResource:) 加载音频文件。播…...
微机接口课设——基于Proteus和8086的打地鼠设计(8255、8253、8259)Proteus中Unknown 1-byte opcode / Unknown 2-byte opcode错误
原理图设计 汇编代码 ; I/O 端口地址定义 IOY0 EQU 0600H IOY1 EQU 0640H IOY2 EQU 0680HMY8255_A EQU IOY000H*2 ; 8255 A 口端口地址 MY8255_B EQU IOY001H*2 ; 8255 B 口端口地址 MY8255_C EQU IOY002H*2 ; 8255 C 口端口地址 MY8255_MODE EQU IOY003H*2 ; …...
MySQL如何执行.sql 文件:详细教学指南
在使用MySQL数据库过程中,我们经常需要执行包含SQL语句的.sql文件。这些文件通常用于数据库的备份和恢复或批量执行SQL脚本。本文将详细介绍如何在不同环境下执行MySQL的.sql文件。 前置准备 在开始之前,请确保以下条件已经满足: 已经安装…...
非周期性脑活动的动态重构支持癫痫患者的认知功能:一种神经指纹识别方法
摘要 颞叶癫痫(TLE)的特征是大脑活动模式发生大规模的变化,并且这种变化与患者的认知功能受损密切相关。本研究旨在使用神经指纹方法分析大脑活动的动态重构,以描绘TLE患者的个体特征及其认知功能相关性。本研究收集了68名TLE患者和34名对照组的10min静息…...
ZYNQ初识6(zynq_7010)clock时钟IP核
基于板子的PL端无时钟晶振,需要从PS端借用clock1(50M)晶振 接下去是自定义clock的IP核封装,为后续的simulation可以正常仿真波形,需要注意顶层文件的设置,需要将自定义的IP核对应的.v文件设置为顶层文件&a…...
使用MFC编写一个paddleclas预测软件
目录 写作目的 环境准备 下载编译环境 解压预编译库 准备训练文件 模型文件 图像文件 路径整理 准备预测代码 创建预测应用 新建mfc应用 拷贝文档 配置环境 界面布局 添加回cpp文件 修改函数 报错1解决 报错2未解决 修改infer代码 修改MFCPaddleClasDlg.cp…...
SAP SD BP名称和销售订单描述的对应不起来的问题
问题 VBPA-ADRNR地址 和 KNA1-ADRNR 指向同一个号码 销售订单读取这个地址 改正后恢复正常 原因:推测 应该是创建Y0 电商客户的时候,引起锁和混乱导致的。 具体实际时什么样,不太清楚 写于20241230 浙江台州...
FlastOcc-网络复现-1.环境配置及问题
研究OCC网络 1.RuntimeError: Ninja is required to load C extensions RuntimeError: Ninja is required to load C extensions #32 Ninja is required to load C extensions File “/FlashOCC/projects/mmdet3d_plugin/core/evaluation/ray_metrics.py”, line 12, in dvr …...
Go语言中值接收者和指针接收者的区别?
在 Go 语言中,值接收者和指针接收者是方法定义中的两种接收者类型。它们的主要区别在于方法调用时的行为、接收者是否可以被修改,以及性能上的差异。 值接收者 定义 值接收者的方法接收的是调用对象的一个副本,方法内部对该副本的修改不会影…...
kafka小实站
需要先在前面的文章里面照着下载好kafka,并且启动 先启动zookeeper 项目目录 package kafka; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import…...
基于Python实现车辆检测、机动车检测、识别位置标记、计数
目录 引言背景与应用场景车辆检测的研究意义相关工作车辆检测概述机动车检测方法分类基于传统计算机视觉的检测方法基于深度学习的检测方法技术与方法车辆检测技术概述基于Python的车辆检测方法图像处理与特征提取深度学习方法(如YOLO、SSD、Faster R-CNN等)数据集与标注常用…...
心理学硕士
心理学硕士的主要研究方向包括基础心理学、发展心理学和应用心理学。 基础心理学研究一般的心理现象与规律,如心理的实质及神经机制、感觉与知觉、意识与注意、学习与记忆、思维与语言、情绪与意识、人格等。发展心理学研究人类个体心理发生发展的特点和规律&a…...
python量化分析学习与实践1:API接口篇
业内比较流行的几款API数据接口,有聚宽、TuShare,yfinance,以及pandas的pandas_datareader等。国内的一般都需要用户认证,才能下载数据。国外的yfinance与pandas_datareader等则不需要,但需要科学上网。 聚宽 测试下…...
【GO基础学习】gin的使用
文章目录 模版使用流程参数传递路由分组数据解析和绑定gin中间件 模版使用流程 package mainimport ("net/http""github.com/gin-gonic/gin" )func main() {// 1.创建路由r : gin.Default()// 2.绑定路由规则,执行的函数// gin.Context&#x…...
网卡状态变更,virtio-net检测
实现方案: 现在在amp模式下linux端有个真实的物理网卡eth0,有一个虚拟网卡virtio-net0后端,此时需要一种机制,将真实物理网卡的状态发送rtos的virtio-net0前端。这里使用register_netdevice_notifier机制,每个virtio-n…...
合宙ESP32-C3用PlatformIO调试,别再为USB CDC和DIO配置头疼了
合宙ESP32-C3 PlatformIO调试实战:USB CDC与DIO配置全解析 第一次接触合宙ESP32-C3的开发板时,我被它简洁的设计和亲民的价格所吸引。但当我兴冲冲地打开PlatformIO准备大展拳脚时,却接连遇到了串口无法识别、程序下载失败等一系列问题。经过…...
基于NModbus4的ModbusRTU通信实战:从模拟到开发
1. 环境准备:搭建ModbusRTU开发测试环境 第一次接触ModbusRTU通信时,我花了两天时间才把开发环境搭好。现在回想起来,其实只需要三个关键工具就能快速搭建完整的测试环境:虚拟串口工具、ModbusSlave模拟软件和Visual Studio开发环…...
别再手动配环境了!用vcpkg在Windows上无痛安装osgEarth 3.7(附VS2019+避坑指南)
现代C开发者的效率革命:vcpkg一键部署osgEarth全攻略 在三维地理信息系统(GIS)和可视化领域,osgEarth作为开源地理空间工具包一直备受开发者青睐。然而,其复杂的依赖链和繁琐的编译过程常常让开发者望而却步——从OpenSceneGraph(OSG)基础库到…...
Wan2.2-I2V-A14B快速上手:3步启动WebUI,5分钟生成首条AI视频
Wan2.2-I2V-A14B快速上手:3步启动WebUI,5分钟生成首条AI视频 1. 镜像介绍与环境准备 Wan2.2-I2V-A14B是一款强大的文生视频模型,能够根据文本描述生成高质量视频内容。这个私有部署镜像已经针对RTX 4090D 24GB显卡进行了深度优化࿰…...
LangChain教程-、Langchain基础妨
简介 AI Agent 不仅仅是一个能聊天的机器人(如普通的 ChatGPT),而是一个能够感知环境、进行推理、自主决策并调用工具来完成特定任务的智能系统,更够完成更为复杂的AI场景需求。 AI Agent 功能 根据查阅的资料,agent的…...
pymilvus操作milvus向量数据库笔记(二)
文章目录表结构迁移通过代码迁移内容有点多,拆出来一篇。表结构迁移 导出schema太难看了。 通过代码迁移...
AppleRa1n:轻松绕过iOS 15-16激活锁的图形化工具
AppleRa1n:轻松绕过iOS 15-16激活锁的图形化工具 【免费下载链接】applera1n icloud bypass for ios 15-16 项目地址: https://gitcode.com/gh_mirrors/ap/applera1n 还在为忘记Apple ID密码而无法使用自己的iPhone或iPad而烦恼吗?或者你购买了一…...
WeChatIntercept:Mac微信消息防撤回的本地化解决方案
WeChatIntercept:Mac微信消息防撤回的本地化解决方案 【免费下载链接】WeChatIntercept 微信防撤回插件,一键安装,仅MAC可用,支持v3.7.0微信 项目地址: https://gitcode.com/gh_mirrors/we/WeChatIntercept 核心价值&#…...
让 DDL 源码真正可读:SAP ADT 里 DDL Formatter 的配置逻辑、团队协作方式与项目实践
在很多 ABAP 项目里,开发人员会把注意力集中在语义正确、激活成功、性能可接受这些层面,却容易低估一件事:DDL 源码的可读性,直接影响建模质量、代码审查效率,以及团队协作时的认知成本。CDS 语句一旦开始出现较长的元素列表、复杂的 Boolean expression、多层 JOIN、多个…...
S2-Pro Java项目开发实战:SpringBoot微服务集成AI能力
S2-Pro Java项目开发实战:SpringBoot微服务集成AI能力 1. 企业级Java项目如何拥抱AI能力 最近两年,AI技术在企业应用中的渗透率显著提升。作为Java开发者,我们经常面临这样的需求:如何在现有SpringBoot微服务架构中快速集成AI能…...
