@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.4.0 → 0.4.2
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 -7
- package/index.js +62 -27
- package/package.json +3 -2
- package/src/list-helpers.js +5 -3
- package/src/phase0-description-list.js +37 -15
- package/src/phase1-analyze.js +44 -131
- package/src/phase2-convert.js +53 -69
- package/src/phase3-attributes.js +21 -29
- package/src/phase5-spans.js +13 -14
- package/src/preprocess-literal-lists.js +25 -36
- package/src/types-utility.js +95 -236
package/src/phase1-analyze.js
CHANGED
|
@@ -3,109 +3,27 @@
|
|
|
3
3
|
import { detectMarkerType, detectMarkerTypeWithContext, detectSequencePattern } from './types-utility.js'
|
|
4
4
|
import { buildListCloseIndexMap, findMatchingClose } from './list-helpers.js'
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Pre-compute DL scope (identify all DL ranges in O(n))
|
|
8
|
-
* @param {Array} tokens - Token array
|
|
9
|
-
* @returns {Array<[number, number]>} DL range pairs [[start, end], ...]
|
|
10
|
-
*/
|
|
11
|
-
function buildDLStateMap(tokens) {
|
|
12
|
-
const state = new Array(tokens.length).fill(false)
|
|
13
|
-
let dlDepth = 0
|
|
14
|
-
let htmlDdDepth = 0
|
|
15
|
-
|
|
16
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
17
|
-
const token = tokens[i]
|
|
18
|
-
|
|
19
|
-
if (dlDepth > 0 || htmlDdDepth > 0) {
|
|
20
|
-
state[i] = true
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (token.type === 'dl_open') {
|
|
24
|
-
state[i] = true
|
|
25
|
-
dlDepth++
|
|
26
|
-
continue
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (token.type === 'dl_close') {
|
|
30
|
-
state[i] = true
|
|
31
|
-
if (dlDepth > 0) {
|
|
32
|
-
dlDepth--
|
|
33
|
-
}
|
|
34
|
-
continue
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (token.type === 'html_block') {
|
|
38
|
-
if (token.content === '<dd>\n') {
|
|
39
|
-
state[i] = true
|
|
40
|
-
htmlDdDepth++
|
|
41
|
-
continue
|
|
42
|
-
}
|
|
43
|
-
if (token.content === '</dd>\n') {
|
|
44
|
-
state[i] = true
|
|
45
|
-
if (htmlDdDepth > 0) {
|
|
46
|
-
htmlDdDepth--
|
|
47
|
-
}
|
|
48
|
-
continue
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return state
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Check if token at specified index is inside DL
|
|
58
|
-
* @param {number} index - Index to check
|
|
59
|
-
* @param {Array<[number, number]>} dlRanges - DL range pairs
|
|
60
|
-
* @returns {boolean} True if inside DL
|
|
61
|
-
*/
|
|
62
|
-
function isInsideDL(index, dlState) {
|
|
63
|
-
if (!dlState || index < 0 || index >= dlState.length) {
|
|
64
|
-
return false
|
|
65
|
-
}
|
|
66
|
-
return dlState[index] === true
|
|
67
|
-
}
|
|
68
|
-
|
|
69
6
|
/**
|
|
70
7
|
* Collect list information from token array
|
|
71
8
|
* @param {Array} tokens - markdown-it token array
|
|
72
|
-
* @param {Object} opt - Plugin options
|
|
73
9
|
* @returns {Array} Flat array of list information (including nested lists)
|
|
74
10
|
*/
|
|
75
|
-
export function analyzeListStructure(tokens
|
|
11
|
+
export function analyzeListStructure(tokens) {
|
|
76
12
|
const listInfos = []
|
|
77
13
|
const closeMap = buildListCloseIndexMap(tokens)
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
(tokens[i].type === 'html_block' && tokens[i].content === '<dd>\n')) {
|
|
84
|
-
hasDL = true
|
|
85
|
-
break
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Pre-compute DL scope flags (only when DL exists)
|
|
90
|
-
const dlScope = hasDL ? buildDLStateMap(tokens) : null
|
|
91
|
-
|
|
92
|
-
// Process only top-level lists (nested lists collected recursively)
|
|
93
|
-
// Also process lists inside DL
|
|
14
|
+
|
|
15
|
+
// Process every list root in the token stream, including lists nested in
|
|
16
|
+
// containers such as blockquotes or description-list descriptions. Nested
|
|
17
|
+
// lists inside an already-analyzed list are collected recursively, then the
|
|
18
|
+
// scan jumps to that list's close token to avoid duplicate processing.
|
|
94
19
|
for (let i = 0; i < tokens.length; i++) {
|
|
95
20
|
const token = tokens[i]
|
|
96
21
|
if (!token) {
|
|
97
22
|
continue
|
|
98
23
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
(token.level === 0 || token.level === undefined)
|
|
103
|
-
const isListInDD = (token.type === 'bullet_list_open' || token.type === 'ordered_list_open') &&
|
|
104
|
-
token.level > 0 &&
|
|
105
|
-
isInsideDL(i, dlScope)
|
|
106
|
-
|
|
107
|
-
if (isTopLevelList || isListInDD) {
|
|
108
|
-
const listInfo = analyzeList(tokens, i, opt, closeMap)
|
|
24
|
+
|
|
25
|
+
if (token.type === 'bullet_list_open' || token.type === 'ordered_list_open') {
|
|
26
|
+
const listInfo = analyzeList(tokens, i, closeMap)
|
|
109
27
|
if (listInfo) {
|
|
110
28
|
listInfos.push(listInfo)
|
|
111
29
|
|
|
@@ -139,7 +57,7 @@ function collectNestedLists(listInfo, listInfos) {
|
|
|
139
57
|
/**
|
|
140
58
|
* Analyze detailed information of a single list
|
|
141
59
|
*/
|
|
142
|
-
function analyzeList(tokens, startIndex,
|
|
60
|
+
function analyzeList(tokens, startIndex, closeMap) {
|
|
143
61
|
const listToken = tokens[startIndex]
|
|
144
62
|
const endIndex = findListEnd(tokens, startIndex, closeMap)
|
|
145
63
|
|
|
@@ -149,7 +67,7 @@ function analyzeList(tokens, startIndex, opt, closeMap) {
|
|
|
149
67
|
|
|
150
68
|
const level = listToken.level || 0
|
|
151
69
|
const originalType = listToken.type
|
|
152
|
-
const items = analyzeListItems(tokens, startIndex, endIndex,
|
|
70
|
+
const items = analyzeListItems(tokens, startIndex, endIndex, closeMap)
|
|
153
71
|
const isLoose = detectLooseList(tokens, startIndex, endIndex, items)
|
|
154
72
|
if (!isLoose) {
|
|
155
73
|
hideFirstParagraphsForTightList(tokens, items, level)
|
|
@@ -159,7 +77,7 @@ function analyzeList(tokens, startIndex, opt, closeMap) {
|
|
|
159
77
|
listToken._isLoose = isLoose
|
|
160
78
|
|
|
161
79
|
// Extract marker info (for both bullet_list and ordered_list)
|
|
162
|
-
const markerInfo = extractMarkerInfo(tokens, startIndex, endIndex
|
|
80
|
+
const markerInfo = extractMarkerInfo(tokens, startIndex, endIndex)
|
|
163
81
|
|
|
164
82
|
// Recheck marker consistency against item count
|
|
165
83
|
if (markerInfo && markerInfo.count < items.length) {
|
|
@@ -170,7 +88,7 @@ function analyzeList(tokens, startIndex, opt, closeMap) {
|
|
|
170
88
|
// Conversion decision (only consider for bullet_list)
|
|
171
89
|
// Convert even loose lists if markers are consistent
|
|
172
90
|
const shouldConvert = originalType === 'bullet_list_open' &&
|
|
173
|
-
shouldConvertToOrdered(originalType, markerInfo
|
|
91
|
+
shouldConvertToOrdered(originalType, markerInfo)
|
|
174
92
|
|
|
175
93
|
if (markerInfo) {
|
|
176
94
|
listToken._markerInfo = markerInfo
|
|
@@ -194,7 +112,7 @@ function analyzeList(tokens, startIndex, opt, closeMap) {
|
|
|
194
112
|
/**
|
|
195
113
|
* Analyze list items
|
|
196
114
|
*/
|
|
197
|
-
function analyzeListItems(tokens, startIndex, endIndex,
|
|
115
|
+
function analyzeListItems(tokens, startIndex, endIndex, closeMap) {
|
|
198
116
|
const items = []
|
|
199
117
|
let i = startIndex + 1
|
|
200
118
|
|
|
@@ -203,7 +121,7 @@ function analyzeListItems(tokens, startIndex, endIndex, opt, closeMap) {
|
|
|
203
121
|
|
|
204
122
|
if (token.type === 'list_item_open') {
|
|
205
123
|
const itemEndIndex = findListItemEnd(tokens, i, closeMap)
|
|
206
|
-
const item = analyzeListItem(tokens, i, itemEndIndex,
|
|
124
|
+
const item = analyzeListItem(tokens, i, itemEndIndex, closeMap)
|
|
207
125
|
items.push(item)
|
|
208
126
|
i = itemEndIndex + 1
|
|
209
127
|
} else {
|
|
@@ -217,11 +135,10 @@ function analyzeListItems(tokens, startIndex, endIndex, opt, closeMap) {
|
|
|
217
135
|
/**
|
|
218
136
|
* Analyze a single list item
|
|
219
137
|
*/
|
|
220
|
-
function analyzeListItem(tokens, startIndex, endIndex,
|
|
221
|
-
let content = ''
|
|
138
|
+
function analyzeListItem(tokens, startIndex, endIndex, closeMap) {
|
|
222
139
|
let markerInfo = null
|
|
223
140
|
let hasNestedList = false
|
|
224
|
-
|
|
141
|
+
const nestedLists = []
|
|
225
142
|
let firstParagraphIsLoose = false
|
|
226
143
|
let lastInlineContent = ''
|
|
227
144
|
|
|
@@ -259,7 +176,7 @@ function analyzeListItem(tokens, startIndex, endIndex, opt, closeMap) {
|
|
|
259
176
|
}
|
|
260
177
|
checkedFirstParagraphLoose = true
|
|
261
178
|
hasNestedList = true
|
|
262
|
-
const nestedListInfo = analyzeList(tokens, i,
|
|
179
|
+
const nestedListInfo = analyzeList(tokens, i, closeMap)
|
|
263
180
|
nestedLists.push(nestedListInfo)
|
|
264
181
|
i = nestedListInfo.endIndex
|
|
265
182
|
}
|
|
@@ -268,7 +185,6 @@ function analyzeListItem(tokens, startIndex, endIndex, opt, closeMap) {
|
|
|
268
185
|
if (firstParagraphIsLoose && hasNestedList && lastInlineContent) {
|
|
269
186
|
markerInfo = detectMarkerType(lastInlineContent)
|
|
270
187
|
}
|
|
271
|
-
content = lastInlineContent
|
|
272
188
|
|
|
273
189
|
if (tokens[startIndex]) {
|
|
274
190
|
tokens[startIndex]._firstParagraphIsLoose = firstParagraphIsLoose
|
|
@@ -277,7 +193,7 @@ function analyzeListItem(tokens, startIndex, endIndex, opt, closeMap) {
|
|
|
277
193
|
return {
|
|
278
194
|
startIndex,
|
|
279
195
|
endIndex,
|
|
280
|
-
content,
|
|
196
|
+
content: lastInlineContent,
|
|
281
197
|
markerInfo,
|
|
282
198
|
hasNestedList,
|
|
283
199
|
nestedLists,
|
|
@@ -288,7 +204,7 @@ function analyzeListItem(tokens, startIndex, endIndex, opt, closeMap) {
|
|
|
288
204
|
/**
|
|
289
205
|
* Extract marker information
|
|
290
206
|
*/
|
|
291
|
-
function extractMarkerInfo(tokens, startIndex, endIndex
|
|
207
|
+
function extractMarkerInfo(tokens, startIndex, endIndex) {
|
|
292
208
|
const listToken = tokens[startIndex]
|
|
293
209
|
const markers = []
|
|
294
210
|
const literalMarkerInfo = listToken._literalMarkerInfo || null
|
|
@@ -349,35 +265,32 @@ function extractMarkerInfo(tokens, startIndex, endIndex, opt) {
|
|
|
349
265
|
// Detect markers using full context
|
|
350
266
|
let sequentialNumber = 1 // Sequential number counter
|
|
351
267
|
for (const token of inlineTokens) {
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
268
|
+
const markerInfo = detectMarkerTypeWithContext(token.content, contextResult)
|
|
269
|
+
if (markerInfo && markerInfo.type) {
|
|
270
|
+
// Use sequential numbers when same marker continues
|
|
271
|
+
// (e.g., "イ. イ. イ." → interpreted as "イ、ロ、ハ")
|
|
272
|
+
const adjustedMarkerInfo = { ...markerInfo }
|
|
273
|
+
adjustedMarkerInfo.originalNumber = markerInfo.number
|
|
274
|
+
|
|
275
|
+
// Assign sequential numbers based on first marker's number
|
|
276
|
+
if (markers.length === 0) {
|
|
277
|
+
// Use first marker as-is
|
|
278
|
+
sequentialNumber = markerInfo.number || 1
|
|
279
|
+
} else {
|
|
280
|
+
// For 2nd and later, use sequential number if same as previous marker
|
|
281
|
+
const prevMarker = markers[markers.length - 1]
|
|
282
|
+
if (markerInfo.marker === prevMarker.marker &&
|
|
283
|
+
markerInfo.type === prevMarker.type) {
|
|
284
|
+
// Assign sequential number when same marker continues
|
|
285
|
+
sequentialNumber++
|
|
286
|
+
adjustedMarkerInfo.number = sequentialNumber
|
|
365
287
|
} else {
|
|
366
|
-
// For
|
|
367
|
-
|
|
368
|
-
if (markerInfo.marker === prevMarker.marker &&
|
|
369
|
-
markerInfo.type === prevMarker.type) {
|
|
370
|
-
// Assign sequential number when same marker continues
|
|
371
|
-
sequentialNumber++
|
|
372
|
-
adjustedMarkerInfo.number = sequentialNumber
|
|
373
|
-
} else {
|
|
374
|
-
// For different marker, use detected number
|
|
375
|
-
sequentialNumber = markerInfo.number || sequentialNumber + 1
|
|
376
|
-
}
|
|
288
|
+
// For different marker, use detected number
|
|
289
|
+
sequentialNumber = markerInfo.number || sequentialNumber + 1
|
|
377
290
|
}
|
|
378
|
-
|
|
379
|
-
markers.push(adjustedMarkerInfo)
|
|
380
291
|
}
|
|
292
|
+
|
|
293
|
+
markers.push(adjustedMarkerInfo)
|
|
381
294
|
}
|
|
382
295
|
}
|
|
383
296
|
}
|
|
@@ -411,7 +324,7 @@ function extractMarkerInfo(tokens, startIndex, endIndex, opt) {
|
|
|
411
324
|
/**
|
|
412
325
|
* Determine if should convert to ordered list
|
|
413
326
|
*/
|
|
414
|
-
function shouldConvertToOrdered(originalType, markerInfo
|
|
327
|
+
function shouldConvertToOrdered(originalType, markerInfo) {
|
|
415
328
|
// No conversion needed if already ordered_list
|
|
416
329
|
if (originalType === 'ordered_list_open') {
|
|
417
330
|
return false
|
package/src/phase2-convert.js
CHANGED
|
@@ -12,7 +12,11 @@ import { buildListCloseIndexMap, findMatchingClose } from './list-helpers.js'
|
|
|
12
12
|
export function convertLists(tokens, listInfos, opt) {
|
|
13
13
|
// Convert bullet_list to ordered_list
|
|
14
14
|
// listInfos already collected in depth-first order in Phase 1, no sorting needed
|
|
15
|
+
let hasBulletLists = false
|
|
15
16
|
for (const listInfo of listInfos) {
|
|
17
|
+
if (!hasBulletLists && listInfo.originalType === 'bullet_list_open') {
|
|
18
|
+
hasBulletLists = true
|
|
19
|
+
}
|
|
16
20
|
if (listInfo.shouldConvert) {
|
|
17
21
|
convertBulletToOrdered(tokens, listInfo)
|
|
18
22
|
}
|
|
@@ -20,11 +24,8 @@ export function convertLists(tokens, listInfos, opt) {
|
|
|
20
24
|
|
|
21
25
|
// In default mode (unremoveUlNest=false), simplify ul>li>ol structure
|
|
22
26
|
// Runs after conversion, so already-converted ordered_lists are also processed
|
|
23
|
-
if (!opt.unremoveUlNest) {
|
|
24
|
-
|
|
25
|
-
if (hasBulletLists && hasSimplifiableBulletStructure(tokens)) {
|
|
26
|
-
simplifyNestedBulletLists(tokens)
|
|
27
|
-
}
|
|
27
|
+
if (!opt.unremoveUlNest && hasBulletLists && hasSimplifiableBulletStructure(tokens)) {
|
|
28
|
+
simplifyNestedBulletLists(tokens)
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -44,6 +45,26 @@ function hasSimplifiableBulletStructure(tokens) {
|
|
|
44
45
|
return false
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
function resolveListClose(tokens, openIdx, listCloseByOpen = null) {
|
|
49
|
+
const mapped = listCloseByOpen ? listCloseByOpen[openIdx] : -1
|
|
50
|
+
if (typeof mapped === 'number' && mapped !== -1) {
|
|
51
|
+
return mapped
|
|
52
|
+
}
|
|
53
|
+
const openType = tokens[openIdx]?.type
|
|
54
|
+
if (!openType) {
|
|
55
|
+
return -1
|
|
56
|
+
}
|
|
57
|
+
return findMatchingClose(tokens, openIdx, openType, openType.replace('_open', '_close'))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveListItemClose(tokens, openIdx, listItemCloseByOpen = null) {
|
|
61
|
+
const mapped = listItemCloseByOpen ? listItemCloseByOpen[openIdx] : -1
|
|
62
|
+
if (typeof mapped === 'number' && mapped !== -1) {
|
|
63
|
+
return mapped
|
|
64
|
+
}
|
|
65
|
+
return findMatchingClose(tokens, openIdx, 'list_item_open', 'list_item_close')
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
/**
|
|
48
69
|
* Convert bullet_list to ordered_list
|
|
49
70
|
*/
|
|
@@ -138,22 +159,17 @@ function removeMarkersFromContent(tokens, startIndex, endIndex, markerInfo) {
|
|
|
138
159
|
if (token.type === 'inline' && token.content && token.level === targetLevel) {
|
|
139
160
|
const marker = markers[markerIndex]
|
|
140
161
|
if (marker && marker.marker && token.content.startsWith(marker.marker)) {
|
|
141
|
-
|
|
142
|
-
newContent = newContent.replace(LEADING_SPACE_REGEX, '')
|
|
143
|
-
token.content = newContent
|
|
162
|
+
token.content = token.content.slice(marker.marker.length).trimStart()
|
|
144
163
|
markerIndex++
|
|
145
164
|
}
|
|
146
165
|
}
|
|
147
166
|
}
|
|
148
167
|
}
|
|
149
168
|
|
|
150
|
-
const LEADING_SPACE_REGEX = /^\s+/
|
|
151
|
-
|
|
152
169
|
/**
|
|
153
|
-
* Simplify nested ul>li>
|
|
170
|
+
* Simplify nested ul>li>ol structures.
|
|
154
171
|
*
|
|
155
|
-
* Pattern
|
|
156
|
-
* Pattern 2: bullet_list_open → list_item_open → ordered_list_open → ... (repeated)
|
|
172
|
+
* Pattern: bullet_list_open → list_item_open → ordered_list_open → ... (repeated)
|
|
157
173
|
*
|
|
158
174
|
* When the middle list_item is empty (contains only the inner list),
|
|
159
175
|
* remove the outer ul and the intermediate li.
|
|
@@ -168,24 +184,6 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
168
184
|
while (modified) {
|
|
169
185
|
modified = false
|
|
170
186
|
const { listCloseByOpen, listItemCloseByOpen } = buildListCloseIndexMap(tokens)
|
|
171
|
-
const getListClose = (openIdx) => {
|
|
172
|
-
const mapped = listCloseByOpen[openIdx]
|
|
173
|
-
if (typeof mapped === 'number' && mapped !== -1) {
|
|
174
|
-
return mapped
|
|
175
|
-
}
|
|
176
|
-
const openType = tokens[openIdx]?.type
|
|
177
|
-
if (!openType) {
|
|
178
|
-
return -1
|
|
179
|
-
}
|
|
180
|
-
return findMatchingClose(tokens, openIdx, openType, openType.replace('_open', '_close'))
|
|
181
|
-
}
|
|
182
|
-
const getListItemClose = (openIdx) => {
|
|
183
|
-
const mapped = listItemCloseByOpen[openIdx]
|
|
184
|
-
if (typeof mapped === 'number' && mapped !== -1) {
|
|
185
|
-
return mapped
|
|
186
|
-
}
|
|
187
|
-
return findMatchingClose(tokens, openIdx, 'list_item_open', 'list_item_close')
|
|
188
|
-
}
|
|
189
187
|
|
|
190
188
|
for (let i = 0; i < tokens.length; i++) {
|
|
191
189
|
const token = tokens[i]
|
|
@@ -196,7 +194,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
196
194
|
}
|
|
197
195
|
|
|
198
196
|
// Check if this bullet_list is all ul>li>ol/ul pattern
|
|
199
|
-
const listCloseIdx =
|
|
197
|
+
const listCloseIdx = resolveListClose(tokens, i, listCloseByOpen)
|
|
200
198
|
if (listCloseIdx === -1) continue
|
|
201
199
|
|
|
202
200
|
// Fast reject: flattening requires the first list item to start with ordered_list_open.
|
|
@@ -215,7 +213,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
215
213
|
|
|
216
214
|
while (idx < listCloseIdx) {
|
|
217
215
|
if (tokens[idx].type === 'list_item_open') {
|
|
218
|
-
|
|
216
|
+
const itemCloseIdx = resolveListItemClose(tokens, idx, listItemCloseByOpen)
|
|
219
217
|
if (itemCloseIdx === -1) {
|
|
220
218
|
allItemsHaveDirectInnerList = false
|
|
221
219
|
break
|
|
@@ -229,7 +227,7 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
229
227
|
for (let j = idx + 1; j < itemCloseIdx; j++) {
|
|
230
228
|
if (tokens[j].type === 'bullet_list_open' || tokens[j].type === 'ordered_list_open') {
|
|
231
229
|
const candidateType = tokens[j].type
|
|
232
|
-
|
|
230
|
+
const candidateClose = resolveListClose(tokens, j, listCloseByOpen)
|
|
233
231
|
if (tokens[j]._literalList) {
|
|
234
232
|
if (candidateClose === -1) {
|
|
235
233
|
break
|
|
@@ -250,8 +248,8 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
250
248
|
break
|
|
251
249
|
}
|
|
252
250
|
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
if (innerListCloseIdx === -1) {
|
|
252
|
+
innerListCloseIdx = resolveListClose(tokens, innerListOpen, listCloseByOpen)
|
|
255
253
|
}
|
|
256
254
|
if (innerListCloseIdx === -1) {
|
|
257
255
|
allItemsHaveDirectInnerList = false
|
|
@@ -259,22 +257,16 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
259
257
|
}
|
|
260
258
|
|
|
261
259
|
// 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
260
|
const afterContent = itemCloseIdx - (innerListCloseIdx + 1) // Token count from after ol to list_item_close
|
|
264
|
-
const hasExtraContent =
|
|
261
|
+
const hasExtraContent = afterContent > 0
|
|
265
262
|
const literalNumber = extractFirstListItemNumber(tokens, innerListOpen, innerListCloseIdx)
|
|
266
263
|
|
|
267
264
|
const innerListMarkerInfo = tokens[innerListOpen]?._markerInfo
|
|
268
|
-
const isSimpleMarkerParagraph =
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const paraClose = tokens[idx + 3]
|
|
274
|
-
return paraOpen?.type === 'paragraph_open' &&
|
|
275
|
-
inlineToken?.type === 'inline' &&
|
|
276
|
-
paraClose?.type === 'paragraph_close'
|
|
277
|
-
})()
|
|
265
|
+
const isSimpleMarkerParagraph =
|
|
266
|
+
innerListOpen - (idx + 1) === 3 &&
|
|
267
|
+
tokens[idx + 1]?.type === 'paragraph_open' &&
|
|
268
|
+
tokens[idx + 2]?.type === 'inline' &&
|
|
269
|
+
tokens[idx + 3]?.type === 'paragraph_close'
|
|
278
270
|
|
|
279
271
|
itemIndices.push({
|
|
280
272
|
outerItemOpen: idx,
|
|
@@ -687,10 +679,8 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
687
679
|
if (shouldPropagateLooseToChildren) {
|
|
688
680
|
// Set _parentIsLoose flag (optimize with direct property assignment)
|
|
689
681
|
tokenToPush._parentIsLoose = true
|
|
690
|
-
replacementTokens.push(tokenToPush)
|
|
691
|
-
} else {
|
|
692
|
-
replacementTokens.push(tokenToPush)
|
|
693
682
|
}
|
|
683
|
+
replacementTokens.push(tokenToPush)
|
|
694
684
|
} else if (tokenToPush.type === 'bullet_list_close' || tokenToPush.type === 'ordered_list_close') {
|
|
695
685
|
nestedListDepth--
|
|
696
686
|
replacementTokens.push(tokenToPush)
|
|
@@ -833,37 +823,31 @@ function simplifyNestedBulletLists(tokens) {
|
|
|
833
823
|
// ===== Propagate ordered_list's _parentIsLoose flag to child lists =====
|
|
834
824
|
// Check all ordered_list_open, and if _parentIsLoose flag exists,
|
|
835
825
|
// propagate to child lists
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const openType = tokens[openIdx]?.type
|
|
843
|
-
if (!openType) {
|
|
844
|
-
return -1
|
|
826
|
+
let hasParentLooseList = false
|
|
827
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
828
|
+
const token = tokens[i]
|
|
829
|
+
if ((token.type === 'ordered_list_open' || token.type === 'bullet_list_open') && token._parentIsLoose) {
|
|
830
|
+
hasParentLooseList = true
|
|
831
|
+
break
|
|
845
832
|
}
|
|
846
|
-
return findMatchingClose(tokens, openIdx, openType, openType.replace('_open', '_close'))
|
|
847
833
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (typeof mapped === 'number' && mapped !== -1) {
|
|
851
|
-
return mapped
|
|
852
|
-
}
|
|
853
|
-
return findMatchingClose(tokens, openIdx, 'list_item_open', 'list_item_close')
|
|
834
|
+
if (!hasParentLooseList) {
|
|
835
|
+
return
|
|
854
836
|
}
|
|
837
|
+
|
|
838
|
+
const postCloseMap = buildListCloseIndexMap(tokens)
|
|
855
839
|
for (let i = 0; i < tokens.length; i++) {
|
|
856
840
|
const token = tokens[i]
|
|
857
841
|
|
|
858
842
|
if ((token.type === 'ordered_list_open' || token.type === 'bullet_list_open') && token._parentIsLoose) {
|
|
859
843
|
// Find this list's close token
|
|
860
|
-
const listCloseIdx =
|
|
844
|
+
const listCloseIdx = resolveListClose(tokens, i, postCloseMap.listCloseByOpen)
|
|
861
845
|
if (listCloseIdx === -1) continue
|
|
862
846
|
|
|
863
847
|
// Search list_items in this list and set _parentIsLoose flag to child lists in those list_items
|
|
864
848
|
for (let j = i + 1; j < listCloseIdx; j++) {
|
|
865
849
|
if (tokens[j].type === 'list_item_open' && tokens[j].level === token.level + 1) {
|
|
866
|
-
const itemCloseIdx =
|
|
850
|
+
const itemCloseIdx = resolveListItemClose(tokens, j, postCloseMap.listItemCloseByOpen)
|
|
867
851
|
|
|
868
852
|
// Search child lists in this list_item
|
|
869
853
|
for (let k = j + 1; k < itemCloseIdx; k++) {
|
package/src/phase3-attributes.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { getTypeAttributes } from './types-utility.js'
|
|
5
5
|
import { buildListCloseIndexMap, findMatchingClose } from './list-helpers.js'
|
|
6
6
|
|
|
7
|
+
const WHITESPACE_SUFFIX_REGEX = /^[ \u3000]+$/
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Add attributes to lists
|
|
9
11
|
* @param {Array} tokens - Token array
|
|
@@ -35,6 +37,8 @@ export function addAttributes(tokens, opt) {
|
|
|
35
37
|
if (hasAnyListItemValue) {
|
|
36
38
|
normalizeAndConvertValueAttributes(tokens, listCloseByOpen)
|
|
37
39
|
}
|
|
40
|
+
|
|
41
|
+
return closeMap
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
function hasValueAttr(token) {
|
|
@@ -53,15 +57,14 @@ function hasValueAttr(token) {
|
|
|
53
57
|
* Add attributes to a single list token
|
|
54
58
|
*/
|
|
55
59
|
function addListAttributesForToken(tokens, token, tokenIndex, opt, listCloseByOpen = null) {
|
|
56
|
-
// Initialize attribute array
|
|
57
|
-
if (!token.attrs) {
|
|
58
|
-
token.attrs = []
|
|
59
|
-
}
|
|
60
|
-
|
|
61
60
|
// Get marker info
|
|
62
61
|
const markerInfo = token._markerInfo
|
|
63
62
|
|
|
64
63
|
if (!markerInfo) {
|
|
64
|
+
if (!token.attrs) {
|
|
65
|
+
token.attrs = []
|
|
66
|
+
}
|
|
67
|
+
|
|
65
68
|
// Default attributes for lists without markerInfo
|
|
66
69
|
if (opt.useCounterStyle) {
|
|
67
70
|
// Do not add type attribute; add class so user CSS/@counter-style can target
|
|
@@ -83,7 +86,7 @@ function addListAttributesForToken(tokens, token, tokenIndex, opt, listCloseByOp
|
|
|
83
86
|
|
|
84
87
|
// Attributes according to marker type
|
|
85
88
|
// Pass first marker's prefix/suffix info to determine class name
|
|
86
|
-
const firstMarker = markerInfo.markers[0]
|
|
89
|
+
const firstMarker = markerInfo.markers?.[0]
|
|
87
90
|
const typeAttrs = getTypeAttributes(markerInfo.type, firstMarker, opt)
|
|
88
91
|
|
|
89
92
|
// Reset attribute array
|
|
@@ -112,35 +115,23 @@ function addListAttributesForToken(tokens, token, tokenIndex, opt, listCloseByOp
|
|
|
112
115
|
} else {
|
|
113
116
|
startOverride = undefined
|
|
114
117
|
}
|
|
115
|
-
const firstNumber = startOverride ?? (
|
|
118
|
+
const firstNumber = startOverride ?? (firstMarker?.originalNumber ?? firstMarker?.number)
|
|
116
119
|
if (firstNumber !== undefined && firstNumber !== 1) {
|
|
117
120
|
addAttr(token, 'start', String(firstNumber))
|
|
118
|
-
} else if (token.attrs) {
|
|
119
|
-
const startIdx = token.attrs.findIndex(attr => attr[0] === 'start')
|
|
120
|
-
if (startIdx >= 0) {
|
|
121
|
-
token.attrs.splice(startIdx, 1)
|
|
122
|
-
if (token.attrs.length === 0) token.attrs = null
|
|
123
|
-
}
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
// 3. Add class attribute
|
|
127
124
|
if (typeAttrs.class) {
|
|
128
|
-
|
|
129
|
-
const existing = token.attrs.find(a => a[0] === 'class')
|
|
130
|
-
if (existing) {
|
|
131
|
-
existing[1] = (existing[1] + ' ' + typeAttrs.class).trim()
|
|
132
|
-
} else {
|
|
133
|
-
addAttr(token, 'class', typeAttrs.class)
|
|
134
|
-
}
|
|
125
|
+
addAttr(token, 'class', typeAttrs.class)
|
|
135
126
|
}
|
|
136
127
|
// 4. data-marker-prefix/suffix
|
|
137
128
|
if (!opt.omitMarkerMetadata) {
|
|
138
|
-
if (
|
|
139
|
-
addAttr(token, 'data-marker-prefix',
|
|
129
|
+
if (firstMarker?.prefix) {
|
|
130
|
+
addAttr(token, 'data-marker-prefix', firstMarker.prefix)
|
|
140
131
|
}
|
|
141
132
|
// Do not emit data-marker-suffix when suffix is only whitespace (halfwidth or fullwidth)
|
|
142
|
-
const suffix =
|
|
143
|
-
if (suffix &&
|
|
133
|
+
const suffix = firstMarker?.suffix
|
|
134
|
+
if (suffix && !WHITESPACE_SUFFIX_REGEX.test(suffix)) {
|
|
144
135
|
addAttr(token, 'data-marker-suffix', suffix)
|
|
145
136
|
}
|
|
146
137
|
}
|
|
@@ -263,10 +254,11 @@ function normalizeAndConvertValueAttributes(tokens, listCloseByOpen = null) {
|
|
|
263
254
|
* Add or replace an attribute on a token (with duplicate check).
|
|
264
255
|
*/
|
|
265
256
|
function addAttr(token, name, value) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
257
|
+
for (let i = 0; i < token.attrs.length; i++) {
|
|
258
|
+
if (token.attrs[i][0] === name) {
|
|
259
|
+
token.attrs[i] = [name, value]
|
|
260
|
+
return
|
|
261
|
+
}
|
|
271
262
|
}
|
|
263
|
+
token.attrs.push([name, value])
|
|
272
264
|
}
|