前言
根据第7周修改的文本实现思路,获取到文本的HTML在前端进行,因此需要一个文本编辑器组件完成以下功能。
- 编辑富文本,并获取到其HTML
- 解析Markdown语法并转化为对应富文本样式
- 解析LaTeX语法并转化为HTML
实现过程
富文本编辑
首先考虑如何实现富文本,第一步当然是查询资料,如何从零实现一个富文本编辑器。
在浏览器中,实现富文本编辑的原理大致可分为下面这三种:
- 在 <textarea> 上定位各种样式。这是 Facebook 早期评论系统所使用的。
- 实现自己的布局引擎,连闪烁的光标都是通过<div> 控制的。这是 Google Docs 所使用的。
- 使用浏览器原生的 <ContentEditable> 编辑模式。这是绝大多数现有富文本编辑器所使用的。
由于第一种方法的功能过于有限、第二种方法的工作量极其庞大,想要实现属于自己的富文本编辑器只能从那个第三种方法入手。然而在进一步的查阅资料中我找到了一篇文章:Why ContentEditable is Terrible 。
这篇文章表述了ContentEditable方法的缺陷,其归根结底在于HTML本身标签的自由性,或者说是弱一致性:对于同一个样式,可能有许多种标签的插入的实现方法。
这种可能存在的冗余的标签可能会导致破坏数据结构的恶性bug。而我所需要的使用场景,需要文本HTML在SVG的<foreignObject>标签与编辑器中互相转化,这种可能的bug可能导致显示问题,为了防止潜在的bug,我决定寻找更优解。
富文本编辑框架
通过查阅资料和比较,我选择使用Slate.js框架。和contentEditable方法相比,这个框架将编辑内容作为数据节点,通过更改其状态来修改其样式;同时能够以组件的形式来实现样式,有着良好的拓展性和较低的门槛。
首先简要介绍其原理:通过用户输入事件生成State–可以理解为插入文字或修改样式等操作,然后根据新的State渲染到编辑器,从而更新编辑器内的节点,显示结果。由于实际的HTML内容是根据抽象数据结构渲染得到了,因此可以避免上述的情况,保证一致性。
但是他也存在一定的缺陷,当将HTML内容转化到slate元素时,必须满足特定的规则才能转化到对应的元素。考虑到目标场景虽然数据流发送频繁,但是只需要在编辑器中进行编辑,因此可以忽略这个缺点。
integratedEditor 集成编辑器设计
包含编辑器及其工具栏。
- 用于作为侧边栏显示。
接口
setContent
传入HTML字符串,设置编辑器的内容
- 编辑器内渲染HTML后显示
getContent
获取到标题编辑器的HTML字符串
- 当编辑器文本包含
${}$
形式的字符串时,会根据latex语法将其解析为MathML字符串并替换
editorChange事件
当编辑器修改时,释放editorChange
事件
获取到文本内容
当选中组件时,获取到其内部文本的HTML,调用setContent
方法传入;同时监听编辑器的editorChange
事件,每次事件发生时,调用getContent
接口获取到新的HTML并设置到组件。
latex
- 从编辑器内获取到的公式对应的MathML格式如下
<math data-value='{latex公式}'>
...
</math>
- 能够正常在编辑器中回显的公式对应的HTML格式同理。
使用方法
富文本
参考工具栏
LaTeX公式
- 使用工具栏插入公式。
即时渲染;inline。 - 在行首输入
$
及空格。
非即时渲染;block。 - 在行内使用
${}$
包裹
非即时渲染;inline。
markdown
- 标题
行首
#
,分为五级
- 列表
-
+
*
- 引用
>
- 分割线
---
- 代码块
```
- 公式
$
逻辑
- 创建过程
当创建一个新的文本框时,调用核心接口,新建一个TextBox
对象,并传入文本框原点坐标坐标 - 获取到特定文本框
当选中画布中文本矢量图时,唤醒Text Editor
组件,从而对文本进行修改;修改完成后,根据DOM对象获取到其对应的TextBox
对象 - 渲染
当修改某个文本框内容或样式时,即时更新其HTML,从而实现即时渲染
- 对于LaTeX公式,在修改完成后进行渲染
- 更新
当修改文本框内容或样式完成后,调用核心TextBox
对象接口,传入新的HTML