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 ...
随机推荐
- 受不了Android SDK文档打开缓慢问题,自己开发简易脱机浏览器。
google android sdk离线文档打开的时候特别慢,据说是要从谷歌官网拉取一些东西导致的.脱机浏览能够解决该问题.PC端能够使用firefox. 可是Android端貌似没有支持脱机工作的浏 ...
- js斐波那契数列求和
一.递归算法 function recurFib(n) { if (n < 2) { return n; } else { return recurFib(n-1) ...
- oracle之ROWNUM的查询应用
1 在ORACLE数据库中,ROWNUM是ORACLE数据库为查询结果加入的一个伪列.起始值为1.经常使用来处理查询结果的分页. 2 因为ROWNUM的特殊性,使用时候一般是分三层: 第一层:先进行查 ...
- hdu 1102 Constructing Roads(kruskal || prim)
求最小生成树.有一点点的变化,就是有的边已经给出来了.所以,最小生成树里面必须有这些边,kruskal和prim算法都能够,prim更简单一些.有一点须要注意,用克鲁斯卡尔算法的时候须要将已经存在的边 ...
- hdu1879 继续畅通project(最小生成树)
继续畅通project Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tota ...
- JVM-ClassLoader装载class的流程
在JVM中,有三种默认的类加载器,分别为Bootstrap ClassLoader,Extension CLassLoader以及App ClassLoader.其中,Bootstrap Classl ...
- 80.简单搭建nodeJS服务,访问本地站点文件
转自:https://blog.csdn.net/iteye_1217/article/details/82679843 搭建nodejs服务器步骤: 1.安装nodejs服务(从官网下载安装),no ...
- P3507 [POI2010]GRA-The Minima Game
题目描述 Alice and Bob learned the minima game, which they like very much, recently. The rules of the ga ...
- 【基础篇】activity生命周期及数据保存
常见的Android 的界面,均采用Activity+view的形式显示的,一提到Activity,立即就能联想到Activity的生命周期与状态的保存. 下面先从Activity的生命周期开始说起 ...
- Atcoder B - Moderate Differences
http://agc017.contest.atcoder.jp/tasks/agc017_b B - Moderate Differences Time limit : 2sec / Memory ...