百度前端学院任务markdown解析器

最近打算写一个支持markdown的的博客,就顺便做一下百度的前端学院中对应的任务。模仿了简书markdown的样式和功能……目前基本完成了任务,打算搭好服务器弄好图片上传等再完善。


先看要求:

任务要求

完成效果:

完成效果

目前存在的问题是markdown语法不能嵌套使用会有bug。


可以看出这个任务是练习使用正则表达式,之前只在表单验证时使用过,所以如果有错误的地方欢迎指正,以免误导更多人。


先引用简书的文章介绍下markdown的部分语法:

标题
这是最为常用的格式,在平时常用的的文本编辑器中大多是这样实现的:输入文本、选中文本、设置标题格式。
而在 Markdown 中,你只需要在文本前面加上 # 即可,同理、你还可以增加二级标题、三级标题、四级标题、五级标题和六级标题,总共六级,只需要增加 # 即可,标题字号相应降低。

# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题

列表
列表格式也很常用,在 Markdown 中,你只需要在文字前面加上 - 就可以了,例如:

- 文本1
- 文本2
- 文本3

如果你希望有序列表,
也可以在文字前面加上 1. 2. 3. 就可以了,例如:

1. 文本1
2. 文本2
3. 文本3

注:-、1.和文本之间要保留一个字符的空格。
引用
在我们写作的时候经常需要引用他人的文字,这个时候引用这个格式就很有必要了,在 Markdown 中,你只需要在你希望引用的文字前面加上 > 就好了,例如:

一盏灯, 一片昏黄; 一简书, 一杯淡茶。 守着那一份淡定, 品读属于自己的寂寞。 保持淡定, 才能欣赏到最美丽的风景! 保持淡定, 人生从此不再寂寞。
注:> 和文本之间要保留一个字符的空格。
需要引用代码时,如果引用的语句只有一段,不分行,可以用 ` 将语句包起来。
如果引用的语句为多行,可以将```置于这段代码的首行和末行。

简书的markdown也有些小问题:可以直接输入html标签,并且会被p抱起来,但是下方还有副本。空格只有一个生效(这个我也是)。它的回车也只能间隔一行,多个是无法起作用的,导致引用和引用之间没有别的东西时会自动合并,因为简书判断应用结束的标志是连续两个\n,类似的还有列表的判断结束的方法。ಠ_ಠ


关于正则表达式:

正则速查.png

30分钟正则表达式
正则表达式匹配可视化
可以用来做匹配实验
推荐一本书:精通正则表达式(适合深入)


方案:

左右两栏:按照习惯:左边输入、右边输出。因为默认会有标题的,所以总的标题可以单独抽出来,和下面的文章分开,减少检索文章时的总量、dom操作量。

<div class="left-wrapper">
    <input type="text" id="titleInput" value="无标题文章">
    <ul class="tools"></ul>
    <textarea id="articleInput"></textarea>
</div>
<div class="right-wrapper">
    <div class="output">
        <h1 id="titleOutput"></h1>
        <div id="articleOutput"></div>
    </div>
</div>

大概dom结构就是这样,给title的输入加一个默认选中,方便修改:

const titleInput = document.getElementById('titleInput');
titleInput.select();

想要左边输入,右边预览,就需要事件把输入、预览联系起来:

键盘事件

看起来好像keypress更何时,可以连续触发、又不包含一些不会输出的键,但是实际上keypress对于中文输入不会触发,所以只能选择keyup事件。对于事件不建议在html里直接写上,这样不符合html、js分离降低耦合的原则。简易使用addEventListener来给dom节点添加对应的事件,标题和文章本身分别绑定对应的事件即可。因为可能连续输入,如果字数、格式很多就会有大量操作,为了降低性能负担,使用一个节流函数:

        let throttle = (method) => {
            method.tId && clearTimeout(method.tId);
            method.tId = setTimeout(function () {
                method();
            }, 140);
        };

方法第一次调用没有影响,会添加一个定时器然后调用,第二次调用同一个方法时如果第一个方法还没有触发,就会清楚掉第一个定时器,再重新添加一个定时器,保证了同一个方法在一定时间内只会执行一次。定时的时间间隔越长性能越好,体验越差……一般来说一次js执行时间小于150ms体验比较好。

        titleInput.addEventListener('keyup', () => {
            throttle(addTitle);
        });

类似这样绑定一个通过节流的方法,输出的title非常简单,因为是固定的h1只要动态的获取输入的title然后赋值给输出的title就可以了。文章的输入输出也同样,不过需要通过正则表达式,把对应的符号换成html标签,通过标签来改变样式。


第一部分关于标题:

因为简书的文章没说#号后必须要有空格,所以我的标题的定义就是行开头的#。首先使用了一个宽泛的表达式来判断(test方法)是否有行开头的#或者行开头任意空格后的#:

/^ *#{1,6}(( +[^\s]|[^#\s]).*)/gm
宽泛匹配标题

匹配在行开头头的任意空格加上1到6个#加上任意空格以及一个非空白符或者没有空格任意一个非#的非空白符加上任意数量的非换行符。

如果test为true就执行循环,将h1到h6都替换一遍:

//定义标题循环次数
const circles = 6;
for (let i = times; i > 0; i--) {
    reg3 = new RegExp(`^ *(#{${i}})(( +[^\s]|[^#\s]).*)(\n?)`, 'gm');
    articleOutput.innerHTML =articleOutput.innerHTML.replace(reg3, `<h${i}>$2</h${i}>`);
}

把符合具体格式的,#和\n中间的分组放到对应的h标签内就可以了。


第二部分关于列表:

判断每行开头或者是闭合的标签后-号后面加上一个空格加上一个非空白字符后面无所谓但是应该是单行的:

//给符合格式的创建ul和li
const reg4 = /(^ *|<\/.+>) *- (\S.*)/gm;
//给符合格式的创建ol和li
const reg5 = /(^ *|\/.+>) *\d\. (\S.*)/gm;

因为js的正则不支持后瞻,判断上文有没有某物挺麻烦,所以我就给每个都li都加上了ul或者ol,在用一个正则把连着的/ul和ul标签或者/ol和ol标签给去掉,用这个正则replace为空即可:

//去掉多余的ul、ol
const reg6 = /<\/([uo]l)>\n? *<\1>/g;

这里使用了一个分组的的引用,保证第二个和第一个都是ul或者都是ol;


第三部分关于引用和代码块:

同样也是每行的开头或者闭合标签后,但是可以是多行的,和简书一样以空白的一行作为结束。内容中的的每行肯定有一个非空白符字符一个可选的换行符其他无所谓所以,需要注意的是在html中>是用>表示的:

const reg7 = /(^ *|\/.+>) *> ((\S.*\n?)+)/gm;

单行代码根据markdown语法只要匹配同一行的``及中间不能有换行和`即可:

const reg8 = /(^ *|\/.+>) *`([^`\n]+)`/gm;

代码块类似但是由于语法,可以看出出了上下语法标志,中间每行必有换行,所以可以写成任意字符加上换行重复任意次:

const reg9 = /~~~\n((.*\n)+)~~~/g;

demo
代码

求点赞、关注、star,_

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 143,906评论 1 305
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,698评论 1 257
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 95,242评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,257评论 0 182
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 49,076评论 1 260
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,912评论 1 178
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,509评论 2 274
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,255评论 0 168
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,145评论 6 235
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,613评论 0 214
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,376评论 2 216
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,733评论 1 231
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,287评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,196评论 2 213
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,644评论 3 211
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,651评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,059评论 0 169
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,644评论 2 233
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,789评论 2 237

推荐阅读更多精彩内容