这里是在简书仿简书的第十三篇,早睡早起身体好
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 符号打起来不是很方便。
这里就涉及到一些冷知识了,插入符号换言之就是字符串拼接,字符串拼接是很常规的操作了,这里的核心是如何找到拼接点。
这里就需要用到一些光标的冷知识了,上图。
[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 中光标没有了,此时如果你再次点击插入操作会有什么现象呢?它会插到最前面。
光标消失的原因吧,其实很简单,就是本来 textarea 是处于激活状态,而当你点击插入按钮时焦点移交给了按钮,自然 textarea 就没有光标了。
既然如此,当插入完毕时我们将焦点再次移交给 textarea 就好了。
dom.focus()
此时你会发现另一个问题,那就是光标的位置跑到了最后。
好家伙,从头跑到尾了,要解决也很简单。在找光标位置时已经用到了,再来,设置一下。
dom.selectionStart = start + string.length;
dom.selectionEnd = start + string.length;
再试一试应该就好了。
这个 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 重新获取焦点后键盘才会弹出。此时就会频繁出现键盘的收起和弹出。