@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.2.0 → 0.2.1
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 +336 -335
- package/debug/dump_listinfos.mjs +65 -0
- package/debug/dump_tokens.mjs +72 -0
- package/index.js +5 -0
- package/package.json +5 -2
- package/src/phase0-description-list.js +654 -654
- package/src/phase1-analyze.js +90 -7
- package/src/phase2-convert.js +1037 -738
- package/src/phase3-attributes.js +260 -244
- package/src/phase5-spans.js +1 -1
- package/src/phase6-attrs-migration.js +1 -1
- package/src/preprocess-literal-lists.js +776 -0
- package/src/types-utility.js +995 -991
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
// Normalize literal ordered-list lines that markdown-it failed to parse
|
|
2
|
+
// e.g. nested "2. Child" without preceding "- ".
|
|
3
|
+
import { detectMarkerType } from './types-utility.js'
|
|
4
|
+
import { findMatchingClose, findListItemEnd } from './list-helpers.js'
|
|
5
|
+
|
|
6
|
+
const INLINE_LITERAL_HINT = /\n[ \t]+\S/
|
|
7
|
+
const CODE_LITERAL_HINT = /^[ \t]+\S/m
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Normalize literal nested ordered lists inside list items.
|
|
11
|
+
* Converts indented numeric lines into proper ordered_list tokens before Phase 1.
|
|
12
|
+
* @param {Array} tokens
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeLiteralOrderedLists(tokens) {
|
|
15
|
+
if (!Array.isArray(tokens) || tokens.length === 0) {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
const TokenClass = tokens[0]?.constructor
|
|
19
|
+
if (!TokenClass) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
24
|
+
const token = tokens[i]
|
|
25
|
+
if (token.type !== 'list_item_open') {
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
let listItemClose = findListItemEnd(tokens, i)
|
|
29
|
+
if (listItemClose === -1) {
|
|
30
|
+
listItemClose = tokens.length - 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let j = i + 1
|
|
34
|
+
while (j < listItemClose) {
|
|
35
|
+
const current = tokens[j]
|
|
36
|
+
if (current.type !== 'paragraph_open') {
|
|
37
|
+
j++
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (current.type === 'paragraph_open') {
|
|
42
|
+
const inlineIdx = j + 1
|
|
43
|
+
const paragraphCloseIdx = j + 2
|
|
44
|
+
if (inlineIdx >= listItemClose ||
|
|
45
|
+
paragraphCloseIdx >= tokens.length ||
|
|
46
|
+
tokens[inlineIdx].type !== 'inline' ||
|
|
47
|
+
tokens[paragraphCloseIdx].type !== 'paragraph_close') {
|
|
48
|
+
j++
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const inlineToken = tokens[inlineIdx]
|
|
53
|
+
if (!INLINE_LITERAL_HINT.test(inlineToken.content)) {
|
|
54
|
+
j = paragraphCloseIdx + 1
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
const baseLine = tokens[j].map ? tokens[j].map[0] : null
|
|
58
|
+
const segments = parseSegments(inlineToken.content, baseLine)
|
|
59
|
+
if (!segments.hasLiteral) {
|
|
60
|
+
j = paragraphCloseIdx + 1
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const listItemLevel = tokens[i].level ?? 0
|
|
65
|
+
const { tokens: replacementTokens, literalListPositions } = buildReplacementTokens(
|
|
66
|
+
segments.list,
|
|
67
|
+
listItemLevel,
|
|
68
|
+
TokenClass,
|
|
69
|
+
tokens[j],
|
|
70
|
+
tokens[inlineIdx],
|
|
71
|
+
tokens[paragraphCloseIdx]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const originalLength = paragraphCloseIdx - j + 1
|
|
75
|
+
tokens.splice(j, originalLength, ...replacementTokens)
|
|
76
|
+
|
|
77
|
+
const delta = replacementTokens.length - originalLength
|
|
78
|
+
listItemClose += delta
|
|
79
|
+
|
|
80
|
+
let mergeDelta = 0
|
|
81
|
+
for (const info of literalListPositions) {
|
|
82
|
+
const absoluteIdx = j + info.relativeIndex
|
|
83
|
+
mergeDelta += mergeFollowingLists(tokens, absoluteIdx)
|
|
84
|
+
}
|
|
85
|
+
listItemClose += mergeDelta
|
|
86
|
+
|
|
87
|
+
j = j + replacementTokens.length
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (current.type === 'ordered_list_open' &&
|
|
92
|
+
current.level === (tokens[i].level ?? 0) + 1) {
|
|
93
|
+
const delta = splitOrderedListForLiteralChildren(tokens, j, TokenClass)
|
|
94
|
+
listItemClose += delta
|
|
95
|
+
j++
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
j++
|
|
100
|
+
}
|
|
101
|
+
i = listItemClose
|
|
102
|
+
}
|
|
103
|
+
convertLiteralCodeBlocks(tokens, TokenClass)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function convertLiteralCodeBlocks(tokens, TokenClass) {
|
|
107
|
+
if (!TokenClass) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
111
|
+
const token = tokens[i]
|
|
112
|
+
if (!token || token.type !== 'code_block') {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
if (token.level === undefined || token.level < 1) {
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
if (!CODE_LITERAL_HINT.test(token.content)) {
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
const rawLines = token.content.split(/\r?\n/)
|
|
122
|
+
const literalCache = []
|
|
123
|
+
const baseLine = Array.isArray(token.map) ? token.map[0] : null
|
|
124
|
+
const { lists, nextIndex } = parseLiteralBlock(rawLines, 0, literalCache, baseLine)
|
|
125
|
+
const hasRemainingContent = rawLines.slice(nextIndex).some(line => line.trim().length > 0)
|
|
126
|
+
if (!Array.isArray(lists) || lists.length === 0 || hasRemainingContent) {
|
|
127
|
+
if (tryConvertCodeBlockContinuation(tokens, i, TokenClass)) {
|
|
128
|
+
i--
|
|
129
|
+
}
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const targetItemLevel = token.level + 1
|
|
134
|
+
let listItemIdx = -1
|
|
135
|
+
for (let k = i - 1; k >= 0; k--) {
|
|
136
|
+
const tk = tokens[k]
|
|
137
|
+
if (tk.type === 'list_item_open' && tk.level === targetItemLevel) {
|
|
138
|
+
listItemIdx = k
|
|
139
|
+
break
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (listItemIdx === -1) {
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
let listItemCloseIdx = findMatchingClose(tokens, listItemIdx, 'list_item_open', 'list_item_close')
|
|
146
|
+
if (listItemCloseIdx === -1) {
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
tokens.splice(i, 1)
|
|
151
|
+
if (listItemCloseIdx > i) {
|
|
152
|
+
listItemCloseIdx--
|
|
153
|
+
}
|
|
154
|
+
revealFirstParagraph(tokens, listItemIdx, listItemCloseIdx)
|
|
155
|
+
|
|
156
|
+
const insertionStart = listItemCloseIdx
|
|
157
|
+
const replacementTokens = []
|
|
158
|
+
const newListLevel = targetItemLevel + 1
|
|
159
|
+
for (const listNode of lists) {
|
|
160
|
+
replacementTokens.push(...buildListTokens(listNode, newListLevel, TokenClass))
|
|
161
|
+
}
|
|
162
|
+
tokens.splice(insertionStart, 0, ...replacementTokens)
|
|
163
|
+
|
|
164
|
+
for (let offset = 0; offset < replacementTokens.length; offset++) {
|
|
165
|
+
if (replacementTokens[offset]?._literalList) {
|
|
166
|
+
mergeFollowingLists(tokens, insertionStart + offset)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
i = insertionStart + replacementTokens.length - 1
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function tryConvertCodeBlockContinuation(tokens, codeBlockIndex, TokenClass) {
|
|
174
|
+
const codeToken = tokens[codeBlockIndex]
|
|
175
|
+
const targetLevel = (codeToken.level ?? 0) + 1
|
|
176
|
+
let listItemCloseIdx = -1
|
|
177
|
+
for (let k = codeBlockIndex - 1; k >= 0; k--) {
|
|
178
|
+
const tk = tokens[k]
|
|
179
|
+
if (tk.type === 'list_item_close' && tk.level === targetLevel) {
|
|
180
|
+
listItemCloseIdx = k
|
|
181
|
+
break
|
|
182
|
+
}
|
|
183
|
+
if (tk.type === 'list_item_open' && tk.level <= codeToken.level) {
|
|
184
|
+
break
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (listItemCloseIdx === -1) {
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
let listItemOpenIdx = -1
|
|
191
|
+
for (let k = listItemCloseIdx; k >= 0; k--) {
|
|
192
|
+
if (tokens[k].type === 'list_item_open' && tokens[k].level === targetLevel) {
|
|
193
|
+
const closeIdx = findListItemEnd(tokens, k)
|
|
194
|
+
if (closeIdx === listItemCloseIdx) {
|
|
195
|
+
listItemOpenIdx = k
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (listItemOpenIdx === -1) {
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
revealFirstParagraph(tokens, listItemOpenIdx, listItemCloseIdx)
|
|
204
|
+
const paragraphLevel = (tokens[listItemOpenIdx].level ?? 0) + 1
|
|
205
|
+
const paragraphs = buildParagraphTokensFromCodeBlock(codeToken.content, paragraphLevel, TokenClass)
|
|
206
|
+
if (paragraphs.length === 0) {
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
tokens.splice(listItemCloseIdx, 0, ...paragraphs)
|
|
210
|
+
const newIndex = codeBlockIndex + paragraphs.length
|
|
211
|
+
tokens.splice(newIndex, 1)
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function buildParagraphTokensFromCodeBlock(content, paragraphLevel, TokenClass) {
|
|
216
|
+
if (!content) {
|
|
217
|
+
return []
|
|
218
|
+
}
|
|
219
|
+
const normalized = content.replace(/\r\n?/g, '\n')
|
|
220
|
+
const lines = normalized.split('\n')
|
|
221
|
+
const nonEmpty = lines.filter(line => line.trim().length > 0)
|
|
222
|
+
if (nonEmpty.length === 0) {
|
|
223
|
+
return []
|
|
224
|
+
}
|
|
225
|
+
const indent = Math.min(...nonEmpty.map(line => line.match(/^\s*/)[0].length))
|
|
226
|
+
const dedented = lines
|
|
227
|
+
.map(line => (indent > 0 ? line.slice(Math.min(indent, line.length)) : line))
|
|
228
|
+
.join('\n')
|
|
229
|
+
const blocks = dedented.split(/\n{2,}/).map(block => block.trim()).filter(Boolean)
|
|
230
|
+
if (blocks.length === 0) {
|
|
231
|
+
return []
|
|
232
|
+
}
|
|
233
|
+
const paragraphs = []
|
|
234
|
+
for (const block of blocks) {
|
|
235
|
+
const pOpen = new TokenClass('paragraph_open', 'p', 1)
|
|
236
|
+
pOpen.level = paragraphLevel
|
|
237
|
+
pOpen.block = true
|
|
238
|
+
pOpen.hidden = false
|
|
239
|
+
paragraphs.push(pOpen)
|
|
240
|
+
|
|
241
|
+
const inline = new TokenClass('inline', '', 0)
|
|
242
|
+
inline.level = paragraphLevel + 1
|
|
243
|
+
inline.content = block
|
|
244
|
+
inline.children = []
|
|
245
|
+
paragraphs.push(inline)
|
|
246
|
+
|
|
247
|
+
const pClose = new TokenClass('paragraph_close', 'p', -1)
|
|
248
|
+
pClose.level = paragraphLevel
|
|
249
|
+
pClose.block = true
|
|
250
|
+
pClose.hidden = false
|
|
251
|
+
paragraphs.push(pClose)
|
|
252
|
+
}
|
|
253
|
+
return paragraphs
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function parseSegments(content, baseLine = null) {
|
|
257
|
+
if (!content) {
|
|
258
|
+
return { hasLiteral: false, list: [{ type: 'text', text: '', tight: false }] }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const lines = content.split('\n')
|
|
262
|
+
const literalCache = new Array(lines.length)
|
|
263
|
+
const segments = []
|
|
264
|
+
let buffer = []
|
|
265
|
+
let blankLinesInBuffer = 0
|
|
266
|
+
let hasLiteral = false
|
|
267
|
+
let idx = 0
|
|
268
|
+
|
|
269
|
+
const flushBuffer = ({ trimTrailing = false } = {}) => {
|
|
270
|
+
if (buffer.length === 0) return
|
|
271
|
+
const hadBlankLine = blankLinesInBuffer > 0
|
|
272
|
+
if (trimTrailing) {
|
|
273
|
+
while (buffer.length > 0 && buffer[buffer.length - 1].trim().length === 0) {
|
|
274
|
+
buffer.pop()
|
|
275
|
+
blankLinesInBuffer = Math.max(0, blankLinesInBuffer - 1)
|
|
276
|
+
}
|
|
277
|
+
if (buffer.length === 0) {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const textValue = buffer.join('\n')
|
|
282
|
+
const hasBlankLine = hadBlankLine
|
|
283
|
+
segments.push({ type: 'text', text: textValue, tight: !hasBlankLine })
|
|
284
|
+
buffer = []
|
|
285
|
+
blankLinesInBuffer = 0
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
while (idx < lines.length) {
|
|
289
|
+
const literalInfo = getLiteralInfo(lines, idx, literalCache)
|
|
290
|
+
if (!literalInfo) {
|
|
291
|
+
buffer.push(lines[idx])
|
|
292
|
+
if (lines[idx].trim().length === 0) {
|
|
293
|
+
blankLinesInBuffer++
|
|
294
|
+
}
|
|
295
|
+
idx++
|
|
296
|
+
continue
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
hasLiteral = true
|
|
300
|
+
flushBuffer({ trimTrailing: true })
|
|
301
|
+
const { lists, nextIndex } = parseLiteralBlock(lines, idx, literalCache, baseLine)
|
|
302
|
+
if (lists.length > 0) {
|
|
303
|
+
segments.push({ type: 'literal', lists })
|
|
304
|
+
}
|
|
305
|
+
idx = nextIndex
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
flushBuffer()
|
|
309
|
+
return { hasLiteral, list: segments }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function detectLiteralLine(line) {
|
|
313
|
+
if (!line) return null
|
|
314
|
+
if (/^\s*$/.test(line)) {
|
|
315
|
+
return null
|
|
316
|
+
}
|
|
317
|
+
const match = line.match(/^([ \t]+)(.*)$/)
|
|
318
|
+
if (!match) {
|
|
319
|
+
return null
|
|
320
|
+
}
|
|
321
|
+
const indentWidth = match[1].replace(/\t/g, ' ').length
|
|
322
|
+
if (indentWidth < 1) {
|
|
323
|
+
return null
|
|
324
|
+
}
|
|
325
|
+
const trimmed = match[2].trimStart()
|
|
326
|
+
if (!trimmed) {
|
|
327
|
+
return null
|
|
328
|
+
}
|
|
329
|
+
const markerInfo = detectMarkerType(trimmed)
|
|
330
|
+
if (!markerInfo || !markerInfo.marker) {
|
|
331
|
+
return null
|
|
332
|
+
}
|
|
333
|
+
if (!trimmed.startsWith(markerInfo.marker)) {
|
|
334
|
+
return null
|
|
335
|
+
}
|
|
336
|
+
const remainder = trimmed.slice(markerInfo.marker.length).replace(/^\s+/, '')
|
|
337
|
+
return {
|
|
338
|
+
indent: indentWidth,
|
|
339
|
+
markerInfo,
|
|
340
|
+
content: remainder
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function parseLiteralBlock(lines, startIndex, literalCache = null, baseLine = null) {
|
|
345
|
+
const rootLists = []
|
|
346
|
+
const stack = []
|
|
347
|
+
let idx = startIndex
|
|
348
|
+
let baseIndent = null
|
|
349
|
+
|
|
350
|
+
while (idx < lines.length) {
|
|
351
|
+
const rawLine = lines[idx]
|
|
352
|
+
if (rawLine !== undefined && rawLine.trim().length === 0) {
|
|
353
|
+
if (stack.length > 0) {
|
|
354
|
+
stack[stack.length - 1].list.isLoose = true
|
|
355
|
+
if (baseLine !== null) {
|
|
356
|
+
stack[stack.length - 1].list.lastLine = baseLine + idx
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
idx++
|
|
360
|
+
continue
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const literalInfo = getLiteralInfo(lines, idx, literalCache)
|
|
364
|
+
if (!literalInfo) {
|
|
365
|
+
break
|
|
366
|
+
}
|
|
367
|
+
if (baseIndent === null) {
|
|
368
|
+
baseIndent = literalInfo.indent
|
|
369
|
+
}
|
|
370
|
+
if (literalInfo.indent < baseIndent) {
|
|
371
|
+
break
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
while (stack.length > 0 && literalInfo.indent < stack[stack.length - 1].indent) {
|
|
375
|
+
stack.pop()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (stack.length === 0 || literalInfo.indent > stack[stack.length - 1].indent) {
|
|
379
|
+
const newList = createListNode(literalInfo, baseIndent === null || baseLine === null ? null : baseLine + idx)
|
|
380
|
+
if (stack.length === 0) {
|
|
381
|
+
rootLists.push(newList)
|
|
382
|
+
} else {
|
|
383
|
+
const parent = stack[stack.length - 1].list
|
|
384
|
+
const parentItems = parent.items
|
|
385
|
+
const parentItem = parentItems[parentItems.length - 1]
|
|
386
|
+
if (parentItem) {
|
|
387
|
+
parentItem.children.push(newList)
|
|
388
|
+
} else {
|
|
389
|
+
rootLists.push(newList)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
stack.push({ indent: literalInfo.indent, list: newList })
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const currentList = stack[stack.length - 1]?.list
|
|
396
|
+
if (!currentList) {
|
|
397
|
+
break
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
currentList.items.push({
|
|
401
|
+
markerInfo: literalInfo.markerInfo,
|
|
402
|
+
content: literalInfo.content,
|
|
403
|
+
children: []
|
|
404
|
+
})
|
|
405
|
+
if (baseLine !== null) {
|
|
406
|
+
currentList.lastLine = baseLine + idx
|
|
407
|
+
}
|
|
408
|
+
idx++
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return { lists: rootLists, nextIndex: idx }
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function getLiteralInfo(lines, index, cache = null) {
|
|
415
|
+
if (!cache) {
|
|
416
|
+
return detectLiteralLine(lines[index])
|
|
417
|
+
}
|
|
418
|
+
if (cache[index] === undefined) {
|
|
419
|
+
cache[index] = detectLiteralLine(lines[index]) || null
|
|
420
|
+
}
|
|
421
|
+
return cache[index]
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function createListNode(literalInfo, lineNumber = null) {
|
|
425
|
+
const markerInfo = literalInfo.markerInfo || {}
|
|
426
|
+
return {
|
|
427
|
+
markerType: markerInfo.type || 'decimal',
|
|
428
|
+
suffix: markerInfo.suffix || '.',
|
|
429
|
+
prefix: markerInfo.prefix || '',
|
|
430
|
+
startNumber: markerInfo.number || 1,
|
|
431
|
+
items: [],
|
|
432
|
+
isLoose: false,
|
|
433
|
+
startLine: lineNumber,
|
|
434
|
+
lastLine: lineNumber
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function buildReplacementTokens(segments, listItemLevel, TokenClass, paragraphOpen, inlineToken, paragraphClose) {
|
|
439
|
+
const tokens = []
|
|
440
|
+
const literalListPositions = []
|
|
441
|
+
let templateUsed = false
|
|
442
|
+
|
|
443
|
+
for (const segment of segments) {
|
|
444
|
+
if (segment.type === 'text') {
|
|
445
|
+
if (!segment.text) {
|
|
446
|
+
templateUsed = true
|
|
447
|
+
continue
|
|
448
|
+
}
|
|
449
|
+
const template = !templateUsed ? { open: paragraphOpen, inline: inlineToken, close: paragraphClose } : null
|
|
450
|
+
tokens.push(...createParagraphTokens(segment.text, listItemLevel, TokenClass, template, segment.tight))
|
|
451
|
+
templateUsed = true
|
|
452
|
+
} else if (segment.type === 'literal') {
|
|
453
|
+
for (const listNode of segment.lists) {
|
|
454
|
+
const relativeIndex = tokens.length
|
|
455
|
+
tokens.push(...buildListTokens(listNode, listItemLevel + 1, TokenClass))
|
|
456
|
+
literalListPositions.push({ relativeIndex, level: listItemLevel + 1 })
|
|
457
|
+
}
|
|
458
|
+
templateUsed = true
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return { tokens, literalListPositions }
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function createParagraphTokens(text, listItemLevel, TokenClass, template, forceTight = false) {
|
|
466
|
+
if (!text) {
|
|
467
|
+
return []
|
|
468
|
+
}
|
|
469
|
+
const tokens = []
|
|
470
|
+
const level = listItemLevel + 1
|
|
471
|
+
const open = template ? cloneToken(template.open) : new TokenClass('paragraph_open', 'p', 1)
|
|
472
|
+
open.level = level
|
|
473
|
+
open.block = true
|
|
474
|
+
const baseHidden = template && typeof template.open?.hidden === 'boolean' ? template.open.hidden : false
|
|
475
|
+
const shouldHide = baseHidden
|
|
476
|
+
open.hidden = shouldHide
|
|
477
|
+
if (forceTight) {
|
|
478
|
+
open._literalTight = true
|
|
479
|
+
}
|
|
480
|
+
tokens.push(open)
|
|
481
|
+
|
|
482
|
+
const inline = new TokenClass('inline', '', 0)
|
|
483
|
+
inline.level = level + 1
|
|
484
|
+
inline.content = text
|
|
485
|
+
inline.children = []
|
|
486
|
+
if (template?.inline?.meta) {
|
|
487
|
+
inline.meta = { ...template.inline.meta }
|
|
488
|
+
}
|
|
489
|
+
if (template?.inline?.attrs) {
|
|
490
|
+
inline.attrs = template.inline.attrs.map(([name, value]) => [name, value])
|
|
491
|
+
}
|
|
492
|
+
inline.block = template?.inline?.block ?? false
|
|
493
|
+
inline.hidden = template?.inline?.hidden ?? false
|
|
494
|
+
tokens.push(inline)
|
|
495
|
+
|
|
496
|
+
const close = template ? cloneToken(template.close) : new TokenClass('paragraph_close', 'p', -1)
|
|
497
|
+
close.level = level
|
|
498
|
+
close.block = true
|
|
499
|
+
close.hidden = shouldHide
|
|
500
|
+
if (forceTight) {
|
|
501
|
+
close._literalTight = true
|
|
502
|
+
}
|
|
503
|
+
tokens.push(close)
|
|
504
|
+
return tokens
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function buildListTokens(listNode, listLevel, TokenClass) {
|
|
508
|
+
const tokens = []
|
|
509
|
+
const listOpen = new TokenClass('ordered_list_open', 'ol', 1)
|
|
510
|
+
listOpen.level = listLevel
|
|
511
|
+
listOpen.block = true
|
|
512
|
+
listOpen.markup = listNode.suffix || '.'
|
|
513
|
+
listOpen.attrs = null
|
|
514
|
+
listOpen._literalList = true
|
|
515
|
+
if (typeof listNode.startLine === 'number') {
|
|
516
|
+
listOpen._literalStartLine = listNode.startLine
|
|
517
|
+
}
|
|
518
|
+
if (typeof listNode.lastLine === 'number') {
|
|
519
|
+
listOpen._literalLastLine = listNode.lastLine
|
|
520
|
+
}
|
|
521
|
+
if (typeof listNode.startNumber === 'number' && listNode.startNumber !== 1) {
|
|
522
|
+
listOpen.attrs = [['start', String(listNode.startNumber)]]
|
|
523
|
+
}
|
|
524
|
+
tokens.push(listOpen)
|
|
525
|
+
|
|
526
|
+
const listIsLoose = !!listNode.isLoose
|
|
527
|
+
for (const item of listNode.items) {
|
|
528
|
+
tokens.push(...buildListItemTokens(item, listLevel, TokenClass, listIsLoose))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const listClose = new TokenClass('ordered_list_close', 'ol', -1)
|
|
532
|
+
listClose.level = listLevel
|
|
533
|
+
listClose.block = true
|
|
534
|
+
listClose.markup = listNode.suffix || '.'
|
|
535
|
+
tokens.push(listClose)
|
|
536
|
+
return tokens
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function buildListItemTokens(item, listLevel, TokenClass, parentListIsLoose = false) {
|
|
540
|
+
const tokens = []
|
|
541
|
+
const itemLevel = listLevel + 1
|
|
542
|
+
const liOpen = new TokenClass('list_item_open', 'li', 1)
|
|
543
|
+
liOpen.level = itemLevel
|
|
544
|
+
liOpen.block = true
|
|
545
|
+
liOpen.markup = item.markerInfo?.suffix || '.'
|
|
546
|
+
liOpen.info = item.markerInfo?.number !== undefined ? String(item.markerInfo.number) : ''
|
|
547
|
+
tokens.push(liOpen)
|
|
548
|
+
|
|
549
|
+
const paragraphLevel = itemLevel + 1
|
|
550
|
+
const pOpen = new TokenClass('paragraph_open', 'p', 1)
|
|
551
|
+
pOpen.level = paragraphLevel
|
|
552
|
+
pOpen.block = true
|
|
553
|
+
pOpen.hidden = !parentListIsLoose
|
|
554
|
+
if (!parentListIsLoose) {
|
|
555
|
+
pOpen._literalTight = true
|
|
556
|
+
}
|
|
557
|
+
tokens.push(pOpen)
|
|
558
|
+
|
|
559
|
+
const inline = new TokenClass('inline', '', 0)
|
|
560
|
+
inline.level = paragraphLevel + 1
|
|
561
|
+
inline.content = item.content || ''
|
|
562
|
+
inline.children = []
|
|
563
|
+
tokens.push(inline)
|
|
564
|
+
|
|
565
|
+
const pClose = new TokenClass('paragraph_close', 'p', -1)
|
|
566
|
+
pClose.level = paragraphLevel
|
|
567
|
+
pClose.block = true
|
|
568
|
+
pClose.hidden = !parentListIsLoose
|
|
569
|
+
if (!parentListIsLoose) {
|
|
570
|
+
pClose._literalTight = true
|
|
571
|
+
}
|
|
572
|
+
tokens.push(pClose)
|
|
573
|
+
|
|
574
|
+
if (item.children && item.children.length > 0) {
|
|
575
|
+
for (const childList of item.children) {
|
|
576
|
+
tokens.push(...buildListTokens(childList, itemLevel + 1, TokenClass))
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const liClose = new TokenClass('list_item_close', 'li', -1)
|
|
581
|
+
liClose.level = itemLevel
|
|
582
|
+
liClose.block = true
|
|
583
|
+
tokens.push(liClose)
|
|
584
|
+
return tokens
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function cloneToken(token) {
|
|
588
|
+
if (!token) {
|
|
589
|
+
return token
|
|
590
|
+
}
|
|
591
|
+
const TokenClass = token.constructor
|
|
592
|
+
const cloned = new TokenClass(token.type, token.tag, token.nesting)
|
|
593
|
+
cloned.attrs = token.attrs ? token.attrs.map(([name, value]) => [name, value]) : null
|
|
594
|
+
cloned.map = token.map ? [...token.map] : null
|
|
595
|
+
cloned.level = token.level
|
|
596
|
+
cloned.content = token.content
|
|
597
|
+
cloned.markup = token.markup
|
|
598
|
+
cloned.info = token.info
|
|
599
|
+
cloned.meta = token.meta ? { ...token.meta } : null
|
|
600
|
+
cloned.block = token.block
|
|
601
|
+
cloned.hidden = token.hidden
|
|
602
|
+
cloned.children = token.children ? token.children.map(child => cloneToken(child)) : null
|
|
603
|
+
return cloned
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function revealFirstParagraph(tokens, listItemOpenIdx, listItemCloseIdx) {
|
|
607
|
+
const paragraphLevel = (tokens[listItemOpenIdx].level ?? 0) + 1
|
|
608
|
+
for (let k = listItemOpenIdx + 1; k < listItemCloseIdx; k++) {
|
|
609
|
+
if (tokens[k].type === 'paragraph_open' && tokens[k].level === paragraphLevel) {
|
|
610
|
+
tokens[k].hidden = false
|
|
611
|
+
for (let m = k + 1; m < listItemCloseIdx; m++) {
|
|
612
|
+
if (tokens[m].type === 'paragraph_close' && tokens[m].level === paragraphLevel) {
|
|
613
|
+
tokens[m].hidden = false
|
|
614
|
+
break
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
break
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function mergeFollowingLists(tokens, listOpenIndex) {
|
|
623
|
+
if (listOpenIndex < 0 || listOpenIndex >= tokens.length) {
|
|
624
|
+
return 0
|
|
625
|
+
}
|
|
626
|
+
const listOpen = tokens[listOpenIndex]
|
|
627
|
+
if (!listOpen || listOpen.type !== 'ordered_list_open') {
|
|
628
|
+
return 0
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
let totalDelta = 0
|
|
632
|
+
let listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
|
|
633
|
+
if (listCloseIndex === -1) {
|
|
634
|
+
return 0
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
let forceLoose = false
|
|
638
|
+
while (listCloseIndex + 1 < tokens.length) {
|
|
639
|
+
const nextToken = tokens[listCloseIndex + 1]
|
|
640
|
+
if (!nextToken ||
|
|
641
|
+
nextToken.type !== 'ordered_list_open' ||
|
|
642
|
+
nextToken.level !== listOpen.level) {
|
|
643
|
+
break
|
|
644
|
+
}
|
|
645
|
+
const nextClose = findMatchingClose(tokens, listCloseIndex + 1, 'ordered_list_open', 'ordered_list_close')
|
|
646
|
+
if (nextClose === -1) {
|
|
647
|
+
break
|
|
648
|
+
}
|
|
649
|
+
const nextIsLiteral = !!nextToken._literalList
|
|
650
|
+
const innerTokens = tokens.slice(listCloseIndex + 2, nextClose)
|
|
651
|
+
const removeCount = nextClose - (listCloseIndex + 1) + 1
|
|
652
|
+
tokens.splice(listCloseIndex + 1, removeCount)
|
|
653
|
+
tokens.splice(listCloseIndex, 0, ...innerTokens)
|
|
654
|
+
|
|
655
|
+
listCloseIndex = listCloseIndex + innerTokens.length
|
|
656
|
+
totalDelta -= 2
|
|
657
|
+
if (!nextIsLiteral) {
|
|
658
|
+
const startLine = Array.isArray(nextToken.map) ? nextToken.map[0] : null
|
|
659
|
+
if (typeof startLine === 'number' && typeof listOpen._literalLastLine === 'number') {
|
|
660
|
+
if (startLine - listOpen._literalLastLine > 1) {
|
|
661
|
+
forceLoose = true
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (typeof nextToken._literalLastLine === 'number') {
|
|
666
|
+
listOpen._literalLastLine = nextToken._literalLastLine
|
|
667
|
+
} else if (Array.isArray(nextToken.map) && typeof nextToken.map[1] === 'number') {
|
|
668
|
+
listOpen._literalLastLine = nextToken.map[1]
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (forceLoose) {
|
|
673
|
+
markLiteralListLoose(tokens, listOpenIndex)
|
|
674
|
+
}
|
|
675
|
+
return totalDelta
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function markLiteralListLoose(tokens, listOpenIndex) {
|
|
679
|
+
const listOpen = tokens[listOpenIndex]
|
|
680
|
+
if (!listOpen || listOpen.type !== 'ordered_list_open') {
|
|
681
|
+
return
|
|
682
|
+
}
|
|
683
|
+
const listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
|
|
684
|
+
if (listCloseIndex === -1) {
|
|
685
|
+
return
|
|
686
|
+
}
|
|
687
|
+
const paragraphLevel = (listOpen.level ?? 0) + 2
|
|
688
|
+
for (let i = listOpenIndex + 1; i < listCloseIndex; i++) {
|
|
689
|
+
const token = tokens[i]
|
|
690
|
+
if ((token.type === 'paragraph_open' || token.type === 'paragraph_close') &&
|
|
691
|
+
token.level === paragraphLevel) {
|
|
692
|
+
token.hidden = false
|
|
693
|
+
if (token._literalTight) {
|
|
694
|
+
delete token._literalTight
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function splitOrderedListForLiteralChildren(tokens, listOpenIndex, TokenClass) {
|
|
701
|
+
const listToken = tokens[listOpenIndex]
|
|
702
|
+
const listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
|
|
703
|
+
if (listCloseIndex === -1) {
|
|
704
|
+
return 0
|
|
705
|
+
}
|
|
706
|
+
const childLevel = (listToken.level ?? 0) + 1
|
|
707
|
+
const ranges = []
|
|
708
|
+
|
|
709
|
+
for (let idx = listOpenIndex + 1; idx < listCloseIndex; idx++) {
|
|
710
|
+
const token = tokens[idx]
|
|
711
|
+
if (token.type === 'list_item_open' && token.level === childLevel) {
|
|
712
|
+
const closeIdx = findMatchingClose(tokens, idx, 'list_item_open', 'list_item_close')
|
|
713
|
+
if (closeIdx === -1) {
|
|
714
|
+
break
|
|
715
|
+
}
|
|
716
|
+
ranges.push({ open: idx, close: closeIdx })
|
|
717
|
+
idx = closeIdx
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (ranges.length <= 1) {
|
|
722
|
+
return 0
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const extraRanges = ranges.slice(1)
|
|
726
|
+
const childTokens = []
|
|
727
|
+
let removedCount = 0
|
|
728
|
+
|
|
729
|
+
for (const range of extraRanges) {
|
|
730
|
+
childTokens.push(...tokens.slice(range.open, range.close + 1))
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
for (let k = extraRanges.length - 1; k >= 0; k--) {
|
|
734
|
+
const range = extraRanges[k]
|
|
735
|
+
const len = range.close - range.open + 1
|
|
736
|
+
tokens.splice(range.open, len)
|
|
737
|
+
removedCount += len
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (listToken._markerInfo) {
|
|
741
|
+
delete listToken._markerInfo
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const levelShift = 2
|
|
745
|
+
for (const token of childTokens) {
|
|
746
|
+
if (typeof token.level === 'number') {
|
|
747
|
+
token.level += levelShift
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const nestedListOpen = new TokenClass('ordered_list_open', 'ol', 1)
|
|
752
|
+
nestedListOpen.level = (listToken.level ?? 0) + 2
|
|
753
|
+
nestedListOpen.block = true
|
|
754
|
+
nestedListOpen.markup = listToken.markup
|
|
755
|
+
nestedListOpen.attrs = null
|
|
756
|
+
nestedListOpen._literalList = true
|
|
757
|
+
|
|
758
|
+
const firstChild = childTokens.find(t => t.type === 'list_item_open')
|
|
759
|
+
if (firstChild?.info) {
|
|
760
|
+
const num = parseInt(firstChild.info, 10)
|
|
761
|
+
if (!Number.isNaN(num) && num !== 1) {
|
|
762
|
+
nestedListOpen.attrs = [['start', String(num)]]
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const nestedListClose = new TokenClass('ordered_list_close', 'ol', -1)
|
|
767
|
+
nestedListClose.level = nestedListOpen.level
|
|
768
|
+
nestedListClose.block = true
|
|
769
|
+
nestedListClose.markup = listToken.markup
|
|
770
|
+
|
|
771
|
+
const insertionIndex = ranges[0].close
|
|
772
|
+
tokens.splice(insertionIndex, 0, nestedListOpen, ...childTokens, nestedListClose)
|
|
773
|
+
|
|
774
|
+
const addedCount = childTokens.length + 2
|
|
775
|
+
return addedCount - removedCount
|
|
776
|
+
}
|