@dr-ishaan/rehype-perfect-code-blocks 2.0.0 → 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 +86 -0
- 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 +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -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/transformer.d.ts.map +1 -1
- package/dist/transformer.js +2 -0
- package/dist/transformer.js.map +1 -1
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/copy-script.ts +5 -0
- package/src/dev-warnings.ts +125 -0
- package/src/index.ts +9 -1
- package/src/katex.d.ts +16 -0
- package/src/math.ts +268 -0
- package/src/shiki.ts +54 -1
- package/src/transformer.ts +2 -0
- package/src/types.ts +59 -0
package/dist/types.d.ts
CHANGED
|
@@ -114,6 +114,21 @@ export interface PerfectCodeOptions {
|
|
|
114
114
|
themes: string[];
|
|
115
115
|
langs: string[];
|
|
116
116
|
}) => Promise<unknown>;
|
|
117
|
+
/**
|
|
118
|
+
* v2.1.0: Lazy initialization — don't load Shiki until the first code block
|
|
119
|
+
* is encountered. On pages with no code blocks, Shiki (and its WASM engine)
|
|
120
|
+
* is never loaded, saving ~1MB of bundle.
|
|
121
|
+
*
|
|
122
|
+
* Default: false (Shiki initializes eagerly, same as v1.x/v2.0)
|
|
123
|
+
*/
|
|
124
|
+
lazy?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* v2.1.0: Languages to preload when lazy is true. Only loaded if the
|
|
127
|
+
* document contains at least one code block.
|
|
128
|
+
*
|
|
129
|
+
* Default: ['typescript', 'bash', 'javascript', 'json', 'html', 'css']
|
|
130
|
+
*/
|
|
131
|
+
preloadLangs?: string[];
|
|
117
132
|
[key: string]: unknown;
|
|
118
133
|
};
|
|
119
134
|
/**
|
|
@@ -474,6 +489,44 @@ export interface PerfectCodeOptions {
|
|
|
474
489
|
* Default: `undefined` (no scoping; CSS applies everywhere)
|
|
475
490
|
*/
|
|
476
491
|
scope?: string;
|
|
492
|
+
/**
|
|
493
|
+
* Math/LaTeX rendering via KaTeX. When enabled, the plugin intercepts:
|
|
494
|
+
* - Inline math: `$...$` in text (when `math.inline` is true)
|
|
495
|
+
* - Block math: `$$...$$` blocks (when `math.block` is true)
|
|
496
|
+
* - Fenced code blocks with language `math`, `latex`, or `tex`
|
|
497
|
+
*
|
|
498
|
+
* KaTeX renders at build time (server-side) — no client-side JS needed.
|
|
499
|
+
* The KaTeX CSS + fonts must be loaded on the client (the plugin injects
|
|
500
|
+
* a `<link>` to KaTeX CSS if `math.injectCss` is true).
|
|
501
|
+
*
|
|
502
|
+
* `katex` must be installed: `npm install katex`
|
|
503
|
+
*
|
|
504
|
+
* Default: `undefined` (no math rendering; `$...$` renders as literal text)
|
|
505
|
+
*/
|
|
506
|
+
math?: {
|
|
507
|
+
/** Math engine. 'katex' renders via KaTeX (must be installed). Default: 'none' */
|
|
508
|
+
engine?: 'katex' | 'none';
|
|
509
|
+
/** Render inline `$...$` math. Default: true */
|
|
510
|
+
inline?: boolean;
|
|
511
|
+
/** Render block `$$...$$` and ```math blocks. Default: true */
|
|
512
|
+
block?: boolean;
|
|
513
|
+
/** Inject KaTeX CSS alongside plugin CSS. Default: true */
|
|
514
|
+
injectCss?: boolean;
|
|
515
|
+
/** Don't crash on invalid LaTeX — render the source as-is. Default: true */
|
|
516
|
+
throwOnError?: boolean;
|
|
517
|
+
/** Allow non-standard LaTeX commands. Default: false */
|
|
518
|
+
strict?: boolean | 'ignore' | 'error' | 'warn';
|
|
519
|
+
};
|
|
520
|
+
/**
|
|
521
|
+
* Emit warnings to the logger during build/dev for common misconfigurations:
|
|
522
|
+
* - Unknown language not loaded in Shiki
|
|
523
|
+
* - Invalid meta syntax (e.g., `{1,a-5}` instead of `{1,3-5}`)
|
|
524
|
+
* - Conflicting options (e.g., `wrap` + `collapseAfter` both enabled)
|
|
525
|
+
* - Code block inside raw HTML detected but rehype-raw not installed
|
|
526
|
+
*
|
|
527
|
+
* Default: true in development (when `NODE_ENV !== 'production'`), false in production.
|
|
528
|
+
*/
|
|
529
|
+
devWarnings?: boolean;
|
|
477
530
|
/** Also style inline `code` cosmetically (no tokenization). Default: false */
|
|
478
531
|
inline?: boolean;
|
|
479
532
|
}
|
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;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,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;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yDAAyD;IACzD,aAAa,CAAC,EAAE,QAAQ,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;IACxF,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;;;;WAIG;QACH,KAAK,CAAC,EAAE,MAAM,GAAG;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC1E,uFAAuF;QACvF,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB;;;;WAIG;QACH,WAAW,CAAC,EAAE,WAAW,GAAG,YAAY,CAAC;QACzC,0EAA0E;QAC1E,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAClC;;;;WAIG;QACH,gBAAgB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;QACtC,4EAA4E;QAC5E,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YAAC,KAAK,EAAE,MAAM,EAAE,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;QACnF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EAAE,CAAC;IAGrF;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAG/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IACxC,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAGnC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAG5B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;IAG1B,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,sDAAsD;IACtD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACvE,yCAAyC;IACzC,sBAAsB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/F,+CAA+C;IAC/C,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3F,iDAAiD;IACjD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,mDAAmD;IACnD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAG5C;;;OAGG;IACH,KAAK,CAAC,EAAE;QACN,2CAA2C;QAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,8DAA8D;QAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,6DAA6D;QAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,wFAAwF;QACxF,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,qEAAqE;QACrE,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,oGAAoG;QACpG,mBAAmB,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;KAC7C,CAAC;IAGF;;;OAGG;IACH,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IAGvE;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IAC5C,sFAAsF;IACtF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAIlC;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IAE7C;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACH,MAAM,CAAC,EAAE;QACP,kDAAkD;QAClD,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,2CAA2C;QAC3C,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,4CAA4C;QAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,uCAAuC;QACvC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,2CAA2C;QAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,OAAO,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;QACrD,4FAA4F;QAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,mGAAmG;QACnG,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,sGAAsG;QACtG,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,gFAAgF;QAChF,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IAEF;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
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": "2.
|
|
3
|
+
"version": "2.1.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",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsc -p tsconfig.json && cp src/styles.css dist/styles.css",
|
|
53
53
|
"dev": "tsc -p tsconfig.json --watch",
|
|
54
|
-
"test": "node test-meta-parser.mjs && node test-dom-structure.mjs && node test-options.mjs && node test-notations.mjs && node test-security.mjs && node test-integration.mjs && node test-regression.mjs && node test-css.mjs && node test-edge-cases.mjs && node stress-tests.mjs && node new-feature-tests.mjs && node test-issue-12.mjs && node test-issue-11.mjs && node test-architecture-patterns.mjs && node test-copy-button-fix.mjs && node test-tailwind-compat.mjs && node test-v2-css-architecture.mjs",
|
|
54
|
+
"test": "node test-meta-parser.mjs && node test-dom-structure.mjs && node test-options.mjs && node test-notations.mjs && node test-security.mjs && node test-integration.mjs && node test-regression.mjs && node test-css.mjs && node test-edge-cases.mjs && node stress-tests.mjs && node new-feature-tests.mjs && node test-issue-12.mjs && node test-issue-11.mjs && node test-architecture-patterns.mjs && node test-copy-button-fix.mjs && node test-tailwind-compat.mjs && node test-v2-css-architecture.mjs && node test-v2-phase2.mjs",
|
|
55
55
|
"prepublishOnly": "npm run build && npm test",
|
|
56
56
|
"prepack": "npm run build"
|
|
57
57
|
},
|
package/src/copy-script.ts
CHANGED
|
@@ -103,6 +103,9 @@ export const COPY_SCRIPT = `
|
|
|
103
103
|
var finish = function () {
|
|
104
104
|
btn.classList.add('pcb__copy--done');
|
|
105
105
|
if (label) label.textContent = done;
|
|
106
|
+
// v2.1.0: Update aria-label for screen readers — announce "copied" state
|
|
107
|
+
var originalAriaLabel = btn.getAttribute('aria-label') || 'Copy code';
|
|
108
|
+
btn.setAttribute('aria-label', done + ' — ' + originalAriaLabel);
|
|
106
109
|
if (successIconHtml && icon) {
|
|
107
110
|
var tmp = document.createElement('span');
|
|
108
111
|
tmp.innerHTML = successIconHtml;
|
|
@@ -116,6 +119,8 @@ export const COPY_SCRIPT = `
|
|
|
116
119
|
setTimeout(function () {
|
|
117
120
|
btn.classList.remove('pcb__copy--done');
|
|
118
121
|
if (label && originalLabel != null) label.textContent = originalLabel;
|
|
122
|
+
// v2.1.0: Restore original aria-label after feedback duration
|
|
123
|
+
btn.setAttribute('aria-label', originalAriaLabel);
|
|
119
124
|
if (originalIconHtml && icon) {
|
|
120
125
|
var tmp2 = document.createElement('span');
|
|
121
126
|
tmp2.innerHTML = originalIconHtml;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development warnings (v2.1.0).
|
|
3
|
+
*
|
|
4
|
+
* Emits warnings to the logger during build/dev for common misconfigurations:
|
|
5
|
+
* - Unknown language not loaded in Shiki
|
|
6
|
+
* - Invalid meta syntax (e.g., `{1,a-5}` instead of `{1,3-5}`)
|
|
7
|
+
* - Conflicting options (e.g., `wrap` + `collapseAfter` both enabled)
|
|
8
|
+
* - Code block inside raw HTML detected but rehype-raw not installed
|
|
9
|
+
*
|
|
10
|
+
* Warnings are emitted once per unique message (deduped) to avoid spam.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Element, Root } from 'hast';
|
|
14
|
+
import { visit } from 'unist-util-visit';
|
|
15
|
+
|
|
16
|
+
export interface DevWarningContext {
|
|
17
|
+
logger: { warn: (msg: string) => void; error: (msg: string) => void };
|
|
18
|
+
hasRehypeRaw: boolean;
|
|
19
|
+
wrap: boolean;
|
|
20
|
+
collapseAfter: number | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const warnedMessages = new Set<string>();
|
|
24
|
+
|
|
25
|
+
function warnOnce(ctx: DevWarningContext, msg: string): void {
|
|
26
|
+
if (warnedMessages.has(msg)) return;
|
|
27
|
+
warnedMessages.add(msg);
|
|
28
|
+
ctx.logger.warn(msg);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check for common misconfigurations and emit dev warnings.
|
|
33
|
+
* Call this once per document after the plugin has processed the tree.
|
|
34
|
+
*/
|
|
35
|
+
export function runDevWarnings(tree: Root, ctx: DevWarningContext): void {
|
|
36
|
+
// 1. Check for conflicting options
|
|
37
|
+
if (ctx.wrap && ctx.collapseAfter !== null) {
|
|
38
|
+
warnOnce(
|
|
39
|
+
ctx,
|
|
40
|
+
'[rehype-perfect-code-blocks] Both `wrap` and `collapseAfter` are enabled. ' +
|
|
41
|
+
'Collapsed blocks may not wrap correctly. Consider disabling one.'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2. Check for code blocks inside raw HTML without rehype-raw
|
|
46
|
+
if (!ctx.hasRehypeRaw) {
|
|
47
|
+
let foundRawHtmlAroundCode = false;
|
|
48
|
+
visit(tree, 'element', (node: Element) => {
|
|
49
|
+
if (foundRawHtmlAroundCode) return;
|
|
50
|
+
// Look for <pre> elements that are children of raw HTML elements
|
|
51
|
+
// like <details>, <div> with class containing "card", etc.
|
|
52
|
+
// This is a heuristic — we can't perfectly detect raw HTML vs markdown HTML.
|
|
53
|
+
if (
|
|
54
|
+
node.tagName === 'details' ||
|
|
55
|
+
(node.tagName === 'div' && node.properties?.className &&
|
|
56
|
+
Array.isArray(node.properties.className) &&
|
|
57
|
+
node.properties.className.some((c: unknown) =>
|
|
58
|
+
typeof c === 'string' && (c.includes('card') || c.includes('container'))
|
|
59
|
+
))
|
|
60
|
+
) {
|
|
61
|
+
const hasPre = node.children?.some(
|
|
62
|
+
(c) => c.type === 'element' && c.tagName === 'pre'
|
|
63
|
+
);
|
|
64
|
+
if (hasPre) foundRawHtmlAroundCode = true;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (foundRawHtmlAroundCode) {
|
|
68
|
+
warnOnce(
|
|
69
|
+
ctx,
|
|
70
|
+
'[rehype-perfect-code-blocks] Code block inside raw HTML detected but rehype-raw ' +
|
|
71
|
+
'does not appear to be installed. Code blocks inside <details>, <div class="card">, ' +
|
|
72
|
+
'etc. may not render correctly. Add rehype-raw to your pipeline: ' +
|
|
73
|
+
'npm install rehype-raw'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 3. Check for unknown/invalid meta syntax
|
|
79
|
+
visit(tree, 'element', (node: Element) => {
|
|
80
|
+
if (node.tagName !== 'pre') return;
|
|
81
|
+
const codeEl = node.children?.find(
|
|
82
|
+
(c): c is Element => c.type === 'element' && c.tagName === 'code'
|
|
83
|
+
);
|
|
84
|
+
if (!codeEl) return;
|
|
85
|
+
const meta = (codeEl.properties?.dataMeta as string | undefined) ??
|
|
86
|
+
(node.properties?.dataMeta as string | undefined) ?? '';
|
|
87
|
+
if (!meta) return;
|
|
88
|
+
|
|
89
|
+
// Check for invalid range syntax like {1,a-5} or {1-}
|
|
90
|
+
const rangeMatch = meta.match(/\{([^}]*)\}/g);
|
|
91
|
+
if (rangeMatch) {
|
|
92
|
+
for (const range of rangeMatch) {
|
|
93
|
+
const inside = range.slice(1, -1);
|
|
94
|
+
// Valid: digits, commas, hyphens, spaces, #id, /word/
|
|
95
|
+
// Invalid: letters (except in /word/ or "phrase"), other punctuation
|
|
96
|
+
if (!/^[\d\s,/-]+$/.test(inside) && !inside.includes('"') && !inside.includes('/')) {
|
|
97
|
+
warnOnce(
|
|
98
|
+
ctx,
|
|
99
|
+
`[rehype-perfect-code-blocks] Invalid meta syntax: "${range}" in "${meta}". ` +
|
|
100
|
+
'Expected format like {1,3-5} for line highlighting.'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Warn about an unknown language that Shiki couldn't load.
|
|
110
|
+
* Called from shiki.ts when a language fails to tokenize.
|
|
111
|
+
*/
|
|
112
|
+
export function warnUnknownLanguage(lang: string, ctx: DevWarningContext): void {
|
|
113
|
+
warnOnce(
|
|
114
|
+
ctx,
|
|
115
|
+
`[rehype-perfect-code-blocks] Unknown language "${lang}" — not loaded in Shiki. ` +
|
|
116
|
+
'Falling back to plaintext. Add it to `shiki.langs` or install the grammar.'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Reset the warning dedup set (for testing).
|
|
122
|
+
*/
|
|
123
|
+
export function resetWarningDedup(): void {
|
|
124
|
+
warnedMessages.clear();
|
|
125
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -26,13 +26,18 @@ import { wordDiff, hasChanges } from './word-diff.js';
|
|
|
26
26
|
import type { DiffToken } from './word-diff.js';
|
|
27
27
|
import { generateTokenStyles, applyScopeToCss, generateDarkModeSelector, generateLightModeSelector } from './tokens.js';
|
|
28
28
|
import type { DesignTokens } from './tokens.js';
|
|
29
|
+
import { resolveMathOptions, isMathLanguage, renderMath } from './math.js';
|
|
30
|
+
import type { MathOptions, ResolvedMathOptions } from './math.js';
|
|
31
|
+
import { runDevWarnings, warnUnknownLanguage } from './dev-warnings.js';
|
|
29
32
|
import type { PerfectCodeOptions } from './types.js';
|
|
30
33
|
|
|
31
34
|
export { remarkPreserveCodeMeta };
|
|
32
35
|
export { disposeHighlighter, runHighlighterTask };
|
|
33
36
|
export { wordDiff, hasChanges };
|
|
34
37
|
export { generateTokenStyles, applyScopeToCss, generateDarkModeSelector, generateLightModeSelector };
|
|
35
|
-
export
|
|
38
|
+
export { resolveMathOptions, isMathLanguage, renderMath };
|
|
39
|
+
export { runDevWarnings, warnUnknownLanguage };
|
|
40
|
+
export type { DiffToken, DesignTokens, MathOptions, ResolvedMathOptions };
|
|
36
41
|
|
|
37
42
|
export const rehypePerfectCodeBlocks: Plugin<[PerfectCodeOptions?], Root> =
|
|
38
43
|
(options = {}) => {
|
|
@@ -166,6 +171,9 @@ function resolveDefaults(opts: PerfectCodeOptions): Required<PerfectCodeOptions>
|
|
|
166
171
|
tokens: opts.tokens ?? (undefined as unknown as NonNullable<typeof opts.tokens>),
|
|
167
172
|
darkMode: opts.darkMode ?? (undefined as unknown as NonNullable<typeof opts.darkMode>),
|
|
168
173
|
scope: opts.scope ?? (undefined as unknown as string),
|
|
174
|
+
// v2.1.0: P1 features
|
|
175
|
+
math: opts.math ?? (undefined as unknown as NonNullable<typeof opts.math>),
|
|
176
|
+
devWarnings: opts.devWarnings ?? (process.env.NODE_ENV !== 'production'),
|
|
169
177
|
inline: opts.inline ?? false,
|
|
170
178
|
};
|
|
171
179
|
}
|
package/src/katex.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal type declaration for katex (optional peer dependency).
|
|
3
|
+
* When katex is installed, its own types take precedence.
|
|
4
|
+
*/
|
|
5
|
+
declare module 'katex' {
|
|
6
|
+
export interface KatexOptions {
|
|
7
|
+
displayMode?: boolean;
|
|
8
|
+
throwOnError?: boolean;
|
|
9
|
+
strict?: boolean | 'ignore' | 'error' | 'warn';
|
|
10
|
+
output?: 'html' | 'mathml' | 'htmlAndMathml';
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export function renderToString(latex: string, options?: KatexOptions): string;
|
|
14
|
+
const _default: { renderToString: typeof renderToString };
|
|
15
|
+
export default _default;
|
|
16
|
+
}
|
package/src/math.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Math/LaTeX rendering via KaTeX (v2.1.0).
|
|
3
|
+
*
|
|
4
|
+
* Renders LaTeX at build time (server-side) — no client-side JS needed.
|
|
5
|
+
* `katex` is an optional peer dependency: if not installed, the plugin
|
|
6
|
+
* falls back to rendering the LaTeX source as plain text in a styled
|
|
7
|
+
* container.
|
|
8
|
+
*
|
|
9
|
+
* Supports:
|
|
10
|
+
* - Inline math: `$...$` in text nodes (via a remark plugin)
|
|
11
|
+
* - Block math: `$$...$$` blocks
|
|
12
|
+
* - Fenced code blocks with language `math`, `latex`, or `tex`
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface MathOptions {
|
|
16
|
+
engine?: 'katex' | 'none';
|
|
17
|
+
inline?: boolean;
|
|
18
|
+
block?: boolean;
|
|
19
|
+
injectCss?: boolean;
|
|
20
|
+
throwOnError?: boolean;
|
|
21
|
+
strict?: boolean | 'ignore' | 'error' | 'warn';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ResolvedMathOptions {
|
|
25
|
+
engine: 'katex' | 'none';
|
|
26
|
+
inline: boolean;
|
|
27
|
+
block: boolean;
|
|
28
|
+
injectCss: boolean;
|
|
29
|
+
throwOnError: boolean;
|
|
30
|
+
strict: boolean | 'ignore' | 'error' | 'warn';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveMathOptions(math?: MathOptions): ResolvedMathOptions {
|
|
34
|
+
return {
|
|
35
|
+
engine: math?.engine ?? 'none',
|
|
36
|
+
inline: math?.inline ?? true,
|
|
37
|
+
block: math?.block ?? true,
|
|
38
|
+
injectCss: math?.injectCss ?? true,
|
|
39
|
+
throwOnError: math?.throwOnError ?? true,
|
|
40
|
+
strict: math?.strict ?? false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Languages that should be rendered as math instead of syntax-highlighted. */
|
|
45
|
+
export const MATH_LANGS = new Set(['math', 'latex', 'tex']);
|
|
46
|
+
|
|
47
|
+
/** Cache for the dynamically-imported katex module. */
|
|
48
|
+
let _katexModule: typeof import('katex') | null | undefined;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Try to load the katex module. Returns null if katex is not installed.
|
|
52
|
+
* Caches the result so subsequent calls don't re-import.
|
|
53
|
+
*/
|
|
54
|
+
async function getKatex(): Promise<typeof import('katex') | null> {
|
|
55
|
+
if (_katexModule !== undefined) return _katexModule;
|
|
56
|
+
try {
|
|
57
|
+
_katexModule = await import('katex');
|
|
58
|
+
return _katexModule;
|
|
59
|
+
} catch {
|
|
60
|
+
_katexModule = null;
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if a language identifier should be treated as math.
|
|
67
|
+
*/
|
|
68
|
+
export function isMathLanguage(lang: string): boolean {
|
|
69
|
+
return MATH_LANGS.has(lang.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Render a LaTeX string to HTML via KaTeX.
|
|
74
|
+
*
|
|
75
|
+
* @param latex The LaTeX source string
|
|
76
|
+
* @param displayMode true for block math ($$...$$), false for inline ($...$)
|
|
77
|
+
* @param options Resolved math options
|
|
78
|
+
* @returns { html: string, isKatex: boolean } — if katex is available,
|
|
79
|
+
* html is the KaTeX-rendered HTML; otherwise it's the LaTeX source in a
|
|
80
|
+
* `<code>` element, and isKatex is false.
|
|
81
|
+
*/
|
|
82
|
+
export async function renderMath(
|
|
83
|
+
latex: string,
|
|
84
|
+
displayMode: boolean,
|
|
85
|
+
options: ResolvedMathOptions
|
|
86
|
+
): Promise<{ html: string; isKatex: boolean }> {
|
|
87
|
+
if (options.engine === 'none') {
|
|
88
|
+
return { html: escapeHtml(latex), isKatex: false };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const katex = await getKatex();
|
|
92
|
+
if (!katex) {
|
|
93
|
+
// KaTeX not installed — fall back to plain text
|
|
94
|
+
return { html: escapeHtml(latex), isKatex: false };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const html = katex.renderToString(latex, {
|
|
99
|
+
displayMode,
|
|
100
|
+
throwOnError: options.throwOnError,
|
|
101
|
+
strict: options.strict,
|
|
102
|
+
output: 'html',
|
|
103
|
+
});
|
|
104
|
+
return { html, isKatex: true };
|
|
105
|
+
} catch {
|
|
106
|
+
// KaTeX rendering failed — fall back to plain text
|
|
107
|
+
return { html: escapeHtml(latex), isKatex: false };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Escape HTML special characters in a string (for the fallback path).
|
|
113
|
+
*/
|
|
114
|
+
function escapeHtml(s: string): string {
|
|
115
|
+
return s
|
|
116
|
+
.replace(/&/g, '&')
|
|
117
|
+
.replace(/</g, '<')
|
|
118
|
+
.replace(/>/g, '>')
|
|
119
|
+
.replace(/"/g, '"');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Regex to find inline `$...$` math in text.
|
|
124
|
+
* Matches `$` followed by non-$ content followed by `$`.
|
|
125
|
+
* Does NOT match `$$` (which is block math) or escaped `\$`.
|
|
126
|
+
*
|
|
127
|
+
* Examples:
|
|
128
|
+
* "$x^2$" → match (inline math)
|
|
129
|
+
* "$$x^2$$" → NO match (block math)
|
|
130
|
+
* "cost is \$5" → NO match (escaped dollar sign)
|
|
131
|
+
* "a $ b $ c" → match "$ b $" (ambiguous, but we match it)
|
|
132
|
+
*/
|
|
133
|
+
export const INLINE_MATH_REGEX = /(^|[^\\$])\$(?!\$)([^$]+?)\$(?!\$)/g;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Regex to find block `$$...$$` math in text.
|
|
137
|
+
* Matches `$$` followed by content followed by `$$`.
|
|
138
|
+
*/
|
|
139
|
+
export const BLOCK_MATH_REGEX = /\$\$([\s\S]+?)\$\$/g;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Process a text string, replacing inline `$...$` and block `$$...$$`
|
|
143
|
+
* with rendered math HTML.
|
|
144
|
+
*
|
|
145
|
+
* @param text The input text
|
|
146
|
+
* @param options Resolved math options
|
|
147
|
+
* @returns Array of { type: 'text' | 'inline-math' | 'block-math', content: string }
|
|
148
|
+
* segments. The caller is responsible for converting these to HAST nodes.
|
|
149
|
+
*/
|
|
150
|
+
export async function processMathInText(
|
|
151
|
+
text: string,
|
|
152
|
+
options: ResolvedMathOptions
|
|
153
|
+
): Promise<Array<{ type: 'text' | 'inline-math' | 'block-math'; content: string; html?: string }>> {
|
|
154
|
+
if (options.engine === 'none' || (!options.inline && !options.block)) {
|
|
155
|
+
return [{ type: 'text', content: text }];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const segments: Array<{ type: 'text' | 'inline-math' | 'block-math'; content: string; html?: string }> = [];
|
|
159
|
+
let remaining = text;
|
|
160
|
+
|
|
161
|
+
// First, extract block math ($$...$$)
|
|
162
|
+
if (options.block) {
|
|
163
|
+
let lastIndex = 0;
|
|
164
|
+
let match: RegExpExecArray | null;
|
|
165
|
+
const blockRegex = new RegExp(BLOCK_MATH_REGEX);
|
|
166
|
+
while ((match = blockRegex.exec(remaining)) !== null) {
|
|
167
|
+
// Text before the block math
|
|
168
|
+
if (match.index > lastIndex) {
|
|
169
|
+
const beforeText = remaining.slice(lastIndex, match.index);
|
|
170
|
+
// Process inline math in the text before
|
|
171
|
+
if (options.inline) {
|
|
172
|
+
const inlineSegments = await processInlineMath(beforeText, options);
|
|
173
|
+
segments.push(...inlineSegments);
|
|
174
|
+
} else {
|
|
175
|
+
segments.push({ type: 'text', content: beforeText });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// The block math
|
|
179
|
+
const latex = match[1].trim();
|
|
180
|
+
const { html, isKatex } = await renderMath(latex, true, options);
|
|
181
|
+
segments.push({ type: 'block-math', content: latex, html });
|
|
182
|
+
lastIndex = match.index + match[0].length;
|
|
183
|
+
}
|
|
184
|
+
// Remaining text after the last block math
|
|
185
|
+
if (lastIndex < remaining.length) {
|
|
186
|
+
const afterText = remaining.slice(lastIndex);
|
|
187
|
+
if (options.inline) {
|
|
188
|
+
const inlineSegments = await processInlineMath(afterText, options);
|
|
189
|
+
segments.push(...inlineSegments);
|
|
190
|
+
} else {
|
|
191
|
+
segments.push({ type: 'text', content: afterText });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} else if (options.inline) {
|
|
195
|
+
// Only inline math
|
|
196
|
+
const inlineSegments = await processInlineMath(remaining, options);
|
|
197
|
+
segments.push(...inlineSegments);
|
|
198
|
+
} else {
|
|
199
|
+
segments.push({ type: 'text', content: remaining });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return segments;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Process inline `$...$` math in a text string.
|
|
207
|
+
* Returns segments of text and inline-math.
|
|
208
|
+
*/
|
|
209
|
+
async function processInlineMath(
|
|
210
|
+
text: string,
|
|
211
|
+
options: ResolvedMathOptions
|
|
212
|
+
): Promise<Array<{ type: 'text' | 'inline-math'; content: string; html?: string }>> {
|
|
213
|
+
const segments: Array<{ type: 'text' | 'inline-math'; content: string; html?: string }> = [];
|
|
214
|
+
const regex = new RegExp(INLINE_MATH_REGEX);
|
|
215
|
+
let lastIndex = 0;
|
|
216
|
+
let match: RegExpExecArray | null;
|
|
217
|
+
|
|
218
|
+
while ((match = regex.exec(text)) !== null) {
|
|
219
|
+
// Text before the match (including the prefix character)
|
|
220
|
+
const prefix = match[1] || '';
|
|
221
|
+
const matchStart = match.index + prefix.length;
|
|
222
|
+
if (matchStart > lastIndex) {
|
|
223
|
+
segments.push({ type: 'text', content: text.slice(lastIndex, match.index) + prefix });
|
|
224
|
+
} else if (prefix) {
|
|
225
|
+
segments.push({ type: 'text', content: prefix });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const latex = match[2].trim();
|
|
229
|
+
const { html } = await renderMath(latex, false, options);
|
|
230
|
+
segments.push({ type: 'inline-math', content: latex, html });
|
|
231
|
+
lastIndex = regex.lastIndex;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (lastIndex < text.length) {
|
|
235
|
+
segments.push({ type: 'text', content: text.slice(lastIndex) });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return segments.length > 0 ? segments : [{ type: 'text', content: text }];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get the KaTeX CSS path for injection.
|
|
243
|
+
* Returns the path to `katex/dist/katex.min.css` relative to the project.
|
|
244
|
+
*/
|
|
245
|
+
export function getKatexCssPath(): string {
|
|
246
|
+
return 'katex/dist/katex.min.css';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Try to read the KaTeX CSS content from the filesystem.
|
|
251
|
+
* Returns null if katex is not installed or the CSS file can't be read.
|
|
252
|
+
*/
|
|
253
|
+
export async function tryReadKatexCss(): Promise<string | null> {
|
|
254
|
+
try {
|
|
255
|
+
const katex = await getKatex();
|
|
256
|
+
if (!katex) return null;
|
|
257
|
+
// katex module path — try to read the CSS
|
|
258
|
+
const { readFileSync } = await import('node:fs');
|
|
259
|
+
const { createRequire } = await import('node:module');
|
|
260
|
+
const require = createRequire(import.meta.url);
|
|
261
|
+
const katexPath = require.resolve('katex');
|
|
262
|
+
const katexDir = katexPath.replace(/[/\\]katex\.(mjs|js|cjs)$/, '');
|
|
263
|
+
const cssPath = katexDir + '/katex.min.css';
|
|
264
|
+
return readFileSync(cssPath, 'utf8');
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
package/src/shiki.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { fromHtml } from 'hast-util-from-html';
|
|
|
18
18
|
import { visit } from 'unist-util-visit';
|
|
19
19
|
import type { PerfectCodeOptions } from './types.js';
|
|
20
20
|
import { computeThemeAwareDefaults } from './color-utils.js';
|
|
21
|
+
import { isMathLanguage, renderMath, resolveMathOptions, MATH_LANGS } from './math.js';
|
|
21
22
|
import {
|
|
22
23
|
transformerNotationDiff,
|
|
23
24
|
transformerNotationFocus,
|
|
@@ -447,6 +448,50 @@ export async function runShikiOnRawBlocks(
|
|
|
447
448
|
|
|
448
449
|
if (targets.length === 0) return;
|
|
449
450
|
|
|
451
|
+
// v2.1.0: Handle math language blocks — render via KaTeX instead of Shiki.
|
|
452
|
+
const mathOpts = resolveMathOptions(opts.math as Record<string, unknown> | undefined);
|
|
453
|
+
if (mathOpts.engine === 'katex' && mathOpts.block) {
|
|
454
|
+
const mathTargets: Element[] = [];
|
|
455
|
+
const codeTargets: Element[] = [];
|
|
456
|
+
for (const pre of targets) {
|
|
457
|
+
const code = pre.children.find(
|
|
458
|
+
(c): c is Element => c.type === 'element' && c.tagName === 'code'
|
|
459
|
+
);
|
|
460
|
+
if (!code) { codeTargets.push(pre); continue; }
|
|
461
|
+
const cls = (code.properties?.className as string[] | undefined) ?? [];
|
|
462
|
+
const langClass = cls.find((c) => c.startsWith('language-'));
|
|
463
|
+
const lang = langClass ? langClass.replace('language-', '') : '';
|
|
464
|
+
if (isMathLanguage(lang)) {
|
|
465
|
+
mathTargets.push(pre);
|
|
466
|
+
} else {
|
|
467
|
+
codeTargets.push(pre);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Render math blocks via KaTeX
|
|
472
|
+
for (const pre of mathTargets) {
|
|
473
|
+
const code = pre.children.find(
|
|
474
|
+
(c): c is Element => c.type === 'element' && c.tagName === 'code'
|
|
475
|
+
);
|
|
476
|
+
if (!code) continue;
|
|
477
|
+
const text = extractText(code).replace(/\r\n?/g, '\n').trim();
|
|
478
|
+
const { html, isKatex } = await renderMath(text, true, mathOpts);
|
|
479
|
+
// Replace the <pre> with rendered math
|
|
480
|
+
const mathDiv: Element = {
|
|
481
|
+
type: 'element',
|
|
482
|
+
tagName: 'div',
|
|
483
|
+
properties: { className: ['pcb__math', isKatex ? 'pcb__math--katex' : 'pcb__math--fallback'] },
|
|
484
|
+
children: [{ type: 'text', value: html }],
|
|
485
|
+
};
|
|
486
|
+
Object.assign(pre, mathDiv);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Only process non-math targets with Shiki
|
|
490
|
+
targets.splice(0, targets.length, ...codeTargets);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (targets.length === 0) return;
|
|
494
|
+
|
|
450
495
|
// Build theme keys — supports single (string), dual ({light,dark}), and
|
|
451
496
|
// multi-theme (Record<string,string> with 3+ entries) for advanced use cases.
|
|
452
497
|
const themeSpec = opts.shiki.theme;
|
|
@@ -473,8 +518,16 @@ export async function runShikiOnRawBlocks(
|
|
|
473
518
|
// (javascript, typescript, python). This matches Shiki's own case-
|
|
474
519
|
// insensitive behavior in codeToHast/codeToHtml, and matches what every
|
|
475
520
|
// other CommonMark renderer accepts. See issue #12.
|
|
521
|
+
//
|
|
522
|
+
// v2.1.0: When shiki.lazy is true, don't preload the user's `langs` list —
|
|
523
|
+
// only load languages that are actually in this document. This avoids
|
|
524
|
+
// loading grammars for pages that only use 1-2 languages out of a
|
|
525
|
+
// configured set of 20+. The lazy-load path below will load them on demand.
|
|
526
|
+
const isLazy = (opts.shiki as { lazy?: boolean }).lazy === true;
|
|
476
527
|
const langSet = new Set<string>(
|
|
477
|
-
|
|
528
|
+
isLazy
|
|
529
|
+
? [] // Lazy: don't preload anything — document-specific langs added below
|
|
530
|
+
: (opts.shiki.langs ?? []).map((l) => l.toLowerCase())
|
|
478
531
|
);
|
|
479
532
|
for (const pre of targets) {
|
|
480
533
|
const code = pre.children.find(
|
package/src/transformer.ts
CHANGED
|
@@ -247,6 +247,8 @@ export function rehypePerfectCodeBlocks(userOptions: PerfectCodeOptions = {}) {
|
|
|
247
247
|
tokens: undefined as unknown as NonNullable<PerfectCodeOptions['tokens']>,
|
|
248
248
|
darkMode: undefined as unknown as NonNullable<PerfectCodeOptions['darkMode']>,
|
|
249
249
|
scope: undefined as unknown as string,
|
|
250
|
+
math: undefined as unknown as NonNullable<PerfectCodeOptions['math']>,
|
|
251
|
+
devWarnings: process.env.NODE_ENV !== 'production',
|
|
250
252
|
inline: false,
|
|
251
253
|
...rest,
|
|
252
254
|
};
|