@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.
@@ -1,79 +1,79 @@
1
- // Phase 3: Attribute Addition
2
- // Add type, class, and data-* attributes to ordered lists
3
-
4
- import { getTypeAttributes } from './types-utility.js'
5
- import { findMatchingClose } from './list-helpers.js'
6
-
7
- /**
8
- * Add attributes to lists
9
- * @param {Array} tokens - Token array
10
- * @param {Array} listInfos - List information
11
- * @param {Object} opt - Options
12
- */
13
- export function addAttributes(tokens, listInfos, opt) {
14
- const listInfoMap = buildListInfoMap(listInfos)
15
-
16
- // Traverse token array and add attributes to ordered_list_open tokens
17
- // Token array may have been rebuilt in Phase2,
18
- // so don't use listInfo.startIndex, check token's _markerInfo/_listInfo
19
- for (let i = 0; i < tokens.length; i++) {
20
- const token = tokens[i]
21
-
22
- if (token.type === 'ordered_list_open') {
23
- // Find listInfo corresponding to this token
24
- // Check _markerInfo or _convertedFromBullet flag saved in Phase2
25
- if (token._markerInfo || token._convertedFromBullet) {
26
- addListAttributesForToken(tokens, token, i, opt)
27
- } else {
28
- // If originally ordered_list, find from listInfos
29
- const listInfo = listInfoMap.get(i)
30
- if (listInfo) {
31
- addListAttributesForToken(tokens, token, i, opt, listInfo)
32
- }
33
- }
34
- }
35
- }
36
-
37
- // Normalize value attributes of ordered_list generated by markdown-it
38
- // Remove unnecessary value attributes after Phase2 simplification
39
- // Normalize value attributes and marker metadata for all ordered lists
40
- normalizeAndConvertValueAttributes(tokens, opt)
41
- }
42
-
43
- /**
44
- * Build a lookup map for listInfos keyed by startIndex.
45
- */
46
- function buildListInfoMap(listInfos) {
47
- if (!Array.isArray(listInfos) || listInfos.length === 0) {
48
- return new Map()
49
- }
50
-
51
- const map = new Map()
52
- for (const info of listInfos) {
53
- if (!info || typeof info.startIndex !== 'number' || !info.markerInfo) {
54
- continue
55
- }
56
- if (!map.has(info.startIndex)) {
57
- map.set(info.startIndex, info)
58
- }
59
- }
60
- return map
61
- }
62
-
63
- /**
64
- * Add attributes to a single list token
65
- */
66
- function addListAttributesForToken(tokens, token, tokenIndex, opt, listInfo = null) {
67
- // Initialize attribute array
68
- if (!token.attrs) {
69
- token.attrs = []
70
- }
71
-
72
- // Get marker info
73
- const markerInfo = token._markerInfo || listInfo?.markerInfo
74
-
75
- if (!markerInfo) {
76
- // Default attributes for lists without markerInfo
1
+ // Phase 3: Attribute Addition
2
+ // Add type, class, and data-* attributes to ordered lists
3
+
4
+ import { getTypeAttributes } from './types-utility.js'
5
+ import { findMatchingClose } from './list-helpers.js'
6
+
7
+ /**
8
+ * Add attributes to lists
9
+ * @param {Array} tokens - Token array
10
+ * @param {Array} listInfos - List information
11
+ * @param {Object} opt - Options
12
+ */
13
+ export function addAttributes(tokens, listInfos, opt) {
14
+ const listInfoMap = buildListInfoMap(listInfos)
15
+
16
+ // Traverse token array and add attributes to ordered_list_open tokens
17
+ // Token array may have been rebuilt in Phase2,
18
+ // so don't use listInfo.startIndex, check token's _markerInfo/_listInfo
19
+ for (let i = 0; i < tokens.length; i++) {
20
+ const token = tokens[i]
21
+
22
+ if (token.type === 'ordered_list_open') {
23
+ // Find listInfo corresponding to this token
24
+ // Check _markerInfo or _convertedFromBullet flag saved in Phase2
25
+ if (token._markerInfo || token._convertedFromBullet) {
26
+ addListAttributesForToken(tokens, token, i, opt)
27
+ } else {
28
+ // If originally ordered_list, find from listInfos
29
+ const listInfo = listInfoMap.get(i)
30
+ if (listInfo) {
31
+ addListAttributesForToken(tokens, token, i, opt, listInfo)
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ // Normalize value attributes of ordered_list generated by markdown-it
38
+ // Remove unnecessary value attributes after Phase2 simplification
39
+ // Normalize value attributes and marker metadata for all ordered lists
40
+ normalizeAndConvertValueAttributes(tokens, opt)
41
+ }
42
+
43
+ /**
44
+ * Build a lookup map for listInfos keyed by startIndex.
45
+ */
46
+ function buildListInfoMap(listInfos) {
47
+ if (!Array.isArray(listInfos) || listInfos.length === 0) {
48
+ return new Map()
49
+ }
50
+
51
+ const map = new Map()
52
+ for (const info of listInfos) {
53
+ if (!info || typeof info.startIndex !== 'number' || !info.markerInfo) {
54
+ continue
55
+ }
56
+ if (!map.has(info.startIndex)) {
57
+ map.set(info.startIndex, info)
58
+ }
59
+ }
60
+ return map
61
+ }
62
+
63
+ /**
64
+ * Add attributes to a single list token
65
+ */
66
+ function addListAttributesForToken(tokens, token, tokenIndex, opt, listInfo = null) {
67
+ // Initialize attribute array
68
+ if (!token.attrs) {
69
+ token.attrs = []
70
+ }
71
+
72
+ // Get marker info
73
+ const markerInfo = token._markerInfo || listInfo?.markerInfo
74
+
75
+ if (!markerInfo) {
76
+ // Default attributes for lists without markerInfo
77
77
  if (opt.useCounterStyle) {
78
78
  // Do not add type attribute; add class so user CSS/@counter-style can target
79
79
  addAttr(token, 'class', 'ol-decimal')
@@ -91,56 +91,69 @@ function addListAttributesForToken(tokens, token, tokenIndex, opt, listInfo = nu
91
91
  }
92
92
  return
93
93
  }
94
-
95
- // Attributes according to marker type
96
- // Pass first marker's prefix/suffix info to determine class name
97
- const firstMarker = markerInfo.markers[0]
98
- const typeAttrs = getTypeAttributes(markerInfo.type, firstMarker)
99
-
100
- // Reset attribute array
101
- token.attrs = []
102
- // Add attributes in expected order
103
- // Standard marker: type -> start -> class -> data-marker-*
104
- // Custom marker or alwaysMarkerSpan: role -> start -> class -> data-marker-* (no type attribute)
105
-
106
- // 1. type attribute or role attribute (add first)
107
- // Always use role="list" for alwaysMarkerSpan
108
- if (opt.useCounterStyle) {
109
- // When user chooses @counter-style, we avoid role and inline styles.
110
- // Still add class so users can target with CSS (e.g., counter-reset/counter-increment or @counter-style usage).
111
- // Do not add type attribute (counter-style will handle visuals)
112
- } else if (typeAttrs.type && !opt.alwaysMarkerSpan) {
113
- addAttr(token, 'type', typeAttrs.type)
114
- } else {
115
- addAttr(token, 'role', 'list')
116
- if (opt.hasListStyleNone) {
117
- addAttr(token, 'style', 'list-style: none;')
118
- }
119
- }
120
-
121
- // 2. Add start attribute when numbering doesn't begin at 1
122
- const firstNumber = markerInfo.markers[0]?.number
123
- if (firstNumber !== undefined && firstNumber !== 1) {
124
- addAttr(token, 'start', String(firstNumber))
125
- }
126
-
127
- // 3. Add class attribute
128
- if (typeAttrs.class) {
129
- // Merge or add class; preserve existing classes and append
130
- const existing = token.attrs.find(a => a[0] === 'class')
131
- if (existing) {
132
- existing[1] = (existing[1] + ' ' + typeAttrs.class).trim()
133
- } else {
134
- addAttr(token, 'class', typeAttrs.class)
135
- }
136
- }
137
-
138
- // If user requested @counter-style, add a helper class to identify lists
139
- if (opt.useCounterStyle) {
140
- // Do not add helper class when using counter-style; user CSS should target generated ol-* classes.
141
- }
142
-
143
- // 4. data-marker-prefix/suffix
94
+
95
+ // Attributes according to marker type
96
+ // Pass first marker's prefix/suffix info to determine class name
97
+ const firstMarker = markerInfo.markers[0]
98
+ const typeAttrs = getTypeAttributes(markerInfo.type, firstMarker, opt)
99
+
100
+ // Reset attribute array
101
+ token.attrs = []
102
+ // Add attributes in expected order
103
+ // Standard marker: type -> start -> class -> data-marker-*
104
+ // Custom marker or alwaysMarkerSpan: role -> start -> class -> data-marker-* (no type attribute)
105
+
106
+ // 1. type attribute or role attribute (add first)
107
+ // Always use role="list" for alwaysMarkerSpan
108
+ if (opt.useCounterStyle) {
109
+ // When user chooses @counter-style, we avoid role and inline styles.
110
+ // Still add class so users can target with CSS (e.g., counter-reset/counter-increment or @counter-style usage).
111
+ // Do not add type attribute (counter-style will handle visuals)
112
+ } else if (typeAttrs.type && !opt.alwaysMarkerSpan) {
113
+ addAttr(token, 'type', typeAttrs.type)
114
+ } else {
115
+ addAttr(token, 'role', 'list')
116
+ if (opt.hasListStyleNone) {
117
+ addAttr(token, 'style', 'list-style: none;')
118
+ }
119
+ }
120
+
121
+ // 2. Add start attribute when numbering doesn't begin at 1
122
+ let startOverride = token._startOverride
123
+ if (startOverride !== undefined && startOverride !== null) {
124
+ const parsed = typeof startOverride === 'number' ? startOverride : parseInt(startOverride, 10)
125
+ startOverride = Number.isNaN(parsed) ? undefined : parsed
126
+ } else {
127
+ startOverride = undefined
128
+ }
129
+ const firstNumber = startOverride ?? (markerInfo.markers[0]?.originalNumber ?? markerInfo.markers[0]?.number)
130
+ if (firstNumber !== undefined && firstNumber !== 1) {
131
+ addAttr(token, 'start', String(firstNumber))
132
+ } else if (token.attrs) {
133
+ const startIdx = token.attrs.findIndex(attr => attr[0] === 'start')
134
+ if (startIdx >= 0) {
135
+ token.attrs.splice(startIdx, 1)
136
+ if (token.attrs.length === 0) token.attrs = null
137
+ }
138
+ }
139
+
140
+ // 3. Add class attribute
141
+ if (typeAttrs.class) {
142
+ // Merge or add class; preserve existing classes and append
143
+ const existing = token.attrs.find(a => a[0] === 'class')
144
+ if (existing) {
145
+ existing[1] = (existing[1] + ' ' + typeAttrs.class).trim()
146
+ } else {
147
+ addAttr(token, 'class', typeAttrs.class)
148
+ }
149
+ }
150
+
151
+ // If user requested @counter-style, add a helper class to identify lists
152
+ if (opt.useCounterStyle) {
153
+ // Do not add helper class when using counter-style; user CSS should target generated ol-* classes.
154
+ }
155
+
156
+ // 4. data-marker-prefix/suffix
144
157
  if (!opt.omitMarkerMetadata) {
145
158
  if (markerInfo.markers[0].prefix) {
146
159
  addAttr(token, 'data-marker-prefix', markerInfo.markers[0].prefix)
@@ -151,121 +164,124 @@ function addListAttributesForToken(tokens, token, tokenIndex, opt, listInfo = nu
151
164
  addAttr(token, 'data-marker-suffix', suffix)
152
165
  }
153
166
  }
154
-
155
- // Add value attribute to list items (for non-consecutive numbers)
156
- addListItemValues(tokens, tokenIndex, markerInfo)
157
- }
158
-
159
- /**
160
- * Add `value` attributes to list items when numbering is non-consecutive.
161
- * @param {Array} tokens - Array of tokens
162
- * @param {number} listOpenIndex - Index of the corresponding `ordered_list_open` token
163
- * @param {Object} markerInfo - Marker information object
164
- */
165
- function addListItemValues(tokens, listOpenIndex, markerInfo) {
166
- if (!markerInfo || !markerInfo.markers) {
167
- return
168
- }
169
-
170
- // Find end position of this ordered_list
171
- const listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
172
- if (listCloseIndex === -1) return
173
-
174
- const markers = markerInfo.markers
175
-
176
- // Get start value from start attribute (if not, use first marker's number, or 1 if neither)
177
- const listToken = tokens[listOpenIndex]
178
- const startAttr = listToken.attrs?.find(attr => attr[0] === 'start')
179
- let expectedValue = startAttr ? parseInt(startAttr[1], 10) : (markers[0]?.number || 1)
180
- let markerIndex = 0
181
-
182
- // Find list_item_open and add value attribute
183
- for (let i = listOpenIndex + 1; i < listCloseIndex; i++) {
184
- const token = tokens[i]
185
-
186
- if (token.type === 'list_item_open' && token.level === tokens[listOpenIndex].level + 1) {
187
- const marker = markers[markerIndex]
188
- if (marker && marker.number !== undefined) {
189
- // Add value attribute only when differs from expected (number not consecutive)
190
- // Example: 3→4 is consecutive (unnecessary), 3→5 skips (needed), 3→2 goes back (needed)
191
- if (marker.number !== expectedValue) {
192
- if (!token.attrs) {
193
- token.attrs = []
194
- }
195
- addAttr(token, 'value', String(marker.number))
196
- }
197
-
198
- expectedValue = marker.number + 1
199
- } else {
200
- expectedValue++
201
- }
202
- markerIndex++
203
- }
204
- }
205
- }
206
-
207
- /**
208
- * Normalize value attributes generated by markdown-it for ordered lists.
209
- * Remove value attributes for consecutive numbers.
210
- * @param {Array} tokens - Token array
211
- * @param {Object} opt - Plugin options
212
- */
213
- function normalizeAndConvertValueAttributes(tokens, opt) {
214
- for (let i = 0; i < tokens.length; i++) {
215
- const token = tokens[i]
216
-
217
- if (token.type === 'ordered_list_open') {
218
- const listCloseIndex = findMatchingClose(tokens, i, 'ordered_list_open', 'ordered_list_close')
219
- if (listCloseIndex === -1) continue
220
-
221
- // Get start value from start attribute
222
- const startAttr = token.attrs?.find(attr => attr[0] === 'start')
223
- let expectedValue = startAttr ? parseInt(startAttr[1], 10) : 1
224
-
225
- // Process list_item_open in this list
226
- for (let j = i + 1; j < listCloseIndex; j++) {
227
- const itemToken = tokens[j]
228
-
229
- if (itemToken.type === 'list_item_open' && itemToken.level === token.level + 1) {
230
- // Check existing value attribute
231
- if (itemToken.attrs) {
232
- const valueAttrIdx = itemToken.attrs.findIndex(attr => attr[0] === 'value')
233
- if (valueAttrIdx !== -1) {
234
- const itemValue = parseInt(itemToken.attrs[valueAttrIdx][1], 10)
235
-
236
- // Remove value attribute if matches expected value
237
- if (itemValue === expectedValue) {
238
- itemToken.attrs.splice(valueAttrIdx, 1)
239
- if (itemToken.attrs.length === 0) {
240
- itemToken.attrs = null
241
- }
242
- } else {
243
- // If differs from expected value
244
- expectedValue = itemValue
245
- }
246
-
247
- expectedValue++
248
- } else {
249
- // Increment expectedValue if no value attribute
250
- expectedValue++
251
- }
252
- } else {
253
- expectedValue++
254
- }
255
- }
256
- }
257
- }
258
- }
259
- }
260
-
261
- /**
262
- * Add or replace an attribute on a token (with duplicate check).
263
- */
264
- function addAttr(token, name, value) {
265
- const existingIndex = token.attrs.findIndex(attr => attr[0] === name)
266
- if (existingIndex >= 0) {
267
- token.attrs[existingIndex] = [name, value]
268
- } else {
269
- token.attrs.push([name, value])
270
- }
271
- }
167
+
168
+ // Add value attribute to list items (for non-consecutive numbers)
169
+ addListItemValues(tokens, tokenIndex, markerInfo)
170
+ }
171
+
172
+ /**
173
+ * Add `value` attributes to list items when numbering is non-consecutive.
174
+ * @param {Array} tokens - Array of tokens
175
+ * @param {number} listOpenIndex - Index of the corresponding `ordered_list_open` token
176
+ * @param {Object} markerInfo - Marker information object
177
+ */
178
+ function addListItemValues(tokens, listOpenIndex, markerInfo) {
179
+ if (!markerInfo || !markerInfo.markers) {
180
+ return
181
+ }
182
+ if (markerInfo.allNumbersIdentical) {
183
+ return
184
+ }
185
+
186
+ // Find end position of this ordered_list
187
+ const listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
188
+ if (listCloseIndex === -1) return
189
+
190
+ const markers = markerInfo.markers
191
+
192
+ // Get start value from start attribute (if not, use first marker's number, or 1 if neither)
193
+ const listToken = tokens[listOpenIndex]
194
+ const startAttr = listToken.attrs?.find(attr => attr[0] === 'start')
195
+ let expectedValue = startAttr ? parseInt(startAttr[1], 10) : (markers[0]?.number || 1)
196
+ let markerIndex = 0
197
+
198
+ // Find list_item_open and add value attribute
199
+ for (let i = listOpenIndex + 1; i < listCloseIndex; i++) {
200
+ const token = tokens[i]
201
+
202
+ if (token.type === 'list_item_open' && token.level === tokens[listOpenIndex].level + 1) {
203
+ const marker = markers[markerIndex]
204
+ if (marker && marker.number !== undefined) {
205
+ // Add value attribute only when differs from expected (number not consecutive)
206
+ // Example: 3→4 is consecutive (unnecessary), 3→5 skips (needed), 3→2 goes back (needed)
207
+ if (marker.number !== expectedValue) {
208
+ if (!token.attrs) {
209
+ token.attrs = []
210
+ }
211
+ addAttr(token, 'value', String(marker.number))
212
+ }
213
+
214
+ expectedValue = marker.number + 1
215
+ } else {
216
+ expectedValue++
217
+ }
218
+ markerIndex++
219
+ }
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Normalize value attributes generated by markdown-it for ordered lists.
225
+ * Remove value attributes for consecutive numbers.
226
+ * @param {Array} tokens - Token array
227
+ * @param {Object} opt - Plugin options
228
+ */
229
+ function normalizeAndConvertValueAttributes(tokens, opt) {
230
+ for (let i = 0; i < tokens.length; i++) {
231
+ const token = tokens[i]
232
+
233
+ if (token.type === 'ordered_list_open') {
234
+ const listCloseIndex = findMatchingClose(tokens, i, 'ordered_list_open', 'ordered_list_close')
235
+ if (listCloseIndex === -1) continue
236
+
237
+ // Get start value from start attribute
238
+ const startAttr = token.attrs?.find(attr => attr[0] === 'start')
239
+ let expectedValue = startAttr ? parseInt(startAttr[1], 10) : 1
240
+
241
+ // Process list_item_open in this list
242
+ for (let j = i + 1; j < listCloseIndex; j++) {
243
+ const itemToken = tokens[j]
244
+
245
+ if (itemToken.type === 'list_item_open' && itemToken.level === token.level + 1) {
246
+ // Check existing value attribute
247
+ if (itemToken.attrs) {
248
+ const valueAttrIdx = itemToken.attrs.findIndex(attr => attr[0] === 'value')
249
+ if (valueAttrIdx !== -1) {
250
+ const itemValue = parseInt(itemToken.attrs[valueAttrIdx][1], 10)
251
+
252
+ // Remove value attribute if matches expected value
253
+ if (itemValue === expectedValue) {
254
+ itemToken.attrs.splice(valueAttrIdx, 1)
255
+ if (itemToken.attrs.length === 0) {
256
+ itemToken.attrs = null
257
+ }
258
+ } else {
259
+ // If differs from expected value
260
+ expectedValue = itemValue
261
+ }
262
+
263
+ expectedValue++
264
+ } else {
265
+ // Increment expectedValue if no value attribute
266
+ expectedValue++
267
+ }
268
+ } else {
269
+ expectedValue++
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Add or replace an attribute on a token (with duplicate check).
279
+ */
280
+ function addAttr(token, name, value) {
281
+ const existingIndex = token.attrs.findIndex(attr => attr[0] === name)
282
+ if (existingIndex >= 0) {
283
+ token.attrs[existingIndex] = [name, value]
284
+ } else {
285
+ token.attrs.push([name, value])
286
+ }
287
+ }
@@ -18,7 +18,7 @@ export function generateSpans(tokens, listInfos, opt) {
18
18
  if (token.type === 'ordered_list_open' && token._markerInfo) {
19
19
  const markerInfo = token._markerInfo
20
20
  const firstMarker = markerInfo.markers[0]
21
- const typeAttrs = getTypeAttributes(markerInfo.type, firstMarker)
21
+ const typeAttrs = getTypeAttributes(markerInfo.type, firstMarker, opt)
22
22
 
23
23
  // Generate span if no type attribute (custom marker) or alwaysMarkerSpan mode
24
24
  // If user opts to use @counter-style, do not generate inline marker spans
@@ -99,7 +99,7 @@ export function moveNestedListAttributes(tokens) {
99
99
  const [key, value] = childToken.attrs[j]
100
100
 
101
101
  // Exclude plugin-generated attributes
102
- if (key === 'type' || key === 'role' || key === 'style' || key.startsWith('data-marker-')) {
102
+ if (key === 'type' || key === 'role' || key === 'style' || key === 'start' || key.startsWith('data-marker-')) {
103
103
  remainingAttrs.push([key, value])
104
104
  continue
105
105
  }