@peaceroad/markdown-it-renderer-fence 0.4.1 → 0.6.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.
@@ -0,0 +1,590 @@
1
+ import {
2
+ appendStyleValue,
3
+ createAttrOrderIndexGetter,
4
+ getInfoAttr,
5
+ getLangFromClassAttr,
6
+ } from '../utils/attr-utils.js'
7
+
8
+ const infoReg = /^([^{\s]*)(?:\s*\{(.*)\})?$/
9
+ const tagReg = /<\/?([A-Za-z][A-Za-z0-9-]*)(?:\s+[^>]*?)?\/?\s*>/g
10
+ const preLineTag = '<span class="pre-line">'
11
+ const preLineNoNumberClass = 'pre-line-no-number'
12
+ const emphOpenTag = '<span class="pre-lines-emphasis">'
13
+ const commentLineClass = 'pre-line-comment'
14
+ const closeTag = '</span>'
15
+ const closeTagLen = closeTag.length
16
+ const preWrapStyle = 'white-space: pre-wrap; overflow-wrap: anywhere;'
17
+ const voidTags = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'])
18
+ const nonNegativeIntReg = /^\d+$/
19
+ const positiveIntReg = /^[1-9]\d*$/
20
+
21
+ const getLineVisualLengthIgnoringTags = (line, threshold) => {
22
+ let len = 0
23
+ let inTag = false
24
+ for (let i = 0; i < line.length; i++) {
25
+ const code = line.charCodeAt(i)
26
+ if (inTag) {
27
+ if (code === 62) inTag = false // >
28
+ continue
29
+ }
30
+ if (code === 60) { // <
31
+ inTag = true
32
+ continue
33
+ }
34
+ len += code > 255 ? 2 : 1
35
+ if (len >= threshold) return len
36
+ }
37
+ return len
38
+ }
39
+
40
+ const getNowMs = () => {
41
+ if (typeof performance !== 'undefined' && performance && typeof performance.now === 'function') return performance.now()
42
+ return Date.now()
43
+ }
44
+
45
+ const roundTimingMs = (value) => {
46
+ return Math.round(value * 1000) / 1000
47
+ }
48
+
49
+ const addTimingMs = (timings, key, value) => {
50
+ if (!timings || !key || !Number.isFinite(value) || value <= 0) return
51
+ timings[key] = (timings[key] || 0) + value
52
+ }
53
+
54
+ const finalizeFenceTimings = (timings, startedAtMs) => {
55
+ if (!timings || !Number.isFinite(startedAtMs)) return null
56
+ const totalMs = getNowMs() - startedAtMs
57
+ const out = {}
58
+ if (timings.attrNormalizeMs) out.attrNormalizeMs = roundTimingMs(timings.attrNormalizeMs)
59
+ if (timings.highlightMs) out.highlightMs = roundTimingMs(timings.highlightMs)
60
+ if (timings.providerMs) out.providerMs = roundTimingMs(timings.providerMs)
61
+ if (timings.lineSplitMs) out.lineSplitMs = roundTimingMs(timings.lineSplitMs)
62
+ out.totalMs = roundTimingMs(totalMs > 0 ? totalMs : 0)
63
+ return out
64
+ }
65
+
66
+ const applyLineEndAlias = (opt, sourceOption) => {
67
+ if (sourceOption && sourceOption.lineEndSpanThreshold == null && sourceOption.setLineEndSpan != null) {
68
+ opt.lineEndSpanThreshold = sourceOption.setLineEndSpan
69
+ }
70
+ }
71
+
72
+ const createCommonFenceOptionDefaults = (md) => {
73
+ return {
74
+ attrsOrder: ['class', 'id', 'data-*', 'style'],
75
+ setHighlight: true,
76
+ setLineNumber: true,
77
+ setEmphasizeLines: true,
78
+ lineEndSpanThreshold: 0,
79
+ lineEndSpanClass: 'pre-lineend-spacer',
80
+ setPreWrapStyle: true,
81
+ useHighlightPre: false,
82
+ onFenceDecision: null,
83
+ onFenceDecisionTiming: false,
84
+ sampLang: 'shell,console',
85
+ langPrefix: md.options.langPrefix || 'language-',
86
+ }
87
+ }
88
+
89
+ const finalizeCommonFenceOption = (opt) => {
90
+ opt._sampReg = new RegExp('^(?:samp|' + opt.sampLang.split(',').join('|') + ')$')
91
+ opt._attrOrderIndex = createAttrOrderIndexGetter(opt.attrsOrder || [])
92
+ return opt
93
+ }
94
+
95
+ const emitFenceDecision = (opt, data) => {
96
+ if (!opt || typeof opt.onFenceDecision !== 'function') return
97
+ try {
98
+ opt.onFenceDecision(data)
99
+ } catch (e) {}
100
+ }
101
+
102
+ const parseStartNumber = (val) => {
103
+ const str = String(val ?? '')
104
+ if (!nonNegativeIntReg.test(str)) return null
105
+ const num = Number(str)
106
+ if (!Number.isSafeInteger(num) || num < 0) return null
107
+ return num
108
+ }
109
+
110
+ const parsePositiveLineIndex = (val) => {
111
+ const str = String(val ?? '').trim()
112
+ if (!positiveIntReg.test(str)) return null
113
+ const num = Number(str)
114
+ if (!Number.isSafeInteger(num) || num <= 0) return null
115
+ return num
116
+ }
117
+
118
+ const getLogicalLineCount = (text) => {
119
+ const str = String(text ?? '')
120
+ if (!str) return 0
121
+ let count = 1
122
+ for (let i = 0; i < str.length; i++) {
123
+ const ch = str.charCodeAt(i)
124
+ if (ch !== 10 && ch !== 13) continue
125
+ if (ch === 13 && i + 1 < str.length && str.charCodeAt(i + 1) === 10) i++
126
+ count++
127
+ }
128
+ const last = str.charCodeAt(str.length - 1)
129
+ if (last === 10 || last === 13) count--
130
+ return count
131
+ }
132
+
133
+ const parseLineRangeList = (attrVal, parseValue) => {
134
+ const str = String(attrVal ?? '')
135
+ if (!str) return []
136
+ const result = []
137
+ for (const partRaw of str.split(',')) {
138
+ const part = partRaw.trim()
139
+ if (!part) continue
140
+ const hyphen = part.indexOf('-')
141
+ if (hyphen === -1) {
142
+ const n = parseValue(part)
143
+ if (n != null) result.push([n, n])
144
+ continue
145
+ }
146
+ const start = part.slice(0, hyphen).trim()
147
+ const end = part.slice(hyphen + 1).trim()
148
+ const s = start ? parseValue(start) : null
149
+ const e = end ? parseValue(end) : null
150
+ if (s == null && e == null) continue
151
+ if (start && s == null) continue
152
+ if (end && e == null) continue
153
+ result.push([s, e])
154
+ }
155
+ return result
156
+ }
157
+
158
+ const parsePositiveLineNumber = (val) => {
159
+ const num = Number(val)
160
+ if (!Number.isFinite(num) || num <= 0) return null
161
+ return num
162
+ }
163
+
164
+ const getEmphasizeLines = (attrVal) => {
165
+ return parseLineRangeList(attrVal, parsePositiveLineNumber)
166
+ }
167
+
168
+ const getLineNumberSkipRanges = (attrVal) => {
169
+ return parseLineRangeList(attrVal, parsePositiveLineIndex)
170
+ }
171
+
172
+ const normalizeEmphasisRanges = (emphasizeLines, maxLine) => {
173
+ const normalized = []
174
+ if (!emphasizeLines || !emphasizeLines.length || maxLine <= 0) return normalized
175
+ for (const range of emphasizeLines) {
176
+ let s = range[0]
177
+ let e = range[1]
178
+ if (s == null) s = 1
179
+ if (e == null) e = maxLine
180
+ if (!Number.isFinite(s) || !Number.isFinite(e) || s <= 0 || e <= 0) continue
181
+ if (s > e) {
182
+ const tmp = s
183
+ s = e
184
+ e = tmp
185
+ }
186
+ if (s > maxLine || e < 1) continue
187
+ if (e > maxLine) e = maxLine
188
+ normalized.push([s, e])
189
+ }
190
+ return normalized
191
+ }
192
+
193
+ const getLineNumberResetEntries = (attrVal) => {
194
+ const str = String(attrVal ?? '')
195
+ if (!str) return []
196
+ const result = []
197
+ for (const partRaw of str.split(',')) {
198
+ const part = partRaw.trim()
199
+ if (!part) continue
200
+ const colon = part.indexOf(':')
201
+ if (colon === -1) continue
202
+ const lineNumber = parsePositiveLineIndex(part.slice(0, colon).trim())
203
+ const resetNumber = parseStartNumber(part.slice(colon + 1).trim())
204
+ if (lineNumber == null || resetNumber == null) continue
205
+ result.push([lineNumber, resetNumber])
206
+ }
207
+ return result
208
+ }
209
+
210
+ const createSparseLineFlagMap = (ranges, maxLine) => {
211
+ const normalized = normalizeEmphasisRanges(ranges, maxLine)
212
+ if (!normalized.length) return null
213
+ const flags = []
214
+ for (let i = 0; i < normalized.length; i++) {
215
+ const range = normalized[i]
216
+ for (let line = range[0]; line <= range[1]; line++) flags[line - 1] = true
217
+ }
218
+ return flags
219
+ }
220
+
221
+ const createSparseLineResetMap = (entries, maxLine) => {
222
+ if (!entries || !entries.length || maxLine <= 0) return null
223
+ const resets = []
224
+ let hasValue = false
225
+ for (let i = 0; i < entries.length; i++) {
226
+ const line = entries[i][0]
227
+ const value = entries[i][1]
228
+ if (!Number.isSafeInteger(line) || line <= 0 || line > maxLine) continue
229
+ resets[line - 1] = value
230
+ hasValue = true
231
+ }
232
+ return hasValue ? resets : null
233
+ }
234
+
235
+ const buildAdvancedLineNumberPlan = (skipRanges, resetEntries, sourceLineCount, renderedLineCount) => {
236
+ const hasSkip = !!(skipRanges && skipRanges.length)
237
+ const hasReset = !!(resetEntries && resetEntries.length)
238
+ if (!hasSkip && !hasReset) return null
239
+ if (!Number.isSafeInteger(sourceLineCount) || sourceLineCount <= 0) return null
240
+ if (sourceLineCount !== renderedLineCount) return null
241
+ const hidden = hasSkip ? createSparseLineFlagMap(skipRanges, sourceLineCount) : null
242
+ const resets = hasReset ? createSparseLineResetMap(resetEntries, sourceLineCount) : null
243
+ if (!hidden && !resets) return null
244
+ return { hidden, resets }
245
+ }
246
+
247
+ const resolveAdvancedLineNumberPlan = (lineNumberSkipValue, lineNumberResetValue, sourceLineCount, renderedLineCount) => {
248
+ const hasSkipValue = lineNumberSkipValue !== undefined
249
+ const hasResetValue = lineNumberResetValue !== undefined
250
+ if (!hasSkipValue && !hasResetValue) return null
251
+ if (!Number.isSafeInteger(sourceLineCount) || sourceLineCount <= 0) return null
252
+ if (sourceLineCount !== renderedLineCount) return null
253
+ const skipRanges = hasSkipValue ? getLineNumberSkipRanges(lineNumberSkipValue) : null
254
+ const resetEntries = hasResetValue ? getLineNumberResetEntries(lineNumberResetValue) : null
255
+ return buildAdvancedLineNumberPlan(skipRanges, resetEntries, sourceLineCount, renderedLineCount)
256
+ }
257
+
258
+ const getPreLineOpenTag = (lineNumberPlan, lineIndex) => {
259
+ if (!lineNumberPlan) return preLineTag
260
+ const hidden = !!(lineNumberPlan.hidden && lineNumberPlan.hidden[lineIndex])
261
+ const resetNumber = lineNumberPlan.resets ? lineNumberPlan.resets[lineIndex] : undefined
262
+ if (!hidden && resetNumber == null) return preLineTag
263
+ let tag = '<span class="pre-line'
264
+ if (hidden) tag += ' ' + preLineNoNumberClass
265
+ tag += '"'
266
+ if (resetNumber != null) tag += ` style="counter-set:pre-line-number ${resetNumber};"`
267
+ return tag + '>'
268
+ }
269
+
270
+ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, threshold, lineEndSpanClass, br, commentLines, commentClass, lineNumberPlan) => {
271
+ const lines = content.split(br)
272
+ const max = lines.length
273
+ const hasDynamicLineNumberPlan = !!lineNumberPlan
274
+ let emIdx = 0
275
+ let emStart = -1
276
+ let emEnd = -1
277
+ if (needEmphasis && emphasizeLines && emphasizeLines.length) {
278
+ emStart = emphasizeLines[0][0]
279
+ emEnd = emphasizeLines[0][1]
280
+ } else {
281
+ needEmphasis = false
282
+ }
283
+
284
+ const endSpanTag = needEndSpan ? `<span class="${lineEndSpanClass}"></span>` : ''
285
+
286
+ for (let n = 0; n < max; n++) {
287
+ let line = lines[n]
288
+ const notLastLine = n < max - 1
289
+ const doComment = commentLines && commentLines[n]
290
+ let hasLt = false
291
+ let hasLtChecked = false
292
+
293
+ if (needEndSpan && threshold > 0) {
294
+ if (!hasLtChecked) {
295
+ hasLt = line.indexOf('<') !== -1
296
+ hasLtChecked = true
297
+ }
298
+ let lineLen = 0
299
+ if (!hasLt) {
300
+ lineLen = line.length
301
+ } else {
302
+ lineLen = getLineVisualLengthIgnoringTags(line, threshold)
303
+ }
304
+ if (lineLen >= threshold) {
305
+ if (line.endsWith(closeTag)) {
306
+ line = line.slice(0, -closeTagLen) + endSpanTag + closeTag
307
+ } else {
308
+ line = line + endSpanTag
309
+ }
310
+ }
311
+ }
312
+
313
+ if (needLineNumber && notLastLine) {
314
+ if (!hasLtChecked) {
315
+ hasLt = line.indexOf('<') !== -1
316
+ hasLtChecked = true
317
+ }
318
+ if (hasLt && line.indexOf('>') !== -1) {
319
+ const tagStack = []
320
+ tagReg.lastIndex = 0
321
+ let match
322
+ while ((match = tagReg.exec(line)) !== null) {
323
+ const fullMatch = match[0]
324
+ const tagNameLower = match[1].toLowerCase()
325
+ if (fullMatch.startsWith('</')) {
326
+ if (tagStack[tagStack.length - 1] === tagNameLower) tagStack.pop()
327
+ } else if (!fullMatch.endsWith('/>') && !voidTags.has(tagNameLower)) {
328
+ tagStack.push(tagNameLower)
329
+ }
330
+ }
331
+ for (let i = tagStack.length - 1; i >= 0; i--) {
332
+ const tagName = tagStack[i]
333
+ line += `</${tagName}>`
334
+ lines[n + 1] = `<${tagName}>` + lines[n + 1]
335
+ }
336
+ }
337
+ }
338
+
339
+ if (doComment) {
340
+ line = `<span class="${commentClass}">` + line + closeTag
341
+ }
342
+
343
+ if (needLineNumber && notLastLine) {
344
+ line = (hasDynamicLineNumberPlan ? getPreLineOpenTag(lineNumberPlan, n) : preLineTag) + line + closeTag
345
+ }
346
+
347
+ if (needEmphasis && emIdx < emphasizeLines.length && emStart === n + 1) {
348
+ line = emphOpenTag + line
349
+ }
350
+ if (needEmphasis && emIdx < emphasizeLines.length && emEnd === n) {
351
+ line = closeTag + line
352
+ emIdx++
353
+ const nextEmphasis = emphasizeLines[emIdx] || []
354
+ emStart = nextEmphasis[0]
355
+ emEnd = nextEmphasis[1]
356
+ }
357
+
358
+ lines[n] = line
359
+ }
360
+ return lines.join(br)
361
+ }
362
+
363
+ const orderTokenAttrs = (token, opt) => {
364
+ const attrs = token.attrs
365
+ if (!attrs || attrs.length < 2) return
366
+ const rankCache = new Map()
367
+ const getRank = (name) => {
368
+ let rank = rankCache.get(name)
369
+ if (rank === undefined) {
370
+ rank = opt._attrOrderIndex(name)
371
+ rankCache.set(name, rank)
372
+ }
373
+ return rank
374
+ }
375
+ attrs.sort((a, b) => getRank(a[0]) - getRank(b[0]))
376
+ }
377
+
378
+ const prepareFenceRenderContext = (tokens, idx, opt) => {
379
+ const token = tokens[idx]
380
+ const match = token.info.trim().match(infoReg)
381
+ let lang = match ? match[1] : ''
382
+ const timingEnabled = !!(opt.onFenceDecisionTiming && typeof opt.onFenceDecision === 'function')
383
+ const fenceStartedAt = timingEnabled ? getNowMs() : 0
384
+ const timings = timingEnabled ? {} : null
385
+
386
+ if (match && match[2]) {
387
+ const infoAttrs = getInfoAttr(match[2])
388
+ for (let i = 0; i < infoAttrs.length; i++) {
389
+ const attr = infoAttrs[i]
390
+ token.attrJoin(attr[0], attr[1])
391
+ }
392
+ }
393
+ if (lang && lang !== 'samp') {
394
+ const langClass = opt.langPrefix + lang
395
+ const existingClass = token.attrGet('class')
396
+ token.attrSet('class', existingClass ? langClass + ' ' + existingClass : langClass)
397
+ }
398
+
399
+ let startNumber = -1
400
+ let emphasizeLines = []
401
+ let lineNumberSkipValue
402
+ let lineNumberResetValue
403
+ let wrapEnabled = false
404
+ let preWrapValue
405
+ let commentMarkValue
406
+ const attrNormalizeStartedAt = timingEnabled ? getNowMs() : 0
407
+
408
+ if (token.attrs) {
409
+ const newAttrs = []
410
+ let dataPreStartIndex = -1
411
+ let dataPreEmphasisIndex = -1
412
+ let styleIndex = -1
413
+ let dataPreCommentIndex = -1
414
+ let dataPreLineNumberSkipIndex = -1
415
+ let dataPreLineNumberResetIndex = -1
416
+ let startValue
417
+ let emphasisValue
418
+ let styleValue
419
+ let sawCommentMark = false
420
+ let sawStartAttr = false
421
+ let sawEmphasisAttr = false
422
+ let sawLineNumberSkipAttr = false
423
+ let sawLineNumberResetAttr = false
424
+ const appendOrder = []
425
+
426
+ for (const attr of token.attrs) {
427
+ const name = attr[0]
428
+ const val = attr[1]
429
+
430
+ switch (name) {
431
+ case 'class': {
432
+ const classLang = getLangFromClassAttr(val, opt.langPrefix)
433
+ if (classLang) lang = classLang
434
+ newAttrs.push(attr)
435
+ break
436
+ }
437
+ case 'style':
438
+ styleIndex = newAttrs.length
439
+ styleValue = val
440
+ newAttrs.push(attr)
441
+ break
442
+ case 'data-pre-start':
443
+ startNumber = parseStartNumber(val) ?? -1
444
+ startValue = val
445
+ dataPreStartIndex = newAttrs.length
446
+ newAttrs.push(attr)
447
+ break
448
+ case 'line-number-start':
449
+ case 'start':
450
+ case 'pre-start':
451
+ startNumber = parseStartNumber(val) ?? -1
452
+ startValue = val
453
+ if (!sawStartAttr) {
454
+ appendOrder.push('start')
455
+ sawStartAttr = true
456
+ }
457
+ break
458
+ case 'data-pre-emphasis':
459
+ dataPreEmphasisIndex = newAttrs.length
460
+ newAttrs.push(attr)
461
+ break
462
+ case 'data-pre-comment-mark':
463
+ dataPreCommentIndex = newAttrs.length
464
+ commentMarkValue = val
465
+ newAttrs.push(attr)
466
+ break
467
+ case 'data-pre-line-number-skip':
468
+ dataPreLineNumberSkipIndex = newAttrs.length
469
+ lineNumberSkipValue = val
470
+ newAttrs.push(attr)
471
+ break
472
+ case 'data-pre-line-number-reset':
473
+ dataPreLineNumberResetIndex = newAttrs.length
474
+ lineNumberResetValue = val
475
+ newAttrs.push(attr)
476
+ break
477
+ case 'em-lines':
478
+ case 'emphasize-lines':
479
+ if (opt.setEmphasizeLines) emphasizeLines = getEmphasizeLines(val)
480
+ emphasisValue = val
481
+ if (!sawEmphasisAttr) {
482
+ appendOrder.push('emphasis')
483
+ sawEmphasisAttr = true
484
+ }
485
+ break
486
+ case 'comment-mark':
487
+ commentMarkValue = val
488
+ if (!sawCommentMark) {
489
+ appendOrder.push('comment')
490
+ sawCommentMark = true
491
+ }
492
+ break
493
+ case 'line-number-skip':
494
+ case 'pre-line-number-skip':
495
+ lineNumberSkipValue = val
496
+ if (!sawLineNumberSkipAttr) {
497
+ appendOrder.push('line-number-skip')
498
+ sawLineNumberSkipAttr = true
499
+ }
500
+ break
501
+ case 'line-number-reset':
502
+ case 'pre-line-number-reset':
503
+ lineNumberResetValue = val
504
+ if (!sawLineNumberResetAttr) {
505
+ appendOrder.push('line-number-reset')
506
+ sawLineNumberResetAttr = true
507
+ }
508
+ break
509
+ case 'data-pre-wrap':
510
+ preWrapValue = val
511
+ if (val === '' || val === 'true') wrapEnabled = true
512
+ break
513
+ case 'wrap':
514
+ case 'pre-wrap':
515
+ if (val === '' || val === 'true') wrapEnabled = true
516
+ break
517
+ default:
518
+ newAttrs.push(attr)
519
+ }
520
+ }
521
+
522
+ if (startValue !== undefined && dataPreStartIndex >= 0) newAttrs[dataPreStartIndex][1] = startValue
523
+ if (emphasisValue !== undefined && dataPreEmphasisIndex >= 0) newAttrs[dataPreEmphasisIndex][1] = emphasisValue
524
+ if (commentMarkValue !== undefined && dataPreCommentIndex >= 0) newAttrs[dataPreCommentIndex][1] = commentMarkValue
525
+ if (lineNumberSkipValue !== undefined && dataPreLineNumberSkipIndex >= 0) newAttrs[dataPreLineNumberSkipIndex][1] = lineNumberSkipValue
526
+ if (lineNumberResetValue !== undefined && dataPreLineNumberResetIndex >= 0) newAttrs[dataPreLineNumberResetIndex][1] = lineNumberResetValue
527
+
528
+ for (const kind of appendOrder) {
529
+ if (kind === 'start' && dataPreStartIndex === -1 && startValue !== undefined) {
530
+ newAttrs.push(['data-pre-start', startValue])
531
+ } else if (kind === 'emphasis' && dataPreEmphasisIndex === -1 && emphasisValue !== undefined) {
532
+ newAttrs.push(['data-pre-emphasis', emphasisValue])
533
+ } else if (kind === 'comment' && dataPreCommentIndex === -1 && commentMarkValue !== undefined) {
534
+ newAttrs.push(['data-pre-comment-mark', commentMarkValue])
535
+ } else if (kind === 'line-number-skip' && dataPreLineNumberSkipIndex === -1 && lineNumberSkipValue !== undefined) {
536
+ newAttrs.push(['data-pre-line-number-skip', lineNumberSkipValue])
537
+ } else if (kind === 'line-number-reset' && dataPreLineNumberResetIndex === -1 && lineNumberResetValue !== undefined) {
538
+ newAttrs.push(['data-pre-line-number-reset', lineNumberResetValue])
539
+ }
540
+ }
541
+
542
+ if (startNumber !== -1) {
543
+ styleValue = appendStyleValue(styleValue, 'counter-set:pre-line-number ' + startNumber + ';')
544
+ }
545
+ if (styleIndex >= 0) {
546
+ if (styleValue !== undefined) newAttrs[styleIndex][1] = styleValue
547
+ } else if (styleValue) {
548
+ newAttrs.push(['style', styleValue])
549
+ }
550
+
551
+ if (wrapEnabled) preWrapValue = 'true'
552
+ token.attrs = newAttrs
553
+ }
554
+
555
+ if (timingEnabled) addTimingMs(timings, 'attrNormalizeMs', getNowMs() - attrNormalizeStartedAt)
556
+
557
+ return {
558
+ token,
559
+ lang,
560
+ startNumber,
561
+ emphasizeLines,
562
+ lineNumberSkipValue,
563
+ lineNumberResetValue,
564
+ wrapEnabled,
565
+ preWrapValue,
566
+ commentMarkValue,
567
+ timingEnabled,
568
+ timings,
569
+ fenceStartedAt,
570
+ }
571
+ }
572
+
573
+ export {
574
+ addTimingMs,
575
+ applyLineEndAlias,
576
+ commentLineClass,
577
+ createCommonFenceOptionDefaults,
578
+ emitFenceDecision,
579
+ finalizeCommonFenceOption,
580
+ finalizeFenceTimings,
581
+ getLogicalLineCount,
582
+ getNowMs,
583
+ getEmphasizeLines,
584
+ normalizeEmphasisRanges,
585
+ orderTokenAttrs,
586
+ preWrapStyle,
587
+ prepareFenceRenderContext,
588
+ resolveAdvancedLineNumberPlan,
589
+ splitFenceBlockToLines,
590
+ }
@@ -0,0 +1,120 @@
1
+ const attrReg = /^(?:([.#])(.+)|(.+?)(?:=(["'])?(.*?)\1)?)$/
2
+ const interAttrsSpaceReg = / +/
3
+ const htmlAttrReg = /([^\s=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g
4
+
5
+ const createAttrOrderIndexGetter = (order) => {
6
+ const exact = new Map()
7
+ const prefixes = []
8
+ for (let i = 0; i < order.length; i++) {
9
+ const key = order[i]
10
+ if (key.endsWith('*')) {
11
+ prefixes.push([key.slice(0, -1), i])
12
+ } else if (!exact.has(key)) {
13
+ exact.set(key, i)
14
+ }
15
+ }
16
+ const fallback = order.length
17
+ return (name) => {
18
+ const exactIndex = exact.get(name)
19
+ if (exactIndex !== undefined) return exactIndex
20
+ for (let i = 0; i < prefixes.length; i++) {
21
+ if (name.startsWith(prefixes[i][0])) return prefixes[i][1]
22
+ }
23
+ return fallback
24
+ }
25
+ }
26
+
27
+ const appendStyleValue = (style, addition) => {
28
+ if (!addition) return style
29
+ let next = addition.trim()
30
+ if (!next) return style
31
+ if (!next.endsWith(';')) next += ';'
32
+ if (!style) return next
33
+ const base = style.trimEnd()
34
+ const separator = base.endsWith(';') ? ' ' : '; '
35
+ return base + separator + next
36
+ }
37
+
38
+ const parseHtmlAttrs = (attrText) => {
39
+ const attrs = []
40
+ if (!attrText) return attrs
41
+ htmlAttrReg.lastIndex = 0
42
+ let match
43
+ while ((match = htmlAttrReg.exec(attrText)) !== null) {
44
+ const name = match[1]
45
+ const val = match[2] ?? match[3] ?? match[4] ?? ''
46
+ attrs.push([name, val])
47
+ }
48
+ return attrs
49
+ }
50
+
51
+ const mergeAttrSets = (target, source) => {
52
+ if (!source || source.length === 0) return
53
+ const index = new Map()
54
+ for (let i = 0; i < target.length; i++) index.set(target[i][0], i)
55
+ for (const [name, val] of source) {
56
+ const idx = index.get(name)
57
+ if (name === 'class') {
58
+ if (idx === undefined) {
59
+ target.push([name, val])
60
+ index.set(name, target.length - 1)
61
+ } else if (val) {
62
+ const existing = target[idx][1]
63
+ target[idx][1] = existing ? existing + ' ' + val : val
64
+ }
65
+ continue
66
+ }
67
+ if (name === 'style') {
68
+ if (idx === undefined) {
69
+ target.push([name, val])
70
+ index.set(name, target.length - 1)
71
+ } else {
72
+ target[idx][1] = appendStyleValue(target[idx][1], val)
73
+ }
74
+ continue
75
+ }
76
+ if (idx === undefined) {
77
+ target.push([name, val])
78
+ index.set(name, target.length - 1)
79
+ }
80
+ }
81
+ }
82
+
83
+ const getInfoAttr = (infoAttr) => {
84
+ const attrSets = infoAttr.trim().split(interAttrsSpaceReg)
85
+ const out = []
86
+ for (let attrSet of attrSets) {
87
+ const str = attrReg.exec(attrSet)
88
+ if (!str) continue
89
+ if (str[1] === '.') str[1] = 'class'
90
+ if (str[1] === '#') str[1] = 'id'
91
+ if (str[3]) {
92
+ str[1] = str[3]
93
+ str[2] = str[5] ? str[5].replace(/^['"]/, '').replace(/['"]$/, '') : ''
94
+ }
95
+ out.push([str[1], str[2]])
96
+ }
97
+ return out
98
+ }
99
+
100
+ const getLangFromClassAttr = (classText, langPrefix) => {
101
+ const value = String(classText || '')
102
+ if (!value || !langPrefix || value.indexOf(langPrefix) === -1) return ''
103
+ const list = value.trim().split(/\s+/)
104
+ for (let i = 0; i < list.length; i++) {
105
+ const name = list[i]
106
+ if (name.startsWith(langPrefix) && name.length > langPrefix.length) {
107
+ return name.slice(langPrefix.length)
108
+ }
109
+ }
110
+ return ''
111
+ }
112
+
113
+ export {
114
+ appendStyleValue,
115
+ createAttrOrderIndexGetter,
116
+ getInfoAttr,
117
+ getLangFromClassAttr,
118
+ mergeAttrSets,
119
+ parseHtmlAttrs,
120
+ }