@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.2.1 → 0.2.3
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/package.json +2 -2
- package/src/list-helpers.js +48 -0
- package/src/phase0-description-list.js +104 -90
- package/src/phase1-analyze.js +50 -52
- package/src/phase2-convert.js +79 -50
- package/src/phase3-attributes.js +18 -10
- package/src/phase4-html-blocks.js +47 -44
- package/src/phase5-spans.js +13 -7
- package/src/phase6-attrs-migration.js +29 -18
- package/src/preprocess-literal-lists.js +116 -73
- package/src/types-utility.js +43 -41
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-it-numbering-ul-regarded-as-ol",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "This markdown-it plugin regard ul element with numbering lists as ol element.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/peaceroad/p7d-markdown-it-numbering-ul-regarded-as-ol#readme",
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@peaceroad/markdown-it-strong-ja": "^0.5.
|
|
30
|
+
"@peaceroad/markdown-it-strong-ja": "^0.5.5",
|
|
31
31
|
"markdown-it": "^14.1.0",
|
|
32
32
|
"markdown-it-attrs": "^4.3.1",
|
|
33
33
|
"markdown-it-deflist": "^3.0.0"
|
package/src/list-helpers.js
CHANGED
|
@@ -45,3 +45,51 @@ export function findListEnd(tokens, startIndex) {
|
|
|
45
45
|
export function findListItemEnd(tokens, startIndex) {
|
|
46
46
|
return findMatchingClose(tokens, startIndex, 'list_item_open', 'list_item_close')
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build close-index maps for list and list_item tokens.
|
|
51
|
+
* @param {Array} tokens - Token array
|
|
52
|
+
* @returns {{ listCloseByOpen: number[], listItemCloseByOpen: number[] }}
|
|
53
|
+
*/
|
|
54
|
+
export function buildListCloseIndexMap(tokens) {
|
|
55
|
+
const listCloseByOpen = new Array(tokens.length).fill(-1)
|
|
56
|
+
const listItemCloseByOpen = new Array(tokens.length).fill(-1)
|
|
57
|
+
const bulletStack = []
|
|
58
|
+
const orderedStack = []
|
|
59
|
+
const listItemStack = []
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
62
|
+
const type = tokens[i]?.type
|
|
63
|
+
if (type === 'bullet_list_open') {
|
|
64
|
+
bulletStack.push(i)
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
if (type === 'bullet_list_close') {
|
|
68
|
+
if (bulletStack.length > 0) {
|
|
69
|
+
listCloseByOpen[bulletStack.pop()] = i
|
|
70
|
+
}
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
if (type === 'ordered_list_open') {
|
|
74
|
+
orderedStack.push(i)
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
if (type === 'ordered_list_close') {
|
|
78
|
+
if (orderedStack.length > 0) {
|
|
79
|
+
listCloseByOpen[orderedStack.pop()] = i
|
|
80
|
+
}
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
if (type === 'list_item_open') {
|
|
84
|
+
listItemStack.push(i)
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
if (type === 'list_item_close') {
|
|
88
|
+
if (listItemStack.length > 0) {
|
|
89
|
+
listItemCloseByOpen[listItemStack.pop()] = i
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { listCloseByOpen, listItemCloseByOpen }
|
|
95
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Converts bullet_list with **Term** pattern to description_list (dl/dt/dd)
|
|
3
3
|
// This must run before Phase 1
|
|
4
4
|
|
|
5
|
-
import { findMatchingClose, findListEnd as coreFindListEnd
|
|
5
|
+
import { findMatchingClose, findListEnd as coreFindListEnd } from './list-helpers.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Parse attribute string like ".class1 .class2 #id data-foo="bar""
|
|
@@ -73,11 +73,32 @@ const findListEnd = (tokens, startIndex) => {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
76
|
+
* Collect direct list_item ranges within a list in a single pass.
|
|
77
77
|
*/
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
78
|
+
const collectListItemRanges = (tokens, listStart, listEnd) => {
|
|
79
|
+
const listToken = tokens[listStart]
|
|
80
|
+
if (!listToken) {
|
|
81
|
+
return []
|
|
82
|
+
}
|
|
83
|
+
const childLevel = (listToken.level ?? 0) + 1
|
|
84
|
+
const ranges = []
|
|
85
|
+
let currentOpen = -1
|
|
86
|
+
|
|
87
|
+
for (let i = listStart + 1; i < listEnd; i++) {
|
|
88
|
+
const token = tokens[i]
|
|
89
|
+
if (token.type === 'list_item_open' && token.level === childLevel) {
|
|
90
|
+
currentOpen = i
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
if (token.type === 'list_item_close' && token.level === childLevel) {
|
|
94
|
+
if (currentOpen !== -1) {
|
|
95
|
+
ranges.push({ open: currentOpen, close: i })
|
|
96
|
+
currentOpen = -1
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return ranges
|
|
81
102
|
}
|
|
82
103
|
|
|
83
104
|
/**
|
|
@@ -103,77 +124,72 @@ const findDDEnd = (tokens, startIndex) => {
|
|
|
103
124
|
const checkAndConvertToDL = (tokens, listStart, listEnd, opt) => {
|
|
104
125
|
// First pass: validate all items match DL pattern
|
|
105
126
|
let hasAnyDLItem = false
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
127
|
+
const itemRanges = collectListItemRanges(tokens, listStart, listEnd)
|
|
128
|
+
|
|
129
|
+
for (const range of itemRanges) {
|
|
130
|
+
const itemStart = range.open
|
|
131
|
+
const itemEnd = range.close
|
|
132
|
+
|
|
133
|
+
// Find first paragraph
|
|
134
|
+
let firstPara = -1
|
|
135
|
+
for (let j = itemStart + 1; j < itemEnd; j++) {
|
|
136
|
+
if (tokens[j].type === 'paragraph_open') {
|
|
137
|
+
firstPara = j
|
|
138
|
+
break
|
|
119
139
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (firstPara !== -1) {
|
|
143
|
+
const inlineToken = tokens[firstPara + 1]
|
|
144
|
+
if (inlineToken && inlineToken.type === 'inline') {
|
|
145
|
+
const dlCheck = isDLPattern(inlineToken.content)
|
|
146
|
+
if (dlCheck.isMatch) {
|
|
147
|
+
// Check if there's a description
|
|
148
|
+
let hasDescription = false
|
|
149
|
+
|
|
150
|
+
const afterStrong = dlCheck.afterStrong
|
|
151
|
+
|
|
152
|
+
// Pattern 1: **Term** description (2+ spaces, including newlines)
|
|
153
|
+
// Pattern 2: **Term**: description (colon)
|
|
154
|
+
// Pattern 3: **Term**\ description (backslash escape)
|
|
155
|
+
if (/^\s{2,}/.test(afterStrong) || /^\s*:/.test(afterStrong) || /^\\/.test(afterStrong)) {
|
|
156
|
+
// Remove leading space/colon/backslash and check remaining text
|
|
157
|
+
const cleaned = afterStrong.replace(/^[\s:]+/, '').replace(/^\\/, '').trim()
|
|
158
|
+
if (cleaned) {
|
|
159
|
+
hasDescription = true
|
|
140
160
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Pattern 4: Description in next paragraph (only **Term** in first para)
|
|
164
|
+
if (!hasDescription) {
|
|
165
|
+
// Check for additional paragraphs/lists
|
|
166
|
+
for (let k = firstPara + 3; k < itemEnd; k++) {
|
|
167
|
+
if (tokens[k].type === 'paragraph_open' ||
|
|
168
|
+
tokens[k].type === 'bullet_list_open' ||
|
|
169
|
+
tokens[k].type === 'ordered_list_open') {
|
|
170
|
+
hasDescription = true
|
|
171
|
+
break
|
|
152
172
|
}
|
|
153
173
|
}
|
|
154
|
-
|
|
155
|
-
// If no description, not a DL item
|
|
156
|
-
if (!hasDescription) {
|
|
157
|
-
return false
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
hasAnyDLItem = true
|
|
161
|
-
} else {
|
|
162
|
-
// Not all items are DL pattern - not a description list
|
|
163
|
-
return { nextIndex: listEnd + 1 }
|
|
164
174
|
}
|
|
175
|
+
|
|
176
|
+
// If no description, not a DL item
|
|
177
|
+
if (!hasDescription) {
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
hasAnyDLItem = true
|
|
182
|
+
} else {
|
|
183
|
+
// Not all items are DL pattern - not a description list
|
|
184
|
+
return { nextIndex: listEnd + 1 }
|
|
165
185
|
}
|
|
166
186
|
}
|
|
167
|
-
|
|
168
|
-
i = itemEnd + 1
|
|
169
|
-
} else {
|
|
170
|
-
i++
|
|
171
187
|
}
|
|
172
188
|
}
|
|
173
189
|
|
|
174
190
|
// If valid DL, convert immediately (avoid re-scanning)
|
|
175
191
|
if (hasAnyDLItem) {
|
|
176
|
-
convertBulletListToDL(tokens, listStart, listEnd, opt)
|
|
192
|
+
convertBulletListToDL(tokens, listStart, listEnd, opt, itemRanges)
|
|
177
193
|
// After conversion, tokens are replaced - continue from original listEnd position
|
|
178
194
|
// Note: convertBulletListToDL may change token count, but we use original listEnd
|
|
179
195
|
return { nextIndex: listStart + 1 } // Re-check from start since tokens changed
|
|
@@ -212,7 +228,7 @@ const isDLPattern = (content) => {
|
|
|
212
228
|
/**
|
|
213
229
|
* Convert bullet_list to dl/dt/dd structure using dl_open/dl_close tokens
|
|
214
230
|
*/
|
|
215
|
-
const convertBulletListToDL = (tokens, listStart, listEnd, opt) => {
|
|
231
|
+
const convertBulletListToDL = (tokens, listStart, listEnd, opt, itemRanges = null) => {
|
|
216
232
|
const newTokens = []
|
|
217
233
|
const listLevel = tokens[listStart].level
|
|
218
234
|
|
|
@@ -242,33 +258,31 @@ const convertBulletListToDL = (tokens, listStart, listEnd, opt) => {
|
|
|
242
258
|
const listAttrsFromItems = []
|
|
243
259
|
|
|
244
260
|
// Process each list_item
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
261
|
+
const ranges = Array.isArray(itemRanges) && itemRanges.length > 0
|
|
262
|
+
? itemRanges
|
|
263
|
+
: collectListItemRanges(tokens, listStart, listEnd)
|
|
264
|
+
|
|
265
|
+
for (const range of ranges) {
|
|
266
|
+
const itemStart = range.open
|
|
267
|
+
const itemEnd = range.close
|
|
268
|
+
const result = convertListItemToDtDd(tokens, itemStart, itemEnd, listLevel, opt)
|
|
269
|
+
|
|
270
|
+
// Update metadata
|
|
271
|
+
dlOpen._dlMetadata.itemCount++
|
|
272
|
+
|
|
273
|
+
// Check for list-level attrs returned from item
|
|
274
|
+
if (result.listAttrs && result.listAttrs.length > 0) {
|
|
275
|
+
listAttrsFromItems.push(...result.listAttrs)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (result.tokens) {
|
|
279
|
+
// Track last dd_open position (relative to newTokens)
|
|
280
|
+
for (let j = 0; j < result.tokens.length; j++) {
|
|
281
|
+
if (result.tokens[j].type === 'dd_open') {
|
|
282
|
+
dlOpen._dlMetadata.lastDdTokenIndex = newTokens.length + j
|
|
265
283
|
}
|
|
266
|
-
newTokens.push(...result.tokens)
|
|
267
284
|
}
|
|
268
|
-
|
|
269
|
-
i = itemEnd + 1
|
|
270
|
-
} else {
|
|
271
|
-
i++
|
|
285
|
+
newTokens.push(...result.tokens)
|
|
272
286
|
}
|
|
273
287
|
}
|
|
274
288
|
|
package/src/phase1-analyze.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Phase 1: List Structure Analysis and Marker Detection
|
|
2
2
|
// Analyze only, no token conversion
|
|
3
|
-
import { detectMarkerType } from './types-utility.js'
|
|
4
|
-
import { findMatchingClose } from './list-helpers.js'
|
|
3
|
+
import { detectMarkerType, detectMarkerTypeWithContext, detectSequencePattern } from './types-utility.js'
|
|
4
|
+
import { buildListCloseIndexMap, findMatchingClose } from './list-helpers.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Pre-compute DL scope (identify all DL ranges in O(n))
|
|
@@ -74,7 +74,7 @@ function isInsideDL(index, dlState) {
|
|
|
74
74
|
*/
|
|
75
75
|
export function analyzeListStructure(tokens, opt) {
|
|
76
76
|
const listInfos = []
|
|
77
|
-
const
|
|
77
|
+
const closeMap = buildListCloseIndexMap(tokens)
|
|
78
78
|
|
|
79
79
|
// Check DL existence (O(n) but optimized with early return)
|
|
80
80
|
let hasDL = false
|
|
@@ -92,12 +92,11 @@ export function analyzeListStructure(tokens, opt) {
|
|
|
92
92
|
// Process only top-level lists (nested lists collected recursively)
|
|
93
93
|
// Also process lists inside DL
|
|
94
94
|
for (let i = 0; i < tokens.length; i++) {
|
|
95
|
-
|
|
95
|
+
const token = tokens[i]
|
|
96
|
+
if (!token) {
|
|
96
97
|
continue
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
const token = tokens[i]
|
|
100
|
-
|
|
101
100
|
// Process level-0 lists or lists at any level inside DD
|
|
102
101
|
const isTopLevelList = (token.type === 'bullet_list_open' || token.type === 'ordered_list_open') &&
|
|
103
102
|
(token.level === 0 || token.level === undefined)
|
|
@@ -106,16 +105,13 @@ export function analyzeListStructure(tokens, opt) {
|
|
|
106
105
|
isInsideDL(i, dlScope)
|
|
107
106
|
|
|
108
107
|
if (isTopLevelList || isListInDD) {
|
|
109
|
-
const listInfo = analyzeList(tokens, i, opt)
|
|
108
|
+
const listInfo = analyzeList(tokens, i, opt, closeMap)
|
|
110
109
|
if (listInfo) {
|
|
111
110
|
listInfos.push(listInfo)
|
|
112
|
-
// Mark this list range as processed
|
|
113
|
-
for (let j = listInfo.startIndex; j <= listInfo.endIndex; j++) {
|
|
114
|
-
processed.add(j)
|
|
115
|
-
}
|
|
116
111
|
|
|
117
112
|
// Recursively collect nested lists
|
|
118
113
|
collectNestedLists(listInfo, listInfos)
|
|
114
|
+
i = listInfo.endIndex
|
|
119
115
|
}
|
|
120
116
|
}
|
|
121
117
|
}
|
|
@@ -143,9 +139,9 @@ function collectNestedLists(listInfo, listInfos) {
|
|
|
143
139
|
/**
|
|
144
140
|
* Analyze detailed information of a single list
|
|
145
141
|
*/
|
|
146
|
-
function analyzeList(tokens, startIndex, opt) {
|
|
142
|
+
function analyzeList(tokens, startIndex, opt, closeMap) {
|
|
147
143
|
const listToken = tokens[startIndex]
|
|
148
|
-
const endIndex = findListEnd(tokens, startIndex)
|
|
144
|
+
const endIndex = findListEnd(tokens, startIndex, closeMap)
|
|
149
145
|
|
|
150
146
|
if (endIndex === -1) {
|
|
151
147
|
return null
|
|
@@ -153,7 +149,7 @@ function analyzeList(tokens, startIndex, opt) {
|
|
|
153
149
|
|
|
154
150
|
const level = listToken.level || 0
|
|
155
151
|
const originalType = listToken.type
|
|
156
|
-
const items = analyzeListItems(tokens, startIndex, endIndex, opt)
|
|
152
|
+
const items = analyzeListItems(tokens, startIndex, endIndex, opt, closeMap)
|
|
157
153
|
const isLoose = detectLooseList(tokens, startIndex, endIndex, items)
|
|
158
154
|
if (!isLoose) {
|
|
159
155
|
hideFirstParagraphsForTightList(tokens, items, level)
|
|
@@ -188,7 +184,7 @@ function analyzeList(tokens, startIndex, opt) {
|
|
|
188
184
|
/**
|
|
189
185
|
* Analyze list items
|
|
190
186
|
*/
|
|
191
|
-
function analyzeListItems(tokens, startIndex, endIndex, opt) {
|
|
187
|
+
function analyzeListItems(tokens, startIndex, endIndex, opt, closeMap) {
|
|
192
188
|
const items = []
|
|
193
189
|
let i = startIndex + 1
|
|
194
190
|
|
|
@@ -196,8 +192,8 @@ function analyzeListItems(tokens, startIndex, endIndex, opt) {
|
|
|
196
192
|
const token = tokens[i]
|
|
197
193
|
|
|
198
194
|
if (token.type === 'list_item_open') {
|
|
199
|
-
const itemEndIndex = findListItemEnd(tokens, i)
|
|
200
|
-
const item = analyzeListItem(tokens, i, itemEndIndex, opt)
|
|
195
|
+
const itemEndIndex = findListItemEnd(tokens, i, closeMap)
|
|
196
|
+
const item = analyzeListItem(tokens, i, itemEndIndex, opt, closeMap)
|
|
201
197
|
items.push(item)
|
|
202
198
|
i = itemEndIndex + 1
|
|
203
199
|
} else {
|
|
@@ -211,66 +207,59 @@ function analyzeListItems(tokens, startIndex, endIndex, opt) {
|
|
|
211
207
|
/**
|
|
212
208
|
* Analyze a single list item
|
|
213
209
|
*/
|
|
214
|
-
function analyzeListItem(tokens, startIndex, endIndex, opt) {
|
|
210
|
+
function analyzeListItem(tokens, startIndex, endIndex, opt, closeMap) {
|
|
215
211
|
let content = ''
|
|
216
212
|
let markerInfo = null
|
|
217
213
|
let hasNestedList = false
|
|
218
214
|
let nestedLists = []
|
|
219
215
|
let firstParagraphIsLoose = false
|
|
216
|
+
let lastInlineContent = ''
|
|
220
217
|
|
|
221
218
|
// Check if blank line exists right after first paragraph (only before child lists)
|
|
222
219
|
// paragraph.hidden alone is insufficient: when parent list is loose, all paragraphs have hidden=false
|
|
223
220
|
// Need to use map info to verify actual blank line after paragraph
|
|
224
|
-
let nestedDepth = 0
|
|
225
221
|
let foundParagraph = false
|
|
226
222
|
let firstParagraphIndex = -1
|
|
223
|
+
let checkedFirstParagraphLoose = false
|
|
227
224
|
|
|
228
225
|
for (let i = startIndex + 1; i < endIndex; i++) {
|
|
229
226
|
const token = tokens[i]
|
|
230
227
|
|
|
228
|
+
if (token.type === 'inline' && token.content) {
|
|
229
|
+
lastInlineContent = token.content
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!checkedFirstParagraphLoose && token.type === 'paragraph_open' && !foundParagraph) {
|
|
233
|
+
foundParagraph = true
|
|
234
|
+
firstParagraphIndex = i
|
|
235
|
+
}
|
|
236
|
+
|
|
231
237
|
if (token.type === 'bullet_list_open' || token.type === 'ordered_list_open') {
|
|
232
|
-
|
|
233
|
-
if (foundParagraph && firstParagraphIndex !== -1) {
|
|
238
|
+
if (!checkedFirstParagraphLoose && foundParagraph && firstParagraphIndex !== -1) {
|
|
234
239
|
const paragraphToken = tokens[firstParagraphIndex]
|
|
235
240
|
const paragraphEndLine = paragraphToken.map ? paragraphToken.map[1] : undefined
|
|
236
241
|
const nestedListStartLine = token.map
|
|
237
242
|
? token.map[0]
|
|
238
243
|
: (typeof token._literalStartLine === 'number' ? token._literalStartLine : undefined)
|
|
239
244
|
if (typeof paragraphEndLine === 'number' && typeof nestedListStartLine === 'number') {
|
|
240
|
-
// Check if blank line exists between paragraph end and child list start
|
|
241
245
|
if (nestedListStartLine > paragraphEndLine) {
|
|
242
246
|
firstParagraphIsLoose = true
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
|
-
break
|
|
246
249
|
}
|
|
247
|
-
|
|
248
|
-
} else if (token.type === 'bullet_list_close' || token.type === 'ordered_list_close') {
|
|
249
|
-
nestedDepth--
|
|
250
|
-
} else if (nestedDepth === 0 && token.type === 'paragraph_open') {
|
|
251
|
-
// First paragraph outside nested lists
|
|
252
|
-
foundParagraph = true
|
|
253
|
-
firstParagraphIndex = i
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
for (let i = startIndex + 1; i < endIndex; i++) {
|
|
258
|
-
const token = tokens[i]
|
|
259
|
-
|
|
260
|
-
if (token.type === 'inline' && token.content) {
|
|
261
|
-
content = token.content
|
|
262
|
-
// Detect marker
|
|
263
|
-
markerInfo = detectMarkerType(content, opt)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (token.type === 'bullet_list_open' || token.type === 'ordered_list_open') {
|
|
250
|
+
checkedFirstParagraphLoose = true
|
|
267
251
|
hasNestedList = true
|
|
268
|
-
const nestedListInfo = analyzeList(tokens, i, opt)
|
|
252
|
+
const nestedListInfo = analyzeList(tokens, i, opt, closeMap)
|
|
269
253
|
nestedLists.push(nestedListInfo)
|
|
270
254
|
i = nestedListInfo.endIndex
|
|
271
255
|
}
|
|
272
256
|
}
|
|
273
257
|
|
|
258
|
+
if (firstParagraphIsLoose && hasNestedList && lastInlineContent) {
|
|
259
|
+
markerInfo = detectMarkerType(lastInlineContent)
|
|
260
|
+
}
|
|
261
|
+
content = lastInlineContent
|
|
262
|
+
|
|
274
263
|
return {
|
|
275
264
|
startIndex,
|
|
276
265
|
endIndex,
|
|
@@ -288,7 +277,6 @@ function analyzeListItem(tokens, startIndex, endIndex, opt) {
|
|
|
288
277
|
function extractMarkerInfo(tokens, startIndex, endIndex, opt) {
|
|
289
278
|
const listToken = tokens[startIndex]
|
|
290
279
|
const markers = []
|
|
291
|
-
let level = 0
|
|
292
280
|
|
|
293
281
|
// For ordered_list, get numbers from list_item_open's info
|
|
294
282
|
if (listToken.type === 'ordered_list_open') {
|
|
@@ -314,23 +302,25 @@ function extractMarkerInfo(tokens, startIndex, endIndex, opt) {
|
|
|
314
302
|
// Target only direct children list_items of this list
|
|
315
303
|
const targetLevel = (listToken.level || 0) + 3 // list_open(0) -> list_item(1) -> paragraph(2) -> inline(3)
|
|
316
304
|
|
|
317
|
-
//
|
|
305
|
+
// Collect inline tokens and contents once (for detecting iroha sequence etc.)
|
|
306
|
+
const inlineTokens = []
|
|
318
307
|
const allContents = []
|
|
319
308
|
for (let i = startIndex + 1; i < endIndex; i++) {
|
|
320
309
|
const token = tokens[i]
|
|
321
310
|
if (token.type === 'inline' && token.content && token.level === targetLevel) {
|
|
311
|
+
inlineTokens.push(token)
|
|
322
312
|
allContents.push(token.content)
|
|
323
313
|
}
|
|
324
314
|
}
|
|
325
315
|
|
|
316
|
+
const contextResult = allContents.length > 0 ? detectSequencePattern(allContents) : null
|
|
317
|
+
|
|
326
318
|
// Detect markers using full context
|
|
327
319
|
let sequentialNumber = 1 // Sequential number counter
|
|
328
|
-
for (
|
|
329
|
-
const token = tokens[i]
|
|
330
|
-
|
|
320
|
+
for (const token of inlineTokens) {
|
|
331
321
|
// Process only inline tokens of direct child items of this list
|
|
332
322
|
if (token.type === 'inline' && token.content && token.level === targetLevel) {
|
|
333
|
-
const markerInfo =
|
|
323
|
+
const markerInfo = detectMarkerTypeWithContext(token.content, contextResult)
|
|
334
324
|
if (markerInfo && markerInfo.type) {
|
|
335
325
|
// Use sequential numbers when same marker continues
|
|
336
326
|
// (e.g., "イ. イ. イ." → interpreted as "イ、ロ、ハ")
|
|
@@ -494,7 +484,11 @@ function hideFirstParagraphsForTightList(tokens, items, level) {
|
|
|
494
484
|
/**
|
|
495
485
|
* Find list end position
|
|
496
486
|
*/
|
|
497
|
-
function findListEnd(tokens, startIndex) {
|
|
487
|
+
function findListEnd(tokens, startIndex, closeMap) {
|
|
488
|
+
const mapped = closeMap?.listCloseByOpen?.[startIndex]
|
|
489
|
+
if (typeof mapped === 'number' && mapped !== -1) {
|
|
490
|
+
return mapped
|
|
491
|
+
}
|
|
498
492
|
const startToken = tokens[startIndex]
|
|
499
493
|
const openType = startToken.type
|
|
500
494
|
const closeType = openType.replace('_open', '_close')
|
|
@@ -505,7 +499,11 @@ function findListEnd(tokens, startIndex) {
|
|
|
505
499
|
/**
|
|
506
500
|
* Find list item end position
|
|
507
501
|
*/
|
|
508
|
-
function findListItemEnd(tokens, startIndex) {
|
|
502
|
+
function findListItemEnd(tokens, startIndex, closeMap) {
|
|
503
|
+
const mapped = closeMap?.listItemCloseByOpen?.[startIndex]
|
|
504
|
+
if (typeof mapped === 'number' && mapped !== -1) {
|
|
505
|
+
return mapped
|
|
506
|
+
}
|
|
509
507
|
const result = findMatchingClose(tokens, startIndex, 'list_item_open', 'list_item_close')
|
|
510
508
|
return result === -1 ? tokens.length - 1 : result
|
|
511
509
|
}
|