@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
|
@@ -8,39 +8,152 @@ import { findMatchingClose, findListEnd as coreFindListEnd } from './list-helper
|
|
|
8
8
|
* Parse attribute string like ".class1 .class2 #id data-foo="bar""
|
|
9
9
|
* Returns array of [key, value] pairs
|
|
10
10
|
*/
|
|
11
|
+
const ATTR_TOKEN_REGEX = /\s*(\.[\w-]+|#[\w-]+|[\w:-]+=(?:"[^"]*"|'[^']*'|[^\s"'{}]+)|[\w:-]+)/y
|
|
12
|
+
const ATTR_KEY_VALUE_REGEX = /^([\w:-]+)=(?:"([^"]*)"|'([^']*)'|([^\s"'{}]+))$/
|
|
13
|
+
const LEADING_ATTR_BLOCK_REGEX = /^[ \t]*\{([^}]+)\}/
|
|
14
|
+
const STANDALONE_ATTR_BLOCK_REGEX = /^\{([^}]+)\}$/
|
|
15
|
+
const TRAILING_TWO_SPACES_REGEX = / {2,}$/
|
|
16
|
+
const TRAILING_BACKSLASH_BREAK_REGEX = /\\\s*$/
|
|
17
|
+
|
|
11
18
|
const parseAttrString = (attrStr) => {
|
|
19
|
+
if (!attrStr || typeof attrStr !== 'string') {
|
|
20
|
+
return []
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
const attrs = []
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
const customAttrs = []
|
|
25
|
+
const classes = []
|
|
26
|
+
let id = null
|
|
27
|
+
let cursor = 0
|
|
28
|
+
|
|
29
|
+
while (cursor < attrStr.length) {
|
|
30
|
+
ATTR_TOKEN_REGEX.lastIndex = cursor
|
|
31
|
+
const match = ATTR_TOKEN_REGEX.exec(attrStr)
|
|
32
|
+
if (!match) {
|
|
33
|
+
// Allow only trailing whitespace; otherwise reject as invalid attr syntax.
|
|
34
|
+
if (/^\s*$/.test(attrStr.slice(cursor))) {
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
return []
|
|
38
|
+
}
|
|
39
|
+
const token = match[1]
|
|
40
|
+
cursor = ATTR_TOKEN_REGEX.lastIndex
|
|
41
|
+
if (token[0] === '.') {
|
|
42
|
+
const cls = token.slice(1)
|
|
43
|
+
if (cls) {
|
|
44
|
+
classes.push(cls)
|
|
45
|
+
}
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
if (token[0] === '#') {
|
|
49
|
+
if (!id) {
|
|
50
|
+
id = token.slice(1)
|
|
51
|
+
}
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const kv = token.match(ATTR_KEY_VALUE_REGEX)
|
|
56
|
+
if (!kv) {
|
|
57
|
+
// markdown-it-attrs boolean attribute (e.g. {foo})
|
|
58
|
+
customAttrs.push([token, ''])
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
const key = kv[1]
|
|
62
|
+
const value = kv[2] ?? kv[3] ?? kv[4] ?? ''
|
|
63
|
+
customAttrs.push([key, value])
|
|
21
64
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
attrs.push(['id', idMatch[1]])
|
|
65
|
+
|
|
66
|
+
if (classes.length > 0) {
|
|
67
|
+
attrs.push(['class', classes.join(' ')])
|
|
26
68
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (dataMatches) {
|
|
30
|
-
dataMatches.forEach(match => {
|
|
31
|
-
const [, key, value] = match.match(/([\w-]+)="([^"]+)"/)
|
|
32
|
-
attrs.push([key, value])
|
|
33
|
-
})
|
|
69
|
+
if (id) {
|
|
70
|
+
attrs.push(['id', id])
|
|
34
71
|
}
|
|
35
|
-
|
|
72
|
+
if (customAttrs.length > 0) {
|
|
73
|
+
attrs.push(...customAttrs)
|
|
74
|
+
}
|
|
75
|
+
|
|
36
76
|
return attrs
|
|
37
77
|
}
|
|
38
78
|
|
|
79
|
+
const consumeLeadingValidAttrBlocks = (text) => {
|
|
80
|
+
if (typeof text !== 'string' || text.length === 0) {
|
|
81
|
+
return { rest: text || '', attrs: [] }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let rest = text
|
|
85
|
+
const attrs = []
|
|
86
|
+
while (true) {
|
|
87
|
+
const match = rest.match(LEADING_ATTR_BLOCK_REGEX)
|
|
88
|
+
if (!match) {
|
|
89
|
+
break
|
|
90
|
+
}
|
|
91
|
+
const parsed = parseAttrString(match[1])
|
|
92
|
+
if (parsed.length === 0) {
|
|
93
|
+
break
|
|
94
|
+
}
|
|
95
|
+
attrs.push(...parsed)
|
|
96
|
+
rest = rest.slice(match[0].length)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { rest, attrs }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const hasExplicitLineBreakMarker = (afterStrong) => {
|
|
103
|
+
if (typeof afterStrong !== 'string') {
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
const lineBreakIndex = afterStrong.indexOf('\n')
|
|
107
|
+
if (lineBreakIndex === -1) {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
const firstLine = afterStrong.slice(0, lineBreakIndex)
|
|
111
|
+
return TRAILING_TWO_SPACES_REGEX.test(firstLine) || TRAILING_BACKSLASH_BREAK_REGEX.test(firstLine)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const getDescriptionAfterExplicitLineBreak = (afterStrong) => {
|
|
115
|
+
if (!hasExplicitLineBreakMarker(afterStrong)) {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
const lineBreakIndex = afterStrong.indexOf('\n')
|
|
119
|
+
return afterStrong.slice(lineBreakIndex + 1)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const hasMeaningfulDescriptionContent = (text) => {
|
|
123
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const lines = text.split('\n')
|
|
128
|
+
const renderedLines = []
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
const trimmed = line.trim()
|
|
131
|
+
if (!trimmed) {
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
const attrMatch = trimmed.match(STANDALONE_ATTR_BLOCK_REGEX)
|
|
135
|
+
if (attrMatch) {
|
|
136
|
+
const parsed = parseAttrString(attrMatch[1])
|
|
137
|
+
if (parsed.length > 0) {
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
renderedLines.push(trimmed)
|
|
142
|
+
}
|
|
143
|
+
return renderedLines.join('\n').trim().length > 0
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const copyMap = (target, source) => {
|
|
147
|
+
if (!target || !source || !Array.isArray(source.map)) {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
target.map = source.map.slice()
|
|
151
|
+
}
|
|
152
|
+
|
|
39
153
|
/**
|
|
40
154
|
* Process description list patterns in tokens
|
|
41
155
|
* @param {Array} tokens - Token array
|
|
42
156
|
* @param {Object} opt - Options object
|
|
43
|
-
```
|
|
44
157
|
*/
|
|
45
158
|
export const processDescriptionList = (tokens, opt) => {
|
|
46
159
|
if (!opt.descriptionList && !opt.descriptionListWithDiv) {
|
|
@@ -54,7 +167,7 @@ export const processDescriptionList = (tokens, opt) => {
|
|
|
54
167
|
if (tokens[i].type === 'bullet_list_open') {
|
|
55
168
|
const listEnd = findListEnd(tokens, i)
|
|
56
169
|
const dlCheck = checkAndConvertToDL(tokens, i, listEnd, opt)
|
|
57
|
-
i = dlCheck.nextIndex
|
|
170
|
+
i = typeof dlCheck?.nextIndex === 'number' ? dlCheck.nextIndex : listEnd + 1
|
|
58
171
|
} else {
|
|
59
172
|
i++
|
|
60
173
|
}
|
|
@@ -101,6 +214,21 @@ const collectListItemRanges = (tokens, listStart, listEnd) => {
|
|
|
101
214
|
return ranges
|
|
102
215
|
}
|
|
103
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Return the first direct child token index of a list_item.
|
|
219
|
+
* Direct child means token.level === list_item.level + 1.
|
|
220
|
+
*/
|
|
221
|
+
const findFirstDirectChildInListItem = (tokens, itemStart, itemEnd) => {
|
|
222
|
+
const itemLevel = tokens[itemStart]?.level ?? 0
|
|
223
|
+
const childLevel = itemLevel + 1
|
|
224
|
+
for (let i = itemStart + 1; i < itemEnd; i++) {
|
|
225
|
+
if (tokens[i].level === childLevel) {
|
|
226
|
+
return i
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return -1
|
|
230
|
+
}
|
|
231
|
+
|
|
104
232
|
/**
|
|
105
233
|
* Find matching dl_close token
|
|
106
234
|
*/
|
|
@@ -130,60 +258,57 @@ const checkAndConvertToDL = (tokens, listStart, listEnd, opt) => {
|
|
|
130
258
|
const itemStart = range.open
|
|
131
259
|
const itemEnd = range.close
|
|
132
260
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (tokens[j].type === 'paragraph_open') {
|
|
137
|
-
firstPara = j
|
|
138
|
-
break
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (firstPara !== -1) {
|
|
261
|
+
const firstChild = findFirstDirectChildInListItem(tokens, itemStart, itemEnd)
|
|
262
|
+
if (firstChild !== -1 && tokens[firstChild].type === 'paragraph_open') {
|
|
263
|
+
const firstPara = firstChild
|
|
143
264
|
const inlineToken = tokens[firstPara + 1]
|
|
144
|
-
if (inlineToken
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
}
|
|
265
|
+
if (!inlineToken || inlineToken.type !== 'inline') {
|
|
266
|
+
return { nextIndex: listEnd + 1 }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const dlCheck = isDLPattern(inlineToken.content)
|
|
270
|
+
if (!dlCheck.isMatch) {
|
|
271
|
+
// Not all items are DL pattern - not a description list
|
|
272
|
+
return { nextIndex: listEnd + 1 }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Check if there's a description
|
|
276
|
+
let hasDescription = false
|
|
277
|
+
const afterStrong = dlCheck.afterStrong
|
|
278
|
+
|
|
279
|
+
// Pattern 1: Explicit line-break marker after term line (` ` or `\`) and
|
|
280
|
+
// description text in the remaining first paragraph content.
|
|
281
|
+
const explicitBreakDescription = getDescriptionAfterExplicitLineBreak(afterStrong)
|
|
282
|
+
if (explicitBreakDescription !== null && hasMeaningfulDescriptionContent(explicitBreakDescription)) {
|
|
283
|
+
hasDescription = true
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Pattern 2: Description in next paragraph/list (term-only first paragraph).
|
|
287
|
+
if (!hasDescription) {
|
|
288
|
+
// Check for additional paragraphs/lists
|
|
289
|
+
const itemChildLevel = (tokens[itemStart]?.level ?? 0) + 1
|
|
290
|
+
for (let k = firstPara + 3; k < itemEnd; k++) {
|
|
291
|
+
if (tokens[k].level !== itemChildLevel) {
|
|
292
|
+
continue
|
|
174
293
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
294
|
+
if (tokens[k].type === 'paragraph_open' ||
|
|
295
|
+
tokens[k].type === 'bullet_list_open' ||
|
|
296
|
+
tokens[k].type === 'ordered_list_open') {
|
|
297
|
+
hasDescription = true
|
|
298
|
+
break
|
|
179
299
|
}
|
|
180
|
-
|
|
181
|
-
hasAnyDLItem = true
|
|
182
|
-
} else {
|
|
183
|
-
// Not all items are DL pattern - not a description list
|
|
184
|
-
return { nextIndex: listEnd + 1 }
|
|
185
300
|
}
|
|
186
301
|
}
|
|
302
|
+
|
|
303
|
+
// If no description, not a DL item
|
|
304
|
+
if (!hasDescription) {
|
|
305
|
+
return { nextIndex: listEnd + 1 }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
hasAnyDLItem = true
|
|
309
|
+
} else {
|
|
310
|
+
// Any list_item that doesn't start with a paragraph cannot be a DL item.
|
|
311
|
+
return { nextIndex: listEnd + 1 }
|
|
187
312
|
}
|
|
188
313
|
}
|
|
189
314
|
|
|
@@ -211,16 +336,11 @@ const isDLPattern = (content) => {
|
|
|
211
336
|
|
|
212
337
|
const afterStrong = match[2] // Text after closing **
|
|
213
338
|
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const isMatch = /^\s{2,}/.test(afterStrong) ||
|
|
220
|
-
/^\s*:/.test(afterStrong) ||
|
|
221
|
-
/^\\/.test(afterStrong) ||
|
|
222
|
-
/^\s*$/.test(afterStrong) ||
|
|
223
|
-
/^\s*\{[^}]+\}/.test(afterStrong) // {.class} or {#id} etc
|
|
339
|
+
// Match only strict description-list starts:
|
|
340
|
+
// 1) explicit line-break marker after term line (` ` or `\`) + newline
|
|
341
|
+
// 2) term-only first line (optionally with valid attrs only), expecting next block as description
|
|
342
|
+
const { rest: afterAttrs } = consumeLeadingValidAttrBlocks(afterStrong)
|
|
343
|
+
const isMatch = hasExplicitLineBreakMarker(afterStrong) || /^\s*$/.test(afterAttrs)
|
|
224
344
|
|
|
225
345
|
return { isMatch, afterStrong }
|
|
226
346
|
}
|
|
@@ -236,6 +356,7 @@ const convertBulletListToDL = (tokens, listStart, listEnd, opt, itemRanges = nul
|
|
|
236
356
|
const dlOpen = new tokens[listStart].constructor('dl_open', 'dl', 1)
|
|
237
357
|
dlOpen.level = listLevel
|
|
238
358
|
dlOpen.block = true
|
|
359
|
+
copyMap(dlOpen, tokens[listStart])
|
|
239
360
|
|
|
240
361
|
// Copy attributes from bullet_list_open (e.g., {.class} from markdown-it-attrs)
|
|
241
362
|
if (tokens[listStart].attrs && tokens[listStart].attrs.length > 0) {
|
|
@@ -295,6 +416,7 @@ const convertBulletListToDL = (tokens, listStart, listEnd, opt, itemRanges = nul
|
|
|
295
416
|
const dlClose = new tokens[listStart].constructor('dl_close', 'dl', -1)
|
|
296
417
|
dlClose.level = listLevel
|
|
297
418
|
dlClose.block = true
|
|
419
|
+
copyMap(dlClose, tokens[listEnd])
|
|
298
420
|
newTokens.push(dlClose)
|
|
299
421
|
|
|
300
422
|
// Replace tokens
|
|
@@ -313,14 +435,10 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
313
435
|
const listItemToken = tokens[itemStart]
|
|
314
436
|
let dtAttrs = listItemToken.attrs ? [...listItemToken.attrs] : null
|
|
315
437
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
firstPara = i
|
|
321
|
-
break
|
|
322
|
-
}
|
|
323
|
-
}
|
|
438
|
+
const firstChild = findFirstDirectChildInListItem(tokens, itemStart, itemEnd)
|
|
439
|
+
const firstPara = firstChild !== -1 && tokens[firstChild].type === 'paragraph_open'
|
|
440
|
+
? firstChild
|
|
441
|
+
: -1
|
|
324
442
|
|
|
325
443
|
if (firstPara === -1) return { tokens: result, listAttrs }
|
|
326
444
|
|
|
@@ -335,24 +453,19 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
335
453
|
const match = content.match(/^\*\*(.*?)\*\*(.*)/s) // s flag for including newlines
|
|
336
454
|
|
|
337
455
|
if (match) {
|
|
338
|
-
|
|
456
|
+
// Trim to avoid leading space when **Term** starts with whitespace (e.g. "** *term*")
|
|
457
|
+
term = match[1].trim()
|
|
339
458
|
let afterStrong = match[2]
|
|
340
459
|
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
const
|
|
344
|
-
if (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const parsedAttrs = parseAttrString(attrString)
|
|
348
|
-
if (parsedAttrs.length > 0) {
|
|
349
|
-
if (!dtAttrs) {
|
|
350
|
-
dtAttrs = []
|
|
351
|
-
}
|
|
352
|
-
dtAttrs.push(...parsedAttrs)
|
|
460
|
+
// Pattern A: one or more leading attr blocks right after **Term**.
|
|
461
|
+
// These map to <dt> like markdown-it-attrs trailing attrs on a block line.
|
|
462
|
+
const leadingAttrs = consumeLeadingValidAttrBlocks(afterStrong)
|
|
463
|
+
if (leadingAttrs.attrs.length > 0) {
|
|
464
|
+
if (!dtAttrs) {
|
|
465
|
+
dtAttrs = []
|
|
353
466
|
}
|
|
354
|
-
|
|
355
|
-
afterStrong =
|
|
467
|
+
dtAttrs.push(...leadingAttrs.attrs)
|
|
468
|
+
afterStrong = leadingAttrs.rest
|
|
356
469
|
}
|
|
357
470
|
|
|
358
471
|
// Pattern B: {.attrs} on last line (e.g., "Description\n{.attrs}")
|
|
@@ -365,13 +478,13 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
365
478
|
const parsedAttrs = parseAttrString(attrString)
|
|
366
479
|
if (parsedAttrs.length > 0) {
|
|
367
480
|
listAttrs.push(...parsedAttrs)
|
|
481
|
+
// Remove {.attrs} line from afterStrong
|
|
482
|
+
afterStrong = afterStrong.replace(/\n\s*\{[^}]+\}\s*$/, '')
|
|
368
483
|
}
|
|
369
|
-
// Remove {.attrs} line from afterStrong
|
|
370
|
-
afterStrong = afterStrong.replace(/\n\s*\{[^}]+\}\s*$/, '')
|
|
371
484
|
}
|
|
372
485
|
|
|
373
|
-
// Clean up afterStrong: remove leading spaces/
|
|
374
|
-
let cleaned = afterStrong.replace(
|
|
486
|
+
// Clean up afterStrong: remove leading spaces/backslash, then trim each line
|
|
487
|
+
let cleaned = afterStrong.replace(/^\s+/, '').replace(/^\\/, '')
|
|
375
488
|
|
|
376
489
|
// Remove leading whitespace from each line (remove list indent)
|
|
377
490
|
cleaned = cleaned.split('\n').map(line => line.replace(/^\s+/, '')).join('\n').trim()
|
|
@@ -386,7 +499,10 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
386
499
|
const divOpen = new tokens[firstPara].constructor('div_open', 'div', 1)
|
|
387
500
|
divOpen.level = parentLevel + 1
|
|
388
501
|
divOpen.block = true
|
|
389
|
-
|
|
502
|
+
copyMap(divOpen, tokens[firstPara])
|
|
503
|
+
const divClass = typeof opt.descriptionListDivClass === 'string'
|
|
504
|
+
? opt.descriptionListDivClass.trim()
|
|
505
|
+
: ''
|
|
390
506
|
if (divClass) {
|
|
391
507
|
divOpen.attrs = [['class', divClass]]
|
|
392
508
|
}
|
|
@@ -397,6 +513,7 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
397
513
|
const dtOpen = new tokens[firstPara].constructor('dt_open', 'dt', 1)
|
|
398
514
|
dtOpen.level = parentLevel + 1
|
|
399
515
|
dtOpen.block = true
|
|
516
|
+
copyMap(dtOpen, tokens[firstPara])
|
|
400
517
|
// Add collected attributes to dt_open
|
|
401
518
|
if (dtAttrs && dtAttrs.length > 0) {
|
|
402
519
|
dtOpen.attrs = dtAttrs
|
|
@@ -414,20 +531,22 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
414
531
|
const dtClose = new tokens[firstPara].constructor('dt_close', 'dt', -1)
|
|
415
532
|
dtClose.level = parentLevel + 1
|
|
416
533
|
dtClose.block = true
|
|
534
|
+
copyMap(dtClose, tokens[firstPara])
|
|
417
535
|
result.push(dtClose)
|
|
418
536
|
|
|
419
537
|
// Create dd_open token
|
|
420
538
|
const ddOpen = new tokens[firstPara].constructor('dd_open', 'dd', 1)
|
|
421
539
|
ddOpen.level = parentLevel + 1
|
|
422
540
|
ddOpen.block = true
|
|
541
|
+
copyMap(ddOpen, tokens[firstPara])
|
|
423
542
|
result.push(ddOpen)
|
|
424
543
|
|
|
425
544
|
// First paragraph in dd (if description exists)
|
|
426
|
-
let hasFirstParagraph = false
|
|
427
545
|
if (descStart.trim()) {
|
|
428
546
|
const pOpen = new tokens[firstPara].constructor('paragraph_open', 'p', 1)
|
|
429
547
|
pOpen.level = parentLevel + 2
|
|
430
548
|
pOpen.block = true // IMPORTANT: Enable block mode for proper newline rendering
|
|
549
|
+
copyMap(pOpen, tokens[firstPara])
|
|
431
550
|
result.push(pOpen)
|
|
432
551
|
|
|
433
552
|
const pInline = new tokens[firstPara].constructor('inline', '', 0)
|
|
@@ -442,9 +561,9 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
442
561
|
const pClose = new tokens[firstPara].constructor('paragraph_close', 'p', -1)
|
|
443
562
|
pClose.level = parentLevel + 2
|
|
444
563
|
pClose.block = true // IMPORTANT: Enable block mode for proper newline rendering
|
|
564
|
+
copyMap(pClose, tokens[firstPara])
|
|
445
565
|
result.push(pClose)
|
|
446
566
|
|
|
447
|
-
hasFirstParagraph = true
|
|
448
567
|
}
|
|
449
568
|
|
|
450
569
|
// Add remaining content in dd (paragraphs, lists, etc.)
|
|
@@ -454,8 +573,6 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
454
573
|
|
|
455
574
|
// Handle paragraph
|
|
456
575
|
if (token.type === 'paragraph_open') {
|
|
457
|
-
hasFirstParagraph = true
|
|
458
|
-
|
|
459
576
|
result.push(tokens[i]) // paragraph_open
|
|
460
577
|
result.push(tokens[i + 1]) // inline
|
|
461
578
|
result.push(tokens[i + 2]) // paragraph_close
|
|
@@ -491,6 +608,7 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
491
608
|
const ddClose = new tokens[firstPara].constructor('dd_close', 'dd', -1)
|
|
492
609
|
ddClose.level = parentLevel + 1
|
|
493
610
|
ddClose.block = true
|
|
611
|
+
copyMap(ddClose, tokens[firstPara])
|
|
494
612
|
result.push(ddClose)
|
|
495
613
|
|
|
496
614
|
// Create div_close if descriptionListWithDiv is enabled
|
|
@@ -498,6 +616,7 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
|
|
|
498
616
|
const divClose = new tokens[firstPara].constructor('div_close', 'div', -1)
|
|
499
617
|
divClose.level = parentLevel + 1
|
|
500
618
|
divClose.block = true
|
|
619
|
+
copyMap(divClose, tokens[firstPara])
|
|
501
620
|
result.push(divClose)
|
|
502
621
|
}
|
|
503
622
|
|
package/src/phase1-analyze.js
CHANGED
|
@@ -154,6 +154,9 @@ function analyzeList(tokens, startIndex, opt, closeMap) {
|
|
|
154
154
|
if (!isLoose) {
|
|
155
155
|
hideFirstParagraphsForTightList(tokens, items, level)
|
|
156
156
|
}
|
|
157
|
+
|
|
158
|
+
// Cache loose/tight state for later phases (mapless flattening fallback).
|
|
159
|
+
listToken._isLoose = isLoose
|
|
157
160
|
|
|
158
161
|
// Extract marker info (for both bullet_list and ordered_list)
|
|
159
162
|
const markerInfo = extractMarkerInfo(tokens, startIndex, endIndex, opt)
|
|
@@ -168,6 +171,13 @@ function analyzeList(tokens, startIndex, opt, closeMap) {
|
|
|
168
171
|
// Convert even loose lists if markers are consistent
|
|
169
172
|
const shouldConvert = originalType === 'bullet_list_open' &&
|
|
170
173
|
shouldConvertToOrdered(originalType, markerInfo, opt)
|
|
174
|
+
|
|
175
|
+
if (markerInfo) {
|
|
176
|
+
listToken._markerInfo = markerInfo
|
|
177
|
+
}
|
|
178
|
+
if (originalType === 'bullet_list_open') {
|
|
179
|
+
listToken._shouldConvert = shouldConvert
|
|
180
|
+
}
|
|
171
181
|
|
|
172
182
|
return {
|
|
173
183
|
startIndex,
|
|
@@ -259,6 +269,10 @@ function analyzeListItem(tokens, startIndex, endIndex, opt, closeMap) {
|
|
|
259
269
|
markerInfo = detectMarkerType(lastInlineContent)
|
|
260
270
|
}
|
|
261
271
|
content = lastInlineContent
|
|
272
|
+
|
|
273
|
+
if (tokens[startIndex]) {
|
|
274
|
+
tokens[startIndex]._firstParagraphIsLoose = firstParagraphIsLoose
|
|
275
|
+
}
|
|
262
276
|
|
|
263
277
|
return {
|
|
264
278
|
startIndex,
|
|
@@ -277,9 +291,15 @@ function analyzeListItem(tokens, startIndex, endIndex, opt, closeMap) {
|
|
|
277
291
|
function extractMarkerInfo(tokens, startIndex, endIndex, opt) {
|
|
278
292
|
const listToken = tokens[startIndex]
|
|
279
293
|
const markers = []
|
|
294
|
+
const literalMarkerInfo = listToken._literalMarkerInfo || null
|
|
280
295
|
|
|
281
296
|
// For ordered_list, get numbers from list_item_open's info
|
|
282
297
|
if (listToken.type === 'ordered_list_open') {
|
|
298
|
+
const literalMarkers = Array.isArray(literalMarkerInfo?.markers) ? literalMarkerInfo.markers : null
|
|
299
|
+
const literalType = literalMarkerInfo?.type || 'decimal'
|
|
300
|
+
const literalPrefix = literalMarkers?.[0]?.prefix ?? ''
|
|
301
|
+
const literalSuffix = literalMarkers?.[0]?.suffix ?? (listToken.markup || '.')
|
|
302
|
+
let listItemIndex = 0
|
|
283
303
|
for (let i = startIndex + 1; i < endIndex; i++) {
|
|
284
304
|
const token = tokens[i]
|
|
285
305
|
|
|
@@ -287,14 +307,25 @@ function extractMarkerInfo(tokens, startIndex, endIndex, opt) {
|
|
|
287
307
|
// Get number from info
|
|
288
308
|
const itemNumber = token.info ? parseInt(token.info, 10) : markers.length + 1
|
|
289
309
|
const markup = token.markup || listToken.markup || '.'
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
number
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
310
|
+
if (literalMarkers && literalMarkers[listItemIndex]) {
|
|
311
|
+
const marker = { ...literalMarkers[listItemIndex] }
|
|
312
|
+
if (typeof marker.originalNumber !== 'number') {
|
|
313
|
+
marker.originalNumber = itemNumber
|
|
314
|
+
}
|
|
315
|
+
if (typeof marker.number !== 'number') {
|
|
316
|
+
marker.number = itemNumber
|
|
317
|
+
}
|
|
318
|
+
markers.push(marker)
|
|
319
|
+
} else {
|
|
320
|
+
markers.push({
|
|
321
|
+
number: itemNumber,
|
|
322
|
+
originalNumber: itemNumber,
|
|
323
|
+
prefix: literalPrefix,
|
|
324
|
+
suffix: literalSuffix || markup,
|
|
325
|
+
type: literalType
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
listItemIndex++
|
|
298
329
|
}
|
|
299
330
|
}
|
|
300
331
|
} else {
|