@dr-ishaan/rehype-perfect-code-blocks 1.3.3 → 2.1.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 +175 -0
- package/dist/astro.d.ts.map +1 -1
- package/dist/astro.js +17 -2
- package/dist/astro.js.map +1 -1
- package/dist/copy-script.d.ts +1 -1
- package/dist/copy-script.d.ts.map +1 -1
- package/dist/copy-script.js +5 -0
- package/dist/copy-script.js.map +1 -1
- package/dist/dev-warnings.d.ts +36 -0
- package/dist/dev-warnings.d.ts.map +1 -0
- package/dist/dev-warnings.js +95 -0
- package/dist/dev-warnings.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/math.d.ts +92 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +229 -0
- package/dist/math.js.map +1 -0
- package/dist/shiki.d.ts.map +1 -1
- package/dist/shiki.js +52 -1
- package/dist/shiki.js.map +1 -1
- package/dist/styles.css +0 -0
- package/dist/tokens.d.ts +62 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +168 -0
- package/dist/tokens.js.map +1 -0
- package/dist/transformer.d.ts.map +1 -1
- package/dist/transformer.js +7 -0
- package/dist/transformer.js.map +1 -1
- package/dist/types.d.ts +203 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/astro.ts +20 -2
- package/src/copy-script.ts +5 -0
- package/src/dev-warnings.ts +125 -0
- package/src/index.ts +18 -1
- package/src/katex.d.ts +16 -0
- package/src/math.ts +268 -0
- package/src/shiki.ts +54 -1
- package/src/styles.css +0 -0
- package/src/tokens.ts +223 -0
- package/src/transformer.ts +7 -0
- package/src/types.ts +216 -0
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design-token bridge — derives 20+ --pcb-* variables from 5 core values.
|
|
3
|
+
*
|
|
4
|
+
* v2.0.0 feature. Uses `color-mix(in oklch, ...)` for automatic color
|
|
5
|
+
* derivation, which is supported in all modern browsers (Chrome 111+,
|
|
6
|
+
* Safari 16.4+, Firefox 113+). For older browsers, the fallback values
|
|
7
|
+
* from the Shiki theme (Pattern 2, v1.3.0) still apply on the <pre> element.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface DesignTokens {
|
|
11
|
+
bg?: string;
|
|
12
|
+
fg?: string;
|
|
13
|
+
border?: string;
|
|
14
|
+
radius?: string;
|
|
15
|
+
monoFont?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate a CSS style string containing all derived --pcb-* variables
|
|
20
|
+
* from the 5 core token values. Returns an empty string if no tokens
|
|
21
|
+
* are provided.
|
|
22
|
+
*
|
|
23
|
+
* The generated CSS is applied to `:where(.pcb)` so it has zero
|
|
24
|
+
* specificity — user CSS always wins.
|
|
25
|
+
*/
|
|
26
|
+
export function generateTokenStyles(tokens: DesignTokens, scope?: string): string {
|
|
27
|
+
if (!tokens || Object.keys(tokens).length === 0) return '';
|
|
28
|
+
|
|
29
|
+
const { bg, fg, border, radius, monoFont } = tokens;
|
|
30
|
+
if (!bg && !fg && !border && !radius && !monoFont) return '';
|
|
31
|
+
|
|
32
|
+
const scopePrefix = scope ? `${scope} ` : '';
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
|
|
35
|
+
// Direct mappings (1:1)
|
|
36
|
+
if (bg) lines.push(` --pcb-bg: ${bg};`);
|
|
37
|
+
if (fg) lines.push(` --pcb-fg: ${fg};`);
|
|
38
|
+
if (border) lines.push(` --pcb-border: ${border};`);
|
|
39
|
+
if (radius) lines.push(` --pcb-radius: ${radius};`);
|
|
40
|
+
if (monoFont) lines.push(` --pcb-font-mono: ${monoFont};`);
|
|
41
|
+
|
|
42
|
+
// Derived mappings (auto-computed from core values via color-mix)
|
|
43
|
+
// Only derive if BOTH bg and fg are available (needed for color-mix)
|
|
44
|
+
if (bg && fg) {
|
|
45
|
+
// Line numbers: muted (50% mix of fg over bg)
|
|
46
|
+
lines.push(` --pcb-text-muted: color-mix(in oklch, ${fg}, ${bg} 50%);`);
|
|
47
|
+
lines.push(` --pcb-ln-fg: color-mix(in oklch, ${fg}, ${bg} 50%);`);
|
|
48
|
+
|
|
49
|
+
// Header bar background: slightly different from body (5% fg over bg)
|
|
50
|
+
lines.push(` --pcb-bg-header: color-mix(in oklch, ${fg} 5%, ${bg});`);
|
|
51
|
+
|
|
52
|
+
// Header text: muted (30% fg mixed toward bg)
|
|
53
|
+
lines.push(` --pcb-text-bar: color-mix(in oklch, ${fg}, ${bg} 30%);`);
|
|
54
|
+
|
|
55
|
+
// Line highlight: subtle tint (12% fg over bg)
|
|
56
|
+
lines.push(` --pcb-line-highlight: color-mix(in oklch, ${fg} 12%, ${bg});`);
|
|
57
|
+
|
|
58
|
+
// Diff add: green tint (18% green over bg)
|
|
59
|
+
lines.push(` --pcb-line-add: color-mix(in oklch, #2ea043 18%, ${bg});`);
|
|
60
|
+
|
|
61
|
+
// Diff del: red tint (18% red over bg)
|
|
62
|
+
lines.push(` --pcb-line-del: color-mix(in oklch, #f85149 18%, ${bg});`);
|
|
63
|
+
|
|
64
|
+
// Focus: blue tint (18% blue over bg)
|
|
65
|
+
lines.push(` --pcb-line-focus: color-mix(in oklch, #58a6ff 18%, ${bg});`);
|
|
66
|
+
|
|
67
|
+
// Copy button hover background: subtle (8% fg over bg)
|
|
68
|
+
lines.push(` --pcb-copy-hover-bg: color-mix(in oklch, ${fg} 8%, ${bg});`);
|
|
69
|
+
|
|
70
|
+
// Gutter background: same as bg by default
|
|
71
|
+
lines.push(` --pcb-bg-gutter: ${bg};`);
|
|
72
|
+
|
|
73
|
+
// Caption background: slightly different (like header)
|
|
74
|
+
lines.push(` --pcb-caption-bg: color-mix(in oklch, ${fg} 5%, ${bg});`);
|
|
75
|
+
|
|
76
|
+
// Caption color: muted (like header text)
|
|
77
|
+
lines.push(` --pcb-caption-color: color-mix(in oklch, ${fg}, ${bg} 30%);`);
|
|
78
|
+
|
|
79
|
+
// Word highlight: gold tint (30% gold over bg)
|
|
80
|
+
lines.push(` --pcb-word-bg: color-mix(in oklch, #bb8009 30%, ${bg});`);
|
|
81
|
+
|
|
82
|
+
// Word highlight (id): blue tint (30% blue over bg)
|
|
83
|
+
lines.push(` --pcb-word-bg-id: color-mix(in oklch, #58a6ff 30%, ${bg});`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (lines.length === 0) return '';
|
|
87
|
+
|
|
88
|
+
return `:where(${scopePrefix}.pcb) {\n${lines.join('\n')}\n}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generate the dark-mode CSS selector based on the user's dark mode strategy.
|
|
93
|
+
*
|
|
94
|
+
* Returns the selector prefix that should be placed before `.pcb` in CSS rules.
|
|
95
|
+
* For 'media' strategy, returns empty string (the rules are wrapped in @media).
|
|
96
|
+
*/
|
|
97
|
+
export function generateDarkModeSelector(
|
|
98
|
+
darkMode?: {
|
|
99
|
+
strategy?: 'media' | 'attribute' | 'class' | 'custom';
|
|
100
|
+
attribute?: string;
|
|
101
|
+
attributeValue?: string;
|
|
102
|
+
class?: string;
|
|
103
|
+
customSelector?: string;
|
|
104
|
+
},
|
|
105
|
+
scope?: string
|
|
106
|
+
): { selector: string; mediaQuery: string | null } {
|
|
107
|
+
const scopePrefix = scope ? `${scope} ` : '';
|
|
108
|
+
|
|
109
|
+
if (!darkMode || darkMode.strategy === 'media' || !darkMode.strategy) {
|
|
110
|
+
// Default: prefers-color-scheme media query
|
|
111
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: dark' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (darkMode.strategy === 'attribute') {
|
|
115
|
+
const attr = darkMode.attribute ?? 'data-theme';
|
|
116
|
+
const val = darkMode.attributeValue ?? 'dark';
|
|
117
|
+
return {
|
|
118
|
+
selector: `html[${attr}="${val}"] ${scopePrefix}`.trim(),
|
|
119
|
+
mediaQuery: null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (darkMode.strategy === 'class') {
|
|
124
|
+
const cls = darkMode.class ?? 'dark';
|
|
125
|
+
return {
|
|
126
|
+
selector: `html.${cls} ${scopePrefix}`.trim(),
|
|
127
|
+
mediaQuery: null,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (darkMode.strategy === 'custom') {
|
|
132
|
+
const sel = darkMode.customSelector ?? ':root';
|
|
133
|
+
return {
|
|
134
|
+
selector: `${sel} ${scopePrefix}`.trim(),
|
|
135
|
+
mediaQuery: null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: dark' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Generate the light-mode CSS selector (the inverse of dark mode).
|
|
144
|
+
* When dark mode is NOT active, light mode defaults apply.
|
|
145
|
+
*/
|
|
146
|
+
export function generateLightModeSelector(
|
|
147
|
+
darkMode?: {
|
|
148
|
+
strategy?: 'media' | 'attribute' | 'class' | 'custom';
|
|
149
|
+
attribute?: string;
|
|
150
|
+
attributeValue?: string;
|
|
151
|
+
class?: string;
|
|
152
|
+
customSelector?: string;
|
|
153
|
+
},
|
|
154
|
+
scope?: string
|
|
155
|
+
): { selector: string; mediaQuery: string | null } {
|
|
156
|
+
const scopePrefix = scope ? `${scope} ` : '';
|
|
157
|
+
|
|
158
|
+
if (!darkMode || darkMode.strategy === 'media' || !darkMode.strategy) {
|
|
159
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: light' };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (darkMode.strategy === 'attribute') {
|
|
163
|
+
const attr = darkMode.attribute ?? 'data-theme';
|
|
164
|
+
const val = darkMode.attributeValue ?? 'dark';
|
|
165
|
+
// Light = when the attribute is NOT the dark value
|
|
166
|
+
return {
|
|
167
|
+
selector: `html:not([${attr}="${val}"]) ${scopePrefix}`.trim(),
|
|
168
|
+
mediaQuery: null,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (darkMode.strategy === 'class') {
|
|
173
|
+
const cls = darkMode.class ?? 'dark';
|
|
174
|
+
return {
|
|
175
|
+
selector: `html:not(.${cls}) ${scopePrefix}`.trim(),
|
|
176
|
+
mediaQuery: null,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (darkMode.strategy === 'custom') {
|
|
181
|
+
// For custom, light = when the custom selector does NOT match
|
|
182
|
+
// This is tricky — we can't easily negate an arbitrary selector.
|
|
183
|
+
// Fall back to media query for the light case.
|
|
184
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: light' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { selector: '', mediaQuery: 'prefers-color-scheme: light' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Apply a scope prefix to all CSS selectors in a stylesheet.
|
|
192
|
+
* This is a simple regex-based approach that handles the plugin's CSS
|
|
193
|
+
* structure. It prefixes every selector that starts with `.pcb` or
|
|
194
|
+
* `:where(.pcb` or `html` with the scope.
|
|
195
|
+
*/
|
|
196
|
+
export function applyScopeToCss(css: string, scope: string): string {
|
|
197
|
+
if (!scope) return css;
|
|
198
|
+
|
|
199
|
+
// Don't double-prefix if the scope is already present
|
|
200
|
+
if (css.includes(`${scope} .pcb`) || css.includes(`${scope}.pcb`)) return css;
|
|
201
|
+
|
|
202
|
+
// Prefix selectors that target .pcb or html.no-js
|
|
203
|
+
let result = css;
|
|
204
|
+
|
|
205
|
+
// :where(.pcb) → :where(scope .pcb)
|
|
206
|
+
result = result.replace(/:where\(\.pcb\)/g, `:where(${scope} .pcb)`);
|
|
207
|
+
|
|
208
|
+
// :where(html:not(...)) :where(.pcb:not(...)) → :where(scope html:not(...)) :where(scope .pcb:not(...))
|
|
209
|
+
// This is complex — for nested :where() with html, just prefix the whole compound
|
|
210
|
+
result = result.replace(/:where\(html/g, `:where(${scope} html`);
|
|
211
|
+
|
|
212
|
+
// .pcb pre → scope .pcb pre (the framework-reset overrides)
|
|
213
|
+
result = result.replace(/^(\.pcb\s)/gm, `${scope} $1`);
|
|
214
|
+
|
|
215
|
+
// .pcb__copy → scope .pcb__copy (note: .pcb__ starts with a dot, don't add another)
|
|
216
|
+
result = result.replace(/^(\.pcb__)/gm, `${scope} $1`);
|
|
217
|
+
|
|
218
|
+
// html.no-js .pcb__copy → scope html.no-js scope .pcb__copy
|
|
219
|
+
// (This is an edge case — the no-js rule needs both html and .pcb scoped)
|
|
220
|
+
result = result.replace(/html\.no-js\s+\.pcb/g, `${scope} html.no-js ${scope} .pcb`);
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
package/src/transformer.ts
CHANGED
|
@@ -242,6 +242,13 @@ export function rehypePerfectCodeBlocks(userOptions: PerfectCodeOptions = {}) {
|
|
|
242
242
|
preset: 'default',
|
|
243
243
|
injectStyles: true,
|
|
244
244
|
theme: 'auto',
|
|
245
|
+
cssInjection: 'inline' as const,
|
|
246
|
+
cssLayer: 'pcb',
|
|
247
|
+
tokens: undefined as unknown as NonNullable<PerfectCodeOptions['tokens']>,
|
|
248
|
+
darkMode: undefined as unknown as NonNullable<PerfectCodeOptions['darkMode']>,
|
|
249
|
+
scope: undefined as unknown as string,
|
|
250
|
+
math: undefined as unknown as NonNullable<PerfectCodeOptions['math']>,
|
|
251
|
+
devWarnings: process.env.NODE_ENV !== 'production',
|
|
245
252
|
inline: false,
|
|
246
253
|
...rest,
|
|
247
254
|
};
|
package/src/types.ts
CHANGED
|
@@ -122,6 +122,21 @@ export interface PerfectCodeOptions {
|
|
|
122
122
|
transformerOrder?: 'before' | 'after';
|
|
123
123
|
/** Override the highlighter factory (e.g. for custom TextMate grammars). */
|
|
124
124
|
getHighlighter?: (opts: { themes: string[]; langs: string[] }) => Promise<unknown>;
|
|
125
|
+
/**
|
|
126
|
+
* v2.1.0: Lazy initialization — don't load Shiki until the first code block
|
|
127
|
+
* is encountered. On pages with no code blocks, Shiki (and its WASM engine)
|
|
128
|
+
* is never loaded, saving ~1MB of bundle.
|
|
129
|
+
*
|
|
130
|
+
* Default: false (Shiki initializes eagerly, same as v1.x/v2.0)
|
|
131
|
+
*/
|
|
132
|
+
lazy?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* v2.1.0: Languages to preload when lazy is true. Only loaded if the
|
|
135
|
+
* document contains at least one code block.
|
|
136
|
+
*
|
|
137
|
+
* Default: ['typescript', 'bash', 'javascript', 'json', 'html', 'css']
|
|
138
|
+
*/
|
|
139
|
+
preloadLangs?: string[];
|
|
125
140
|
[key: string]: unknown;
|
|
126
141
|
};
|
|
127
142
|
/**
|
|
@@ -334,6 +349,207 @@ export interface PerfectCodeOptions {
|
|
|
334
349
|
/** Manual theme override. Default: 'auto' (prefers-color-scheme) */
|
|
335
350
|
theme?: 'auto' | 'dark' | 'light';
|
|
336
351
|
|
|
352
|
+
/* ---------- v2.0.0: CSS Architecture ---------- */
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* CSS injection strategy. Controls how the plugin's stylesheet is delivered.
|
|
356
|
+
*
|
|
357
|
+
* - `'inline'` (default) — injects the full CSS in a `<style>` tag on every page.
|
|
358
|
+
* Simple, works everywhere, but the CSS is unlayered (can be overridden by
|
|
359
|
+
* framework resets on cascade ties).
|
|
360
|
+
*
|
|
361
|
+
* - `'layer'` — injects the CSS wrapped in `@layer <cssLayer> { ... }`.
|
|
362
|
+
* On sites using cascade layers (Tailwind v3+, daisyUI, etc.), this ensures
|
|
363
|
+
* the plugin's CSS sits in the correct layer and framework resets don't
|
|
364
|
+
* win on specificity ties. Requires `cssLayer` to be set.
|
|
365
|
+
*
|
|
366
|
+
* - `'import'` — does NOT inject CSS. The user imports it manually:
|
|
367
|
+
* `import '@dr-ishaan/rehype-perfect-code-blocks/styles.css'`
|
|
368
|
+
* Useful for sites that want full control over CSS loading (e.g., bundling
|
|
369
|
+
* with PostCSS, or importing into a specific `@layer` in their own CSS).
|
|
370
|
+
*
|
|
371
|
+
* Default: `'inline'` (backward-compatible with v1.x).
|
|
372
|
+
*/
|
|
373
|
+
cssInjection?: 'inline' | 'layer' | 'import';
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* The cascade layer name to use when `cssInjection: 'layer'`.
|
|
377
|
+
* The plugin's CSS will be wrapped in `@layer <cssLayer> { ... }`.
|
|
378
|
+
*
|
|
379
|
+
* Example: `cssLayer: 'components'` → all plugin CSS goes into
|
|
380
|
+
* `@layer components { ... }`, which the user can order above
|
|
381
|
+
* `@layer base` (framework resets) in their CSS:
|
|
382
|
+
*
|
|
383
|
+
* ```css
|
|
384
|
+
* @layer base, components, utilities;
|
|
385
|
+
* ```
|
|
386
|
+
*
|
|
387
|
+
* Default: `'pcb'`
|
|
388
|
+
*/
|
|
389
|
+
cssLayer?: string;
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Design-token bridge — provide 5 core values and the plugin auto-derives
|
|
393
|
+
* all 20+ `--pcb-*` variables. This is a shortcut for mapping the plugin
|
|
394
|
+
* to an external design system (Tailwind theme, daisyUI theme, CSS custom
|
|
395
|
+
* properties, etc.) without manually overriding every variable.
|
|
396
|
+
*
|
|
397
|
+
* Each value can be a CSS value string (e.g., `'#1a1b26'`) or a
|
|
398
|
+
* `var(--your-var)` reference.
|
|
399
|
+
*
|
|
400
|
+
* When set, the plugin generates a `<style>` block with the derived
|
|
401
|
+
* `--pcb-*` variables applied to `:where(.pcb)` (zero specificity, so
|
|
402
|
+
* user CSS still wins). If a specific `--pcb-*` variable is ALSO set
|
|
403
|
+
* in user CSS, the user's value wins (the token bridge is a default,
|
|
404
|
+
* not an override).
|
|
405
|
+
*
|
|
406
|
+
* Example:
|
|
407
|
+
* ```js
|
|
408
|
+
* perfectCode({
|
|
409
|
+
* tokens: {
|
|
410
|
+
* bg: 'var(--bg-subtle)',
|
|
411
|
+
* fg: 'var(--ink)',
|
|
412
|
+
* border: 'var(--rule)',
|
|
413
|
+
* radius: 'var(--radius-card)',
|
|
414
|
+
* monoFont: 'var(--font-mono)',
|
|
415
|
+
* },
|
|
416
|
+
* })
|
|
417
|
+
* ```
|
|
418
|
+
*
|
|
419
|
+
* The plugin auto-derives:
|
|
420
|
+
* - `--pcb-bg` = `bg`
|
|
421
|
+
* - `--pcb-fg` = `fg`
|
|
422
|
+
* - `--pcb-border` = `border`
|
|
423
|
+
* - `--pcb-radius` = `radius`
|
|
424
|
+
* - `--pcb-font-mono` = `monoFont`
|
|
425
|
+
* - `--pcb-ln-fg` = `color-mix(in oklch, fg, bg 50%)` (muted line numbers)
|
|
426
|
+
* - `--pcb-bg-header` = `color-mix(in oklch, fg 5%, bg)` (header slightly different)
|
|
427
|
+
* - `--pcb-text-bar` = `color-mix(in oklch, fg, bg 30%)` (header text muted)
|
|
428
|
+
* - `--pcb-line-highlight` = `color-mix(in oklch, fg 12%, bg)` (subtle highlight)
|
|
429
|
+
* - `--pcb-line-add` = `color-mix(in oklch, #2ea043 18%, bg)` (diff add)
|
|
430
|
+
* - `--pcb-line-del` = `color-mix(in oklch, #f85149 18%, bg)` (diff del)
|
|
431
|
+
* - `--pcb-line-focus` = `color-mix(in oklch, #58a6ff 18%, bg)` (focus)
|
|
432
|
+
* - `--pcb-copy-bg` (hover) = `color-mix(in oklch, fg 8%, bg)`
|
|
433
|
+
*
|
|
434
|
+
* Uses `color-mix(in oklch, ...)` which is supported in all modern browsers
|
|
435
|
+
* (Chrome 111+, Safari 16.4+, Firefox 113+). For older browsers, the
|
|
436
|
+
* fallback values from the Shiki theme (Pattern 2, v1.3.0) still apply.
|
|
437
|
+
*
|
|
438
|
+
* Default: `undefined` (no token bridge; use the plugin's built-in defaults)
|
|
439
|
+
*/
|
|
440
|
+
tokens?: {
|
|
441
|
+
/** Background color of the code block surface. */
|
|
442
|
+
bg?: string;
|
|
443
|
+
/** Foreground (text) color of the code. */
|
|
444
|
+
fg?: string;
|
|
445
|
+
/** Border color of the code block frame. */
|
|
446
|
+
border?: string;
|
|
447
|
+
/** Border radius of the code block. */
|
|
448
|
+
radius?: string;
|
|
449
|
+
/** Monospace font family for code text. */
|
|
450
|
+
monoFont?: string;
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Dark mode strategy — controls how the plugin switches between light
|
|
455
|
+
* and dark themes.
|
|
456
|
+
*
|
|
457
|
+
* - `'media'` (default) — uses `@media (prefers-color-scheme: dark)`.
|
|
458
|
+
* Automatic, no configuration needed. Same as v1.x behavior.
|
|
459
|
+
*
|
|
460
|
+
* - `'attribute'` — switches based on an attribute on `<html>`.
|
|
461
|
+
* Requires `darkMode.attribute` and `darkMode.attributeValue`.
|
|
462
|
+
* Example: `darkMode: { strategy: 'attribute', attribute: 'data-theme', attributeValue: 'dark' }`
|
|
463
|
+
* → CSS: `html[data-theme="dark"] .pcb { ... }`
|
|
464
|
+
*
|
|
465
|
+
* - `'class'` — switches based on a class on `<html>`.
|
|
466
|
+
* Requires `darkMode.class`.
|
|
467
|
+
* Example: `darkMode: { strategy: 'class', class: 'dark' }`
|
|
468
|
+
* → CSS: `html.dark .pcb { ... }`
|
|
469
|
+
*
|
|
470
|
+
* - `'custom'` — uses a custom CSS selector.
|
|
471
|
+
* Requires `darkMode.customSelector`.
|
|
472
|
+
* Example: `darkMode: { strategy: 'custom', customSelector: ':root[data-mode="night"]' }`
|
|
473
|
+
* → CSS: `:root[data-mode="night"] .pcb { ... }`
|
|
474
|
+
*
|
|
475
|
+
* When using Shiki dual themes (`shiki.theme: { light, dark }`), the
|
|
476
|
+
* Shiki token CSS variables (`--shiki-light` / `--shiki-dark`) are also
|
|
477
|
+
* switched using the same strategy, not just `prefers-color-scheme`.
|
|
478
|
+
*
|
|
479
|
+
* Default: `'media'` (backward-compatible with v1.x)
|
|
480
|
+
*/
|
|
481
|
+
darkMode?: {
|
|
482
|
+
strategy: 'media' | 'attribute' | 'class' | 'custom';
|
|
483
|
+
/** For `strategy: 'attribute'` — the attribute name on `<html>`. Default: `'data-theme'` */
|
|
484
|
+
attribute?: string;
|
|
485
|
+
/** For `strategy: 'attribute'` — the attribute value that triggers dark mode. Default: `'dark'` */
|
|
486
|
+
attributeValue?: string;
|
|
487
|
+
/** For `strategy: 'class'` — the class name on `<html>` that triggers dark mode. Default: `'dark'` */
|
|
488
|
+
class?: string;
|
|
489
|
+
/** For `strategy: 'custom'` — the full CSS selector that triggers dark mode. */
|
|
490
|
+
customSelector?: string;
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* CSS containment scope — prefix all generated CSS selectors with this
|
|
495
|
+
* string. Useful when code blocks appear in specific contexts (e.g., only
|
|
496
|
+
* inside `.prose` or `article`) and you don't want the plugin's CSS to
|
|
497
|
+
* affect code blocks elsewhere on the page.
|
|
498
|
+
*
|
|
499
|
+
* Example: `scope: '.prose'` → all selectors become `.prose .pcb { ... }`,
|
|
500
|
+
* `.prose .pcb__header { ... }`, etc.
|
|
501
|
+
*
|
|
502
|
+
* The scope is applied to ALL generated CSS, including the framework-reset
|
|
503
|
+
* overrides and the dark-mode selectors.
|
|
504
|
+
*
|
|
505
|
+
* Default: `undefined` (no scoping; CSS applies everywhere)
|
|
506
|
+
*/
|
|
507
|
+
scope?: string;
|
|
508
|
+
|
|
509
|
+
/* ---------- v2.1.0: Math/LaTeX rendering ---------- */
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Math/LaTeX rendering via KaTeX. When enabled, the plugin intercepts:
|
|
513
|
+
* - Inline math: `$...$` in text (when `math.inline` is true)
|
|
514
|
+
* - Block math: `$$...$$` blocks (when `math.block` is true)
|
|
515
|
+
* - Fenced code blocks with language `math`, `latex`, or `tex`
|
|
516
|
+
*
|
|
517
|
+
* KaTeX renders at build time (server-side) — no client-side JS needed.
|
|
518
|
+
* The KaTeX CSS + fonts must be loaded on the client (the plugin injects
|
|
519
|
+
* a `<link>` to KaTeX CSS if `math.injectCss` is true).
|
|
520
|
+
*
|
|
521
|
+
* `katex` must be installed: `npm install katex`
|
|
522
|
+
*
|
|
523
|
+
* Default: `undefined` (no math rendering; `$...$` renders as literal text)
|
|
524
|
+
*/
|
|
525
|
+
math?: {
|
|
526
|
+
/** Math engine. 'katex' renders via KaTeX (must be installed). Default: 'none' */
|
|
527
|
+
engine?: 'katex' | 'none';
|
|
528
|
+
/** Render inline `$...$` math. Default: true */
|
|
529
|
+
inline?: boolean;
|
|
530
|
+
/** Render block `$$...$$` and ```math blocks. Default: true */
|
|
531
|
+
block?: boolean;
|
|
532
|
+
/** Inject KaTeX CSS alongside plugin CSS. Default: true */
|
|
533
|
+
injectCss?: boolean;
|
|
534
|
+
/** Don't crash on invalid LaTeX — render the source as-is. Default: true */
|
|
535
|
+
throwOnError?: boolean;
|
|
536
|
+
/** Allow non-standard LaTeX commands. Default: false */
|
|
537
|
+
strict?: boolean | 'ignore' | 'error' | 'warn';
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
/* ---------- v2.1.0: Development warnings ---------- */
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Emit warnings to the logger during build/dev for common misconfigurations:
|
|
544
|
+
* - Unknown language not loaded in Shiki
|
|
545
|
+
* - Invalid meta syntax (e.g., `{1,a-5}` instead of `{1,3-5}`)
|
|
546
|
+
* - Conflicting options (e.g., `wrap` + `collapseAfter` both enabled)
|
|
547
|
+
* - Code block inside raw HTML detected but rehype-raw not installed
|
|
548
|
+
*
|
|
549
|
+
* Default: true in development (when `NODE_ENV !== 'production'`), false in production.
|
|
550
|
+
*/
|
|
551
|
+
devWarnings?: boolean;
|
|
552
|
+
|
|
337
553
|
/* ---------- Inline code (legacy cosmetic option) ---------- */
|
|
338
554
|
/** Also style inline `code` cosmetically (no tokenization). Default: false */
|
|
339
555
|
inline?: boolean;
|