h是什么意思 富文本辑器_主流的开源「富文本编辑器」都有什么缺陷?

美团的知识管理系统-学城,其富文本编辑器是基于prosemirror来实现的,我在其中开发了内部团队接入版本及当前正在开发的block化新版本。富文本涉及到的东西实在太多太深,在这个领域仅仅能算初来乍到,有理解的错误和说的不对的,大家多多指正。

prosemirror的schema系统还是非常强大,主要分为marks及nodes两类, 整个文档是由nodes构成的,schemaSpec定义了节点自身类型(inline / block等)及其所能包含的nodes类型(content),marks是对inline content比如(text*)的一种修饰例如加粗、斜体,两者的关系有点像胳膊和胳膊上的纹身。整体构成了文档的骨骼-schema,可以非常灵活的以颗粒度非常精细的方式自由定义文档结构和解析方式。但是pm有一个默认的设定:inline和block在schemaSpec的content中是不能mixed的, 一个典型的例子就是list标签的tab嵌套,这要求li的content是ul这类block级别的schema - list,所以哪怕是纯文本的输入,也必须加上paragraph这类block级别的schema - paragraph。content="(paragraph | list)+"导致文档结构如下:

纯文本的li标签下必须有paragraph标签,这会导致类似列表居中在内的一些布局因为这层paragraph异常。

另外notion的block设计一定程度上可以用prosemirrror来实现,discuss中也是有一些讨论Block structure editor by prosemirror​discuss.prosemirror.net

decoration、nodeview及toDOM

渲染层是富文本编辑器很重要的一层,在prosemirror中非文档内容的渲染由decoration层完成,而文档内容的渲染由toDOM或者nodeview来提供,toDOM适合轻量的展示如blockquote、link等,对于复杂如calendar之类的节点,nodeview的能力就体现出来了,毕竟谁也不喜欢在toDOM里写一大堆的冗余的无法理解的节点关系,可惜的是prosemirror更偏向于原生js开发,并没有针对框架的适配层,所以适合渲染复杂节点的nodeview与react之类的框架需要开发者自行集成,好在atlaskit、remirror及tiptap在这方面有一些工程实践的。 对于Decoration层,widget、inline及node的api能非常便利的处理placeholder等简单渲染,在复杂节点渲染上还是不得不依靠reactDOM.render等方式hack或者以直接以node+特定attribute的方式去代替decoration实现比如上传附件在内的中间态。

另外decoration的contenteditable=false属性还非常容易带来浏览器的各类光标问题,overlap的decoration的互相感知能力不足。decoration的事件机制缺乏,导致在decoration在更多的场景下仅仅能处理非常简单的渲染。

光标系统

麻烦的光标系统。。。prosemirror的indexing系统官网有个说明

是一套基于整型的下标索引,在整个html标签和textcontent层面精细化的索引系统,如果希望获取基于pos位置的详细信息,通过Node.resolve能获取到该pos下的详细信息。由于富文本编辑的过程就是在文本的什么位置做了什么操作,所以编辑过程中所涉及到的插入,拖入,拖拽,拖出,光标选区,复制,粘贴,macro等都需要pos的信息。pos => Node,pos => Dom, pos => resolvedPos, 都具备强大的api,但是Node作为编辑器对文档节点的描述,Node节点实例并不直接包含实例的pos。所以prosemirror mutation API几乎都以pos和dom为参数,posByNode很多时候不得不接触child,forEach,descendants等api转化,如果能支持互相的映射会极大的便利开发。pos <==> Node.

prosemirror中的选区有textSelection,nodeSelection,allSelection,充分的支持了选区range的不同情况。尽管在contentDOM这类边界情况下的选择还有一些奇怪的非预期行为(部分来自浏览器,部分来自prosemirror),总体是完善简单直接的,配合schema中的spec还能丰富节点的各种行为。即使如此,代码中还是不可避免的会有pos + 1, pos - 1等不怎么优雅的代码。咋说呢。。。pos +- n, n > 2就做好注释吧。。

由光标产生另一个问题就是,prosemirror是基于plugin/extension去开发的,各个plugin/extension可以利用很多的工程化能力让他们解耦和可插拔,在cursor这类基础功能插件中,如果真的有很多自定义特定node的边界光标要处理,还是要尽量避免耦合,这可能不是一件很难的事,但是是一件很麻烦的事。

Plugin system

plugin系统极大的丰富和扩展了编辑器的行为和能力。大部分情况下开发者也是以plugin来区分功能边界来开发的。

除了定义鼠标键盘行为以外,每个plugin可以维护自己的state,这有点类似react中state的感觉。对于prosemirror来说,setMeta/getMeta/getState/apply 等API可以方便的修改和获取state,并且是可以跨越plugin去操作的。但是有一个明显的感觉是,react中setState及单向数据流让组件状态的修改和影响范围可控又较易于理解,然而在prosemirror中这种基于链式的插件事件流体系下,配合上view层及nodeView层,使得状态的变更起始点和终结点不直观,对于复杂的插件,比如键盘唤醒的可输入搜索菜单,基于plugin state的维护终究还是略显杂乱。不过prosemirror plugin系统带来了极大的开发和设计自由度,并不完全是缺陷。

剪切/复制/粘贴

复制的之后的走向分为对内粘贴和对外部粘贴两类,由于上述的schema及parseDOM的解析规则,对内粘贴剪切板slice只要携带必要信息就会自动重走pm的解析逻辑,类似二次加载,所以不存在展示和适配问题。但是对外粘贴的兼容(如复制到word,wps等)如果仅仅基于clipboardhtml就很难做到适配和兼容了,比如富文本编辑器中的附件卡片,如果复制到word中下载事件和卡片本身可能就无法被word理解解析了。这就涉及到对剪切板的修改过程让内容同时满足对内对外的支持,prosemirror的这个能力主要来自clipboardSerializer层,形式上等同于schemaSpec的toDOM,这一层可以理解成在复制场景的特殊转移规则,可以想象,一个形态为

attachment filename。 prosemirror的ClipboardSerialilzer API的设计很好的补充丰富了schema。prosemirror在设计上很多的API既可以实现在plugin也可以直接整合至EditorView, 在不同的维度上很方便的支持了场景化的需求。总体来说,prosemirror对剪切/复制等行为的支持程度还是不错的。

实时编辑

基于crdt算法的yjs对prosemirror提供了y-prosemirror库,在prosemirror编辑器中的操作都被映射成yjs的type比如yxml的变化,客户端ydoc的变化在各端同步op,作用于各端的ydoc进而变成prosemirror的transaction。相关的另一个方案就是prosemirror-collab 官方库,prosemirror是一套mvc的模式,对state的修改触发view的渲染,修改state的方式为dispatch transacation,有些类似redux的工作方式。

collab库就是基于transaction的各端同步,区别在于yjs的ydoc接管了文档的形态,基于op/update还原文档的操作历史而不是传统的json或者htmlstring,这使得prosemirror的tojson在yjs方案下的实时编辑编辑器中变得略显鸡肋,也让server(非node)端对文档的掌控力大大削弱,比如清洗文档id,过滤不和谐内容等等变得复杂。

另一方面,基于transaction的方式,编辑器能在操作维度上做到非常细粒度的控制,例如version或者操作历史等方面。在没有实时编辑的版本,我写了一个基于结果(base、 remote、 local)的automerge合并逻辑以替代之前的合并冲突逻辑,但是这种基于结果的历史版本对比仅仅能在内容的差异上完成对比,颗粒度最小也仅仅能在childNodes级别,如果涉及到Node.TEXT_NODE,由于基于结果diff本身的颗粒度限制,在string层面实现单字节这种操作变更展示几乎不太可能,但是如果借助yjs的update或者pm‘transaction,就能在单用户维度上完整展示编辑历史和操作过程。

这种区别本质上是把文档视为某个确定时间点上的状态,还是一个时间轴上的操作集合。基本上,实时编辑在用户体验上无疑更好,但本质更是对非实时文档无法解决的问题的一个解决方案。

编辑器和浏览器的默认行为

hex2rgb、br标签,unicode等问题在富文本编辑过程中也是常见问题,部分来自浏览器默认处理行为,部分来自prosemirror本身的逻辑限制。

能想到的大概是这些,又想到的再补充吧。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值