@peaceroad/markdown-it-renderer-fence 0.6.0 → 0.6.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 CHANGED
@@ -115,7 +115,7 @@ Note:
115
115
 
116
116
  - `samp` rendering for `samp`, `shell`, `console` languages.
117
117
  - line number wrapping via `start` (`line-number-start` long form) / `data-pre-start`.
118
- - line number skip/reset controls via `line-number-skip` / `line-number-reset`.
118
+ - line number skip/set controls via `line-number-skip` / `line-number-set`.
119
119
  - emphasized lines via `em-lines` / `emphasize-lines`.
120
120
  - optional line-end spacer via `lineEndSpanThreshold`.
121
121
  - optional pre-wrap support via `wrap` / `pre-wrap`.
@@ -157,7 +157,7 @@ console.log(a)
157
157
  Advanced line number control:
158
158
 
159
159
  ~~~md
160
- ```txt {start="25" line-number-skip="5" line-number-reset="6:136"}
160
+ ```txt {start="25" line-number-skip="5" line-number-set="6:136"}
161
161
  line1
162
162
  line2
163
163
  line3
@@ -168,7 +168,7 @@ line6
168
168
  ~~~
169
169
 
170
170
  ```html
171
- <pre><code class="language-txt" data-pre-start="25" data-pre-line-number-skip="5" data-pre-line-number-reset="6:136" style="counter-set:pre-line-number 25;">
171
+ <pre><code class="language-txt" data-pre-start="25" data-pre-line-number-skip="5" data-pre-line-number-set="6:136" style="counter-set:pre-line-number 25;">
172
172
  <span class="pre-line">line1</span>
173
173
  <span class="pre-line">line2</span>
174
174
  <span class="pre-line">line3</span>
@@ -235,32 +235,48 @@ echo 1
235
235
  - In that passthrough path, line-splitting features are intentionally disabled:
236
236
  - line numbers
237
237
  - `line-number-skip`
238
- - `line-number-reset`
238
+ - `line-number-set`
239
239
  - `em-lines`
240
240
  - line-end spacer
241
241
  - `comment-mark`
242
242
  - `samp` conversion
243
243
 
244
- Official line-number CSS contract:
244
+ Reference line-number CSS contract:
245
245
 
246
- ```css
247
- .pre-line {
248
- counter-increment: pre-line-number;
249
- }
246
+ Minimum counter contract:
250
247
 
251
- .pre-line::before {
248
+ ```css
249
+ pre :is(code, samp)[data-pre-start] .pre-line::before {
252
250
  content: counter(pre-line-number);
253
251
  }
254
252
 
255
- .pre-line.pre-line-no-number {
256
- counter-increment: none;
253
+ pre :is(code, samp)[data-pre-start] .pre-line::after {
254
+ content: "";
255
+ counter-increment: pre-line-number;
257
256
  }
258
257
 
259
- .pre-line.pre-line-no-number::before {
258
+ pre :is(code, samp)[data-pre-start] .pre-line.pre-line-no-number::before {
260
259
  content: "";
261
260
  }
261
+
262
+ pre :is(code, samp)[data-pre-start] .pre-line.pre-line-no-number::after {
263
+ counter-increment: none;
264
+ }
262
265
  ```
263
266
 
267
+ Recommended display CSS:
268
+
269
+ - See [`example/line-number.css`](./example/line-number.css) for the full reference stylesheet.
270
+ - See [`example/line-number-sample.html`](./example/line-number-sample.html) for Markdown / Preview / HTML side-by-side examples.
271
+
272
+ Note:
273
+
274
+ - renderer-fence emits `counter-set` as the displayed line number value itself
275
+ - the reference CSS increments the counter in `span.pre-line::after`, so `start="1"` maps directly to `counter-set:pre-line-number 1;`
276
+ - `line-number-set="6:136"` similarly emits `counter-set:pre-line-number 136;` on that line wrapper
277
+ - the minimum contract above is the required part; `example/line-number.css` adds gutter width, divider line, and whitespace handling
278
+ - if a consumer uses a different counter strategy, its CSS must be aligned with this HTML contract
279
+
264
280
  ### Markup Options
265
281
 
266
282
  - `attrsOrder` (default: `['class', 'id', 'data-*', 'style']`): output attribute order (`data-*` wildcard supported).
@@ -287,13 +303,15 @@ Line-number attr syntax note:
287
303
 
288
304
  - `line-number-start` is the long form of `start`; rendered output still uses `data-pre-start`.
289
305
  - `line-number-skip` supports single values (`2`), ranges (`4-6`), and open-ended forms (`3-`).
290
- - `line-number-reset` uses `line:number` pairs (for example `6:136,14:220`).
291
- - `line-number-skip` / `line-number-reset` are applied only when source and highlighted logical line counts match; otherwise renderer falls back to plain sequential numbering.
306
+ - `line-number-set` uses `line:number` pairs (for example `6:136,14:220`).
307
+ - `line-number-skip` and `line-number-set` must not target the same source line. If they overlap, the skipped line stays blank and the conflicting `line-number-set` entry is ignored.
308
+ - `line-number-skip` / `line-number-set` are applied only when source and highlighted logical line counts match; otherwise renderer falls back to plain sequential numbering.
292
309
 
293
310
  Migration note (`0.5.0`):
294
311
 
295
312
  - `comment-line` / `data-pre-comment-line` / `pre-comment-line` were removed
296
313
  - use `comment-mark` / `data-pre-comment-mark` / `pre-line-comment`
314
+ - `line-number-reset` was removed; use `line-number-set`
297
315
 
298
316
  ## Custom Highlight API Mode (Experimental / Advanced)
299
317
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peaceroad/markdown-it-renderer-fence",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "In code blocks, the `<samp>` tag is used with the language keywords `samp`, `shell`, and `console`, and provides an option to display line numbers.",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -45,7 +45,7 @@ const buildApiPreAttrs = (preWrapValue, wrapEnabled, opt) => {
45
45
  return preAttrs
46
46
  }
47
47
 
48
- const renderFenceApiOrPlain = (token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberResetValue, wrapEnabled, preWrapValue, commentMarkValue, includePayload, timings) => {
48
+ const renderFenceApiOrPlain = (token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberSetValue, wrapEnabled, preWrapValue, commentMarkValue, includePayload, timings) => {
49
49
  const isSamp = opt._sampReg.test(lang)
50
50
  const tag = isSamp ? 'samp' : 'code'
51
51
  let content = md.utils.escapeHtml(token.content)
@@ -53,9 +53,9 @@ const renderFenceApiOrPlain = (token, lang, md, opt, slf, env, startNumber, emph
53
53
  const needLineNumber = lineStrategy === 'hybrid' && opt.setLineNumber && startNumber >= 0
54
54
  const needEndSpan = lineStrategy === 'hybrid' && opt.lineEndSpanThreshold > 0
55
55
  let lineNumberPlan = null
56
- if (needLineNumber && (lineNumberSkipValue !== undefined || lineNumberResetValue !== undefined)) {
56
+ if (needLineNumber && (lineNumberSkipValue !== undefined || lineNumberSetValue !== undefined)) {
57
57
  const logicalLineCount = getLogicalLineCount(token.content)
58
- lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberResetValue, logicalLineCount, logicalLineCount)
58
+ lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberSetValue, logicalLineCount, logicalLineCount)
59
59
  }
60
60
  if (needLineNumber || needEndSpan) {
61
61
  const splitStartedAt = timings ? getNowMs() : 0
@@ -101,7 +101,7 @@ const getFenceHtml = (context, md, opt, slf, env) => {
101
101
  const startNumber = context.startNumber
102
102
  const emphasizeLines = context.emphasizeLines
103
103
  const lineNumberSkipValue = context.lineNumberSkipValue
104
- const lineNumberResetValue = context.lineNumberResetValue
104
+ const lineNumberSetValue = context.lineNumberSetValue
105
105
  const wrapEnabled = context.wrapEnabled
106
106
  const preWrapValue = context.preWrapValue
107
107
  const commentMarkValue = context.commentMarkValue
@@ -112,11 +112,11 @@ const getFenceHtml = (context, md, opt, slf, env) => {
112
112
  fallbackUsed: false,
113
113
  lineFeatureStrategy: opt.customHighlight.lineFeatureStrategy,
114
114
  disabledFeatures: opt.customHighlight.lineFeatureStrategy === 'disable'
115
- ? ['setLineNumber', 'line-number-skip', 'line-number-reset', 'lineEndSpanThreshold']
115
+ ? ['setLineNumber', 'line-number-skip', 'line-number-set', 'lineEndSpanThreshold']
116
116
  : [],
117
117
  }
118
118
  try {
119
- const html = renderFenceApiOrPlain(token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberResetValue, wrapEnabled, preWrapValue, commentMarkValue, true, timings)
119
+ const html = renderFenceApiOrPlain(token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberSetValue, wrapEnabled, preWrapValue, commentMarkValue, true, timings)
120
120
  if (timingEnabled) apiDecisionBase.timings = finalizeFenceTimings(timings, fenceStartedAt)
121
121
  emitFenceDecision(opt, apiDecisionBase)
122
122
  return html
@@ -130,7 +130,7 @@ const getFenceHtml = (context, md, opt, slf, env) => {
130
130
  reason: 'provider-error',
131
131
  lineFeatureStrategy: opt.customHighlight.lineFeatureStrategy,
132
132
  }
133
- const html = renderFenceApiOrPlain(token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberResetValue, wrapEnabled, preWrapValue, commentMarkValue, false, timings)
133
+ const html = renderFenceApiOrPlain(token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberSetValue, wrapEnabled, preWrapValue, commentMarkValue, false, timings)
134
134
  if (timingEnabled) fallbackDecision.timings = finalizeFenceTimings(timings, fenceStartedAt)
135
135
  emitFenceDecision(opt, fallbackDecision)
136
136
  return html
@@ -29,7 +29,7 @@ const renderFenceMarkup = (context, md, opt, slf) => {
29
29
  const startNumber = context.startNumber
30
30
  const emphasizeLines = context.emphasizeLines
31
31
  const lineNumberSkipValue = context.lineNumberSkipValue
32
- const lineNumberResetValue = context.lineNumberResetValue
32
+ const lineNumberSetValue = context.lineNumberSetValue
33
33
  const wrapEnabled = context.wrapEnabled
34
34
  const preWrapValue = context.preWrapValue
35
35
  const commentMarkValue = context.commentMarkValue
@@ -118,9 +118,9 @@ const renderFenceMarkup = (context, md, opt, slf) => {
118
118
  normalizedEmphasis = normalizeEmphasisRanges(emphasizeLines, highlightedLogicalLineCount)
119
119
  }
120
120
  let lineNumberPlan = null
121
- if (needLineNumber && (lineNumberSkipValue !== undefined || lineNumberResetValue !== undefined)) {
121
+ if (needLineNumber && (lineNumberSkipValue !== undefined || lineNumberSetValue !== undefined)) {
122
122
  ensureLogicalLineCounts()
123
- lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberResetValue, sourceLogicalLineCount, highlightedLogicalLineCount)
123
+ lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberSetValue, sourceLogicalLineCount, highlightedLogicalLineCount)
124
124
  }
125
125
  const needEmphasis = normalizedEmphasis.length > 0
126
126
  const needEndSpan = opt.lineEndSpanThreshold > 0
@@ -165,7 +165,7 @@ const renderFenceMarkup = (context, md, opt, slf) => {
165
165
  renderer: 'markup',
166
166
  useHighlightPre,
167
167
  hasHighlightPre,
168
- disabledFeatures: useHighlightPre ? ['setLineNumber', 'line-number-skip', 'line-number-reset', 'setEmphasizeLines', 'lineEndSpanThreshold', 'comment-mark', 'samp'] : [],
168
+ disabledFeatures: useHighlightPre ? ['setLineNumber', 'line-number-skip', 'line-number-set', 'setEmphasizeLines', 'lineEndSpanThreshold', 'comment-mark', 'samp'] : [],
169
169
  }
170
170
  if (timingEnabled) decision.timings = finalizeFenceTimings(timings, fenceStartedAt)
171
171
  emitFenceDecision(opt, decision)
@@ -190,7 +190,7 @@ const normalizeEmphasisRanges = (emphasizeLines, maxLine) => {
190
190
  return normalized
191
191
  }
192
192
 
193
- const getLineNumberResetEntries = (attrVal) => {
193
+ const getLineNumberSetEntries = (attrVal) => {
194
194
  const str = String(attrVal ?? '')
195
195
  if (!str) return []
196
196
  const result = []
@@ -200,9 +200,9 @@ const getLineNumberResetEntries = (attrVal) => {
200
200
  const colon = part.indexOf(':')
201
201
  if (colon === -1) continue
202
202
  const lineNumber = parsePositiveLineIndex(part.slice(0, colon).trim())
203
- const resetNumber = parseStartNumber(part.slice(colon + 1).trim())
204
- if (lineNumber == null || resetNumber == null) continue
205
- result.push([lineNumber, resetNumber])
203
+ const setNumber = parseStartNumber(part.slice(colon + 1).trim())
204
+ if (lineNumber == null || setNumber == null) continue
205
+ result.push([lineNumber, setNumber])
206
206
  }
207
207
  return result
208
208
  }
@@ -218,52 +218,51 @@ const createSparseLineFlagMap = (ranges, maxLine) => {
218
218
  return flags
219
219
  }
220
220
 
221
- const createSparseLineResetMap = (entries, maxLine) => {
221
+ const createSparseLineSetMap = (entries, maxLine, hidden) => {
222
222
  if (!entries || !entries.length || maxLine <= 0) return null
223
- const resets = []
223
+ const sets = []
224
224
  let hasValue = false
225
225
  for (let i = 0; i < entries.length; i++) {
226
226
  const line = entries[i][0]
227
227
  const value = entries[i][1]
228
228
  if (!Number.isSafeInteger(line) || line <= 0 || line > maxLine) continue
229
- resets[line - 1] = value
229
+ if (hidden && hidden[line - 1]) continue
230
+ sets[line - 1] = value
230
231
  hasValue = true
231
232
  }
232
- return hasValue ? resets : null
233
+ return hasValue ? sets : null
233
234
  }
234
235
 
235
- const buildAdvancedLineNumberPlan = (skipRanges, resetEntries, sourceLineCount, renderedLineCount) => {
236
+ const buildAdvancedLineNumberPlan = (skipRanges, setEntries, lineCount) => {
236
237
  const hasSkip = !!(skipRanges && skipRanges.length)
237
- const hasReset = !!(resetEntries && resetEntries.length)
238
- if (!hasSkip && !hasReset) return null
239
- if (!Number.isSafeInteger(sourceLineCount) || sourceLineCount <= 0) return null
240
- if (sourceLineCount !== renderedLineCount) return null
241
- const hidden = hasSkip ? createSparseLineFlagMap(skipRanges, sourceLineCount) : null
242
- const resets = hasReset ? createSparseLineResetMap(resetEntries, sourceLineCount) : null
243
- if (!hidden && !resets) return null
244
- return { hidden, resets }
238
+ const hasSet = !!(setEntries && setEntries.length)
239
+ if (!hasSkip && !hasSet) return null
240
+ const hidden = hasSkip ? createSparseLineFlagMap(skipRanges, lineCount) : null
241
+ const sets = hasSet ? createSparseLineSetMap(setEntries, lineCount, hidden) : null
242
+ if (!hidden && !sets) return null
243
+ return { hidden, sets }
245
244
  }
246
245
 
247
- const resolveAdvancedLineNumberPlan = (lineNumberSkipValue, lineNumberResetValue, sourceLineCount, renderedLineCount) => {
246
+ const resolveAdvancedLineNumberPlan = (lineNumberSkipValue, lineNumberSetValue, sourceLineCount, renderedLineCount) => {
248
247
  const hasSkipValue = lineNumberSkipValue !== undefined
249
- const hasResetValue = lineNumberResetValue !== undefined
250
- if (!hasSkipValue && !hasResetValue) return null
248
+ const hasSetValue = lineNumberSetValue !== undefined
249
+ if (!hasSkipValue && !hasSetValue) return null
251
250
  if (!Number.isSafeInteger(sourceLineCount) || sourceLineCount <= 0) return null
252
251
  if (sourceLineCount !== renderedLineCount) return null
253
252
  const skipRanges = hasSkipValue ? getLineNumberSkipRanges(lineNumberSkipValue) : null
254
- const resetEntries = hasResetValue ? getLineNumberResetEntries(lineNumberResetValue) : null
255
- return buildAdvancedLineNumberPlan(skipRanges, resetEntries, sourceLineCount, renderedLineCount)
253
+ const setEntries = hasSetValue ? getLineNumberSetEntries(lineNumberSetValue) : null
254
+ return buildAdvancedLineNumberPlan(skipRanges, setEntries, sourceLineCount)
256
255
  }
257
256
 
258
257
  const getPreLineOpenTag = (lineNumberPlan, lineIndex) => {
259
258
  if (!lineNumberPlan) return preLineTag
260
259
  const hidden = !!(lineNumberPlan.hidden && lineNumberPlan.hidden[lineIndex])
261
- const resetNumber = lineNumberPlan.resets ? lineNumberPlan.resets[lineIndex] : undefined
262
- if (!hidden && resetNumber == null) return preLineTag
260
+ const setNumber = lineNumberPlan.sets ? lineNumberPlan.sets[lineIndex] : undefined
261
+ if (!hidden && setNumber == null) return preLineTag
263
262
  let tag = '<span class="pre-line'
264
263
  if (hidden) tag += ' ' + preLineNoNumberClass
265
264
  tag += '"'
266
- if (resetNumber != null) tag += ` style="counter-set:pre-line-number ${resetNumber};"`
265
+ if (setNumber != null) tag += ` style="counter-set:pre-line-number ${setNumber};"`
267
266
  return tag + '>'
268
267
  }
269
268
 
@@ -399,7 +398,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
399
398
  let startNumber = -1
400
399
  let emphasizeLines = []
401
400
  let lineNumberSkipValue
402
- let lineNumberResetValue
401
+ let lineNumberSetValue
403
402
  let wrapEnabled = false
404
403
  let preWrapValue
405
404
  let commentMarkValue
@@ -412,7 +411,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
412
411
  let styleIndex = -1
413
412
  let dataPreCommentIndex = -1
414
413
  let dataPreLineNumberSkipIndex = -1
415
- let dataPreLineNumberResetIndex = -1
414
+ let dataPreLineNumberSetIndex = -1
416
415
  let startValue
417
416
  let emphasisValue
418
417
  let styleValue
@@ -420,7 +419,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
420
419
  let sawStartAttr = false
421
420
  let sawEmphasisAttr = false
422
421
  let sawLineNumberSkipAttr = false
423
- let sawLineNumberResetAttr = false
422
+ let sawLineNumberSetAttr = false
424
423
  const appendOrder = []
425
424
 
426
425
  for (const attr of token.attrs) {
@@ -469,11 +468,13 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
469
468
  lineNumberSkipValue = val
470
469
  newAttrs.push(attr)
471
470
  break
472
- case 'data-pre-line-number-reset':
473
- dataPreLineNumberResetIndex = newAttrs.length
474
- lineNumberResetValue = val
471
+ case 'data-pre-line-number-set':
472
+ dataPreLineNumberSetIndex = newAttrs.length
473
+ lineNumberSetValue = val
475
474
  newAttrs.push(attr)
476
475
  break
476
+ case 'data-pre-line-number-reset':
477
+ break
477
478
  case 'em-lines':
478
479
  case 'emphasize-lines':
479
480
  if (opt.setEmphasizeLines) emphasizeLines = getEmphasizeLines(val)
@@ -498,13 +499,16 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
498
499
  sawLineNumberSkipAttr = true
499
500
  }
500
501
  break
502
+ case 'line-number-set':
503
+ case 'pre-line-number-set':
504
+ lineNumberSetValue = val
505
+ if (!sawLineNumberSetAttr) {
506
+ appendOrder.push('line-number-set')
507
+ sawLineNumberSetAttr = true
508
+ }
509
+ break
501
510
  case 'line-number-reset':
502
511
  case 'pre-line-number-reset':
503
- lineNumberResetValue = val
504
- if (!sawLineNumberResetAttr) {
505
- appendOrder.push('line-number-reset')
506
- sawLineNumberResetAttr = true
507
- }
508
512
  break
509
513
  case 'data-pre-wrap':
510
514
  preWrapValue = val
@@ -523,7 +527,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
523
527
  if (emphasisValue !== undefined && dataPreEmphasisIndex >= 0) newAttrs[dataPreEmphasisIndex][1] = emphasisValue
524
528
  if (commentMarkValue !== undefined && dataPreCommentIndex >= 0) newAttrs[dataPreCommentIndex][1] = commentMarkValue
525
529
  if (lineNumberSkipValue !== undefined && dataPreLineNumberSkipIndex >= 0) newAttrs[dataPreLineNumberSkipIndex][1] = lineNumberSkipValue
526
- if (lineNumberResetValue !== undefined && dataPreLineNumberResetIndex >= 0) newAttrs[dataPreLineNumberResetIndex][1] = lineNumberResetValue
530
+ if (lineNumberSetValue !== undefined && dataPreLineNumberSetIndex >= 0) newAttrs[dataPreLineNumberSetIndex][1] = lineNumberSetValue
527
531
 
528
532
  for (const kind of appendOrder) {
529
533
  if (kind === 'start' && dataPreStartIndex === -1 && startValue !== undefined) {
@@ -534,8 +538,8 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
534
538
  newAttrs.push(['data-pre-comment-mark', commentMarkValue])
535
539
  } else if (kind === 'line-number-skip' && dataPreLineNumberSkipIndex === -1 && lineNumberSkipValue !== undefined) {
536
540
  newAttrs.push(['data-pre-line-number-skip', lineNumberSkipValue])
537
- } else if (kind === 'line-number-reset' && dataPreLineNumberResetIndex === -1 && lineNumberResetValue !== undefined) {
538
- newAttrs.push(['data-pre-line-number-reset', lineNumberResetValue])
541
+ } else if (kind === 'line-number-set' && dataPreLineNumberSetIndex === -1 && lineNumberSetValue !== undefined) {
542
+ newAttrs.push(['data-pre-line-number-set', lineNumberSetValue])
539
543
  }
540
544
  }
541
545
 
@@ -560,7 +564,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
560
564
  startNumber,
561
565
  emphasizeLines,
562
566
  lineNumberSkipValue,
563
- lineNumberResetValue,
567
+ lineNumberSetValue,
564
568
  wrapEnabled,
565
569
  preWrapValue,
566
570
  commentMarkValue,