目录
本文收录在《如何开发一个自己的笔记软件》系列中,该系列源码均可在 Blossom 笔记软件 仓库中查看。仓库地址:
安装
源码地址 Markedjs 源码地址
官方文档 Markedjs 官方文档
安装依赖
bash
npm install marked
npm install @types/marked
npm install marked-highlight
npm install highlight.js
使用
你可以直接引入marked
,默认是全局共享同一个实例
typescript
// 引入默认实例
import { marked } from 'marked'
marked.use({
// 开启异步渲染
async: true,
pedantic: false,
gfm: true,
mangle: false,
headerIds: false
})
// 异步方式渲染
marked.parse(markdown, { async: true }).then((html: string) => {
console.log(html)
})
// 同步方式渲染
const html = marked.parse(markdown)
如果你要创建一个新实例,可以使用如下方式
typescript
import { Marked } from 'marked'
const newMarked = new Marked({ mangle: false, headerIds: false })
添加代码高亮
你需要安装下列两个依赖
bash
npm install marked-highlight
npm install highlight.js
然后进行如下配置,代码块中的内容会自动添加高亮样式。
typescript
import { marked } from 'marked'
import { markedHighlight } from "marked-highlight"
import hljs from 'highlight.js'
// 注意引入样式,你可以前往 node_module 下查看更多的样式主题
import 'highlight.js/styles/base16/darcula.css'
// 高亮拓展
marked.use(markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'shell'
return hljs.highlight(code, { language }).value
}
}))
自定义渲染格式
你可以重写 renderer
函数来自定义渲染结果。下方会介绍一些笔者开发的笔记软件中用到的拓展方式。
源码地址:Markedjs 封装
拓展标题ID实现目录跳转
重写 heading
函数,并为所有的标题标签添加ID来实现目录跳转。
java
let html:string = ''
const renderer = {
/**
* 标题解析为 TOC 集合, 增加锚点跳转
*
* @param text 标题内容
* @param level 标题级别
*/
heading(text: any, level: number): string {
const realLevel = level
return `<h${realLevel} id="${realLevel}-${text}">${text}</h${realLevel}>`
}
}
// 重写标题的渲染结果
marked.use({ renderer: renderer })
marked.parse('# 标题1', { async: true }).then((htmlResult: string) => {
// 结果为 <h1 id="1-标题1">标题1</h1>
html = htmlResult
})
然后解析 html 来自动获取标题标签(h1
~ h6
),并解析成一个数组。
typescript
// 定义目录结构
export interface Toc {
name: string,
id: string,
level: number
}
export const initTocs = (id: string): Toc[] => {
// 获取指定ID,渲染后的html都在该标签内
let ele = document.getElementById(id)
if (ele == null || undefined) {
return []
}
let heads = document.getElementById(id)!.querySelectorAll('h1, h2, h3, h4, h5, h6');
let tocs: Toc[] = []
for (let i = 0; i < heads.length; i++) {
let head = heads[i]
let level = 1;
let name = (head as HTMLElement).innerText
let id = head.id
switch (head.localName) {
case 'h2':
level = 2;
break;
case 'h3':
level = 3;
break;
case 'h4':
level = 4;
break;
}
let toc: Toc = {
name: name,
id: id,
level: level
}
tocs.push(toc)
}
return tocs
}
随后无论你使用 vue 还是 react,你都可以将数组结果列渲染到你的页面上。并为每一个元素添加一个 click 事件,即可以实现跳转。比方说 vue 中:
html
<template>
<div :class="['toc', 'toc-' + toc.level]" v-for="toc in tocs" :key="toc.id" @click="toScroll(toc.id)">
{{ toc.name }}
</div>
</template>
<script setup lang="ts">
const toScroll = (id: string) => {
let ele: HTMLElement = document.getElementById(id) as HTMLElement
if (ele == null || ele == undefined) {
return
}
(ele.parentNode as Element).scrollTop = ele.offsetTop
}
</script>
拓展图片的样式
可以通过约定 markdown 格式,来实现更加丰富的样式。比如下方的例子中,为图片增加了宽度 和阴影的拓展。只需要在图片名称中指定相关配置
text

typescript
// 拓展语法标识
const grammar = '##'
const renderer = {
/**
* 拓展图片
*
* @param href 图片路径
* @param _title null
* @param text 图片的名称
*/
image(href: string | null, _title: string | null, text: string): string {
let width = 'auto' // 宽度
let style = '' // 样式
let tags: string[] = text.split(grammar)
if (tags.length > 1) {
for (let i = 0; i < tags.length; i++) {
let tag = tags[i]
if (tag === 'shadow') {
style += 'box-shadow: 0 0 3px #000000;'
}
//
if (tag.startsWith('w')) {
width = tags[i].substring(1)
if (!width.endsWith('%')) {
width += 'px'
}
}
}
}
return `<img width="${width}" style="${style}" src="${href}" alt="${text}">`
}
}
// 重写图片的渲染结果
marked.use({ renderer: renderer })
marked.parse('', { async: true }).then((htmlResult: string) => {
// 结果为 <img width="200px" style="box-shadow: 0 0 3px #000000;" src="http://domain.com" alt="图片名称##w200##shadow">图片名称</img>
html = htmlResult
})
更多拓展
除此之外,笔者还拓展了一些其他的 markedjs 渲染方法。这些拓展内容都可以在Markedjs 封装中查看。
1. 使用表格进行布局

2. 多色引用块

3. 视频引用

4. katex 语法拓展
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> { 0 = c x − a x 0 − d x 0 ( c x − a x 0 ) ⋅ d x 0 ∥ d x 0 ∥ 2 + c x − a x 1 − d x 1 ( c x − a x 1 ) ⋅ d x 1 ∥ d x 1 ∥ 2 0 = c y − a y 0 − d y 0 ( c y − a y 0 ) ⋅ d y 0 ∥ d y 0 ∥ 2 + c y − a y 1 − d y 1 ( c y − a y 1 ) ⋅ d y 1 ∥ d y 1 ∥ 2 \left\{ \begin{array}{l} 0 = c_x-a_{x0}-d_{x0}\dfrac{(c_x-a_{x0})\cdot d_{x0}}{\|d_{x0}\|^2} + c_x-a_{x1}-d_{x1}\dfrac{(c_x-a_{x1})\cdot d_{x1}}{\|d_{x1}\|^2} \\[2ex] 0 = c_y-a_{y0}-d_{y0}\dfrac{(c_y-a_{y0})\cdot d_{y0}}{\|d_{y0}\|^2} + c_y-a_{y1}-d_{y1}\dfrac{(c_y-a_{y1})\cdot d_{y1}}{\|d_{y1}\|^2} \end{array} \right. </math>⎩ ⎨ ⎧0=cx−ax0−dx0∥dx0∥2(cx−ax0)⋅dx0+cx−ax1−dx1∥dx1∥2(cx−ax1)⋅dx10=cy−ay0−dy0∥dy0∥2(cy−ay0)⋅dy0+cy−ay1−dy1∥dy1∥2(cy−ay1)⋅dy1
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> R C O H R ′ S O 3 N a → Hydrolysis, Δ , D i l . H C l ( R C O R ′ ) + N a C l + S O 2 + H 2 O \begin{CD} RCOHR'SO_3Na @>{\text{Hydrolysis,\\Delta, Dil.HCl}}>> (RCOR')+NaCl+SO_2+ H_2O \end{CD} </math>RCOHR′SO3NaHydrolysis,Δ,Dil.HCl (RCOR′)+NaCl+SO2+H2O
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> ∑ n = 1 ∞ 1 n 2 → ∑ n = 1 ∞ 1 n 2 → ∑ n = 1 ∞ 1 n 2 \sum_{n=1}^\infty \frac{1}{n^2} \to \textstyle \sum_{n=1}^\infty \frac{1}{n^2} \to \displaystyle \sum_{n=1}^\infty \frac{1}{n^2} </math>n=1∑∞n21→∑n=1∞n21→n=1∑∞n21
5. mermaid 图表拓展
6. 拓展链接来构造双链笔记
