@peaceroad/markdown-it-renderer-fence 0.4.1 → 0.5.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 +440 -117
- package/THIRD_PARTY_NOTICES.md +56 -0
- package/index.js +33 -503
- package/package.json +28 -4
- package/src/custom-highlight/option-validator.js +152 -0
- package/src/custom-highlight/payload-utils.js +43 -0
- package/src/custom-highlight/runtime-utils.js +83 -0
- package/src/custom-highlight/shiki-keyword-rules.js +407 -0
- package/src/custom-highlight/shiki-keyword.js +417 -0
- package/src/entry/custom-highlight-runtime.js +11 -0
- package/src/entry/custom-highlight.js +25 -0
- package/src/entry/markup-highlight.js +40 -0
- package/src/fence/render-api-constants.js +34 -0
- package/src/fence/render-api-provider.js +810 -0
- package/src/fence/render-api-renderer.js +136 -0
- package/src/fence/render-api-runtime.js +712 -0
- package/src/fence/render-api.js +117 -0
- package/src/fence/render-markup.js +161 -0
- package/src/fence/render-shared.js +428 -0
- package/src/utils/attr-utils.js +120 -0
- package/src/utils/pre-code-wrapper-parser.js +114 -0
package/README.md
CHANGED
|
@@ -1,177 +1,500 @@
|
|
|
1
1
|
# p7d-markdown-it-renderer-fence
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A `markdown-it` plugin for code block rendering and enhancements.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Default is `markup` mode. Custom Highlight API mode is available as an advanced/experimental path.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Install
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm i @peaceroad/markdown-it-renderer-fence markdown-it markdown-it-attrs
|
|
11
|
+
```
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
import mdit from 'markdown-it'
|
|
13
|
-
import mditAttrs from 'markdown-it-attrs'
|
|
14
|
-
import highlightjs from 'highlihgtjs'
|
|
15
|
-
import mditRendererFence from '@peaceroad/markdown-it-renderer-fence'
|
|
13
|
+
If you use syntax highlighting, install a highlighter too (for example `highlight.js` or `shiki`).
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
langPrefix: 'language-',
|
|
19
|
-
highlight: (str, lang) => {
|
|
20
|
-
if (lang && highlightjs.getLanguage(lang)) {
|
|
21
|
-
try {
|
|
22
|
-
return highlightjs.highlight(str, { language: lang }).value
|
|
23
|
-
} catch (__) {}
|
|
24
|
-
}
|
|
25
|
-
return md.utils.escapeHtml(str)
|
|
26
|
-
}
|
|
27
|
-
}).use(mditAttrs).use(mditRendererFence)
|
|
15
|
+
## Entry Points
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
- `@peaceroad/markdown-it-renderer-fence`
|
|
18
|
+
Dispatcher entry (`highlightRenderer` selects mode).
|
|
19
|
+
- `@peaceroad/markdown-it-renderer-fence/markup-highlight`
|
|
20
|
+
Markup-focused entry.
|
|
21
|
+
- `@peaceroad/markdown-it-renderer-fence/custom-highlight`
|
|
22
|
+
API-mode entry + runtime/payload helpers.
|
|
23
|
+
- `@peaceroad/markdown-it-renderer-fence/custom-highlight-runtime`
|
|
24
|
+
Runtime-only entry (`applyCustomHighlights` / `observeCustomHighlights` / `clearCustomHighlights`).
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
## Markup Mode (Default)
|
|
33
27
|
|
|
34
|
-
|
|
28
|
+
### Quick Start
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
Dispatcher entry:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import MarkdownIt from 'markdown-it'
|
|
34
|
+
import markdownItAttrs from 'markdown-it-attrs'
|
|
35
|
+
import hljs from 'highlight.js'
|
|
36
|
+
import rendererFence from '@peaceroad/markdown-it-renderer-fence'
|
|
37
|
+
// Markup-only entry (skips dispatcher/API branch):
|
|
38
|
+
// import rendererFence from '@peaceroad/markdown-it-renderer-fence/markup-highlight'
|
|
39
|
+
|
|
40
|
+
const md = MarkdownIt({
|
|
41
|
+
html: true,
|
|
42
|
+
langPrefix: 'language-',
|
|
43
|
+
highlight: (code, lang) => {
|
|
44
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
45
|
+
try {
|
|
46
|
+
return hljs.highlight(code, { language: lang }).value
|
|
47
|
+
} catch (e) {}
|
|
48
|
+
}
|
|
49
|
+
return md.utils.escapeHtml(code)
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
.use(markdownItAttrs)
|
|
53
|
+
.use(rendererFence) // defaults to markup; direct markup entry can be leaner
|
|
41
54
|
```
|
|
42
|
-
[HTML]
|
|
43
|
-
<pre><samp>$ pwd
|
|
44
|
-
/home/User
|
|
45
|
-
</samp></pre>
|
|
46
55
|
|
|
56
|
+
### Markup with Shiki
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
Use the markup-only entry and provide `md.options.highlight` with Shiki.
|
|
59
|
+
For production/blog builds, pre-scan markdown and preload only used languages.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import MarkdownIt from 'markdown-it'
|
|
63
|
+
import markdownItAttrs from 'markdown-it-attrs'
|
|
64
|
+
import { createHighlighter } from 'shiki'
|
|
65
|
+
import rendererFence from '@peaceroad/markdown-it-renderer-fence/markup-highlight'
|
|
66
|
+
import fs from 'node:fs'
|
|
67
|
+
import path from 'node:path'
|
|
68
|
+
|
|
69
|
+
const fenceInfoReg = /^```([^\s`{]+)/gm
|
|
70
|
+
const collectFenceLangs = (markdown) => {
|
|
71
|
+
const out = new Set()
|
|
72
|
+
if (!markdown) return out
|
|
73
|
+
let m
|
|
74
|
+
while ((m = fenceInfoReg.exec(markdown)) !== null) {
|
|
75
|
+
const lang = String(m[1] || '').trim().toLowerCase()
|
|
76
|
+
if (lang) out.add(lang)
|
|
77
|
+
}
|
|
78
|
+
return out
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Example: scan markdown source(s) before highlighter creation
|
|
82
|
+
const markdown = fs.readFileSync(path.join(process.cwd(), 'article.md'), 'utf8')
|
|
83
|
+
const langs = Array.from(collectFenceLangs(markdown))
|
|
84
|
+
langs.push('text') // safe fallback
|
|
85
|
+
|
|
86
|
+
const highlighter = await createHighlighter({
|
|
87
|
+
themes: ['github-light'],
|
|
88
|
+
langs, // preload grammars; Shiki does not auto-detect language
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const md = MarkdownIt({
|
|
92
|
+
html: true,
|
|
93
|
+
langPrefix: 'language-',
|
|
94
|
+
highlight: (code, lang) => {
|
|
95
|
+
const targetLang = lang || 'text'
|
|
96
|
+
try {
|
|
97
|
+
return highlighter.codeToHtml(code, { lang: targetLang, theme: 'github-light' })
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// when lang grammar is not loaded (or invalid), keep output safe
|
|
100
|
+
return md.utils.escapeHtml(code)
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
.use(markdownItAttrs)
|
|
105
|
+
.use(rendererFence)
|
|
52
106
|
```
|
|
53
|
-
[HTML]
|
|
54
|
-
<pre><samp class="language-shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">pwd</span></span>
|
|
55
|
-
/home/User
|
|
56
|
-
</samp></pre>
|
|
57
107
|
|
|
108
|
+
Note:
|
|
109
|
+
|
|
110
|
+
- For small projects, a fixed list like `['javascript', 'typescript', 'json', 'text']` is also fine.
|
|
111
|
+
- If Shiki grammar for a language is not loaded, `codeToHtml` can fail.
|
|
112
|
+
- In markup mode, handle this in your `highlight` callback (for example fallback to escaped plain text as above).
|
|
58
113
|
|
|
59
|
-
|
|
60
|
-
|
|
114
|
+
### Main Features
|
|
115
|
+
|
|
116
|
+
- `samp` rendering for `samp`, `shell`, `console` languages.
|
|
117
|
+
- line number wrapping via `start` / `data-pre-start`.
|
|
118
|
+
- emphasized lines via `em-lines` / `emphasize-lines`.
|
|
119
|
+
- optional line-end spacer via `lineEndSpanThreshold`.
|
|
120
|
+
- optional pre-wrap support via `wrap` / `pre-wrap`.
|
|
121
|
+
- comment line markers via `comment-mark`.
|
|
122
|
+
|
|
123
|
+
### Fence Attribute Examples
|
|
124
|
+
|
|
125
|
+
Simplified HTML below focuses on renderer-fence structure (not full highlighter token spans).
|
|
126
|
+
|
|
127
|
+
`samp` conversion:
|
|
128
|
+
|
|
129
|
+
~~~md
|
|
130
|
+
```shell
|
|
61
131
|
$ pwd
|
|
62
|
-
/home/User
|
|
63
132
|
```
|
|
64
|
-
[HTML]
|
|
65
|
-
<pre><samp class="language-console"><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">pwd</span></span>
|
|
66
|
-
/home/User
|
|
67
|
-
</samp></pre>
|
|
68
133
|
~~~
|
|
69
134
|
|
|
135
|
+
```html
|
|
136
|
+
<pre><samp class="language-shell">$ pwd
|
|
137
|
+
</samp></pre>
|
|
138
|
+
```
|
|
70
139
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Add `start` or `data-pre-start` attribute by adding attributes used markdown-it-attrs.
|
|
74
|
-
Line numbering is enabled only when `start` is a non-negative integer (`0`, `1`, `2`, ...).
|
|
140
|
+
Line numbers:
|
|
75
141
|
|
|
76
142
|
~~~md
|
|
77
143
|
```js {start="1"}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
md.render('Nyaan')
|
|
144
|
+
const a = 1
|
|
145
|
+
console.log(a)
|
|
81
146
|
```
|
|
82
147
|
~~~
|
|
83
148
|
|
|
84
|
-
|
|
85
|
-
<pre><code class="language-js" data-pre-start="1" style="counter-set:pre-line-number 1;"
|
|
86
|
-
<span class="pre-line"
|
|
87
|
-
<span class="pre-line">
|
|
149
|
+
```html
|
|
150
|
+
<pre><code class="language-js" data-pre-start="1" style="counter-set:pre-line-number 1;">
|
|
151
|
+
<span class="pre-line">const a = 1</span>
|
|
152
|
+
<span class="pre-line">console.log(a)</span>
|
|
88
153
|
</code></pre>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
CSS example: <https://codepen.io/peaceroad/pen/qBGpYGK>
|
|
92
|
-
|
|
93
|
-
If `start` is empty or not a non-negative integer (for example `start=""`, `start="abc"`, `start="1.5"`), the attribute is kept as `data-pre-start` but line-number wrapping/counter style is not enabled.
|
|
94
|
-
|
|
95
|
-
## Add span elements to add background color to the row ranges.
|
|
154
|
+
```
|
|
96
155
|
|
|
97
|
-
|
|
98
|
-
Open-ended ranges are supported: `-5` (1..5), `3-` (3..last), `-` (all).
|
|
156
|
+
Emphasis:
|
|
99
157
|
|
|
100
158
|
~~~md
|
|
101
|
-
``` {em-lines="2,4-5"}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
6
|
|
159
|
+
```js {em-lines="2,4-5"}
|
|
160
|
+
line1
|
|
161
|
+
line2
|
|
162
|
+
line3
|
|
163
|
+
line4
|
|
164
|
+
line5
|
|
108
165
|
```
|
|
109
166
|
~~~
|
|
110
167
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
5
|
|
118
|
-
</span>6
|
|
168
|
+
```html
|
|
169
|
+
<pre><code class="language-js" data-pre-emphasis="2,4-5">line1
|
|
170
|
+
<span class="pre-lines-emphasis">line2</span>
|
|
171
|
+
line3
|
|
172
|
+
<span class="pre-lines-emphasis">line4
|
|
173
|
+
line5</span>
|
|
119
174
|
</code></pre>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
## Enable pre-wrap for code blocks
|
|
175
|
+
```
|
|
123
176
|
|
|
124
|
-
|
|
177
|
+
Wrap:
|
|
125
178
|
|
|
126
179
|
~~~md
|
|
127
180
|
```js {wrap}
|
|
128
|
-
const
|
|
181
|
+
const veryLongLine = '...'
|
|
129
182
|
```
|
|
130
183
|
~~~
|
|
131
184
|
|
|
132
|
-
|
|
133
|
-
<pre data-pre-wrap="true" style="white-space: pre-wrap; overflow-wrap: anywhere;"
|
|
185
|
+
```html
|
|
186
|
+
<pre data-pre-wrap="true" style="white-space: pre-wrap; overflow-wrap: anywhere;">
|
|
187
|
+
<code class="language-js">const veryLongLine = '...'
|
|
134
188
|
</code></pre>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
When `setPreWrapStyle` is set to false, only `data-pre-wrap="true"` is added and the inline style is omitted.
|
|
138
|
-
|
|
139
|
-
## Mark comment lines in code blocks
|
|
189
|
+
```
|
|
140
190
|
|
|
141
|
-
|
|
191
|
+
Comment line marker:
|
|
142
192
|
|
|
143
193
|
~~~md
|
|
144
|
-
```samp {comment-
|
|
194
|
+
```samp {comment-mark="#"}
|
|
145
195
|
# comment
|
|
146
196
|
echo 1
|
|
147
197
|
```
|
|
148
198
|
~~~
|
|
149
199
|
|
|
150
|
-
|
|
151
|
-
<pre><samp data-pre-comment-
|
|
200
|
+
```html
|
|
201
|
+
<pre><samp data-pre-comment-mark="#" class="language-samp">
|
|
202
|
+
<span class="pre-line-comment"># comment</span>
|
|
152
203
|
echo 1
|
|
153
204
|
</samp></pre>
|
|
154
|
-
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Markup Notes
|
|
155
208
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
209
|
+
- `useHighlightPre: true` keeps highlighter-provided `<pre><code>` when present.
|
|
210
|
+
- In that passthrough path, line-splitting features are intentionally disabled:
|
|
211
|
+
- line numbers
|
|
212
|
+
- `em-lines`
|
|
213
|
+
- line-end spacer
|
|
214
|
+
- `comment-mark`
|
|
215
|
+
- `samp` conversion
|
|
159
216
|
|
|
217
|
+
### Markup Options
|
|
160
218
|
|
|
161
|
-
|
|
219
|
+
- `attrsOrder` (default: `['class', 'id', 'data-*', 'style']`): output attribute order (`data-*` wildcard supported).
|
|
220
|
+
- `setHighlight` (default: `true`): call `md.options.highlight` when available.
|
|
221
|
+
- `setLineNumber` (default: `true`): enable line wrapper spans when `start` is valid.
|
|
222
|
+
- `setEmphasizeLines` (default: `true`): enable `em-lines` / `emphasize-lines`.
|
|
223
|
+
- `lineEndSpanThreshold` (default: `0`): append line-end spacer span when visual width threshold is met.
|
|
224
|
+
- `setLineEndSpan`: alias of `lineEndSpanThreshold`.
|
|
225
|
+
- `lineEndSpanClass` (default: `'pre-lineend-spacer'`): CSS class of the spacer span.
|
|
226
|
+
- `setPreWrapStyle` (default: `true`): inject inline pre-wrap style for wrap-enabled blocks.
|
|
227
|
+
- `sampLang` (default: `'shell,console'`): additional fence langs rendered as `<samp>`.
|
|
228
|
+
- `langPrefix` (default: `md.options.langPrefix || 'language-'`): class prefix for language class.
|
|
229
|
+
- `useHighlightPre` (default: `false`): passthrough highlighter `<pre><code>` wrappers and skip line-splitting features.
|
|
230
|
+
- `onFenceDecision` (default: `null`): debug hook for per-fence branch decisions.
|
|
231
|
+
- `onFenceDecisionTiming` (default: `false`): include timing fields in `onFenceDecision`.
|
|
232
|
+
|
|
233
|
+
`em-lines` syntax note:
|
|
234
|
+
|
|
235
|
+
- supports single values (`2`) and ranges (`4-6`)
|
|
236
|
+
- supports open-ended forms (`3-`, `-2`)
|
|
237
|
+
- reversed ranges are normalized (`5-3` behaves as `3-5`)
|
|
238
|
+
|
|
239
|
+
Migration note (`0.5.0`):
|
|
240
|
+
|
|
241
|
+
- `comment-line` / `data-pre-comment-line` / `pre-comment-line` were removed
|
|
242
|
+
- use `comment-mark` / `data-pre-comment-mark` / `pre-line-comment`
|
|
243
|
+
|
|
244
|
+
## Custom Highlight API Mode (Experimental / Advanced)
|
|
245
|
+
|
|
246
|
+
Custom Highlight API mode renders plain code text and emits range payloads for browser-side Custom Highlight API application.
|
|
247
|
+
|
|
248
|
+
Important:
|
|
249
|
+
|
|
250
|
+
- You must run runtime apply in the browser (`applyCustomHighlights` or `observeCustomHighlights`).
|
|
251
|
+
- Without runtime apply, payload exists but browser highlights are not activated.
|
|
252
|
+
- `test/custom-highlight/pre-highlight.js` is a demo helper, not the package runtime API contract.
|
|
253
|
+
|
|
254
|
+
Use API mode only if you need runtime range highlighting and can manage runtime/CSS integration in your app.
|
|
255
|
+
|
|
256
|
+
### Minimal API Example (Shiki provider)
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
import MarkdownIt from 'markdown-it'
|
|
260
|
+
import markdownItAttrs from 'markdown-it-attrs'
|
|
261
|
+
import { createHighlighter } from 'shiki'
|
|
262
|
+
import rendererFenceApi, {
|
|
263
|
+
applyCustomHighlights,
|
|
264
|
+
renderCustomHighlightPayloadScript,
|
|
265
|
+
} from '@peaceroad/markdown-it-renderer-fence/custom-highlight'
|
|
266
|
+
// Dispatcher entry alternative:
|
|
267
|
+
// import rendererFenceApi from '@peaceroad/markdown-it-renderer-fence'
|
|
268
|
+
|
|
269
|
+
const highlighter = await createHighlighter({
|
|
270
|
+
themes: ['github-light'],
|
|
271
|
+
langs: ['javascript', 'typescript', 'json'], // preload grammars; Shiki does not auto-detect language
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
const md = MarkdownIt({ html: true })
|
|
275
|
+
.use(markdownItAttrs)
|
|
276
|
+
.use(rendererFenceApi, {
|
|
277
|
+
// highlightRenderer: 'api', // required only with dispatcher entry
|
|
278
|
+
customHighlight: {
|
|
279
|
+
provider: 'shiki',
|
|
280
|
+
highlighter,
|
|
281
|
+
theme: 'github-light',
|
|
282
|
+
transport: 'env',
|
|
283
|
+
},
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
const env = {}
|
|
287
|
+
const html = md.render('```js\nconst x = 1\n```', env)
|
|
288
|
+
const payloadScript = renderCustomHighlightPayloadScript(env) // id="pre-highlight-data"
|
|
289
|
+
```
|
|
162
290
|
|
|
163
|
-
|
|
291
|
+
Browser-side apply:
|
|
164
292
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
293
|
+
```js
|
|
294
|
+
applyCustomHighlights(document)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
When using `colorScheme: 'auto'`, scheme resolution happens at apply time.
|
|
298
|
+
If OS/browser theme changes after first apply, re-run apply (or use observer watch mode below).
|
|
299
|
+
|
|
300
|
+
If you only need runtime apply on the browser, import runtime-only entry:
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
import { observeCustomHighlights } from '@peaceroad/markdown-it-renderer-fence/custom-highlight-runtime'
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`langs` note:
|
|
307
|
+
|
|
308
|
+
- `createHighlighter({ langs: [...] })` should include the languages you expect to render.
|
|
309
|
+
- Shiki does not auto-detect language from code text.
|
|
310
|
+
- if a target language is not loaded, this plugin falls back to Shiki `text` tokenization for that block (safe, but no syntax color buckets).
|
|
311
|
+
|
|
312
|
+
### Minimal API Example (highlight.js provider)
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
import MarkdownIt from 'markdown-it'
|
|
316
|
+
import markdownItAttrs from 'markdown-it-attrs'
|
|
317
|
+
import hljs from 'highlight.js'
|
|
318
|
+
import rendererFenceApi from '@peaceroad/markdown-it-renderer-fence/custom-highlight'
|
|
319
|
+
// Dispatcher entry alternative:
|
|
320
|
+
// import rendererFenceApi from '@peaceroad/markdown-it-renderer-fence'
|
|
321
|
+
|
|
322
|
+
const md = MarkdownIt({ html: true })
|
|
323
|
+
.use(markdownItAttrs)
|
|
324
|
+
.use(rendererFenceApi, {
|
|
325
|
+
// highlightRenderer: 'api', // required only with dispatcher entry
|
|
326
|
+
customHighlight: {
|
|
327
|
+
provider: 'hljs',
|
|
328
|
+
hljsHighlight: (code, lang) => {
|
|
329
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
330
|
+
return hljs.highlight(code, { language: lang }).value
|
|
331
|
+
}
|
|
332
|
+
return hljs.highlight(code, { language: 'plaintext' }).value
|
|
333
|
+
},
|
|
334
|
+
transport: 'env',
|
|
335
|
+
},
|
|
336
|
+
})
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### API Providers
|
|
340
|
+
|
|
341
|
+
- `customHighlight.provider: 'shiki'`
|
|
342
|
+
Requires `customHighlight.highlighter.codeToTokens(...)` (synchronous).
|
|
343
|
+
- `customHighlight.provider: 'hljs'`
|
|
344
|
+
Uses `customHighlight.hljsHighlight`, fallback to `customHighlight.highlight`, then `md.options.highlight`.
|
|
345
|
+
- `customHighlight.provider: 'custom'`
|
|
346
|
+
Escape hatch. Requires synchronous `customHighlight.getRanges(...)`.
|
|
347
|
+
|
|
348
|
+
### Shiki Scope Modes
|
|
349
|
+
|
|
350
|
+
- `auto` (default)
|
|
351
|
+
- `color`
|
|
352
|
+
- `semantic`
|
|
353
|
+
- `keyword` (best for stable CSS-managed styling)
|
|
354
|
+
|
|
355
|
+
Migration note (`0.5.0`):
|
|
356
|
+
|
|
357
|
+
- legacy aliases like `json`, `bucket`, `keyword-only` are removed
|
|
358
|
+
- use canonical values: `auto | color | semantic | keyword`
|
|
359
|
+
|
|
360
|
+
Recommended production profile for API mode:
|
|
361
|
+
|
|
362
|
+
```js
|
|
363
|
+
customHighlight: {
|
|
364
|
+
provider: 'shiki',
|
|
365
|
+
shikiScopeMode: 'keyword',
|
|
366
|
+
includeScopeStyles: false
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### API Multi-Theme (Shiki, `v:1` additive payload)
|
|
371
|
+
|
|
372
|
+
When you need runtime light/dark switching with Shiki color styles, pass object form theme:
|
|
373
|
+
|
|
374
|
+
```js
|
|
375
|
+
customHighlight: {
|
|
376
|
+
provider: 'shiki',
|
|
377
|
+
highlighter,
|
|
378
|
+
shikiScopeMode: 'color',
|
|
379
|
+
includeScopeStyles: true,
|
|
380
|
+
theme: {
|
|
381
|
+
light: 'github-light',
|
|
382
|
+
dark: 'github-dark',
|
|
383
|
+
default: 'light',
|
|
384
|
+
},
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Runtime apply can choose variant:
|
|
389
|
+
|
|
390
|
+
```js
|
|
391
|
+
applyCustomHighlights(document, { colorScheme: 'auto' }) // 'auto' | 'light' | 'dark'
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Auto re-apply on color-scheme change:
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
observeCustomHighlights(document, {
|
|
398
|
+
applyOptions: { colorScheme: 'auto', incremental: true },
|
|
399
|
+
watchColorScheme: true,
|
|
400
|
+
})
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### CMS / Copy-Paste Operation
|
|
404
|
+
|
|
405
|
+
If you copy rendered HTML+payload JSON into another CMS/page:
|
|
406
|
+
|
|
407
|
+
- API mode works only when runtime JS is also available on that page.
|
|
408
|
+
- payload JSON alone is not enough.
|
|
409
|
+
- if the target CMS cannot run custom JS, use `markup` mode instead.
|
|
410
|
+
- this plugin does not auto-generate/write runtime JS files during markdown render.
|
|
411
|
+
|
|
412
|
+
Practical patterns:
|
|
413
|
+
|
|
414
|
+
1. Site/app with bundler
|
|
415
|
+
Bundle `@peaceroad/markdown-it-renderer-fence/custom-highlight-runtime` and run `observeCustomHighlights(...)`.
|
|
416
|
+
2. Static page with module scripts
|
|
417
|
+
Load runtime entry via your ESM delivery path and call `applyCustomHighlights(...)`.
|
|
418
|
+
3. HTML-only CMS (no JS injection)
|
|
419
|
+
Use `markup` mode (API mode is not suitable).
|
|
420
|
+
|
|
421
|
+
### CLI Build Artifact Contract
|
|
422
|
+
|
|
423
|
+
For CLI/static generation, treat API mode output as two artifacts:
|
|
424
|
+
|
|
425
|
+
1. HTML artifact
|
|
426
|
+
Includes `<pre data-pre-highlight="...">...</pre>` and payload JSON (`env` script or inline-script transport).
|
|
427
|
+
2. Runtime artifact
|
|
428
|
+
Browser JS that imports `@peaceroad/markdown-it-renderer-fence/custom-highlight-runtime` and runs `applyCustomHighlights(...)` or `observeCustomHighlights(...)`.
|
|
429
|
+
|
|
430
|
+
Typical runtime bridge file:
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
import { observeCustomHighlights } from '@peaceroad/markdown-it-renderer-fence/custom-highlight-runtime'
|
|
434
|
+
|
|
435
|
+
observeCustomHighlights(document, {
|
|
436
|
+
applyOptions: { colorScheme: 'auto', incremental: true },
|
|
437
|
+
watchColorScheme: true,
|
|
438
|
+
})
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Then include that bridge in generated HTML:
|
|
442
|
+
|
|
443
|
+
```html
|
|
444
|
+
<script type="module" src="/assets/custom-highlight-runtime.js"></script>
|
|
445
|
+
```
|
|
176
446
|
|
|
177
|
-
|
|
447
|
+
### API Options
|
|
448
|
+
|
|
449
|
+
- `highlightRenderer` (`'markup' | 'api' | 'custom-highlight-api'`, default: `'markup'`): dispatcher mode selector (`/custom-highlight` entry does not need this).
|
|
450
|
+
- `customHighlight.provider` (default: `'shiki'`): range source (`'shiki' | 'hljs' | 'custom'`).
|
|
451
|
+
- `customHighlight.getRanges`: required when `provider: 'custom'`; must return synchronous ranges.
|
|
452
|
+
- `customHighlight.highlighter`: Shiki highlighter object with synchronous `codeToTokens`.
|
|
453
|
+
- `customHighlight.hljsHighlight`: highlight.js-style function used when `provider: 'hljs'`.
|
|
454
|
+
- `customHighlight.highlight`: fallback highlight function for `hljs` provider.
|
|
455
|
+
- `customHighlight.defaultLang`: default language when fence language is empty.
|
|
456
|
+
- `customHighlight.theme`: Shiki theme name string, or object `{ light, dark, default? }` for dual-theme payload.
|
|
457
|
+
- `customHighlight.shikiScopeMode` (default: `'auto'`): scope naming mode (`auto | color | semantic | keyword`).
|
|
458
|
+
- `customHighlight.shikiKeywordClassifier`: custom classifier hook for keyword mode.
|
|
459
|
+
- `customHighlight.shikiKeywordLangResolver`: custom language resolver hook for keyword mode.
|
|
460
|
+
- `customHighlight.shikiKeywordLangAliases`: language alias map for keyword resolver.
|
|
461
|
+
- `customHighlight.includeScopeStyles` (default: `true`): include payload `scopeStyles` when available.
|
|
462
|
+
- `customHighlight.fallback` (`'plain' | 'markup'`, default: `'plain'`): server-side fallback renderer on provider errors.
|
|
463
|
+
- `customHighlight.fallbackOn`: restrict fallback trigger reasons.
|
|
464
|
+
- `customHighlight.transport` (`'env' | 'inline-script'`, default: `'env'`): payload transport target.
|
|
465
|
+
- `customHighlight.idPrefix` (default: `'hl-'`): generated per-block payload id prefix.
|
|
466
|
+
- `customHighlight.scopePrefix` (default: `'hl'`): scope name prefix used for highlight registration.
|
|
467
|
+
- `customHighlight.lineFeatureStrategy` (`'hybrid' | 'disable'`, default: `'hybrid'`): keep or disable line-span features in API rendering.
|
|
468
|
+
|
|
469
|
+
### API Helper Exports
|
|
470
|
+
|
|
471
|
+
- `applyCustomHighlights`
|
|
472
|
+
- `observeCustomHighlights`
|
|
473
|
+
- `clearCustomHighlights`
|
|
474
|
+
- `shouldRuntimeFallback`
|
|
475
|
+
- `getCustomHighlightPayloadMap`
|
|
476
|
+
- `renderCustomHighlightPayloadScript`
|
|
477
|
+
- `renderCustomHighlightScopeStyleTag`
|
|
478
|
+
- `customHighlightPayloadSchemaVersion`
|
|
479
|
+
- `customHighlightPayloadSupportedVersions`
|
|
480
|
+
|
|
481
|
+
## Docs and Examples
|
|
482
|
+
|
|
483
|
+
- API styling guide: `docs/custom-highlight-styling-guide.md`
|
|
484
|
+
- default preset CSS sample: `docs/default-highlight-theme.css`
|
|
485
|
+
- provider matrix page: `example/custom-highlight-provider-matrix.html`
|
|
486
|
+
|
|
487
|
+
## Tests and Benchmarks
|
|
488
|
+
|
|
489
|
+
- all tests: `npm test`
|
|
490
|
+
- provider contract: `npm run test:provider:contract`
|
|
491
|
+
- keyword coverage: `npm run test:provider:keyword`
|
|
492
|
+
- keyword holdout parity: `npm run test:provider:keyword:holdout`
|
|
493
|
+
- performance baseline: `npm run test:performance`
|
|
494
|
+
- runtime apply benchmark: `npm run test:performance:runtime`
|
|
495
|
+
|
|
496
|
+
## License and Notices
|
|
497
|
+
|
|
498
|
+
- Project license: MIT (`LICENSE`)
|
|
499
|
+
- Third-party notices: `THIRD_PARTY_NOTICES.md`
|
|
500
|
+
- `docs/default-highlight-theme.css` contains mappings derived from highlight.js theme families (BSD-3-Clause; see notices).
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
|