@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.
- package/README.md +20 -5
- package/index.js +38 -54
- package/package.json +2 -2
- package/src/phase0-description-list.js +232 -113
- package/src/phase1-analyze.js +39 -8
- package/src/phase2-convert.js +177 -193
- package/src/phase3-attributes.js +24 -47
- package/src/phase5-spans.js +73 -54
- package/src/preprocess-literal-lists.js +295 -362
- package/src/types-utility.js +0 -15
- package/src/phase6-attrs-migration.js +0 -184
package/src/phase2-convert.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
244
|
-
|
|
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
|
|
300
|
+
const outerListToken = tokens[i]
|
|
301
|
+
const outerShouldConvert = !!outerListToken?._shouldConvert
|
|
302
|
+
const outerMarkerInfo = outerListToken?._markerInfo || null
|
|
266
303
|
|
|
267
|
-
if (
|
|
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
|
|
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 (
|
|
313
|
-
allMarkers.push(...
|
|
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 (!
|
|
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
|
|
356
|
-
const
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
|
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
|
-
|
|
595
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
643
|
-
const
|
|
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
|
|
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
|
-
|
|
690
|
+
replacementTokens.push(tokenToPush)
|
|
685
691
|
} else {
|
|
686
|
-
|
|
692
|
+
replacementTokens.push(tokenToPush)
|
|
687
693
|
}
|
|
688
694
|
} else if (tokenToPush.type === 'bullet_list_close' || tokenToPush.type === 'ordered_list_close') {
|
|
689
695
|
nestedListDepth--
|
|
690
|
-
|
|
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 =
|
|
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
|
-
|
|
730
|
+
replacementTokens.push(tokenToPush)
|
|
725
731
|
} else {
|
|
726
|
-
|
|
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
|
-
|
|
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.
|
|
784
|
+
item.innerListMarkerInfo
|
|
779
785
|
)
|
|
780
786
|
for (const nestedToken of nestedTokens) {
|
|
781
|
-
|
|
787
|
+
replacementTokens.push(nestedToken)
|
|
782
788
|
}
|
|
783
789
|
}
|
|
784
790
|
|
|
785
791
|
// Add list_item_close
|
|
786
|
-
|
|
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 (
|
|
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
|
-
|
|
809
|
+
replacementTokens.push(lastListCloseToken)
|
|
804
810
|
|
|
805
|
-
//
|
|
806
|
-
|
|
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
|
-
|
|
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')
|
|
820
|
+
firstListToken.type.replace('_open', '_close'))
|
|
824
821
|
if (listEndIdx !== -1) {
|
|
825
|
-
removeMarkersFromContent(tokens, listStartIdx, listEndIdx, firstListToken._markerInfo
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
950
|
+
? token.children.map(child => cloneToken(child, { levelShift }))
|
|
967
951
|
: []
|
|
968
952
|
} else {
|
|
969
953
|
cloned.children = token.children ?? null
|