@dr-ishaan/rehype-perfect-code-blocks 1.1.7 → 1.2.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/CHANGELOG.md +82 -40
- package/dist/astro.d.ts.map +1 -1
- package/dist/astro.js +13 -4
- package/dist/astro.js.map +1 -1
- package/dist/copy-script.d.ts +2 -1
- package/dist/copy-script.d.ts.map +1 -1
- package/dist/copy-script.js +16 -2
- package/dist/copy-script.js.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/meta.d.ts.map +1 -1
- package/dist/meta.js +35 -2
- package/dist/meta.js.map +1 -1
- package/dist/shiki.d.ts.map +1 -1
- package/dist/shiki.js +254 -25
- package/dist/shiki.js.map +1 -1
- package/dist/transformer.d.ts.map +1 -1
- package/dist/transformer.js +230 -16
- package/dist/transformer.js.map +1 -1
- package/dist/types.d.ts +109 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/astro.ts +14 -4
- package/src/copy-script.ts +16 -2
- package/src/index.ts +15 -1
- package/src/meta.ts +35 -2
- package/src/shiki.ts +258 -26
- package/src/transformer.ts +243 -17
- package/src/types.ts +105 -4
package/dist/types.d.ts
CHANGED
|
@@ -47,6 +47,16 @@ export interface PerfectCodeOptions {
|
|
|
47
47
|
wrap?: boolean;
|
|
48
48
|
/** Auto-collapse blocks longer than N lines. null = never. Default: null */
|
|
49
49
|
collapseAfter?: number | null;
|
|
50
|
+
/**
|
|
51
|
+
* Per-line collapsible sections.
|
|
52
|
+
* Pass a meta string like `collapse="5-12,20-30"` to wrap matching line ranges
|
|
53
|
+
* in `<details><summary>N collapsed lines</summary>...</details>`.
|
|
54
|
+
* Style options: 'github' (default), 'collapsible-start', 'collapsible-end', 'collapsible-auto'.
|
|
55
|
+
* Default: null (disabled).
|
|
56
|
+
*/
|
|
57
|
+
collapseRanges?: string | null;
|
|
58
|
+
/** Style for collapsible sections. Default: 'github'. */
|
|
59
|
+
collapseStyle?: 'github' | 'collapsible-start' | 'collapsible-end' | 'collapsible-auto';
|
|
50
60
|
/** Show visible whitespace (tabs/spaces). Default: false */
|
|
51
61
|
showWhitespace?: false | 'all' | 'boundary' | 'trailing' | 'leading';
|
|
52
62
|
/** Render vertical indent guides. false | true (default 2) | number (indent width). Default: false */
|
|
@@ -62,11 +72,15 @@ export interface PerfectCodeOptions {
|
|
|
62
72
|
engine?: 'auto' | 'shiki' | 'passthrough';
|
|
63
73
|
/** Shiki options passed through when the plugin calls Shiki itself. */
|
|
64
74
|
shiki?: {
|
|
65
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Theme — string for single theme, { light, dark } for dual-theme via CSS vars,
|
|
77
|
+
* or a Record<string, string> for multi-theme (3+ themes) support.
|
|
78
|
+
* Multi-theme example: `{ light: 'github-light', dark: 'github-dark', dim: 'github-dark-dimmed' }`.
|
|
79
|
+
*/
|
|
66
80
|
theme?: string | {
|
|
67
81
|
light: string;
|
|
68
82
|
dark: string;
|
|
69
|
-
}
|
|
83
|
+
} | Record<string, string>;
|
|
70
84
|
/** Pre-loaded languages. Defaults to a sensible set; missing langs are lazy-loaded. */
|
|
71
85
|
langs?: string[];
|
|
72
86
|
/**
|
|
@@ -77,6 +91,12 @@ export interface PerfectCodeOptions {
|
|
|
77
91
|
regexEngine?: 'oniguruma' | 'javascript';
|
|
78
92
|
/** Additional Shiki transformers to apply (see @shikijs/transformers). */
|
|
79
93
|
transformers?: ShikiTransformer[];
|
|
94
|
+
/**
|
|
95
|
+
* Controls whether user-provided transformers run 'before' or 'after' the
|
|
96
|
+
* auto-registered ones (default: 'after'). Use 'before' to give user
|
|
97
|
+
* transformers first access to the code text.
|
|
98
|
+
*/
|
|
99
|
+
transformerOrder?: 'before' | 'after';
|
|
80
100
|
/** Override the highlighter factory (e.g. for custom TextMate grammars). */
|
|
81
101
|
getHighlighter?: (opts: {
|
|
82
102
|
themes: string[];
|
|
@@ -100,6 +120,38 @@ export interface PerfectCodeOptions {
|
|
|
100
120
|
* round-trip. Faster + safer. Default: true.
|
|
101
121
|
*/
|
|
102
122
|
useHastApi?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Disable auto-registration of @shikijs/transformers. When true, ONLY the
|
|
125
|
+
* transformers in `shiki.transformers` are applied. Default: false.
|
|
126
|
+
* Useful for advanced users who want full manual control.
|
|
127
|
+
*/
|
|
128
|
+
disableAutoTransformers?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Strip all comments from the rendered code (// ..., # ..., /* ... *\/, <!-- ... -->).
|
|
131
|
+
* Powered by @shikijs/transformers `transformerRemoveComments`. Default: false.
|
|
132
|
+
*/
|
|
133
|
+
removeComments?: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Remove line breaks from the rendered code (joins all lines into one).
|
|
136
|
+
* Powered by @shikijs/transformers `transformerRemoveLineBreaks`. Default: false.
|
|
137
|
+
* Useful for compact inline-style code blocks.
|
|
138
|
+
*/
|
|
139
|
+
removeLineBreaks?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* When `true`, treat {1,3-5} meta ranges as zero-indexed (line 0 is the first
|
|
142
|
+
* line). When `false` (default), line numbers start at 1.
|
|
143
|
+
*/
|
|
144
|
+
zeroIndexed?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Programmatic per-line class assignment (Shiki's `transformerCompactLineOptions`).
|
|
147
|
+
* Example: `[{ line: 1, classes: ['highlight'] }, { line: 3, classes: ['add'] }]`.
|
|
148
|
+
* Default: [] (disabled).
|
|
149
|
+
*/
|
|
150
|
+
lineOptions?: {
|
|
151
|
+
line: number;
|
|
152
|
+
classes?: string[];
|
|
153
|
+
attrs?: Record<string, string>;
|
|
154
|
+
}[];
|
|
103
155
|
/**
|
|
104
156
|
* Custom // [!code xxx] notations mapped to CSS classes. Default: {}.
|
|
105
157
|
* Example: `{ 'my-marker': 'pcb__line--custom' }` lets users write
|
|
@@ -127,7 +179,7 @@ export interface PerfectCodeOptions {
|
|
|
127
179
|
tokensMap?: Record<string, string>;
|
|
128
180
|
/**
|
|
129
181
|
* Auto-switch to terminal preset for these languages. Default:
|
|
130
|
-
* ['sh', 'bash', 'zsh', 'shell', 'console', 'powershell', 'bat', 'cmd', 'fish']
|
|
182
|
+
* ['sh', 'bash', 'zsh', 'shell', 'console', 'powershell', 'bat', 'cmd', 'fish', 'ansi']
|
|
131
183
|
*/
|
|
132
184
|
terminalLangs?: string[];
|
|
133
185
|
/**
|
|
@@ -159,7 +211,19 @@ export interface PerfectCodeOptions {
|
|
|
159
211
|
*/
|
|
160
212
|
defaultInlineLang?: string;
|
|
161
213
|
/**
|
|
162
|
-
*
|
|
214
|
+
* Replace tabs with N spaces before tokenization. 0 disables (default).
|
|
215
|
+
* Useful for languages where Shiki's tab rendering doesn't match the
|
|
216
|
+
* surrounding code style.
|
|
217
|
+
*/
|
|
218
|
+
tabWidth?: number;
|
|
219
|
+
/**
|
|
220
|
+
* Strip leading `#` comment lines from terminal code when copying to clipboard.
|
|
221
|
+
* Default: true (only effective when preset === 'terminal').
|
|
222
|
+
*/
|
|
223
|
+
copyStripComments?: boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Add `role="region"`, `aria-label`, and `tabindex="0"` to scrollable code
|
|
226
|
+
* blocks (WCAG 2.1.1 keyboard accessible, 4.1.2 name-role-value).
|
|
163
227
|
* Default: true.
|
|
164
228
|
*/
|
|
165
229
|
accessibleScroll?: boolean;
|
|
@@ -174,6 +238,12 @@ export interface PerfectCodeOptions {
|
|
|
174
238
|
* Default: true.
|
|
175
239
|
*/
|
|
176
240
|
hideCopyWithoutJs?: boolean;
|
|
241
|
+
/**
|
|
242
|
+
* Add a screen-reader-only `<span class="pcb__sr-only">Terminal window</span>`
|
|
243
|
+
* to terminal-preset blocks that have no title. Improves screen reader context.
|
|
244
|
+
* Default: true.
|
|
245
|
+
*/
|
|
246
|
+
terminalSrOnlyTitle?: boolean;
|
|
177
247
|
/**
|
|
178
248
|
* Additional rehype plugins to run BEFORE rehype-perfect-code-blocks.
|
|
179
249
|
* Pass `rehypeRaw` here if your markdown contains raw HTML (`<details>`,
|
|
@@ -205,6 +275,37 @@ export interface PerfectCodeOptions {
|
|
|
205
275
|
onVisitTitle?: (element: unknown) => void;
|
|
206
276
|
/** Called for the caption element (if present). */
|
|
207
277
|
onVisitCaption?: (element: unknown) => void;
|
|
278
|
+
/**
|
|
279
|
+
* Localized UI strings. Defaults are English. Override per-locale by
|
|
280
|
+
* passing a different `texts` object based on the current language.
|
|
281
|
+
*/
|
|
282
|
+
texts?: {
|
|
283
|
+
/** Copy button label (default: 'copy'). */
|
|
284
|
+
copyLabel?: string;
|
|
285
|
+
/** Label shown after successful copy (default: 'copied!'). */
|
|
286
|
+
doneLabel?: string;
|
|
287
|
+
/** Aria-label for the copy button (default: 'Copy code'). */
|
|
288
|
+
copyAriaLabel?: string;
|
|
289
|
+
/** Screen-reader-only title for terminal-preset blocks (default: 'Terminal window'). */
|
|
290
|
+
terminalSrOnlyTitle?: string;
|
|
291
|
+
/** aria-label prefix for scrollable body (default: 'Code block'). */
|
|
292
|
+
codeBlockAriaPrefix?: string;
|
|
293
|
+
/** Summary text for collapsed sections, with `{n}` placeholder (default: '{n} collapsed lines'). */
|
|
294
|
+
collapsedLinesLabel?: (n: number) => string;
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* Custom logger. Defaults to `console`. Useful for silencing warnings in
|
|
298
|
+
* production or routing them to a structured logger.
|
|
299
|
+
*/
|
|
300
|
+
logger?: {
|
|
301
|
+
warn: (msg: string) => void;
|
|
302
|
+
error: (msg: string) => void;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Nonce to add to injected `<script>` and `<style>` tags. Enables strict CSP
|
|
306
|
+
* (`script-src 'self' 'nonce-...'`). Default: undefined (no nonce).
|
|
307
|
+
*/
|
|
308
|
+
cspNonce?: string;
|
|
208
309
|
/** Visual preset. Default: 'default' */
|
|
209
310
|
preset?: 'default' | 'terminal' | 'minimal';
|
|
210
311
|
/** Inject the bundled CSS automatically. Set false to ship your own. Default: true */
|
|
@@ -241,6 +342,10 @@ export interface ParsedMeta {
|
|
|
241
342
|
id?: string;
|
|
242
343
|
}[];
|
|
243
344
|
lineNumbersStart: number | null;
|
|
345
|
+
collapseRanges: {
|
|
346
|
+
from: number;
|
|
347
|
+
to: number;
|
|
348
|
+
}[];
|
|
244
349
|
flags: {
|
|
245
350
|
wrap: boolean | null;
|
|
246
351
|
lineNumbers: boolean | null;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAEvC,MAAM,WAAW,kBAAkB;IAEjC,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;OAOG;IACH,UAAU,CAAC,EACP,OAAO,GACP;QACE,UAAU,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;QAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACN,uFAAuF;IACvF,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iEAAiE;IACjE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAG7B,+EAA+E;IAC/E,WAAW,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IAC1C,wFAAwF;IACxF,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IACvC,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,0FAA0F;IAC1F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2FAA2F;IAC3F,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wCAAwC;IACxC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,4DAA4D;IAC5D,cAAc,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;IACrE,sGAAsG;IACtG,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAChC,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAGlB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,aAAa,CAAC;IAC1C,uEAAuE;IACvE,KAAK,CAAC,EAAE;QACN
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dr-ishaan/rehype-perfect-code-blocks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Beautiful, configurable code blocks for Astro / MDX / any rehype pipeline. Built on Shiki, inspired by rehype-pretty-code, VitePress, Docusaurus, and Expressive Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/src/astro.ts
CHANGED
|
@@ -52,6 +52,13 @@ function loadCss(): string {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/** Escape a string for use as an HTML attribute value (defense in depth). */
|
|
56
|
+
function escapeAttr(s: string): string {
|
|
57
|
+
return s.replace(/[<>"'&]/g, (c) => ({
|
|
58
|
+
'<': '<', '>': '>', '"': '"', "'": ''', '&': '&',
|
|
59
|
+
}[c] ?? c));
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
/** Recursively walk a directory and return all .html file paths. */
|
|
56
63
|
function findHtmlFiles(dir: string): string[] {
|
|
57
64
|
const results: string[] = [];
|
|
@@ -109,22 +116,25 @@ export default function perfectCode(
|
|
|
109
116
|
// (The previous `injectScript('page', '<style>...')` approach
|
|
110
117
|
// breaks on Astro 6 where injectScript expects JS, not HTML.)
|
|
111
118
|
const injections: string[] = [];
|
|
119
|
+
// CSP nonce: if set, add `nonce="..."` to all <script> and <style> tags
|
|
120
|
+
// so they pass a strict Content-Security-Policy.
|
|
121
|
+
const nonceAttr = options.cspNonce ? ` nonce="${escapeAttr(options.cspNonce)}"` : '';
|
|
112
122
|
|
|
113
123
|
// CSS
|
|
114
124
|
if (options.injectStyles !== false) {
|
|
115
125
|
const css = loadCss();
|
|
116
126
|
if (css) {
|
|
117
|
-
injections.push(`<style data-pcb>${css}</style>`);
|
|
127
|
+
injections.push(`<style data-pcb${nonceAttr}>${css}</style>`);
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
// Copy-button script
|
|
122
132
|
if (options.copyButton !== false) {
|
|
123
|
-
injections.push(`<script>${COPY_SCRIPT}</script>`);
|
|
133
|
+
injections.push(`<script${nonceAttr}>${COPY_SCRIPT}</script>`);
|
|
124
134
|
// Graceful degradation: .no-js class
|
|
125
135
|
if (options.hideCopyWithoutJs !== false) {
|
|
126
136
|
injections.push(
|
|
127
|
-
`<script>document.documentElement.classList.add('no-js');</script>`
|
|
137
|
+
`<script${nonceAttr}>document.documentElement.classList.add('no-js');</script>`
|
|
128
138
|
);
|
|
129
139
|
}
|
|
130
140
|
}
|
|
@@ -136,7 +146,7 @@ export default function perfectCode(
|
|
|
136
146
|
: 'auto';
|
|
137
147
|
if (safeTheme !== 'auto') {
|
|
138
148
|
injections.push(
|
|
139
|
-
`<script>document.documentElement.setAttribute('data-theme','${safeTheme}');</script>`
|
|
149
|
+
`<script${nonceAttr}>document.documentElement.setAttribute('data-theme','${safeTheme}');</script>`
|
|
140
150
|
);
|
|
141
151
|
}
|
|
142
152
|
}
|
package/src/copy-script.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - Falls back to execCommand for non-secure contexts
|
|
11
11
|
* - Respects `prefers-reduced-motion` (the CSS handles the animation)
|
|
12
12
|
* - Reads `data-done-label`, `data-success-icon`, `data-feedback-duration` from the button
|
|
13
|
+
* - Strips leading `#` comment lines when `data-strip-comments` is set (terminal preset)
|
|
13
14
|
* - Announces "Copied" to screen readers via an aria-live region (WCAG 4.1.2)
|
|
14
15
|
* - Hides copy button when JS is disabled (via .no-js class on <html>)
|
|
15
16
|
*/
|
|
@@ -50,6 +51,15 @@ export const COPY_SCRIPT = `
|
|
|
50
51
|
return btn.querySelector('svg');
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
// Strip leading comment lines (e.g. shell prompts like "# comment") from
|
|
55
|
+
// the text before copying. Used for terminal-preset blocks where the
|
|
56
|
+
// displayed code may include comments the user doesn't want on the clipboard.
|
|
57
|
+
function stripComments(text) {
|
|
58
|
+
// Strip lines that start with optional whitespace followed by # (shell),
|
|
59
|
+
// // (C-style), or REM (Windows batch). Keep everything else.
|
|
60
|
+
return text.replace(/^[ \\t]*(?:#|\\/\\/|REM\\b).*$/gm, '').replace(/\\n{3,}/g, '\\n\\n').trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
document.addEventListener('click', function (e) {
|
|
54
64
|
var btn = e.target && e.target.closest && e.target.closest('.pcb__copy');
|
|
55
65
|
if (!btn) return;
|
|
@@ -60,12 +70,16 @@ export const COPY_SCRIPT = `
|
|
|
60
70
|
var done = btn.getAttribute('data-done-label') || 'copied!';
|
|
61
71
|
var duration = parseInt(btn.getAttribute('data-feedback-duration') || '1600', 10);
|
|
62
72
|
var successIconHtml = btn.getAttribute('data-success-icon');
|
|
73
|
+
var stripCommentsFlag = btn.hasAttribute('data-strip-comments');
|
|
63
74
|
|
|
64
75
|
var label = findLabel(btn);
|
|
65
76
|
var icon = findIcon(btn);
|
|
66
77
|
var originalLabel = label ? label.textContent : null;
|
|
67
78
|
var originalIconHtml = icon ? icon.outerHTML : null;
|
|
68
79
|
|
|
80
|
+
var rawText = code.innerText;
|
|
81
|
+
var textToCopy = stripCommentsFlag ? stripComments(rawText) : rawText;
|
|
82
|
+
|
|
69
83
|
var finish = function () {
|
|
70
84
|
btn.classList.add('pcb__copy--done');
|
|
71
85
|
if (label) label.textContent = done;
|
|
@@ -93,14 +107,14 @@ export const COPY_SCRIPT = `
|
|
|
93
107
|
};
|
|
94
108
|
|
|
95
109
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
96
|
-
navigator.clipboard.writeText(
|
|
110
|
+
navigator.clipboard.writeText(textToCopy).then(finish).catch(fallback);
|
|
97
111
|
} else {
|
|
98
112
|
fallback();
|
|
99
113
|
}
|
|
100
114
|
|
|
101
115
|
function fallback() {
|
|
102
116
|
var ta = document.createElement('textarea');
|
|
103
|
-
ta.value =
|
|
117
|
+
ta.value = textToCopy;
|
|
104
118
|
ta.style.position = 'fixed';
|
|
105
119
|
ta.style.opacity = '0';
|
|
106
120
|
document.body.appendChild(ta);
|
package/src/index.ts
CHANGED
|
@@ -94,6 +94,8 @@ function resolveDefaults(opts: PerfectCodeOptions): Required<PerfectCodeOptions>
|
|
|
94
94
|
errorLevels: opts.errorLevels ?? true,
|
|
95
95
|
wrap: opts.wrap ?? false,
|
|
96
96
|
collapseAfter: opts.collapseAfter ?? null,
|
|
97
|
+
collapseRanges: opts.collapseRanges ?? null,
|
|
98
|
+
collapseStyle: opts.collapseStyle ?? 'github',
|
|
97
99
|
showWhitespace: opts.showWhitespace ?? false,
|
|
98
100
|
indentGuides: opts.indentGuides ?? false,
|
|
99
101
|
caption: opts.caption ?? true,
|
|
@@ -102,11 +104,17 @@ function resolveDefaults(opts: PerfectCodeOptions): Required<PerfectCodeOptions>
|
|
|
102
104
|
theme: { light: 'github-light', dark: 'github-dark' },
|
|
103
105
|
langs: [],
|
|
104
106
|
transformers: [],
|
|
107
|
+
transformerOrder: 'after',
|
|
105
108
|
...userShiki,
|
|
106
109
|
},
|
|
107
110
|
keepBackground: opts.keepBackground ?? false,
|
|
108
111
|
styleToClass: opts.styleToClass ?? false,
|
|
109
112
|
useHastApi: opts.useHastApi ?? true,
|
|
113
|
+
disableAutoTransformers: opts.disableAutoTransformers ?? false,
|
|
114
|
+
removeComments: opts.removeComments ?? false,
|
|
115
|
+
removeLineBreaks: opts.removeLineBreaks ?? false,
|
|
116
|
+
zeroIndexed: opts.zeroIndexed ?? false,
|
|
117
|
+
lineOptions: opts.lineOptions ?? [],
|
|
110
118
|
customNotations: opts.customNotations ?? {},
|
|
111
119
|
magicComments: opts.magicComments ?? [
|
|
112
120
|
{
|
|
@@ -119,14 +127,17 @@ function resolveDefaults(opts: PerfectCodeOptions): Required<PerfectCodeOptions>
|
|
|
119
127
|
inlineDefaultLang: opts.inlineDefaultLang ?? opts.defaultInlineLang ?? '',
|
|
120
128
|
defaultInlineLang: opts.defaultInlineLang ?? opts.inlineDefaultLang ?? '',
|
|
121
129
|
tokensMap: opts.tokensMap ?? {},
|
|
122
|
-
terminalLangs: opts.terminalLangs ?? ['sh', 'bash', 'zsh', 'shell', 'console', 'powershell', 'bat', 'cmd', 'fish'],
|
|
130
|
+
terminalLangs: opts.terminalLangs ?? ['sh', 'bash', 'zsh', 'shell', 'console', 'powershell', 'bat', 'cmd', 'fish', 'ansi'],
|
|
123
131
|
extractFileNameFromCode: opts.extractFileNameFromCode ?? false,
|
|
124
132
|
languageLabels: opts.languageLabels ?? {},
|
|
125
133
|
languageAliases: opts.languageAliases ?? {},
|
|
126
134
|
defaultBlockLang: opts.defaultBlockLang ?? '',
|
|
135
|
+
tabWidth: opts.tabWidth ?? 0,
|
|
136
|
+
copyStripComments: opts.copyStripComments ?? true,
|
|
127
137
|
accessibleScroll: opts.accessibleScroll ?? true,
|
|
128
138
|
announceCopy: opts.announceCopy ?? true,
|
|
129
139
|
hideCopyWithoutJs: opts.hideCopyWithoutJs ?? true,
|
|
140
|
+
terminalSrOnlyTitle: opts.terminalSrOnlyTitle ?? true,
|
|
130
141
|
rehypePlugins: opts.rehypePlugins ?? [],
|
|
131
142
|
filterMetaString: opts.filterMetaString ?? ((s) => s),
|
|
132
143
|
onVisitLine: opts.onVisitLine ?? (() => {}),
|
|
@@ -134,6 +145,9 @@ function resolveDefaults(opts: PerfectCodeOptions): Required<PerfectCodeOptions>
|
|
|
134
145
|
onVisitHighlightedChars: opts.onVisitHighlightedChars ?? (() => {}),
|
|
135
146
|
onVisitTitle: opts.onVisitTitle ?? (() => {}),
|
|
136
147
|
onVisitCaption: opts.onVisitCaption ?? (() => {}),
|
|
148
|
+
texts: opts.texts ?? {},
|
|
149
|
+
logger: opts.logger ?? console,
|
|
150
|
+
cspNonce: opts.cspNonce ?? '',
|
|
137
151
|
preset: opts.preset ?? 'default',
|
|
138
152
|
injectStyles: opts.injectStyles ?? true,
|
|
139
153
|
theme: opts.theme ?? 'auto',
|
package/src/meta.ts
CHANGED
|
@@ -56,6 +56,7 @@ export function parseMeta(meta: string | undefined): ParsedMeta {
|
|
|
56
56
|
highlightGroups: [],
|
|
57
57
|
wordHighlights: [],
|
|
58
58
|
lineNumbersStart: null,
|
|
59
|
+
collapseRanges: [],
|
|
59
60
|
flags: {
|
|
60
61
|
wrap: null,
|
|
61
62
|
lineNumbers: null,
|
|
@@ -84,6 +85,16 @@ export function parseMeta(meta: string | undefined): ParsedMeta {
|
|
|
84
85
|
continue;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
// collapse="5-12,20-30" — per-line collapsible sections
|
|
89
|
+
if (tok.startsWith('collapse=')) {
|
|
90
|
+
const val = unquote(tok.slice('collapse='.length));
|
|
91
|
+
result.collapseRanges = parseCollapseRanges(val);
|
|
92
|
+
if (result.collapseRanges.length > 0) {
|
|
93
|
+
result.flags.collapse = true;
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
// {1,3-5} or {1,3-5}#id — line highlight (optionally grouped)
|
|
88
99
|
if (tok.startsWith('{') && tok.includes('}')) {
|
|
89
100
|
const closeIdx = tok.indexOf('}');
|
|
@@ -165,8 +176,8 @@ function tokenize(input: string): string[] {
|
|
|
165
176
|
while (i < input.length && /\s/.test(input[i])) i++;
|
|
166
177
|
if (i >= input.length) break;
|
|
167
178
|
|
|
168
|
-
// Quoted key="value" or key='value' (title=, caption=)
|
|
169
|
-
if (/^(?:title|caption)=$/.test(input.slice(i).match(/^[a-z]+=/i)?.[0] ?? '')) {
|
|
179
|
+
// Quoted key="value" or key='value' (title=, caption=, collapse=)
|
|
180
|
+
if (/^(?:title|caption|collapse)=$/.test(input.slice(i).match(/^[a-z]+=/i)?.[0] ?? '')) {
|
|
170
181
|
const eq = input.indexOf('=', i);
|
|
171
182
|
let j = eq + 1;
|
|
172
183
|
const quote = input[j];
|
|
@@ -299,6 +310,28 @@ function unquote(s: string): string {
|
|
|
299
310
|
return s;
|
|
300
311
|
}
|
|
301
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Parse a collapse-range spec like "5-12,20-30" into structured ranges.
|
|
315
|
+
* Each part is either `N` (single line) or `N-M` (range, inclusive).
|
|
316
|
+
* Whitespace around commas and `-` is allowed.
|
|
317
|
+
*
|
|
318
|
+
* Example: "5-12, 20-30" → [{from:5,to:12}, {from:20,to:30}]
|
|
319
|
+
*/
|
|
320
|
+
function parseCollapseRanges(spec: string): { from: number; to: number }[] {
|
|
321
|
+
const out: { from: number; to: number }[] = [];
|
|
322
|
+
// Normalize whitespace around `-` so `5 - 12` parses correctly (mirrors parseRanges).
|
|
323
|
+
const normalized = spec.replace(/\s*-\s*/g, '-');
|
|
324
|
+
for (const part of normalized.split(/[\s,]+/)) {
|
|
325
|
+
if (!part) continue;
|
|
326
|
+
const m = part.match(/^(\d+)(?:-(\d+))?$/);
|
|
327
|
+
if (!m) continue;
|
|
328
|
+
const start = parseInt(m[1], 10);
|
|
329
|
+
const end = m[2] ? parseInt(m[2], 10) : start;
|
|
330
|
+
out.push({ from: Math.min(start, end), to: Math.max(start, end) });
|
|
331
|
+
}
|
|
332
|
+
return out.sort((a, b) => a.from - b.from);
|
|
333
|
+
}
|
|
334
|
+
|
|
302
335
|
function unescapeRegex(s: string): string {
|
|
303
336
|
return s.replace(/\\\//g, '/').replace(/\\\\/g, '\\');
|
|
304
337
|
}
|