vue使用marked.js实现markdown转html并提取标题生成目录
html:
<template>
<div class="wrapper">
<div class="container">
<div class="menu">
<ul class="menu-list">
<li v-for="(nav, index) in navList"
:key="index"
:class="{on: activeIndex === index}"
@click="currentClick(index)">
<a href="javascript:;" @click="pageJump(nav.index)">{{nav.title}}</a>
<div v-if="nav.children.length > 0 && activeIndex === index"
class="menu-children-list">
<ul class="nav-list">
<li v-for="(item, idx) in nav.children"
:key="idx"
:class="{on: childrenActiveIndex === idx}"
@click.stop="childrenCurrentClick(idx)">
<a href="javascript:;" @click="pageJump(item.index)">
{{item.title}}
</a>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="help-center-content" v-html="compiledMarkdown"
ref="helpDocs" @scroll="docsScroll"></div>
</div>
</div>
</template>
js部分:
<script> import marked from 'marked'; let rendererMD = new marked.Renderer();
marked.setOptions({
renderer: rendererMD,
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false
}); export default {
props: ['mdContent'],
data() {
return {
navList: [],
activeIndex: 0,
docsFirstLevels: [],
docsSecondLevels: [],
childrenActiveIndex: 0
}
},
mounted() {
this.navList = this.handleNavTree();
this.getDocsFirstLevels(0);
},
methods: {
childrenCurrentClick(index) {
this.childrenActiveIndex = index
},
getDocsFirstLevels(times) {
// 解决图片加载会影响高度问题
setTimeout(() => {
let firstLevels = [];
Array.from(document.querySelectorAll('h1'), element => {
firstLevels.push(element.offsetTop - 60)
})
this.docsFirstLevels = firstLevels; if (times < 8) {
this.getDocsFirstLevels(times + 1);
}
}, 500);
},
getDocsSecondLevels(parentActiveIndex) {
let idx = parentActiveIndex;
let secondLevels = [];
let navChildren = this.navList[idx].children if(navChildren.length > 0) {
secondLevels = navChildren.map((item)=>{
return this.$el.querySelector(`#data-${item.index}`).offsetTop - 60
})
this.docsSecondLevels = secondLevels;
}
},
docsScroll() {
if (this.titleClickScroll) {
return;
} let scrollTop = this.$refs.helpDocs.scrollTop
let firstLevelIndex = this.getLevelActiveIndex(scrollTop, this.docsFirstLevels)
this.currentClick(firstLevelIndex) let secondLevelIndex = this.getLevelActiveIndex(scrollTop, this.docsSecondLevels)
this.childrenCurrentClick(secondLevelIndex)
},
getLevelActiveIndex(scrollTop, docsLevels) {
let currentIdx = null;
let nowActive = docsLevels.some((currentValue, index) => {
if(currentValue >= scrollTop) {
currentIdx = index
return true
}
}) currentIdx = currentIdx - 1 if (nowActive && currentIdx === -1) {
currentIdx = 0
} else if (!nowActive && currentIdx === -1) {
currentIdx = docsLevels.length - 1
}
return currentIdx
},
pageJump(id) {
this.titleClickScroll = true;
this.$refs.helpDocs.scrollTop = this.$el.querySelector(`#data-${id}`).offsetTop - 40;
setTimeout(() => this.titleClickScroll = false, 100);
},
currentClick(index) {
this.activeIndex = index
this.getDocsSecondLevels(index)
},
getTitle(content) {
let nav = []; let tempArr = [];
content.replace(/(#+)[^#][^\n]*?(?:\n)/g, function(match, m1, m2) {
let title = match.replace('\n', '');
let level = m1.length;
tempArr.push({
title: title.replace(/^#+/, '').replace(/\([^)]*?\)/, ''),
level: level,
children: [],
});
}); // 只处理一级二级标题,以及添加与id对应的index值
nav = tempArr.filter(item => item.level <= 2);
let index = 0;
return nav = nav.map(item => {
item.index = index++;
return item;
});
},
// 将一级二级标题数据处理成树结构
handleNavTree() {
let navs = this.getTitle(this.content)
let navLevel = [1, 2];
let retNavs = [];
let toAppendNavList; navLevel.forEach(level => {
// 遍历一级二级标题,将同一级的标题组成新数组
toAppendNavList = this.find(navs, {
level: level
}); if (retNavs.length === 0) {
// 处理一级标题
retNavs = retNavs.concat(toAppendNavList);
} else {
// 处理二级标题,并将二级标题添加到对应的父级标题的children中
toAppendNavList.forEach(item => {
item = Object.assign(item);
let parentNavIndex = this.getParentIndex(navs, item.index);
return this.appendToParentNav(retNavs, parentNavIndex, item);
});
}
});
return retNavs;
},
find(arr, condition) {
return arr.filter(item => {
for (let key in condition) {
if (condition.hasOwnProperty(key) && condition[key] !== item[key]) {
return false;
}
}
return true;
});
},
getParentIndex(nav, endIndex) {
for (var i = endIndex - 1; i >= 0; i--) {
if (nav[endIndex].level > nav[i].level) {
return nav[i].index;
}
}
},
appendToParentNav(nav, parentIndex, newNav) {
let index = this.findIndex(nav, {
index: parentIndex
});
nav[index].children = nav[index].children.concat(newNav);
},
findIndex(arr, condition) {
let ret = -1;
arr.forEach((item, index) => {
for (var key in condition) {
if (condition.hasOwnProperty(key) && condition[key] !== item[key]) {
return false;
}
}
ret = index;
});
return ret;
},
},
computed: {
content() {
return this.mdContent
},
compiledMarkdown: function() {
let index = 0;
rendererMD.heading = function(text, level) {
if (level <= 2) {
return `<h${level} id="data-${index++}">${text}</h${level}>`;
} else {
return `<h${level}>${text}</h${level}>`;
}
};
rendererMD.code = function(code, language) {
code = code.replace(/\r\n/g,"<br>")
code = code.replace(/\n/g,"<br>");
return `<div class="text">${code}</div>`;
};
return marked(this.content);
}
}
}
</script>
参考链接:
https://github.com/markedjs/marked
https://www.jianshu.com/p/d182ea991609
https://hk.saowen.com/a/bf975e4296e33a14e2d0ad50aa7cbf24fbfb4a9fb851de171b4c71da54eb95e5
vue使用marked.js实现markdown转html并提取标题生成目录的更多相关文章
- Angular 利用 marked.js 添加 Markdown + HTML 同时渲染的 Pipe
背景 最近在公司开发的一个项目需要在 Angular 上展示图文,并且需要同时支持 Markdown 和 HTML 对于同时支持 Markdown 和 HTML ,应该要分为编辑和渲染两部分考虑. 对 ...
- marked.js简易手册
marked.js简易手册 本文介绍的是marked.js.秉持"来之即用"的原则,对它进行简要的翻译和归纳, 安装 在网上引用或者是引用本地文件即可.要么就用命令行: npm i ...
- 使用 Vue 和 epub.js 制作电子书阅读器
ePub 简介 ePub 是一种电子书的标准格式,平时我看的电子书大部分是这种格式.在手机上我一般用"多看"阅读 ePub 电子书,在 Windows 上找不到用起来比较顺心的软件 ...
- js解析MarkDown语法
1.问题描述: 我们使用MarkDown编辑器之后,比如我们写的MarkDown的语法是: # 一级标题 ## 二级标题 ### 三级标题 这种语法我们最终要转换成HTML的格式最终要存入数据库 ...
- vue 将markdown字符串转html、修改主题、生成目录
前言 将 markdown 字符串转成 html 显示出来,同时把目录也提取出来一起显示.可以使用 marked 来读取 markdown 字符串解析成 html marked官网:https://m ...
- Vue过渡效果之JS过渡
前面的话 与CSS过渡不同,JS过渡主要通过事件进行触发.本文将详细介绍Vue过渡效果之JS过渡 事件钩子 JS过渡主要通过事件监听事件钩子来触发过渡,共包括如下的事件钩子 <transitio ...
- socket应用(vue、node.js、M站)
socket应用(vue.node.js.M站) 前言:我们在做一些项目的时候需要做到实时变化, 比如我们有时候有需求会要求我们做一个类似于聊天室的页面 比如有些时候我们对某些东西进行点赞和刷票,需要 ...
- vue项目中主要文件的加载顺序(index.html、App.vue、main.js)
先后顺序: index.html > App.vue的export外的js代码 > main.js > App.vue的export里面的js代码 > Index.vue的ex ...
- vue.runtime.esm.js:593 [Vue warn]: Invalid prop: custom validator check failed for prop "value".报错解决
在uni中使用 picker组件,一直报错 vue.runtime.esm.js:593 [Vue warn]: Invalid prop: custom validator check failed ...
随机推荐
- EventBus框架原理解析(结合源代码)(上)
上一篇文章http://blog.csdn.net/crazy__chen/article/details/47425779 和大家一起模仿EventBus的实现机制.和大家一起写出了一个简易的Eve ...
- 维护的JSP站点数据丢失
两个月前换了份工作,然后接手了三台server.上面乱七八糟的网站和应用大把. 当中有维护一个瀚石苑:http://www.hanshiyuan.com/.三天两头的丢失数据. 都不知道怎么找回,好在 ...
- thinkphp5项目--企业单车网站(五)
thinkphp5项目--企业单车网站(五) 项目地址 fry404006308/BicycleEnterpriseWebsite: Bicycle Enterprise Websitehttps:/ ...
- 关于jacob支持BSTR类型的经验总结
作者:朱金灿 来源:http://blog.csdn.net/clever101 jacob是实现Java和COM之间互操作的一个开源中间件.网上大多的程序示例基本上是使用jacob调用微软的offi ...
- 局域网ARP病毒的清理
局域网ARP病毒的清理 作者:IT动力源 来源:IT动力源收集整理 现在局域网中感染ARP 病毒的情况比较多,清理和防范都比较困难,给不少的网络管理员造成了很多的困扰.下面就是个人在处理这个 ...
- python爬虫批量抓取ip代理
使用爬虫抓取数据时,经常要用到多个ip代理,防止单个ip访问太过频繁被封禁.ip代理可以从这个网站获取:http://www.xicidaili.com/nn/.因此写一个python程序来获取ip代 ...
- 紫书 例题 9-9 UVa 10003 (区间dp+递推顺序)
区间dp,可以以一个区间为状态,f[i][j]是第i个切点到第j个切点的木棍的最小费用 那么对于当前这一个区间,枚举切点k, 可以得出f[i][j] = min{dp(i, k) + dp(k, j) ...
- SpringBoot @PathVariable 和 @requestParam区别
1.若获取的入参的 参数 是下面这种形式 就使用 @requestParam 去获取 参数‘1’ /user?id=1 // url:xxx/user?id=1 @RequestMapping(&qu ...
- LRJ入门经典-0903切蛋糕305
原题 LRJ入门经典-0903切蛋糕305 难度级别:B: 运行时间限制:1000ms: 运行空间限制:256000KB: 代码长度限制:2000000B 试题描述 如图所示有一个矩形蛋糕,上面划分成 ...
- OpenStack_Swift源代码分析——Object-auditor源代码分析(2)
1 Object-aduitor审计详细分析 上一篇文章中,解说了Object-aduitor的启动,当中审计的详细运行是AuditorWorker实现的.在run_audit中实例化了Auditor ...