@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.3.0 → 0.4.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 +11 -2
- package/index.js +11 -12
- package/package.json +2 -2
- package/src/phase0-description-list.js +211 -107
- package/src/phase2-convert.js +133 -142
- package/src/phase3-attributes.js +24 -15
- package/src/phase5-spans.js +20 -7
- package/src/preprocess-literal-lists.js +123 -105
- package/src/types-utility.js +0 -15
- package/src/phase6-attrs-migration.js +0 -184
package/src/phase2-convert.js
CHANGED
|
@@ -22,12 +22,28 @@ export function convertLists(tokens, listInfos, opt) {
|
|
|
22
22
|
// Runs after conversion, so already-converted ordered_lists are also processed
|
|
23
23
|
if (!opt.unremoveUlNest) {
|
|
24
24
|
const hasBulletLists = listInfos.some(info => info.originalType === 'bullet_list_open')
|
|
25
|
-
if (hasBulletLists) {
|
|
25
|
+
if (hasBulletLists && hasSimplifiableBulletStructure(tokens)) {
|
|
26
26
|
simplifyNestedBulletLists(tokens)
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function hasSimplifiableBulletStructure(tokens) {
|
|
32
|
+
for (let i = 0; i < tokens.length - 2; i++) {
|
|
33
|
+
if (tokens[i].type !== 'bullet_list_open') {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
const firstItem = tokens[i + 1]
|
|
37
|
+
const firstChild = tokens[i + 2]
|
|
38
|
+
if (firstItem?.type === 'list_item_open' &&
|
|
39
|
+
firstChild?.type === 'ordered_list_open' &&
|
|
40
|
+
!firstChild._literalList) {
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
/**
|
|
32
48
|
* Convert bullet_list to ordered_list
|
|
33
49
|
*/
|
|
@@ -85,7 +101,7 @@ function convertBulletToOrdered(tokens, listInfo) {
|
|
|
85
101
|
// Compare parent item's marker type with child list's marker type
|
|
86
102
|
// Don't propagate if different (will be flattened)
|
|
87
103
|
const childMarkerInfo = nestedList.items && nestedList.items[0] && nestedList.items[0].markerInfo
|
|
88
|
-
if (!childMarkerInfo || item.markerInfo.
|
|
104
|
+
if (!childMarkerInfo || item.markerInfo.type !== childMarkerInfo.type) {
|
|
89
105
|
continue // Skip if marker types differ
|
|
90
106
|
}
|
|
91
107
|
|
|
@@ -103,14 +119,14 @@ function convertBulletToOrdered(tokens, listInfo) {
|
|
|
103
119
|
|
|
104
120
|
// Remove marker text from inline tokens
|
|
105
121
|
if (markerInfo && markerInfo.markers) {
|
|
106
|
-
removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo
|
|
122
|
+
removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo)
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
|
|
110
126
|
/**
|
|
111
127
|
* Remove markers from inline token content
|
|
112
128
|
*/
|
|
113
|
-
function removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo
|
|
129
|
+
function removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo) {
|
|
114
130
|
const listToken = tokens[startIndex]
|
|
115
131
|
const targetLevel = (listToken.level || 0) + 3 // list_open(0) -> list_item(1) -> paragraph(2) -> inline(3)
|
|
116
132
|
|
|
@@ -182,16 +198,28 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
182
198
|
// Check if this bullet_list is all ul>li>ol/ul pattern
|
|
183
199
|
const listCloseIdx = getListClose(i)
|
|
184
200
|
if (listCloseIdx === -1) continue
|
|
201
|
+
|
|
202
|
+
// Fast reject: flattening requires the first list item to start with ordered_list_open.
|
|
203
|
+
const firstItemOpen = i + 1
|
|
204
|
+
const firstItemChild = i + 2
|
|
205
|
+
if (tokens[firstItemOpen]?.type !== 'list_item_open' ||
|
|
206
|
+
tokens[firstItemChild]?.type !== 'ordered_list_open' ||
|
|
207
|
+
tokens[firstItemChild]?._literalList) {
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
185
210
|
|
|
186
211
|
// Check all list_items in bullet_list
|
|
187
212
|
const itemIndices = []
|
|
188
|
-
let totalItems = 0
|
|
189
213
|
let idx = i + 1
|
|
214
|
+
let allItemsHaveDirectInnerList = true
|
|
190
215
|
|
|
191
216
|
while (idx < listCloseIdx) {
|
|
192
217
|
if (tokens[idx].type === 'list_item_open') {
|
|
193
|
-
totalItems++
|
|
194
218
|
const itemCloseIdx = getListItemClose(idx)
|
|
219
|
+
if (itemCloseIdx === -1) {
|
|
220
|
+
allItemsHaveDirectInnerList = false
|
|
221
|
+
break
|
|
222
|
+
}
|
|
195
223
|
|
|
196
224
|
|
|
197
225
|
// Find inner list within list_item
|
|
@@ -216,43 +244,51 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
216
244
|
}
|
|
217
245
|
}
|
|
218
246
|
|
|
219
|
-
if (innerListOpen
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// Check if there's extra content before/after ol (whether it's only ol)
|
|
225
|
-
const beforeContent = innerListOpen - (idx + 1) // Token count from after list_item_open to ol
|
|
226
|
-
const afterContent = itemCloseIdx - (innerListCloseIdx + 1) // Token count from after ol to list_item_close
|
|
227
|
-
const hasExtraContent = beforeContent > 0 || afterContent > 0
|
|
228
|
-
const literalNumber = extractFirstListItemNumber(tokens, innerListOpen, innerListCloseIdx)
|
|
229
|
-
|
|
230
|
-
const innerListMarkerInfo = tokens[innerListOpen]?._markerInfo
|
|
231
|
-
const isSimpleMarkerParagraph = (() => {
|
|
232
|
-
const precedingCount = innerListOpen - (idx + 1)
|
|
233
|
-
if (precedingCount !== 3) return false
|
|
234
|
-
const paraOpen = tokens[idx + 1]
|
|
235
|
-
const inlineToken = tokens[idx + 2]
|
|
236
|
-
const paraClose = tokens[idx + 3]
|
|
237
|
-
return paraOpen?.type === 'paragraph_open' &&
|
|
238
|
-
inlineToken?.type === 'inline' &&
|
|
239
|
-
paraClose?.type === 'paragraph_close'
|
|
240
|
-
})()
|
|
247
|
+
if (innerListOpen === -1 || innerListOpen !== idx + 1) {
|
|
248
|
+
// Flattening requires every list item to contain only one direct child list.
|
|
249
|
+
allItemsHaveDirectInnerList = false
|
|
250
|
+
break
|
|
251
|
+
}
|
|
241
252
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
outerItemClose: itemCloseIdx,
|
|
245
|
-
innerListOpen: innerListOpen,
|
|
246
|
-
innerListClose: innerListCloseIdx,
|
|
247
|
-
innerListType: innerListType,
|
|
248
|
-
hasExtraContent: hasExtraContent,
|
|
249
|
-
extraContentStart: innerListCloseIdx + 1,
|
|
250
|
-
extraContentEnd: itemCloseIdx,
|
|
251
|
-
innerListMarkerInfo,
|
|
252
|
-
literalNumber,
|
|
253
|
-
flattenFirstParagraph: isSimpleMarkerParagraph
|
|
254
|
-
})
|
|
253
|
+
if (innerListCloseIdx === -1) {
|
|
254
|
+
innerListCloseIdx = getListClose(innerListOpen)
|
|
255
255
|
}
|
|
256
|
+
if (innerListCloseIdx === -1) {
|
|
257
|
+
allItemsHaveDirectInnerList = false
|
|
258
|
+
break
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Check if there's extra content before/after ol (whether it's only ol)
|
|
262
|
+
const beforeContent = innerListOpen - (idx + 1) // Token count from after list_item_open to ol
|
|
263
|
+
const afterContent = itemCloseIdx - (innerListCloseIdx + 1) // Token count from after ol to list_item_close
|
|
264
|
+
const hasExtraContent = beforeContent > 0 || afterContent > 0
|
|
265
|
+
const literalNumber = extractFirstListItemNumber(tokens, innerListOpen, innerListCloseIdx)
|
|
266
|
+
|
|
267
|
+
const innerListMarkerInfo = tokens[innerListOpen]?._markerInfo
|
|
268
|
+
const isSimpleMarkerParagraph = (() => {
|
|
269
|
+
const precedingCount = innerListOpen - (idx + 1)
|
|
270
|
+
if (precedingCount !== 3) return false
|
|
271
|
+
const paraOpen = tokens[idx + 1]
|
|
272
|
+
const inlineToken = tokens[idx + 2]
|
|
273
|
+
const paraClose = tokens[idx + 3]
|
|
274
|
+
return paraOpen?.type === 'paragraph_open' &&
|
|
275
|
+
inlineToken?.type === 'inline' &&
|
|
276
|
+
paraClose?.type === 'paragraph_close'
|
|
277
|
+
})()
|
|
278
|
+
|
|
279
|
+
itemIndices.push({
|
|
280
|
+
outerItemOpen: idx,
|
|
281
|
+
outerItemClose: itemCloseIdx,
|
|
282
|
+
innerListOpen: innerListOpen,
|
|
283
|
+
innerListClose: innerListCloseIdx,
|
|
284
|
+
innerListType: innerListType,
|
|
285
|
+
hasExtraContent: hasExtraContent,
|
|
286
|
+
extraContentStart: innerListCloseIdx + 1,
|
|
287
|
+
extraContentEnd: itemCloseIdx,
|
|
288
|
+
innerListMarkerInfo,
|
|
289
|
+
literalNumber,
|
|
290
|
+
flattenFirstParagraph: isSimpleMarkerParagraph
|
|
291
|
+
})
|
|
256
292
|
|
|
257
293
|
idx = itemCloseIdx + 1
|
|
258
294
|
} else {
|
|
@@ -290,10 +326,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
290
326
|
// 1. Every list_item has a nested list (the classic `- 1.` pattern)
|
|
291
327
|
// 2. That nested list appears as the very first child (no preceding paragraph text)
|
|
292
328
|
// This prevents flattening normal `* Parent` lists that intentionally nest an ordered list.
|
|
293
|
-
const
|
|
294
|
-
const innerListIsFirstChild = allItemsHaveInnerList &&
|
|
295
|
-
itemIndices.every(item => item.innerListOpen === item.outerItemOpen + 1)
|
|
296
|
-
const shouldSimplify = allItemsHaveInnerList && innerListIsFirstChild
|
|
329
|
+
const shouldSimplify = allItemsHaveDirectInnerList && itemIndices.length > 0
|
|
297
330
|
|
|
298
331
|
if (shouldSimplify && itemIndices.length > 0) {
|
|
299
332
|
const allSameType = itemIndices.every(item => item.innerListType === itemIndices[0].innerListType)
|
|
@@ -353,13 +386,8 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
353
386
|
}
|
|
354
387
|
}
|
|
355
388
|
|
|
356
|
-
// Build
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
// Tokens before bullet_list_open
|
|
360
|
-
for (let j = 0; j < i; j++) {
|
|
361
|
-
newTokens.push(tokens[j])
|
|
362
|
-
}
|
|
389
|
+
// Build replacement tokens for this list block only.
|
|
390
|
+
const replacementTokens = []
|
|
363
391
|
|
|
364
392
|
// Add first inner list open token and save merged marker info
|
|
365
393
|
const firstListToken = tokens[itemIndices[0].innerListOpen]
|
|
@@ -381,7 +409,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
381
409
|
// If already ordered_list, use as is (already converted)
|
|
382
410
|
}
|
|
383
411
|
|
|
384
|
-
|
|
412
|
+
replacementTokens.push(firstListToken)
|
|
385
413
|
|
|
386
414
|
// Merge and save marker info
|
|
387
415
|
if (allMarkers.length > 0) {
|
|
@@ -421,6 +449,11 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
421
449
|
}
|
|
422
450
|
}
|
|
423
451
|
}
|
|
452
|
+
|
|
453
|
+
// Cache top-level list-item ranges for each inner list to avoid repeated scans.
|
|
454
|
+
const listItemRangesByItem = itemIndices.map(item =>
|
|
455
|
+
collectListItemRanges(tokens, item.innerListOpen, item.innerListClose, listItemCloseByOpen)
|
|
456
|
+
)
|
|
424
457
|
|
|
425
458
|
// ===== Outer UL (Level 0 after conversion) loose/tight determination =====
|
|
426
459
|
// In flattened pattern (`- 1.` etc), no direct paragraph in outer list_item,
|
|
@@ -479,24 +512,13 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
479
512
|
// When map is missing, skip map-based blank-line checks and fall back to paragraph.hidden.
|
|
480
513
|
if (hasTokenMaps) {
|
|
481
514
|
for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
// Collect list_items in inner list
|
|
485
|
-
const innerListItems = []
|
|
486
|
-
for (let j = item.innerListOpen + 1; j < item.innerListClose; j++) {
|
|
487
|
-
if (tokens[j].type === 'list_item_open' &&
|
|
488
|
-
tokens[j].level === tokens[item.innerListOpen].level + 1) {
|
|
489
|
-
const itemOpen = j
|
|
490
|
-
const itemClose = getListItemClose(j)
|
|
491
|
-
innerListItems.push({ open: itemOpen, close: itemClose })
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
515
|
+
const listItemRanges = listItemRangesByItem[itemIdx]
|
|
516
|
+
|
|
495
517
|
// Check blank lines between list_items in inner list
|
|
496
|
-
if (
|
|
497
|
-
for (let k = 0; k <
|
|
498
|
-
const currentItem =
|
|
499
|
-
const nextItem =
|
|
518
|
+
if (listItemRanges.length > 1) {
|
|
519
|
+
for (let k = 0; k < listItemRanges.length - 1; k++) {
|
|
520
|
+
const currentItem = listItemRanges[k]
|
|
521
|
+
const nextItem = listItemRanges[k + 1]
|
|
500
522
|
|
|
501
523
|
// Get currentItem end line
|
|
502
524
|
let currentEndLine = null
|
|
@@ -526,21 +548,11 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
526
548
|
// If inner list has paragraph with hidden=false, it's loose
|
|
527
549
|
if (!innerListIsLooseDueToBlankLines) {
|
|
528
550
|
for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
for (let j = item.innerListOpen + 1; j < item.innerListClose; j++) {
|
|
535
|
-
if (tokens[j].type === 'list_item_open' &&
|
|
536
|
-
tokens[j].level === tokens[item.innerListOpen].level + 1) {
|
|
537
|
-
innerListItemOpen = j
|
|
538
|
-
innerListItemClose = getListItemClose(j)
|
|
539
|
-
break
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (innerListItemOpen !== -1 && innerListItemClose !== -1) {
|
|
551
|
+
const listItemRanges = listItemRangesByItem[itemIdx]
|
|
552
|
+
const firstRange = listItemRanges[0]
|
|
553
|
+
if (firstRange) {
|
|
554
|
+
const innerListItemOpen = firstRange.open
|
|
555
|
+
const innerListItemClose = firstRange.close
|
|
544
556
|
// Check hidden of first paragraph in inner list item
|
|
545
557
|
let nestedListDepth = 0
|
|
546
558
|
for (let j = innerListItemOpen + 1; j < innerListItemClose; j++) {
|
|
@@ -568,24 +580,20 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
568
580
|
// This must be executed AFTER innerListIsLooseDueToBlankLines determination
|
|
569
581
|
if (outerUlIsLoose && !(itemIndices.length === 1)) {
|
|
570
582
|
for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
tokens[k].hidden = false
|
|
584
|
-
}
|
|
585
|
-
break // Only first paragraph
|
|
586
|
-
}
|
|
583
|
+
const listItemRanges = listItemRangesByItem[itemIdx]
|
|
584
|
+
const firstRange = listItemRanges[0]
|
|
585
|
+
if (!firstRange) {
|
|
586
|
+
continue
|
|
587
|
+
}
|
|
588
|
+
const listItemOpen = firstRange.open
|
|
589
|
+
const listItemClose = firstRange.close
|
|
590
|
+
for (let k = listItemOpen + 1; k < listItemClose; k++) {
|
|
591
|
+
if (tokens[k].type === 'paragraph_open' &&
|
|
592
|
+
tokens[k].level === tokens[listItemOpen].level + 1) {
|
|
593
|
+
if (!tokens[k]._literalTight) {
|
|
594
|
+
tokens[k].hidden = false
|
|
587
595
|
}
|
|
588
|
-
break // Only first
|
|
596
|
+
break // Only first paragraph
|
|
589
597
|
}
|
|
590
598
|
}
|
|
591
599
|
}
|
|
@@ -597,18 +605,10 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
597
605
|
// ===== Merge each inner list's contents and place extra content appropriately =====
|
|
598
606
|
for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
|
|
599
607
|
const item = itemIndices[itemIdx]
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
for (let j = item.innerListOpen + 1; j < item.innerListClose; j++) {
|
|
604
|
-
if (tokens[j].type === 'list_item_open' &&
|
|
605
|
-
tokens[j].level === tokens[item.innerListOpen].level + 1) {
|
|
606
|
-
innerListItemCount++
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
608
|
+
|
|
609
|
+
const listItemRanges = listItemRangesByItem[itemIdx]
|
|
610
|
+
const innerListItemCount = listItemRanges.length
|
|
610
611
|
const innerListToken = tokens[item.innerListOpen]
|
|
611
|
-
const listItemRanges = collectListItemRanges(tokens, item.innerListOpen, item.innerListClose, listItemCloseByOpen)
|
|
612
612
|
if (listItemRanges.length === 0) {
|
|
613
613
|
continue
|
|
614
614
|
}
|
|
@@ -620,7 +620,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
620
620
|
if (innerListItemOpen !== -1 && innerListItemClose !== -1) {
|
|
621
621
|
// Add list_item_open (use original token to preserve info)
|
|
622
622
|
// Note: value attribute is added in Phase3 (not during simplification)
|
|
623
|
-
|
|
623
|
+
replacementTokens.push(tokens[innerListItemOpen])
|
|
624
624
|
|
|
625
625
|
// This item's loose/tight determination
|
|
626
626
|
// If entire list is loose, this item is also loose
|
|
@@ -669,7 +669,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
669
669
|
// - firstParagraphIsLoose: blank line after parent item's first paragraph (same marker type only)
|
|
670
670
|
// Note: hasExtraContent makes parent item loose but doesn't affect child lists
|
|
671
671
|
// Note: outerUlIsLoose excluded (Test 25: handle case where parent is loose but child is tight)
|
|
672
|
-
const shouldPropagateLooseToChildren = innerListIsLooseDueToBlankLines || token._parentIsLoose || firstParagraphIsLoose
|
|
672
|
+
const shouldPropagateLooseToChildren = innerListIsLooseDueToBlankLines || token._parentIsLoose || firstParagraphIsLoose
|
|
673
673
|
|
|
674
674
|
// Copy tokens in inner list item (exclude nested list paragraphs)
|
|
675
675
|
let nestedListDepth = 0
|
|
@@ -687,13 +687,13 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
687
687
|
if (shouldPropagateLooseToChildren) {
|
|
688
688
|
// Set _parentIsLoose flag (optimize with direct property assignment)
|
|
689
689
|
tokenToPush._parentIsLoose = true
|
|
690
|
-
|
|
690
|
+
replacementTokens.push(tokenToPush)
|
|
691
691
|
} else {
|
|
692
|
-
|
|
692
|
+
replacementTokens.push(tokenToPush)
|
|
693
693
|
}
|
|
694
694
|
} else if (tokenToPush.type === 'bullet_list_close' || tokenToPush.type === 'ordered_list_close') {
|
|
695
695
|
nestedListDepth--
|
|
696
|
-
|
|
696
|
+
replacementTokens.push(tokenToPush)
|
|
697
697
|
} else if (nestedListDepth === 0 && (tokenToPush.type === 'paragraph_open' || tokenToPush.type === 'paragraph_close')) {
|
|
698
698
|
// Update paragraph_open/close hidden state (only outside nested lists)
|
|
699
699
|
|
|
@@ -718,7 +718,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
718
718
|
} else {
|
|
719
719
|
// Match paragraph_close hidden state to corresponding paragraph_open
|
|
720
720
|
// Reference preceding paragraph_open's hidden state
|
|
721
|
-
const prevToken =
|
|
721
|
+
const prevToken = replacementTokens[replacementTokens.length - 2] // Skip preceding inline, get paragraph_open before it
|
|
722
722
|
if (prevToken && prevToken.type === 'paragraph_open') {
|
|
723
723
|
tokenToPush.hidden = prevToken.hidden
|
|
724
724
|
} else {
|
|
@@ -727,9 +727,9 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
727
727
|
}
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
-
|
|
730
|
+
replacementTokens.push(tokenToPush)
|
|
731
731
|
} else {
|
|
732
|
-
|
|
732
|
+
replacementTokens.push(tokenToPush)
|
|
733
733
|
}
|
|
734
734
|
}
|
|
735
735
|
|
|
@@ -764,7 +764,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
764
764
|
}
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
-
|
|
767
|
+
replacementTokens.push(tokenToPush)
|
|
768
768
|
}
|
|
769
769
|
}
|
|
770
770
|
|
|
@@ -784,12 +784,12 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
784
784
|
item.innerListMarkerInfo
|
|
785
785
|
)
|
|
786
786
|
for (const nestedToken of nestedTokens) {
|
|
787
|
-
|
|
787
|
+
replacementTokens.push(nestedToken)
|
|
788
788
|
}
|
|
789
789
|
}
|
|
790
790
|
|
|
791
791
|
// Add list_item_close
|
|
792
|
-
|
|
792
|
+
replacementTokens.push(tokens[innerListItemClose])
|
|
793
793
|
}
|
|
794
794
|
}
|
|
795
795
|
|
|
@@ -806,29 +806,20 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
806
806
|
// If already ordered_list_close, use as is
|
|
807
807
|
}
|
|
808
808
|
|
|
809
|
-
|
|
809
|
+
replacementTokens.push(lastListCloseToken)
|
|
810
810
|
|
|
811
|
-
//
|
|
812
|
-
|
|
813
|
-
newTokens.push(tokens[j])
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Replace token array
|
|
817
|
-
tokens.length = 0
|
|
818
|
-
tokens.push(...newTokens)
|
|
811
|
+
// Replace only the current outer bullet-list range.
|
|
812
|
+
tokens.splice(i, listCloseIdx - i + 1, ...replacementTokens)
|
|
819
813
|
|
|
820
814
|
// Remove markers from merged list
|
|
821
815
|
if (firstListToken._markerInfo && firstListToken._markerInfo.markers) {
|
|
822
|
-
|
|
823
|
-
while (listStartIdx < tokens.length && tokens[listStartIdx] !== firstListToken) {
|
|
824
|
-
listStartIdx++
|
|
825
|
-
}
|
|
816
|
+
const listStartIdx = i
|
|
826
817
|
if (listStartIdx < tokens.length) {
|
|
827
818
|
const listEndIdx = findMatchingClose(tokens, listStartIdx,
|
|
828
819
|
firstListToken.type,
|
|
829
|
-
firstListToken.type.replace('_open', '_close')
|
|
820
|
+
firstListToken.type.replace('_open', '_close'))
|
|
830
821
|
if (listEndIdx !== -1) {
|
|
831
|
-
removeMarkersFromContent(tokens, listStartIdx, listEndIdx, firstListToken._markerInfo
|
|
822
|
+
removeMarkersFromContent(tokens, listStartIdx, listEndIdx, firstListToken._markerInfo)
|
|
832
823
|
}
|
|
833
824
|
}
|
|
834
825
|
}
|
|
@@ -933,7 +924,7 @@ function buildNestedListTokens(tokens, childRanges, innerListOpenIdx, innerListC
|
|
|
933
924
|
nestedTokens.push(nestedOpen)
|
|
934
925
|
for (const range of childRanges) {
|
|
935
926
|
for (let i = range.open; i <= range.close; i++) {
|
|
936
|
-
nestedTokens.push(cloneToken(tokens[i], { levelShift
|
|
927
|
+
nestedTokens.push(cloneToken(tokens[i], { levelShift }))
|
|
937
928
|
}
|
|
938
929
|
}
|
|
939
930
|
const nestedClose = cloneToken(tokens[innerListCloseIdx], { levelShift })
|
|
@@ -942,7 +933,7 @@ function buildNestedListTokens(tokens, childRanges, innerListOpenIdx, innerListC
|
|
|
942
933
|
}
|
|
943
934
|
|
|
944
935
|
function cloneToken(token, options = {}) {
|
|
945
|
-
const { levelShift = 0
|
|
936
|
+
const { levelShift = 0 } = options
|
|
946
937
|
const TokenClass = token.constructor
|
|
947
938
|
const cloned = new TokenClass(token.type, token.tag, token.nesting)
|
|
948
939
|
cloned.attrs = token.attrs ? token.attrs.map(([name, value]) => [name, value]) : null
|
|
@@ -956,7 +947,7 @@ function cloneToken(token, options = {}) {
|
|
|
956
947
|
cloned.hidden = token.hidden
|
|
957
948
|
if (Array.isArray(token.children)) {
|
|
958
949
|
cloned.children = token.children.length > 0
|
|
959
|
-
? token.children.map(child => cloneToken(child, { levelShift
|
|
950
|
+
? token.children.map(child => cloneToken(child, { levelShift }))
|
|
960
951
|
: []
|
|
961
952
|
} else {
|
|
962
953
|
cloned.children = token.children ?? null
|
package/src/phase3-attributes.js
CHANGED
|
@@ -12,6 +12,7 @@ import { buildListCloseIndexMap, findMatchingClose } from './list-helpers.js'
|
|
|
12
12
|
export function addAttributes(tokens, opt) {
|
|
13
13
|
const closeMap = buildListCloseIndexMap(tokens)
|
|
14
14
|
const listCloseByOpen = closeMap.listCloseByOpen
|
|
15
|
+
let hasAnyListItemValue = false
|
|
15
16
|
|
|
16
17
|
// Traverse token array and add attributes to ordered_list_open tokens
|
|
17
18
|
// Token array may have been rebuilt in Phase2,
|
|
@@ -21,13 +22,31 @@ export function addAttributes(tokens, opt) {
|
|
|
21
22
|
|
|
22
23
|
if (token.type === 'ordered_list_open') {
|
|
23
24
|
addListAttributesForToken(tokens, token, i, opt, listCloseByOpen)
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
if (!hasAnyListItemValue && token.type === 'list_item_open' && hasValueAttr(token)) {
|
|
28
|
+
hasAnyListItemValue = true
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
// Normalize value attributes of ordered_list generated by markdown-it
|
|
28
33
|
// Remove unnecessary value attributes after Phase2 simplification
|
|
29
34
|
// Normalize value attributes and marker metadata for all ordered lists
|
|
30
|
-
|
|
35
|
+
if (hasAnyListItemValue) {
|
|
36
|
+
normalizeAndConvertValueAttributes(tokens, listCloseByOpen)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasValueAttr(token) {
|
|
41
|
+
if (!Array.isArray(token.attrs)) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
for (const [name] of token.attrs) {
|
|
45
|
+
if (name === 'value') {
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
/**
|
|
@@ -75,13 +94,10 @@ function addListAttributesForToken(tokens, token, tokenIndex, opt, listCloseByOp
|
|
|
75
94
|
|
|
76
95
|
// 1. type attribute or role attribute (add first)
|
|
77
96
|
// Always use role="list" for alwaysMarkerSpan
|
|
78
|
-
if (opt.useCounterStyle) {
|
|
79
|
-
// When user chooses @counter-style, we avoid role and inline styles.
|
|
80
|
-
// Still add class so users can target with CSS (e.g., counter-reset/counter-increment or @counter-style usage).
|
|
81
|
-
// Do not add type attribute (counter-style will handle visuals)
|
|
82
|
-
} else if (typeAttrs.type && !opt.alwaysMarkerSpan) {
|
|
97
|
+
if (!opt.useCounterStyle && typeAttrs.type && !opt.alwaysMarkerSpan) {
|
|
83
98
|
addAttr(token, 'type', typeAttrs.type)
|
|
84
|
-
} else {
|
|
99
|
+
} else if (!opt.useCounterStyle) {
|
|
100
|
+
// In non-counter-style mode, custom markers (or alwaysMarkerSpan) use role=list.
|
|
85
101
|
addAttr(token, 'role', 'list')
|
|
86
102
|
if (opt.hasListStyleNone) {
|
|
87
103
|
addAttr(token, 'style', 'list-style: none;')
|
|
@@ -117,12 +133,6 @@ function addListAttributesForToken(tokens, token, tokenIndex, opt, listCloseByOp
|
|
|
117
133
|
addAttr(token, 'class', typeAttrs.class)
|
|
118
134
|
}
|
|
119
135
|
}
|
|
120
|
-
|
|
121
|
-
// If user requested @counter-style, add a helper class to identify lists
|
|
122
|
-
if (opt.useCounterStyle) {
|
|
123
|
-
// Do not add helper class when using counter-style; user CSS should target generated ol-* classes.
|
|
124
|
-
}
|
|
125
|
-
|
|
126
136
|
// 4. data-marker-prefix/suffix
|
|
127
137
|
if (!opt.omitMarkerMetadata) {
|
|
128
138
|
if (markerInfo.markers[0].prefix) {
|
|
@@ -197,9 +207,8 @@ function addListItemValues(tokens, listOpenIndex, markerInfo, listCloseByOpen =
|
|
|
197
207
|
* Normalize value attributes generated by markdown-it for ordered lists.
|
|
198
208
|
* Remove value attributes for consecutive numbers.
|
|
199
209
|
* @param {Array} tokens - Token array
|
|
200
|
-
* @param {Object} opt - Plugin options
|
|
201
210
|
*/
|
|
202
|
-
function normalizeAndConvertValueAttributes(tokens,
|
|
211
|
+
function normalizeAndConvertValueAttributes(tokens, listCloseByOpen = null) {
|
|
203
212
|
for (let i = 0; i < tokens.length; i++) {
|
|
204
213
|
const token = tokens[i]
|
|
205
214
|
|
package/src/phase5-spans.js
CHANGED
|
@@ -13,8 +13,18 @@ export function generateSpans(tokens, opt) {
|
|
|
13
13
|
if (opt.useCounterStyle) {
|
|
14
14
|
return
|
|
15
15
|
}
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const rawSpanClass = opt?.markerSpanClass
|
|
17
|
+
const normalizedSpanClass = typeof rawSpanClass === 'string'
|
|
18
|
+
? rawSpanClass.trim()
|
|
19
|
+
: ''
|
|
20
|
+
const spanClass = normalizedSpanClass || 'li-num'
|
|
21
|
+
let listCloseByOpen = null
|
|
22
|
+
const getListCloseByOpen = () => {
|
|
23
|
+
if (!listCloseByOpen) {
|
|
24
|
+
listCloseByOpen = buildListCloseIndexMap(tokens).listCloseByOpen
|
|
25
|
+
}
|
|
26
|
+
return listCloseByOpen
|
|
27
|
+
}
|
|
18
28
|
|
|
19
29
|
// Traverse token array and add spans to ordered_list_open tokens
|
|
20
30
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -22,12 +32,16 @@ export function generateSpans(tokens, opt) {
|
|
|
22
32
|
|
|
23
33
|
if (token.type === 'ordered_list_open' && token._markerInfo) {
|
|
24
34
|
const markerInfo = token._markerInfo
|
|
35
|
+
if (opt.alwaysMarkerSpan) {
|
|
36
|
+
addMarkerSpans(tokens, token, i, markerInfo, opt, spanClass, getListCloseByOpen())
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
25
39
|
const firstMarker = markerInfo.markers[0]
|
|
26
40
|
const typeAttrs = getTypeAttributes(markerInfo.type, firstMarker, opt)
|
|
27
41
|
|
|
28
|
-
// Generate span
|
|
29
|
-
if (!typeAttrs.type
|
|
30
|
-
addMarkerSpans(tokens, token, i, markerInfo, opt,
|
|
42
|
+
// Generate span for custom marker lists.
|
|
43
|
+
if (!typeAttrs.type) {
|
|
44
|
+
addMarkerSpans(tokens, token, i, markerInfo, opt, spanClass, getListCloseByOpen())
|
|
31
45
|
}
|
|
32
46
|
}
|
|
33
47
|
}
|
|
@@ -36,7 +50,7 @@ export function generateSpans(tokens, opt) {
|
|
|
36
50
|
/**
|
|
37
51
|
* Add marker <span> to the first inline token of each list item.
|
|
38
52
|
*/
|
|
39
|
-
function addMarkerSpans(tokens, listToken, listIndex, markerInfo, opt, listCloseByOpen = null) {
|
|
53
|
+
function addMarkerSpans(tokens, listToken, listIndex, markerInfo, opt, spanClass, listCloseByOpen = null) {
|
|
40
54
|
// Find end position of this ordered_list
|
|
41
55
|
let listCloseIndex = listCloseByOpen ? listCloseByOpen[listIndex] : -1
|
|
42
56
|
if (typeof listCloseIndex !== 'number' || listCloseIndex === -1) {
|
|
@@ -65,7 +79,6 @@ function addMarkerSpans(tokens, listToken, listIndex, markerInfo, opt, listClose
|
|
|
65
79
|
}
|
|
66
80
|
// Insert span_open, text, span_close before inline token
|
|
67
81
|
const spanOpen = new tokens[i].constructor('span_open', 'span', 1)
|
|
68
|
-
const spanClass = (opt && opt.markerSpanClass) ? String(opt.markerSpanClass) : 'li-num'
|
|
69
82
|
spanOpen.attrSet('class', spanClass)
|
|
70
83
|
spanOpen.attrSet('aria-hidden', 'true')
|
|
71
84
|
|