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并提取标题生成目录的更多相关文章

  1. Angular 利用 marked.js 添加 Markdown + HTML 同时渲染的 Pipe

    背景 最近在公司开发的一个项目需要在 Angular 上展示图文,并且需要同时支持 Markdown 和 HTML 对于同时支持 Markdown 和 HTML ,应该要分为编辑和渲染两部分考虑. 对 ...

  2. marked.js简易手册

    marked.js简易手册 本文介绍的是marked.js.秉持"来之即用"的原则,对它进行简要的翻译和归纳, 安装 在网上引用或者是引用本地文件即可.要么就用命令行: npm i ...

  3. 使用 Vue 和 epub.js 制作电子书阅读器

    ePub 简介 ePub 是一种电子书的标准格式,平时我看的电子书大部分是这种格式.在手机上我一般用"多看"阅读 ePub 电子书,在 Windows 上找不到用起来比较顺心的软件 ...

  4. js解析MarkDown语法

    1.问题描述: 我们使用MarkDown编辑器之后,比如我们写的MarkDown的语法是:  # 一级标题  ## 二级标题  ### 三级标题 这种语法我们最终要转换成HTML的格式最终要存入数据库 ...

  5. vue 将markdown字符串转html、修改主题、生成目录

    前言 将 markdown 字符串转成 html 显示出来,同时把目录也提取出来一起显示.可以使用 marked 来读取 markdown 字符串解析成 html marked官网:https://m ...

  6. Vue过渡效果之JS过渡

    前面的话 与CSS过渡不同,JS过渡主要通过事件进行触发.本文将详细介绍Vue过渡效果之JS过渡 事件钩子 JS过渡主要通过事件监听事件钩子来触发过渡,共包括如下的事件钩子 <transitio ...

  7. socket应用(vue、node.js、M站)

    socket应用(vue.node.js.M站) 前言:我们在做一些项目的时候需要做到实时变化, 比如我们有时候有需求会要求我们做一个类似于聊天室的页面 比如有些时候我们对某些东西进行点赞和刷票,需要 ...

  8. vue项目中主要文件的加载顺序(index.html、App.vue、main.js)

    先后顺序: index.html > App.vue的export外的js代码 > main.js > App.vue的export里面的js代码 > Index.vue的ex ...

  9. 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 ...

随机推荐

  1. export和source的区别

    1.执行脚本是在一个子shell环境运行的,脚本执行完后该子shell自动退出. 2.执行脚本中的系统环境变量(用export定义的变量)才会被复制到子shell中. 3.一个shell中的系统环境变 ...

  2. 4.graph.h

    #pragma once #include <stdio.h> #include <graphics.h> #include <mmsystem.h> #pragm ...

  3. ES6学习笔记(六)数组的扩展

    1.扩展运算符 1.1含义 扩展运算符(spread)是三个点(...).它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列. console.log(...[1, 2, 3]) // ...

  4. 【Uva 10285】Longest Run on a Snowboard

    [Link]: [Description] 在一个r*c的格子上; 求最长的下降路径; [Solution] 记忆化搜索; f[x][y]表示从(x,y)这个格子往下还能走多远; 因为是严格递增,所以 ...

  5. WPF通用框架ZFS《项目结构介绍01》_模块介绍

    首页介绍: 下图为项目运行首页图片, 大的结构分为三块: 1.Header首部模块(存放通知组件[全局通知.消息管理 ].扩展模块[皮肤.系统设置.关于作者.退出系统]) 2.Left左侧菜单模块(存 ...

  6. IT运维分析

    http://www.infoq.com/cn/presentations/case-of-it-operation-analysis-and-log-search-analysis-engine?u ...

  7. 比MD5 和HMAC还要安全的加密 - MD5 加时间戳

    //1.给一个字符串进行MD5加密 NSString *passKey = @"myapp"; passKey = [passKey md5String]; //2.对第一步中得到 ...

  8. Python下opencv使用笔记(一)(图像简单读取、显示与储存)

    写在之前 从去年開始关注python这个软件,途中间间断断看与学过一些关于python的东西.感觉python确实是一个简单优美.easy上手的脚本编程语言,众多的第三方库使得python异常的强大. ...

  9. 关于TabLayout+ViewPager组合实现多页面滑动

    转载请注明出处:http://blog.csdn.net/ht_android/article/details/46647711 在android提供的design library中新增了一个控件,叫 ...

  10. SQL Server字符串分割函数