@miethe/ui 0.2.0 → 0.6.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 +65 -0
- package/README.md +863 -9
- package/dist/components/content-viewer/ArticleViewer.d.ts +42 -0
- package/dist/components/content-viewer/ArticleViewer.d.ts.map +1 -0
- package/dist/components/content-viewer/ArticleViewer.js +321 -0
- package/dist/components/content-viewer/ArticleViewer.js.map +1 -0
- package/dist/components/content-viewer/FrontmatterHeader.d.ts +32 -0
- package/dist/components/content-viewer/FrontmatterHeader.d.ts.map +1 -0
- package/dist/components/content-viewer/FrontmatterHeader.js +95 -0
- package/dist/components/content-viewer/FrontmatterHeader.js.map +1 -0
- package/dist/components/content-viewer/callouts/Callout.d.ts +43 -0
- package/dist/components/content-viewer/callouts/Callout.d.ts.map +1 -0
- package/dist/components/content-viewer/callouts/Callout.js +86 -0
- package/dist/components/content-viewer/callouts/Callout.js.map +1 -0
- package/dist/components/content-viewer/callouts/index.d.ts +2 -0
- package/dist/components/content-viewer/callouts/index.d.ts.map +1 -0
- package/dist/components/content-viewer/callouts/index.js +2 -0
- package/dist/components/content-viewer/callouts/index.js.map +1 -0
- package/dist/components/content-viewer/index.d.ts +21 -0
- package/dist/components/content-viewer/index.d.ts.map +1 -0
- package/dist/components/content-viewer/index.js +29 -0
- package/dist/components/content-viewer/index.js.map +1 -0
- package/dist/components/content-viewer/plugins/index.d.ts +5 -0
- package/dist/components/content-viewer/plugins/index.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/index.js +5 -0
- package/dist/components/content-viewer/plugins/index.js.map +1 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.d.ts +63 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.js +120 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.d.ts +44 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.js +122 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.d.ts +59 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.js +79 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.d.ts +37 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.js +82 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.js.map +1 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.d.ts +39 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.js +77 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.js.map +1 -0
- package/dist/components/content-viewer/plugins/slugify.d.ts +24 -0
- package/dist/components/content-viewer/plugins/slugify.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/slugify.js +31 -0
- package/dist/components/content-viewer/plugins/slugify.js.map +1 -0
- package/dist/components/content-viewer/sanitize.d.ts +75 -0
- package/dist/components/content-viewer/sanitize.d.ts.map +1 -0
- package/dist/components/content-viewer/sanitize.js +252 -0
- package/dist/components/content-viewer/sanitize.js.map +1 -0
- package/dist/components/content-viewer/types.d.ts +315 -0
- package/dist/components/content-viewer/types.d.ts.map +1 -0
- package/dist/components/content-viewer/types.js +8 -0
- package/dist/components/content-viewer/types.js.map +1 -0
- package/dist/components/content-viewer/variants.d.ts +71 -0
- package/dist/components/content-viewer/variants.d.ts.map +1 -0
- package/dist/components/content-viewer/variants.js +105 -0
- package/dist/components/content-viewer/variants.js.map +1 -0
- package/dist/content-viewer/ContentPane.d.ts +44 -1
- package/dist/content-viewer/ContentPane.d.ts.map +1 -1
- package/dist/content-viewer/ContentPane.js +139 -5
- package/dist/content-viewer/ContentPane.js.map +1 -1
- package/dist/content-viewer/FileTree.d.ts +23 -1
- package/dist/content-viewer/FileTree.d.ts.map +1 -1
- package/dist/content-viewer/FileTree.js +20 -5
- package/dist/content-viewer/FileTree.js.map +1 -1
- package/dist/content-viewer/index.d.ts +2 -0
- package/dist/content-viewer/index.d.ts.map +1 -1
- package/dist/content-viewer/index.js +2 -0
- package/dist/content-viewer/index.js.map +1 -1
- package/dist/diff/DiffViewer.js +3 -3
- package/dist/diff/DiffViewer.js.map +1 -1
- package/dist/discovery/discovery-card.d.ts +25 -0
- package/dist/discovery/discovery-card.d.ts.map +1 -0
- package/dist/discovery/discovery-card.js +265 -0
- package/dist/discovery/discovery-card.js.map +1 -0
- package/dist/discovery/index.d.ts +3 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +3 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/display/ContextInfoCard.d.ts +61 -0
- package/dist/display/ContextInfoCard.d.ts.map +1 -0
- package/dist/display/ContextInfoCard.js +45 -0
- package/dist/display/ContextInfoCard.js.map +1 -0
- package/dist/display/index.d.ts +2 -0
- package/dist/display/index.d.ts.map +1 -1
- package/dist/display/index.js +1 -0
- package/dist/display/index.js.map +1 -1
- package/dist/editor/CodeEditor.d.ts +39 -0
- package/dist/editor/CodeEditor.d.ts.map +1 -0
- package/dist/editor/CodeEditor.js +114 -0
- package/dist/editor/CodeEditor.js.map +1 -0
- package/dist/editor/MarkdownEditor.d.ts +3 -2
- package/dist/editor/MarkdownEditor.d.ts.map +1 -1
- package/dist/editor/MarkdownEditor.js +32 -80
- package/dist/editor/MarkdownEditor.js.map +1 -1
- package/dist/editor/SplitPreview.d.ts +10 -1
- package/dist/editor/SplitPreview.d.ts.map +1 -1
- package/dist/editor/SplitPreview.js +4 -2
- package/dist/editor/SplitPreview.js.map +1 -1
- package/dist/editor/codeLanguages.d.ts +28 -0
- package/dist/editor/codeLanguages.d.ts.map +1 -0
- package/dist/editor/codeLanguages.js +54 -0
- package/dist/editor/codeLanguages.js.map +1 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +6 -0
- package/dist/editor/index.js.map +1 -1
- package/dist/editor/theme.d.ts +16 -0
- package/dist/editor/theme.d.ts.map +1 -0
- package/dist/editor/theme.js +82 -0
- package/dist/editor/theme.js.map +1 -0
- package/dist/filters/filter-bar.d.ts +14 -0
- package/dist/filters/filter-bar.d.ts.map +1 -0
- package/dist/filters/filter-bar.js +47 -0
- package/dist/filters/filter-bar.js.map +1 -0
- package/dist/filters/filter-slot-config.d.ts +239 -0
- package/dist/filters/filter-slot-config.d.ts.map +1 -0
- package/dist/filters/filter-slot-config.js +24 -0
- package/dist/filters/filter-slot-config.js.map +1 -0
- package/dist/filters/index.d.ts +2 -0
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/filters/index.js +1 -0
- package/dist/filters/index.js.map +1 -1
- package/dist/primitives/BatchReadinessPill.d.ts +22 -0
- package/dist/primitives/BatchReadinessPill.d.ts.map +1 -0
- package/dist/primitives/BatchReadinessPill.js +20 -0
- package/dist/primitives/BatchReadinessPill.js.map +1 -0
- package/dist/primitives/Card.d.ts +28 -0
- package/dist/primitives/Card.d.ts.map +1 -0
- package/dist/primitives/Card.js +30 -0
- package/dist/primitives/Card.js.map +1 -0
- package/dist/primitives/CollectionPicker.d.ts +47 -0
- package/dist/primitives/CollectionPicker.d.ts.map +1 -0
- package/dist/primitives/CollectionPicker.js +105 -0
- package/dist/primitives/CollectionPicker.js.map +1 -0
- package/dist/primitives/CreateEntityDialog.d.ts +144 -0
- package/dist/primitives/CreateEntityDialog.d.ts.map +1 -0
- package/dist/primitives/CreateEntityDialog.js +379 -0
- package/dist/primitives/CreateEntityDialog.js.map +1 -0
- package/dist/primitives/EffectiveStatusChips.d.ts +43 -0
- package/dist/primitives/EffectiveStatusChips.d.ts.map +1 -0
- package/dist/primitives/EffectiveStatusChips.js +23 -0
- package/dist/primitives/EffectiveStatusChips.js.map +1 -0
- package/dist/primitives/FormField.d.ts +29 -0
- package/dist/primitives/FormField.d.ts.map +1 -0
- package/dist/primitives/FormField.js +27 -0
- package/dist/primitives/FormField.js.map +1 -0
- package/dist/primitives/Label.d.ts +20 -0
- package/dist/primitives/Label.d.ts.map +1 -0
- package/dist/primitives/Label.js +21 -0
- package/dist/primitives/Label.js.map +1 -0
- package/dist/primitives/MismatchBadge.d.ts +34 -0
- package/dist/primitives/MismatchBadge.d.ts.map +1 -0
- package/dist/primitives/MismatchBadge.js +28 -0
- package/dist/primitives/MismatchBadge.js.map +1 -0
- package/dist/primitives/PlanningNodeTypeIcon.d.ts +33 -0
- package/dist/primitives/PlanningNodeTypeIcon.d.ts.map +1 -0
- package/dist/primitives/PlanningNodeTypeIcon.js +35 -0
- package/dist/primitives/PlanningNodeTypeIcon.js.map +1 -0
- package/dist/primitives/SecretField.d.ts +28 -0
- package/dist/primitives/SecretField.d.ts.map +1 -0
- package/dist/primitives/SecretField.js +65 -0
- package/dist/primitives/SecretField.js.map +1 -0
- package/dist/primitives/Spinner.d.ts +16 -0
- package/dist/primitives/Spinner.d.ts.map +1 -0
- package/dist/primitives/Spinner.js +34 -0
- package/dist/primitives/Spinner.js.map +1 -0
- package/dist/primitives/StatusChip.d.ts +17 -0
- package/dist/primitives/StatusChip.d.ts.map +1 -0
- package/dist/primitives/StatusChip.js +22 -0
- package/dist/primitives/StatusChip.js.map +1 -0
- package/dist/primitives/Switch.d.ts +32 -0
- package/dist/primitives/Switch.d.ts.map +1 -0
- package/dist/primitives/Switch.js +43 -0
- package/dist/primitives/Switch.js.map +1 -0
- package/dist/primitives/index.d.ts +28 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +16 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/primitives/variants.d.ts +18 -0
- package/dist/primitives/variants.d.ts.map +1 -0
- package/dist/primitives/variants.js +33 -0
- package/dist/primitives/variants.js.map +1 -0
- package/dist/utils/type-colors.d.ts.map +1 -1
- package/dist/utils/type-colors.js +4 -0
- package/dist/utils/type-colors.js.map +1 -1
- package/package.json +40 -6
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rehypeExternalLinks — shared rehype plugin for external link hardening (PU3-04)
|
|
3
|
+
*
|
|
4
|
+
* Adds `target="_blank"` and `rel="noopener noreferrer"` to every `<a>` element
|
|
5
|
+
* whose `href` is an absolute URL with an http, https, or mailto scheme.
|
|
6
|
+
*
|
|
7
|
+
* This plugin is intentionally written as a pure unified/rehype tree visitor so
|
|
8
|
+
* it works on both the markdown pipeline (via ReactMarkdown's `rehypePlugins`)
|
|
9
|
+
* and the HTML pipeline (unified + rehypeParse + this plugin + rehype-sanitize
|
|
10
|
+
* + rehypeStringify). A single implementation avoids per-component duplication.
|
|
11
|
+
*
|
|
12
|
+
* Scheme detection uses a strict allowlist: `http:`, `https:`, `mailto:`.
|
|
13
|
+
* Protocol-relative URLs (`//example.com`) are also treated as external.
|
|
14
|
+
*
|
|
15
|
+
* Note: The markdown path also uses the `components.a` override in
|
|
16
|
+
* `buildComponentMap()` for link hardening because ReactMarkdown does not
|
|
17
|
+
* thread rehype node attributes through to rendered anchors. The `components.a`
|
|
18
|
+
* override is kept for the markdown path; this plugin is the canonical
|
|
19
|
+
* implementation used for the HTML path and can be tested in isolation.
|
|
20
|
+
*/
|
|
21
|
+
import { visit } from 'unist-util-visit';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helper
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Schemes treated as "external" — anything else (relative, #anchor, data:, etc.)
|
|
26
|
+
// is left unmodified.
|
|
27
|
+
const EXTERNAL_SCHEMES = new Set(['http:', 'https:', 'mailto:']);
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if `href` should be treated as an external link.
|
|
30
|
+
* Protocol-relative URLs (starting with `//`) are also considered external.
|
|
31
|
+
*/
|
|
32
|
+
export function isExternalHref(href) {
|
|
33
|
+
if (href.startsWith('//'))
|
|
34
|
+
return true;
|
|
35
|
+
try {
|
|
36
|
+
const url = new URL(href);
|
|
37
|
+
return EXTERNAL_SCHEMES.has(url.protocol);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Not an absolute URL — treat as relative/internal
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Plugin
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* Unified/rehype plugin that adds `target="_blank"` and
|
|
49
|
+
* `rel="noopener noreferrer"` to external `<a>` elements.
|
|
50
|
+
*
|
|
51
|
+
* Usage (HTML pipeline):
|
|
52
|
+
* ```ts
|
|
53
|
+
* unified()
|
|
54
|
+
* .use(rehypeParse, { fragment: true })
|
|
55
|
+
* .use(rehypeExternalLinks)
|
|
56
|
+
* .use(rehypeSanitize) // sanitize AFTER link attrs are set (allowlist includes rel/target)
|
|
57
|
+
* .use(rehypeStringify)
|
|
58
|
+
* .process(htmlString);
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export const rehypeExternalLinks = () => {
|
|
62
|
+
return (tree) => {
|
|
63
|
+
visit(tree, 'element', (node) => {
|
|
64
|
+
const el = node;
|
|
65
|
+
if (el.tagName !== 'a')
|
|
66
|
+
return;
|
|
67
|
+
const href = el.properties?.['href'];
|
|
68
|
+
if (typeof href !== 'string')
|
|
69
|
+
return;
|
|
70
|
+
if (isExternalHref(href)) {
|
|
71
|
+
el.properties = el.properties ?? {};
|
|
72
|
+
el.properties['target'] = '_blank';
|
|
73
|
+
el.properties['rel'] = 'noopener noreferrer';
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export default rehypeExternalLinks;
|
|
79
|
+
//# sourceMappingURL=rehypeExternalLinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rehypeExternalLinks.js","sourceRoot":"","sources":["../../../../src/components/content-viewer/plugins/rehypeExternalLinks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAwBzC,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E,iFAAiF;AACjF,sBAAsB;AACtB,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AAEjE;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,mDAAmD;QACnD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAyB,GAA0B,EAAE;IACnF,OAAO,CAAC,IAAc,EAAE,EAAE;QACxB,KAAK,CAAC,IAAmC,EAAE,SAAS,EAAE,CAAC,IAAc,EAAE,EAAE;YACvE,MAAM,EAAE,GAAG,IAAmB,CAAC;YAC/B,IAAI,EAAE,CAAC,OAAO,KAAK,GAAG;gBAAE,OAAO;YAE/B,MAAM,IAAI,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC;YACrC,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,OAAO;YAErC,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC;gBACpC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;gBACnC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,qBAAqB,CAAC;YAC/C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rehypeHeadingIds — auto-generate `id` attributes on heading elements.
|
|
3
|
+
*
|
|
4
|
+
* Implements OQ-UCV-D / A-UCV-08 (generateHeadingIds prop, default true).
|
|
5
|
+
*
|
|
6
|
+
* Algorithm:
|
|
7
|
+
* 1. Walk the hast tree for h1–h6 elements.
|
|
8
|
+
* 2. Extract plain text content by recursively collecting Text node values.
|
|
9
|
+
* 3. Slugify the text using a GitHub-compatible slug algorithm:
|
|
10
|
+
* - Lowercase
|
|
11
|
+
* - Replace non-alphanumeric (non-space) chars with nothing
|
|
12
|
+
* - Replace spaces with hyphens
|
|
13
|
+
* - Strip leading/trailing hyphens
|
|
14
|
+
* 4. Deduplicate: if the slug already exists, append `-1`, `-2`, … until unique.
|
|
15
|
+
* 5. Set `id` on the element (skipping elements that already have an `id`).
|
|
16
|
+
*
|
|
17
|
+
* The deduplication counter is per-document (plugin instance). Each page render
|
|
18
|
+
* creates a fresh plugin instance via `createHeadingIdsPlugin()`.
|
|
19
|
+
*
|
|
20
|
+
* @module rehypeHeadingIds
|
|
21
|
+
*/
|
|
22
|
+
import type { Root } from 'hast';
|
|
23
|
+
export { slugify } from './slugify';
|
|
24
|
+
/**
|
|
25
|
+
* Create a rehype plugin that adds `id` attributes to h1–h6 elements.
|
|
26
|
+
*
|
|
27
|
+
* A fresh plugin instance (and thus a fresh deduplication map) must be created
|
|
28
|
+
* for each render to avoid cross-render contamination.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const headingIdsPlugin = createHeadingIdsPlugin();
|
|
33
|
+
* // pass to ReactMarkdown rehypePlugins
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function createHeadingIdsPlugin(): () => (tree: Root) => void;
|
|
37
|
+
//# sourceMappingURL=rehypeHeadingIds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rehypeHeadingIds.d.ts","sourceRoot":"","sources":["../../../../src/components/content-viewer/plugins/rehypeHeadingIds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAA8C,MAAM,MAAM,CAAC;AAI7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2BpC;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAwBnE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rehypeHeadingIds — auto-generate `id` attributes on heading elements.
|
|
3
|
+
*
|
|
4
|
+
* Implements OQ-UCV-D / A-UCV-08 (generateHeadingIds prop, default true).
|
|
5
|
+
*
|
|
6
|
+
* Algorithm:
|
|
7
|
+
* 1. Walk the hast tree for h1–h6 elements.
|
|
8
|
+
* 2. Extract plain text content by recursively collecting Text node values.
|
|
9
|
+
* 3. Slugify the text using a GitHub-compatible slug algorithm:
|
|
10
|
+
* - Lowercase
|
|
11
|
+
* - Replace non-alphanumeric (non-space) chars with nothing
|
|
12
|
+
* - Replace spaces with hyphens
|
|
13
|
+
* - Strip leading/trailing hyphens
|
|
14
|
+
* 4. Deduplicate: if the slug already exists, append `-1`, `-2`, … until unique.
|
|
15
|
+
* 5. Set `id` on the element (skipping elements that already have an `id`).
|
|
16
|
+
*
|
|
17
|
+
* The deduplication counter is per-document (plugin instance). Each page render
|
|
18
|
+
* creates a fresh plugin instance via `createHeadingIdsPlugin()`.
|
|
19
|
+
*
|
|
20
|
+
* @module rehypeHeadingIds
|
|
21
|
+
*/
|
|
22
|
+
import { visit } from 'unist-util-visit';
|
|
23
|
+
import { slugify } from './slugify';
|
|
24
|
+
export { slugify } from './slugify';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Slug helpers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
const HEADING_TAGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
|
|
29
|
+
/**
|
|
30
|
+
* Extract plain text from a hast node by recursively collecting text nodes.
|
|
31
|
+
*/
|
|
32
|
+
function extractText(nodes) {
|
|
33
|
+
let text = '';
|
|
34
|
+
for (const node of nodes) {
|
|
35
|
+
if (node.type === 'text') {
|
|
36
|
+
text += node.value;
|
|
37
|
+
}
|
|
38
|
+
else if ('children' in node && Array.isArray(node.children)) {
|
|
39
|
+
text += extractText(node.children);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Plugin factory
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* Create a rehype plugin that adds `id` attributes to h1–h6 elements.
|
|
49
|
+
*
|
|
50
|
+
* A fresh plugin instance (and thus a fresh deduplication map) must be created
|
|
51
|
+
* for each render to avoid cross-render contamination.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const headingIdsPlugin = createHeadingIdsPlugin();
|
|
56
|
+
* // pass to ReactMarkdown rehypePlugins
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function createHeadingIdsPlugin() {
|
|
60
|
+
return function rehypeHeadingIdsPlugin() {
|
|
61
|
+
return function transformer(tree) {
|
|
62
|
+
const seen = new Map();
|
|
63
|
+
visit(tree, 'element', (node) => {
|
|
64
|
+
if (!HEADING_TAGS.has(node.tagName))
|
|
65
|
+
return;
|
|
66
|
+
// Skip elements that already have an id
|
|
67
|
+
if (node.properties?.id)
|
|
68
|
+
return;
|
|
69
|
+
const rawText = extractText(node.children);
|
|
70
|
+
const base = slugify(rawText);
|
|
71
|
+
if (!base)
|
|
72
|
+
return;
|
|
73
|
+
// Deduplicate
|
|
74
|
+
const count = seen.get(base) ?? 0;
|
|
75
|
+
const id = count === 0 ? base : `${base}-${count}`;
|
|
76
|
+
seen.set(base, count + 1);
|
|
77
|
+
node.properties = { ...node.properties, id };
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=rehypeHeadingIds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rehypeHeadingIds.js","sourceRoot":"","sources":["../../../../src/components/content-viewer/plugins/rehypeHeadingIds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAEnE;;GAEG;AACH,SAAS,WAAW,CAAC,KAAkD;IACrE,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,IAAI,IAAK,IAAa,CAAC,KAAK,CAAC;QAC/B,CAAC;aAAM,IAAI,UAAU,IAAI,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9D,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,QAAyC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,SAAS,sBAAsB;QACpC,OAAO,SAAS,WAAW,CAAC,IAAU;YACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;YAEvC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAa,EAAE,EAAE;gBACvC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;oBAAE,OAAO;gBAE5C,wCAAwC;gBACxC,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE;oBAAE,OAAO;gBAEhC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,QAAyC,CAAC,CAAC;gBAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC9B,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAElB,cAAc;gBACd,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAClC,MAAM,EAAE,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;gBACnD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBAE1B,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,EAAE,EAAE,CAAC;YAC/C,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remarkCallouts — custom remark plugin
|
|
3
|
+
*
|
|
4
|
+
* Transforms `:::type` container directives produced by `remark-directive`
|
|
5
|
+
* into custom HAST elements that ReactMarkdown's `components` map can render.
|
|
6
|
+
*
|
|
7
|
+
* Directive syntax:
|
|
8
|
+
* ```markdown
|
|
9
|
+
* ::: note
|
|
10
|
+
* Content here.
|
|
11
|
+
* :::
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The plugin visits `containerDirective` nodes whose `name` matches one of
|
|
15
|
+
* the recognised callout types (note, reference, warning, info). It rewrites
|
|
16
|
+
* the node in-place so that the mdast → hast bridge produces a custom element
|
|
17
|
+
* name (e.g. `callout-note`) which ReactMarkdown maps to a JSX component.
|
|
18
|
+
*
|
|
19
|
+
* Implementation note: remark-directive (v2) produces mdast nodes of type
|
|
20
|
+
* `containerDirective`. We traverse the tree manually to avoid importing
|
|
21
|
+
* `unist-util-visit` (not in the package's direct deps) while keeping the
|
|
22
|
+
* plugin zero-dependency beyond what remark-directive already provides.
|
|
23
|
+
*/
|
|
24
|
+
/** Recognised callout directive names */
|
|
25
|
+
declare const CALLOUT_TYPES: Set<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Remark plugin: walk the tree, find container directives that match a known
|
|
28
|
+
* callout type, and annotate them with hast properties so ReactMarkdown maps
|
|
29
|
+
* them to the correct callout component.
|
|
30
|
+
*
|
|
31
|
+
* ReactMarkdown uses `node.data.hName` as the element tag name and
|
|
32
|
+
* `node.data.hProperties` as the element's props. By setting `hName` to
|
|
33
|
+
* `"callout-{type}"` we can register a component for each type in the
|
|
34
|
+
* `components` map without name collisions with standard HTML elements.
|
|
35
|
+
*/
|
|
36
|
+
declare function remarkCallouts(): (tree: Record<string, unknown>) => void;
|
|
37
|
+
export default remarkCallouts;
|
|
38
|
+
export { CALLOUT_TYPES };
|
|
39
|
+
//# sourceMappingURL=remarkCallouts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remarkCallouts.d.ts","sourceRoot":"","sources":["../../../../src/components/content-viewer/plugins/remarkCallouts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,yCAAyC;AACzC,QAAA,MAAM,aAAa,aAA4D,CAAC;AAyBhF;;;;;;;;;GASG;AACH,iBAAS,cAAc,KACb,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAAI,CAsB7C;AAED,eAAe,cAAc,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remarkCallouts — custom remark plugin
|
|
3
|
+
*
|
|
4
|
+
* Transforms `:::type` container directives produced by `remark-directive`
|
|
5
|
+
* into custom HAST elements that ReactMarkdown's `components` map can render.
|
|
6
|
+
*
|
|
7
|
+
* Directive syntax:
|
|
8
|
+
* ```markdown
|
|
9
|
+
* ::: note
|
|
10
|
+
* Content here.
|
|
11
|
+
* :::
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The plugin visits `containerDirective` nodes whose `name` matches one of
|
|
15
|
+
* the recognised callout types (note, reference, warning, info). It rewrites
|
|
16
|
+
* the node in-place so that the mdast → hast bridge produces a custom element
|
|
17
|
+
* name (e.g. `callout-note`) which ReactMarkdown maps to a JSX component.
|
|
18
|
+
*
|
|
19
|
+
* Implementation note: remark-directive (v2) produces mdast nodes of type
|
|
20
|
+
* `containerDirective`. We traverse the tree manually to avoid importing
|
|
21
|
+
* `unist-util-visit` (not in the package's direct deps) while keeping the
|
|
22
|
+
* plugin zero-dependency beyond what remark-directive already provides.
|
|
23
|
+
*/
|
|
24
|
+
/** Recognised callout directive names */
|
|
25
|
+
const CALLOUT_TYPES = new Set(['note', 'reference', 'warning', 'info']);
|
|
26
|
+
/**
|
|
27
|
+
* Minimal recursive visitor — walks the mdast tree and invokes `visitor`
|
|
28
|
+
* for every node whose `type` matches `nodeType`.
|
|
29
|
+
*/
|
|
30
|
+
function visitAll(tree, nodeType, visitor) {
|
|
31
|
+
if (!tree || typeof tree !== 'object')
|
|
32
|
+
return;
|
|
33
|
+
if (tree['type'] === nodeType) {
|
|
34
|
+
visitor(tree);
|
|
35
|
+
}
|
|
36
|
+
const children = tree['children'];
|
|
37
|
+
if (Array.isArray(children)) {
|
|
38
|
+
for (const child of children) {
|
|
39
|
+
visitAll(child, nodeType, visitor);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Remark plugin: walk the tree, find container directives that match a known
|
|
45
|
+
* callout type, and annotate them with hast properties so ReactMarkdown maps
|
|
46
|
+
* them to the correct callout component.
|
|
47
|
+
*
|
|
48
|
+
* ReactMarkdown uses `node.data.hName` as the element tag name and
|
|
49
|
+
* `node.data.hProperties` as the element's props. By setting `hName` to
|
|
50
|
+
* `"callout-{type}"` we can register a component for each type in the
|
|
51
|
+
* `components` map without name collisions with standard HTML elements.
|
|
52
|
+
*/
|
|
53
|
+
function remarkCallouts() {
|
|
54
|
+
return (tree) => {
|
|
55
|
+
visitAll(tree, 'containerDirective', (node) => {
|
|
56
|
+
const name = node['name']?.toLowerCase();
|
|
57
|
+
if (!name || !CALLOUT_TYPES.has(name))
|
|
58
|
+
return;
|
|
59
|
+
const calloutType = name;
|
|
60
|
+
// Ensure data bag exists
|
|
61
|
+
if (!node['data'] || typeof node['data'] !== 'object') {
|
|
62
|
+
node['data'] = {};
|
|
63
|
+
}
|
|
64
|
+
const data = node['data'];
|
|
65
|
+
// Map to a custom element name that ReactMarkdown's components prop can target.
|
|
66
|
+
// Using lowercase kebab-case avoids conflicts with standard HTML elements.
|
|
67
|
+
data['hName'] = `callout-${calloutType}`;
|
|
68
|
+
data['hProperties'] = {
|
|
69
|
+
...(data['hProperties'] ?? {}),
|
|
70
|
+
'data-callout-type': calloutType,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export default remarkCallouts;
|
|
76
|
+
export { CALLOUT_TYPES };
|
|
77
|
+
//# sourceMappingURL=remarkCallouts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remarkCallouts.js","sourceRoot":"","sources":["../../../../src/components/content-viewer/plugins/remarkCallouts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,yCAAyC;AACzC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhF;;;GAGG;AACH,SAAS,QAAQ,CACf,IAA6B,EAC7B,QAAgB,EAChB,OAAgD;IAEhD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO;IAE9C,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,QAAQ,CAAC,KAAgC,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,cAAc;IACrB,OAAO,CAAC,IAA6B,EAAQ,EAAE;QAC7C,QAAQ,CAAC,IAAI,EAAE,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE;YAC5C,MAAM,IAAI,GAAI,IAAI,CAAC,MAAM,CAAwB,EAAE,WAAW,EAAE,CAAC;YACjE,IAAI,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO;YAE9C,MAAM,WAAW,GAAG,IAAmB,CAAC;YAExC,yBAAyB;YACzB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACtD,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;YACpB,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAA4B,CAAC;YAErD,gFAAgF;YAChF,2EAA2E;YAC3E,IAAI,CAAC,OAAO,CAAC,GAAG,WAAW,WAAW,EAAE,CAAC;YACzC,IAAI,CAAC,aAAa,CAAC,GAAG;gBACpB,GAAG,CAAE,IAAI,CAAC,aAAa,CAA6B,IAAI,EAAE,CAAC;gBAC3D,mBAAmB,EAAE,WAAW;aACjC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,eAAe,cAAc,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub-compatible heading slug utility.
|
|
3
|
+
*
|
|
4
|
+
* Exported separately from rehypeHeadingIds so consumers can:
|
|
5
|
+
* 1. Import slugify without pulling in the full hast/unist dependency graph.
|
|
6
|
+
* 2. Test the slug algorithm in Jest without ESM transformation issues.
|
|
7
|
+
*
|
|
8
|
+
* Algorithm:
|
|
9
|
+
* - Lowercase the text
|
|
10
|
+
* - Remove characters that are not word chars ([\w]), hyphens, or spaces
|
|
11
|
+
* - Replace whitespace runs with a single hyphen
|
|
12
|
+
* - Collapse multiple consecutive hyphens
|
|
13
|
+
* - Strip leading and trailing hyphens
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Produce a GitHub-compatible URL slug from a heading text string.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* slugify('Hello World') // → 'hello-world'
|
|
20
|
+
* slugify('API Reference') // → 'api-reference'
|
|
21
|
+
* slugify('What is @miethe?') // → 'what-is-miethe'
|
|
22
|
+
*/
|
|
23
|
+
export declare function slugify(text: string): string;
|
|
24
|
+
//# sourceMappingURL=slugify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slugify.d.ts","sourceRoot":"","sources":["../../../../src/components/content-viewer/plugins/slugify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO5C"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub-compatible heading slug utility.
|
|
3
|
+
*
|
|
4
|
+
* Exported separately from rehypeHeadingIds so consumers can:
|
|
5
|
+
* 1. Import slugify without pulling in the full hast/unist dependency graph.
|
|
6
|
+
* 2. Test the slug algorithm in Jest without ESM transformation issues.
|
|
7
|
+
*
|
|
8
|
+
* Algorithm:
|
|
9
|
+
* - Lowercase the text
|
|
10
|
+
* - Remove characters that are not word chars ([\w]), hyphens, or spaces
|
|
11
|
+
* - Replace whitespace runs with a single hyphen
|
|
12
|
+
* - Collapse multiple consecutive hyphens
|
|
13
|
+
* - Strip leading and trailing hyphens
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Produce a GitHub-compatible URL slug from a heading text string.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* slugify('Hello World') // → 'hello-world'
|
|
20
|
+
* slugify('API Reference') // → 'api-reference'
|
|
21
|
+
* slugify('What is @miethe?') // → 'what-is-miethe'
|
|
22
|
+
*/
|
|
23
|
+
export function slugify(text) {
|
|
24
|
+
return text
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[^\w\s-]/g, '') // remove non-word, non-space, non-hyphen chars
|
|
27
|
+
.replace(/[\s]+/g, '-') // spaces → hyphens
|
|
28
|
+
.replace(/-{2,}/g, '-') // collapse multiple consecutive hyphens
|
|
29
|
+
.replace(/^-+|-+$/g, ''); // strip leading/trailing hyphens
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=slugify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slugify.js","sourceRoot":"","sources":["../../../../src/components/content-viewer/plugins/slugify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAG,+CAA+C;SAC1E,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAM,mBAAmB;SAC/C,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAM,wCAAwC;SACpE,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAI,iCAAiC;AAClE,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML sanitization for ArticleViewer — PU3-02 / PU3-03
|
|
3
|
+
*
|
|
4
|
+
* Two sanitization paths, selected via the `useDOMPurify` prop:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Default (rehype-sanitize)** — unified pipeline:
|
|
7
|
+
* `rehypeParse → rehypeExternalLinks → rehypeSanitize(schema) → rehypeStringify`
|
|
8
|
+
* Uses GitHub's default allowlist (`defaultSchema`) with minor additions for
|
|
9
|
+
* callout div wrappers (class attr) and standard data attributes.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Optional (DOMPurify)** — dynamically imports `isomorphic-dompurify`.
|
|
12
|
+
* `isomorphic-dompurify` is NOT a hard dependency; it must be installed by the
|
|
13
|
+
* consumer. If unavailable, falls back to path 1 with a console warning.
|
|
14
|
+
*
|
|
15
|
+
* Both paths:
|
|
16
|
+
* - Strip `<script>`, event handler attributes (`on*`), `javascript:` and unsafe
|
|
17
|
+
* `data:` URLs, `<iframe>`, `<object>`, `<embed>`, `<form>`, `<base>`.
|
|
18
|
+
* - Preserve safe HTML: headings, paragraphs, lists, blockquotes, tables,
|
|
19
|
+
* `<code>`, `<pre>`, `<a>` (http/https/mailto href), `<img>` (http/https src),
|
|
20
|
+
* `<div>`, `<span>` (with class), `<strong>`, `<em>`, `<del>`, `<details>`,
|
|
21
|
+
* `<summary>`, `<figure>`, `<figcaption>`, `<hr>`, `<br>`.
|
|
22
|
+
* - Apply external link hardening (`target="_blank" rel="noopener noreferrer"`).
|
|
23
|
+
*
|
|
24
|
+
* ## Custom schema rationale
|
|
25
|
+
*
|
|
26
|
+
* `rehype-sanitize`'s `defaultSchema` (mirroring GitHub's allowlist) is strict
|
|
27
|
+
* enough for most use cases. We extend it to allow:
|
|
28
|
+
* - `class` on `div` and `span` — callout wrappers use `data-callout-type` and
|
|
29
|
+
* Tailwind utility classes.
|
|
30
|
+
* - `data-callout-type` on `div` — semantic attribute written by the remark
|
|
31
|
+
* callout plugin.
|
|
32
|
+
* - `id` on headings — anchor links in compiled wiki documents.
|
|
33
|
+
* - `rel` and `target` on `a` — preserved after the external-link plugin sets
|
|
34
|
+
* them (rehype-sanitize would otherwise strip `target`).
|
|
35
|
+
*
|
|
36
|
+
* ## Allowlist vs blocklist
|
|
37
|
+
*
|
|
38
|
+
* We use an **allowlist** (default-deny) approach — only explicitly listed tags
|
|
39
|
+
* and attributes pass through. This is safer than blocklist-based sanitization
|
|
40
|
+
* which requires enumerating every possible XSS vector.
|
|
41
|
+
*/
|
|
42
|
+
import type { Options as SanitizeSchema } from 'rehype-sanitize';
|
|
43
|
+
export declare const ARTICLE_VIEWER_SCHEMA: SanitizeSchema;
|
|
44
|
+
/**
|
|
45
|
+
* Sanitize an HTML string using `rehype-sanitize@6`.
|
|
46
|
+
*
|
|
47
|
+
* Pipeline: rehypeParse → rehypeExternalLinks → rehypeSanitize → rehypeStringify
|
|
48
|
+
*/
|
|
49
|
+
export declare function sanitizeWithRehype(html: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Sanitize an HTML string, preferring DOMPurify if available (async).
|
|
52
|
+
*
|
|
53
|
+
* On first call it may do a dynamic import; subsequent calls use the cache.
|
|
54
|
+
* If DOMPurify is unavailable, falls back to `sanitizeWithRehype`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function sanitizeWithDOMPurify(html: string): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Synchronously sanitize `html`.
|
|
59
|
+
*
|
|
60
|
+
* When `useDOMPurify=true` and DOMPurify has already been loaded (warm cache),
|
|
61
|
+
* uses DOMPurify. If the cache is cold (first render), falls back to
|
|
62
|
+
* rehype-sanitize synchronously and triggers the async load in the background
|
|
63
|
+
* so subsequent renders can use DOMPurify.
|
|
64
|
+
*/
|
|
65
|
+
export declare function sanitizeHtml(html: string, opts: {
|
|
66
|
+
useDOMPurify: boolean;
|
|
67
|
+
}): string;
|
|
68
|
+
/**
|
|
69
|
+
* Warm the DOMPurify cache. Call this once at app startup if you plan to use
|
|
70
|
+
* `useDOMPurify={true}` to avoid the rehype fallback on the first render.
|
|
71
|
+
*/
|
|
72
|
+
export declare function warmDOMPurifyCache(): Promise<void>;
|
|
73
|
+
/** Exposed for testing — resets the module-level load state */
|
|
74
|
+
export declare function _resetDOMPurifyCache(): void;
|
|
75
|
+
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../../src/components/content-viewer/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAMH,OAAO,KAAK,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAoCjE,eAAO,MAAM,qBAAqB,EAAE,cAiBnC,CAAC;AAmEF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGvD;AAqCD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBzE;AAMD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IAAE,YAAY,EAAE,OAAO,CAAA;CAAE,GAC9B,MAAM,CAuBR;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAExD;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
|