开发一个markdown编辑器
前言:心血来潮,突然想写一个markdown编辑器,于是就有了这篇文章,本文为边开发边写,因此会显得较为杂乱,待后期会做一个整理 ==文章暂时在飞书文档中进行更新,待完成后再更新到此处== [markdown编辑器][https://ryykhmjhfc.feishu.cn/docx/HfGBd9I8EoJdbpxXnjQc9hsun1f]
对于这个编辑器的大致思路,我有两个设想
方案一
- 准备两个div,一个作为显示的编辑区,一个作为隐藏的中间件,或者用一个变量存起来也行,编辑区用来显示渲染后的内容,中间件用来保存原本的形式,如一级标题,在编辑区中显示的就是h1标签,在中间件中就是# 一级标题
- 当在编辑区进入输出时,阻止它的输入行为,将输入行为插入到中间件中,再通过中间件重新渲染到编辑区中
方案二
- 准备一个div作为编辑区,所有操作将在该编辑区直接进行
- 特殊按键的处理,如在输入标题、有序列表、无序列表这些时,都有一个空格,那么在输入空格的时候,进行一个处理,如果判断本行为一级标题,就将本行的标签改成h1,样式也改为一级标题的样式
在这两种方式中,我认为方式一是更简单的,他相当于将整段文本进行处理,而他唯一的难点在于如何通过编辑区的光标位置,定位到中间件中,举一个简单的例子,当我们的光标处于一个一级标题中,效果如下
一级标题
当光标位于一后面时,得到的光标位置应该是在本行中的1,但我们直接使用这个1来对中间件进行定位的话,显然是不对的,因为在中间件中,他的原文本应该# 一级标题,那么他的光标位置应该为3 这时我们会想,根据不同的标签,进行不同的加法布局可以了吗?如一级标题就是+2,三级标题就是+4 是的,在处理标题,列表这些时,处理方式还是较为简单的,但如果处理的是复杂一点的呢?且不说我认为最复杂的表格,当我们处理的是一些行内元素时,比如下面这个 这里是一段行内
元素 当我们的光标位于行字后面时,我们想要得到的位置应该是7,但实际上我们获取到的会是1,这是因为在通过selection.getRangeAt(0)
所获取到的位置,是基于当前标签的位置,在这个行内标签中,我们想将样式显示出来,就必须用一个span标签或其他标签将他包裹起来,然后再添加样式,这就导致我们获取到的会是一个span标签,而不是这一整行的p标签或其他标签,因此得到的位置,也会是错误的,当然这种情况在方案二中也会遇到 并且,每一次输入都需要将整段文本进行重新渲染,然后在重新定位光标位置,在性能上也会有着明显的劣势 方案一的另一个问题就是语法上的冲突,比如我想生成一个代码块,那么就要输入三个,但是当我们输入到第二个
的时候,由于是整篇文章一起渲染的原因,就会直接渲染成行内代码了 说完方案一,说一说方案二 方案二是一个实时渲染,只对当前标签进行处理的方案,但显然为每一个按键都添加处理的话,也是不合适的,因此我们可以只针对一些特殊的按键进行处理,如空格、回车等等,但也因为这个原因,方案二需要处理的情况将会更多 总的来说,方案一需要处理的情况更少,唯一需要解决的问题就是定位的问题,而这个问题我认为还是比较头疼的,并且使用方案一的话,每一次输入都要对整篇文章进行重新渲染,在性能上劣势也更明显,因此我选择了方案二
具体实现方法
- 准备一个div作为编辑区
- 禁止文本拖拽,contenteditable="true"的标签,其内的内容是可以拖拽的,需要禁止掉
- 特殊键处理
- 空格
- 一 ~ 六级标题、有序列表、无序列表,当前光标位置为语义符+1,语义符为[#, ##, ###, ####, #####, ######, *, -, +, (index).]
- 回车
- 当前行为1 ~ 6级标题、普通文本
- 当前光标位于本行开头
- 向前插入行
- 当前光标位于本行末尾
- 直接换行
- 当前光标位于本行中间
- 当前光标位于行内元素内
- 将本行的所有行内元素,转换为普通文本,重新定位光标,拆分文本并换行
- 当前光标位于行内元素外
- 拆分文本并换行
- 当前行为有序列表
- 换行,且新行自动设为有序列表,仍遵循上面的换行规则
- 当前行为无序列表
- 换行,且新行自动设为无序列表,仍遵循上面的换行规则
- 当前行为引用文本
- 换行,且新行自动设为引用文本,仍遵循上面的换行规则
- 在包含缩进的有序列表、无序列表、引用文本中的特殊处理
- crtl + 回车
- 直接换行,不管光标位于何处
- 退格与删除
- 按退格键时,若文本已为空,至少保留一行普通文本标签
- 按退格键时,若光标处于本行开头且上一行不为空,将本行的内容插入到上一行并将本行的格式处理为上一行的格式
- 按删除键时,若光标处于本行末尾且下一行不为空,将下一行的内容插入到本行并将下一行的格式处理为本行的格式
- 退格键在包含缩进的有序列表、无序列表、引用文本中的特殊处理
- 方向键右和下的处理
- 按下方向键右,且光标处于文末时,添加一行
- 按下方向键下,且光标处于最后一行,添加一行
- 行内元素的处理
- 光标位于行内元素时,将语义符暂时显示出来,此时可对语义符进行修改,光标离开后重新隐藏
- 行内元素的渲染较为特殊,比如当我想输入一个粗体,则应该输入粗体,但输入习惯因人而异,我可能先输入两个*,然后输入文本,最后再输入两个*,也有可能输入四个*,然后回到第二个*后面,再输入文本,这样看来就需要对每一个按键都进行事件处理,显然这不是我想要的目前想到的处理方案为
- 按下回车时,判断本行是否拥有行内元素,进行渲染
- 光标移动时,即键盘中的上下左右四个按键,以及鼠标点击时,判断整篇文章是否拥有行内元素,进行渲染
- 特殊按键,包括[^, ~, `, *, ], ), =],按下这些按键时判断本行是否拥有行内元素,进行渲染
- 超文本的修改处理
- 光标回到图片、超链接时,要将图片、超链接的原始文本显示出来,离开时重新隐藏
- 光标回到代码块中时,显示可替换代码类型
- 光标回到表格中时,显示可替换表格的对齐方式
- 自动补全,需自动补全的符号包括[', ", (, [, {]
- 自动缩进,在含有缩进的有序列表和无序列表中,按下回车需要保存原来的缩进
- 粘贴处理,需要将粘贴内容处理为对应的语法的纯文本形式再粘贴,如复制粘贴一张图片,要变成![加载失败提示文本][图片链接 "鼠标悬浮提示文本"]
- 快捷键处理,如ctrl + 1为设置/取消一级标题
- 接管上下左右按键、回车、删除、退格,以及最头疼的鼠标点击,如在无序列表中,因为是ul包含li,按方向键有可能会将光标从li移动到非编辑区的ul中
- 接管tab
- 在有序列表、无序列表、引用文本中,为创建节点缩进
- 在表格中,为切换单元格
- 其他情况为添加一个制表符
- 接管撤销(ctrl + z)与恢复(ctrl + y)
- 渲染行内元素冲突的问题,如斜体与粗体,斜体,粗体,判断是否为连续的*,中间没有其他内容,如果是,则不渲染为行内元素
猪脑过载,先写这么多=。=
块级元素
效果 | 语法 |
---|---|
一级标题 | # 一级标题 |
二级标题 | ## 二级标题 |
三级标题 | ### 三级标题 |
四级标题 | #### 四级标题 |
五级标题 | ##### 五级标题 |
六级标题 | ###### 六级标题 |
无序列表 | -/+/* 无序列表 |
有序列表 | 数字. 有序列表 |
引用 | > 引用文本 |
代码块 | 代码类型\n代码片段\n |
表格 |
行内元素
效果 | 语法 |
---|---|
图片 | ![加载失败提示文本][图片链接 "鼠标悬浮提示文本"];后中括号可用括号代替 |
链接 | [显示文本][链接];后中括号可用括号代替 |
引用 | |
上标 | ^上标^ |
下标 | ~下标~ |
行内代码 | 行内代码 |
高亮 | ==高亮== |
斜体 | 斜体 |
粗体 | 粗体 |
粗斜体 | 粗斜体 |