如何给MarkDown添加toc文章目录功能nodejs+marked版本
目录
- 更多分享:www.catbro.cn
起源
- 看别人搭建个人站点,好牛逼,不行,自己也来搞个,看别人的文章自带标题引导toc,不行,我也要,故而出现下面的代码。
- marked是非常流行的markdown解析器,但是它并不自带toc功能。但是,我也想要怎么办呢?于是格式baidu、google、噢啦,结果下了个蛋,这个蛋便是我想要的toc;
toc诞生记
- 一下我们将从toc的工作原理及如何实现来进行分析
toc 工作原理
- 首先,toc的工作原理是网页中某个标签有个id字段,然后我们通过一个href=目标ID便可以时页面定位到该元素。
- 那么我们只要给每个h标签添加对应的ID,然后再额外生成一个toc及一堆的a标签,每个a标签分别对应一个h
然而,问题来了,如何给h添加ID呢?
- 我们都知道marked有个Renderer成员,负责渲染相关的,而大多数人可能又知道renderer有个code回调方法,是用来方便自定义处理pre 部分的。
- 其实我们还有一个heading回调,是marked解析到 # 对应的字段时回调的,也就是我们的标题部分。
- 那么,我们在其渲染标题时增加id给他,而且此时我们还能获取其标题内容作为a标签的展示文本,多友好的效果啊!
编码实现
-
啥也不说,代码如下
const highlight = require('highlight.js') const marked = require('marked') const tocObj = { add: function(text, level) { var anchor = `#toc${level}${++this.index}`; this.toc.push({ anchor: anchor, level: level, text: text }); return anchor; }, // 使用堆栈的方式处理嵌套的ul,li,level即ul的嵌套层次,1是最外层 // <ul> // <li></li> // <ul> // <li></li> // </ul> // <li></li> // </ul> toHTML: function() { let levelStack = []; let result = ''; const addStartUL = () => { result += '<ul class="anchor-ul" id="anchor-fix">'; }; const addEndUL = () => { result += '</ul>\n'; }; const addLI = (anchor, text) => { result += '<li><a class="toc-link" href="#'+anchor+'">'+text+'<a></li>\n'; }; this.toc.forEach(function (item) { let levelIndex = levelStack.indexOf(item.level); // 没有找到相应level的ul标签,则将li放入新增的ul中 if (levelIndex === -1) { levelStack.unshift(item.level); addStartUL(); addLI(item.anchor, item.text); } // 找到了相应level的ul标签,并且在栈顶的位置则直接将li放在此ul下 else if (levelIndex === 0) { addLI(item.anchor, item.text); } // 找到了相应level的ul标签,但是不在栈顶位置,需要将之前的所有level出栈并且打上闭合标签,最后新增li else { while (levelIndex--) { levelStack.shift(); addEndUL(); } addLI(item.anchor, item.text); } }); // 如果栈中还有level,全部出栈打上闭合标签 while(levelStack.length) { levelStack.shift(); addEndUL(); } // 清理先前数据供下次使用 this.toc = []; this.index = 0; return result; }, toc: [], index: 0 }; class MarkUtils { constructor() { this.rendererMD = new marked.Renderer(); this.rendererMD.heading = function(text, level, raw) { var anchor = tocObj.add(text, level); return `<h${level} id=${anchor}>${text}</h${level}>\n`; }; highlight.configure({useBR: true}); marked.setOptions({ renderer: this.rendererMD, headerIds: false, gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, smartLists: true, smartypants: false, highlight: function (code) { return highlight.highlightAuto(code).value; } }); } async marked(data) { if (data){ let content = await marked(data); let toc = tocObj.toHTML(); return {content:content,toc:toc} }else { return null; } } } module.exports = new MarkUtils();