@peaceroad/markdown-it-renderer-fence 0.6.0 → 0.7.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 +85 -18
- package/package.json +4 -5
- package/src/custom-highlight/option-validator.js +14 -5
- package/src/custom-highlight/scope-name.js +23 -0
- package/src/entry/markup-highlight.js +4 -0
- package/src/fence/line-notes.js +181 -0
- package/src/fence/render-api-provider.js +3 -12
- package/src/fence/render-api-renderer.js +25 -11
- package/src/fence/render-api-runtime.js +5 -13
- package/src/fence/render-api.js +4 -0
- package/src/fence/render-markup.js +23 -10
- package/src/fence/render-shared.js +196 -50
- package/THIRD_PARTY_NOTICES.md +0 -56
package/README.md
CHANGED
|
@@ -115,11 +115,12 @@ 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/
|
|
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`.
|
|
122
122
|
- comment line markers via `comment-mark`.
|
|
123
|
+
- sidecar line notes via immediate `line-notes` fence (`notes` alias).
|
|
123
124
|
|
|
124
125
|
### Fence Attribute Examples
|
|
125
126
|
|
|
@@ -157,7 +158,7 @@ console.log(a)
|
|
|
157
158
|
Advanced line number control:
|
|
158
159
|
|
|
159
160
|
~~~md
|
|
160
|
-
```txt {start="25" line-number-skip="5" line-number-
|
|
161
|
+
```txt {start="25" line-number-skip="5" line-number-set="6:136"}
|
|
161
162
|
line1
|
|
162
163
|
line2
|
|
163
164
|
line3
|
|
@@ -168,7 +169,7 @@ line6
|
|
|
168
169
|
~~~
|
|
169
170
|
|
|
170
171
|
```html
|
|
171
|
-
<pre><code class="language-txt" data-pre-start="25" data-pre-line-number-skip="5" data-pre-line-number-
|
|
172
|
+
<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
173
|
<span class="pre-line">line1</span>
|
|
173
174
|
<span class="pre-line">line2</span>
|
|
174
175
|
<span class="pre-line">line3</span>
|
|
@@ -229,38 +230,76 @@ echo 1
|
|
|
229
230
|
</samp></pre>
|
|
230
231
|
```
|
|
231
232
|
|
|
233
|
+
Line notes:
|
|
234
|
+
|
|
235
|
+
~~~md
|
|
236
|
+
```js {start="5"}
|
|
237
|
+
const a = 1
|
|
238
|
+
console.log(a)
|
|
239
|
+
```
|
|
240
|
+
```line-notes
|
|
241
|
+
1: setup {width="7em"}
|
|
242
|
+
2:result {width="10em"}
|
|
243
|
+
```
|
|
244
|
+
~~~
|
|
245
|
+
|
|
246
|
+
```html
|
|
247
|
+
<div class="pre-wrapper-line-notes" data-pre-line-notes-layout="anchor"><pre><code class="language-js" data-pre-start="5" data-pre-line-notes="true" style="counter-set:pre-line-number 5;"><span class="pre-line pre-line-has-end-note" data-pre-line-note-from="1" data-pre-line-note-to="1"><span class="pre-line-content" style="anchor-name:--pre-line-note-1;" aria-describedby="pre-line-note-1-1-1">const a = 1</span></span>
|
|
248
|
+
<span class="pre-line pre-line-has-end-note" data-pre-line-note-from="2" data-pre-line-note-to="2"><span class="pre-line-content" style="anchor-name:--pre-line-note-2;" aria-describedby="pre-line-note-1-1-2">console.log(a)</span></span>
|
|
249
|
+
</code></pre><div class="pre-line-note-layer"><div id="pre-line-note-1-1-1" class="pre-line-note" role="note" data-pre-line-note-from="1" data-pre-line-note-to="1" data-pre-line-note-label="1" style="position-anchor:--pre-line-note-1; --line-note-width:7em;">setup</div><div id="pre-line-note-1-1-2" class="pre-line-note" role="note" data-pre-line-note-from="2" data-pre-line-note-to="2" data-pre-line-note-label="2" style="position-anchor:--pre-line-note-2; --line-note-width:10em;">result</div></div></div>
|
|
250
|
+
```
|
|
251
|
+
|
|
232
252
|
### Markup Notes
|
|
233
253
|
|
|
234
254
|
- `useHighlightPre: true` keeps highlighter-provided `<pre><code>` when present.
|
|
235
255
|
- In that passthrough path, line-splitting features are intentionally disabled:
|
|
236
256
|
- line numbers
|
|
237
257
|
- `line-number-skip`
|
|
238
|
-
- `line-number-
|
|
258
|
+
- `line-number-set`
|
|
239
259
|
- `em-lines`
|
|
240
260
|
- line-end spacer
|
|
241
261
|
- `comment-mark`
|
|
262
|
+
- `line-notes`
|
|
242
263
|
- `samp` conversion
|
|
243
264
|
|
|
244
|
-
|
|
265
|
+
Reference line-number CSS contract:
|
|
245
266
|
|
|
246
|
-
|
|
247
|
-
.pre-line {
|
|
248
|
-
counter-increment: pre-line-number;
|
|
249
|
-
}
|
|
267
|
+
Minimum counter contract:
|
|
250
268
|
|
|
251
|
-
|
|
269
|
+
```css
|
|
270
|
+
pre :is(code, samp)[data-pre-start] .pre-line::before {
|
|
252
271
|
content: counter(pre-line-number);
|
|
253
272
|
}
|
|
254
273
|
|
|
255
|
-
|
|
256
|
-
|
|
274
|
+
pre :is(code, samp)[data-pre-start] .pre-line::after {
|
|
275
|
+
content: "";
|
|
276
|
+
counter-increment: pre-line-number;
|
|
257
277
|
}
|
|
258
278
|
|
|
259
|
-
.pre-line.pre-line-no-number::before {
|
|
279
|
+
pre :is(code, samp)[data-pre-start] .pre-line.pre-line-no-number::before {
|
|
260
280
|
content: "";
|
|
261
281
|
}
|
|
282
|
+
|
|
283
|
+
pre :is(code, samp)[data-pre-start] .pre-line.pre-line-no-number::after {
|
|
284
|
+
counter-increment: none;
|
|
285
|
+
}
|
|
262
286
|
```
|
|
263
287
|
|
|
288
|
+
Recommended display CSS:
|
|
289
|
+
|
|
290
|
+
- See [`example/line-number.css`](./example/line-number.css) for the full reference stylesheet.
|
|
291
|
+
- See [`example/line-number-sample.html`](./example/line-number-sample.html) for Markdown / Preview / HTML side-by-side examples.
|
|
292
|
+
- See [`example/line-notes.css`](./example/line-notes.css) for the companion line-note stylesheet.
|
|
293
|
+
- See [`example/line-notes-sample.html`](./example/line-notes-sample.html) for sidecar line-note examples.
|
|
294
|
+
|
|
295
|
+
Note:
|
|
296
|
+
|
|
297
|
+
- renderer-fence emits `counter-set` as the displayed line number value itself
|
|
298
|
+
- the reference CSS increments the counter in `span.pre-line::after`, so `start="1"` maps directly to `counter-set:pre-line-number 1;`
|
|
299
|
+
- `line-number-set="6:136"` similarly emits `counter-set:pre-line-number 136;` on that line wrapper
|
|
300
|
+
- the minimum contract above is the required part; `example/line-number.css` adds gutter width, divider line, and whitespace handling
|
|
301
|
+
- if a consumer uses a different counter strategy, its CSS must be aligned with this HTML contract
|
|
302
|
+
|
|
264
303
|
### Markup Options
|
|
265
304
|
|
|
266
305
|
- `attrsOrder` (default: `['class', 'id', 'data-*', 'style']`): output attribute order (`data-*` wildcard supported).
|
|
@@ -287,13 +326,34 @@ Line-number attr syntax note:
|
|
|
287
326
|
|
|
288
327
|
- `line-number-start` is the long form of `start`; rendered output still uses `data-pre-start`.
|
|
289
328
|
- `line-number-skip` supports single values (`2`), ranges (`4-6`), and open-ended forms (`3-`).
|
|
290
|
-
- `line-number-
|
|
291
|
-
- `line-number-skip`
|
|
329
|
+
- `line-number-set` uses `line:number` pairs (for example `6:136,14:220`).
|
|
330
|
+
- `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.
|
|
331
|
+
- `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.
|
|
332
|
+
|
|
333
|
+
Line-note syntax note:
|
|
334
|
+
|
|
335
|
+
- `line-notes` is the canonical sidecar fence name; `notes` is a short alias.
|
|
336
|
+
- Sidecar notes are folded only when they immediately follow a code/samp fence.
|
|
337
|
+
- Immediate sidecar folding is fail-closed: if any non-empty line is malformed, a note entry is incomplete, or the same note start line appears twice, the `line-notes` fence stays as a normal literal fence block.
|
|
338
|
+
- Syntax supports `N: text`, `N-M: text`, `N:text`, and `N-M:text`.
|
|
339
|
+
- Per-note attrs can be appended in markdown-it-attrs style on the same line for single-line notes, for example `5: cache lookup key {width="8rem"}`.
|
|
340
|
+
- For multiline notes, prefer a trailing attrs-only continuation line, for example ` {width="11rem"}`.
|
|
341
|
+
- Continuation lines start with indentation (` ` or tab) and append multiline note text to the previous note.
|
|
342
|
+
- Output keeps note text outside `<code>/<samp>` in a sibling note layer for safer copy semantics.
|
|
343
|
+
- Line-note blocks use the external wrapper contract `pre-wrapper-line-notes`.
|
|
344
|
+
- `data-pre-line-notes-layout="anchor"` is added only for simple non-overlapping note layouts. The current check is intentionally lightweight: it uses the note start line plus the note text's explicit logical line count, so a two-line note starting at line `3` prevents another anchored note from starting at line `4`.
|
|
345
|
+
- This anchor-safety check does not measure CSS wrapping. If a note wraps because of narrow CSS width, consumer CSS should still be prepared to fall back to a below-block list.
|
|
346
|
+
- When anchor positioning is unavailable or the layout is not marked safe, consumer CSS should render notes as a below-block list. The reference stylesheet uses `data-pre-line-note-label` so the fallback still shows which source line each note belongs to.
|
|
347
|
+
- In anchor layouts, the rendered line HTML carries note metadata on `.pre-line`, and the anchor itself is attached to `.pre-line-content` so notes can start from the rendered code end.
|
|
348
|
+
- Accessibility contract: the rendered `.pre-line-content` carries `aria-describedby`, and each note item renders with a stable `id` plus `role="note"`, so assistive tech can associate the note text with its anchor line.
|
|
349
|
+
- When a sidecar fence is folded, the previous fence token `map` is extended to cover the absorbed note-fence lines so editor scroll/jump integrations can still treat the merged output as one source span.
|
|
350
|
+
- `line-notes` are applied only when source and highlighted logical line counts match; otherwise note markup is skipped.
|
|
292
351
|
|
|
293
352
|
Migration note (`0.5.0`):
|
|
294
353
|
|
|
295
354
|
- `comment-line` / `data-pre-comment-line` / `pre-comment-line` were removed
|
|
296
355
|
- use `comment-mark` / `data-pre-comment-mark` / `pre-line-comment`
|
|
356
|
+
- `line-number-reset` was removed; use `line-number-set`
|
|
297
357
|
|
|
298
358
|
## Custom Highlight API Mode (Experimental / Advanced)
|
|
299
359
|
|
|
@@ -519,6 +579,7 @@ Then include that bridge in generated HTML:
|
|
|
519
579
|
- `customHighlight.idPrefix` (default: `'hl-'`): generated per-block payload id prefix.
|
|
520
580
|
- `customHighlight.scopePrefix` (default: `'hl'`): scope name prefix used for highlight registration.
|
|
521
581
|
- `customHighlight.lineFeatureStrategy` (`'hybrid' | 'disable'`, default: `'hybrid'`): keep or disable line-span features in API rendering.
|
|
582
|
+
- In `NODE_ENV=development`, invalid known enum-like values and unknown `customHighlight` keys emit warn-once diagnostics while keeping normalized fallback behavior.
|
|
522
583
|
|
|
523
584
|
### API Helper Exports
|
|
524
585
|
|
|
@@ -532,11 +593,19 @@ Then include that bridge in generated HTML:
|
|
|
532
593
|
- `customHighlightPayloadSchemaVersion`
|
|
533
594
|
- `customHighlightPayloadSupportedVersions`
|
|
534
595
|
|
|
596
|
+
### Runtime Version Policy
|
|
597
|
+
|
|
598
|
+
- `applyCustomHighlights(..., { strictVersion: true })` accepts only `customHighlightPayloadSupportedVersions` (currently `v:1`).
|
|
599
|
+
- `supportedVersion` / `supportedVersions` allow explicit accepted payload versions.
|
|
600
|
+
- If `strictVersion` is `true`, it takes precedence and custom accepted versions are ignored.
|
|
601
|
+
|
|
535
602
|
## Docs and Examples
|
|
536
603
|
|
|
537
604
|
- API styling guide: `docs/custom-highlight-styling-guide.md`
|
|
538
605
|
- default preset CSS sample: `docs/default-highlight-theme.css`
|
|
539
606
|
- provider matrix page: `example/custom-highlight-provider-matrix.html`
|
|
607
|
+
- line-number sample: `example/line-number-sample.html`
|
|
608
|
+
- line-notes sample: `example/line-notes-sample.html`
|
|
540
609
|
|
|
541
610
|
## Tests and Benchmarks
|
|
542
611
|
|
|
@@ -547,8 +616,6 @@ Then include that bridge in generated HTML:
|
|
|
547
616
|
- performance baseline: `npm run test:performance`
|
|
548
617
|
- runtime apply benchmark: `npm run test:performance:runtime`
|
|
549
618
|
|
|
550
|
-
## License
|
|
619
|
+
## License
|
|
551
620
|
|
|
552
621
|
- Project license: MIT (`LICENSE`)
|
|
553
|
-
- Third-party notices: `THIRD_PARTY_NOTICES.md`
|
|
554
|
-
- `docs/default-highlight-theme.css` contains mappings derived from highlight.js theme families (BSD-3-Clause; see notices).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-it-renderer-fence",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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": {
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"index.js",
|
|
14
14
|
"src",
|
|
15
15
|
"LICENSE",
|
|
16
|
-
"THIRD_PARTY_NOTICES.md",
|
|
17
16
|
"README.md"
|
|
18
17
|
],
|
|
19
18
|
"type": "module",
|
|
@@ -42,10 +41,10 @@
|
|
|
42
41
|
"repository": "https://github.com/peaceroad/p7d-markdown-it-renderer-fence",
|
|
43
42
|
"license": "MIT",
|
|
44
43
|
"devDependencies": {
|
|
45
|
-
"@peaceroad/markdown-it-figure-with-p-caption": "^0.
|
|
44
|
+
"@peaceroad/markdown-it-figure-with-p-caption": "^0.17.0",
|
|
46
45
|
"highlight.js": "^11.11.1",
|
|
47
|
-
"markdown-it": "^14.1.
|
|
46
|
+
"markdown-it": "^14.1.1",
|
|
48
47
|
"markdown-it-attrs": "^4.3.1",
|
|
49
|
-
"shiki": "^
|
|
48
|
+
"shiki": "^4.0.2"
|
|
50
49
|
}
|
|
51
50
|
}
|
|
@@ -42,6 +42,15 @@ const knownCustomHighlightKeys = new Set([
|
|
|
42
42
|
'theme',
|
|
43
43
|
])
|
|
44
44
|
|
|
45
|
+
const isExactValidOptionValue = (value, validSet) => {
|
|
46
|
+
return typeof value === 'string' && validSet.has(value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isNormalizedValidOptionValue = (value, validSet) => {
|
|
50
|
+
if (typeof value !== 'string') return false
|
|
51
|
+
return validSet.has(value.trim().toLowerCase())
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
const validateCustomHighlightOptions = (rawOpt, normalizedOpt) => {
|
|
46
55
|
if (!rawOpt || typeof rawOpt !== 'object') return
|
|
47
56
|
|
|
@@ -54,31 +63,31 @@ const validateCustomHighlightOptions = (rawOpt, normalizedOpt) => {
|
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
if (rawOpt.provider != null && !
|
|
66
|
+
if (rawOpt.provider != null && !isExactValidOptionValue(rawOpt.provider, validProviderSet)) {
|
|
58
67
|
warnOptionIssueOnce(
|
|
59
68
|
'provider',
|
|
60
69
|
`Invalid customHighlight.provider "${String(rawOpt.provider)}". Using "${normalizedOpt.provider}".`,
|
|
61
70
|
)
|
|
62
71
|
}
|
|
63
|
-
if (rawOpt.fallback != null && !
|
|
72
|
+
if (rawOpt.fallback != null && !isExactValidOptionValue(rawOpt.fallback, validFallbackSet)) {
|
|
64
73
|
warnOptionIssueOnce(
|
|
65
74
|
'fallback',
|
|
66
75
|
`Invalid customHighlight.fallback "${String(rawOpt.fallback)}". Using "${normalizedOpt.fallback}".`,
|
|
67
76
|
)
|
|
68
77
|
}
|
|
69
|
-
if (rawOpt.transport != null && !
|
|
78
|
+
if (rawOpt.transport != null && !isExactValidOptionValue(rawOpt.transport, validTransportSet)) {
|
|
70
79
|
warnOptionIssueOnce(
|
|
71
80
|
'transport',
|
|
72
81
|
`Invalid customHighlight.transport "${String(rawOpt.transport)}". Using "${normalizedOpt.transport}".`,
|
|
73
82
|
)
|
|
74
83
|
}
|
|
75
|
-
if (rawOpt.lineFeatureStrategy != null && !
|
|
84
|
+
if (rawOpt.lineFeatureStrategy != null && !isExactValidOptionValue(rawOpt.lineFeatureStrategy, validLineFeatureStrategySet)) {
|
|
76
85
|
warnOptionIssueOnce(
|
|
77
86
|
'line-feature-strategy',
|
|
78
87
|
`Invalid customHighlight.lineFeatureStrategy "${String(rawOpt.lineFeatureStrategy)}". Using "${normalizedOpt.lineFeatureStrategy}".`,
|
|
79
88
|
)
|
|
80
89
|
}
|
|
81
|
-
if (rawOpt.shikiScopeMode != null && !
|
|
90
|
+
if (rawOpt.shikiScopeMode != null && !isNormalizedValidOptionValue(rawOpt.shikiScopeMode, validShikiScopeModeSet)) {
|
|
82
91
|
warnOptionIssueOnce(
|
|
83
92
|
'shiki-scope-mode',
|
|
84
93
|
`Invalid customHighlight.shikiScopeMode "${String(rawOpt.shikiScopeMode)}". Using "${normalizedOpt.shikiScopeMode}".`,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const highlightNameUnsafeReg = /[^A-Za-z0-9_-]+/g
|
|
2
|
+
const hyphenMultiReg = /-+/g
|
|
3
|
+
|
|
4
|
+
const sanitizeHighlightNamePart = (value, fallback = '') => {
|
|
5
|
+
let safe = String(value == null ? '' : value)
|
|
6
|
+
.replace(highlightNameUnsafeReg, '-')
|
|
7
|
+
.replace(hyphenMultiReg, '-')
|
|
8
|
+
.replace(/^-+|-+$/g, '')
|
|
9
|
+
if (!safe) safe = fallback
|
|
10
|
+
if (/^[0-9]/.test(safe)) safe = 'x-' + safe
|
|
11
|
+
if (safe.startsWith('--')) safe = safe.slice(2) || fallback
|
|
12
|
+
return safe
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const sanitizeHighlightName = (name, prefix = '') => {
|
|
16
|
+
const safe = sanitizeHighlightNamePart(name, 'scope')
|
|
17
|
+
const prefixBase = sanitizeHighlightNamePart(prefix, '')
|
|
18
|
+
return prefixBase ? `${prefixBase}-${safe}` : safe
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
sanitizeHighlightName,
|
|
23
|
+
}
|
|
@@ -4,6 +4,9 @@ import {
|
|
|
4
4
|
finalizeCommonFenceOption,
|
|
5
5
|
prepareFenceRenderContext,
|
|
6
6
|
} from '../fence/render-shared.js'
|
|
7
|
+
import {
|
|
8
|
+
installLineNotesCoreRule,
|
|
9
|
+
} from '../fence/line-notes.js'
|
|
7
10
|
import {
|
|
8
11
|
renderFenceMarkup,
|
|
9
12
|
} from '../fence/render-markup.js'
|
|
@@ -17,6 +20,7 @@ const mditRendererFenceMarkup = (md, option) => {
|
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
finalizeCommonFenceOption(opt)
|
|
23
|
+
installLineNotesCoreRule(md)
|
|
20
24
|
|
|
21
25
|
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
|
|
22
26
|
const context = prepareFenceRenderContext(tokens, idx, opt)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getInfoAttr,
|
|
3
|
+
} from '../utils/attr-utils.js'
|
|
4
|
+
|
|
5
|
+
const lineNotesRuleName = 'renderer_fence_line_notes'
|
|
6
|
+
const lineNotesMetaKey = '__rendererFenceLineNotes'
|
|
7
|
+
const lineNotesInstalledKey = '__rendererFenceLineNotesInstalled'
|
|
8
|
+
const fenceInfoNameReg = /^([^{\s]*)/
|
|
9
|
+
const lineNoteHeaderReg = /^\s*([1-9]\d*)(?:\s*-\s*([1-9]\d*))?\s*[::]\s*(.*)$/
|
|
10
|
+
const lineNoteContinuationReg = /^(?: {2,}|\t)/
|
|
11
|
+
const lineNoteFenceNameSet = new Set(['line-notes', 'notes'])
|
|
12
|
+
const lineNoteAttrsReg = /^(.*?)(?:\s+\{([^{}]+)\})\s*$/
|
|
13
|
+
const lineNoteAttrsOnlyReg = /^\{([^{}]+)\}\s*$/
|
|
14
|
+
const lineNoteWidthReg = /^(?:\d+(?:\.\d+)?|\.\d+)(?:px|em|rem|ch|vw|svw|lvw|dvw|%)$/
|
|
15
|
+
|
|
16
|
+
const getFenceInfoName = (info) => {
|
|
17
|
+
const match = String(info ?? '').trim().match(fenceInfoNameReg)
|
|
18
|
+
return match ? match[1] : ''
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const stripContinuationIndent = (line) => {
|
|
22
|
+
if (!line) return ''
|
|
23
|
+
if (line[0] === '\t') return line.slice(1)
|
|
24
|
+
if (line.startsWith(' ')) return line.slice(2)
|
|
25
|
+
return line
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const parseValidatedLineNoteWidth = (attrText) => {
|
|
29
|
+
if (!attrText || attrText.indexOf('width') === -1) return ''
|
|
30
|
+
const attrs = getInfoAttr(attrText)
|
|
31
|
+
if (!attrs.length) return ''
|
|
32
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
33
|
+
const name = attrs[i][0]
|
|
34
|
+
const value = String(attrs[i][1] || '').trim()
|
|
35
|
+
if (name !== 'width') continue
|
|
36
|
+
if (lineNoteWidthReg.test(value)) return value
|
|
37
|
+
}
|
|
38
|
+
return ''
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parseLineNoteHeaderText = (text) => {
|
|
42
|
+
const str = String(text ?? '')
|
|
43
|
+
if (str.indexOf('{') === -1 || str.indexOf('}') === -1) {
|
|
44
|
+
return { text: str, width: '' }
|
|
45
|
+
}
|
|
46
|
+
const match = str.match(lineNoteAttrsReg)
|
|
47
|
+
if (!match) return { text: str, width: '' }
|
|
48
|
+
return {
|
|
49
|
+
text: match[1].trimEnd(),
|
|
50
|
+
width: parseValidatedLineNoteWidth(match[2]),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parseLineNoteAttrsOnlyText = (text) => {
|
|
55
|
+
const str = String(text ?? '').trim()
|
|
56
|
+
if (str[0] !== '{' || str[str.length - 1] !== '}') return null
|
|
57
|
+
const match = str.match(lineNoteAttrsOnlyReg)
|
|
58
|
+
if (!match) return null
|
|
59
|
+
const width = parseValidatedLineNoteWidth(match[1])
|
|
60
|
+
return width ? { width } : null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const parseLineNotesContent = (content) => {
|
|
64
|
+
const raw = String(content ?? '')
|
|
65
|
+
if (!raw || (raw.indexOf(':') === -1 && raw.indexOf(':') === -1)) return []
|
|
66
|
+
const lines = raw.split('\n')
|
|
67
|
+
const notes = []
|
|
68
|
+
const seenStarts = new Set()
|
|
69
|
+
let current = null
|
|
70
|
+
|
|
71
|
+
const pushCurrent = () => {
|
|
72
|
+
if (!current) return true
|
|
73
|
+
if (!current.lines.length) return false
|
|
74
|
+
notes.push({
|
|
75
|
+
from: current.from,
|
|
76
|
+
to: current.to,
|
|
77
|
+
text: current.lines.join('\n'),
|
|
78
|
+
lineCount: current.lines.length,
|
|
79
|
+
width: current.width || '',
|
|
80
|
+
})
|
|
81
|
+
seenStarts.add(current.from)
|
|
82
|
+
current = null
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < lines.length; i++) {
|
|
87
|
+
const line = lines[i]
|
|
88
|
+
const header = line.match(lineNoteHeaderReg)
|
|
89
|
+
if (header) {
|
|
90
|
+
if (!pushCurrent()) return null
|
|
91
|
+
let from = Number(header[1])
|
|
92
|
+
let to = header[2] ? Number(header[2]) : from
|
|
93
|
+
if (from > to) {
|
|
94
|
+
const swap = from
|
|
95
|
+
from = to
|
|
96
|
+
to = swap
|
|
97
|
+
}
|
|
98
|
+
if (seenStarts.has(from)) return null
|
|
99
|
+
current = {
|
|
100
|
+
from,
|
|
101
|
+
to,
|
|
102
|
+
lines: [],
|
|
103
|
+
width: '',
|
|
104
|
+
}
|
|
105
|
+
const parsed = parseLineNoteHeaderText(header[3])
|
|
106
|
+
current.width = parsed.width
|
|
107
|
+
if (parsed.text) current.lines.push(parsed.text)
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (current && lineNoteContinuationReg.test(line)) {
|
|
112
|
+
const stripped = stripContinuationIndent(line)
|
|
113
|
+
const attrsOnly = parseLineNoteAttrsOnlyText(stripped)
|
|
114
|
+
if (attrsOnly) {
|
|
115
|
+
if (attrsOnly.width) current.width = attrsOnly.width
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
current.lines.push(stripped)
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!line.trim()) {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!pushCurrent()) return null
|
|
130
|
+
return notes
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const getTokenLineNotes = (token) => {
|
|
134
|
+
if (!token || !token.meta || typeof token.meta !== 'object') return null
|
|
135
|
+
const notes = token.meta[lineNotesMetaKey]
|
|
136
|
+
return Array.isArray(notes) && notes.length ? notes : null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const setTokenLineNotes = (token, notes) => {
|
|
140
|
+
if (!token || !notes || !notes.length) return
|
|
141
|
+
if (!token.meta || typeof token.meta !== 'object') token.meta = {}
|
|
142
|
+
token.meta[lineNotesMetaKey] = notes
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const extendTokenMapWithImmediateFollower = (token, next) => {
|
|
146
|
+
if (!token || !Array.isArray(token.map) || token.map.length !== 2) return
|
|
147
|
+
if (!next || !Array.isArray(next.map) || next.map.length !== 2) return
|
|
148
|
+
const start = token.map[0]
|
|
149
|
+
const end = next.map[1]
|
|
150
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end)) return
|
|
151
|
+
if (end > token.map[1]) token.map = [start, end]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const installLineNotesCoreRule = (md) => {
|
|
155
|
+
if (!md || md[lineNotesInstalledKey]) return
|
|
156
|
+
md[lineNotesInstalledKey] = true
|
|
157
|
+
md.core.ruler.after('block', lineNotesRuleName, (state) => {
|
|
158
|
+
const tokens = state && state.tokens
|
|
159
|
+
if (!Array.isArray(tokens) || tokens.length < 2) return
|
|
160
|
+
|
|
161
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
162
|
+
const token = tokens[i]
|
|
163
|
+
const next = tokens[i + 1]
|
|
164
|
+
if (!token || token.type !== 'fence' || !next || next.type !== 'fence') continue
|
|
165
|
+
const nextName = getFenceInfoName(next.info)
|
|
166
|
+
if (!lineNoteFenceNameSet.has(nextName)) continue
|
|
167
|
+
const currentName = getFenceInfoName(token.info)
|
|
168
|
+
if (lineNoteFenceNameSet.has(currentName)) continue
|
|
169
|
+
const notes = parseLineNotesContent(next.content)
|
|
170
|
+
if (!notes || !notes.length) continue
|
|
171
|
+
setTokenLineNotes(token, notes)
|
|
172
|
+
extendTokenMapWithImmediateFollower(token, next)
|
|
173
|
+
tokens.splice(i + 1, 1)
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export {
|
|
179
|
+
getTokenLineNotes,
|
|
180
|
+
installLineNotesCoreRule,
|
|
181
|
+
}
|
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
validateCustomHighlightOptions,
|
|
17
17
|
} from '../custom-highlight/option-validator.js'
|
|
18
|
+
import {
|
|
19
|
+
sanitizeHighlightName,
|
|
20
|
+
} from '../custom-highlight/scope-name.js'
|
|
18
21
|
import {
|
|
19
22
|
commentLineClass,
|
|
20
23
|
normalizeEmphasisRanges,
|
|
@@ -31,7 +34,6 @@ import {
|
|
|
31
34
|
} from './render-api-constants.js'
|
|
32
35
|
|
|
33
36
|
const highlightNameUnsafeReg = /[^A-Za-z0-9_-]+/g
|
|
34
|
-
const hyphenMultiReg = /-+/g
|
|
35
37
|
|
|
36
38
|
const defaultCustomHighlightOpt = {
|
|
37
39
|
provider: 'shiki',
|
|
@@ -183,16 +185,6 @@ const shouldApplyApiFallbackForReason = (chOpt, reason) => {
|
|
|
183
185
|
return chOpt._fallbackOnSet.has(reason)
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
const sanitizeHighlightName = (name, prefix = '') => {
|
|
187
|
-
const prefixBase = String(prefix == null ? '' : prefix).replace(highlightNameUnsafeReg, '-').replace(hyphenMultiReg, '-').replace(/^-+|-+$/g, '')
|
|
188
|
-
const raw = String(name || '')
|
|
189
|
-
let safe = raw.replace(highlightNameUnsafeReg, '-').replace(hyphenMultiReg, '-').replace(/^-+|-+$/g, '')
|
|
190
|
-
if (!safe) safe = 'scope'
|
|
191
|
-
if (/^[0-9]/.test(safe)) safe = 'x-' + safe
|
|
192
|
-
if (safe.startsWith('--')) safe = safe.slice(2) || 'scope'
|
|
193
|
-
return prefixBase ? `${prefixBase}-${safe}` : safe
|
|
194
|
-
}
|
|
195
|
-
|
|
196
188
|
const uniqueHighlightName = (base, usedMap) => {
|
|
197
189
|
if (!usedMap.has(base)) {
|
|
198
190
|
usedMap.set(base, 1)
|
|
@@ -805,6 +797,5 @@ export {
|
|
|
805
797
|
normalizeCustomHighlightOpt,
|
|
806
798
|
renderCustomHighlightPayloadScript,
|
|
807
799
|
renderCustomHighlightScopeStyleTag,
|
|
808
|
-
sanitizeHighlightName,
|
|
809
800
|
shouldApplyApiFallbackForReason,
|
|
810
801
|
}
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
orderTokenAttrs,
|
|
12
12
|
preWrapStyle,
|
|
13
13
|
resolveAdvancedLineNumberPlan,
|
|
14
|
+
resolveLineNotesPlan,
|
|
14
15
|
splitFenceBlockToLines,
|
|
16
|
+
wrapFencePreWithLineNotes,
|
|
15
17
|
} from './render-shared.js'
|
|
16
18
|
import {
|
|
17
19
|
createApiPayloadForFence,
|
|
@@ -26,6 +28,8 @@ import {
|
|
|
26
28
|
renderFenceMarkup,
|
|
27
29
|
} from './render-markup.js'
|
|
28
30
|
|
|
31
|
+
const apiDisableLineFeatureList = Object.freeze(['setLineNumber', 'line-number-skip', 'line-number-set', 'lineEndSpanThreshold', 'line-notes'])
|
|
32
|
+
|
|
29
33
|
let fallbackCustomHighlightSeq = 0
|
|
30
34
|
|
|
31
35
|
const nextCustomHighlightId = (env, prefix) => {
|
|
@@ -45,23 +49,30 @@ const buildApiPreAttrs = (preWrapValue, wrapEnabled, opt) => {
|
|
|
45
49
|
return preAttrs
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
const renderFenceApiOrPlain = (token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue,
|
|
52
|
+
const renderFenceApiOrPlain = (token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberSetValue, wrapEnabled, preWrapValue, commentMarkValue, lineNotes, lineNoteIdPrefix, includePayload, timings) => {
|
|
49
53
|
const isSamp = opt._sampReg.test(lang)
|
|
50
54
|
const tag = isSamp ? 'samp' : 'code'
|
|
51
55
|
let content = md.utils.escapeHtml(token.content)
|
|
52
56
|
const lineStrategy = opt.customHighlight.lineFeatureStrategy
|
|
53
57
|
const needLineNumber = lineStrategy === 'hybrid' && opt.setLineNumber && startNumber >= 0
|
|
54
58
|
const needEndSpan = lineStrategy === 'hybrid' && opt.lineEndSpanThreshold > 0
|
|
59
|
+
const needAdvancedLineNumberPlan = needLineNumber && (lineNumberSkipValue !== undefined || lineNumberSetValue !== undefined)
|
|
60
|
+
const needLineNotes = lineStrategy !== 'disable' && !!(lineNotes && lineNotes.length)
|
|
61
|
+
const logicalLineCount = (needAdvancedLineNumberPlan || needLineNotes)
|
|
62
|
+
? getLogicalLineCount(token.content)
|
|
63
|
+
: -1
|
|
55
64
|
let lineNumberPlan = null
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberResetValue, logicalLineCount, logicalLineCount)
|
|
65
|
+
if (needAdvancedLineNumberPlan) {
|
|
66
|
+
lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberSetValue, logicalLineCount, logicalLineCount)
|
|
59
67
|
}
|
|
60
|
-
|
|
68
|
+
const lineNotePlan = needLineNotes
|
|
69
|
+
? resolveLineNotesPlan(lineNotes, logicalLineCount, logicalLineCount, lineNoteIdPrefix)
|
|
70
|
+
: null
|
|
71
|
+
if (needLineNumber || needEndSpan || lineNotePlan) {
|
|
61
72
|
const splitStartedAt = timings ? getNowMs() : 0
|
|
62
73
|
const nlIndex = content.indexOf('\n')
|
|
63
74
|
const br = nlIndex > 0 && content[nlIndex - 1] === '\r' ? '\r\n' : '\n'
|
|
64
|
-
content = splitFenceBlockToLines(content, [], needLineNumber, false, needEndSpan, opt.lineEndSpanThreshold, opt.lineEndSpanClass, br, undefined, commentLineClass, lineNumberPlan)
|
|
75
|
+
content = splitFenceBlockToLines(content, [], needLineNumber, false, needEndSpan, opt.lineEndSpanThreshold, opt.lineEndSpanClass, br, undefined, commentLineClass, lineNumberPlan, lineNotePlan)
|
|
65
76
|
if (timings) addTimingMs(timings, 'lineSplitMs', getNowMs() - splitStartedAt)
|
|
66
77
|
}
|
|
67
78
|
|
|
@@ -88,7 +99,8 @@ const renderFenceApiOrPlain = (token, lang, md, opt, slf, env, startNumber, emph
|
|
|
88
99
|
|
|
89
100
|
if (preAttrs.length) orderTokenAttrs({ attrs: preAttrs }, opt)
|
|
90
101
|
const preAttrsText = preAttrs.length ? slf.renderAttrs({ attrs: preAttrs }) : ''
|
|
91
|
-
const
|
|
102
|
+
const preHtml = `<pre${preAttrsText}><${tag}${slf.renderAttrs(token)}>${content}</${tag}></pre>`
|
|
103
|
+
const html = wrapFencePreWithLineNotes(preHtml, lineNotePlan)
|
|
92
104
|
return inlinePayloadScript ? html + inlinePayloadScript : html
|
|
93
105
|
}
|
|
94
106
|
|
|
@@ -101,10 +113,12 @@ const getFenceHtml = (context, md, opt, slf, env) => {
|
|
|
101
113
|
const startNumber = context.startNumber
|
|
102
114
|
const emphasizeLines = context.emphasizeLines
|
|
103
115
|
const lineNumberSkipValue = context.lineNumberSkipValue
|
|
104
|
-
const
|
|
116
|
+
const lineNumberSetValue = context.lineNumberSetValue
|
|
105
117
|
const wrapEnabled = context.wrapEnabled
|
|
106
118
|
const preWrapValue = context.preWrapValue
|
|
107
119
|
const commentMarkValue = context.commentMarkValue
|
|
120
|
+
const lineNotes = context.lineNotes
|
|
121
|
+
const lineNoteIdPrefix = context.lineNoteIdPrefix
|
|
108
122
|
|
|
109
123
|
const apiDecisionBase = {
|
|
110
124
|
renderer: 'api',
|
|
@@ -112,11 +126,11 @@ const getFenceHtml = (context, md, opt, slf, env) => {
|
|
|
112
126
|
fallbackUsed: false,
|
|
113
127
|
lineFeatureStrategy: opt.customHighlight.lineFeatureStrategy,
|
|
114
128
|
disabledFeatures: opt.customHighlight.lineFeatureStrategy === 'disable'
|
|
115
|
-
?
|
|
129
|
+
? apiDisableLineFeatureList
|
|
116
130
|
: [],
|
|
117
131
|
}
|
|
118
132
|
try {
|
|
119
|
-
const html = renderFenceApiOrPlain(token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue,
|
|
133
|
+
const html = renderFenceApiOrPlain(token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberSetValue, wrapEnabled, preWrapValue, commentMarkValue, lineNotes, lineNoteIdPrefix, true, timings)
|
|
120
134
|
if (timingEnabled) apiDecisionBase.timings = finalizeFenceTimings(timings, fenceStartedAt)
|
|
121
135
|
emitFenceDecision(opt, apiDecisionBase)
|
|
122
136
|
return html
|
|
@@ -130,7 +144,7 @@ const getFenceHtml = (context, md, opt, slf, env) => {
|
|
|
130
144
|
reason: 'provider-error',
|
|
131
145
|
lineFeatureStrategy: opt.customHighlight.lineFeatureStrategy,
|
|
132
146
|
}
|
|
133
|
-
const html = renderFenceApiOrPlain(token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue,
|
|
147
|
+
const html = renderFenceApiOrPlain(token, lang, md, opt, slf, env, startNumber, emphasizeLines, lineNumberSkipValue, lineNumberSetValue, wrapEnabled, preWrapValue, commentMarkValue, lineNotes, lineNoteIdPrefix, false, timings)
|
|
134
148
|
if (timingEnabled) fallbackDecision.timings = finalizeFenceTimings(timings, fenceStartedAt)
|
|
135
149
|
emitFenceDecision(opt, fallbackDecision)
|
|
136
150
|
return html
|
|
@@ -4,6 +4,9 @@ import {
|
|
|
4
4
|
getPayloadMapFromScript,
|
|
5
5
|
styleToHighlightCss,
|
|
6
6
|
} from '../custom-highlight/runtime-utils.js'
|
|
7
|
+
import {
|
|
8
|
+
sanitizeHighlightName,
|
|
9
|
+
} from '../custom-highlight/scope-name.js'
|
|
7
10
|
import {
|
|
8
11
|
customHighlightAppliedAttr,
|
|
9
12
|
customHighlightAppliedSelector,
|
|
@@ -19,17 +22,6 @@ const globalRuntimeInsertedScopeStyles = new Map()
|
|
|
19
22
|
const runtimeInsertedScopeStylesByDoc = new WeakMap()
|
|
20
23
|
const runtimeApplyStateByRoot = new WeakMap()
|
|
21
24
|
const validColorSchemeSet = new Set(['light', 'dark', 'auto'])
|
|
22
|
-
const highlightNameUnsafeReg = /[^A-Za-z0-9_-]+/g
|
|
23
|
-
const hyphenMultiReg = /-+/g
|
|
24
|
-
|
|
25
|
-
const sanitizeHighlightName = (name) => {
|
|
26
|
-
const raw = String(name || '')
|
|
27
|
-
let safe = raw.replace(highlightNameUnsafeReg, '-').replace(hyphenMultiReg, '-').replace(/^-+|-+$/g, '')
|
|
28
|
-
if (!safe) safe = 'scope'
|
|
29
|
-
if (/^[0-9]/.test(safe)) safe = 'x-' + safe
|
|
30
|
-
if (safe.startsWith('--')) safe = safe.slice(2) || 'scope'
|
|
31
|
-
return safe
|
|
32
|
-
}
|
|
33
25
|
|
|
34
26
|
const getCustomHighlightBlockId = (node) => {
|
|
35
27
|
if (!node || typeof node.getAttribute !== 'function') return null
|
|
@@ -133,10 +125,10 @@ const addMediaQueryChangeListener = (queryList, listener) => {
|
|
|
133
125
|
}
|
|
134
126
|
|
|
135
127
|
const getRuntimeSupportedPayloadVersions = (options = {}) => {
|
|
136
|
-
const set = new Set()
|
|
137
128
|
if (options.strictVersion === true) {
|
|
138
|
-
|
|
129
|
+
return new Set(customHighlightPayloadSupportedVersions)
|
|
139
130
|
}
|
|
131
|
+
const set = new Set()
|
|
140
132
|
if (Number.isSafeInteger(options.supportedVersion)) set.add(options.supportedVersion)
|
|
141
133
|
if (Array.isArray(options.supportedVersions)) {
|
|
142
134
|
for (const v of options.supportedVersions) {
|
package/src/fence/render-api.js
CHANGED
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
finalizeCommonFenceOption,
|
|
8
8
|
prepareFenceRenderContext,
|
|
9
9
|
} from './render-shared.js'
|
|
10
|
+
import {
|
|
11
|
+
installLineNotesCoreRule,
|
|
12
|
+
} from './line-notes.js'
|
|
10
13
|
import {
|
|
11
14
|
customHighlightDataEnvKey,
|
|
12
15
|
customHighlightEnvInitRuleName,
|
|
@@ -73,6 +76,7 @@ const mditRendererFenceCustomHighlight = (md, option) => {
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
finalizeCommonFenceOption(opt)
|
|
79
|
+
installLineNotesCoreRule(md)
|
|
76
80
|
|
|
77
81
|
md.core.ruler.before('block', customHighlightEnvInitRuleName, (state) => {
|
|
78
82
|
const env = state && state.env
|
|
@@ -14,12 +14,16 @@ import {
|
|
|
14
14
|
orderTokenAttrs,
|
|
15
15
|
preWrapStyle,
|
|
16
16
|
resolveAdvancedLineNumberPlan,
|
|
17
|
+
resolveLineNotesPlan,
|
|
17
18
|
splitFenceBlockToLines,
|
|
19
|
+
wrapFencePreWithLineNotes,
|
|
18
20
|
} from './render-shared.js'
|
|
19
21
|
import {
|
|
20
22
|
parsePreCodeWrapper,
|
|
21
23
|
} from '../utils/pre-code-wrapper-parser.js'
|
|
22
24
|
|
|
25
|
+
const markupPassthroughDisabledFeatures = Object.freeze(['setLineNumber', 'line-number-skip', 'line-number-set', 'setEmphasizeLines', 'lineEndSpanThreshold', 'comment-mark', 'line-notes', 'samp'])
|
|
26
|
+
|
|
23
27
|
const renderFenceMarkup = (context, md, opt, slf) => {
|
|
24
28
|
const token = context.token
|
|
25
29
|
const lang = context.lang
|
|
@@ -29,10 +33,12 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
29
33
|
const startNumber = context.startNumber
|
|
30
34
|
const emphasizeLines = context.emphasizeLines
|
|
31
35
|
const lineNumberSkipValue = context.lineNumberSkipValue
|
|
32
|
-
const
|
|
36
|
+
const lineNumberSetValue = context.lineNumberSetValue
|
|
33
37
|
const wrapEnabled = context.wrapEnabled
|
|
34
38
|
const preWrapValue = context.preWrapValue
|
|
35
39
|
const commentMarkValue = context.commentMarkValue
|
|
40
|
+
const lineNotes = context.lineNotes
|
|
41
|
+
const lineNoteIdPrefix = context.lineNoteIdPrefix
|
|
36
42
|
|
|
37
43
|
const isSamp = opt._sampReg.test(lang)
|
|
38
44
|
const sourceContent = token.content
|
|
@@ -76,7 +82,7 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
76
82
|
passthrough: true,
|
|
77
83
|
passthroughReason: 'pre-code-parse-failed',
|
|
78
84
|
hasHighlightPre: false,
|
|
79
|
-
disabledFeatures:
|
|
85
|
+
disabledFeatures: markupPassthroughDisabledFeatures,
|
|
80
86
|
}
|
|
81
87
|
if (timingEnabled) decision.timings = finalizeFenceTimings(timings, fenceStartedAt)
|
|
82
88
|
emitFenceDecision(opt, decision)
|
|
@@ -105,7 +111,8 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
105
111
|
if (preAttrs.length) orderTokenAttrs({ attrs: preAttrs }, opt)
|
|
106
112
|
const preAttrsText = preAttrs.length ? slf.renderAttrs({ attrs: preAttrs }) : ''
|
|
107
113
|
|
|
108
|
-
const
|
|
114
|
+
const useHighlightPre = opt.useHighlightPre && hasHighlightPre
|
|
115
|
+
const needLineNumber = !useHighlightPre && opt.setLineNumber && startNumber >= 0
|
|
109
116
|
let sourceLogicalLineCount = -1
|
|
110
117
|
let highlightedLogicalLineCount = -1
|
|
111
118
|
const ensureLogicalLineCounts = () => {
|
|
@@ -113,18 +120,22 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
113
120
|
if (highlightedLogicalLineCount === -1) highlightedLogicalLineCount = getLogicalLineCount(content)
|
|
114
121
|
}
|
|
115
122
|
let normalizedEmphasis = []
|
|
116
|
-
if (opt.setEmphasizeLines && emphasizeLines.length > 0) {
|
|
123
|
+
if (!useHighlightPre && opt.setEmphasizeLines && emphasizeLines.length > 0) {
|
|
117
124
|
ensureLogicalLineCounts()
|
|
118
125
|
normalizedEmphasis = normalizeEmphasisRanges(emphasizeLines, highlightedLogicalLineCount)
|
|
119
126
|
}
|
|
120
127
|
let lineNumberPlan = null
|
|
121
|
-
if (needLineNumber && (lineNumberSkipValue !== undefined ||
|
|
128
|
+
if (needLineNumber && (lineNumberSkipValue !== undefined || lineNumberSetValue !== undefined)) {
|
|
122
129
|
ensureLogicalLineCounts()
|
|
123
|
-
lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue,
|
|
130
|
+
lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberSetValue, sourceLogicalLineCount, highlightedLogicalLineCount)
|
|
131
|
+
}
|
|
132
|
+
let lineNotePlan = null
|
|
133
|
+
if (!useHighlightPre && lineNotes && lineNotes.length) {
|
|
134
|
+
ensureLogicalLineCounts()
|
|
135
|
+
lineNotePlan = resolveLineNotesPlan(lineNotes, sourceLogicalLineCount, highlightedLogicalLineCount, lineNoteIdPrefix)
|
|
124
136
|
}
|
|
125
137
|
const needEmphasis = normalizedEmphasis.length > 0
|
|
126
138
|
const needEndSpan = opt.lineEndSpanThreshold > 0
|
|
127
|
-
const useHighlightPre = opt.useHighlightPre && hasHighlightPre
|
|
128
139
|
|
|
129
140
|
if (!useHighlightPre && commentMarkValue && sourceContent.indexOf(commentMarkValue) !== -1) {
|
|
130
141
|
ensureLogicalLineCounts()
|
|
@@ -140,7 +151,7 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
140
151
|
}
|
|
141
152
|
}
|
|
142
153
|
|
|
143
|
-
if (!useHighlightPre && (needLineNumber || needEmphasis || needEndSpan || needComment)) {
|
|
154
|
+
if (!useHighlightPre && (needLineNumber || needEmphasis || needEndSpan || needComment || lineNotePlan)) {
|
|
144
155
|
const splitStartedAt = timingEnabled ? getNowMs() : 0
|
|
145
156
|
const nlIndex = content.indexOf('\n')
|
|
146
157
|
const br = nlIndex > 0 && content[nlIndex - 1] === '\r' ? '\r\n' : '\n'
|
|
@@ -156,6 +167,7 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
156
167
|
commentLines,
|
|
157
168
|
commentLineClass,
|
|
158
169
|
lineNumberPlan,
|
|
170
|
+
lineNotePlan,
|
|
159
171
|
)
|
|
160
172
|
if (timingEnabled) addTimingMs(timings, 'lineSplitMs', getNowMs() - splitStartedAt)
|
|
161
173
|
}
|
|
@@ -165,11 +177,12 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
165
177
|
renderer: 'markup',
|
|
166
178
|
useHighlightPre,
|
|
167
179
|
hasHighlightPre,
|
|
168
|
-
disabledFeatures: useHighlightPre ?
|
|
180
|
+
disabledFeatures: useHighlightPre ? markupPassthroughDisabledFeatures : [],
|
|
169
181
|
}
|
|
170
182
|
if (timingEnabled) decision.timings = finalizeFenceTimings(timings, fenceStartedAt)
|
|
171
183
|
emitFenceDecision(opt, decision)
|
|
172
|
-
|
|
184
|
+
const preHtml = `<pre${preAttrsText}><${tag}${slf.renderAttrs(token)}>${content}</${tag}></pre>`
|
|
185
|
+
return wrapFencePreWithLineNotes(preHtml, lineNotePlan)
|
|
173
186
|
}
|
|
174
187
|
|
|
175
188
|
export {
|
|
@@ -4,20 +4,36 @@ import {
|
|
|
4
4
|
getInfoAttr,
|
|
5
5
|
getLangFromClassAttr,
|
|
6
6
|
} from '../utils/attr-utils.js'
|
|
7
|
+
import {
|
|
8
|
+
getTokenLineNotes,
|
|
9
|
+
} from './line-notes.js'
|
|
7
10
|
|
|
8
11
|
const infoReg = /^([^{\s]*)(?:\s*\{(.*)\})?$/
|
|
9
12
|
const tagReg = /<\/?([A-Za-z][A-Za-z0-9-]*)(?:\s+[^>]*?)?\/?\s*>/g
|
|
10
13
|
const preLineTag = '<span class="pre-line">'
|
|
11
14
|
const preLineNoNumberClass = 'pre-line-no-number'
|
|
15
|
+
const preLineHasEndNoteClass = 'pre-line-has-end-note'
|
|
16
|
+
const preLineContentClass = 'pre-line-content'
|
|
12
17
|
const emphOpenTag = '<span class="pre-lines-emphasis">'
|
|
13
18
|
const commentLineClass = 'pre-line-comment'
|
|
19
|
+
const preWithLineNotesClass = 'pre-wrapper-line-notes'
|
|
20
|
+
const lineNoteLayerClass = 'pre-line-note-layer'
|
|
21
|
+
const lineNoteClass = 'pre-line-note'
|
|
14
22
|
const closeTag = '</span>'
|
|
15
23
|
const closeTagLen = closeTag.length
|
|
24
|
+
const defaultPreLineTags = { open: preLineTag, close: closeTag }
|
|
16
25
|
const preWrapStyle = 'white-space: pre-wrap; overflow-wrap: anywhere;'
|
|
17
26
|
const voidTags = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'])
|
|
18
27
|
const nonNegativeIntReg = /^\d+$/
|
|
19
28
|
const positiveIntReg = /^[1-9]\d*$/
|
|
20
29
|
|
|
30
|
+
const getLineNoteIdPrefix = (token, idx) => {
|
|
31
|
+
const tokenMap = token && Array.isArray(token.map) ? token.map : null
|
|
32
|
+
const mapStart = tokenMap && Number.isSafeInteger(tokenMap[0]) ? tokenMap[0] + 1 : 0
|
|
33
|
+
const tokenIndex = Number.isSafeInteger(idx) ? idx + 1 : 0
|
|
34
|
+
return `pre-line-note-${mapStart}-${tokenIndex}`
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
const getLineVisualLengthIgnoringTags = (line, threshold) => {
|
|
22
38
|
let len = 0
|
|
23
39
|
let inTag = false
|
|
@@ -89,6 +105,7 @@ const createCommonFenceOptionDefaults = (md) => {
|
|
|
89
105
|
const finalizeCommonFenceOption = (opt) => {
|
|
90
106
|
opt._sampReg = new RegExp('^(?:samp|' + opt.sampLang.split(',').join('|') + ')$')
|
|
91
107
|
opt._attrOrderIndex = createAttrOrderIndexGetter(opt.attrsOrder || [])
|
|
108
|
+
opt._attrOrderRankCache = new Map()
|
|
92
109
|
return opt
|
|
93
110
|
}
|
|
94
111
|
|
|
@@ -115,6 +132,13 @@ const parsePositiveLineIndex = (val) => {
|
|
|
115
132
|
return num
|
|
116
133
|
}
|
|
117
134
|
|
|
135
|
+
const escapeHtmlText = (value) => {
|
|
136
|
+
return String(value ?? '')
|
|
137
|
+
.replace(/&/g, '&')
|
|
138
|
+
.replace(/</g, '<')
|
|
139
|
+
.replace(/>/g, '>')
|
|
140
|
+
}
|
|
141
|
+
|
|
118
142
|
const getLogicalLineCount = (text) => {
|
|
119
143
|
const str = String(text ?? '')
|
|
120
144
|
if (!str) return 0
|
|
@@ -190,7 +214,7 @@ const normalizeEmphasisRanges = (emphasizeLines, maxLine) => {
|
|
|
190
214
|
return normalized
|
|
191
215
|
}
|
|
192
216
|
|
|
193
|
-
const
|
|
217
|
+
const getLineNumberSetEntries = (attrVal) => {
|
|
194
218
|
const str = String(attrVal ?? '')
|
|
195
219
|
if (!str) return []
|
|
196
220
|
const result = []
|
|
@@ -200,9 +224,9 @@ const getLineNumberResetEntries = (attrVal) => {
|
|
|
200
224
|
const colon = part.indexOf(':')
|
|
201
225
|
if (colon === -1) continue
|
|
202
226
|
const lineNumber = parsePositiveLineIndex(part.slice(0, colon).trim())
|
|
203
|
-
const
|
|
204
|
-
if (lineNumber == null ||
|
|
205
|
-
result.push([lineNumber,
|
|
227
|
+
const setNumber = parseStartNumber(part.slice(colon + 1).trim())
|
|
228
|
+
if (lineNumber == null || setNumber == null) continue
|
|
229
|
+
result.push([lineNumber, setNumber])
|
|
206
230
|
}
|
|
207
231
|
return result
|
|
208
232
|
}
|
|
@@ -218,59 +242,142 @@ const createSparseLineFlagMap = (ranges, maxLine) => {
|
|
|
218
242
|
return flags
|
|
219
243
|
}
|
|
220
244
|
|
|
221
|
-
const
|
|
245
|
+
const createSparseLineSetMap = (entries, maxLine, hidden) => {
|
|
222
246
|
if (!entries || !entries.length || maxLine <= 0) return null
|
|
223
|
-
const
|
|
247
|
+
const sets = []
|
|
224
248
|
let hasValue = false
|
|
225
249
|
for (let i = 0; i < entries.length; i++) {
|
|
226
250
|
const line = entries[i][0]
|
|
227
251
|
const value = entries[i][1]
|
|
228
252
|
if (!Number.isSafeInteger(line) || line <= 0 || line > maxLine) continue
|
|
229
|
-
|
|
253
|
+
if (hidden && hidden[line - 1]) continue
|
|
254
|
+
sets[line - 1] = value
|
|
230
255
|
hasValue = true
|
|
231
256
|
}
|
|
232
|
-
return hasValue ?
|
|
257
|
+
return hasValue ? sets : null
|
|
233
258
|
}
|
|
234
259
|
|
|
235
|
-
const buildAdvancedLineNumberPlan = (skipRanges,
|
|
260
|
+
const buildAdvancedLineNumberPlan = (skipRanges, setEntries, lineCount) => {
|
|
236
261
|
const hasSkip = !!(skipRanges && skipRanges.length)
|
|
237
|
-
const
|
|
238
|
-
if (!hasSkip && !
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (!hidden && !resets) return null
|
|
244
|
-
return { hidden, resets }
|
|
262
|
+
const hasSet = !!(setEntries && setEntries.length)
|
|
263
|
+
if (!hasSkip && !hasSet) return null
|
|
264
|
+
const hidden = hasSkip ? createSparseLineFlagMap(skipRanges, lineCount) : null
|
|
265
|
+
const sets = hasSet ? createSparseLineSetMap(setEntries, lineCount, hidden) : null
|
|
266
|
+
if (!hidden && !sets) return null
|
|
267
|
+
return { hidden, sets }
|
|
245
268
|
}
|
|
246
269
|
|
|
247
|
-
const resolveAdvancedLineNumberPlan = (lineNumberSkipValue,
|
|
270
|
+
const resolveAdvancedLineNumberPlan = (lineNumberSkipValue, lineNumberSetValue, sourceLineCount, renderedLineCount) => {
|
|
248
271
|
const hasSkipValue = lineNumberSkipValue !== undefined
|
|
249
|
-
const
|
|
250
|
-
if (!hasSkipValue && !
|
|
272
|
+
const hasSetValue = lineNumberSetValue !== undefined
|
|
273
|
+
if (!hasSkipValue && !hasSetValue) return null
|
|
251
274
|
if (!Number.isSafeInteger(sourceLineCount) || sourceLineCount <= 0) return null
|
|
252
275
|
if (sourceLineCount !== renderedLineCount) return null
|
|
253
276
|
const skipRanges = hasSkipValue ? getLineNumberSkipRanges(lineNumberSkipValue) : null
|
|
254
|
-
const
|
|
255
|
-
return buildAdvancedLineNumberPlan(skipRanges,
|
|
277
|
+
const setEntries = hasSetValue ? getLineNumberSetEntries(lineNumberSetValue) : null
|
|
278
|
+
return buildAdvancedLineNumberPlan(skipRanges, setEntries, sourceLineCount)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const createLineNotePlan = (lineNotes, maxLine, idPrefix) => {
|
|
282
|
+
if (!Array.isArray(lineNotes) || !lineNotes.length || maxLine <= 0) return null
|
|
283
|
+
const anchors = []
|
|
284
|
+
const items = []
|
|
285
|
+
const noteIdPrefix = String(idPrefix || 'pre-line-note')
|
|
286
|
+
let prevFrom = 0
|
|
287
|
+
let prevTo = 0
|
|
288
|
+
let needsSort = false
|
|
289
|
+
for (let i = 0; i < lineNotes.length; i++) {
|
|
290
|
+
const note = lineNotes[i]
|
|
291
|
+
if (!note || !Number.isSafeInteger(note.from) || !Number.isSafeInteger(note.to)) continue
|
|
292
|
+
let from = note.from
|
|
293
|
+
let to = note.to
|
|
294
|
+
if (from > to) {
|
|
295
|
+
const swap = from
|
|
296
|
+
from = to
|
|
297
|
+
to = swap
|
|
298
|
+
}
|
|
299
|
+
if (to < 1 || from > maxLine) continue
|
|
300
|
+
if (from < 1) from = 1
|
|
301
|
+
if (to > maxLine) to = maxLine
|
|
302
|
+
if (anchors[from - 1] !== undefined) return null
|
|
303
|
+
const lineCount = Number.isSafeInteger(note.lineCount) && note.lineCount > 0
|
|
304
|
+
? note.lineCount
|
|
305
|
+
: Math.max(getLogicalLineCount(note.text), 1)
|
|
306
|
+
const item = {
|
|
307
|
+
from,
|
|
308
|
+
to,
|
|
309
|
+
label: from === to ? String(from) : `${from}-${to}`,
|
|
310
|
+
html: escapeHtmlText(note.text),
|
|
311
|
+
anchorName: `--pre-line-note-${from}`,
|
|
312
|
+
id: `${noteIdPrefix}-${from}`,
|
|
313
|
+
lineCount,
|
|
314
|
+
width: note.width || '',
|
|
315
|
+
}
|
|
316
|
+
anchors[from - 1] = item
|
|
317
|
+
items.push(item)
|
|
318
|
+
if (!needsSort && items.length > 1 && (from < prevFrom || (from === prevFrom && to < prevTo))) {
|
|
319
|
+
needsSort = true
|
|
320
|
+
}
|
|
321
|
+
prevFrom = from
|
|
322
|
+
prevTo = to
|
|
323
|
+
}
|
|
324
|
+
if (!items.length) return null
|
|
325
|
+
|
|
326
|
+
if (needsSort) items.sort((a, b) => a.from - b.from || a.to - b.to)
|
|
327
|
+
let canAnchor = true
|
|
328
|
+
let previousVisualEnd = 0
|
|
329
|
+
let overflowLines = 0
|
|
330
|
+
for (let i = 0; i < items.length; i++) {
|
|
331
|
+
const item = items[i]
|
|
332
|
+
const visualEnd = item.from + item.lineCount - 1
|
|
333
|
+
if (item.from <= previousVisualEnd) canAnchor = false
|
|
334
|
+
if (visualEnd > maxLine) overflowLines = Math.max(overflowLines, visualEnd - maxLine)
|
|
335
|
+
previousVisualEnd = visualEnd
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
anchors,
|
|
340
|
+
items,
|
|
341
|
+
canAnchor,
|
|
342
|
+
overflowLines,
|
|
343
|
+
}
|
|
256
344
|
}
|
|
257
345
|
|
|
258
|
-
const
|
|
259
|
-
if (!
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
346
|
+
const resolveLineNotesPlan = (lineNotes, sourceLineCount, renderedLineCount, idPrefix) => {
|
|
347
|
+
if (!Array.isArray(lineNotes) || !lineNotes.length) return null
|
|
348
|
+
if (!Number.isSafeInteger(sourceLineCount) || sourceLineCount <= 0) return null
|
|
349
|
+
if (sourceLineCount !== renderedLineCount) return null
|
|
350
|
+
return createLineNotePlan(lineNotes, sourceLineCount, idPrefix)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const buildPreLineTags = (hidden, setNumber, lineNote) => {
|
|
354
|
+
if (!hidden && setNumber == null && !lineNote) return defaultPreLineTags
|
|
263
355
|
let tag = '<span class="pre-line'
|
|
264
356
|
if (hidden) tag += ' ' + preLineNoNumberClass
|
|
357
|
+
if (lineNote) tag += ' ' + preLineHasEndNoteClass
|
|
265
358
|
tag += '"'
|
|
266
|
-
if (
|
|
267
|
-
|
|
359
|
+
if (setNumber != null) tag += ` style="counter-set:pre-line-number ${setNumber};"`
|
|
360
|
+
if (lineNote) {
|
|
361
|
+
tag += ` data-pre-line-note-from="${lineNote.from}"`
|
|
362
|
+
tag += ` data-pre-line-note-to="${lineNote.to}"`
|
|
363
|
+
}
|
|
364
|
+
tag += '>'
|
|
365
|
+
if (!lineNote) return { open: tag, close: closeTag }
|
|
366
|
+
const describedByAttr = lineNote.id ? ` aria-describedby="${lineNote.id}"` : ''
|
|
367
|
+
return {
|
|
368
|
+
open: `${tag}<span class="${preLineContentClass}" style="anchor-name:${lineNote.anchorName};"${describedByAttr}>`,
|
|
369
|
+
close: `${closeTag}${closeTag}`,
|
|
370
|
+
}
|
|
268
371
|
}
|
|
269
372
|
|
|
270
|
-
const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, threshold, lineEndSpanClass, br, commentLines, commentClass, lineNumberPlan) => {
|
|
373
|
+
const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, threshold, lineEndSpanClass, br, commentLines, commentClass, lineNumberPlan, lineNotePlan) => {
|
|
271
374
|
const lines = content.split(br)
|
|
272
375
|
const max = lines.length
|
|
273
|
-
const
|
|
376
|
+
const lineNumberHidden = lineNumberPlan ? lineNumberPlan.hidden : null
|
|
377
|
+
const lineNumberSets = lineNumberPlan ? lineNumberPlan.sets : null
|
|
378
|
+
const lineNoteAnchors = lineNotePlan ? lineNotePlan.anchors : null
|
|
379
|
+
const hasLineNotes = !!lineNoteAnchors
|
|
380
|
+
const needLineWrap = needLineNumber || hasLineNotes
|
|
274
381
|
let emIdx = 0
|
|
275
382
|
let emStart = -1
|
|
276
383
|
let emEnd = -1
|
|
@@ -287,6 +394,9 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
|
|
|
287
394
|
let line = lines[n]
|
|
288
395
|
const notLastLine = n < max - 1
|
|
289
396
|
const doComment = commentLines && commentLines[n]
|
|
397
|
+
const hidden = !!(lineNumberHidden && lineNumberHidden[n])
|
|
398
|
+
const setNumber = lineNumberSets ? lineNumberSets[n] : undefined
|
|
399
|
+
const lineNote = hasLineNotes ? lineNoteAnchors[n] : null
|
|
290
400
|
let hasLt = false
|
|
291
401
|
let hasLtChecked = false
|
|
292
402
|
|
|
@@ -310,7 +420,7 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
|
|
|
310
420
|
}
|
|
311
421
|
}
|
|
312
422
|
|
|
313
|
-
if (
|
|
423
|
+
if (needLineWrap && notLastLine) {
|
|
314
424
|
if (!hasLtChecked) {
|
|
315
425
|
hasLt = line.indexOf('<') !== -1
|
|
316
426
|
hasLtChecked = true
|
|
@@ -340,8 +450,9 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
|
|
|
340
450
|
line = `<span class="${commentClass}">` + line + closeTag
|
|
341
451
|
}
|
|
342
452
|
|
|
343
|
-
if (
|
|
344
|
-
|
|
453
|
+
if (needLineWrap && notLastLine) {
|
|
454
|
+
const lineTags = buildPreLineTags(hidden, setNumber, lineNote)
|
|
455
|
+
line = lineTags.open + line + lineTags.close
|
|
345
456
|
}
|
|
346
457
|
|
|
347
458
|
if (needEmphasis && emIdx < emphasizeLines.length && emStart === n + 1) {
|
|
@@ -360,10 +471,33 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
|
|
|
360
471
|
return lines.join(br)
|
|
361
472
|
}
|
|
362
473
|
|
|
474
|
+
const renderLineNotesLayer = (lineNotePlan) => {
|
|
475
|
+
const items = lineNotePlan && lineNotePlan.items
|
|
476
|
+
if (!items || !items.length) return ''
|
|
477
|
+
let html = `<div class="${lineNoteLayerClass}">`
|
|
478
|
+
for (let i = 0; i < items.length; i++) {
|
|
479
|
+
const item = items[i]
|
|
480
|
+
let style = `position-anchor:${item.anchorName};`
|
|
481
|
+
if (item.width) style += ` --line-note-width:${item.width};`
|
|
482
|
+
html += `<div id="${item.id}" class="${lineNoteClass}" role="note" data-pre-line-note-from="${item.from}" data-pre-line-note-to="${item.to}" data-pre-line-note-label="${item.label}" style="${style}">${item.html}</div>`
|
|
483
|
+
}
|
|
484
|
+
html += '</div>'
|
|
485
|
+
return html
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const wrapFencePreWithLineNotes = (preHtml, lineNotePlan) => {
|
|
489
|
+
const items = lineNotePlan && lineNotePlan.items
|
|
490
|
+
if (!items || !items.length) return preHtml + '\n'
|
|
491
|
+
let attrs = ` class="${preWithLineNotesClass}"`
|
|
492
|
+
if (lineNotePlan.canAnchor) attrs += ' data-pre-line-notes-layout="anchor"'
|
|
493
|
+
if (lineNotePlan.overflowLines > 0) attrs += ` style="--pre-line-note-overflow-lines:${lineNotePlan.overflowLines};"`
|
|
494
|
+
return `<div${attrs}>${preHtml}${renderLineNotesLayer(lineNotePlan)}</div>\n`
|
|
495
|
+
}
|
|
496
|
+
|
|
363
497
|
const orderTokenAttrs = (token, opt) => {
|
|
364
498
|
const attrs = token.attrs
|
|
365
499
|
if (!attrs || attrs.length < 2) return
|
|
366
|
-
const rankCache = new Map()
|
|
500
|
+
const rankCache = opt._attrOrderRankCache || new Map()
|
|
367
501
|
const getRank = (name) => {
|
|
368
502
|
let rank = rankCache.get(name)
|
|
369
503
|
if (rank === undefined) {
|
|
@@ -399,10 +533,12 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
399
533
|
let startNumber = -1
|
|
400
534
|
let emphasizeLines = []
|
|
401
535
|
let lineNumberSkipValue
|
|
402
|
-
let
|
|
536
|
+
let lineNumberSetValue
|
|
403
537
|
let wrapEnabled = false
|
|
404
538
|
let preWrapValue
|
|
405
539
|
let commentMarkValue
|
|
540
|
+
const lineNotes = getTokenLineNotes(token)
|
|
541
|
+
const lineNoteIdPrefix = lineNotes && lineNotes.length ? getLineNoteIdPrefix(token, idx) : ''
|
|
406
542
|
const attrNormalizeStartedAt = timingEnabled ? getNowMs() : 0
|
|
407
543
|
|
|
408
544
|
if (token.attrs) {
|
|
@@ -412,7 +548,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
412
548
|
let styleIndex = -1
|
|
413
549
|
let dataPreCommentIndex = -1
|
|
414
550
|
let dataPreLineNumberSkipIndex = -1
|
|
415
|
-
let
|
|
551
|
+
let dataPreLineNumberSetIndex = -1
|
|
416
552
|
let startValue
|
|
417
553
|
let emphasisValue
|
|
418
554
|
let styleValue
|
|
@@ -420,7 +556,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
420
556
|
let sawStartAttr = false
|
|
421
557
|
let sawEmphasisAttr = false
|
|
422
558
|
let sawLineNumberSkipAttr = false
|
|
423
|
-
let
|
|
559
|
+
let sawLineNumberSetAttr = false
|
|
424
560
|
const appendOrder = []
|
|
425
561
|
|
|
426
562
|
for (const attr of token.attrs) {
|
|
@@ -469,11 +605,13 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
469
605
|
lineNumberSkipValue = val
|
|
470
606
|
newAttrs.push(attr)
|
|
471
607
|
break
|
|
472
|
-
case 'data-pre-line-number-
|
|
473
|
-
|
|
474
|
-
|
|
608
|
+
case 'data-pre-line-number-set':
|
|
609
|
+
dataPreLineNumberSetIndex = newAttrs.length
|
|
610
|
+
lineNumberSetValue = val
|
|
475
611
|
newAttrs.push(attr)
|
|
476
612
|
break
|
|
613
|
+
case 'data-pre-line-number-reset':
|
|
614
|
+
break
|
|
477
615
|
case 'em-lines':
|
|
478
616
|
case 'emphasize-lines':
|
|
479
617
|
if (opt.setEmphasizeLines) emphasizeLines = getEmphasizeLines(val)
|
|
@@ -498,13 +636,16 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
498
636
|
sawLineNumberSkipAttr = true
|
|
499
637
|
}
|
|
500
638
|
break
|
|
639
|
+
case 'line-number-set':
|
|
640
|
+
case 'pre-line-number-set':
|
|
641
|
+
lineNumberSetValue = val
|
|
642
|
+
if (!sawLineNumberSetAttr) {
|
|
643
|
+
appendOrder.push('line-number-set')
|
|
644
|
+
sawLineNumberSetAttr = true
|
|
645
|
+
}
|
|
646
|
+
break
|
|
501
647
|
case 'line-number-reset':
|
|
502
648
|
case 'pre-line-number-reset':
|
|
503
|
-
lineNumberResetValue = val
|
|
504
|
-
if (!sawLineNumberResetAttr) {
|
|
505
|
-
appendOrder.push('line-number-reset')
|
|
506
|
-
sawLineNumberResetAttr = true
|
|
507
|
-
}
|
|
508
649
|
break
|
|
509
650
|
case 'data-pre-wrap':
|
|
510
651
|
preWrapValue = val
|
|
@@ -523,7 +664,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
523
664
|
if (emphasisValue !== undefined && dataPreEmphasisIndex >= 0) newAttrs[dataPreEmphasisIndex][1] = emphasisValue
|
|
524
665
|
if (commentMarkValue !== undefined && dataPreCommentIndex >= 0) newAttrs[dataPreCommentIndex][1] = commentMarkValue
|
|
525
666
|
if (lineNumberSkipValue !== undefined && dataPreLineNumberSkipIndex >= 0) newAttrs[dataPreLineNumberSkipIndex][1] = lineNumberSkipValue
|
|
526
|
-
if (
|
|
667
|
+
if (lineNumberSetValue !== undefined && dataPreLineNumberSetIndex >= 0) newAttrs[dataPreLineNumberSetIndex][1] = lineNumberSetValue
|
|
527
668
|
|
|
528
669
|
for (const kind of appendOrder) {
|
|
529
670
|
if (kind === 'start' && dataPreStartIndex === -1 && startValue !== undefined) {
|
|
@@ -534,8 +675,8 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
534
675
|
newAttrs.push(['data-pre-comment-mark', commentMarkValue])
|
|
535
676
|
} else if (kind === 'line-number-skip' && dataPreLineNumberSkipIndex === -1 && lineNumberSkipValue !== undefined) {
|
|
536
677
|
newAttrs.push(['data-pre-line-number-skip', lineNumberSkipValue])
|
|
537
|
-
} else if (kind === 'line-number-
|
|
538
|
-
newAttrs.push(['data-pre-line-number-
|
|
678
|
+
} else if (kind === 'line-number-set' && dataPreLineNumberSetIndex === -1 && lineNumberSetValue !== undefined) {
|
|
679
|
+
newAttrs.push(['data-pre-line-number-set', lineNumberSetValue])
|
|
539
680
|
}
|
|
540
681
|
}
|
|
541
682
|
|
|
@@ -553,6 +694,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
553
694
|
}
|
|
554
695
|
|
|
555
696
|
if (timingEnabled) addTimingMs(timings, 'attrNormalizeMs', getNowMs() - attrNormalizeStartedAt)
|
|
697
|
+
if (lineNotes && lineNotes.length) token.attrSet('data-pre-line-notes', 'true')
|
|
556
698
|
|
|
557
699
|
return {
|
|
558
700
|
token,
|
|
@@ -560,10 +702,12 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
560
702
|
startNumber,
|
|
561
703
|
emphasizeLines,
|
|
562
704
|
lineNumberSkipValue,
|
|
563
|
-
|
|
705
|
+
lineNumberSetValue,
|
|
564
706
|
wrapEnabled,
|
|
565
707
|
preWrapValue,
|
|
566
708
|
commentMarkValue,
|
|
709
|
+
lineNotes,
|
|
710
|
+
lineNoteIdPrefix,
|
|
567
711
|
timingEnabled,
|
|
568
712
|
timings,
|
|
569
713
|
fenceStartedAt,
|
|
@@ -586,5 +730,7 @@ export {
|
|
|
586
730
|
preWrapStyle,
|
|
587
731
|
prepareFenceRenderContext,
|
|
588
732
|
resolveAdvancedLineNumberPlan,
|
|
733
|
+
resolveLineNotesPlan,
|
|
589
734
|
splitFenceBlockToLines,
|
|
735
|
+
wrapFencePreWithLineNotes,
|
|
590
736
|
}
|
package/THIRD_PARTY_NOTICES.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# Third-Party Notices
|
|
2
|
-
|
|
3
|
-
This repository is licensed under MIT (see `LICENSE`), and also contains documentation assets derived from third-party themes.
|
|
4
|
-
|
|
5
|
-
## highlight.js theme references used in docs
|
|
6
|
-
|
|
7
|
-
Files:
|
|
8
|
-
|
|
9
|
-
- `docs/default-highlight-theme.css`
|
|
10
|
-
|
|
11
|
-
Source references:
|
|
12
|
-
|
|
13
|
-
- `highlight.js` style family (GitHub / GitHub Dark)
|
|
14
|
-
- Upstream paths (for reference):
|
|
15
|
-
- `node_modules/highlight.js/styles/github.css`
|
|
16
|
-
- `node_modules/highlight.js/styles/github-dark.css`
|
|
17
|
-
|
|
18
|
-
Upstream license:
|
|
19
|
-
|
|
20
|
-
- BSD 3-Clause License
|
|
21
|
-
- Copyright (c) 2006, Ivan Sagalaev.
|
|
22
|
-
|
|
23
|
-
Full upstream license text:
|
|
24
|
-
|
|
25
|
-
```text
|
|
26
|
-
BSD 3-Clause License
|
|
27
|
-
|
|
28
|
-
Copyright (c) 2006, Ivan Sagalaev.
|
|
29
|
-
All rights reserved.
|
|
30
|
-
|
|
31
|
-
Redistribution and use in source and binary forms, with or without
|
|
32
|
-
modification, are permitted provided that the following conditions are met:
|
|
33
|
-
|
|
34
|
-
* Redistributions of source code must retain the above copyright notice, this
|
|
35
|
-
list of conditions and the following disclaimer.
|
|
36
|
-
|
|
37
|
-
* Redistributions in binary form must reproduce the above copyright notice,
|
|
38
|
-
this list of conditions and the following disclaimer in the documentation
|
|
39
|
-
and/or other materials provided with the distribution.
|
|
40
|
-
|
|
41
|
-
* Neither the name of the copyright holder nor the names of its
|
|
42
|
-
contributors may be used to endorse or promote products derived from
|
|
43
|
-
this software without specific prior written permission.
|
|
44
|
-
|
|
45
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
46
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
47
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
48
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
49
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
50
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
51
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
52
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
53
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
54
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
55
|
-
```
|
|
56
|
-
|