重学编辑器

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 重学编辑器

🙋🏻‍♀️ 编者按:本文作者是蚂蚁集团前端工程师天授,读完本文可以了解:编辑器的技术发展历史、重点模块的技术方案。

  一、引言

编辑器是人机交互最复杂的场景之一,核心需求是 WYSIWYG,所见即所得。

核心痛点也一直很明确:WYSIWYG 的衍生需求越来越复杂、实现难度越来越大:

WYSIWYG

生产实现

文本输入、查找替换、撤销/恢复、高亮

看似简单,但实际操作表现有很多不确定性

表格、内容嵌套、标注等操作

图表、画板、代码块等内容形式

音、视频等数据格式

浏览器厂商对标准的实现各不相同,带来兼容性问题

多人协同、云编辑服务

对编辑器及其背后的服务、存储架构设计都是考验

跨/多端使用、复杂排版

性能瓶颈

...

...


  二、编辑器技术的发展


从我们可以对着计算机屏幕按键编辑开始,WYSIWYG(所见即所得)就成为了最基本的需求,编辑器也由此诞生。

终端编辑器

在没有 GUI 的时代,各系统的终端就是最早的编辑器。早期需要通过各种终端设备(如 TTY https://en.wikipedia.org/wiki/TTY)和计算机交互;后来操作系统内核直接提供了虚拟控制台作为终端模拟器,接收键盘输入、调用图形接口、把结果渲染到显示器。

GUI 出现后,操作系统上有了终端窗口(也属于终端模拟器),直到现在一直被我们普遍使用。

传统文本/富文本编辑器

GUI 时代文本编辑器窗口软件很快出现了,围绕 WYSIWYG,功能还是以下四方面:文本修改、查找替换、撤销/恢复、内容高亮。同时浏览器上也可以编辑,比如 Input 和 Textarea 实现的文本编辑器。

随着用户对图片、图表排版等需求兴起,富文本编辑器出现了,比如 Word。最早的 Word for DOS (https://winworldpc.com/product/microsoft-word/1x-dos)显示的是内容标识符、还不算 WYSIWYG,但 1985 年 Word for Mac (https://winworldpc.com/product/microsoft-word/1x-mac)已经是 WYSIWYG 的富文本编辑器了。

基于浏览器自带编辑能力的 Web 编辑器

随着主流(几乎是全部)浏览器支持了 contentEditable(https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable),有了编辑富文本的能力,Web 编辑器开始兴起。相比传统的文本图片编辑,Web 编辑器需要考虑互联网格式数据和 HTML 结构的解析。

这也是开源社区最关注的方向,以下是几个有代表性的项目:

CKEditor (2008 至今)(https://github.com/ckeditor/

CKEditor 1-4(CKEditor 3 之前 FCKEditor)的方案是早期经典:

  • 通过打开 contentEditable 让页面内容可编辑
  • 通过原生 execCommand 即浏览器默认行为实现大部分内容编辑
  • 通过自定义拓展 execCommand 自主更新 DOM,实现非浏览器默认的编辑操作
  • 输出是 HTML 字符串直出,因此通过约束嵌套 HTML DTD(https://www.w3.org/TR/html4/sgml/dtd.html) 和内容过滤规则、保证内容的正确性。

CKEditor 5 (https://github.com/ckeditor)相比之前就改头换面了,对 DOM Tree 的数据操作做了抽象,并支持了协同编辑,他在 2020 年发布,其实借鉴了不少后备编辑器的思路。

KissyEditor(2010)(https://github.com/hotoo/kissy-editor

淘宝的经典产品:

  • 在使用方法上基于 Kissy 的模块化机制和组件基础。
  • 编辑器核心其实也借鉴了一部分 CKEditor 的经典方案,封装了 execCommand 的命令系统,定制了 HTML DTD 和数据过滤规则。
  • 对 Select 和 Range 的兼容问题有自主实现。

Quill (2012 至今)(https://github.com/quilljs/quill

Quill 最大的特点是在 View 视图之外,抽象了 Model 模型层的概念:

  • 内容编辑能力上依然靠打开 contentEditable 实现
  • 对 DOM 和数据的修改做了一层抽象。
  • 通过 Delta (类似 JSON) 描述数据操作变化
  • Parchment 和 Blots 描述要渲染的 DOM
  • 浏览器默认支持的编辑,MutationObserver 监听后触发 Delta 变化。
  • 非浏览器默认或复杂的操作,直接更新 Delta。

Quill 的新型设计很受欢迎,由此编辑器们都开始吸收主流框架的架构理念,也开始陆续有了编辑器框架。

ProseMirror (2015 至今)(https://github.com/ProseMirror/prosemirror

ProseMirror 在 Quill 的基础上更进一步,是和 React 很像的思想:

  • 纯 JSON 描述数据内容。
  • 重新定义了数据到 DOM 渲染的 Schema,为了支持任意格式输出。
  • 引入了 Virtual DOM,代理了浏览器默认行为,把编辑操作转成了数据操作。
  • 引入了不可变数据,进而实现了编辑操作的单项数据流。

注意 ProseMirror 借助了 contentEditable,但并没有直接让浏览器处理用户输入。

Draftjs (2015-2020)(https://github.com/facebookarchive/draft-js

Draftjs 来自 Facebook,是一个跟 React 深度结合的开源项目,受众很广。直接将 React 作为视图渲染层。

  • 打开了 contentEditable,但没有直接让浏览器处理用户输入。
  • 定义了数据到渲染规则的 Schema,每一行都是一个 Block。
  • 结合了 React 的 Virtual DOM、不可变数据、状态管理。代理了光标、键盘操作。
  • 插件系统

React 让 Draftjs 的上手难度降低、架构稳定性更好,但这套Schema 在当时带来了很多问题。我们业务中的项目在使用 Draftjs (2019 年左右)的时候也遇到不少坑:

  • 每一行都是一个 Block,Inline 元素也被定义成 Block,无法嵌套。
  • 插入的外部 HTML 如果想解析就得手动 parse 适配这套 Schema,自定义很多 Block Entity。

因为种种原因,Facebook 在 2020 年断更了它另开新坑。

Slatejs (2016 至今)(https://github.com/ianstormtaylor/slate

Slatejs 的架构和分包设计很有特色,他自己的定位是编辑器框架,语雀前期也是基于 Slatejs 开发。Slatejs 一开始吸取了前者们的各种优点,Schema 和数据 Model、不可变数据。它的核心是实现了对编辑状态的 change ,以及 change 的链式调用。

之后一直在迭代优化:

  • 把一系列 Command 封装成 Transform,提供了插件机制。
  • 独立视图层,并融入自己的插件体系,以插件形式提供对各类 UI 框架的支持。
  • 用简洁的 immer 实现不可变数据。
  • 持续优化 Schema ,甚至在新版移除了 Schema,改成通过 normalizeNode 校验,Slatejs 提供了内置约束、支持自定义约束。
  • 支持用 JSX 创建 Slatejs 的数据

BlockSuite (2022 至今)(https://github.com/toeverything/blocksuite

随着 Notion 的横空出世,Block 风格的编辑器成为了新潮流。Block 的思想在之前的编辑器中也出现过(比如 Draftjs),但 Notion 在市场上的成功才真正教育了用户去使用 Block、无所谓 Inline,包括很多老牌笔记也都做了 Block 风格的改版支持,比如思源笔记。

从 Notion API 文档 中可以看出他的设计思想。中文社区里也有尝试分析过 Notion 的实现,探索 Notion 的实现(https://zhuanlan.zhihu.com/p/152964640)、Notion 编辑器是怎么实现的(https://www.yuexun.me/blog/how-the-notion-editor-is-implemented):

  • Block 限制了编辑的可操作范围,其实是将编辑操作的处理简化了。
  • 重点其实在于 Block 如何组织页面,Notion 依然基于 DOM(Flex)去排版。
  • Block 内部的编辑依然依赖于 contentEditable,同样有 Schema,有操作事件的监听和渲染。
  • 由于 Block 的存在,对光标和 select 选区高亮做了独立处理,支持跨 Block 选区。

不过在开源社区,看着让人眼前一亮的项目不多,BlockSuite 是少有的优秀开源框架:

  • BlockSuite 使用了 Yjs 去支持协同编辑,并基于 Yjs 的 Shared Types 拓展了一层 Typed Block Tree,实际是借助了 Yjs 去组织 Block。
  • 在视图模型层面,没有自定义 Schema、Model,也是借助了 Yjs 实现操作状态管理。
  • 在 Block 内部编辑操作被简化了(前面提过),Block 实例之间则互不影响。
  • 渲染层面,不局限于前端框架,甚至不局限于 DOM 实现了 Web Components 的视图渲染,还提供了 基于 Canvas 的渲染器(https://github.com/toeverything/blocksuite/tree/master/packages/phasor) 用于白板绘图。
  • 支持本地数据持久化。

BlockSuite 的设计理念跟前辈编辑器框架们完全不同,也是新一代只是协同工具 AFFiNE (https://affine.pro/)的底层依赖。

脱离浏览器自带编辑能力的 Web 编辑器

即便前面我们提到 Block,即便可能不再直接开放 contentEditable ,但最终还是会依赖 DOM 、依赖 contentEditable。

有些厂商可能会追求交互表现的跨平台系统的一致性、高性能、高拓展性,不想被 contentEditable 的不可靠表现卡脖子。那么另一条路就是完全脱离浏览器的编辑能力,完全自主实现编辑能力。目前看到的几条思路:

  • 不依赖 contentEditable,但依然依赖 DOM 的排版,自主实现选区和光标、拦截输入操作和事件,比如有道云笔记(https://www.cnblogs.com/163yun/p/9210457.html)。
  • 不依赖 contentEditable,依赖 DOM 显示内容,但基于浏览器或系统 API 又自主实现了一层文本布局引擎,包括光标、选区、字体渲染,比如 2010 年 ~ 2021 年的 Google Doc(https://drive.googleblog.com/2010/05/whats-different-about-new-google-docs.html
  • 完全不依赖 DOM,基于 Canvas 实现排版、光标、选区、输入和事件,比如 2021 年后的 Google Doc、腾讯文档。

这难度显然很夸张,最重要的点是排版怎么做。即便不了解完整的排版怎么实现,想想浏览器的回流和重绘就可以一斑窥豹。

另外前面说的 Word,虽然本身就不是 Web 编辑器,但在上个世纪就给出了脱离浏览器 DOM 做排版的方案,理论上完全可以移植到 Web 上来。

思路总结

总结下前述各种方案的思路,依次如下:

  • HTML
  • contentEditable + document.execCommand + HTML
  • contentEditable + Custom Command + Selection/Range API + HTML
  • contentEditable + DataModel + Schema
  • contentEditable + Block Tree + HTML/Canvas
  • Layout Engine + HTML/Canvas

很多人会把他们定义成不同时代或不同级别(L0/L1/L2)的编辑器技术。我理解也不一定完全正确,这几种方式其实并没有明显的代差,你也不能说后期的方案一定比早期更好,很多编辑器融合了不同时代的技术,市场上不同技术的产品也依然存在。

  三、重点技术

下面再梳理下前面提到的一些重点技术和问题。

架构设计

无论各种编辑器怎么更新换代,架构上普遍还是经典的 MVC 模型。

Model

不同框架产品可能会有更细致的划分,总体看是考虑两方面

  • 一是视图渲染 ViewModel,也是我们最常提到的 Model,直接的表现就是 Schema 的设计,关系到数据如何映射到视图的渲染、视图如何被解析成数据。最当然也可以没有 Schema 只做元数据校验,比如新版的 Slatejs。
  • 二是数据存储 DataModel,从而支持文件加载,文件缓存、导出。

这两种 Model 一般可以互相转化,过程就涉及到了数据解析和校验、序列化,比如像 Obsidian 之类本地化的编辑器,数据存储就是 Markdown,加载时解析成 ViewModel,存储时需要校验、序列化。

View

从视图模型渲染到真正可编辑的视图 UI,这一层也就是渲染层。前面提到绝大部分基于 HTML 默认的可编辑能力,借助 contentEditable,也有自主实现光标、选区和排版的,甚至有脱离 DOM 用 canvas 实现 View 层的。

Controller

其实是决定这框架/产品的易用性。这一层通常会拦截各种事件,修改视图模型,触发 View 层的变化。涉及三方面:

  • 事件,如果不脱离 DOM,通常会 MutationObserver 监听浏览器事件,如果基于 Canvas,就需要自己记录鼠标事件,自己做像素检测定位等。
  • 修改视图模型,可以基于 execCommand 做自定义拓展处理,也可以完全自定义一套命令,这个视图 Model 的设计也有关系,修改的记录也让文档有了状态。
  • 触发 View 层变化,如何管理状态,很多框架会做命令封装(比如 Quill 的 Transform),设计事件派发机制(比如 ProseMirror 的 transaction),借助前端框架(比如 React)维护单向数据流并渲染。

contentEditable 和渲染

一致性问题

contentEditable 让浏览器有了编辑富内容的能力,但 contentEditable 带来的重要问题是一致性。这篇经典的 Why ContentEditable is Terrible (https://medium.engineering/why-contenteditable-is-terrible-122d8a40e480)就谈到了这个问题:

  • 由于 HTML 标签本就可以嵌套组合,描述同一个样式就有多种可能:
<strong><em>Baggins</em></strong>
<em><strong>Baggins</strong></em>
<em><strong>Bagg</strong><strong>ins</strong></em>
<em><strong>Bagg</strong></em><strong><em>ins</em></strong>

甚至给 span 标签加上 CSS 样式也可以实现。浏览器里的实现不尽相同,因此不同浏览器里互相编辑的产物就不可控了。

  • 在处理选区时同样也会有问题,一个光标( 用 『|』 表示)看起来在某个文字前面,但具体在那个 HTML 标签处,完全不可知:
|<strong><em>Baggins</em></strong>
<strong>|<em>Baggins</em></strong>
<strong><em>|Baggins</em></strong>

另外比如字体渲染,绝大多数情况下,浏览器都 fallback 到了系统渲染,比如 Mac OS X 使用 CoreText渲染引擎,Windows 一般是 DirectWrite 等,渲染上也会有不一致性。

这些理论上都需要编辑器实现时候处理。

渲染上的选择

另一方面,引用 BlockSuite 作者 doodlewind (https://github.com/doodlewind)的表述(https://www.zhihu.com/question/366666295/answer/976815981)『在浏览器里,打开了 contentEditable 不等于借助了 contentEditable』

  • 借助了 contentEditable,意味着依赖了浏览器默认的富文本编辑行为,早期编辑器基本都是这套方案,
  • 页面打开了 contentEditable,意味着页面这个区域内具备了进行富文本编辑的能力,但具体如何实现编辑行为,其实是框架或编辑器自己控制的。

早期 CKEditor 是前者,渲染层交给 HTML 自己;Draftjs、Slate.js 就是后者,将 View 渲染层交给了他们依赖或运行时的前端框架(比如 React)。

渲染层交给 HTML,好处就是 contentEditable 只是更改内容,实际渲染性能几乎不会有问题。渲染自行调度或者交给前端框架,比如 React,重渲染、状态刷新有时候反而会带来性能问题,比如不可变数据,本身还是有大量计算,尤其在内容多的时候。

前面提过,Slatejs 就在持续简化自己的 Schema 结构,直到最后移除了 Schema,改成通过 normalizeNode 校验。但这其实也就把难度交给了开发者,如果要粘贴外源的富文本,就需要开发者写各种兼容逻辑。

所以数据驱动、框架调度等方式看似高级,但并非完美的方案,实际还是要看场景。比如语雀后来还是放弃了 Slatejs,改自研,自研的实现是前面提到的方式:contentEditable + Custom Command + Selection/Range API + HTML 在渲染层摆脱了 React。

输入事件

一个 DOM 元素打开了 contentEditable 属性,execCommand 也会作用于光标区,但 execCommand 触发的一些浏览器默认行为会导致交互不可预期。:

  • 选区拖选、光标、按键时的默认行为,前面提过,有不一致性
  • 输入文字时的默认行为,但涉及到输入法时会不可控
  • 复制粘贴时的默认行为,涉及到 HTML 富文本格式时会不可控

如果希望编辑器的行为可控,我们可以想到自主定义 Command 指令集(像早期的 CKEditor)。目标都是让整个操作可控,对输入、复制粘贴等做拦截,阻止浏览器默认行为跳过视图模型直接更新 DOM 的行为。

  • 中文输入的时候:一方面用输入法全选输入,输入法编辑器自己会直接改 DOM;另一方面onBeforeInput 会触发视图模型去更新 DOM,但此时 DOM 结构可能已经被输入法改变。导致视图模型和实际 DOM 对不上。
  • 解法二:在解法一基础上借助 compositionstarthttps://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event)/compositionend (https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event)事件,compositionstart 会在输入法编辑器时触发,选词结束时触发 compositionend;因此可以在触发 compositionstart 时,备份选区的 DOM,当 compositionend 时恢复选区的 DOM。
  • 解法三:加上 MutationObserver 监听 DOM 变化,一旦出现变动,同步 DOM 的更改到视图模型。这其实就是做了双向数据流,遇到问题会难排查一些。

选区和光标

选区和光标系统也是编辑器的核心。DOM 也提供了相关 API,前面提到的 Selection/Range API。

  • Selection 对象表示选择的选区或插入符号的当前位置
  • Range 对象表示被选中的文档片段
  • ::selection 匹配被选中的部分,修改选区样式

通常是操作 Selection 对象对应的 Range 对象。实际中还有多 Range 的场景,如果有很多自定义 Block,那就还要处理跨 Block 选区等问题。

光标其实就是是一种特殊的选区,选中区域被压缩成一个点。由于 HTML 结构的不确定性,编辑器们经常需要自己处理光标定位(比如同级子元素如何定位、回车换行时光标定位等问题),有的定位方式是绝对定位,有的是相对定位加偏移量。

Canvas

HTML 自己直接渲染相比 React 等框架二次调度会好很多,但在性能要求高的场景下又显得不够好。比如在 Google Doc 看来,HTML渲染过程中的回流、重绘开销太大,他需要接管整个文字处理区域的布局、排版,从字体解析到光栅化都要自己控制,基于 Canvas 实现整个编辑器。

由于没有了天然可编辑区域,前面提到的几方面事情都需要基于 Canvas 自主实现,我们可以简单思考下:

选区和光标

通过记录鼠标事件,获取鼠标选区位置,用绝对定位遮罩实现选区高亮的效果;如果计算出操作位置相同,则插入光标代替遮罩;点击后的选中状态,没了 contentEditable,就需要自己计算坐标对应那个文字,进而意味着要解析字体参数做排版。

排版

显然是天坑,根据图可视化上的做法可以猜测下:在 Canvas 中用 measureText 测量文字宽高,文本数据可能是发送到 Web Worker, Worker 中根据可视宽高、排版规则,分配每个页面的字符,计算结果返回主线程,主线程计算每个字符的具体位置。

输入事件

首先输入法编辑器的相关事件捕获不到了,如果不使用 input、 contentEditable 这些 designMode (https://developer.mozilla.org/en-US/docs/Web/API/Document/designMode)元素,就没有办法输入中文。Google Doc 的方法是在光标后面藏一个隐藏的 input 和 contentEditable 元素,不参与排版展示,仅用来触发输入法编辑器的输入事件。其他操作,还得结合点击定位实现一个事件系统。

图形拾取

没有了 DOM 的天然标签区分,Canvas 中文字、线条、表格、图片如何区分识别,就需要持续优化比如包围盒的计算等。

局部渲染

在一整块 Canvas 中如果只修改一小段文字,显然不能全量重渲染,所以需要判断用户操作的影响范围,计算出需要局部渲染的包围盒,局部刷新。

依然想提一下,Word 在上个世纪就给出了脱离浏览器 DOM 做排版的方案,理论上完全可以移植到 Web 上来。

Block

Block Style 的编辑器把每个元素块当作独立的 Block,Block 内部用 contentEditable 的 DOM 元素来实现接收用户的编辑输入。这种做法的好处是,编辑可操作、可能出问题的范围都变得更加可控,从而降低了整个编辑器实现的复杂度。

而缺点可能是 inline image 这种看起来应该天然支持的需求不好实现。但 Notion 们通过优秀的产品体验,成功教育了用户别去用 Inline 图片,大家都接受了 Block Style 模块化的编辑器。

前面提过 Draftjs 其实很早就引入了 Block 思想,每行都是 Block,各种元素都是 Block、都做成 entity,开发者可以拓展,但也让编辑器内容的开发变得复杂。Notion 也许参考了 Draftjs 的实现,但将 Block 简化了,要么是纯富文本的 contentEditable 类型的 Block,要么就是完全不同的其他类型(完全自定义编辑行为或直接不可编辑)。

Block Style 的编辑器,核心已经不在是处理内容编辑,而是构造一个类富文本文档的视图让用户可交互,重点在于处理 Block 和 Block 之间的关系。比如 Notion 是有自己定义的 Schema 结构 ,布局排版依赖浏览器的 Flex 布局。而 BlockSuite 则是借助了 Yjs 去管理 Block 的关系。

Yjs 本身提供了 YText、YMap 等常用数据类型 Shared Types,可以作为 Model 层,拓展 Shared Types 就可以适配各种 Model,因此已经有了不少 Yjs 对编辑器的 bindings。感兴趣可以看 Yjs 的文档。

协同

现代编辑器大多都会实现多人实时编辑能力,复杂度和成本很高。两种基础方案:OT 和 CRDT

  • OT (Operational transformation)操作转化;包括操作和转换两步,适合用于纯文本。OT 操作必须通过服务器的转换才可以合并。
  • CRDT (Conflict-free replicated data type) 无冲突可复制数据类型;CRDT 由于其数据结构特性,不通过服务器也可以合并,在分布式系统的最终一致性上也有应用。

设计上有 Model 层、有 Schema 的编辑器其实很容易和 OT 结合,Model 层描述一系列原子化操作,OT 负责把不同用户的原子化操作合并。

而前面提到 BlockSuite 使用的 Yjs 也是一个前端 CRDT 基础库。

具体算法实现不是本文的重点,重点在于编辑器 Client 是负责产出动作指令、动作效果合并展现,服务层 Server 即便 CRDT 不做合并也需要广播分发,所以也涉及到整个后端数据存储架构、甚至网络层的设计。

所以协同编辑领域还有非常多的事情要做。

  四、展望

本文大致梳理了编辑器的技术发展历史、重点模块的技术方案。大方向上,编辑器技术的几种实现思路、核心技术难点都是比较明确的,上面提到的每种方案都还有很多坑要填,需要持续探索。

最大变数可能是支持多模态的 GPT 带来的交互革命。如果自然语言能够成为一种普及的交互模式,那么编辑器甚至是所有 GUI 软件的价值都会变化,也许所有的编辑器都只需要一个文本对话框。不过这是另一个话题了,也不知道这一天什么时候会到来。

抛开 GPT 潜在的交互革命,立足现在面向未来的编辑器,可能更多会专注于特定领域,针对不同的领域场景做技术优化。抛砖引玉一下:

  • 代码编辑器,前面本文没怎么提,像 MonacEditor、CodeMirror 都是社区里的长青项目,包括 WebIDE 之类的产品,关注重点一是响应速度,优化智能提示,优化 LSP,关心的是 Editor 如何更快的和 LS 交互;关注重点二是生态建设、如何提供开箱即用的体验。
  • 图形编辑器,像 Figma,技术上目标是突破浏览器的瓶颈、尝试通过 WASM、Skia (比如 CanvasKit)优化渲染速度,功能上会更关心诸如无限画布、图形绘制,以及对整个设计生态的支持。
  • 通用文档编辑器,比如语雀通过 Canvas 实现表格编辑器、通过 SVG 实现思维导图编辑器,关键词是灵活、合适,把合适的技术用在合适的场景上,让整个产品足够灵活,讲究的是场景覆盖。
  • 报告等专业编辑器,这类容易跟垂直行业挂钩的编辑器,既要深入到具体业务,又要避免定制、考虑通用的问题,我理解重点还是提高编辑效率、数据校验核准。

  引用

  1. https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable
  2. https://github.com/ckeditor/
  3. https://github.com/hotoo/kissy-editor
  4. https://github.com/quilljs/quill
  5. https://github.com/ProseMirror/prosemirror
  6. https://github.com/facebookarchive/draft-js
  7. https://github.com/ianstormtaylor/slate
  8. https://github.com/toeverything/blocksuite
  9. https://developers.notion.com/reference/block
  10. https://zhuanlan.zhihu.com/p/152964640
  11. https://www.zhihu.com/question/366666295/answer/976815981
  12. https://www.yuexun.me/blog/how-the-notion-editor-is-implemented
  13. https://block-suite.com/introduction.html
  14. https://www.cnblogs.com/163yun/p/9210457.html
  15. https://drive.googleblog.com/2010/05/whats-different-about-new-google-docs.html
  16. https://medium.engineering/why-contenteditable-is-terrible-122d8a40e480
  17. https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event
  18. https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event
  19. https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event
  20. https://developer.mozilla.org/en-US/docs/Web/API/Document/designMode
  21. https://docs.yjs.dev/
相关文章
|
1月前
Threejs用官方提供的编辑器做一个简单的模型
这篇文章介绍了如何使用Three.js内置的编辑器来创建和编辑简单的3D模型,并提供了相应的操作指南。
147 0
MakeDown编辑器怎么使用
MakeDown编辑器怎么使用
86 0
CocosCreator3.8研究笔记(二十三)CocosCreator 动画系统-动画编辑器相关功能面板说明
CocosCreator3.8研究笔记(二十三)CocosCreator 动画系统-动画编辑器相关功能面板说明
196 0
|
前端开发 JavaScript IDE
封装库/工具库中重要概念之编辑器
在前端开发中,编辑器(Code Editor)是一项非常重要的工具,它可以帮助我们更加高效地编写和编辑代码。虽然市面上已经有了许多强大的编辑器,但是使用封装库/工具库可以帮助我们更加方便地集成编辑器到我们的项目中
125 0
|
编解码 前端开发 数据可视化
Unity面试题——Unity编辑器基础
Unity面试题——Unity编辑器基础
434 0
|
数据可视化 API 图形学
Unity开发者必备的编辑器技巧
我是一名Unity开发爱好者,自己总结了一些Unity编辑器技巧
701 0
|
Linux 程序员 开发工具
程序员最喜欢的4个编辑器
程序员对于编程的工具存在很大的差异性,而且一般的程序员一旦选择了一种编程器基本上就不再选择别的编辑器,这算是程序员独有的一种特性,一旦喜欢就很难选择放弃,现在就程序员最喜欢5种编辑器在做个介绍,选择什么样的编辑器还和所从事的行业息息相关。
6876 0