CKEditor5学习开发之路
条评论在开发富文本编辑器的过程中,遇到了很多问题,这里做下记录与分享。
说明:本文中涉及的代码较少,也并非单纯的开发步骤,而是整理的一些思路和问题,如果你是想一步一参考的开发一个Demo,本文并不适合你。
一、背景
公司的一款产品中要加入报告功能,简单来讲,就是通过Web浏览器定义word模板、定时任务,服务端根据模板、任务定时生成周报、月报的功能。对于服务端的定时任务、生成周报月报的功能实现,本文不做阐述。本文只介绍Web端的word模板定义的实现思路及遇到的一些问题,对于该功能,除了富文本编辑器基本功能之外,还需支持插入占位符(用于服务端生成word时替换为实际内容)、图表。
二、技术选型
接到需求之后,首先想到的一些成熟的产品,像有道云笔记、腾讯文档、石墨文档、语雀、我来(没使用过,形式好像不太一样)等都有类似功能,但具体是如何实现的呢,不得而知。由于本人之前使用过有道云,所以就想查查有没有相关文章、代码可以参考,没想到还真有。
这是有道技术团队分享的架构设计,具有极高的参考价值。看完这两篇文章,本人甚至都有自己开发实现一把的冲动了。奈何考虑到只有我一个人在做这个事情,而且文章虽好,但没有技术细节,实现难度较大,最终只能作罢,只好沿着富文本编辑器的思路继续调研。
网上关于富文本编辑器的推荐着实不少,推荐几款好用的富文本编辑器、推荐10款常用的富文本编辑器,经过综合对比,最终选定了CKEditor。之所以选CKEditor,一是因为它适配了Angular框架,提供了依赖包@ckeditor/ckeditor5-angular
(公司技术栈就是Angular),二是支持从 Word、Excel 和 Google Docs 粘贴。
三、CKEditor的使用与踩坑
对于CKEditor的介绍,这里就不再赘述了,官方文档介绍的更为详细,但目前还没找到中文文档。
不过需要说明的是,能够提前阅读到上述有道技术团队分享的架构设计,也算是比较幸运的,这对于理解CKEditor的架构设计及后面的插件开发,是相当有帮助的。
1、Document Editor
CKEditor支持在5种模式下使用:Classic、Balloon、Balloon Block、Inline、Document,而Document恰恰是我们需要的。按照其文档介绍,很容易就能构建出一个Demo。
2、添加Page break插件
由于Document模式默认是不带分页功能的,而Pagination又是收费插件,我们只能退而求其次使用Page break。而当我们按照文档尝试安装并引入该插件时,程序报错了。
core.js:7744 ERROR Error: Uncaught (in promise): CKEditorError: ckeditor-duplicated-modules
对于这个错误,官网也有详细说明,大概意思就是不能在已经构建的包@ckeditor/ckeditor5-build-decoupled-document
中再导入新的插件。
如果只是使用CKEditor提供的现有插件,则可以使用Online Builder来解决上述问题。但正如前文提到的,我们的需求还包括支持插入占位符、图表,因此我们还需继续研究CKEditor如何扩展一个新的插件。
3、从源代码构建编辑器
正如上文提到,当我们想要扩展一个新插件时,就不能使用CKEditor提供的已经构建好的包了[CKEditor提供了5种已构建好的包,对应上述提到的5种使用模式]
,而是需要基于源码进行开发构建。可以按照其文档一步步搭建开发环境、开发、构建,而另一种相对比较简单的方式则是直接下载源码,进入packages/ckeditor5-build-decoupled-document目录,直接安装依赖、开发、构建。
其实我一开始的想法是,在一个Angular工程中安装@ckeditor/ckeditor5-angular
,然后开发新插件,最后将这个Angular工程打包成一个依赖包提供给产线各部门使用(我们部门的职责之一就是为各部门提供开发套件)。但是在打包代码时程序报错了,具体什么错误记不清了,由于时间关系没有继续研究。
TODO: 后面有时间了还得再回来看看。
四、插件开发
好了,准备工作已完成,下面可以进入代码开发阶段了。关于插件开发的入门,可参考这里。
1、图片上传
我们要开发的第一个插件是图片上传插件,确切的说是扩展一个自定义的图片上传适配器。CKEditor默认提供了图片插入功能,但图片并没有提交至服务端,这部分逻辑需要自行实现,按照文档一步步开发即可,没有什么需要特别注意的。
2、占位符
占位符插件也比较简单,直接基于文档中的一个实例改造即可。
3、插入图表
前两个插件的开发,基本上只要是理解了CKEditor的架构设计,并且按照文档的步骤,一般是不会有太多问题的。但是对于插入图表的功能,完全找不到参考,无奈,只能一边梳理思路一边研究官方文档了。
思考一
对于图表,我们测试使用的是ECharts。我们知道如果想要实例化一个图表,也即调用它的初始化方法echarts.init(dom?: HTMLDivElement|HTMLCanvasElement)
,需要一个Dom元素作为参数,那么我们需要研究的就是在插件中如何能够获取到新创建的Dom了。
通过文档中的 Using a React component in a block widget 这个教程,我们发现其在渲染 React component 时,就是获取到插件中新创建的Dom进而渲染组件的。
那么这个问题便迎刃而解了。通过这个问题,需要再次告诫自己,对于这种比较成熟的产品、开发套件、开源库等,一手文档一定是其官方文档,哪怕是英文的,也要仔细研读,不要图省事去网上搜罗别人的博客、技术分享。
刚开始的时候,我是各种搜、各种查,但始终没有找到如何获取新创建的Dom的方案,最后还是通过官方文档找到了答案。思考二
图表插入之后,如何确定当前选中的是图表元素,进而进行后续的业务操作?
相关 API 主要涉及 setCustomProperty、getCustomProperty、getSelectedElement、isWidget,具体含义及用法还是查阅官方文档吧。思考三
图表插入之后,如何调整图表尺寸?
开始以为这个问题应该很好解决,因为图片在插入之后,是能够直接拖拽调整大小的,我们只需参考图片插件扩展一个图表 resize 插件即可。但当我们查看图片插件源码时,发现代码量较大,没学习到的 API 也较多,遂暂时放弃了拖拽调整图表大小的想法。
那能否通过设定固定尺寸调整图表大小呢?答案是肯定的。这里主要是参考了 @emagtechlabs/ckeditor5-classic-image-resize 的代码实现。思考四
实例化多个图表之后,如何区分?
这里主要是在定义图表插件的 schema 时带入一个 Id 即可,将来不论是图表的数据填充还是保存模板到服务端,都将通过 Id 来标识唯一的图表实例。
五、源码及Demo
https://github.com/DoAutumn/CKEditor5
https://doautumn.github.io/CKEditor5/