@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.3.0 → 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 CHANGED
@@ -71,6 +71,7 @@ After the marker separator, an ASCII space is normally expected. For `fullwidth`
71
71
 
72
72
  - Marker type detection is deterministic: gather all markers at a nesting level, match them against the canonical definitions, keep the type that explains the most items while preserving numeric continuity, and use the order in `listTypes.json` only as a final tiebreaker.
73
73
  - Flattening: A pattern like `- 1.` is represented by the default `ul > li > ol` nesting structure in markdown-it, but this plugin simplifies it to a single `ol` by default to match the representation of other markers.
74
+ - With `markdown-it-attrs`, attr blocks follow that plugin's nearest-block behavior. This plugin does not reassign list attrs after flattening.
74
75
 
75
76
  ### Source map behavior
76
77
 
@@ -90,14 +91,22 @@ You can customize the conversion using options.
90
91
  - `omitMarkerMetadata` (boolean) — If `true`, omit the `data-marker-prefix` / `data-marker-suffix` attributes.
91
92
  - `addMarkerStyleToClass` (boolean) — When `true`, append suffix-style information to the generated class name (e.g. `ol-decimal-with-round-round`). When `false` (default) the class stays as `ol-decimal`.
92
93
  - `enableLiteralNumberingFix` (boolean) — Enable literal nested list recovery (for example, nested lists starting with 2 or greater). This is opt-in; it only applies inside list items, evaluates indentation relative to the parent list marker (marker width + 0–3 spaces), and does not convert code blocks (indent >= marker width + 4).
94
+ - Compatibility note: switching the default from `false` to `true` changes rendered HTML by design. On the current test corpus (`394` markdown cases), `18` cases change.
95
+ - Changed files in that comparison: `examples-default-14-repeated-numbers.txt` (`7`), `examples-option-literal-numbering-attrs.txt` (`1`), `examples-option-literal-numbering-fix-disabled.txt` (`2`), `examples-option-literal-numbering-fix.txt` (`3`), `examples-option-literal-numbering-indent.txt` (`4`).
96
+ - Recommendation: keep the default as `false` for patch/minor releases; if changing the default to `true`, treat it as a breaking change and release a major version.
93
97
 
94
98
  ## Description lists conversion behavior
95
99
 
96
- When the `descriptionList` option is enabled the plugin converts specially formatted bullet lists into HTML description lists (`<dl>`).
100
+ When `descriptionList` or `descriptionListWithDiv` is enabled, the plugin converts specially formatted bullet lists into HTML description lists (`<dl>`).
97
101
 
98
102
  - Each list item must start with a `**Term**` line.
103
+ - The `**Term**` line must be the first direct block inside that list item (term-like text inside nested sub-lists does not trigger conversion).
99
104
  - If the description continues on the next line without a blank line, the Term line must end with two ASCII spaces (a Markdown line-break) or a backslash `\`. Inline `{.attrs}` immediately after the term are allowed.
100
105
  - If the Term line is followed by a blank line, the description can start in the next paragraph or list (line-break control characters are optional).
106
+ - Same-line descriptions such as `- **Term** Description` are not converted.
107
+ - Same-line backslash forms such as `- **Term**\ Description` are also not converted (the hard break marker must be followed by an actual newline).
108
+ - Term-line attrs can be written in multiple adjacent blocks (`**Term** {.a} {#id} {data-x=1}`) and are merged onto `<dt>`.
109
+ - Attr-like braces are kept as description text only when your plugin chain does not parse them as attributes. With `markdown-it-attrs`, forms like `{foo}` may be treated as boolean attributes.
101
110
 
102
111
  In the conversion the `**Term**` line becomes a `<dt>` and the subsequent lines become the corresponding `<dd>`.
103
112
 
@@ -106,7 +115,7 @@ Note: Text descriptions are wrapped in `<p>` elements; additional paragraphs and
106
115
  ### Description list options
107
116
 
108
117
  - `descriptionList` (boolean) — Enable conversion of `**Term**` list patterns into `<dl>` description lists.
109
- - `descriptionListWithDiv` (boolean) — Wrap `<dt>/<dd>` pairs in a `<div>` when enabled.
118
+ - `descriptionListWithDiv` (boolean) — Wrap `<dt>/<dd>` pairs in a `<div>` when enabled. This option also enables description-list conversion even when `descriptionList` is `false`.
110
119
  - `descriptionListDivClass` (string) — Class applied to the wrapper `<div>` when `descriptionListWithDiv` is enabled (empty string disables the class).
111
120
 
112
121
  ## Examples: Ordered Lists
package/index.js CHANGED
@@ -5,7 +5,6 @@ import { convertLists } from './src/phase2-convert.js'
5
5
  import { addAttributes } from './src/phase3-attributes.js'
6
6
  import { processHtmlBlocks } from './src/phase4-html-blocks.js'
7
7
  import { generateSpans } from './src/phase5-spans.js'
8
- import { moveNestedListAttributes } from './src/phase6-attrs-migration.js'
9
8
  import { normalizeLiteralOrderedLists } from './src/preprocess-literal-lists.js'
10
9
 
11
10
  const mditNumberingUl = (md, option) => {
@@ -73,7 +72,17 @@ const mditNumberingUl = (md, option) => {
73
72
 
74
73
  // ===== PHASE 4: HTML Block Processing =====
75
74
  // Remove indents from HTML blocks in lists and normalize line breaks
76
- processHtmlBlocks(state)
75
+ let hasNestedHtmlBlock = false
76
+ for (let i = 0; i < tokens.length; i++) {
77
+ const token = tokens[i]
78
+ if (token.type === 'html_block' && token.level > 0) {
79
+ hasNestedHtmlBlock = true
80
+ break
81
+ }
82
+ }
83
+ if (hasNestedHtmlBlock) {
84
+ processHtmlBlocks(state)
85
+ }
77
86
 
78
87
  // ===== PHASE 5: Span Generation =====
79
88
  // Generate marker spans in alwaysMarkerSpan mode
@@ -85,16 +94,6 @@ const mditNumberingUl = (md, option) => {
85
94
  md.core.ruler.before('inline', 'numbering_dl_parser', dlProcessor)
86
95
  md.core.ruler.after('numbering_dl_parser', 'numbering_ul_phases', listProcessor)
87
96
 
88
- if (!opt.unremoveUlNest) {
89
- // Move nested list attributes only when flattening is enabled
90
- const nestedListAttrProcessor = (state) => {
91
- moveNestedListAttributes(state.tokens)
92
- return true
93
- }
94
-
95
- addRuleAfter(md.core.ruler, 'curly_attributes', 'numbering_ul_nested_attrs', nestedListAttrProcessor)
96
- }
97
-
98
97
  // Description list: Move paragraph attributes to dl and add custom renderers
99
98
  if (opt.descriptionList || opt.descriptionListWithDiv) {
100
99
  // Move paragraph attributes to dl (after inline and any attribute plugins)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peaceroad/markdown-it-numbering-ul-regarded-as-ol",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
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.6.2",
30
+ "@peaceroad/markdown-it-strong-ja": "^0.7.2",
31
31
  "markdown-it": "^14.1.0",
32
32
  "markdown-it-attrs": "^4.3.1",
33
33
  "markdown-it-deflist": "^3.0.0"
@@ -8,34 +8,141 @@ 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 classMatches = attrStr.match(/\.[\w-]+/g)
14
- const idMatch = attrStr.match(/#([\w-]+)/)
15
- const dataMatches = attrStr.match(/([\w-]+)="([^"]+)"/g)
16
-
17
- // Collect all classes
18
- if (classMatches) {
19
- const classes = classMatches.map(c => c.substring(1)).join(' ')
20
- attrs.push(['class', classes])
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
- // Add id
24
- if (idMatch) {
25
- attrs.push(['id', idMatch[1]])
65
+
66
+ if (classes.length > 0) {
67
+ attrs.push(['class', classes.join(' ')])
26
68
  }
27
-
28
- // Add data attributes
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
+
39
146
  const copyMap = (target, source) => {
40
147
  if (!target || !source || !Array.isArray(source.map)) {
41
148
  return
@@ -47,7 +154,6 @@ const copyMap = (target, source) => {
47
154
  * Process description list patterns in tokens
48
155
  * @param {Array} tokens - Token array
49
156
  * @param {Object} opt - Options object
50
- ```
51
157
  */
52
158
  export const processDescriptionList = (tokens, opt) => {
53
159
  if (!opt.descriptionList && !opt.descriptionListWithDiv) {
@@ -61,7 +167,7 @@ export const processDescriptionList = (tokens, opt) => {
61
167
  if (tokens[i].type === 'bullet_list_open') {
62
168
  const listEnd = findListEnd(tokens, i)
63
169
  const dlCheck = checkAndConvertToDL(tokens, i, listEnd, opt)
64
- i = dlCheck.nextIndex
170
+ i = typeof dlCheck?.nextIndex === 'number' ? dlCheck.nextIndex : listEnd + 1
65
171
  } else {
66
172
  i++
67
173
  }
@@ -108,6 +214,21 @@ const collectListItemRanges = (tokens, listStart, listEnd) => {
108
214
  return ranges
109
215
  }
110
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
+
111
232
  /**
112
233
  * Find matching dl_close token
113
234
  */
@@ -137,59 +258,57 @@ const checkAndConvertToDL = (tokens, listStart, listEnd, opt) => {
137
258
  const itemStart = range.open
138
259
  const itemEnd = range.close
139
260
 
140
- // Find first paragraph
141
- let firstPara = -1
142
- for (let j = itemStart + 1; j < itemEnd; j++) {
143
- if (tokens[j].type === 'paragraph_open') {
144
- firstPara = j
145
- break
146
- }
147
- }
148
-
149
- if (firstPara !== -1) {
261
+ const firstChild = findFirstDirectChildInListItem(tokens, itemStart, itemEnd)
262
+ if (firstChild !== -1 && tokens[firstChild].type === 'paragraph_open') {
263
+ const firstPara = firstChild
150
264
  const inlineToken = tokens[firstPara + 1]
151
- if (inlineToken && inlineToken.type === 'inline') {
152
- const dlCheck = isDLPattern(inlineToken.content)
153
- if (dlCheck.isMatch) {
154
- // Check if there's a description
155
- let hasDescription = false
156
-
157
- const afterStrong = dlCheck.afterStrong
158
-
159
- // Pattern 1: **Term** description (2+ spaces, including newlines)
160
- // Pattern 2: **Term**\ description (backslash escape)
161
- if (/^\s{2,}/.test(afterStrong) || /^\\/.test(afterStrong)) {
162
- // Remove leading space/backslash and check remaining text
163
- const cleaned = afterStrong.replace(/^\s+/, '').replace(/^\\/, '').trim()
164
- if (cleaned) {
165
- hasDescription = true
166
- }
167
- }
168
-
169
- // Pattern 4: Description in next paragraph (only **Term** in first para)
170
- if (!hasDescription) {
171
- // Check for additional paragraphs/lists
172
- for (let k = firstPara + 3; k < itemEnd; k++) {
173
- if (tokens[k].type === 'paragraph_open' ||
174
- tokens[k].type === 'bullet_list_open' ||
175
- tokens[k].type === 'ordered_list_open') {
176
- hasDescription = true
177
- break
178
- }
179
- }
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
180
293
  }
181
-
182
- // If no description, not a DL item
183
- if (!hasDescription) {
184
- return false
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
185
299
  }
186
-
187
- hasAnyDLItem = true
188
- } else {
189
- // Not all items are DL pattern - not a description list
190
- return { nextIndex: listEnd + 1 }
191
300
  }
192
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 }
193
312
  }
194
313
  }
195
314
 
@@ -217,14 +336,11 @@ const isDLPattern = (content) => {
217
336
 
218
337
  const afterStrong = match[2] // Text after closing **
219
338
 
220
- // Pattern 1: **Term** description (2+ spaces, including newlines)
221
- // Pattern 2: **Term**\ description (backslash escape)
222
- // Pattern 4: **Term** only (no content after)
223
- // Pattern 5: **Term** {.attrs} (markdown-it-attrs syntax, optionally with content after)
224
- const isMatch = /^\s{2,}/.test(afterStrong) ||
225
- /^\\/.test(afterStrong) ||
226
- /^\s*$/.test(afterStrong) ||
227
- /^\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)
228
344
 
229
345
  return { isMatch, afterStrong }
230
346
  }
@@ -319,14 +435,10 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
319
435
  const listItemToken = tokens[itemStart]
320
436
  let dtAttrs = listItemToken.attrs ? [...listItemToken.attrs] : null
321
437
 
322
- // Find first paragraph
323
- let firstPara = -1
324
- for (let i = itemStart + 1; i < itemEnd; i++) {
325
- if (tokens[i].type === 'paragraph_open') {
326
- firstPara = i
327
- break
328
- }
329
- }
438
+ const firstChild = findFirstDirectChildInListItem(tokens, itemStart, itemEnd)
439
+ const firstPara = firstChild !== -1 && tokens[firstChild].type === 'paragraph_open'
440
+ ? firstChild
441
+ : -1
330
442
 
331
443
  if (firstPara === -1) return { tokens: result, listAttrs }
332
444
 
@@ -345,21 +457,15 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
345
457
  term = match[1].trim()
346
458
  let afterStrong = match[2]
347
459
 
348
- // Extract {.attrs} from afterStrong if present (markdown-it-attrs hasn't processed yet)
349
- // Pattern A: Inline {.attrs} immediately after **Term** like **Term** {.class}
350
- const inlineAttrsMatch = afterStrong.match(/^\s*\{([^}]+)\}/)
351
- if (inlineAttrsMatch) {
352
- // Parse attributes manually
353
- const attrString = inlineAttrsMatch[1]
354
- const parsedAttrs = parseAttrString(attrString)
355
- if (parsedAttrs.length > 0) {
356
- if (!dtAttrs) {
357
- dtAttrs = []
358
- }
359
- 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 = []
360
466
  }
361
- // Remove {.attrs} from afterStrong (including trailing newline if attrs-only line)
362
- afterStrong = afterStrong.replace(/^\s*\{[^}]+\}\s*/, '')
467
+ dtAttrs.push(...leadingAttrs.attrs)
468
+ afterStrong = leadingAttrs.rest
363
469
  }
364
470
 
365
471
  // Pattern B: {.attrs} on last line (e.g., "Description\n{.attrs}")
@@ -372,9 +478,9 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
372
478
  const parsedAttrs = parseAttrString(attrString)
373
479
  if (parsedAttrs.length > 0) {
374
480
  listAttrs.push(...parsedAttrs)
481
+ // Remove {.attrs} line from afterStrong
482
+ afterStrong = afterStrong.replace(/\n\s*\{[^}]+\}\s*$/, '')
375
483
  }
376
- // Remove {.attrs} line from afterStrong
377
- afterStrong = afterStrong.replace(/\n\s*\{[^}]+\}\s*$/, '')
378
484
  }
379
485
 
380
486
  // Clean up afterStrong: remove leading spaces/backslash, then trim each line
@@ -394,7 +500,9 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
394
500
  divOpen.level = parentLevel + 1
395
501
  divOpen.block = true
396
502
  copyMap(divOpen, tokens[firstPara])
397
- const divClass = typeof opt.descriptionListDivClass === 'string' ? opt.descriptionListDivClass : ''
503
+ const divClass = typeof opt.descriptionListDivClass === 'string'
504
+ ? opt.descriptionListDivClass.trim()
505
+ : ''
398
506
  if (divClass) {
399
507
  divOpen.attrs = [['class', divClass]]
400
508
  }
@@ -434,7 +542,6 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
434
542
  result.push(ddOpen)
435
543
 
436
544
  // First paragraph in dd (if description exists)
437
- let hasFirstParagraph = false
438
545
  if (descStart.trim()) {
439
546
  const pOpen = new tokens[firstPara].constructor('paragraph_open', 'p', 1)
440
547
  pOpen.level = parentLevel + 2
@@ -457,7 +564,6 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
457
564
  copyMap(pClose, tokens[firstPara])
458
565
  result.push(pClose)
459
566
 
460
- hasFirstParagraph = true
461
567
  }
462
568
 
463
569
  // Add remaining content in dd (paragraphs, lists, etc.)
@@ -467,8 +573,6 @@ const convertListItemToDtDd = (tokens, itemStart, itemEnd, parentLevel, opt) =>
467
573
 
468
574
  // Handle paragraph
469
575
  if (token.type === 'paragraph_open') {
470
- hasFirstParagraph = true
471
-
472
576
  result.push(tokens[i]) // paragraph_open
473
577
  result.push(tokens[i + 1]) // inline
474
578
  result.push(tokens[i + 2]) // paragraph_close