Skip to content

关于for-editor的扩展开发及markedjs语法扩展的心得

🕒 Published at:

关于for-editor

开始接触for-editor是因为想自己写一个基于Git的支持markdown的笔记本PC应用,常用开发框架是React、在PC端的开发工具的选择上选择了吃内存狂魔electron,就这样抱着能不造轮子就不造轮子的原则开始使用了for-editor(虽然之后没有打算继续开发)。for-editor如果对于语法的支持没有太高的要求的话,是一个非常优秀、简洁的编辑器组件。支持Tex渲染插入、mermaid流程图的支持、高级markdown或者扩展的markdown语法,随着需求的提升就准备了开始自己扩展语法之路,当然其中发现了很多需要优化和修改的地方。

开发之路

通读源码

源码可以从for-editor中查看。

首先,阅读package.jsonpackage.json是所有React组件开发必不可少的环节,在此之前还是需要足够了解工程的README.md的。在package.json里,源工程对于markdown解析所用的引擎为marked.jshighlight.js。在之前了解markdown --> HTML的渲染学习的时候所用的是marked.js,好处是足够简洁,坏处是语法很少并且扩展要求并不低。

其次,对于源码结构的解读。dist为生成产物,doc为相关文档,example为演示文档,src为源码目录,webpack为配置项。在src下有componentslibindex.tsxcomponents为工具栏的组件,lib是开发的依赖和功能源码,index.tsx为整体的页面结构。

发现的问题

这里提到的问题其实有一些是还没有完全修复的,有些问题真的存在了很多年了,至今我也没能想到比较好的解决办法。

响应式布局

在现代web开发中,响应式布局对于用户体验来说是非常好的,但是在访问速度上相比加载速度会稍微慢一些。针对于高标准的用户体验,我选择了牺牲掉一些访问速度,当然对于纯的通过CSS实现响应式布局在某些时候根本达不到好的效果,是需要JavaScript来加buff的,这也为之后的开发其实还挖了一个坑,对此我不得不做出妥协,完全的响应式目前看来是做不到的。在下面,我也会阐述具体的坑到底是什么。

响应式布局方面我做出的优化是针对900px这个标准进行的分割,随着工具栏功能的拓展,原本工具栏的布局会溢出。因此对于没有二级菜单的工具栏button,我选择写在了more里,并且另开了for-mobilefor-pc,使用了flex布局来处理宽度不够换行处理的优化措施。在下一个坑没有遇到之前,这个方案我觉得解决的还算不错。(当然开了新得分支,还是被提了issue,主要是他遇到的浏览器尺寸在我测试的时候真的没有发现任何问题,很迷惑)

mermaid的引入

这是我几个月都没有解决的问题,可能是打开方式不太对吧。。。参考了mermaidAPI和与mermaid开发者交流提了issue但是始终没有解决。难道mermaid真的是只能用已存在DOM节点来做渲染的吗?希望能有大佬带我深入了解一下。mermaid的渲染要写在主组件的生命周期里面,但是就我刚刚说的,如果已知存在,在什么时候插进去来触发渲染?最后我选择了原样插入,然后再触发渲染的方式,当我满心欢喜觉得一切都能如愿的时候。我发现真的能不能渲染出来都是薛定谔的猫:(然后不得不放弃,想想也真的可能跟后面那个深坑有关系,总有办法能解决这个问题,然后参考过CSDN的HTML代码,也就是我后来的考虑的深坑。对此,我移除了mermaid的渲染支持。

修复js对于二级菜单的控制

感谢@ivanandonov的issue,之前我并不太觉得点击关掉二级菜单很重要,其实也就是添加个关掉二级菜单的事件,问题不大。

拓展marked.js的语法

这是本部分最核心的内容了。如何去扩展marked.js的语法???我看到网上有很多小伙伴尝试去扩展语法,但是也有不少选择了放弃。

如果你想尝试去扩展语法,熟读marked.js使用指南marked.js源码和实现逻辑,当然还需要写正则表达式:)

marked.js最牛逼的部分就在于正则表达式,就不重复匹配的问题,如何去用正则表达式去表示?因此重写renderer的时候我参考了很多源码部分的内容,这里就不针对marked.js详细展开介绍了,我只写一写我到底做了哪些事。

  • 抽离highlight.js。在原项目发现的问题之一就是——我如何去让使用者自行决定高亮的代码类型?我总不能把所有的语言都注册一遍吧?!不但增加了代码量,还需求不是很大,所以我选择了把highlight.js依赖给移除掉。让使用者传入Hljs.highlightAuto这个函数,其他的自己引入highlight.js然后自己注册就完事了。
  • 引入emojiTexdiff语法、mark做行内高亮。这就涉及了不少的正则表达式,尤其是在mark这个渲染上,你总不能把每一句都拿正则循环跑吧,根本不实际。所以得先把高亮块抽离出来然后再排回去,并且不能涉及需要使用的非特殊字符。感兴趣的小伙伴可以参考marked.ts这个部分的源码。对于类似```这种就是对于code块的解析,如果是行内嵌入或者自定义渲染块呢就在paragraph的部分进行重写renderer就好了,但是记得一定要看源码对应的html的标签。

扩展渲染的锚点和大纲

渲染的锚点就是重写heading部分的HTML,不赘述了就是添加一个a标签就能解决的事情。大纲、目录、TOC,一个东西需要用到marked.js提供的解析器lexer,提取heading部分和深度,然后来写样式部分。

深坑——如何去调整textarea的高度

这一切的问题还是来源于,当我使用分栏的功能。我发现记行号并不正确,并且textarea的部分并不能很好的解决高度问题,因为高度不足以显示全部的内容。为什么overflow: hidden其实我在不断优化这个问题的时候也能体会到,因为外部的滚动条需要与行号对齐。

  • 修改一:换掉计算行号的方式。源代码的计算方式是通过计算\n来实现的,貌似vscode的markdown也是这么实现的,但是就优化而言textarea对于行号样式能有vscode这样的调整我是没有发现能有什么办法可以做到的。因此我也考虑了很多办法去优化这个问题,甚至重构编辑器。在目前2.x.x的版本中,我已经换成了根据textarea的高度来计算行号了。
  • 修改二:自适应调整textarea的高度。认真地说,这个坑都坑了多少年了,不知道坑过多少人,知乎还有很多的讨论。其实改起来也不算难,就是把height: auto,然后动态调整高度等于scrollHeight。然后我选择了把计算行号在重新计算高度的函数中进行了调用。看起来,everything is ok了是吧?:)惊喜的是当分栏激活的时候scrollHeight并不是会增大,是会减小的,没想到吧。然后我只能选择目前来说我能提供的最优的解决方案,仅当分栏关掉的时候计算高度,然后通过修改值就可以做到精确计算。原作者还提供了fontSize这个可选项,为此我去了解了line-heightfont-size之间的关系,具体的源码请参考index.tsxreHeight部分的源码。其实我还考虑过,根据上一时刻的面积除当前时刻的宽度来计算高度的,听起来是真的很美好。实际情况是,当你切换过快的时候,读取到的面积并不准确,因此这个方法就是理想很丰满,现实很骨感的问题。
  • 为什么不考虑去用可编辑div重写?下次一定,我是准备在3.x.x的版本重构的,但是我目前没有这么多时间去考虑这个问题了,是真的很麻烦。

心得感悟

小小的富文本编辑器竟然如此复杂,我突然发现我是想慢慢尝试去实现word的最基础的功能。在拓展for-editor的过程中我学到了很多根据教程根本是不可能学到的实际开发问题,为此我付出了很长的事件去研究源码,当然也去开始适应TypeScript来做开发。使用开源项目不是减少工作量,很大程度上其实增加了不少学习成本。

如果喜欢for-editor-herb我这个分支呢,请给原项目一个star。