前言

根据第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