forked from mxx1111/mdlook
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmarkdownHelpers.ts
More file actions
114 lines (102 loc) · 3.42 KB
/
Copy pathmarkdownHelpers.ts
File metadata and controls
114 lines (102 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import type { RendererAPI } from '@md/shared/types'
import type { ReadTimeResults } from '@md/shared/utils/readingTime'
import DOMPurify from 'isomorphic-dompurify'
const INFOGRAPHIC_PLACEHOLDER_REGEX = /<!--infographic-start-->[\s\S]*?<!--infographic-end-->/g
const MERMAID_PLACEHOLDER_REGEX = /<!--mermaid-start-->[\s\S]*?<!--mermaid-end-->/g
const PROTECTED_SPAN_REGEX = /<span data-md-protected="(\d+)"><\/span>/g
/**
* DOMPurify v3.1.7+ 会强制移除 foreignObject 内容
* https://github.com/kkomelin/isomorphic-dompurify/pull/290
* https://github.com/cure53/DOMPurify/issues/1152
* 使用占位符方案:在 sanitize 前保护特定内容,sanitize 后还原
* 注意:HTML 注释会被 DOMPurify 移除,所以使用 span 元素作为占位符
*/
function sanitizeHtml(html: string): string {
const protectedContents: string[] = []
// 保护 infographic-diagram(使用注释标记定界,避免嵌套 div 问题)
html = html.replace(
INFOGRAPHIC_PLACEHOLDER_REGEX,
(match) => {
protectedContents.push(match)
return `<span data-md-protected="${protectedContents.length - 1}"></span>`
},
)
// 保护 mermaid-diagram(使用注释标记定界,避免嵌套 div 问题)
html = html.replace(
MERMAID_PLACEHOLDER_REGEX,
(match) => {
protectedContents.push(match)
return `<span data-md-protected="${protectedContents.length - 1}"></span>`
},
)
// XSS 处理
html = DOMPurify.sanitize(html, { ADD_TAGS: [`mp-common-profile`] })
// 还原被保护的内容
html = html.replace(
PROTECTED_SPAN_REGEX,
(_, i) => protectedContents[Number(i)],
)
return html
}
/**
* 渲染 Markdown 内容
* @param raw - 原始 markdown 字符串
* @param renderer - 渲染器 API
* @returns 渲染结果,包含 HTML 和阅读时间
*/
export function renderMarkdown(raw: string, renderer: RendererAPI) {
// 解析 front-matter 和正文
const { markdownContent, readingTime }
= renderer.parseFrontMatterAndContent(raw)
// marked -> html
let html = renderer.renderMarkdownToHtml(markdownContent)
html = sanitizeHtml(html)
return { html, readingTime }
}
/**
* 后处理 HTML 内容
* @param baseHtml - 基础 HTML 字符串
* @param reading - 阅读时间结果
* @param renderer - 渲染器 API
* @returns 处理后的 HTML 字符串
*/
export function postProcessHtml(baseHtml: string, reading: ReadTimeResults, renderer: RendererAPI): string {
// 阅读时间及字数统计
let html = baseHtml
html = renderer.buildReadingTime(reading) + html
// 引用脚注
html += renderer.buildFootnotes()
// 附加的一些 style
html += renderer.buildAddition()
html += `
<style>
.hljs.code__pre > .mac-sign {
display: ${renderer.getOpts().isMacCodeBlock ? `flex` : `none`};
}
</style>
`
html += `
<style>
h2 strong {
color: inherit !important;
}
</style>
`
// 包裹 HTML
return renderer.createContainer(html)
}
/**
* 修改 HTML 内容
* @param content - 原始内容
* @param renderer - 渲染器 API
* @returns 修改后的 HTML 字符串
*/
export function modifyHtmlContent(content: string, renderer: RendererAPI): string {
const {
markdownContent,
readingTime: readingTimeResult,
} = renderer.parseFrontMatterAndContent(content)
let html = renderer.renderMarkdownToHtml(markdownContent)
html = sanitizeHtml(html)
return postProcessHtml(html, readingTimeResult, renderer)
}