@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/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
- /** Theme — string for single theme, { light, dark } for dual-theme via CSS vars. */
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
- * Add `role="region"` and `aria-label` to scrollable code blocks (WCAG 4.1.2).
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;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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.1.7",
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
+ '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '&': '&amp;',
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
  }
@@ -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(code.innerText).then(finish).catch(fallback);
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 = code.innerText;
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
  }