jjzjj

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

Clown 2023-03-28 原文

前言

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

marked

安装

使用 marked 前需要对其进行安装

npm install marked -s

使用

安装好后,在使用到的页面引入使用即可。

<template>
  <div>
    <div v-html="content"></div>
  </div>
</template>

<script>
import { marked } from 'marked'
export default {
   data(){
     return {
         textData: "# JavaScript高级程序设计\n## 内容简介\n   本书是JavaScript经典图书的新版。第4版全面、深入地介绍了JavaScript开发者必须掌握的前端开发技术,涉及JavaScript的基础特性和高级特性。书中详尽讨论了JavaScript的各个方面,从JavaScript的起源开始,逐步讲解到新出现的技术,其中重点介绍ECMAScript和DOM标准。在此基础上,接下来的各章揭示了JavaScript的基本概念,包括类、期约、迭代器、代理,等等。另外,书中深入探讨了客户端检测、事件、动画、表单、错误处理及JSON。本书同时也介绍了近几年来涌现的重要新规范,包括Fetch API、模块、工作者线程、服务线程以及大量新API。\n## 作者简介\n马特·弗里斯比(Matt Frisbie),Stealth Startup公司CTO,曾担任谷歌公司软件工程师,精通前端技术,拥有十余年Web开发经验,除本书外另著有AngularJS等前端主题图书。毕业于伊利诺伊大学厄巴纳-尚佩恩分校。\n## 目录\n### 第 1章 什么是JavaScript 1\n#### 1.1 简短的历史回顾 1\n#### 1.2 JavaScript实现 2\n#### 1.3 JavaScript版本 9\n#### 1.4 小结 10\n### 第 2章 HTML中的JavaScript 11\n#### 2.1 script元素 11\n#### 2.2 行内代码与外部文件 18\n#### 2.3 文档模式 18\n#### 2.4 noscript元素 19\n测试代码\n```language\nvar a = 1;\n\nfunction fun(){\n  console.log(11111)\n}\nfun()\nconosole.log(a)\n```\n\n#### 2.5 小结 20\n### 第3章 语言基础 21\n#### 3.1 语法 21\n#### 3.2 关键字与保留字 23\n#### 3.3 变量 24\n#### 3.4 数据类型 30\n#### 3.5 操作符 56\n#### 3.6 语句 73\n#### 3.7 函数 80\n#### 3.8 小结 82\n### 第4章 变量、作用域与内存 83\n#### 4.1 原始值与引用值 83\n#### 4.2 执行上下文与作用域 87\n#### 4.3 垃圾回收 94\n#### 4.4 小结 101"
     }
   },
   computed:{
     content(){
        return marked(this.textData)
     }
   }
}
</script>

效果展示

markdown-css

marked 转 html已实现,但是整体的样式效果看起来和在markerdown文档里看的有差别,那是因为还没有引入 markerdown 的样式,接下来需要引入样式

安装

npm install github-markdown-css

使用

导入 github-markdown-css 样式,并绑定样式

import "github-markdown-css"

<div v-html="content"  class="markdown-body"></div>

绑定样式后,实现效果:

修改主题

如果还是觉得默认的效果不满意,可以去这里:https://theme.typoraio.cn/ 选择你想要的主题换上去。使用也很简单,选择自己想要的模板,点击下载样式就好了。

以上就是 markdown 字符串转成 html 并显示了,接下来就是获取目录。获取目录视项目情况而定,因为项目需求中需要使用到目录,所以需要生成目录。

生成目录

找资料时,恰好看到了这个博主的文章,生成目录就是借鉴了这个博主,使用到了里面的一些源码:https://segmentfault.com/a/1190000040462508

获取目录

获取文章中 h1-h6 标题,这里我选择使用 jq 获取,速度快

getCatalog() {
  const h = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
  var elements = $(':header')
  let hElements = []
  for (const key of elements) {
    if (h.indexOf(key.localName) > -1) {
      let text
      if (key.children && key.children.length) {
        text = this.getText(key.children)
      } else {
        text = key.innerHTML
      }
      hElements.push({
        hLevel: parseInt(key.localName[1]),
        text,
        id: key.localName,
        uuid: key.id,
      })
    }
  }
  console.log('hElements:', hElements)
},
  
getText() {
  let result = null
  if (!arr.length) return
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].children && arr[i].children.length) {
      result = this.getText(arr[i].children)
    } else {
      result = arr[i].innerHTML
    }
  }
  return result
},

获取到的数据是这样子的:

完成以上操作后,拿到 hElements 数组,处理数据,方便树形插件显示

toTree(flatArr) {
  var tree = []
  var copyArr = flatArr.map(function (item) {
    return item
  })

  // 根据指定级别查找该级别的子孙级,并删除掉已经查找到的子孙级
  var getChildrenByLevel = function (currentLevelItem, arr, level) {
    if (!currentLevelItem) {
      return
    }
    // 将level值转成负数,再进行比较
    var minusCurrentLevel = -currentLevelItem.hLevel
    var children = []
    for (var i = 0, len = arr.length; i < len; i++) {
      var levelItem = arr[i]
      if (-levelItem.hLevel < minusCurrentLevel) {
        children.push(levelItem)
      } else {
        // 只找最近那些子孙级
        break
      }
    }
    // 从数组中删除已经找到的那些子孙级,以免影响到其他子孙级的查找
    if (children.length > 0) {
      arr.splice(0, children.length)
    }
    return children
  }

  var getTree = function (result, arr, level) {
    // 首先将数组第一位移除掉,并添加到结果集中
    var currentItem = arr.shift()

    currentItem.level = level
    result.push(currentItem)
    while (arr.length > 0) {
      if (!currentItem) {
        return
      }
      // 根据当前级别获取它的子孙级
      var children = getChildrenByLevel(currentItem, arr, level)
      // 如果当前级别没有子孙级则开始下一个
      if (children.length == 0) {
        currentItem = arr.shift()
        currentItem.level = level
        if (currentItem) {
          result.push(currentItem)
        }
        continue
      }
      currentItem.children = []
      // 查找到的子孙级继续查找子孙级
      getTree(currentItem.children, children, level + 1)
    }
  }
  getTree(tree, copyArr, 1)
  return tree
},

最终的实现效果:

代码高亮

文档中的代码部分是没有高亮的,需添加代码高亮。

这里使用到 highlight.js 插件
highlight.js官网地址:https://highlightjs.org/

安装 highlight.js

npm install highlight.js -S

使用

import hljs from 'highlight.js'

//引入一种语法的高亮 【注意】要引入这个css 不然可能会失效
import 'highlight.js/styles/atom-one-light.css' 
// 高亮语法
hljs.highlightAll()

效果展示:

点击目录跳转

这里有两种方式:a标签的锚点定位 和 scrollIntoView() 方法

锚点定位

点击后跳转到指定标题,没有动画效果

<a class="anchor" :href="`#${data.uuid}`">
  {{ data.text }}
</a>

scrollIntoView() 方法

点击跳转后有平滑效果

<div @click="toDiv(data.uuid)">{{ data.text }}</div>

...

toDiv(uuid) {
  document.getElementById(uuid).scrollIntoView({
    behavior: 'smooth',
  })
},

源码

<template>
	<div class="content">
		<el-tree class="tree" ref="tree" node-key="uuid" :data="treeData" :props="defaultProps" default-expand-all>
			<div class="custom-tree-node" slot-scope="{ node, data }">
				<div @click="toDiv(data.uuid)">{{ data.text }}</div>
				<!-- <a class="anchor" :href="`#${data.uuid}`">
					{{ data.text }}
				</a> -->
			</div>
		</el-tree>
		<div v-html="content" class="markdown-body html-content"></div>
	</div>
</template>

<script>
	import "github-markdown-css"
	import hljs from 'highlight.js'
	import 'highlight.js/styles/atom-one-light.css' //引入一种语法的高亮
	import {
		marked
	} from 'marked'
	export default {
		data() {
			return {
				treeData: [],
				defaultProps: {
					label: 'text',
					children: 'children',
				},
				textData: "# JavaScript高级程序设计\n## 内容简介\n   本书是JavaScript经典图书的新版。第4版全面、深入地介绍了JavaScript开发者必须掌握的前端开发技术,涉及JavaScript的基础特性和高级特性。书中详尽讨论了JavaScript的各个方面,从JavaScript的起源开始,逐步讲解到新出现的技术,其中重点介绍ECMAScript和DOM标准。在此基础上,接下来的各章揭示了JavaScript的基本概念,包括类、期约、迭代器、代理,等等。另外,书中深入探讨了客户端检测、事件、动画、表单、错误处理及JSON。本书同时也介绍了近几年来涌现的重要新规范,包括Fetch API、模块、工作者线程、服务线程以及大量新API。\n## 作者简介\n马特·弗里斯比(Matt Frisbie),Stealth Startup公司CTO,曾担任谷歌公司软件工程师,精通前端技术,拥有十余年Web开发经验,除本书外另著有AngularJS等前端主题图书。毕业于伊利诺伊大学厄巴纳-尚佩恩分校。\n## 目录\n### 第 1章 什么是JavaScript 1\n#### 1.1 简短的历史回顾 1\n#### 1.2 JavaScript实现 2\n#### 1.3 JavaScript版本 9\n#### 1.4 小结 10\n### 第 2章 HTML中的JavaScript 11\n#### 2.1 script元素 11\n#### 2.2 行内代码与外部文件 18\n#### 2.3 文档模式 18\n#### 2.4 noscript元素 19\n测试代码\n``` javascript\nvar a = 1;\n\nfunction fun(){\n  console.log(11111)\n}\nfun()\nconosole.log(a)\n```\n\n#### 2.5 小结 20\n### 第3章 语言基础 21\n#### 3.1 语法 21\n#### 3.2 关键字与保留字 23\n#### 3.3 变量 24\n#### 3.4 数据类型 30\n#### 3.5 操作符 56\n#### 3.6 语句 73\n#### 3.7 函数 80\n#### 3.8 小结 82\n### 第4章 变量、作用域与内存 83\n#### 4.1 原始值与引用值 83\n#### 4.2 执行上下文与作用域 87\n#### 4.3 垃圾回收 94\n#### 4.4 小结 101"
			}
		},
		created() {
			this.$nextTick(() => {
				this.getCatalog()
				hljs.highlightAll()
			})
		},
		computed: {
			content() {
				return marked(this.textData)
			}
		},
		methods: {
			// 根据内容获取目录
			getCatalog() {
				const h = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
				var elements = $(':header')
				let hElements = []
				for (const key of elements) {
					if (h.indexOf(key.localName) > -1) {
						let text
						if (key.children && key.children.length) {
							text = this.getText(key.children)
						} else {
							text = key.innerHTML
						}
						hElements.push({
							hLevel: parseInt(key.localName[1]),
							text,
							id: key.localName,
							uuid: key.id,
						})
					}
				}
				// console.log('hElements:', hElements)
				let result = this.toTree(hElements)
				this.treeData = result

				// 目录默认选中第一个
				this.$nextTick(() => {
					if (!result) return
					this.$refs.tree.setCurrentKey(result[0].uuid)
				})
				// console.log('result:', result)
			},
			toTree(flatArr) {
				var tree = []
				var copyArr = flatArr.map(function(item) {
					return item
				})

				// 根据指定级别查找该级别的子孙级,并删除掉已经查找到的子孙级
				var getChildrenByLevel = function(currentLevelItem, arr, level) {
					if (!currentLevelItem) {
						return
					}
					// 将level值转成负数,再进行比较
					var minusCurrentLevel = -currentLevelItem.hLevel
					var children = []
					for (var i = 0, len = arr.length; i < len; i++) {
						var levelItem = arr[i]
						if (-levelItem.hLevel < minusCurrentLevel) {
							children.push(levelItem)
						} else {
							// 只找最近那些子孙级
							break
						}
					}
					// 从数组中删除已经找到的那些子孙级,以免影响到其他子孙级的查找
					if (children.length > 0) {
						arr.splice(0, children.length)
					}
					return children
				}

				var getTree = function(result, arr, level) {
					// 首先将数组第一位移除掉,并添加到结果集中
					var currentItem = arr.shift()

					currentItem.level = level
					result.push(currentItem)
					while (arr.length > 0) {
						if (!currentItem) {
							return
						}
						// 根据当前级别获取它的子孙级
						var children = getChildrenByLevel(currentItem, arr, level)
						// 如果当前级别没有子孙级则开始下一个
						if (children.length == 0) {
							currentItem = arr.shift()
							currentItem.level = level
							if (currentItem) {
								result.push(currentItem)
							}
							continue
						}
						currentItem.children = []
						// 查找到的子孙级继续查找子孙级
						getTree(currentItem.children, children, level + 1)
					}
				}
				getTree(tree, copyArr, 1)
				return tree
			},
			getText() {
				let result = null
				if (!arr.length) return
				for (let i = 0; i < arr.length; i++) {
					if (arr[i].children && arr[i].children.length) {
						result = this.getText(arr[i].children)
					} else {
						result = arr[i].innerHTML
					}
				}

				return result
			},
			toDiv(uuid) {
				document.getElementById(uuid).scrollIntoView({
					behavior: 'smooth',
				})
			},
		}
	}
</script>

<style>
	.content {
		display: flex;
		/* padding: 30px; */
		overflow: hidden;
		height: 100vh;
	}

	.tree {
		margin-right: 15px;
		overflow-y: auto;
		width: 280px;
		flex-shrink: 0;
	}

	.html-content {
		overflow-y: auto;
	}
</style>

最终效果

感谢这个大佬的分享:https://segmentfault.com/a/1190000040462508

有关vue 将markdown字符串转html、修改主题、生成目录的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  5. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  6. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  7. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  8. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  9. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  10. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

随机推荐