前提:此需求在全网未找到实现的案例,故自己记录一下。
使用到的插件:markdown-it、markdown-it-anchor、markdown-it-toc-done-right、uslug
例子使用vue实现,nodejs可以参考,用法一样
最终成品直接看3、4
template和样式部分:
<template>
<div class="md-content">
<div class="sidebar">
<div v-html="tocContent"></div>
</div>
<div class="content" v-html="htmlMD"></div>
</div>
</template>
<style scoped>
.md-content {display: flex;}
.sidebar {position: sticky;top: 0;width: 300px;padding: 20px;background-color: #f0f0f0;overflow-y: auto;}
.content {flex-grow: 1;padding: 20px;}
.sidebar ul {list-style: none;padding: 0;}
.sidebar li {margin-left: 10px;}
.sidebar a {display: inline-block;margin-bottom: 5px;}
</style>
1、只使用makdown-it
时,设置为html:true
可以实现将md转换为html页面展示:
<script>
import axios from "axios";
export default {
data(){
return {
htmlMD:'',
tocContent:''
}
},
mounted() {
this.ok()
},
methods:{
ok(){
axios.get("/测试平台使用手册.md").then((response) => {
const md = require("markdown-it")({
html: true,
typographer: true
})
var result = md.render(response.data);
this.htmlMD = result;
});
},
}
}
</script>
此时的界面:
此时标题的html标签:
2、markdown-it-anchor
可以为 Markdown 文本生成唯一 ID 的标题,并自动在内容中创建链接:
<script>
import axios from "axios";
import markdownItAnchor from "markdown-it-anchor"; //增加
export default {
data(){
return {
htmlMD:'',
tocContent:''
}
},
mounted() {
this.ok()
},
methods:{
ok(){
axios.get("/测试平台使用手册.md").then((response) => {
const md = require("markdown-it")({
html: true,
typographer: true
})
//增加
.use(markdownItAnchor, {
permalink: true,
permalinkSymbol: "",
})
var result = md.render(response.data);
this.htmlMD = result;
});
},
}
}
</script>
此时标题的html标签:
3、解析md,可以获取标题节点,并结合markdown-it-toc-done-right
生成锚点菜单:
<script>
import axios from "axios";
import markdownItAnchor from "markdown-it-anchor";
import markdownItTocDoneRight from "markdown-it-toc-done-right"; //新增
export default {
data(){
return {
htmlMD:'',
tocContent:''
}
},
mounted() {
this.ok()
},
methods:{
ok(){
axios.get("/测试平台使用手册.md").then((response) => {
const md = require("markdown-it")({
html: true,
typographer: true
})
.use(markdownItAnchor, {
permalink: true,
permalinkSymbol: "",
})
//新增
.use(markdownItTocDoneRight, {
includeLevel: [1, 2],
})
var result = md.render(response.data);
this.htmlMD = result;
//新增
this.generateToc(response.data,md);
});
},
generateToc(markdownText,md) {
let tocHtml = '';
const tokens = md.parse(markdownText, {}); //解析为token数组
console.log("tokens",tokens)
tokens.forEach((token, index) => {
if (token.type === 'heading_open') { //标题的开始标签节点
const anchor = token.attrGet('id'); //获取标题的id
let title = '';
const nextToken = tokens[index + 1];
if (nextToken.type === 'inline') {
// 如果下一个标记是文本标记,则将其内容作为标题内容
title = md.renderer.render(nextToken.children, md.options, {});
} else {
// 否则使用默认的标题内容
title = md.renderer.renderToken(tokens[index + 1], md.options, {});
}
tocHtml += `<a href="#${anchor}">${title}</a><br>`; //渲染为锚点链接
}
});
this.tocContent = tocHtml;
},
}
}
</script>
此时,菜单可以正常跳转使用。
注:不能用markdown-it-toc
替换markdown-it-toc-done-right
,markdown-it-toc
生成的html中标题id跟#${anchor}
获取到的不一致,不可控
4、利用uslug
自定义标题id。
如果有自定义id的需求就安装使用uslug
比如,会有网页的地址url是hash多级路径的,比如:http://127.0.0.1:8080/#/helpdocument
点击菜单的锚点链接后地址栏会变成http://127.0.0.1:8080/#/helpdocument/#11-物理机管理
,这时页面404,因为此时识别的锚点为#/helpdocument/#11-物理机管理
,而页面里的标题id是11-物理机管理
,匹配错误。
uslug(string, options)
参数说明:
string是待传入的字符串;options有三个值可以设置:
①allowedChars: 可以指定字符串保持原样,不转换,缺省值:‘-_~’.
②lower: 布尔值,是否强制转换为小写?缺省为true
③spaces: 布尔值,是否允许空格?缺省为false。
<script>
import axios from "axios";
import markdownItAnchor from "markdown-it-anchor";
import markdownItTocDoneRight from "markdown-it-toc-done-right";
export default {
data(){
return {
htmlMD:'',
tocContent:''
}
},
mounted() {
this.ok()
},
methods:{
ok(){
axios.get("/测试平台使用手册.md").then((response) => {
//新增
const uslug = require('uslug')
//括号里为自定义标题格式,第二个参数是允许保留/和#符号
const uslugify = s => uslug('/helpDocument/#'+s,{ allowedChars: ['/','#'] })
const md = require("markdown-it")({
html: true,
typographer: true
})
.use(markdownItAnchor, {
permalink: true,
permalinkSymbol: "",
slugify: uslugify //新增
})
.use(markdownItTocDoneRight, {
includeLevel: [1, 2],
})
var result = md.render(response.data);
this.htmlMD = result;
this.generateToc(response.data,md);
});
},
generateToc(markdownText,md) {
let tocHtml = '';
const tokens = md.parse(markdownText, {}); //解析为token数组
console.log("tokens",tokens)
tokens.forEach((token, index) => {
if (token.type === 'heading_open') { //标题的开始标签节点
const anchor = token.attrGet('id'); //获取标题的id
let title = '';
const nextToken = tokens[index + 1];
if (nextToken.type === 'inline') {
// 如果下一个标记是文本标记,则将其内容作为标题内容
title = md.renderer.render(nextToken.children, md.options, {});
} else {
// 否则使用默认的标题内容
title = md.renderer.renderToken(tokens[index + 1], md.options, {});
}
tocHtml += `<a href="#${anchor}">${title}</a><br>`; //渲染为锚点链接
}
});
this.tocContent = tocHtml;
},
}
}
</script>
此时的菜单链接:
正文标题标签:
示例界面:
参考:将Markdown字符串转成HTML