forked from mxx1111/mdlook
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinfographic.ts
More file actions
145 lines (123 loc) · 4.89 KB
/
Copy pathinfographic.ts
File metadata and controls
145 lines (123 loc) · 4.89 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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import type { MarkedExtension, Token } from 'marked'
import type { InfographicToken } from '../types/marked-tokens'
import { asDiagramToken, asTextTokenRenderer, isCodeToken } from '../types/marked-tokens'
import { simpleHash } from '../utils/basicHelpers'
import { createSVGCache } from '../utils/svgCache'
interface InfographicOptions {
themeMode?: 'dark' | 'light'
}
type InfographicOptionsSource = InfographicOptions | (() => InfographicOptions | undefined)
// key -> svg(LRU 缓存,上限 50 条)
const svgCache = createSVGCache(50)
const RE_INFOGRAPHIC_START = /^```infographic/m
const RE_INFOGRAPHIC_BLOCK = /^```infographic\r?\n([\s\S]*?)\r?\n```/
async function renderInfographic(containerId: string, code: string, cacheKey: string, options?: InfographicOptions) {
if (typeof window === 'undefined')
return
try {
const { Infographic, setDefaultFont, setFontExtendFactor, exportToSVG } = await import('@antv/infographic')
setFontExtendFactor(1.1)
setDefaultFont('-apple-system-font, "system-ui", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif')
const findContainer = (retries = 5, delay = 100) => {
const container = document.getElementById(containerId)
if (container) {
const isDark = options?.themeMode === 'dark'
// 从 CSS 变量中读取主题颜色
const root = document.documentElement
const computedStyle = getComputedStyle(root)
const primaryColor = computedStyle.getPropertyValue('--md-primary-color').trim()
const backgroundColor = computedStyle.getPropertyValue('--background').trim()
// 转换 HSL 格式
const toHSLString = (variant: string) => {
const vars = variant.split(' ')
if (vars.length === 3)
return `hsl(${vars.join(', ')})`
if (vars.length === 4)
return `hsla(${vars.join(', ')})`
return ''
}
const instance = new Infographic({
container,
svg: {
style: {
width: '100%',
height: '100%',
background: isDark ? '#000' : 'transparent',
},
background: false,
},
theme: isDark ? 'dark' : 'default',
themeConfig: {
colorPrimary: primaryColor || undefined,
colorBg: toHSLString(backgroundColor) || undefined,
},
})
instance.on('loaded', ({ node }) => {
exportToSVG(node, { removeIds: true }).then((svg) => {
container.replaceChildren(svg)
svgCache.set(cacheKey, container.innerHTML)
})
})
instance.render(code)
return
}
if (retries > 0) {
setTimeout(findContainer, delay, retries - 1, delay)
}
}
findContainer()
}
catch (error) {
console.error('Failed to render Infographic:', error)
const container = document.getElementById(containerId)
if (container) {
container.innerHTML = `<div style="color: red; padding: 10px; border: 1px solid red;">Infographic 渲染失败: ${error instanceof Error ? error.message : String(error)}</div>`
}
}
}
function resolveOptions(options?: InfographicOptionsSource): InfographicOptions | undefined {
return typeof options === 'function' ? options() : options
}
export function markedInfographic(options?: InfographicOptionsSource): MarkedExtension {
const className = 'infographic-diagram'
return {
extensions: [
{
name: 'infographic',
level: 'block',
start(src: string) {
return src.match(RE_INFOGRAPHIC_START)?.index
},
tokenizer(src: string) {
const match = RE_INFOGRAPHIC_BLOCK.exec(src)
if (match) {
return {
type: 'infographic',
raw: match[0],
text: match[1].trim(),
}
}
},
renderer: asTextTokenRenderer((token: InfographicToken) => {
const code = token.text
const currentOptions = resolveOptions(options)
const cacheKey = simpleHash(`${code}-${currentOptions?.themeMode || 'light'}`)
// 有缓存直接返回
const cached = svgCache.get(cacheKey)
if (cached) {
return `<!--infographic-start--><div class="${className}" style="width: 100%;">${cached}</div><!--infographic-end-->`
}
// 没有缓存,触发渲染
const id = `infographic-${cacheKey}`
renderInfographic(id, code, cacheKey, currentOptions)
return `<!--infographic-start--><div id="${id}" class="${className}" style="width: 100%;">正在加载 Infographic...</div><!--infographic-end-->`
}),
},
],
walkTokens(token: Token) {
if (isCodeToken(token) && token.lang === 'infographic') {
asDiagramToken<InfographicToken>(token, 'infographic')
}
},
}
}