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…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...
