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 ...
随机推荐
- Zookeeper简单概念介绍
过去,每个应用都是一个CPU.一个主机上的单一系统.然而今天,随着大数据和云计算时代的到来,不论什么相互独立的程序都可以运行在多个计算机上.然而面临的问题是,协调这些集群的系统比在单一主机上要复杂的多 ...
- 彻底解决lazarus安装组件后烦人的编译时单元找不到的问题!
以安装indy为例 1/下载组件包, http://www.indyproject.org/Sockets/fpc/indy-10.2.0.3.zip 2/爆开放于C:\lazarus\compone ...
- ControlDesigner
GetHitTest https://stackoverflow.com/questions/7762397/how-do-i-click-a-usercontrols-child-in-design ...
- 19.浏览器Window服务($window)
转自:https://www.cnblogs.com/best/tag/Angular/ 引用浏览器的window对象.默认浏览器的window是全局的根对象. 示例代码: <!DOCTYPE ...
- 27.C语言宽字符操作
#include <locale.h> setlocale(LC_ALL, "zh-CN"); wchar_t wch = L'我'; putwchar(wch); # ...
- Gym - 100338C Important Roads 最短路+tarjan
题意:给你一幅图,问有多少条路径使得去掉该条路后最短路发生变化. 思路:先起始两点求两遍单源最短路,利用s[u] + t[v] + G[u][v] = dis 找出所有最短路径,构造新图.在新图中找到 ...
- main函数argc,argv操作
使用main(int argc, char *argv[])==main(int argc, char **argv)的基本操作是linux编程的最基本的一步,在windows下也是exe脱离IDE运 ...
- C/C++(C++拷贝构造器,赋值运算符重载)
拷贝构造器 由己存在的对象,创建新对象.也就是说新对象,不由构造器来构造,而是由拷贝构造器来完成.拷贝构造器的格式是固定的. class 类名 { 类名(const 类名 & another) ...
- Swift学习笔记(1)--基本语法
1.分号; 1>Swift不要求每个语句后面跟一个分号作为语句结束的标识,如果加上也可以,看个人喜好 2>在一行中写了两句执行语句,需要用分号隔开,比如 let x = 0; printl ...
- 03014_properties配置文件
1.使用properties配置文件 (1)开发中获得连接的4个参数(驱动.URL.用户名.密码)通常都存在配置文件中,方便后期维护,程序如果需要更换数据库,只需要修改配置文件即可: (2)通常情况下 ...