@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/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,92 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.1.0] — 2026-06-20
|
|
9
|
+
|
|
10
|
+
### Summary
|
|
11
|
+
|
|
12
|
+
Minor release implementing the four P1 items from the v2.0.0 roadmap: Math/LaTeX rendering (KaTeX integration), Lazy Shiki initialization, Dev-mode warnings, and Screen reader copy announcement. No breaking changes — all new features are opt-in with backward-compatible defaults.
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
#### P1-1: Math/LaTeX rendering (KaTeX integration)
|
|
17
|
+
|
|
18
|
+
New `math` option — renders LaTeX at build time via KaTeX (server-side, no client JS needed):
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
perfectCode({
|
|
22
|
+
math: {
|
|
23
|
+
engine: 'katex', // 'katex' | 'none' (default: 'none')
|
|
24
|
+
inline: true, // render $...$ inline
|
|
25
|
+
block: true, // render $$...$$ and ```math blocks
|
|
26
|
+
injectCss: true, // inject KaTeX CSS
|
|
27
|
+
throwOnError: true, // graceful fallback for invalid LaTeX
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- `katex` is an optional peer dependency — if not installed, falls back to rendering the LaTeX source as plain text
|
|
33
|
+
- Handles fenced code blocks with language `math`, `latex`, or `tex` → renders via KaTeX instead of Shiki
|
|
34
|
+
- New `src/math.ts` module with `isMathLanguage()`, `renderMath()`, `resolveMathOptions()` utilities
|
|
35
|
+
- New `src/katex.d.ts` minimal type declaration for the optional `katex` module
|
|
36
|
+
|
|
37
|
+
#### P1-2: Lazy Shiki initialization
|
|
38
|
+
|
|
39
|
+
New `shiki.lazy` option — don't load Shiki languages until the first code block is encountered:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
perfectCode({
|
|
43
|
+
shiki: {
|
|
44
|
+
lazy: true, // skip preloading langs; only load what's in each document
|
|
45
|
+
preloadLangs: ['typescript', 'bash'], // langs to preload when code blocks exist
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
When `lazy: true`, the plugin skips preloading `shiki.langs` and only loads languages actually found in the document. On pages with no code blocks, Shiki is never initialized. Saves ~1MB of bundle on code-free pages.
|
|
51
|
+
|
|
52
|
+
#### P1-3: Dev-mode warnings
|
|
53
|
+
|
|
54
|
+
New `devWarnings` option — emits warnings during build/dev for common misconfigurations:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
perfectCode({
|
|
58
|
+
devWarnings: true, // default: true in dev (NODE_ENV !== 'production'), false in prod
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Warnings include:
|
|
63
|
+
- Unknown language not loaded in Shiki
|
|
64
|
+
- Invalid meta syntax (e.g., `{1,a-5}` instead of `{1,3-5}`)
|
|
65
|
+
- Conflicting options (e.g., `wrap` + `collapseAfter` both enabled)
|
|
66
|
+
- Code block inside raw HTML detected but rehype-raw not installed
|
|
67
|
+
|
|
68
|
+
Warnings are deduped per unique message to avoid spam. New `src/dev-warnings.ts` module with `runDevWarnings()` and `warnUnknownLanguage()`.
|
|
69
|
+
|
|
70
|
+
#### P1-4: Screen reader copy announcement
|
|
71
|
+
|
|
72
|
+
The copy button now dynamically updates its `aria-label` when copy succeeds:
|
|
73
|
+
- Before copy: `aria-label="Copy code"`
|
|
74
|
+
- After copy: `aria-label="copied! — Copy code"` (announced via screen reader)
|
|
75
|
+
- After feedback duration: restores original `aria-label`
|
|
76
|
+
|
|
77
|
+
Combined with the existing `aria-live="polite"` region (from v1.3.0), screen readers now announce both the button state change AND the "Copied!" text — WCAG 4.1.3 compliant.
|
|
78
|
+
|
|
79
|
+
### New exports
|
|
80
|
+
|
|
81
|
+
- `resolveMathOptions(options)` — resolves math config with defaults
|
|
82
|
+
- `isMathLanguage(lang)` — checks if a language is a math language
|
|
83
|
+
- `renderMath(latex, displayMode, options)` — renders LaTeX via KaTeX (or fallback)
|
|
84
|
+
- `runDevWarnings(tree, context)` — runs dev warning checks on a hast tree
|
|
85
|
+
- `warnUnknownLanguage(lang, context)` — warns about an unknown language
|
|
86
|
+
- `MathOptions`, `ResolvedMathOptions` types
|
|
87
|
+
|
|
88
|
+
### Verification
|
|
89
|
+
|
|
90
|
+
- All 1177 pre-existing tests pass (no regressions).
|
|
91
|
+
- New `test-v2-phase2.mjs` adds 42 regression tests covering all 4 P1 features.
|
|
92
|
+
- Total: 1219/1219 tests passing.
|
|
93
|
+
|
|
8
94
|
## [2.0.0] — 2026-06-20
|
|
9
95
|
|
|
10
96
|
### Summary
|
package/dist/copy-script.d.ts
CHANGED
|
@@ -21,5 +21,5 @@
|
|
|
21
21
|
* class swap when new code blocks are added to the DOM (SPA support).
|
|
22
22
|
* - `astro:page-load` event listener for Astro view transitions.
|
|
23
23
|
*/
|
|
24
|
-
export declare const COPY_SCRIPT = "\n(function () {\n if (window.__pcbCopyReady) return;\n window.__pcbCopyReady = true;\n\n // Remove the .no-js class so the copy buttons become visible (graceful degradation).\n function swapNoJs() {\n if (document.documentElement.classList.contains('no-js')) {\n document.documentElement.classList.remove('no-js');\n document.documentElement.classList.add('js');\n }\n }\n swapNoJs();\n\n // Reuse a single aria-live region for all copy announcements.\n var liveRegion = null;\n function ensureLiveRegion() {\n if (liveRegion && document.body.contains(liveRegion)) return liveRegion;\n liveRegion = document.querySelector('.pcb__sr-live');\n if (!liveRegion) {\n liveRegion = document.createElement('span');\n liveRegion.className = 'pcb__sr-live';\n liveRegion.setAttribute('aria-live', 'polite');\n liveRegion.setAttribute('aria-atomic', 'true');\n liveRegion.setAttribute('role', 'status');\n document.body.appendChild(liveRegion);\n }\n return liveRegion;\n }\n ensureLiveRegion();\n\n function announce(msg) {\n var lr = ensureLiveRegion();\n if (!lr) return;\n lr.textContent = '';\n // Force re-announcement by clearing then setting on next tick.\n setTimeout(function () { lr.textContent = msg; }, 50);\n }\n\n function findLabel(btn) {\n return btn.querySelector('.pcb__copy-label');\n }\n\n function findIcon(btn) {\n return btn.querySelector('svg');\n }\n\n // Strip leading comment lines (e.g. shell prompts like \"# comment\") from\n // the text before copying. Used for terminal-preset blocks where the\n // displayed code may include comments the user doesn't want on the clipboard.\n function stripComments(text) {\n // Strip lines that start with optional whitespace followed by # (shell),\n // // (C-style), or REM (Windows batch). Keep everything else.\n return text.replace(/^[ \\t]*(?:#|\\/\\/|REM\\b).*$/gm, '').replace(/\\n{3,}/g, '\\n\\n').trim();\n }\n\n // Event-delegated click handler. Works for buttons added after initial\n // render (e.g. via React/Vue re-render or Astro view transitions) because\n // the listener is on document, not on each button.\n document.addEventListener('click', function (e) {\n var btn = e.target && e.target.closest && e.target.closest('.pcb__copy');\n if (!btn) return;\n var figure = btn.closest('.pcb');\n var code = figure && figure.querySelector('pre code');\n if (!code) return;\n\n var done = btn.getAttribute('data-done-label') || 'copied!';\n var duration = parseInt(btn.getAttribute('data-feedback-duration') || '1600', 10);\n var successIconHtml = btn.getAttribute('data-success-icon');\n var stripCommentsFlag = btn.hasAttribute('data-strip-comments');\n\n var label = findLabel(btn);\n var icon = findIcon(btn);\n var originalLabel = label ? label.textContent : null;\n var originalIconHtml = icon ? icon.outerHTML : null;\n\n var rawText = code.innerText;\n var textToCopy = stripCommentsFlag ? stripComments(rawText) : rawText;\n\n var finish = function () {\n btn.classList.add('pcb__copy--done');\n if (label) label.textContent = done;\n if (successIconHtml && icon) {\n var tmp = document.createElement('span');\n tmp.innerHTML = successIconHtml;\n var newIcon = tmp.firstChild;\n if (newIcon) {\n icon.replaceWith(newIcon);\n icon = newIcon;\n }\n }\n announce(done);\n setTimeout(function () {\n btn.classList.remove('pcb__copy--done');\n if (label && originalLabel != null) label.textContent = originalLabel;\n if (originalIconHtml && icon) {\n var tmp2 = document.createElement('span');\n tmp2.innerHTML = originalIconHtml;\n var oldIcon = icon;\n icon = tmp2.firstChild;\n if (icon) oldIcon.replaceWith(icon);\n }\n }, duration);\n };\n\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(textToCopy).then(finish).catch(fallback);\n } else {\n fallback();\n }\n\n function fallback() {\n var ta = document.createElement('textarea');\n ta.value = textToCopy;\n ta.style.position = 'fixed';\n ta.style.opacity = '0';\n document.body.appendChild(ta);\n ta.select();\n try { document.execCommand('copy'); finish(); } catch (_) {}\n document.body.removeChild(ta);\n }\n });\n\n // Pattern 4: MutationObserver for SPA support + .no-js race fix.\n // Watches for:\n // - New code blocks added to the DOM (React/Vue re-render, Astro view\n // transitions, Turbolinks) \u2192 re-apply .no-js \u2192 .js swap + ensure\n // aria-live region.\n // - Attribute changes on <html> (specifically the class attribute) \u2192\n // catches the case where a later script adds .no-js AFTER this script\n // ran (a race condition that previously left copy buttons hidden).\n if (typeof MutationObserver !== 'undefined') {\n var pendingObserve = false;\n var observer = new MutationObserver(function (mutations) {\n // Batch checks with microtask to avoid layout thrash.\n if (pendingObserve) return;\n pendingObserve = true;\n Promise.resolve().then(function () {\n pendingObserve = false;\n var needsSwap = false;\n for (var i = 0; i < mutations.length; i++) {\n var m = mutations[i];\n // childList: new nodes added (SPA navigation, view transitions)\n if (m.type === 'childList' && m.addedNodes && m.addedNodes.length) {\n needsSwap = true;\n }\n // attributes: class changed on <html> (the .no-js race fix)\n if (m.type === 'attributes' && m.attributeName === 'class') {\n needsSwap = true;\n }\n }\n if (needsSwap) {\n swapNoJs();\n ensureLiveRegion();\n }\n });\n });\n // Observe documentElement for BOTH childList (new nodes) AND attribute\n // changes (class attribute \u2014 catches .no-js being added by a later script).\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['class'],\n });\n }\n\n // Defensive: re-run swapNoJs() on DOMContentLoaded and window.load.\n // Belt + suspenders for the .no-js race \u2014 if a framework (Astro, Next.js,\n // etc.) adds .no-js in a way the MutationObserver doesn't catch (e.g.,\n // before the observer is set up, or in a different document context),\n // these event handlers will catch it.\n if (typeof document.addEventListener === 'function') {\n document.addEventListener('DOMContentLoaded', function () {\n swapNoJs();\n ensureLiveRegion();\n });\n }\n if (typeof window.addEventListener === 'function') {\n window.addEventListener('load', function () {\n swapNoJs();\n ensureLiveRegion();\n });\n }\n\n // Pattern 4: astro:page-load event listener for Astro view transitions.\n // Astro emits this event after a view transition completes; the new page's\n // DOM may have replaced the old, so re-apply the .no-js \u2192 .js swap.\n if (typeof document.addEventListener === 'function') {\n document.addEventListener('astro:page-load', function () {\n swapNoJs();\n ensureLiveRegion();\n });\n }\n})();\n";
|
|
24
|
+
export declare const COPY_SCRIPT = "\n(function () {\n if (window.__pcbCopyReady) return;\n window.__pcbCopyReady = true;\n\n // Remove the .no-js class so the copy buttons become visible (graceful degradation).\n function swapNoJs() {\n if (document.documentElement.classList.contains('no-js')) {\n document.documentElement.classList.remove('no-js');\n document.documentElement.classList.add('js');\n }\n }\n swapNoJs();\n\n // Reuse a single aria-live region for all copy announcements.\n var liveRegion = null;\n function ensureLiveRegion() {\n if (liveRegion && document.body.contains(liveRegion)) return liveRegion;\n liveRegion = document.querySelector('.pcb__sr-live');\n if (!liveRegion) {\n liveRegion = document.createElement('span');\n liveRegion.className = 'pcb__sr-live';\n liveRegion.setAttribute('aria-live', 'polite');\n liveRegion.setAttribute('aria-atomic', 'true');\n liveRegion.setAttribute('role', 'status');\n document.body.appendChild(liveRegion);\n }\n return liveRegion;\n }\n ensureLiveRegion();\n\n function announce(msg) {\n var lr = ensureLiveRegion();\n if (!lr) return;\n lr.textContent = '';\n // Force re-announcement by clearing then setting on next tick.\n setTimeout(function () { lr.textContent = msg; }, 50);\n }\n\n function findLabel(btn) {\n return btn.querySelector('.pcb__copy-label');\n }\n\n function findIcon(btn) {\n return btn.querySelector('svg');\n }\n\n // Strip leading comment lines (e.g. shell prompts like \"# comment\") from\n // the text before copying. Used for terminal-preset blocks where the\n // displayed code may include comments the user doesn't want on the clipboard.\n function stripComments(text) {\n // Strip lines that start with optional whitespace followed by # (shell),\n // // (C-style), or REM (Windows batch). Keep everything else.\n return text.replace(/^[ \\t]*(?:#|\\/\\/|REM\\b).*$/gm, '').replace(/\\n{3,}/g, '\\n\\n').trim();\n }\n\n // Event-delegated click handler. Works for buttons added after initial\n // render (e.g. via React/Vue re-render or Astro view transitions) because\n // the listener is on document, not on each button.\n document.addEventListener('click', function (e) {\n var btn = e.target && e.target.closest && e.target.closest('.pcb__copy');\n if (!btn) return;\n var figure = btn.closest('.pcb');\n var code = figure && figure.querySelector('pre code');\n if (!code) return;\n\n var done = btn.getAttribute('data-done-label') || 'copied!';\n var duration = parseInt(btn.getAttribute('data-feedback-duration') || '1600', 10);\n var successIconHtml = btn.getAttribute('data-success-icon');\n var stripCommentsFlag = btn.hasAttribute('data-strip-comments');\n\n var label = findLabel(btn);\n var icon = findIcon(btn);\n var originalLabel = label ? label.textContent : null;\n var originalIconHtml = icon ? icon.outerHTML : null;\n\n var rawText = code.innerText;\n var textToCopy = stripCommentsFlag ? stripComments(rawText) : rawText;\n\n var finish = function () {\n btn.classList.add('pcb__copy--done');\n if (label) label.textContent = done;\n // v2.1.0: Update aria-label for screen readers \u2014 announce \"copied\" state\n var originalAriaLabel = btn.getAttribute('aria-label') || 'Copy code';\n btn.setAttribute('aria-label', done + ' \u2014 ' + originalAriaLabel);\n if (successIconHtml && icon) {\n var tmp = document.createElement('span');\n tmp.innerHTML = successIconHtml;\n var newIcon = tmp.firstChild;\n if (newIcon) {\n icon.replaceWith(newIcon);\n icon = newIcon;\n }\n }\n announce(done);\n setTimeout(function () {\n btn.classList.remove('pcb__copy--done');\n if (label && originalLabel != null) label.textContent = originalLabel;\n // v2.1.0: Restore original aria-label after feedback duration\n btn.setAttribute('aria-label', originalAriaLabel);\n if (originalIconHtml && icon) {\n var tmp2 = document.createElement('span');\n tmp2.innerHTML = originalIconHtml;\n var oldIcon = icon;\n icon = tmp2.firstChild;\n if (icon) oldIcon.replaceWith(icon);\n }\n }, duration);\n };\n\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(textToCopy).then(finish).catch(fallback);\n } else {\n fallback();\n }\n\n function fallback() {\n var ta = document.createElement('textarea');\n ta.value = textToCopy;\n ta.style.position = 'fixed';\n ta.style.opacity = '0';\n document.body.appendChild(ta);\n ta.select();\n try { document.execCommand('copy'); finish(); } catch (_) {}\n document.body.removeChild(ta);\n }\n });\n\n // Pattern 4: MutationObserver for SPA support + .no-js race fix.\n // Watches for:\n // - New code blocks added to the DOM (React/Vue re-render, Astro view\n // transitions, Turbolinks) \u2192 re-apply .no-js \u2192 .js swap + ensure\n // aria-live region.\n // - Attribute changes on <html> (specifically the class attribute) \u2192\n // catches the case where a later script adds .no-js AFTER this script\n // ran (a race condition that previously left copy buttons hidden).\n if (typeof MutationObserver !== 'undefined') {\n var pendingObserve = false;\n var observer = new MutationObserver(function (mutations) {\n // Batch checks with microtask to avoid layout thrash.\n if (pendingObserve) return;\n pendingObserve = true;\n Promise.resolve().then(function () {\n pendingObserve = false;\n var needsSwap = false;\n for (var i = 0; i < mutations.length; i++) {\n var m = mutations[i];\n // childList: new nodes added (SPA navigation, view transitions)\n if (m.type === 'childList' && m.addedNodes && m.addedNodes.length) {\n needsSwap = true;\n }\n // attributes: class changed on <html> (the .no-js race fix)\n if (m.type === 'attributes' && m.attributeName === 'class') {\n needsSwap = true;\n }\n }\n if (needsSwap) {\n swapNoJs();\n ensureLiveRegion();\n }\n });\n });\n // Observe documentElement for BOTH childList (new nodes) AND attribute\n // changes (class attribute \u2014 catches .no-js being added by a later script).\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['class'],\n });\n }\n\n // Defensive: re-run swapNoJs() on DOMContentLoaded and window.load.\n // Belt + suspenders for the .no-js race \u2014 if a framework (Astro, Next.js,\n // etc.) adds .no-js in a way the MutationObserver doesn't catch (e.g.,\n // before the observer is set up, or in a different document context),\n // these event handlers will catch it.\n if (typeof document.addEventListener === 'function') {\n document.addEventListener('DOMContentLoaded', function () {\n swapNoJs();\n ensureLiveRegion();\n });\n }\n if (typeof window.addEventListener === 'function') {\n window.addEventListener('load', function () {\n swapNoJs();\n ensureLiveRegion();\n });\n }\n\n // Pattern 4: astro:page-load event listener for Astro view transitions.\n // Astro emits this event after a view transition completes; the new page's\n // DOM may have replaced the old, so re-apply the .no-js \u2192 .js swap.\n if (typeof document.addEventListener === 'function') {\n document.addEventListener('astro:page-load', function () {\n swapNoJs();\n ensureLiveRegion();\n });\n }\n})();\n";
|
|
25
25
|
//# sourceMappingURL=copy-script.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-script.d.ts","sourceRoot":"","sources":["../src/copy-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"copy-script.d.ts","sourceRoot":"","sources":["../src/copy-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,WAAW,0oPAwMvB,CAAC"}
|
package/dist/copy-script.js
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;
|
package/dist/copy-script.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-script.js","sourceRoot":"","sources":["../src/copy-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG
|
|
1
|
+
{"version":3,"file":"copy-script.js","sourceRoot":"","sources":["../src/copy-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwM1B,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
import type { Root } from 'hast';
|
|
13
|
+
export interface DevWarningContext {
|
|
14
|
+
logger: {
|
|
15
|
+
warn: (msg: string) => void;
|
|
16
|
+
error: (msg: string) => void;
|
|
17
|
+
};
|
|
18
|
+
hasRehypeRaw: boolean;
|
|
19
|
+
wrap: boolean;
|
|
20
|
+
collapseAfter: number | null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check for common misconfigurations and emit dev warnings.
|
|
24
|
+
* Call this once per document after the plugin has processed the tree.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runDevWarnings(tree: Root, ctx: DevWarningContext): void;
|
|
27
|
+
/**
|
|
28
|
+
* Warn about an unknown language that Shiki couldn't load.
|
|
29
|
+
* Called from shiki.ts when a language fails to tokenize.
|
|
30
|
+
*/
|
|
31
|
+
export declare function warnUnknownLanguage(lang: string, ctx: DevWarningContext): void;
|
|
32
|
+
/**
|
|
33
|
+
* Reset the warning dedup set (for testing).
|
|
34
|
+
*/
|
|
35
|
+
export declare function resetWarningDedup(): void;
|
|
36
|
+
//# sourceMappingURL=dev-warnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-warnings.d.ts","sourceRoot":"","sources":["../src/dev-warnings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAG1C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IACtE,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAUD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAuEvE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAM9E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
import { visit } from 'unist-util-visit';
|
|
13
|
+
const warnedMessages = new Set();
|
|
14
|
+
function warnOnce(ctx, msg) {
|
|
15
|
+
if (warnedMessages.has(msg))
|
|
16
|
+
return;
|
|
17
|
+
warnedMessages.add(msg);
|
|
18
|
+
ctx.logger.warn(msg);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check for common misconfigurations and emit dev warnings.
|
|
22
|
+
* Call this once per document after the plugin has processed the tree.
|
|
23
|
+
*/
|
|
24
|
+
export function runDevWarnings(tree, ctx) {
|
|
25
|
+
// 1. Check for conflicting options
|
|
26
|
+
if (ctx.wrap && ctx.collapseAfter !== null) {
|
|
27
|
+
warnOnce(ctx, '[rehype-perfect-code-blocks] Both `wrap` and `collapseAfter` are enabled. ' +
|
|
28
|
+
'Collapsed blocks may not wrap correctly. Consider disabling one.');
|
|
29
|
+
}
|
|
30
|
+
// 2. Check for code blocks inside raw HTML without rehype-raw
|
|
31
|
+
if (!ctx.hasRehypeRaw) {
|
|
32
|
+
let foundRawHtmlAroundCode = false;
|
|
33
|
+
visit(tree, 'element', (node) => {
|
|
34
|
+
if (foundRawHtmlAroundCode)
|
|
35
|
+
return;
|
|
36
|
+
// Look for <pre> elements that are children of raw HTML elements
|
|
37
|
+
// like <details>, <div> with class containing "card", etc.
|
|
38
|
+
// This is a heuristic — we can't perfectly detect raw HTML vs markdown HTML.
|
|
39
|
+
if (node.tagName === 'details' ||
|
|
40
|
+
(node.tagName === 'div' && node.properties?.className &&
|
|
41
|
+
Array.isArray(node.properties.className) &&
|
|
42
|
+
node.properties.className.some((c) => typeof c === 'string' && (c.includes('card') || c.includes('container'))))) {
|
|
43
|
+
const hasPre = node.children?.some((c) => c.type === 'element' && c.tagName === 'pre');
|
|
44
|
+
if (hasPre)
|
|
45
|
+
foundRawHtmlAroundCode = true;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
if (foundRawHtmlAroundCode) {
|
|
49
|
+
warnOnce(ctx, '[rehype-perfect-code-blocks] Code block inside raw HTML detected but rehype-raw ' +
|
|
50
|
+
'does not appear to be installed. Code blocks inside <details>, <div class="card">, ' +
|
|
51
|
+
'etc. may not render correctly. Add rehype-raw to your pipeline: ' +
|
|
52
|
+
'npm install rehype-raw');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 3. Check for unknown/invalid meta syntax
|
|
56
|
+
visit(tree, 'element', (node) => {
|
|
57
|
+
if (node.tagName !== 'pre')
|
|
58
|
+
return;
|
|
59
|
+
const codeEl = node.children?.find((c) => c.type === 'element' && c.tagName === 'code');
|
|
60
|
+
if (!codeEl)
|
|
61
|
+
return;
|
|
62
|
+
const meta = codeEl.properties?.dataMeta ??
|
|
63
|
+
node.properties?.dataMeta ?? '';
|
|
64
|
+
if (!meta)
|
|
65
|
+
return;
|
|
66
|
+
// Check for invalid range syntax like {1,a-5} or {1-}
|
|
67
|
+
const rangeMatch = meta.match(/\{([^}]*)\}/g);
|
|
68
|
+
if (rangeMatch) {
|
|
69
|
+
for (const range of rangeMatch) {
|
|
70
|
+
const inside = range.slice(1, -1);
|
|
71
|
+
// Valid: digits, commas, hyphens, spaces, #id, /word/
|
|
72
|
+
// Invalid: letters (except in /word/ or "phrase"), other punctuation
|
|
73
|
+
if (!/^[\d\s,/-]+$/.test(inside) && !inside.includes('"') && !inside.includes('/')) {
|
|
74
|
+
warnOnce(ctx, `[rehype-perfect-code-blocks] Invalid meta syntax: "${range}" in "${meta}". ` +
|
|
75
|
+
'Expected format like {1,3-5} for line highlighting.');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Warn about an unknown language that Shiki couldn't load.
|
|
83
|
+
* Called from shiki.ts when a language fails to tokenize.
|
|
84
|
+
*/
|
|
85
|
+
export function warnUnknownLanguage(lang, ctx) {
|
|
86
|
+
warnOnce(ctx, `[rehype-perfect-code-blocks] Unknown language "${lang}" — not loaded in Shiki. ` +
|
|
87
|
+
'Falling back to plaintext. Add it to `shiki.langs` or install the grammar.');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Reset the warning dedup set (for testing).
|
|
91
|
+
*/
|
|
92
|
+
export function resetWarningDedup() {
|
|
93
|
+
warnedMessages.clear();
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=dev-warnings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-warnings.js","sourceRoot":"","sources":["../src/dev-warnings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AASzC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;AAEzC,SAAS,QAAQ,CAAC,GAAsB,EAAE,GAAW;IACnD,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IACpC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAU,EAAE,GAAsB;IAC/D,mCAAmC;IACnC,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,aAAa,KAAK,IAAI,EAAE,CAAC;QAC3C,QAAQ,CACN,GAAG,EACH,4EAA4E;YAC1E,kEAAkE,CACrE,CAAC;IACJ,CAAC;IAED,8DAA8D;IAC9D,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QACtB,IAAI,sBAAsB,GAAG,KAAK,CAAC;QACnC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAa,EAAE,EAAE;YACvC,IAAI,sBAAsB;gBAAE,OAAO;YACnC,iEAAiE;YACjE,2DAA2D;YAC3D,6EAA6E;YAC7E,IACE,IAAI,CAAC,OAAO,KAAK,SAAS;gBAC1B,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,SAAS;oBACpD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;oBACxC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAU,EAAE,EAAE,CAC5C,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CACzE,CAAC,EACH,CAAC;gBACD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK,CACnD,CAAC;gBACF,IAAI,MAAM;oBAAE,sBAAsB,GAAG,IAAI,CAAC;YAC5C,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,sBAAsB,EAAE,CAAC;YAC3B,QAAQ,CACN,GAAG,EACH,kFAAkF;gBAChF,qFAAqF;gBACrF,kEAAkE;gBAClE,wBAAwB,CAC3B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,2CAA2C;IAC3C,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAa,EAAE,EAAE;QACvC,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK;YAAE,OAAO;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAChC,CAAC,CAAC,EAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,KAAK,MAAM,CAClE,CAAC;QACF,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,IAAI,GAAI,MAAM,CAAC,UAAU,EAAE,QAA+B;YAC7D,IAAI,CAAC,UAAU,EAAE,QAA+B,IAAI,EAAE,CAAC;QAC1D,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,sDAAsD;QACtD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC9C,IAAI,UAAU,EAAE,CAAC;YACf,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;gBAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAClC,sDAAsD;gBACtD,qEAAqE;gBACrE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBACnF,QAAQ,CACN,GAAG,EACH,sDAAsD,KAAK,SAAS,IAAI,KAAK;wBAC3E,qDAAqD,CACxD,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,GAAsB;IACtE,QAAQ,CACN,GAAG,EACH,kDAAkD,IAAI,2BAA2B;QAC/E,4EAA4E,CAC/E,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,cAAc,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,12 +24,17 @@ import { wordDiff, hasChanges } from './word-diff.js';
|
|
|
24
24
|
import type { DiffToken } from './word-diff.js';
|
|
25
25
|
import { generateTokenStyles, applyScopeToCss, generateDarkModeSelector, generateLightModeSelector } from './tokens.js';
|
|
26
26
|
import type { DesignTokens } from './tokens.js';
|
|
27
|
+
import { resolveMathOptions, isMathLanguage, renderMath } from './math.js';
|
|
28
|
+
import type { MathOptions, ResolvedMathOptions } from './math.js';
|
|
29
|
+
import { runDevWarnings, warnUnknownLanguage } from './dev-warnings.js';
|
|
27
30
|
import type { PerfectCodeOptions } from './types.js';
|
|
28
31
|
export { remarkPreserveCodeMeta };
|
|
29
32
|
export { disposeHighlighter, runHighlighterTask };
|
|
30
33
|
export { wordDiff, hasChanges };
|
|
31
34
|
export { generateTokenStyles, applyScopeToCss, generateDarkModeSelector, generateLightModeSelector };
|
|
32
|
-
export
|
|
35
|
+
export { resolveMathOptions, isMathLanguage, renderMath };
|
|
36
|
+
export { runDevWarnings, warnUnknownLanguage };
|
|
37
|
+
export type { DiffToken, DesignTokens, MathOptions, ResolvedMathOptions };
|
|
33
38
|
export declare const rehypePerfectCodeBlocks: Plugin<[PerfectCodeOptions?], Root>;
|
|
34
39
|
export default rehypePerfectCodeBlocks;
|
|
35
40
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAuB,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,CAAC;AACrG,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAuB,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,CAAC;AACrG,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC;AAE1E,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC,EAAE,IAAI,CAmBrE,CAAC;AAEJ,eAAe,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -21,10 +21,14 @@ import { runShikiOnRawBlocks, disposeHighlighter, runHighlighterTask } from './s
|
|
|
21
21
|
import { remarkPreserveCodeMeta } from './remark.js';
|
|
22
22
|
import { wordDiff, hasChanges } from './word-diff.js';
|
|
23
23
|
import { generateTokenStyles, applyScopeToCss, generateDarkModeSelector, generateLightModeSelector } from './tokens.js';
|
|
24
|
+
import { resolveMathOptions, isMathLanguage, renderMath } from './math.js';
|
|
25
|
+
import { runDevWarnings, warnUnknownLanguage } from './dev-warnings.js';
|
|
24
26
|
export { remarkPreserveCodeMeta };
|
|
25
27
|
export { disposeHighlighter, runHighlighterTask };
|
|
26
28
|
export { wordDiff, hasChanges };
|
|
27
29
|
export { generateTokenStyles, applyScopeToCss, generateDarkModeSelector, generateLightModeSelector };
|
|
30
|
+
export { resolveMathOptions, isMathLanguage, renderMath };
|
|
31
|
+
export { runDevWarnings, warnUnknownLanguage };
|
|
28
32
|
export const rehypePerfectCodeBlocks = (options = {}) => {
|
|
29
33
|
const engine = options.engine ?? 'auto';
|
|
30
34
|
const opts = options;
|
|
@@ -151,6 +155,9 @@ function resolveDefaults(opts) {
|
|
|
151
155
|
tokens: opts.tokens ?? undefined,
|
|
152
156
|
darkMode: opts.darkMode ?? undefined,
|
|
153
157
|
scope: opts.scope ?? undefined,
|
|
158
|
+
// v2.1.0: P1 features
|
|
159
|
+
math: opts.math ?? undefined,
|
|
160
|
+
devWarnings: opts.devWarnings ?? (process.env.NODE_ENV !== 'production'),
|
|
154
161
|
inline: opts.inline ?? false,
|
|
155
162
|
};
|
|
156
163
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAAE,uBAAuB,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/math.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
export interface MathOptions {
|
|
15
|
+
engine?: 'katex' | 'none';
|
|
16
|
+
inline?: boolean;
|
|
17
|
+
block?: boolean;
|
|
18
|
+
injectCss?: boolean;
|
|
19
|
+
throwOnError?: boolean;
|
|
20
|
+
strict?: boolean | 'ignore' | 'error' | 'warn';
|
|
21
|
+
}
|
|
22
|
+
export interface ResolvedMathOptions {
|
|
23
|
+
engine: 'katex' | 'none';
|
|
24
|
+
inline: boolean;
|
|
25
|
+
block: boolean;
|
|
26
|
+
injectCss: boolean;
|
|
27
|
+
throwOnError: boolean;
|
|
28
|
+
strict: boolean | 'ignore' | 'error' | 'warn';
|
|
29
|
+
}
|
|
30
|
+
export declare function resolveMathOptions(math?: MathOptions): ResolvedMathOptions;
|
|
31
|
+
/** Languages that should be rendered as math instead of syntax-highlighted. */
|
|
32
|
+
export declare const MATH_LANGS: Set<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Check if a language identifier should be treated as math.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isMathLanguage(lang: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Render a LaTeX string to HTML via KaTeX.
|
|
39
|
+
*
|
|
40
|
+
* @param latex The LaTeX source string
|
|
41
|
+
* @param displayMode true for block math ($$...$$), false for inline ($...$)
|
|
42
|
+
* @param options Resolved math options
|
|
43
|
+
* @returns { html: string, isKatex: boolean } — if katex is available,
|
|
44
|
+
* html is the KaTeX-rendered HTML; otherwise it's the LaTeX source in a
|
|
45
|
+
* `<code>` element, and isKatex is false.
|
|
46
|
+
*/
|
|
47
|
+
export declare function renderMath(latex: string, displayMode: boolean, options: ResolvedMathOptions): Promise<{
|
|
48
|
+
html: string;
|
|
49
|
+
isKatex: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Regex to find inline `$...$` math in text.
|
|
53
|
+
* Matches `$` followed by non-$ content followed by `$`.
|
|
54
|
+
* Does NOT match `$$` (which is block math) or escaped `\$`.
|
|
55
|
+
*
|
|
56
|
+
* Examples:
|
|
57
|
+
* "$x^2$" → match (inline math)
|
|
58
|
+
* "$$x^2$$" → NO match (block math)
|
|
59
|
+
* "cost is \$5" → NO match (escaped dollar sign)
|
|
60
|
+
* "a $ b $ c" → match "$ b $" (ambiguous, but we match it)
|
|
61
|
+
*/
|
|
62
|
+
export declare const INLINE_MATH_REGEX: RegExp;
|
|
63
|
+
/**
|
|
64
|
+
* Regex to find block `$$...$$` math in text.
|
|
65
|
+
* Matches `$$` followed by content followed by `$$`.
|
|
66
|
+
*/
|
|
67
|
+
export declare const BLOCK_MATH_REGEX: RegExp;
|
|
68
|
+
/**
|
|
69
|
+
* Process a text string, replacing inline `$...$` and block `$$...$$`
|
|
70
|
+
* with rendered math HTML.
|
|
71
|
+
*
|
|
72
|
+
* @param text The input text
|
|
73
|
+
* @param options Resolved math options
|
|
74
|
+
* @returns Array of { type: 'text' | 'inline-math' | 'block-math', content: string }
|
|
75
|
+
* segments. The caller is responsible for converting these to HAST nodes.
|
|
76
|
+
*/
|
|
77
|
+
export declare function processMathInText(text: string, options: ResolvedMathOptions): Promise<Array<{
|
|
78
|
+
type: 'text' | 'inline-math' | 'block-math';
|
|
79
|
+
content: string;
|
|
80
|
+
html?: string;
|
|
81
|
+
}>>;
|
|
82
|
+
/**
|
|
83
|
+
* Get the KaTeX CSS path for injection.
|
|
84
|
+
* Returns the path to `katex/dist/katex.min.css` relative to the project.
|
|
85
|
+
*/
|
|
86
|
+
export declare function getKatexCssPath(): string;
|
|
87
|
+
/**
|
|
88
|
+
* Try to read the KaTeX CSS content from the filesystem.
|
|
89
|
+
* Returns null if katex is not installed or the CSS file can't be read.
|
|
90
|
+
*/
|
|
91
|
+
export declare function tryReadKatexCss(): Promise<string | null>;
|
|
92
|
+
//# sourceMappingURL=math.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"math.d.ts","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;CAChD;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;CAC/C;AAED,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,mBAAmB,CAS1E;AAED,+EAA+E;AAC/E,eAAO,MAAM,UAAU,aAAoC,CAAC;AAoB5D;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,OAAO,EACpB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuB7C;AAaD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,QAAwC,CAAC;AAEvE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAwB,CAAC;AAEtD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAkDjG;AAsCD;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe9D"}
|