@peaceroad/markdown-it-renderer-fence 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +440 -117
- package/THIRD_PARTY_NOTICES.md +56 -0
- package/index.js +33 -503
- package/package.json +28 -4
- package/src/custom-highlight/option-validator.js +152 -0
- package/src/custom-highlight/payload-utils.js +43 -0
- package/src/custom-highlight/runtime-utils.js +83 -0
- package/src/custom-highlight/shiki-keyword-rules.js +407 -0
- package/src/custom-highlight/shiki-keyword.js +417 -0
- package/src/entry/custom-highlight-runtime.js +11 -0
- package/src/entry/custom-highlight.js +25 -0
- package/src/entry/markup-highlight.js +40 -0
- package/src/fence/render-api-constants.js +34 -0
- package/src/fence/render-api-provider.js +810 -0
- package/src/fence/render-api-renderer.js +136 -0
- package/src/fence/render-api-runtime.js +712 -0
- package/src/fence/render-api.js +117 -0
- package/src/fence/render-markup.js +161 -0
- package/src/fence/render-shared.js +428 -0
- package/src/utils/attr-utils.js +120 -0
- package/src/utils/pre-code-wrapper-parser.js +114 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyShikiScopeKeyword,
|
|
3
|
+
getShikiRawScopeName,
|
|
4
|
+
normalizeShikiKeywordLangAliasMap,
|
|
5
|
+
resolveShikiKeywordLangForFence,
|
|
6
|
+
} from '../custom-highlight/shiki-keyword.js'
|
|
7
|
+
import {
|
|
8
|
+
escapeHtmlAttr,
|
|
9
|
+
getCustomHighlightPayloadMap,
|
|
10
|
+
renderCustomHighlightPayloadScript as renderPayloadScriptUtil,
|
|
11
|
+
} from '../custom-highlight/payload-utils.js'
|
|
12
|
+
import {
|
|
13
|
+
styleToHighlightCss,
|
|
14
|
+
} from '../custom-highlight/runtime-utils.js'
|
|
15
|
+
import {
|
|
16
|
+
validateCustomHighlightOptions,
|
|
17
|
+
} from '../custom-highlight/option-validator.js'
|
|
18
|
+
import {
|
|
19
|
+
commentLineClass,
|
|
20
|
+
normalizeEmphasisRanges,
|
|
21
|
+
} from './render-shared.js'
|
|
22
|
+
import {
|
|
23
|
+
parsePreCodeWrapper,
|
|
24
|
+
} from '../utils/pre-code-wrapper-parser.js'
|
|
25
|
+
import {
|
|
26
|
+
customHighlightDataEnvKey,
|
|
27
|
+
customHighlightDataScriptId,
|
|
28
|
+
customHighlightPayloadSchemaVersion,
|
|
29
|
+
customHighlightPayloadSupportedVersions,
|
|
30
|
+
fallbackOnDefault,
|
|
31
|
+
} from './render-api-constants.js'
|
|
32
|
+
|
|
33
|
+
const highlightNameUnsafeReg = /[^A-Za-z0-9_-]+/g
|
|
34
|
+
const hyphenMultiReg = /-+/g
|
|
35
|
+
|
|
36
|
+
const defaultCustomHighlightOpt = {
|
|
37
|
+
provider: 'shiki',
|
|
38
|
+
getRanges: null,
|
|
39
|
+
fallback: 'plain',
|
|
40
|
+
fallbackOn: fallbackOnDefault,
|
|
41
|
+
transport: 'env',
|
|
42
|
+
idPrefix: 'hl-',
|
|
43
|
+
lineFeatureStrategy: 'hybrid',
|
|
44
|
+
scopePrefix: 'hl',
|
|
45
|
+
includeScopeStyles: true,
|
|
46
|
+
shikiScopeMode: 'auto',
|
|
47
|
+
shikiKeywordClassifier: null,
|
|
48
|
+
shikiKeywordLangResolver: null,
|
|
49
|
+
shikiKeywordLangAliases: null,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalizeCustomHighlightProvider = (provider) => {
|
|
53
|
+
if (provider === 'custom') return 'custom'
|
|
54
|
+
if (provider === 'hljs') return 'hljs'
|
|
55
|
+
return 'shiki'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const normalizeShikiScopeMode = (mode) => {
|
|
59
|
+
const key = String(mode || '').trim().toLowerCase()
|
|
60
|
+
if (!key) return null
|
|
61
|
+
if (key === 'keyword') return 'keyword'
|
|
62
|
+
if (key === 'semantic') return 'semantic'
|
|
63
|
+
if (key === 'color') return 'color'
|
|
64
|
+
if (key === 'auto') return 'auto'
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const normalizeThemeName = (name) => {
|
|
69
|
+
if (typeof name !== 'string') return ''
|
|
70
|
+
return name.trim()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const normalizeCustomHighlightTheme = (theme) => {
|
|
74
|
+
const singleTheme = normalizeThemeName(theme)
|
|
75
|
+
if (singleTheme) {
|
|
76
|
+
return {
|
|
77
|
+
singleTheme,
|
|
78
|
+
themeVariants: null,
|
|
79
|
+
defaultVariant: '',
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!theme || typeof theme !== 'object' || Array.isArray(theme)) {
|
|
83
|
+
return {
|
|
84
|
+
singleTheme: '',
|
|
85
|
+
themeVariants: null,
|
|
86
|
+
defaultVariant: '',
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const lightTheme = normalizeThemeName(theme.light)
|
|
90
|
+
const darkTheme = normalizeThemeName(theme.dark)
|
|
91
|
+
if (!lightTheme && !darkTheme) {
|
|
92
|
+
return {
|
|
93
|
+
singleTheme: '',
|
|
94
|
+
themeVariants: null,
|
|
95
|
+
defaultVariant: '',
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!lightTheme || !darkTheme) {
|
|
99
|
+
return {
|
|
100
|
+
singleTheme: lightTheme || darkTheme,
|
|
101
|
+
themeVariants: null,
|
|
102
|
+
defaultVariant: '',
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let defaultVariant = String(theme.default || '').trim().toLowerCase()
|
|
106
|
+
if (defaultVariant !== 'light' && defaultVariant !== 'dark') defaultVariant = 'light'
|
|
107
|
+
return {
|
|
108
|
+
singleTheme: '',
|
|
109
|
+
themeVariants: { light: lightTheme, dark: darkTheme },
|
|
110
|
+
defaultVariant,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const buildShikiInternalLangAliasMap = (highlighter) => {
|
|
115
|
+
if (!highlighter || typeof highlighter.getLoadedLanguages !== 'function' || typeof highlighter.getLanguage !== 'function') {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
let loaded
|
|
119
|
+
try {
|
|
120
|
+
loaded = highlighter.getLoadedLanguages()
|
|
121
|
+
} catch (e) {
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
if (!Array.isArray(loaded) || loaded.length === 0) return null
|
|
125
|
+
const rawMap = {}
|
|
126
|
+
for (let i = 0; i < loaded.length; i++) {
|
|
127
|
+
const rawName = String(loaded[i] || '')
|
|
128
|
+
if (!rawName) continue
|
|
129
|
+
let canonical = rawName
|
|
130
|
+
try {
|
|
131
|
+
const langDef = highlighter.getLanguage(rawName)
|
|
132
|
+
if (langDef && typeof langDef.name === 'string' && langDef.name) canonical = langDef.name
|
|
133
|
+
} catch (e) {}
|
|
134
|
+
rawMap[rawName] = canonical
|
|
135
|
+
}
|
|
136
|
+
return normalizeShikiKeywordLangAliasMap(rawMap)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const normalizeCustomHighlightOpt = (opt = {}) => {
|
|
140
|
+
const rawOpt = (opt && typeof opt === 'object') ? opt : {}
|
|
141
|
+
const next = Object.assign({}, defaultCustomHighlightOpt, rawOpt)
|
|
142
|
+
next.provider = normalizeCustomHighlightProvider(next.provider)
|
|
143
|
+
next.fallback = (next.fallback === 'markup') ? 'markup' : 'plain'
|
|
144
|
+
next.transport = (next.transport === 'inline-script') ? 'inline-script' : 'env'
|
|
145
|
+
next.lineFeatureStrategy = (next.lineFeatureStrategy === 'disable') ? 'disable' : 'hybrid'
|
|
146
|
+
next.idPrefix = String(next.idPrefix || 'hl-')
|
|
147
|
+
next.scopePrefix = next.scopePrefix == null ? 'hl' : String(next.scopePrefix)
|
|
148
|
+
next.includeScopeStyles = next.includeScopeStyles !== false
|
|
149
|
+
next.shikiScopeMode = normalizeShikiScopeMode(next.shikiScopeMode) || 'auto'
|
|
150
|
+
const normalizedTheme = normalizeCustomHighlightTheme(next.theme)
|
|
151
|
+
next._singleTheme = normalizedTheme.singleTheme
|
|
152
|
+
next._themeVariants = normalizedTheme.themeVariants
|
|
153
|
+
next._themeVariantDefault = normalizedTheme.defaultVariant
|
|
154
|
+
if (normalizedTheme.themeVariants) {
|
|
155
|
+
next.theme = {
|
|
156
|
+
light: normalizedTheme.themeVariants.light,
|
|
157
|
+
dark: normalizedTheme.themeVariants.dark,
|
|
158
|
+
default: normalizedTheme.defaultVariant,
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
next.theme = normalizedTheme.singleTheme || null
|
|
162
|
+
}
|
|
163
|
+
if (typeof next.shikiKeywordClassifier !== 'function') next.shikiKeywordClassifier = null
|
|
164
|
+
if (typeof next.shikiKeywordLangResolver !== 'function') next.shikiKeywordLangResolver = null
|
|
165
|
+
next.shikiKeywordLangAliases = normalizeShikiKeywordLangAliasMap(next.shikiKeywordLangAliases)
|
|
166
|
+
next._shikiInternalLangAliasMap =
|
|
167
|
+
(next.provider === 'shiki' && next.shikiScopeMode === 'keyword')
|
|
168
|
+
? buildShikiInternalLangAliasMap(next.highlighter)
|
|
169
|
+
: null
|
|
170
|
+
const fallbackOn = Array.isArray(next.fallbackOn) ? next.fallbackOn : fallbackOnDefault
|
|
171
|
+
next._fallbackOnSet = new Set(fallbackOn)
|
|
172
|
+
validateCustomHighlightOptions(rawOpt, next)
|
|
173
|
+
return next
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const isNormalizedCustomHighlightOpt = (opt) => {
|
|
177
|
+
return !!(opt && typeof opt === 'object' && opt._fallbackOnSet instanceof Set)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const shouldApplyApiFallbackForReason = (chOpt, reason) => {
|
|
181
|
+
if (!reason) return true
|
|
182
|
+
if (!chOpt || !chOpt._fallbackOnSet) return true
|
|
183
|
+
return chOpt._fallbackOnSet.has(reason)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const sanitizeHighlightName = (name, prefix = '') => {
|
|
187
|
+
const prefixBase = String(prefix == null ? '' : prefix).replace(highlightNameUnsafeReg, '-').replace(hyphenMultiReg, '-').replace(/^-+|-+$/g, '')
|
|
188
|
+
const raw = String(name || '')
|
|
189
|
+
let safe = raw.replace(highlightNameUnsafeReg, '-').replace(hyphenMultiReg, '-').replace(/^-+|-+$/g, '')
|
|
190
|
+
if (!safe) safe = 'scope'
|
|
191
|
+
if (/^[0-9]/.test(safe)) safe = 'x-' + safe
|
|
192
|
+
if (safe.startsWith('--')) safe = safe.slice(2) || 'scope'
|
|
193
|
+
return prefixBase ? `${prefixBase}-${safe}` : safe
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const uniqueHighlightName = (base, usedMap) => {
|
|
197
|
+
if (!usedMap.has(base)) {
|
|
198
|
+
usedMap.set(base, 1)
|
|
199
|
+
return base
|
|
200
|
+
}
|
|
201
|
+
let seq = usedMap.get(base) + 1
|
|
202
|
+
usedMap.set(base, seq)
|
|
203
|
+
return `${base}-${seq}`
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const normalizeScopeStyle = (style) => {
|
|
207
|
+
if (!style || typeof style !== 'object') return null
|
|
208
|
+
const next = {}
|
|
209
|
+
let hasValue = false
|
|
210
|
+
if (typeof style.color === 'string' && style.color) {
|
|
211
|
+
next.color = style.color
|
|
212
|
+
hasValue = true
|
|
213
|
+
}
|
|
214
|
+
if (typeof style.backgroundColor === 'string' && style.backgroundColor) {
|
|
215
|
+
next.backgroundColor = style.backgroundColor
|
|
216
|
+
hasValue = true
|
|
217
|
+
}
|
|
218
|
+
if (typeof style.textDecoration === 'string' && style.textDecoration) {
|
|
219
|
+
next.textDecoration = style.textDecoration
|
|
220
|
+
hasValue = true
|
|
221
|
+
}
|
|
222
|
+
if (typeof style.textShadow === 'string' && style.textShadow) {
|
|
223
|
+
next.textShadow = style.textShadow
|
|
224
|
+
hasValue = true
|
|
225
|
+
}
|
|
226
|
+
return hasValue ? next : null
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const getScopeStyleKey = (style) => {
|
|
230
|
+
if (!style) return ''
|
|
231
|
+
let key = ''
|
|
232
|
+
if (style.color) key += `c:${style.color};`
|
|
233
|
+
if (style.backgroundColor) key += `bg:${style.backgroundColor};`
|
|
234
|
+
if (style.textDecoration) key += `td:${style.textDecoration};`
|
|
235
|
+
if (style.textShadow) key += `ts:${style.textShadow};`
|
|
236
|
+
return key
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const sameStringArray = (a, b) => {
|
|
240
|
+
if (a === b) return true
|
|
241
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false
|
|
242
|
+
for (let i = 0; i < a.length; i++) {
|
|
243
|
+
if (a[i] !== b[i]) return false
|
|
244
|
+
}
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const sameRangeTuples = (a, b) => {
|
|
249
|
+
if (a === b) return true
|
|
250
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false
|
|
251
|
+
for (let i = 0; i < a.length; i++) {
|
|
252
|
+
const left = a[i]
|
|
253
|
+
const right = b[i]
|
|
254
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false
|
|
255
|
+
for (let j = 0; j < left.length; j++) {
|
|
256
|
+
if (left[j] !== right[j]) return false
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const sameScopeStyles = (a, b) => {
|
|
263
|
+
const left = Array.isArray(a) ? a : null
|
|
264
|
+
const right = Array.isArray(b) ? b : null
|
|
265
|
+
if (left === right) return true
|
|
266
|
+
if (!left || !right || left.length !== right.length) return false
|
|
267
|
+
for (let i = 0; i < left.length; i++) {
|
|
268
|
+
const lv = left[i]
|
|
269
|
+
const rv = right[i]
|
|
270
|
+
if (lv === rv) continue
|
|
271
|
+
if (!lv || !rv || typeof lv !== 'object' || typeof rv !== 'object') return false
|
|
272
|
+
const lk = Object.keys(lv)
|
|
273
|
+
const rk = Object.keys(rv)
|
|
274
|
+
if (lk.length !== rk.length) return false
|
|
275
|
+
for (let j = 0; j < lk.length; j++) {
|
|
276
|
+
const key = lk[j]
|
|
277
|
+
if (lv[key] !== rv[key]) return false
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return true
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const getLogicalLinesAndOffsets = (text) => {
|
|
284
|
+
const rawLines = text.split('\n')
|
|
285
|
+
const logicalLineCount = rawLines.length > 0 && rawLines[rawLines.length - 1] === '' ? rawLines.length - 1 : rawLines.length
|
|
286
|
+
const lines = new Array(logicalLineCount)
|
|
287
|
+
const offsets = new Array(logicalLineCount)
|
|
288
|
+
let cursor = 0
|
|
289
|
+
for (let i = 0; i < logicalLineCount; i++) {
|
|
290
|
+
const line = rawLines[i]
|
|
291
|
+
lines[i] = line
|
|
292
|
+
offsets[i] = [cursor, cursor + line.length]
|
|
293
|
+
cursor += line.length + 1
|
|
294
|
+
}
|
|
295
|
+
return { lines, offsets }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const getShikiTokenStyle = (token) => {
|
|
299
|
+
const style = {}
|
|
300
|
+
if (typeof token.color === 'string' && token.color) style.color = token.color
|
|
301
|
+
const fontStyle = Number.isFinite(token.fontStyle) ? token.fontStyle : 0
|
|
302
|
+
if (fontStyle & 1) style.fontStyle = 'italic'
|
|
303
|
+
if (fontStyle & 2) style.fontWeight = '700'
|
|
304
|
+
if (fontStyle & 4) style.textDecoration = 'underline'
|
|
305
|
+
return normalizeScopeStyle(style)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const toShikiTokenLines = (result) => {
|
|
309
|
+
if (!result) return null
|
|
310
|
+
if (Array.isArray(result)) return result
|
|
311
|
+
if (Array.isArray(result.tokens)) return result.tokens
|
|
312
|
+
return null
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const highlightClassReg = /\bclass\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/i
|
|
316
|
+
|
|
317
|
+
const decodeHtmlEntity = (entity) => {
|
|
318
|
+
if (entity === '<') return '<'
|
|
319
|
+
if (entity === '>') return '>'
|
|
320
|
+
if (entity === '&') return '&'
|
|
321
|
+
if (entity === '"') return '"'
|
|
322
|
+
if (entity === ''' || entity === ''') return '\''
|
|
323
|
+
if (entity.startsWith('&#x') || entity.startsWith('&#X')) {
|
|
324
|
+
const n = Number.parseInt(entity.slice(3, -1), 16)
|
|
325
|
+
if (!Number.isFinite(n)) return entity
|
|
326
|
+
try {
|
|
327
|
+
return String.fromCodePoint(n)
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return entity
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (entity.startsWith('&#')) {
|
|
333
|
+
const n = Number.parseInt(entity.slice(2, -1), 10)
|
|
334
|
+
if (!Number.isFinite(n)) return entity
|
|
335
|
+
try {
|
|
336
|
+
return String.fromCodePoint(n)
|
|
337
|
+
} catch (e) {
|
|
338
|
+
return entity
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return entity
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const decodeHtmlEntities = (text) => {
|
|
345
|
+
if (!text || text.indexOf('&') === -1) return text
|
|
346
|
+
return text.replace(/&(?:#x[0-9a-fA-F]+|#\d+|[A-Za-z][A-Za-z0-9]+);/g, decodeHtmlEntity)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const hljsScopeToName = (scope, lang) => {
|
|
350
|
+
const raw = String(scope || '').trim()
|
|
351
|
+
if (raw) {
|
|
352
|
+
if (raw.startsWith('hljs-')) return raw
|
|
353
|
+
return 'hljs-' + raw.replace(/\./g, '-')
|
|
354
|
+
}
|
|
355
|
+
return `hljs-${lang || 'plain'}`
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const getHljsScopeFromClassText = (classText, lang) => {
|
|
359
|
+
if (!classText) return null
|
|
360
|
+
const classes = String(classText).trim().split(/\s+/).filter(Boolean)
|
|
361
|
+
if (!classes.length) return null
|
|
362
|
+
for (let i = 0; i < classes.length; i++) {
|
|
363
|
+
const name = classes[i]
|
|
364
|
+
if (name.startsWith('hljs-') && name !== 'hljs') return name
|
|
365
|
+
}
|
|
366
|
+
if (classes[0] && classes[0] !== 'hljs') return hljsScopeToName(classes[0], lang)
|
|
367
|
+
return hljsScopeToName('', lang)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const createRangesFromHighlightHtml = (lang, html) => {
|
|
371
|
+
const entries = []
|
|
372
|
+
if (!html) return entries
|
|
373
|
+
const stack = []
|
|
374
|
+
const source = String(html)
|
|
375
|
+
let cursor = 0
|
|
376
|
+
let i = 0
|
|
377
|
+
while (i < source.length) {
|
|
378
|
+
const lt = source.indexOf('<', i)
|
|
379
|
+
const textEnd = lt === -1 ? source.length : lt
|
|
380
|
+
if (textEnd > i) {
|
|
381
|
+
const rawText = source.slice(i, textEnd)
|
|
382
|
+
const decoded = decodeHtmlEntities(rawText)
|
|
383
|
+
const len = decoded.length
|
|
384
|
+
if (len > 0) {
|
|
385
|
+
const scope = stack.length ? stack[stack.length - 1] : null
|
|
386
|
+
if (scope) entries.push({ scope, start: cursor, end: cursor + len })
|
|
387
|
+
cursor += len
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (lt === -1) break
|
|
391
|
+
const gt = source.indexOf('>', lt + 1)
|
|
392
|
+
if (gt === -1) {
|
|
393
|
+
const rest = decodeHtmlEntities(source.slice(lt))
|
|
394
|
+
const len = rest.length
|
|
395
|
+
if (len > 0) {
|
|
396
|
+
const scope = stack.length ? stack[stack.length - 1] : null
|
|
397
|
+
if (scope) entries.push({ scope, start: cursor, end: cursor + len })
|
|
398
|
+
cursor += len
|
|
399
|
+
}
|
|
400
|
+
break
|
|
401
|
+
}
|
|
402
|
+
const body = source.slice(lt + 1, gt).trim()
|
|
403
|
+
if (body.startsWith('/')) {
|
|
404
|
+
if (body.slice(1).trim().toLowerCase().startsWith('span') && stack.length) stack.pop()
|
|
405
|
+
} else {
|
|
406
|
+
const space = body.indexOf(' ')
|
|
407
|
+
const name = (space === -1 ? body : body.slice(0, space)).replace(/\/$/, '').toLowerCase()
|
|
408
|
+
if (name === 'span') {
|
|
409
|
+
const classMatch = highlightClassReg.exec(body)
|
|
410
|
+
const classText = classMatch ? (classMatch[1] ?? classMatch[2] ?? classMatch[3] ?? '') : ''
|
|
411
|
+
stack.push(getHljsScopeFromClassText(classText, lang))
|
|
412
|
+
} else if (name === 'br') {
|
|
413
|
+
const scope = stack.length ? stack[stack.length - 1] : null
|
|
414
|
+
if (scope) entries.push({ scope, start: cursor, end: cursor + 1 })
|
|
415
|
+
cursor += 1
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
i = gt + 1
|
|
419
|
+
}
|
|
420
|
+
return entries
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const walkHljsEmitterNode = (node, activeScope, cursorRef, entries, lang) => {
|
|
424
|
+
if (typeof node === 'string') {
|
|
425
|
+
const len = node.length
|
|
426
|
+
if (len > 0 && activeScope) entries.push({ scope: activeScope, start: cursorRef.value, end: cursorRef.value + len })
|
|
427
|
+
cursorRef.value += len
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
if (!node || typeof node !== 'object' || !Array.isArray(node.children)) return
|
|
431
|
+
const nextScope = (typeof node.scope === 'string' && node.scope)
|
|
432
|
+
? hljsScopeToName(node.scope, lang)
|
|
433
|
+
: activeScope
|
|
434
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
435
|
+
walkHljsEmitterNode(node.children[i], nextScope, cursorRef, entries, lang)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const createRangesFromHljsResult = (lang, result) => {
|
|
440
|
+
if (Array.isArray(result) || (result && Array.isArray(result.ranges))) {
|
|
441
|
+
return normalizeCustomProviderRanges(result)
|
|
442
|
+
}
|
|
443
|
+
if (result && result._emitter && result._emitter.rootNode) {
|
|
444
|
+
const entries = []
|
|
445
|
+
const cursorRef = { value: 0 }
|
|
446
|
+
walkHljsEmitterNode(result._emitter.rootNode, null, cursorRef, entries, lang)
|
|
447
|
+
return entries
|
|
448
|
+
}
|
|
449
|
+
let html = ''
|
|
450
|
+
if (typeof result === 'string') html = result
|
|
451
|
+
else if (result && typeof result.value === 'string') html = result.value
|
|
452
|
+
if (!html) return []
|
|
453
|
+
const preMatch = parsePreCodeWrapper(html)
|
|
454
|
+
if (preMatch) html = preMatch.content
|
|
455
|
+
return createRangesFromHighlightHtml(lang, html)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const hasShikiOffsets = (tokenLines) => {
|
|
459
|
+
for (let i = 0; i < tokenLines.length; i++) {
|
|
460
|
+
const line = tokenLines[i]
|
|
461
|
+
if (!Array.isArray(line)) continue
|
|
462
|
+
for (let t = 0; t < line.length; t++) {
|
|
463
|
+
const tok = line[t]
|
|
464
|
+
if (tok && Number.isFinite(tok.offset)) return true
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return false
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const getShikiScopeName = (tok, lang, style, opt, preResolvedKeywordLang = '') => {
|
|
471
|
+
const scopeMode = (opt && opt.shikiScopeMode) || 'auto'
|
|
472
|
+
const rawScope = getShikiRawScopeName(tok)
|
|
473
|
+
let customKeywordName = null
|
|
474
|
+
const isKeywordScopeMode = scopeMode === 'keyword'
|
|
475
|
+
if (isKeywordScopeMode && opt && typeof opt.shikiKeywordClassifier === 'function') {
|
|
476
|
+
try {
|
|
477
|
+
const customName = opt.shikiKeywordClassifier(rawScope, tok, { lang, style, scopeMode })
|
|
478
|
+
if (customName != null && customName !== '') customKeywordName = String(customName)
|
|
479
|
+
} catch (e) {}
|
|
480
|
+
}
|
|
481
|
+
if (isKeywordScopeMode) {
|
|
482
|
+
if (customKeywordName) return 'shiki-' + customKeywordName
|
|
483
|
+
const bucket = classifyShikiScopeKeyword(rawScope, tok, lang, opt, preResolvedKeywordLang)
|
|
484
|
+
return 'shiki-' + bucket
|
|
485
|
+
}
|
|
486
|
+
if (scopeMode === 'semantic') {
|
|
487
|
+
if (rawScope) return 'shiki-' + rawScope
|
|
488
|
+
if (style && style.color) {
|
|
489
|
+
const colorKey = style.color.toLowerCase().replace(highlightNameUnsafeReg, '-')
|
|
490
|
+
const fs = Number.isFinite(tok.fontStyle) ? `-f${tok.fontStyle}` : ''
|
|
491
|
+
return `shiki-${colorKey}${fs}`
|
|
492
|
+
}
|
|
493
|
+
return `shiki-${lang || 'plain'}`
|
|
494
|
+
}
|
|
495
|
+
if (scopeMode === 'auto') {
|
|
496
|
+
if (rawScope) return 'shiki-' + rawScope
|
|
497
|
+
}
|
|
498
|
+
if (scopeMode === 'color') {
|
|
499
|
+
if (style && style.color) {
|
|
500
|
+
const colorKey = style.color.toLowerCase().replace(highlightNameUnsafeReg, '-')
|
|
501
|
+
const fs = Number.isFinite(tok.fontStyle) ? `-f${tok.fontStyle}` : ''
|
|
502
|
+
return `shiki-${colorKey}${fs}`
|
|
503
|
+
}
|
|
504
|
+
if (rawScope) return 'shiki-' + rawScope
|
|
505
|
+
return `shiki-${lang || 'plain'}`
|
|
506
|
+
}
|
|
507
|
+
if (rawScope) return 'shiki-' + rawScope
|
|
508
|
+
if (style && style.color) {
|
|
509
|
+
const colorKey = style.color.toLowerCase().replace(highlightNameUnsafeReg, '-')
|
|
510
|
+
const fs = Number.isFinite(tok.fontStyle) ? `-f${tok.fontStyle}` : ''
|
|
511
|
+
return `shiki-${colorKey}${fs}`
|
|
512
|
+
}
|
|
513
|
+
return `shiki-${lang || 'plain'}`
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const createRangesFromShikiTokens = (lang, tokenLines, opt) => {
|
|
517
|
+
if (!Array.isArray(tokenLines)) throw new Error('Invalid shiki token payload')
|
|
518
|
+
const entries = []
|
|
519
|
+
const hasOffsets = hasShikiOffsets(tokenLines)
|
|
520
|
+
const scopeMode = (opt && opt.shikiScopeMode) || 'auto'
|
|
521
|
+
const includeScopeStyles = !opt || opt.includeScopeStyles !== false
|
|
522
|
+
const needStyleForScopeName =
|
|
523
|
+
scopeMode === 'auto' ||
|
|
524
|
+
scopeMode === 'color' ||
|
|
525
|
+
scopeMode === 'semantic' ||
|
|
526
|
+
(scopeMode === 'keyword' && !!(opt && typeof opt.shikiKeywordClassifier === 'function'))
|
|
527
|
+
const needTokenStyle = includeScopeStyles || needStyleForScopeName
|
|
528
|
+
let preResolvedKeywordLang = ''
|
|
529
|
+
if (scopeMode === 'keyword' && (!opt || typeof opt.shikiKeywordLangResolver !== 'function')) {
|
|
530
|
+
preResolvedKeywordLang = resolveShikiKeywordLangForFence(lang, opt)
|
|
531
|
+
}
|
|
532
|
+
let cursor = 0
|
|
533
|
+
for (let i = 0; i < tokenLines.length; i++) {
|
|
534
|
+
const line = tokenLines[i]
|
|
535
|
+
if (!Array.isArray(line)) throw new Error('Invalid shiki token line')
|
|
536
|
+
for (let t = 0; t < line.length; t++) {
|
|
537
|
+
const tok = line[t]
|
|
538
|
+
if (!tok || typeof tok !== 'object') continue
|
|
539
|
+
const content = String(tok.content || '')
|
|
540
|
+
if (!content) continue
|
|
541
|
+
const hasOffset = Number.isFinite(tok.offset)
|
|
542
|
+
const start = hasOffset ? tok.offset : cursor
|
|
543
|
+
const end = start + content.length
|
|
544
|
+
const style = needTokenStyle ? getShikiTokenStyle(tok) : null
|
|
545
|
+
const scope = getShikiScopeName(tok, lang, style, opt, preResolvedKeywordLang)
|
|
546
|
+
entries.push({ scope, start, end, style })
|
|
547
|
+
cursor = end
|
|
548
|
+
}
|
|
549
|
+
if (!hasOffsets && i < tokenLines.length - 1) cursor += 1
|
|
550
|
+
}
|
|
551
|
+
return entries
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const normalizeCustomProviderRanges = (result) => {
|
|
555
|
+
const source = Array.isArray(result) ? { ranges: result } : result
|
|
556
|
+
if (!source || !Array.isArray(source.ranges)) throw new Error('custom getRanges must return ranges')
|
|
557
|
+
const scopes = Array.isArray(source.scopes) ? source.scopes : null
|
|
558
|
+
const scopeStyles = source.scopeStyles && typeof source.scopeStyles === 'object' ? source.scopeStyles : null
|
|
559
|
+
const entries = []
|
|
560
|
+
for (const range of source.ranges) {
|
|
561
|
+
let scope
|
|
562
|
+
let start
|
|
563
|
+
let end
|
|
564
|
+
let style
|
|
565
|
+
if (Array.isArray(range)) {
|
|
566
|
+
scope = range[0]
|
|
567
|
+
start = range[1]
|
|
568
|
+
end = range[2]
|
|
569
|
+
style = range[3]
|
|
570
|
+
} else if (range && typeof range === 'object') {
|
|
571
|
+
scope = range.scope
|
|
572
|
+
start = range.start
|
|
573
|
+
end = range.end
|
|
574
|
+
style = range.style
|
|
575
|
+
} else {
|
|
576
|
+
continue
|
|
577
|
+
}
|
|
578
|
+
if (typeof scope === 'number' && scopes && scopes[scope] != null) scope = scopes[scope]
|
|
579
|
+
if (scope == null) continue
|
|
580
|
+
if (!style && scopeStyles) {
|
|
581
|
+
if (typeof scope === 'number' && Array.isArray(scopeStyles)) style = scopeStyles[scope]
|
|
582
|
+
else style = scopeStyles[String(scope)]
|
|
583
|
+
}
|
|
584
|
+
entries.push({ scope: String(scope), start, end, style: normalizeScopeStyle(style) })
|
|
585
|
+
}
|
|
586
|
+
return entries
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const buildApiPayload = (entries, text, lang, engine, scopePrefix, includeScopeStyles = true) => {
|
|
590
|
+
const scopes = []
|
|
591
|
+
const ranges = []
|
|
592
|
+
const scopeStyles = includeScopeStyles ? [] : null
|
|
593
|
+
let hasScopeStyles = false
|
|
594
|
+
const scopeKeyToIndex = new Map()
|
|
595
|
+
const usedScopeNames = new Map()
|
|
596
|
+
const textLength = text.length
|
|
597
|
+
for (const entry of entries) {
|
|
598
|
+
if (!entry) continue
|
|
599
|
+
const start = Number(entry.start)
|
|
600
|
+
const end = Number(entry.end)
|
|
601
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end <= start || end > textLength) {
|
|
602
|
+
throw new Error(`Invalid range [${entry.start}, ${entry.end}]`)
|
|
603
|
+
}
|
|
604
|
+
const style = includeScopeStyles ? normalizeScopeStyle(entry.style) : null
|
|
605
|
+
const key = includeScopeStyles
|
|
606
|
+
? `${String(entry.scope)}\u0001${getScopeStyleKey(style)}`
|
|
607
|
+
: String(entry.scope)
|
|
608
|
+
let scopeIndex = scopeKeyToIndex.get(key)
|
|
609
|
+
if (scopeIndex === undefined) {
|
|
610
|
+
const baseName = sanitizeHighlightName(entry.scope, scopePrefix)
|
|
611
|
+
const scopeName = uniqueHighlightName(baseName, usedScopeNames)
|
|
612
|
+
scopeIndex = scopes.length
|
|
613
|
+
scopes.push(scopeName)
|
|
614
|
+
if (scopeStyles) {
|
|
615
|
+
scopeStyles.push(style)
|
|
616
|
+
if (style) hasScopeStyles = true
|
|
617
|
+
}
|
|
618
|
+
scopeKeyToIndex.set(key, scopeIndex)
|
|
619
|
+
}
|
|
620
|
+
ranges.push([scopeIndex, start, end])
|
|
621
|
+
}
|
|
622
|
+
const payload = {
|
|
623
|
+
v: customHighlightPayloadSchemaVersion,
|
|
624
|
+
engine,
|
|
625
|
+
lang: lang || '',
|
|
626
|
+
offsetEncoding: 'utf16',
|
|
627
|
+
newline: 'lf',
|
|
628
|
+
textLength,
|
|
629
|
+
scopes,
|
|
630
|
+
ranges,
|
|
631
|
+
}
|
|
632
|
+
if (scopeStyles && hasScopeStyles) payload.scopeStyles = scopeStyles
|
|
633
|
+
return payload
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const pushLineFeatureRanges = (entries, content, emphasizeLines, setEmphasizeLines, commentMarkValue) => {
|
|
637
|
+
const needsEmphasis = !!(setEmphasizeLines && emphasizeLines && emphasizeLines.length > 0)
|
|
638
|
+
const needsCommentScan = !!(commentMarkValue && content.indexOf(commentMarkValue) !== -1)
|
|
639
|
+
if (!needsEmphasis && !needsCommentScan) return
|
|
640
|
+
const { lines, offsets } = getLogicalLinesAndOffsets(content)
|
|
641
|
+
const maxLine = lines.length
|
|
642
|
+
if (needsEmphasis) {
|
|
643
|
+
const normalized = normalizeEmphasisRanges(emphasizeLines, maxLine)
|
|
644
|
+
for (const [s, e] of normalized) {
|
|
645
|
+
const start = offsets[s - 1][0]
|
|
646
|
+
const end = offsets[e - 1][1]
|
|
647
|
+
if (end > start) entries.push({ scope: 'pre-lines-emphasis', start, end })
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (needsCommentScan) {
|
|
651
|
+
for (let i = 0; i < lines.length; i++) {
|
|
652
|
+
if (lines[i].trimStart().startsWith(commentMarkValue)) {
|
|
653
|
+
const [start, end] = offsets[i]
|
|
654
|
+
if (end > start) entries.push({ scope: commentLineClass, start, end })
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const buildShikiTokenOption = (chOpt, targetLang, themeOverride = '') => {
|
|
661
|
+
const base = chOpt && chOpt._shikiTokenOptionBase
|
|
662
|
+
const option = base ? Object.assign({ lang: targetLang }, base) : { lang: targetLang }
|
|
663
|
+
if (themeOverride) option.theme = themeOverride
|
|
664
|
+
return option
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const getApiProviderEntries = (token, lang, chOpt, md, env, override) => {
|
|
668
|
+
const context = { token, md, env, option: chOpt }
|
|
669
|
+
if (chOpt.provider === 'custom') {
|
|
670
|
+
const getRanges = chOpt._customGetRanges
|
|
671
|
+
if (typeof getRanges !== 'function') throw new Error('customHighlight.getRanges must be a function')
|
|
672
|
+
const result = getRanges(token.content, lang, context)
|
|
673
|
+
if (result && typeof result.then === 'function') throw new Error('customHighlight.getRanges must be synchronous')
|
|
674
|
+
return normalizeCustomProviderRanges(result)
|
|
675
|
+
}
|
|
676
|
+
if (chOpt.provider === 'hljs') {
|
|
677
|
+
const highlightFn = chOpt._hljsHighlightFn
|
|
678
|
+
if (typeof highlightFn !== 'function') {
|
|
679
|
+
throw new Error('customHighlight.hljsHighlight (or customHighlight.highlight / md.options.highlight) must be a function when provider=hljs')
|
|
680
|
+
}
|
|
681
|
+
const resolvedLang = lang || chOpt.defaultLang || ''
|
|
682
|
+
let result
|
|
683
|
+
try {
|
|
684
|
+
result = highlightFn(token.content, resolvedLang, context)
|
|
685
|
+
} catch (err) {
|
|
686
|
+
if (resolvedLang && resolvedLang !== 'plaintext') {
|
|
687
|
+
result = highlightFn(token.content, 'plaintext', context)
|
|
688
|
+
} else {
|
|
689
|
+
throw err
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (result && typeof result.then === 'function') throw new Error('customHighlight hljs provider must be synchronous')
|
|
693
|
+
return createRangesFromHljsResult(resolvedLang, result)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (chOpt._hasShikiHighlighter === false || !chOpt.highlighter || typeof chOpt.highlighter.codeToTokens !== 'function') {
|
|
697
|
+
throw new Error('customHighlight.highlighter.codeToTokens must be a function when provider=shiki')
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const resolvedLang = lang || chOpt.defaultLang || 'text'
|
|
701
|
+
const overrideTheme = normalizeThemeName(override && override.theme)
|
|
702
|
+
const themeForPass = overrideTheme || chOpt._singleTheme
|
|
703
|
+
let tokenResult
|
|
704
|
+
try {
|
|
705
|
+
tokenResult = chOpt.highlighter.codeToTokens(token.content, buildShikiTokenOption(chOpt, resolvedLang, themeForPass))
|
|
706
|
+
} catch (err) {
|
|
707
|
+
if (resolvedLang !== 'text') {
|
|
708
|
+
tokenResult = chOpt.highlighter.codeToTokens(token.content, buildShikiTokenOption(chOpt, 'text', themeForPass))
|
|
709
|
+
} else {
|
|
710
|
+
throw err
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (tokenResult && typeof tokenResult.then === 'function') throw new Error('customHighlight highlighter provider must be synchronous')
|
|
714
|
+
const tokenLines = toShikiTokenLines(tokenResult)
|
|
715
|
+
return createRangesFromShikiTokens(resolvedLang, tokenLines, chOpt)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const buildApiPayloadVariantRecord = (variantPayload, basePayload) => {
|
|
719
|
+
const record = {}
|
|
720
|
+
if (!sameStringArray(variantPayload.scopes, basePayload.scopes)) record.scopes = variantPayload.scopes
|
|
721
|
+
if (!sameRangeTuples(variantPayload.ranges, basePayload.ranges)) record.ranges = variantPayload.ranges
|
|
722
|
+
const baseStyles = Array.isArray(basePayload.scopeStyles) ? basePayload.scopeStyles : null
|
|
723
|
+
const variantStyles = Array.isArray(variantPayload.scopeStyles) ? variantPayload.scopeStyles : null
|
|
724
|
+
if (!sameScopeStyles(variantStyles, baseStyles)) {
|
|
725
|
+
if (variantStyles) record.scopeStyles = variantStyles
|
|
726
|
+
else if (baseStyles) record.scopeStyles = []
|
|
727
|
+
}
|
|
728
|
+
return record
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const createShikiDualThemePayloadForFence = (token, lang, opt, md, env, emphasizeLines, commentMarkValue) => {
|
|
732
|
+
const chOpt = opt.customHighlight
|
|
733
|
+
const themeVariants = chOpt._themeVariants
|
|
734
|
+
const lineFeatureEntries = []
|
|
735
|
+
if (chOpt.lineFeatureStrategy !== 'disable') {
|
|
736
|
+
pushLineFeatureRanges(lineFeatureEntries, token.content, emphasizeLines, opt.setEmphasizeLines, commentMarkValue)
|
|
737
|
+
}
|
|
738
|
+
const buildVariantPayload = (themeName) => {
|
|
739
|
+
const entries = getApiProviderEntries(token, lang, chOpt, md, env, { theme: themeName })
|
|
740
|
+
if (lineFeatureEntries.length) entries.push(...lineFeatureEntries)
|
|
741
|
+
return buildApiPayload(entries, token.content, lang, chOpt.provider, chOpt.scopePrefix, true)
|
|
742
|
+
}
|
|
743
|
+
const variants = {
|
|
744
|
+
light: buildVariantPayload(themeVariants.light),
|
|
745
|
+
dark: buildVariantPayload(themeVariants.dark),
|
|
746
|
+
}
|
|
747
|
+
const defaultVariant = chOpt._themeVariantDefault === 'dark' ? 'dark' : 'light'
|
|
748
|
+
const otherVariant = defaultVariant === 'dark' ? 'light' : 'dark'
|
|
749
|
+
const basePayload = variants[defaultVariant]
|
|
750
|
+
const payload = Object.assign({}, basePayload)
|
|
751
|
+
payload.defaultVariant = defaultVariant
|
|
752
|
+
payload.variants = {
|
|
753
|
+
[defaultVariant]: {},
|
|
754
|
+
[otherVariant]: buildApiPayloadVariantRecord(variants[otherVariant], basePayload),
|
|
755
|
+
}
|
|
756
|
+
return payload
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const createApiPayloadForFence = (token, lang, opt, md, env, emphasizeLines, commentMarkValue) => {
|
|
760
|
+
const chOpt = opt.customHighlight
|
|
761
|
+
if (chOpt.provider === 'shiki' && chOpt.includeScopeStyles !== false && chOpt._themeVariants) {
|
|
762
|
+
return createShikiDualThemePayloadForFence(token, lang, opt, md, env, emphasizeLines, commentMarkValue)
|
|
763
|
+
}
|
|
764
|
+
const entries = getApiProviderEntries(token, lang, chOpt, md, env)
|
|
765
|
+
if (chOpt.lineFeatureStrategy !== 'disable') {
|
|
766
|
+
pushLineFeatureRanges(entries, token.content, emphasizeLines, opt.setEmphasizeLines, commentMarkValue)
|
|
767
|
+
}
|
|
768
|
+
return buildApiPayload(entries, token.content, lang, chOpt.provider, chOpt.scopePrefix, chOpt.includeScopeStyles)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const renderCustomHighlightPayloadScript = (env, scriptId = customHighlightDataScriptId) => {
|
|
772
|
+
return renderPayloadScriptUtil(env, scriptId, customHighlightDataEnvKey)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const renderCustomHighlightScopeStyleTag = (env, styleTagId = 'pre-highlight-scope-style') => {
|
|
776
|
+
const map = getCustomHighlightPayloadMap(env, customHighlightDataEnvKey)
|
|
777
|
+
const keys = Object.keys(map)
|
|
778
|
+
if (!keys.length) return ''
|
|
779
|
+
const styleId = escapeHtmlAttr(styleTagId || 'pre-highlight-scope-style')
|
|
780
|
+
const used = new Set()
|
|
781
|
+
const cssLines = []
|
|
782
|
+
for (const blockId of keys) {
|
|
783
|
+
const payload = map[blockId]
|
|
784
|
+
if (!payload || !Array.isArray(payload.scopes) || !Array.isArray(payload.scopeStyles)) continue
|
|
785
|
+
for (let i = 0; i < payload.scopes.length; i++) {
|
|
786
|
+
const scopeName = payload.scopes[i]
|
|
787
|
+
if (!scopeName) continue
|
|
788
|
+
const runtimeName = sanitizeHighlightName(scopeName)
|
|
789
|
+
if (used.has(runtimeName)) continue
|
|
790
|
+
const css = styleToHighlightCss(payload.scopeStyles[i])
|
|
791
|
+
if (!css) continue
|
|
792
|
+
used.add(runtimeName)
|
|
793
|
+
cssLines.push(`::highlight(${runtimeName}){${css};}`)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (!cssLines.length) return ''
|
|
797
|
+
return `<style id="${styleId}">\n${cssLines.join('\n')}\n</style>`
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export {
|
|
801
|
+
createApiPayloadForFence,
|
|
802
|
+
customHighlightPayloadSchemaVersion,
|
|
803
|
+
customHighlightPayloadSupportedVersions,
|
|
804
|
+
isNormalizedCustomHighlightOpt,
|
|
805
|
+
normalizeCustomHighlightOpt,
|
|
806
|
+
renderCustomHighlightPayloadScript,
|
|
807
|
+
renderCustomHighlightScopeStyleTag,
|
|
808
|
+
sanitizeHighlightName,
|
|
809
|
+
shouldApplyApiFallbackForReason,
|
|
810
|
+
}
|