13 | 编写markdown编辑器时遇到的冷知识

2021-02-04  本文已影响0人  cemcoe

这里是在简书仿简书的第十三篇,早睡早起身体好

Vue3 版本在线预览 https://shuhe.cemcoe.com/

前段时间在搞在简书仿简书,这个问题的核心点在哪呢?或者说核心是什么?

仅就个人主观感受,大部分人夸简书的点一般是简洁的编辑器,黑的点大概就是首页推荐机制了,那,核心是编辑器咯。

在使用 Vue2 写的时候在 markdown 编辑器这块直接选用了一个组件,在使用 Vue3 重写时,不打算用外部组件了,来看一波核心,搞一个 markdown 编辑器。于是就有了下面的冷知识。


先来想一下 markdown 编辑器的功能点,最重要的就是将 markdown 格式渲染成 html 了,简言之,要完成下面的转化。

# => h1
![]() => img
[]() => a
。。。

好办呀,思路是使用正则找特殊标志位比如 # 号,再使用字符串的一些方法转换成 html 格式的字符串。以语句 # 我要转化h1 为例,找到 # 号和后面的文字,使用 h1 标签包裹就得到了 html 格式的字符串了。

看起来好像很容易的样子呢,但事情远没有那么简单,markdown 语法对于 # 的使用是有规定的,在非开头使用是不会渲染成标题标签的。还有 ##### 等格式,单单一个 # 就够头疼的了,更别说各种符号的排列组合了。这个从 markdown 到 html 的转化的工作量还是很大的,而且也不是简单的使用正则找到值再替换的过程。这里面涉及到一些编译原理的知识。老难搞了。

好在这个略显“无聊”的工作已经有人帮我们做了,就像有人搞出来 babel 来帮我们完成 es6 到 es5 的转化,已经有人搞出了 marked 来帮我们完成 markdown 到 html 的转化,当然还有其他的比如 markdownit。

这里就使用 marked 了。其实还是没有触及到核心科技。翻看 marked 的源码可以发现,找字符或者术语一点叫做词法分析阶段确实用到的正则,具体可参考https://github.com/markedjs/marked/blob/master/src/rules.js

好的,第一项完成,现在在 textarea 写 markdown,点击预览调用 marked 方法。

<textarea
        v-model="content"
        name="post"
        id="post"
        placeholder="请输入正文"
      ></textarea>

 <div class="preview" v-show="isPreview">
    <div v-html="previewContent"></div>
 </div>

<script>
import marked from "marked";
state.previewContent = marked(state.content);
</script>

如果要简洁的话,其实这就搞好了。


如果要在移动端使用的话最好加点按钮用于插入符号,毕竟在手机上一些 markdown 符号打起来不是很方便。

这里就涉及到一些冷知识了,插入符号换言之就是字符串拼接,字符串拼接是很常规的操作了,这里的核心是如何找到拼接点。

这里就需要用到一些光标的冷知识了,上图。


Snipaste_2021-02-03_11-05-56.png
[post.selectionStart, post.selectionEnd]

通过上面的图大概就可以明白这两个属性的意思了。那么插入的逻辑就好搞了。

找到光标的位置接下来就好办了,甭管你用什么法子,把字符串从光标位置劈开往里面插入符号。

let start = dom.selectionStart
let end = dom.selectionEnd
dom.value = dom.value.substring(0, start) + string + dom.value.substring(end, dom.value.length)

看起来完成了需求,诶,别急,当你点击按钮插入符号后,你会发现 textarea 中光标没有了,此时如果你再次点击插入操作会有什么现象呢?它会插到最前面。

demo.gif

光标消失的原因吧,其实很简单,就是本来 textarea 是处于激活状态,而当你点击插入按钮时焦点移交给了按钮,自然 textarea 就没有光标了。

既然如此,当插入完毕时我们将焦点再次移交给 textarea 就好了。

dom.focus()

此时你会发现另一个问题,那就是光标的位置跑到了最后。

demo1.gif

好家伙,从头跑到尾了,要解决也很简单。在找光标位置时已经用到了,再来,设置一下。

dom.selectionStart = start + string.length;
dom.selectionEnd = start + string.length;

再试一试应该就好了。

demo2.gif

这个 markdown 编辑器和 Vue 的关系不是很大,核心是 markdown 到 html 的转化。

代码汇总后:

function useInsertText(dom, string) {
  let start = dom.selectionStart
  let end = dom.selectionEnd
  dom.value = dom.value.substring(0, start) + string + dom.value.substring(end, dom.value.length)
  dom.selectionStart = start + string.length;
  dom.selectionEnd = start + string.length;
  dom.focus()
}

在找资料时发现另一个方案,虽然已经废弃,不过经测试还是好用的。

// 已废弃,不推荐,但无须解决焦点丢失和光标位置
document.execCommand('insertText', false, string)

这里的冷知识主要是光标相关的东西,这玩意一般场景下用到的几率确实也不是很多。

其实这里还是有一些待出来的东西在的,比如移动端的键盘,当你点击插入按钮后,因为 textarea 失去焦点,软键盘将会收起,只有 textarea 重新获取焦点后键盘才会弹出。此时就会频繁出现键盘的收起和弹出。

上一篇下一篇

猜你喜欢

热点阅读