@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.2.3 → 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.
@@ -22,13 +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) {
26
- const listInfoMap = buildListInfoMap(listInfos)
27
- simplifyNestedBulletLists(tokens, opt, listInfoMap)
25
+ if (hasBulletLists && hasSimplifiableBulletStructure(tokens)) {
26
+ simplifyNestedBulletLists(tokens)
28
27
  }
29
28
  }
30
29
  }
31
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
+
32
47
  /**
33
48
  * Convert bullet_list to ordered_list
34
49
  */
@@ -86,7 +101,7 @@ function convertBulletToOrdered(tokens, listInfo) {
86
101
  // Compare parent item's marker type with child list's marker type
87
102
  // Don't propagate if different (will be flattened)
88
103
  const childMarkerInfo = nestedList.items && nestedList.items[0] && nestedList.items[0].markerInfo
89
- if (!childMarkerInfo || item.markerInfo.markerType !== childMarkerInfo.markerType) {
104
+ if (!childMarkerInfo || item.markerInfo.type !== childMarkerInfo.type) {
90
105
  continue // Skip if marker types differ
91
106
  }
92
107
 
@@ -104,14 +119,14 @@ function convertBulletToOrdered(tokens, listInfo) {
104
119
 
105
120
  // Remove marker text from inline tokens
106
121
  if (markerInfo && markerInfo.markers) {
107
- removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo, listInfo)
122
+ removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo)
108
123
  }
109
124
  }
110
125
 
111
126
  /**
112
127
  * Remove markers from inline token content
113
128
  */
114
- function removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo, listInfo) {
129
+ function removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo) {
115
130
  const listToken = tokens[startIndex]
116
131
  const targetLevel = (listToken.level || 0) + 3 // list_open(0) -> list_item(1) -> paragraph(2) -> inline(3)
117
132
 
@@ -143,11 +158,11 @@ const LEADING_SPACE_REGEX = /^\s+/
143
158
  * When the middle list_item is empty (contains only the inner list),
144
159
  * remove the outer ul and the intermediate li.
145
160
  * @param {Array} tokens - Token array
146
- * @param {Object} opt - Options
147
- * @param {Map} listInfoMap - Map of listInfo keyed by startIndex
148
161
  */
149
- function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
162
+ function simplifyNestedBulletLists(tokens) {
150
163
  let modified = true
164
+ // token.map is stable across passes; used to decide map-based blank-line checks.
165
+ const hasTokenMaps = tokens.some(token => token && token.map && token.map.length)
151
166
 
152
167
  // ===== Phase 1: Simplify ul>li>ol structure (multiple passes needed) =====
153
168
  while (modified) {
@@ -183,16 +198,28 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
183
198
  // Check if this bullet_list is all ul>li>ol/ul pattern
184
199
  const listCloseIdx = getListClose(i)
185
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
+ }
186
210
 
187
211
  // Check all list_items in bullet_list
188
212
  const itemIndices = []
189
- let totalItems = 0
190
213
  let idx = i + 1
214
+ let allItemsHaveDirectInnerList = true
191
215
 
192
216
  while (idx < listCloseIdx) {
193
217
  if (tokens[idx].type === 'list_item_open') {
194
- totalItems++
195
218
  const itemCloseIdx = getListItemClose(idx)
219
+ if (itemCloseIdx === -1) {
220
+ allItemsHaveDirectInnerList = false
221
+ break
222
+ }
196
223
 
197
224
 
198
225
  // Find inner list within list_item
@@ -217,43 +244,51 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
217
244
  }
218
245
  }
219
246
 
220
- if (innerListOpen !== -1) {
221
- if (innerListCloseIdx === -1) {
222
- innerListCloseIdx = getListClose(innerListOpen)
223
- }
224
-
225
- // Check if there's extra content before/after ol (whether it's only ol)
226
- const beforeContent = innerListOpen - (idx + 1) // Token count from after list_item_open to ol
227
- const afterContent = itemCloseIdx - (innerListCloseIdx + 1) // Token count from after ol to list_item_close
228
- const hasExtraContent = beforeContent > 0 || afterContent > 0
229
- const literalNumber = extractFirstListItemNumber(tokens, innerListOpen, innerListCloseIdx)
230
-
231
- const innerListInfo = listInfoMap?.get(innerListOpen)
232
- const isSimpleMarkerParagraph = (() => {
233
- const precedingCount = innerListOpen - (idx + 1)
234
- if (precedingCount !== 3) return false
235
- const paraOpen = tokens[idx + 1]
236
- const inlineToken = tokens[idx + 2]
237
- const paraClose = tokens[idx + 3]
238
- return paraOpen?.type === 'paragraph_open' &&
239
- inlineToken?.type === 'inline' &&
240
- paraClose?.type === 'paragraph_close'
241
- })()
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
+ }
242
252
 
243
- itemIndices.push({
244
- outerItemOpen: idx,
245
- outerItemClose: itemCloseIdx,
246
- innerListOpen: innerListOpen,
247
- innerListClose: innerListCloseIdx,
248
- innerListType: innerListType,
249
- hasExtraContent: hasExtraContent,
250
- extraContentStart: innerListCloseIdx + 1,
251
- extraContentEnd: itemCloseIdx,
252
- innerListInfo,
253
- literalNumber,
254
- flattenFirstParagraph: isSimpleMarkerParagraph
255
- })
253
+ if (innerListCloseIdx === -1) {
254
+ innerListCloseIdx = getListClose(innerListOpen)
256
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
+ })
257
292
 
258
293
  idx = itemCloseIdx + 1
259
294
  } else {
@@ -262,9 +297,11 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
262
297
  }
263
298
 
264
299
  // Check if outer ul is convertible (has markers)
265
- const outerListInfo = listInfoMap ? listInfoMap.get(i) : null
300
+ const outerListToken = tokens[i]
301
+ const outerShouldConvert = !!outerListToken?._shouldConvert
302
+ const outerMarkerInfo = outerListToken?._markerInfo || null
266
303
 
267
- if (outerListInfo?.shouldConvert) {
304
+ if (outerShouldConvert) {
268
305
  for (const flattenedItem of itemIndices) {
269
306
  const closeIdx = flattenedItem.innerListOpen - 1
270
307
  const inlineIdx = closeIdx - 1
@@ -289,10 +326,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
289
326
  // 1. Every list_item has a nested list (the classic `- 1.` pattern)
290
327
  // 2. That nested list appears as the very first child (no preceding paragraph text)
291
328
  // This prevents flattening normal `* Parent` lists that intentionally nest an ordered list.
292
- const allItemsHaveInnerList = itemIndices.length > 0 && itemIndices.length === totalItems
293
- const innerListIsFirstChild = allItemsHaveInnerList &&
294
- itemIndices.every(item => item.innerListOpen === item.outerItemOpen + 1)
295
- const shouldSimplify = allItemsHaveInnerList && innerListIsFirstChild
329
+ const shouldSimplify = allItemsHaveDirectInnerList && itemIndices.length > 0
296
330
 
297
331
  if (shouldSimplify && itemIndices.length > 0) {
298
332
  const allSameType = itemIndices.every(item => item.innerListType === itemIndices[0].innerListType)
@@ -309,8 +343,8 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
309
343
  const allMarkers = []
310
344
 
311
345
  // Use outer listInfo marker info if available
312
- if (outerListInfo && outerListInfo.markerInfo && outerListInfo.markerInfo.markers) {
313
- allMarkers.push(...outerListInfo.markerInfo.markers)
346
+ if (outerMarkerInfo && outerMarkerInfo.markers) {
347
+ allMarkers.push(...outerMarkerInfo.markers)
314
348
  }
315
349
 
316
350
  for (const item of itemIndices) {
@@ -325,7 +359,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
325
359
  allMarkers.push(...innerListToken._markerInfo.markers)
326
360
  }
327
361
  // If originally ordered_list, get marker info from start attribute and markup
328
- else if (!outerListInfo || !outerListInfo.markerInfo) {
362
+ else if (!outerMarkerInfo) {
329
363
  // Only get from inner if outer has no marker info
330
364
  // Get start attribute and markup
331
365
  const startAttr = innerListToken.attrs?.find(attr => attr[0] === 'start')
@@ -352,13 +386,8 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
352
386
  }
353
387
  }
354
388
 
355
- // Build new token array
356
- const newTokens = []
357
-
358
- // Tokens before bullet_list_open
359
- for (let j = 0; j < i; j++) {
360
- newTokens.push(tokens[j])
361
- }
389
+ // Build replacement tokens for this list block only.
390
+ const replacementTokens = []
362
391
 
363
392
  // Add first inner list open token and save merged marker info
364
393
  const firstListToken = tokens[itemIndices[0].innerListOpen]
@@ -371,7 +400,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
371
400
 
372
401
  // If outer ul is convertible, convert inner list to ordered_list
373
402
  // (Don't limit to bullet_list as inner list might already be ordered_list)
374
- if (outerListInfo && outerListInfo.shouldConvert) {
403
+ if (outerShouldConvert) {
375
404
  if (firstListToken.type === 'bullet_list_open') {
376
405
  firstListToken.type = 'ordered_list_open'
377
406
  firstListToken.tag = 'ol'
@@ -380,7 +409,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
380
409
  // If already ordered_list, use as is (already converted)
381
410
  }
382
411
 
383
- newTokens.push(firstListToken)
412
+ replacementTokens.push(firstListToken)
384
413
 
385
414
  // Merge and save marker info
386
415
  if (allMarkers.length > 0) {
@@ -420,6 +449,11 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
420
449
  }
421
450
  }
422
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
+ )
423
457
 
424
458
  // ===== Outer UL (Level 0 after conversion) loose/tight determination =====
425
459
  // In flattened pattern (`- 1.` etc), no direct paragraph in outer list_item,
@@ -430,7 +464,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
430
464
  // Single item is always tight
431
465
  if (itemIndices.length === 1) {
432
466
  outerUlIsLoose = false
433
- } else {
467
+ } else if (hasTokenMaps) {
434
468
  // For multiple items, check blank lines with map info
435
469
  for (let itemIdx = 0; itemIdx < itemIndices.length - 1; itemIdx++) {
436
470
  const currentItem = itemIndices[itemIdx]
@@ -458,6 +492,9 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
458
492
  }
459
493
  }
460
494
  }
495
+ } else {
496
+ // Mapless fallback: reuse Phase 1 loose/tight decision for the outer list.
497
+ outerUlIsLoose = !!outerListToken?._isLoose
461
498
  }
462
499
 
463
500
  // ===== Nested list overall loose/tight determination =====
@@ -472,46 +509,38 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
472
509
  // In flattened pattern (`- 1.` etc), each outer list_item has one inner ol
473
510
  // Check if there are blank lines between list_items in inner ol
474
511
  // (Blank lines between outer list_items already checked by outerUlIsLoose)
475
- for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
476
- const item = itemIndices[itemIdx]
477
-
478
- // Collect list_items in inner list
479
- const innerListItems = []
480
- for (let j = item.innerListOpen + 1; j < item.innerListClose; j++) {
481
- if (tokens[j].type === 'list_item_open' &&
482
- tokens[j].level === tokens[item.innerListOpen].level + 1) {
483
- const itemOpen = j
484
- const itemClose = getListItemClose(j)
485
- innerListItems.push({ open: itemOpen, close: itemClose })
486
- }
487
- }
488
-
489
- // Check blank lines between list_items in inner list
490
- if (innerListItems.length > 1) {
491
- for (let k = 0; k < innerListItems.length - 1; k++) {
492
- const currentItem = innerListItems[k]
493
- const nextItem = innerListItems[k + 1]
494
-
495
- // Get currentItem end line
496
- let currentEndLine = null
497
- for (let m = currentItem.close - 1; m > currentItem.open; m--) {
498
- if (tokens[m].map && tokens[m].map[1]) {
499
- currentEndLine = tokens[m].map[1]
500
- break
512
+ // When map is missing, skip map-based blank-line checks and fall back to paragraph.hidden.
513
+ if (hasTokenMaps) {
514
+ for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
515
+ const listItemRanges = listItemRangesByItem[itemIdx]
516
+
517
+ // Check blank lines between list_items in inner list
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]
522
+
523
+ // Get currentItem end line
524
+ let currentEndLine = null
525
+ for (let m = currentItem.close - 1; m > currentItem.open; m--) {
526
+ if (tokens[m].map && tokens[m].map[1]) {
527
+ currentEndLine = tokens[m].map[1]
528
+ break
529
+ }
501
530
  }
502
- }
503
-
504
- const nextMap = tokens[nextItem.open].map
505
-
506
- if (currentEndLine !== null && nextMap) {
507
- const lineGap = nextMap[0] - currentEndLine
508
- if (lineGap > 0) {
509
- innerListIsLooseDueToBlankLines = true
510
- break
531
+
532
+ const nextMap = tokens[nextItem.open].map
533
+
534
+ if (currentEndLine !== null && nextMap) {
535
+ const lineGap = nextMap[0] - currentEndLine
536
+ if (lineGap > 0) {
537
+ innerListIsLooseDueToBlankLines = true
538
+ break
539
+ }
511
540
  }
512
541
  }
542
+ if (innerListIsLooseDueToBlankLines) break
513
543
  }
514
- if (innerListIsLooseDueToBlankLines) break
515
544
  }
516
545
  }
517
546
 
@@ -519,21 +548,11 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
519
548
  // If inner list has paragraph with hidden=false, it's loose
520
549
  if (!innerListIsLooseDueToBlankLines) {
521
550
  for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
522
- const item = itemIndices[itemIdx]
523
-
524
- let innerListItemOpen = -1
525
- let innerListItemClose = -1
526
-
527
- for (let j = item.innerListOpen + 1; j < item.innerListClose; j++) {
528
- if (tokens[j].type === 'list_item_open' &&
529
- tokens[j].level === tokens[item.innerListOpen].level + 1) {
530
- innerListItemOpen = j
531
- innerListItemClose = getListItemClose(j)
532
- break
533
- }
534
- }
535
-
536
- 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
537
556
  // Check hidden of first paragraph in inner list item
538
557
  let nestedListDepth = 0
539
558
  for (let j = innerListItemOpen + 1; j < innerListItemClose; j++) {
@@ -561,24 +580,20 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
561
580
  // This must be executed AFTER innerListIsLooseDueToBlankLines determination
562
581
  if (outerUlIsLoose && !(itemIndices.length === 1)) {
563
582
  for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
564
- const item = itemIndices[itemIdx]
565
- // Find list_items in inner list
566
- for (let j = item.innerListOpen + 1; j < item.innerListClose; j++) {
567
- if (tokens[j].type === 'list_item_open' &&
568
- tokens[j].level === tokens[item.innerListOpen].level + 1) {
569
- // Find first paragraph in list_item
570
- const listItemOpen = j
571
- const listItemClose = getListItemClose(j)
572
- for (let k = listItemOpen + 1; k < listItemClose; k++) {
573
- if (tokens[k].type === 'paragraph_open' &&
574
- tokens[k].level === tokens[listItemOpen].level + 1) {
575
- if (!tokens[k]._literalTight) {
576
- tokens[k].hidden = false
577
- }
578
- break // Only first paragraph
579
- }
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
580
595
  }
581
- break // Only first list_item of inner list
596
+ break // Only first paragraph
582
597
  }
583
598
  }
584
599
  }
@@ -590,18 +605,10 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
590
605
  // ===== Merge each inner list's contents and place extra content appropriately =====
591
606
  for (let itemIdx = 0; itemIdx < itemIndices.length; itemIdx++) {
592
607
  const item = itemIndices[itemIdx]
593
-
594
- // Count list_items in this inner list (ordered_list)
595
- let innerListItemCount = 0
596
- for (let j = item.innerListOpen + 1; j < item.innerListClose; j++) {
597
- if (tokens[j].type === 'list_item_open' &&
598
- tokens[j].level === tokens[item.innerListOpen].level + 1) {
599
- innerListItemCount++
600
- }
601
- }
602
-
608
+
609
+ const listItemRanges = listItemRangesByItem[itemIdx]
610
+ const innerListItemCount = listItemRanges.length
603
611
  const innerListToken = tokens[item.innerListOpen]
604
- const listItemRanges = collectListItemRanges(tokens, item.innerListOpen, item.innerListClose, listItemCloseByOpen)
605
612
  if (listItemRanges.length === 0) {
606
613
  continue
607
614
  }
@@ -613,7 +620,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
613
620
  if (innerListItemOpen !== -1 && innerListItemClose !== -1) {
614
621
  // Add list_item_open (use original token to preserve info)
615
622
  // Note: value attribute is added in Phase3 (not during simplification)
616
- newTokens.push(tokens[innerListItemOpen])
623
+ replacementTokens.push(tokens[innerListItemOpen])
617
624
 
618
625
  // This item's loose/tight determination
619
626
  // If entire list is loose, this item is also loose
@@ -639,9 +646,8 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
639
646
  }
640
647
 
641
648
  // Whether this item's first paragraph is loose
642
- // is obtained from Phase1-analyzed listInfo.items[itemIdx]
643
- const listItem = outerListInfo?.items?.[itemIdx]
644
- const firstParagraphIsLoose = listItem?.firstParagraphIsLoose || false
649
+ // Use metadata recorded on the outer list_item token.
650
+ const firstParagraphIsLoose = !!tokens[item.outerItemOpen]?._firstParagraphIsLoose
645
651
 
646
652
  // Make loose when parent structure is loose or this item has extra block elements
647
653
  // - However, consider cases with multiple paragraphs in item (firstParagraphIsLoose) or
@@ -663,7 +669,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
663
669
  // - firstParagraphIsLoose: blank line after parent item's first paragraph (same marker type only)
664
670
  // Note: hasExtraContent makes parent item loose but doesn't affect child lists
665
671
  // Note: outerUlIsLoose excluded (Test 25: handle case where parent is loose but child is tight)
666
- const shouldPropagateLooseToChildren = innerListIsLooseDueToBlankLines || token._parentIsLoose || firstParagraphIsLoose || false
672
+ const shouldPropagateLooseToChildren = innerListIsLooseDueToBlankLines || token._parentIsLoose || firstParagraphIsLoose
667
673
 
668
674
  // Copy tokens in inner list item (exclude nested list paragraphs)
669
675
  let nestedListDepth = 0
@@ -681,13 +687,13 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
681
687
  if (shouldPropagateLooseToChildren) {
682
688
  // Set _parentIsLoose flag (optimize with direct property assignment)
683
689
  tokenToPush._parentIsLoose = true
684
- newTokens.push(tokenToPush)
690
+ replacementTokens.push(tokenToPush)
685
691
  } else {
686
- newTokens.push(tokenToPush)
692
+ replacementTokens.push(tokenToPush)
687
693
  }
688
694
  } else if (tokenToPush.type === 'bullet_list_close' || tokenToPush.type === 'ordered_list_close') {
689
695
  nestedListDepth--
690
- newTokens.push(tokenToPush)
696
+ replacementTokens.push(tokenToPush)
691
697
  } else if (nestedListDepth === 0 && (tokenToPush.type === 'paragraph_open' || tokenToPush.type === 'paragraph_close')) {
692
698
  // Update paragraph_open/close hidden state (only outside nested lists)
693
699
 
@@ -712,7 +718,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
712
718
  } else {
713
719
  // Match paragraph_close hidden state to corresponding paragraph_open
714
720
  // Reference preceding paragraph_open's hidden state
715
- const prevToken = newTokens[newTokens.length - 2] // Skip preceding inline, get paragraph_open before it
721
+ const prevToken = replacementTokens[replacementTokens.length - 2] // Skip preceding inline, get paragraph_open before it
716
722
  if (prevToken && prevToken.type === 'paragraph_open') {
717
723
  tokenToPush.hidden = prevToken.hidden
718
724
  } else {
@@ -721,9 +727,9 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
721
727
  }
722
728
  }
723
729
 
724
- newTokens.push(tokenToPush)
730
+ replacementTokens.push(tokenToPush)
725
731
  } else {
726
- newTokens.push(tokenToPush)
732
+ replacementTokens.push(tokenToPush)
727
733
  }
728
734
  }
729
735
 
@@ -758,7 +764,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
758
764
  }
759
765
  }
760
766
 
761
- newTokens.push(tokenToPush)
767
+ replacementTokens.push(tokenToPush)
762
768
  }
763
769
  }
764
770
 
@@ -775,15 +781,15 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
775
781
  item.innerListClose,
776
782
  levelShift,
777
783
  markerStartIndex,
778
- item.innerListInfo
784
+ item.innerListMarkerInfo
779
785
  )
780
786
  for (const nestedToken of nestedTokens) {
781
- newTokens.push(nestedToken)
787
+ replacementTokens.push(nestedToken)
782
788
  }
783
789
  }
784
790
 
785
791
  // Add list_item_close
786
- newTokens.push(tokens[innerListItemClose])
792
+ replacementTokens.push(tokens[innerListItemClose])
787
793
  }
788
794
  }
789
795
 
@@ -792,7 +798,7 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
792
798
  const lastListCloseToken = tokens[lastItem.innerListClose]
793
799
 
794
800
  // If outer ul is convertible, also convert close token
795
- if (outerListInfo && outerListInfo.shouldConvert) {
801
+ if (outerShouldConvert) {
796
802
  if (lastListCloseToken.type === 'bullet_list_close') {
797
803
  lastListCloseToken.type = 'ordered_list_close'
798
804
  lastListCloseToken.tag = 'ol'
@@ -800,29 +806,20 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
800
806
  // If already ordered_list_close, use as is
801
807
  }
802
808
 
803
- newTokens.push(lastListCloseToken)
809
+ replacementTokens.push(lastListCloseToken)
804
810
 
805
- // Tokens after bullet_list_close
806
- for (let j = listCloseIdx + 1; j < tokens.length; j++) {
807
- newTokens.push(tokens[j])
808
- }
809
-
810
- // Replace token array
811
- tokens.length = 0
812
- tokens.push(...newTokens)
811
+ // Replace only the current outer bullet-list range.
812
+ tokens.splice(i, listCloseIdx - i + 1, ...replacementTokens)
813
813
 
814
814
  // Remove markers from merged list
815
815
  if (firstListToken._markerInfo && firstListToken._markerInfo.markers) {
816
- let listStartIdx = 0
817
- while (listStartIdx < tokens.length && tokens[listStartIdx] !== firstListToken) {
818
- listStartIdx++
819
- }
816
+ const listStartIdx = i
820
817
  if (listStartIdx < tokens.length) {
821
818
  const listEndIdx = findMatchingClose(tokens, listStartIdx,
822
819
  firstListToken.type,
823
- firstListToken.type.replace('_open', '_close'), false)
820
+ firstListToken.type.replace('_open', '_close'))
824
821
  if (listEndIdx !== -1) {
825
- removeMarkersFromContent(tokens, listStartIdx, listEndIdx, firstListToken._markerInfo, null)
822
+ removeMarkersFromContent(tokens, listStartIdx, listEndIdx, firstListToken._markerInfo)
826
823
  }
827
824
  }
828
825
  }
@@ -884,19 +881,6 @@ function simplifyNestedBulletLists(tokens, opt, listInfoMap = null) {
884
881
  }
885
882
  }
886
883
 
887
- function buildListInfoMap(listInfos) {
888
- const map = new Map()
889
- if (!Array.isArray(listInfos)) {
890
- return map
891
- }
892
- for (const info of listInfos) {
893
- if (info && typeof info.startIndex === 'number') {
894
- map.set(info.startIndex, info)
895
- }
896
- }
897
- return map
898
- }
899
-
900
884
  function collectListItemRanges(tokens, listOpenIdx, listCloseIdx, listItemCloseByOpen = null) {
901
885
  const ranges = []
902
886
  if (listOpenIdx === -1 || listCloseIdx === -1 || listCloseIdx <= listOpenIdx) {
@@ -922,13 +906,13 @@ function collectListItemRanges(tokens, listOpenIdx, listCloseIdx, listItemCloseB
922
906
  return ranges
923
907
  }
924
908
 
925
- function buildNestedListTokens(tokens, childRanges, innerListOpenIdx, innerListCloseIdx, levelShift, markerStartIndex, innerListInfo) {
909
+ function buildNestedListTokens(tokens, childRanges, innerListOpenIdx, innerListCloseIdx, levelShift, markerStartIndex, markerInfo) {
926
910
  if (!Array.isArray(childRanges) || childRanges.length === 0) {
927
911
  return []
928
912
  }
929
913
  const nestedTokens = []
930
914
  const nestedOpen = cloneToken(tokens[innerListOpenIdx], { levelShift })
931
- const markerInfoSlice = createMarkerInfoSlice(innerListInfo?.markerInfo, markerStartIndex)
915
+ const markerInfoSlice = createMarkerInfoSlice(markerInfo, markerStartIndex)
932
916
  if (markerInfoSlice) {
933
917
  nestedOpen._markerInfo = markerInfoSlice
934
918
  const firstMarker = markerInfoSlice.markers?.[0]
@@ -940,7 +924,7 @@ function buildNestedListTokens(tokens, childRanges, innerListOpenIdx, innerListC
940
924
  nestedTokens.push(nestedOpen)
941
925
  for (const range of childRanges) {
942
926
  for (let i = range.open; i <= range.close; i++) {
943
- nestedTokens.push(cloneToken(tokens[i], { levelShift, deep: true }))
927
+ nestedTokens.push(cloneToken(tokens[i], { levelShift }))
944
928
  }
945
929
  }
946
930
  const nestedClose = cloneToken(tokens[innerListCloseIdx], { levelShift })
@@ -949,7 +933,7 @@ function buildNestedListTokens(tokens, childRanges, innerListOpenIdx, innerListC
949
933
  }
950
934
 
951
935
  function cloneToken(token, options = {}) {
952
- const { levelShift = 0, deep = false } = options
936
+ const { levelShift = 0 } = options
953
937
  const TokenClass = token.constructor
954
938
  const cloned = new TokenClass(token.type, token.tag, token.nesting)
955
939
  cloned.attrs = token.attrs ? token.attrs.map(([name, value]) => [name, value]) : null
@@ -963,7 +947,7 @@ function cloneToken(token, options = {}) {
963
947
  cloned.hidden = token.hidden
964
948
  if (Array.isArray(token.children)) {
965
949
  cloned.children = token.children.length > 0
966
- ? token.children.map(child => cloneToken(child, { levelShift, deep: true }))
950
+ ? token.children.map(child => cloneToken(child, { levelShift }))
967
951
  : []
968
952
  } else {
969
953
  cloned.children = token.children ?? null