关于for-editor
开始接触for-editor
是因为想自己写一个基于Git的支持markdown的笔记本PC应用,常用开发框架是React、在PC端的开发工具的选择上选择了吃内存狂魔electron,就这样抱着能不造轮子就不造轮子的原则开始使用了for-editor
(虽然之后没有打算继续开发)。for-editor
如果对于语法的支持没有太高的要求的话,是一个非常优秀、简洁的编辑器组件。支持Tex渲染插入、mermaid流程图的支持、高级markdown或者扩展的markdown语法,随着需求的提升就准备了开始自己扩展语法之路,当然其中发现了很多需要优化和修改的地方。
开发之路
通读源码
源码可以从for-editor中查看。
首先,阅读package.json
。package.json
是所有React组件开发必不可少的环节,在此之前还是需要足够了解工程的README.md
的。在package.json
里,源工程对于markdown解析所用的引擎为marked.js
和highlight.js
。在之前了解markdown
--> HTML
的渲染学习的时候所用的是marked.js
,好处是足够简洁,坏处是语法很少并且扩展要求并不低。
其次,对于源码结构的解读。dist
为生成产物,doc
为相关文档,example
为演示文档,src
为源码目录,webpack
为配置项。在src
下有components
、lib
和index.tsx
,components
为工具栏的组件,lib
是开发的依赖和功能源码,index.tsx
为整体的页面结构。
发现的问题
这里提到的问题其实有一些是还没有完全修复的,有些问题真的存在了很多年了,至今我也没能想到比较好的解决办法。
响应式布局
在现代web开发中,响应式布局对于用户体验来说是非常好的,但是在访问速度上相比加载速度会稍微慢一些。针对于高标准的用户体验,我选择了牺牲掉一些访问速度,当然对于纯的通过CSS实现响应式布局在某些时候根本达不到好的效果,是需要JavaScript来加buff的,这也为之后的开发其实还挖了一个坑,对此我不得不做出妥协,完全的响应式目前看来是做不到的。在下面,我也会阐述具体的坑到底是什么。
响应式布局方面我做出的优化是针对900px
这个标准进行的分割,随着工具栏功能的拓展,原本工具栏的布局会溢出。因此对于没有二级菜单的工具栏button,我选择写在了more里,并且另开了for-mobile
,for-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
然后自己注册就完事了。 - 引入
emoji
、Tex
、diff
语法、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-height
和font-size
之间的关系,具体的源码请参考index.tsx
的reHeight
部分的源码。其实我还考虑过,根据上一时刻的面积除当前时刻的宽度来计算高度的,听起来是真的很美好。实际情况是,当你切换过快的时候,读取到的面积并不准确,因此这个方法就是理想很丰满,现实很骨感
的问题。 - 为什么不考虑去用可编辑div重写?下次一定,我是准备在
3.x.x
的版本重构的,但是我目前没有这么多时间去考虑这个问题了,是真的很麻烦。
心得感悟
小小的富文本编辑器竟然如此复杂,我突然发现我是想慢慢尝试去实现word的最基础的功能。在拓展for-editor
的过程中我学到了很多根据教程根本是不可能学到的实际开发问题,为此我付出了很长的事件去研究源码,当然也去开始适应TypeScript来做开发。使用开源项目不是减少工作量,很大程度上其实增加了不少学习成本。
如果喜欢for-editor-herb我这个分支呢,请给原项目一个star。