@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,712 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectTextSegments,
|
|
3
|
+
findTextPosition,
|
|
4
|
+
getPayloadMapFromScript,
|
|
5
|
+
styleToHighlightCss,
|
|
6
|
+
} from '../custom-highlight/runtime-utils.js'
|
|
7
|
+
import {
|
|
8
|
+
customHighlightAppliedAttr,
|
|
9
|
+
customHighlightAppliedSelector,
|
|
10
|
+
customHighlightCodeSelector,
|
|
11
|
+
customHighlightDataScriptId,
|
|
12
|
+
customHighlightPayloadSupportedVersions,
|
|
13
|
+
customHighlightPreAttr,
|
|
14
|
+
customHighlightPreSelector,
|
|
15
|
+
customHighlightStyleTagId,
|
|
16
|
+
} from './render-api-constants.js'
|
|
17
|
+
|
|
18
|
+
const globalRuntimeInsertedScopeStyles = new Map()
|
|
19
|
+
const runtimeInsertedScopeStylesByDoc = new WeakMap()
|
|
20
|
+
const runtimeApplyStateByRoot = new WeakMap()
|
|
21
|
+
const validColorSchemeSet = new Set(['light', 'dark', 'auto'])
|
|
22
|
+
const highlightNameUnsafeReg = /[^A-Za-z0-9_-]+/g
|
|
23
|
+
const hyphenMultiReg = /-+/g
|
|
24
|
+
|
|
25
|
+
const sanitizeHighlightName = (name) => {
|
|
26
|
+
const raw = String(name || '')
|
|
27
|
+
let safe = raw.replace(highlightNameUnsafeReg, '-').replace(hyphenMultiReg, '-').replace(/^-+|-+$/g, '')
|
|
28
|
+
if (!safe) safe = 'scope'
|
|
29
|
+
if (/^[0-9]/.test(safe)) safe = 'x-' + safe
|
|
30
|
+
if (safe.startsWith('--')) safe = safe.slice(2) || 'scope'
|
|
31
|
+
return safe
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const getCustomHighlightBlockId = (node) => {
|
|
35
|
+
if (!node || typeof node.getAttribute !== 'function') return null
|
|
36
|
+
return node.getAttribute(customHighlightPreAttr)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const getRuntimeInsertedScopeStyles = (doc) => {
|
|
40
|
+
if (!doc || typeof doc !== 'object') return globalRuntimeInsertedScopeStyles
|
|
41
|
+
let map = runtimeInsertedScopeStylesByDoc.get(doc)
|
|
42
|
+
if (!map) {
|
|
43
|
+
map = new Map()
|
|
44
|
+
runtimeInsertedScopeStylesByDoc.set(doc, map)
|
|
45
|
+
}
|
|
46
|
+
return map
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ensureHighlightStyleTag = (doc) => {
|
|
50
|
+
if (!doc || typeof doc.getElementById !== 'function' || typeof doc.createElement !== 'function') return null
|
|
51
|
+
let styleTag = doc.getElementById(customHighlightStyleTagId)
|
|
52
|
+
if (styleTag) return styleTag
|
|
53
|
+
styleTag = doc.createElement('style')
|
|
54
|
+
styleTag.id = customHighlightStyleTagId
|
|
55
|
+
if (doc.head) doc.head.appendChild(styleTag)
|
|
56
|
+
else if (doc.documentElement) doc.documentElement.appendChild(styleTag)
|
|
57
|
+
else return null
|
|
58
|
+
return styleTag
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const clearAppliedHighlightNames = (queryRoot) => {
|
|
62
|
+
if (!queryRoot || typeof queryRoot.querySelectorAll !== 'function') return 0
|
|
63
|
+
if (typeof CSS === 'undefined' || !CSS.highlights) return 0
|
|
64
|
+
const nodes = queryRoot.querySelectorAll(customHighlightAppliedSelector)
|
|
65
|
+
const removed = new Set()
|
|
66
|
+
for (const pre of nodes) {
|
|
67
|
+
const names = String(pre.getAttribute(customHighlightAppliedAttr) || '').split(/\s+/).filter(Boolean)
|
|
68
|
+
for (const name of names) {
|
|
69
|
+
if (removed.has(name)) continue
|
|
70
|
+
CSS.highlights.delete(name)
|
|
71
|
+
removed.add(name)
|
|
72
|
+
}
|
|
73
|
+
pre.removeAttribute(customHighlightAppliedAttr)
|
|
74
|
+
}
|
|
75
|
+
return removed.size
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const getCachedPayloadString = (payload, cache) => {
|
|
79
|
+
if (!cache || payload == null || (typeof payload !== 'object' && typeof payload !== 'function')) {
|
|
80
|
+
return JSON.stringify(payload)
|
|
81
|
+
}
|
|
82
|
+
if (cache.has(payload)) return cache.get(payload)
|
|
83
|
+
const text = JSON.stringify(payload)
|
|
84
|
+
cache.set(payload, text)
|
|
85
|
+
return text
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const createPayloadDigest = (payloadMap, cache) => {
|
|
89
|
+
const keys = Object.keys(payloadMap || {}).sort()
|
|
90
|
+
const parts = new Array(keys.length)
|
|
91
|
+
for (let i = 0; i < keys.length; i++) {
|
|
92
|
+
const key = keys[i]
|
|
93
|
+
const payload = payloadMap[key]
|
|
94
|
+
parts[i] = key + '=' + getCachedPayloadString(payload, cache) + ';'
|
|
95
|
+
}
|
|
96
|
+
return parts.join('')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const getRuntimeApplyState = (queryRoot) => {
|
|
100
|
+
if (!queryRoot || typeof queryRoot !== 'object') return null
|
|
101
|
+
return runtimeApplyStateByRoot.get(queryRoot) || null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const setRuntimeApplyState = (queryRoot, state) => {
|
|
105
|
+
if (!queryRoot || typeof queryRoot !== 'object') return
|
|
106
|
+
runtimeApplyStateByRoot.set(queryRoot, state)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const clearRuntimeApplyState = (queryRoot) => {
|
|
110
|
+
if (!queryRoot || typeof queryRoot !== 'object') return
|
|
111
|
+
runtimeApplyStateByRoot.delete(queryRoot)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const addMediaQueryChangeListener = (queryList, listener) => {
|
|
115
|
+
if (!queryList || typeof listener !== 'function') return null
|
|
116
|
+
if (typeof queryList.addEventListener === 'function' && typeof queryList.removeEventListener === 'function') {
|
|
117
|
+
queryList.addEventListener('change', listener)
|
|
118
|
+
return () => {
|
|
119
|
+
try {
|
|
120
|
+
queryList.removeEventListener('change', listener)
|
|
121
|
+
} catch (e) {}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (typeof queryList.addListener === 'function' && typeof queryList.removeListener === 'function') {
|
|
125
|
+
queryList.addListener(listener)
|
|
126
|
+
return () => {
|
|
127
|
+
try {
|
|
128
|
+
queryList.removeListener(listener)
|
|
129
|
+
} catch (e) {}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const getRuntimeSupportedPayloadVersions = (options = {}) => {
|
|
136
|
+
const set = new Set()
|
|
137
|
+
if (options.strictVersion === true) {
|
|
138
|
+
for (const v of customHighlightPayloadSupportedVersions) set.add(v)
|
|
139
|
+
}
|
|
140
|
+
if (Number.isSafeInteger(options.supportedVersion)) set.add(options.supportedVersion)
|
|
141
|
+
if (Array.isArray(options.supportedVersions)) {
|
|
142
|
+
for (const v of options.supportedVersions) {
|
|
143
|
+
if (Number.isSafeInteger(v)) set.add(v)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (set.size === 0) return null
|
|
147
|
+
return set
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const resolveRuntimeColorScheme = (options = {}, doc) => {
|
|
151
|
+
const raw = String(options.colorScheme || 'auto').trim().toLowerCase()
|
|
152
|
+
const mode = validColorSchemeSet.has(raw) ? raw : 'auto'
|
|
153
|
+
if (mode === 'light' || mode === 'dark') return mode
|
|
154
|
+
const view = doc && doc.defaultView
|
|
155
|
+
const matchMediaFn = (options.matchMedia && typeof options.matchMedia === 'function')
|
|
156
|
+
? options.matchMedia
|
|
157
|
+
: (view && typeof view.matchMedia === 'function' ? view.matchMedia.bind(view) : null)
|
|
158
|
+
if (!matchMediaFn) return 'light'
|
|
159
|
+
try {
|
|
160
|
+
return matchMediaFn('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return 'light'
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const resolvePayloadVariantView = (payload, colorScheme) => {
|
|
167
|
+
const baseScopes = Array.isArray(payload && payload.scopes) ? payload.scopes : []
|
|
168
|
+
const baseRanges = Array.isArray(payload && payload.ranges) ? payload.ranges : []
|
|
169
|
+
const baseScopeStyles = Array.isArray(payload && payload.scopeStyles) ? payload.scopeStyles : null
|
|
170
|
+
const variants = payload && payload.variants && typeof payload.variants === 'object' ? payload.variants : null
|
|
171
|
+
if (!variants) {
|
|
172
|
+
return {
|
|
173
|
+
scopes: baseScopes,
|
|
174
|
+
ranges: baseRanges,
|
|
175
|
+
scopeStyles: baseScopeStyles,
|
|
176
|
+
variantKey: '',
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const availableKeys = Object.keys(variants)
|
|
180
|
+
if (!availableKeys.length) {
|
|
181
|
+
return {
|
|
182
|
+
scopes: baseScopes,
|
|
183
|
+
ranges: baseRanges,
|
|
184
|
+
scopeStyles: baseScopeStyles,
|
|
185
|
+
variantKey: '',
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let key = ''
|
|
189
|
+
if (colorScheme && variants[colorScheme]) key = colorScheme
|
|
190
|
+
if (!key && typeof payload.defaultVariant === 'string' && variants[payload.defaultVariant]) key = payload.defaultVariant
|
|
191
|
+
if (!key) key = availableKeys[0]
|
|
192
|
+
const variant = variants[key] && typeof variants[key] === 'object' ? variants[key] : {}
|
|
193
|
+
const scopes = Array.isArray(variant.scopes) ? variant.scopes : baseScopes
|
|
194
|
+
const ranges = Array.isArray(variant.ranges) ? variant.ranges : baseRanges
|
|
195
|
+
const scopeStyles = Array.isArray(variant.scopeStyles) ? variant.scopeStyles : baseScopeStyles
|
|
196
|
+
return {
|
|
197
|
+
scopes,
|
|
198
|
+
ranges,
|
|
199
|
+
scopeStyles,
|
|
200
|
+
variantKey: key || '',
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const getRuntimeScopeName = (scopeName, variantKey) => {
|
|
205
|
+
const base = sanitizeHighlightName(scopeName)
|
|
206
|
+
if (!variantKey) return base
|
|
207
|
+
const suffix = sanitizeHighlightName(variantKey)
|
|
208
|
+
return `${base}-v-${suffix}`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const getCodeBoundaryRefs = (codeEl) => {
|
|
212
|
+
if (!codeEl || typeof codeEl !== 'object') return { first: null, last: null }
|
|
213
|
+
if ('firstChild' in codeEl || 'lastChild' in codeEl) {
|
|
214
|
+
return { first: codeEl.firstChild || null, last: codeEl.lastChild || null }
|
|
215
|
+
}
|
|
216
|
+
const textNodes = Array.isArray(codeEl.__textNodes) ? codeEl.__textNodes : null
|
|
217
|
+
if (!textNodes || textNodes.length === 0) return { first: null, last: null }
|
|
218
|
+
return { first: textNodes[0], last: textNodes[textNodes.length - 1] }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const createSegmentPositionResolver = (segments) => {
|
|
222
|
+
const count = Array.isArray(segments) ? segments.length : 0
|
|
223
|
+
let hint = 0
|
|
224
|
+
let lastOffset = -1
|
|
225
|
+
return (offset) => {
|
|
226
|
+
if (!Number.isSafeInteger(offset) || count === 0) return null
|
|
227
|
+
if (offset >= lastOffset && hint < count) {
|
|
228
|
+
let i = hint
|
|
229
|
+
while (i < count) {
|
|
230
|
+
const seg = segments[i]
|
|
231
|
+
if (offset < seg.start) break
|
|
232
|
+
if (offset <= seg.end) {
|
|
233
|
+
hint = i
|
|
234
|
+
lastOffset = offset
|
|
235
|
+
return { node: seg.node, offset: offset - seg.start }
|
|
236
|
+
}
|
|
237
|
+
i++
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const pos = findTextPosition(segments, offset)
|
|
241
|
+
if (pos) lastOffset = offset
|
|
242
|
+
return pos
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const applyCustomHighlights = (root, options = {}) => {
|
|
247
|
+
const scopeRoot = root || (typeof document !== 'undefined' ? document : null)
|
|
248
|
+
if (!scopeRoot) return { appliedBlocks: 0, appliedRanges: 0, reason: 'no-document' }
|
|
249
|
+
if (typeof CSS === 'undefined' || !CSS.highlights || typeof Highlight === 'undefined' || typeof Range === 'undefined') {
|
|
250
|
+
return { appliedBlocks: 0, appliedRanges: 0, reason: 'api-unsupported' }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const queryRoot = scopeRoot.querySelectorAll ? scopeRoot : (scopeRoot.ownerDocument || document)
|
|
254
|
+
const doc = queryRoot.ownerDocument || (typeof queryRoot.createElement === 'function' ? queryRoot : (typeof document !== 'undefined' ? document : null))
|
|
255
|
+
if (!doc) return { appliedBlocks: 0, appliedRanges: 0, reason: 'no-document' }
|
|
256
|
+
const onRuntimeDiagnostic = (typeof options.onRuntimeDiagnostic === 'function') ? options.onRuntimeDiagnostic : null
|
|
257
|
+
const emitDiag = onRuntimeDiagnostic
|
|
258
|
+
? (data) => {
|
|
259
|
+
try {
|
|
260
|
+
onRuntimeDiagnostic(data)
|
|
261
|
+
} catch (e) {}
|
|
262
|
+
}
|
|
263
|
+
: null
|
|
264
|
+
const codeNodes = queryRoot.querySelectorAll(customHighlightCodeSelector)
|
|
265
|
+
if (!codeNodes || codeNodes.length === 0) {
|
|
266
|
+
clearRuntimeApplyState(queryRoot)
|
|
267
|
+
return { appliedBlocks: 0, appliedRanges: 0 }
|
|
268
|
+
}
|
|
269
|
+
const payloadMap = options.payloadMap && typeof options.payloadMap === 'object'
|
|
270
|
+
? options.payloadMap
|
|
271
|
+
: getPayloadMapFromScript(queryRoot, options.dataScriptId || customHighlightDataScriptId)
|
|
272
|
+
const resolvedColorScheme = resolveRuntimeColorScheme(options, doc)
|
|
273
|
+
const supportedVersions = getRuntimeSupportedPayloadVersions(options)
|
|
274
|
+
const incremental = options.incremental === true
|
|
275
|
+
const payloadDigestByBlock = incremental ? new Map() : null
|
|
276
|
+
const payloadStringifyCache = incremental ? new Map() : null
|
|
277
|
+
let blockRefs = null
|
|
278
|
+
let digest = null
|
|
279
|
+
let prevBlockCache = null
|
|
280
|
+
let prevScopeMetaMap = null
|
|
281
|
+
let nextBlockCache = null
|
|
282
|
+
let nextScopeMetaMap = null
|
|
283
|
+
if (incremental) {
|
|
284
|
+
blockRefs = new Map()
|
|
285
|
+
for (const codeEl of codeNodes) {
|
|
286
|
+
const pre = codeEl.parentElement
|
|
287
|
+
if (!pre) continue
|
|
288
|
+
const blockId = getCustomHighlightBlockId(pre)
|
|
289
|
+
if (!blockId) continue
|
|
290
|
+
blockRefs.set(blockId, codeEl)
|
|
291
|
+
}
|
|
292
|
+
digest = (options.payloadDigest || createPayloadDigest(payloadMap, payloadStringifyCache)) + '|ids=' + Array.from(blockRefs.keys()).sort().join(',') + `|scheme=${resolvedColorScheme}`
|
|
293
|
+
const prevState = getRuntimeApplyState(queryRoot)
|
|
294
|
+
if (prevState && prevState.blockCache instanceof Map) prevBlockCache = prevState.blockCache
|
|
295
|
+
if (prevState && prevState.scopeMetaMap instanceof Map) prevScopeMetaMap = prevState.scopeMetaMap
|
|
296
|
+
nextBlockCache = new Map()
|
|
297
|
+
nextScopeMetaMap = new Map()
|
|
298
|
+
if (prevState && prevState.digest === digest) {
|
|
299
|
+
const prevRefs = prevState.refs
|
|
300
|
+
if (prevRefs && prevRefs.size === blockRefs.size) {
|
|
301
|
+
let sameRefs = true
|
|
302
|
+
for (const [blockId, codeEl] of blockRefs) {
|
|
303
|
+
if (prevRefs.get(blockId) !== codeEl) {
|
|
304
|
+
sameRefs = false
|
|
305
|
+
break
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (sameRefs) {
|
|
309
|
+
if (emitDiag) emitDiag({ type: 'runtime-skip', reason: 'unchanged' })
|
|
310
|
+
return { appliedBlocks: 0, appliedRanges: 0, skipped: true, reason: 'unchanged' }
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
let styleTag = null
|
|
316
|
+
let insertedScopeStyleMap = null
|
|
317
|
+
if (!incremental) clearAppliedHighlightNames(queryRoot)
|
|
318
|
+
const allScopeRanges = new Map()
|
|
319
|
+
let appliedBlocks = 0
|
|
320
|
+
let appliedRanges = 0
|
|
321
|
+
let pendingCss = ''
|
|
322
|
+
|
|
323
|
+
for (const codeEl of codeNodes) {
|
|
324
|
+
const pre = codeEl.parentElement
|
|
325
|
+
if (!pre) continue
|
|
326
|
+
const blockId = getCustomHighlightBlockId(pre)
|
|
327
|
+
if (!blockId) {
|
|
328
|
+
if (emitDiag) emitDiag({ type: 'block-skip', reason: 'missing-block-id' })
|
|
329
|
+
continue
|
|
330
|
+
}
|
|
331
|
+
const payload = payloadMap[blockId]
|
|
332
|
+
if (!payload || !Array.isArray(payload.ranges) || !Array.isArray(payload.scopes)) {
|
|
333
|
+
pre.removeAttribute(customHighlightAppliedAttr)
|
|
334
|
+
if (emitDiag) emitDiag({ type: 'block-skip', blockId, reason: 'missing-payload' })
|
|
335
|
+
continue
|
|
336
|
+
}
|
|
337
|
+
if (supportedVersions && !supportedVersions.has(payload.v)) {
|
|
338
|
+
pre.removeAttribute(customHighlightAppliedAttr)
|
|
339
|
+
if (emitDiag) emitDiag({ type: 'block-skip', blockId, reason: 'unsupported-version', version: payload.v })
|
|
340
|
+
continue
|
|
341
|
+
}
|
|
342
|
+
const activePayload = resolvePayloadVariantView(payload, resolvedColorScheme)
|
|
343
|
+
if (activePayload.ranges.length === 0 || activePayload.scopes.length === 0) {
|
|
344
|
+
pre.removeAttribute(customHighlightAppliedAttr)
|
|
345
|
+
if (emitDiag) emitDiag({ type: 'block-skip', blockId, reason: 'empty-payload' })
|
|
346
|
+
continue
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let blockPayloadDigest = ''
|
|
350
|
+
if (payloadDigestByBlock) {
|
|
351
|
+
blockPayloadDigest = payloadDigestByBlock.get(blockId)
|
|
352
|
+
if (blockPayloadDigest === undefined) {
|
|
353
|
+
const prevCached = prevBlockCache ? prevBlockCache.get(blockId) : null
|
|
354
|
+
if (prevCached && prevCached.payloadRef === payload && prevCached.variantKey === activePayload.variantKey) {
|
|
355
|
+
blockPayloadDigest = prevCached.payloadDigest || ''
|
|
356
|
+
} else {
|
|
357
|
+
blockPayloadDigest = getCachedPayloadString(payload, payloadStringifyCache) + `|variant=${activePayload.variantKey || ''}`
|
|
358
|
+
}
|
|
359
|
+
payloadDigestByBlock.set(blockId, blockPayloadDigest)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const textSnapshot = typeof codeEl.textContent === 'string' ? codeEl.textContent : null
|
|
363
|
+
const boundaryRefs = getCodeBoundaryRefs(codeEl)
|
|
364
|
+
const cached = prevBlockCache ? prevBlockCache.get(blockId) : null
|
|
365
|
+
if (cached &&
|
|
366
|
+
cached.codeEl === codeEl &&
|
|
367
|
+
cached.payloadDigest === blockPayloadDigest &&
|
|
368
|
+
cached.textSnapshot === textSnapshot &&
|
|
369
|
+
cached.firstRef === boundaryRefs.first &&
|
|
370
|
+
cached.lastRef === boundaryRefs.last &&
|
|
371
|
+
cached.scopeRanges instanceof Map) {
|
|
372
|
+
for (const [runtimeName, ranges] of cached.scopeRanges) {
|
|
373
|
+
let target = allScopeRanges.get(runtimeName)
|
|
374
|
+
if (!target) {
|
|
375
|
+
target = []
|
|
376
|
+
allScopeRanges.set(runtimeName, target)
|
|
377
|
+
}
|
|
378
|
+
for (let i = 0; i < ranges.length; i++) target.push(ranges[i])
|
|
379
|
+
}
|
|
380
|
+
if (cached.appliedAttrText) {
|
|
381
|
+
pre.setAttribute(customHighlightAppliedAttr, cached.appliedAttrText)
|
|
382
|
+
appliedBlocks++
|
|
383
|
+
} else {
|
|
384
|
+
pre.removeAttribute(customHighlightAppliedAttr)
|
|
385
|
+
}
|
|
386
|
+
appliedRanges += cached.appliedRangeCount || 0
|
|
387
|
+
if (nextScopeMetaMap && cached.scopeMeta instanceof Map) {
|
|
388
|
+
for (const [runtimeName, scopeMeta] of cached.scopeMeta) {
|
|
389
|
+
nextScopeMetaMap.set(runtimeName, (nextScopeMetaMap.get(runtimeName) || '') + `|${blockId}:${scopeMeta}`)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (nextBlockCache) nextBlockCache.set(blockId, cached)
|
|
393
|
+
continue
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const segments = collectTextSegments(codeEl)
|
|
397
|
+
const resolveTextPos = createSegmentPositionResolver(segments)
|
|
398
|
+
const textLength = segments.length ? segments[segments.length - 1].end : 0
|
|
399
|
+
if (Number.isSafeInteger(payload.textLength) && payload.textLength !== textLength) {
|
|
400
|
+
pre.removeAttribute(customHighlightAppliedAttr)
|
|
401
|
+
if (emitDiag) {
|
|
402
|
+
emitDiag({
|
|
403
|
+
type: 'block-skip',
|
|
404
|
+
blockId,
|
|
405
|
+
reason: 'text-length-mismatch',
|
|
406
|
+
payloadTextLength: payload.textLength,
|
|
407
|
+
actualTextLength: textLength,
|
|
408
|
+
})
|
|
409
|
+
}
|
|
410
|
+
continue
|
|
411
|
+
}
|
|
412
|
+
const appliedNames = new Set()
|
|
413
|
+
const appliedNameList = []
|
|
414
|
+
const scopeRuntimeNames = new Array(activePayload.scopes.length)
|
|
415
|
+
const scopeStyles = Array.isArray(activePayload.scopeStyles) ? activePayload.scopeStyles : null
|
|
416
|
+
const blockScopeRanges = nextBlockCache ? new Map() : null
|
|
417
|
+
const blockScopeMetaParts = nextBlockCache ? new Map() : null
|
|
418
|
+
let blockAppliedRangeCount = 0
|
|
419
|
+
|
|
420
|
+
for (let tupleIdx = 0; tupleIdx < activePayload.ranges.length; tupleIdx++) {
|
|
421
|
+
const tuple = activePayload.ranges[tupleIdx]
|
|
422
|
+
if (!Array.isArray(tuple) || tuple.length < 3) {
|
|
423
|
+
if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'invalid-tuple', tupleIndex: tupleIdx })
|
|
424
|
+
continue
|
|
425
|
+
}
|
|
426
|
+
const scopeIdx = tuple[0]
|
|
427
|
+
const start = tuple[1]
|
|
428
|
+
const end = tuple[2]
|
|
429
|
+
if (!Number.isSafeInteger(scopeIdx) || !Number.isSafeInteger(start) || !Number.isSafeInteger(end) || end <= start) {
|
|
430
|
+
if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'invalid-range', tupleIndex: tupleIdx })
|
|
431
|
+
continue
|
|
432
|
+
}
|
|
433
|
+
if (scopeIdx < 0 || scopeIdx >= activePayload.scopes.length) {
|
|
434
|
+
if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'invalid-scope-index', tupleIndex: tupleIdx, scopeIdx })
|
|
435
|
+
continue
|
|
436
|
+
}
|
|
437
|
+
const scopeName = activePayload.scopes[scopeIdx]
|
|
438
|
+
if (!scopeName) {
|
|
439
|
+
if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'missing-scope', tupleIndex: tupleIdx, scopeIdx })
|
|
440
|
+
continue
|
|
441
|
+
}
|
|
442
|
+
const startPos = resolveTextPos(start)
|
|
443
|
+
const endPos = resolveTextPos(end)
|
|
444
|
+
if (!startPos || !endPos) {
|
|
445
|
+
if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'range-out-of-bounds', tupleIndex: tupleIdx, start, end })
|
|
446
|
+
continue
|
|
447
|
+
}
|
|
448
|
+
let range
|
|
449
|
+
try {
|
|
450
|
+
range = doc.createRange()
|
|
451
|
+
range.setStart(startPos.node, startPos.offset)
|
|
452
|
+
range.setEnd(endPos.node, endPos.offset)
|
|
453
|
+
} catch (e) {
|
|
454
|
+
if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'range-create-failed', tupleIndex: tupleIdx })
|
|
455
|
+
continue
|
|
456
|
+
}
|
|
457
|
+
let runtimeName = scopeRuntimeNames[scopeIdx]
|
|
458
|
+
if (!runtimeName) {
|
|
459
|
+
runtimeName = getRuntimeScopeName(scopeName, activePayload.variantKey)
|
|
460
|
+
scopeRuntimeNames[scopeIdx] = runtimeName
|
|
461
|
+
if (scopeStyles) {
|
|
462
|
+
const css = styleToHighlightCss(scopeStyles[scopeIdx])
|
|
463
|
+
if (css) {
|
|
464
|
+
if (!insertedScopeStyleMap) insertedScopeStyleMap = getRuntimeInsertedScopeStyles(doc)
|
|
465
|
+
if (insertedScopeStyleMap.get(runtimeName) !== css) {
|
|
466
|
+
if (!styleTag) styleTag = ensureHighlightStyleTag(doc)
|
|
467
|
+
if (styleTag) {
|
|
468
|
+
pendingCss += `\n::highlight(${runtimeName}){${css};}`
|
|
469
|
+
insertedScopeStyleMap.set(runtimeName, css)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
let scopeRanges = allScopeRanges.get(runtimeName)
|
|
476
|
+
if (!scopeRanges) {
|
|
477
|
+
scopeRanges = []
|
|
478
|
+
allScopeRanges.set(runtimeName, scopeRanges)
|
|
479
|
+
}
|
|
480
|
+
scopeRanges.push(range)
|
|
481
|
+
if (blockScopeRanges) {
|
|
482
|
+
let blockRanges = blockScopeRanges.get(runtimeName)
|
|
483
|
+
if (!blockRanges) {
|
|
484
|
+
blockRanges = []
|
|
485
|
+
blockScopeRanges.set(runtimeName, blockRanges)
|
|
486
|
+
}
|
|
487
|
+
blockRanges.push(range)
|
|
488
|
+
}
|
|
489
|
+
if (blockScopeMetaParts) {
|
|
490
|
+
let rangeMeta = blockScopeMetaParts.get(runtimeName)
|
|
491
|
+
if (!rangeMeta) {
|
|
492
|
+
rangeMeta = []
|
|
493
|
+
blockScopeMetaParts.set(runtimeName, rangeMeta)
|
|
494
|
+
}
|
|
495
|
+
rangeMeta.push(`${start}-${end}`)
|
|
496
|
+
}
|
|
497
|
+
appliedRanges++
|
|
498
|
+
blockAppliedRangeCount++
|
|
499
|
+
if (!appliedNames.has(runtimeName)) {
|
|
500
|
+
appliedNames.add(runtimeName)
|
|
501
|
+
appliedNameList.push(runtimeName)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const appliedAttrText = appliedNameList.join(' ')
|
|
506
|
+
if (appliedNames.size > 0) {
|
|
507
|
+
pre.setAttribute(customHighlightAppliedAttr, appliedAttrText)
|
|
508
|
+
appliedBlocks++
|
|
509
|
+
} else {
|
|
510
|
+
pre.removeAttribute(customHighlightAppliedAttr)
|
|
511
|
+
if (emitDiag) emitDiag({ type: 'block-skip', blockId, reason: 'no-valid-ranges' })
|
|
512
|
+
}
|
|
513
|
+
let blockScopeMeta = null
|
|
514
|
+
if (blockScopeMetaParts) {
|
|
515
|
+
blockScopeMeta = new Map()
|
|
516
|
+
for (const [runtimeName, parts] of blockScopeMetaParts) {
|
|
517
|
+
const scopeMeta = parts.join(',')
|
|
518
|
+
blockScopeMeta.set(runtimeName, scopeMeta)
|
|
519
|
+
if (nextScopeMetaMap) nextScopeMetaMap.set(runtimeName, (nextScopeMetaMap.get(runtimeName) || '') + `|${blockId}:${scopeMeta}`)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (nextBlockCache) {
|
|
523
|
+
nextBlockCache.set(blockId, {
|
|
524
|
+
codeEl,
|
|
525
|
+
payloadRef: payload,
|
|
526
|
+
variantKey: activePayload.variantKey || '',
|
|
527
|
+
payloadDigest: blockPayloadDigest,
|
|
528
|
+
textSnapshot,
|
|
529
|
+
firstRef: boundaryRefs.first,
|
|
530
|
+
lastRef: boundaryRefs.last,
|
|
531
|
+
scopeRanges: blockScopeRanges || new Map(),
|
|
532
|
+
scopeMeta: blockScopeMeta || new Map(),
|
|
533
|
+
appliedRangeCount: blockAppliedRangeCount,
|
|
534
|
+
appliedAttrText,
|
|
535
|
+
})
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (incremental) {
|
|
539
|
+
const prevMap = prevScopeMetaMap || new Map()
|
|
540
|
+
const nextMap = nextScopeMetaMap || new Map()
|
|
541
|
+
const names = new Set()
|
|
542
|
+
for (const name of prevMap.keys()) names.add(name)
|
|
543
|
+
for (const name of nextMap.keys()) names.add(name)
|
|
544
|
+
for (const runtimeName of names) {
|
|
545
|
+
const prevMeta = prevMap.get(runtimeName)
|
|
546
|
+
const nextMeta = nextMap.get(runtimeName)
|
|
547
|
+
if (nextMeta == null) {
|
|
548
|
+
CSS.highlights.delete(runtimeName)
|
|
549
|
+
continue
|
|
550
|
+
}
|
|
551
|
+
if (nextMeta === prevMeta) continue
|
|
552
|
+
const scopeRanges = allScopeRanges.get(runtimeName)
|
|
553
|
+
if (!scopeRanges || scopeRanges.length === 0) {
|
|
554
|
+
CSS.highlights.delete(runtimeName)
|
|
555
|
+
continue
|
|
556
|
+
}
|
|
557
|
+
CSS.highlights.set(runtimeName, new Highlight(...scopeRanges))
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
for (const [runtimeName, scopeRanges] of allScopeRanges) {
|
|
561
|
+
if (!scopeRanges.length) continue
|
|
562
|
+
CSS.highlights.set(runtimeName, new Highlight(...scopeRanges))
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (pendingCss && styleTag) styleTag.textContent += pendingCss
|
|
566
|
+
if (incremental) {
|
|
567
|
+
setRuntimeApplyState(queryRoot, { digest, refs: blockRefs, blockCache: nextBlockCache, scopeMetaMap: nextScopeMetaMap })
|
|
568
|
+
} else {
|
|
569
|
+
clearRuntimeApplyState(queryRoot)
|
|
570
|
+
}
|
|
571
|
+
return { appliedBlocks, appliedRanges }
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const observeCustomHighlights = (root, options = {}) => {
|
|
575
|
+
const scopeRoot = root || (typeof document !== 'undefined' ? document : null)
|
|
576
|
+
if (!scopeRoot) {
|
|
577
|
+
return {
|
|
578
|
+
supported: false,
|
|
579
|
+
reason: 'no-document',
|
|
580
|
+
observe: () => 0,
|
|
581
|
+
disconnect: () => {},
|
|
582
|
+
applyNow: () => ({ appliedBlocks: 0, appliedRanges: 0, reason: 'no-document' }),
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const ObserverCtor =
|
|
587
|
+
(typeof options.IntersectionObserver === 'function' && options.IntersectionObserver) ||
|
|
588
|
+
((typeof IntersectionObserver !== 'undefined') ? IntersectionObserver : null)
|
|
589
|
+
if (typeof ObserverCtor !== 'function') {
|
|
590
|
+
return {
|
|
591
|
+
supported: false,
|
|
592
|
+
reason: 'observer-unsupported',
|
|
593
|
+
observe: () => 0,
|
|
594
|
+
disconnect: () => {},
|
|
595
|
+
applyNow: () => applyCustomHighlights(scopeRoot, options.applyOptions || {}),
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const queryRoot = scopeRoot.querySelectorAll ? scopeRoot : (scopeRoot.ownerDocument || document)
|
|
600
|
+
if (!queryRoot || typeof queryRoot.querySelectorAll !== 'function') {
|
|
601
|
+
return {
|
|
602
|
+
supported: false,
|
|
603
|
+
reason: 'invalid-root',
|
|
604
|
+
observe: () => 0,
|
|
605
|
+
disconnect: () => {},
|
|
606
|
+
applyNow: () => ({ appliedBlocks: 0, appliedRanges: 0, reason: 'invalid-root' }),
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const selector = String(options.selector || customHighlightPreSelector)
|
|
611
|
+
const applyOptions = options.applyOptions && typeof options.applyOptions === 'object' ? options.applyOptions : {}
|
|
612
|
+
const runtimeApplyOptions = (typeof options.matchMedia === 'function' && typeof applyOptions.matchMedia !== 'function')
|
|
613
|
+
? Object.assign({}, applyOptions, { matchMedia: options.matchMedia })
|
|
614
|
+
: applyOptions
|
|
615
|
+
const once = options.once !== false
|
|
616
|
+
let isDisposed = false
|
|
617
|
+
let isObserverStopped = false
|
|
618
|
+
let hasApplied = false
|
|
619
|
+
let removeColorSchemeListener = null
|
|
620
|
+
const applyNow = () => {
|
|
621
|
+
const result = applyCustomHighlights(queryRoot, runtimeApplyOptions)
|
|
622
|
+
hasApplied = true
|
|
623
|
+
return result
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const observer = new ObserverCtor((entries) => {
|
|
627
|
+
if (isDisposed || !Array.isArray(entries)) return
|
|
628
|
+
let shouldApply = false
|
|
629
|
+
for (let i = 0; i < entries.length; i++) {
|
|
630
|
+
const entry = entries[i]
|
|
631
|
+
if (entry && (entry.isIntersecting || entry.intersectionRatio > 0)) {
|
|
632
|
+
shouldApply = true
|
|
633
|
+
break
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (!shouldApply) return
|
|
637
|
+
applyNow()
|
|
638
|
+
if (once) {
|
|
639
|
+
observer.disconnect()
|
|
640
|
+
isObserverStopped = true
|
|
641
|
+
}
|
|
642
|
+
}, {
|
|
643
|
+
root: options.root || null,
|
|
644
|
+
rootMargin: options.rootMargin || '200px 0px',
|
|
645
|
+
threshold: options.threshold == null ? 0 : options.threshold,
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
const observe = () => {
|
|
649
|
+
if (isDisposed || isObserverStopped) return 0
|
|
650
|
+
const targets = queryRoot.querySelectorAll(selector)
|
|
651
|
+
for (const target of targets) observer.observe(target)
|
|
652
|
+
return targets.length
|
|
653
|
+
}
|
|
654
|
+
const disconnect = () => {
|
|
655
|
+
if (isDisposed) return
|
|
656
|
+
if (removeColorSchemeListener) {
|
|
657
|
+
removeColorSchemeListener()
|
|
658
|
+
removeColorSchemeListener = null
|
|
659
|
+
}
|
|
660
|
+
observer.disconnect()
|
|
661
|
+
isObserverStopped = true
|
|
662
|
+
isDisposed = true
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const watchColorScheme = options.watchColorScheme === true
|
|
666
|
+
const autoMode = String(applyOptions.colorScheme || 'auto').trim().toLowerCase() === 'auto'
|
|
667
|
+
if (watchColorScheme && autoMode) {
|
|
668
|
+
const doc = queryRoot.ownerDocument || (typeof queryRoot.createElement === 'function' ? queryRoot : null)
|
|
669
|
+
const view = doc && doc.defaultView
|
|
670
|
+
const matchMediaFn = (options.matchMedia && typeof options.matchMedia === 'function')
|
|
671
|
+
? options.matchMedia
|
|
672
|
+
: (view && typeof view.matchMedia === 'function' ? view.matchMedia.bind(view) : null)
|
|
673
|
+
if (matchMediaFn) {
|
|
674
|
+
let queryList = null
|
|
675
|
+
try {
|
|
676
|
+
queryList = matchMediaFn('(prefers-color-scheme: dark)')
|
|
677
|
+
} catch (e) {
|
|
678
|
+
queryList = null
|
|
679
|
+
}
|
|
680
|
+
if (queryList) {
|
|
681
|
+
removeColorSchemeListener = addMediaQueryChangeListener(queryList, () => {
|
|
682
|
+
if (isDisposed || !hasApplied) return
|
|
683
|
+
applyNow()
|
|
684
|
+
})
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (options.autoStart !== false) observe()
|
|
690
|
+
return {
|
|
691
|
+
supported: true,
|
|
692
|
+
observe,
|
|
693
|
+
disconnect,
|
|
694
|
+
applyNow,
|
|
695
|
+
observer,
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const clearCustomHighlights = (root) => {
|
|
700
|
+
const scopeRoot = root || (typeof document !== 'undefined' ? document : null)
|
|
701
|
+
if (!scopeRoot) return { cleared: 0, reason: 'no-document' }
|
|
702
|
+
if (typeof CSS === 'undefined' || !CSS.highlights) return { cleared: 0, reason: 'api-unsupported' }
|
|
703
|
+
const queryRoot = scopeRoot.querySelectorAll ? scopeRoot : (scopeRoot.ownerDocument || document)
|
|
704
|
+
clearRuntimeApplyState(queryRoot)
|
|
705
|
+
return { cleared: clearAppliedHighlightNames(queryRoot) }
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export {
|
|
709
|
+
applyCustomHighlights,
|
|
710
|
+
clearCustomHighlights,
|
|
711
|
+
observeCustomHighlights,
|
|
712
|
+
}
|