@miethe/ui 0.3.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 +51 -0
- package/README.md +776 -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/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/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/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/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 +16 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +9 -0
- package/dist/primitives/index.js.map +1 -1
- 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,252 @@
|
|
|
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 { unified } from 'unified';
|
|
43
|
+
import rehypeParse from 'rehype-parse';
|
|
44
|
+
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
|
45
|
+
import rehypeStringify from 'rehype-stringify';
|
|
46
|
+
import { rehypeExternalLinks } from './plugins/rehypeExternalLinks';
|
|
47
|
+
/**
|
|
48
|
+
* Cast `unified` to a simple callable so TypeScript doesn't balk at the
|
|
49
|
+
* complex overloaded union on `Processor.use()`. The actual runtime behaviour
|
|
50
|
+
* is identical — we just widen the type to avoid TS2349.
|
|
51
|
+
*/
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
const makeProcessor = unified;
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Custom schema — extends GitHub's defaultSchema for Portal HTML
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
/**
|
|
58
|
+
* Extended sanitization schema for Portal-compiled HTML.
|
|
59
|
+
*
|
|
60
|
+
* Extends `defaultSchema` (GitHub's allowlist) to preserve:
|
|
61
|
+
* - `class` on `div`, `span` — callout wrappers + utility classes
|
|
62
|
+
* - `data-callout-type` on `div` — semantic callout attribute
|
|
63
|
+
* - `id` on `h1`–`h6` — anchor link targets in wiki documents
|
|
64
|
+
* - `target` on `a` — set by the external-link plugin (`_blank`)
|
|
65
|
+
* - `rel` on `a` — set by the external-link plugin (`noopener noreferrer`)
|
|
66
|
+
*/
|
|
67
|
+
const defaultAttrs = (defaultSchema.attributes ?? {});
|
|
68
|
+
export const ARTICLE_VIEWER_SCHEMA = {
|
|
69
|
+
...defaultSchema,
|
|
70
|
+
attributes: {
|
|
71
|
+
...defaultAttrs,
|
|
72
|
+
// Allow class on div/span for callout wrappers and Tailwind utilities
|
|
73
|
+
div: [...(defaultAttrs['div'] ?? []), 'className', 'class', 'data-callout-type'],
|
|
74
|
+
span: [...(defaultAttrs['span'] ?? []), 'className', 'class'],
|
|
75
|
+
// Allow id on headings for anchor links
|
|
76
|
+
h1: [...(defaultAttrs['h1'] ?? []), 'id'],
|
|
77
|
+
h2: [...(defaultAttrs['h2'] ?? []), 'id'],
|
|
78
|
+
h3: [...(defaultAttrs['h3'] ?? []), 'id'],
|
|
79
|
+
h4: [...(defaultAttrs['h4'] ?? []), 'id'],
|
|
80
|
+
h5: [...(defaultAttrs['h5'] ?? []), 'id'],
|
|
81
|
+
h6: [...(defaultAttrs['h6'] ?? []), 'id'],
|
|
82
|
+
// Preserve target and rel set by rehypeExternalLinks (before sanitize runs)
|
|
83
|
+
a: [...(defaultAttrs['a'] ?? []), 'target', 'rel'],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// DOMPurify config (for the optional path)
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
/**
|
|
90
|
+
* DOMPurify config that mirrors our rehype-sanitize allowlist:
|
|
91
|
+
* - Permit safe tags only
|
|
92
|
+
* - Strip event handlers and javascript: / data: URLs
|
|
93
|
+
* - Preserve class, id, data-callout-type, target, rel
|
|
94
|
+
*/
|
|
95
|
+
const DOMPURIFY_CONFIG = {
|
|
96
|
+
USE_PROFILES: { html: true },
|
|
97
|
+
ALLOWED_TAGS: [
|
|
98
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
99
|
+
'p', 'br', 'hr',
|
|
100
|
+
'ul', 'ol', 'li',
|
|
101
|
+
'blockquote', 'pre', 'code',
|
|
102
|
+
'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td',
|
|
103
|
+
'a', 'img',
|
|
104
|
+
'strong', 'em', 'del', 's', 'u', 'sup', 'sub',
|
|
105
|
+
'div', 'span', 'section', 'article', 'aside',
|
|
106
|
+
'details', 'summary',
|
|
107
|
+
'figure', 'figcaption',
|
|
108
|
+
'dl', 'dt', 'dd',
|
|
109
|
+
'input', // task list checkboxes (type=checkbox, disabled)
|
|
110
|
+
],
|
|
111
|
+
ALLOWED_ATTR: [
|
|
112
|
+
'class', 'id', 'data-callout-type',
|
|
113
|
+
'href', 'src', 'alt', 'title',
|
|
114
|
+
'target', 'rel',
|
|
115
|
+
'type', 'checked', 'disabled', // for task list checkboxes
|
|
116
|
+
'colspan', 'rowspan', 'align',
|
|
117
|
+
'width', 'height',
|
|
118
|
+
'lang', 'dir',
|
|
119
|
+
],
|
|
120
|
+
FORBID_TAGS: ['script', 'style', 'iframe', 'frame', 'frameset', 'object', 'embed', 'form', 'base', 'meta', 'link'],
|
|
121
|
+
FORBID_ATTR: [],
|
|
122
|
+
ALLOW_DATA_ATTR: false,
|
|
123
|
+
FORCE_BODY: false,
|
|
124
|
+
};
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// rehype-sanitize pipeline (default path) — PU3-02
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
/**
|
|
129
|
+
* Build a unified processor for HTML sanitization.
|
|
130
|
+
*
|
|
131
|
+
* Pipeline:
|
|
132
|
+
* 1. `rehypeParse` — parse HTML fragment to hast
|
|
133
|
+
* 2. `rehypeExternalLinks` — add target/rel to external links
|
|
134
|
+
* 3. `rehypeSanitize(ARTICLE_VIEWER_SCHEMA)` — strip XSS vectors
|
|
135
|
+
* 4. `rehypeStringify` — serialize back to HTML string
|
|
136
|
+
*
|
|
137
|
+
* We build fresh processors rather than sharing one instance to avoid vfile
|
|
138
|
+
* state leaking between calls (rehype-parse attaches vfile metadata).
|
|
139
|
+
*/
|
|
140
|
+
function buildSanitizeProcessor() {
|
|
141
|
+
return makeProcessor()
|
|
142
|
+
.use(rehypeParse, { fragment: true })
|
|
143
|
+
.use(rehypeExternalLinks)
|
|
144
|
+
.use(rehypeSanitize, ARTICLE_VIEWER_SCHEMA)
|
|
145
|
+
.use(rehypeStringify);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Sanitize an HTML string using `rehype-sanitize@6`.
|
|
149
|
+
*
|
|
150
|
+
* Pipeline: rehypeParse → rehypeExternalLinks → rehypeSanitize → rehypeStringify
|
|
151
|
+
*/
|
|
152
|
+
export function sanitizeWithRehype(html) {
|
|
153
|
+
const processor = buildSanitizeProcessor();
|
|
154
|
+
return processor.processSync(html).toString();
|
|
155
|
+
}
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// DOMPurify optional path — PU3-03
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
/** Cache the dynamic import result so we only attempt it once per session */
|
|
160
|
+
let domPurifyModule = null;
|
|
161
|
+
let domPurifyLoadAttempted = false;
|
|
162
|
+
/**
|
|
163
|
+
* Attempt to load `isomorphic-dompurify` dynamically.
|
|
164
|
+
* Returns the module if available; `null` if not installed.
|
|
165
|
+
*/
|
|
166
|
+
async function tryLoadDOMPurify() {
|
|
167
|
+
if (domPurifyLoadAttempted)
|
|
168
|
+
return domPurifyModule;
|
|
169
|
+
domPurifyLoadAttempted = true;
|
|
170
|
+
try {
|
|
171
|
+
// Dynamic import — isomorphic-dompurify is an optional peer dep with no bundled types.
|
|
172
|
+
// @ts-expect-error — optional package; not in node_modules at build time
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
|
+
const mod = await import(/* webpackIgnore: true */ 'isomorphic-dompurify');
|
|
175
|
+
const dp = mod.default ?? mod;
|
|
176
|
+
if (typeof dp?.sanitize === 'function') {
|
|
177
|
+
domPurifyModule = dp;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.warn('[ArticleViewer] isomorphic-dompurify loaded but has unexpected shape; falling back to rehype-sanitize');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Package not installed — expected; fallback handled at call site
|
|
185
|
+
domPurifyModule = null;
|
|
186
|
+
}
|
|
187
|
+
return domPurifyModule;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Sanitize an HTML string, preferring DOMPurify if available (async).
|
|
191
|
+
*
|
|
192
|
+
* On first call it may do a dynamic import; subsequent calls use the cache.
|
|
193
|
+
* If DOMPurify is unavailable, falls back to `sanitizeWithRehype`.
|
|
194
|
+
*/
|
|
195
|
+
export async function sanitizeWithDOMPurify(html) {
|
|
196
|
+
const dp = await tryLoadDOMPurify();
|
|
197
|
+
if (!dp) {
|
|
198
|
+
console.warn('[ArticleViewer] useDOMPurify=true requested but isomorphic-dompurify is not installed. ' +
|
|
199
|
+
'Falling back to rehype-sanitize. ' +
|
|
200
|
+
'Install isomorphic-dompurify to use the DOMPurify path: npm install isomorphic-dompurify');
|
|
201
|
+
return sanitizeWithRehype(html);
|
|
202
|
+
}
|
|
203
|
+
// Apply external link hardening before handing to DOMPurify
|
|
204
|
+
const withLinks = makeProcessor()
|
|
205
|
+
.use(rehypeParse, { fragment: true })
|
|
206
|
+
.use(rehypeExternalLinks)
|
|
207
|
+
.use(rehypeStringify)
|
|
208
|
+
.processSync(html);
|
|
209
|
+
return dp.sanitize(withLinks.toString(), DOMPURIFY_CONFIG);
|
|
210
|
+
}
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Synchronous sanitize dispatcher — used by the component render path
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
/**
|
|
215
|
+
* Synchronously sanitize `html`.
|
|
216
|
+
*
|
|
217
|
+
* When `useDOMPurify=true` and DOMPurify has already been loaded (warm cache),
|
|
218
|
+
* uses DOMPurify. If the cache is cold (first render), falls back to
|
|
219
|
+
* rehype-sanitize synchronously and triggers the async load in the background
|
|
220
|
+
* so subsequent renders can use DOMPurify.
|
|
221
|
+
*/
|
|
222
|
+
export function sanitizeHtml(html, opts) {
|
|
223
|
+
if (opts.useDOMPurify) {
|
|
224
|
+
if (domPurifyModule) {
|
|
225
|
+
// Warm cache — use DOMPurify synchronously
|
|
226
|
+
const withLinks = makeProcessor()
|
|
227
|
+
.use(rehypeParse, { fragment: true })
|
|
228
|
+
.use(rehypeExternalLinks)
|
|
229
|
+
.use(rehypeStringify)
|
|
230
|
+
.processSync(html);
|
|
231
|
+
return domPurifyModule.sanitize(withLinks.toString(), DOMPURIFY_CONFIG);
|
|
232
|
+
}
|
|
233
|
+
// Cold cache — kick off the async load and fall through to rehype for this render
|
|
234
|
+
if (!domPurifyLoadAttempted) {
|
|
235
|
+
void tryLoadDOMPurify();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return sanitizeWithRehype(html);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Warm the DOMPurify cache. Call this once at app startup if you plan to use
|
|
242
|
+
* `useDOMPurify={true}` to avoid the rehype fallback on the first render.
|
|
243
|
+
*/
|
|
244
|
+
export async function warmDOMPurifyCache() {
|
|
245
|
+
await tryLoadDOMPurify();
|
|
246
|
+
}
|
|
247
|
+
/** Exposed for testing — resets the module-level load state */
|
|
248
|
+
export function _resetDOMPurifyCache() {
|
|
249
|
+
domPurifyModule = null;
|
|
250
|
+
domPurifyLoadAttempted = false;
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../src/components/content-viewer/sanitize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @miethe/ui — ArticleViewer type definitions
|
|
3
|
+
*
|
|
4
|
+
* All public types exported from the content-viewer component.
|
|
5
|
+
* No `any` types at module boundaries.
|
|
6
|
+
*/
|
|
7
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
8
|
+
/**
|
|
9
|
+
* Typography variant applied to the ArticleViewer.
|
|
10
|
+
*
|
|
11
|
+
* Each variant maps to a CSS class (`cv-variant-{name}`) that expects
|
|
12
|
+
* CSS custom properties at document root. Consumers (e.g., Portal) define
|
|
13
|
+
* the actual values in their `globals.css`; the component only applies
|
|
14
|
+
* the class. See the CSS-Variable Contract in the content-viewer README.
|
|
15
|
+
*
|
|
16
|
+
* - `"editorial"` — long-form reading; display serif headings, generous line-height
|
|
17
|
+
* - `"compact"` — dense information display; smaller type scale, tighter spacing
|
|
18
|
+
* - `"technical"` — code-heavy content; monospace body available, clear heading hierarchy
|
|
19
|
+
*/
|
|
20
|
+
export type ArticleVariant = 'editorial' | 'compact' | 'technical';
|
|
21
|
+
/**
|
|
22
|
+
* Shape of the CSS-variable slot names for a single variant.
|
|
23
|
+
* Used by the variant utility to document and validate the contract.
|
|
24
|
+
* All slots are optional — missing variables fall back to browser defaults.
|
|
25
|
+
*/
|
|
26
|
+
export interface VariantTokenShape {
|
|
27
|
+
/** CSS var for h1 font family, e.g. `--cv-editorial-h1-font` */
|
|
28
|
+
h1Font: string;
|
|
29
|
+
/** CSS var for h1 font size, e.g. `--cv-editorial-h1-size` */
|
|
30
|
+
h1Size: string;
|
|
31
|
+
/** CSS var for h2 font family */
|
|
32
|
+
h2Font: string;
|
|
33
|
+
/** CSS var for h2 font size */
|
|
34
|
+
h2Size: string;
|
|
35
|
+
/** CSS var for body font family */
|
|
36
|
+
bodyFont: string;
|
|
37
|
+
/** CSS var for body font size */
|
|
38
|
+
bodySize: string;
|
|
39
|
+
/** CSS var for body line-height */
|
|
40
|
+
bodyLineHeight: string;
|
|
41
|
+
/** CSS var for blockquote text color */
|
|
42
|
+
quoteColor: string;
|
|
43
|
+
/** CSS var for blockquote font-style (e.g. "italic") */
|
|
44
|
+
quoteFontStyle: string;
|
|
45
|
+
/** CSS var for `note` callout accent color */
|
|
46
|
+
calloutNoteAccent: string;
|
|
47
|
+
/** CSS var for `note` callout background */
|
|
48
|
+
calloutNoteBg: string;
|
|
49
|
+
/** CSS var for `reference` callout accent color */
|
|
50
|
+
calloutReferenceAccent: string;
|
|
51
|
+
/** CSS var for `reference` callout background */
|
|
52
|
+
calloutReferenceBg: string;
|
|
53
|
+
/** CSS var for `warning` callout accent color */
|
|
54
|
+
calloutWarningAccent: string;
|
|
55
|
+
/** CSS var for `warning` callout background */
|
|
56
|
+
calloutWarningBg: string;
|
|
57
|
+
/** CSS var for `info` callout accent color */
|
|
58
|
+
calloutInfoAccent: string;
|
|
59
|
+
/** CSS var for `info` callout background */
|
|
60
|
+
calloutInfoBg: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Visibility mode for the frontmatter header.
|
|
64
|
+
*
|
|
65
|
+
* - `"show"` — render FrontmatterHeader in expanded state
|
|
66
|
+
* - `"collapse"` — render FrontmatterHeader in collapsed state
|
|
67
|
+
* - `"hide"` — omit FrontmatterHeader entirely (default)
|
|
68
|
+
*/
|
|
69
|
+
export type FrontmatterDisplayMode = 'show' | 'collapse' | 'hide';
|
|
70
|
+
/**
|
|
71
|
+
* Parsed YAML frontmatter object extracted from the markdown content.
|
|
72
|
+
* Values are typed as `unknown` at the boundary; use type guards before rendering.
|
|
73
|
+
*/
|
|
74
|
+
export type FrontmatterData = Record<string, unknown>;
|
|
75
|
+
/**
|
|
76
|
+
* Props for the FrontmatterHeader component.
|
|
77
|
+
*/
|
|
78
|
+
export interface FrontmatterHeaderProps {
|
|
79
|
+
/**
|
|
80
|
+
* Parsed YAML frontmatter key-value map.
|
|
81
|
+
* 1-level nesting is rendered as indented rows; deeper objects appear as JSON strings.
|
|
82
|
+
*/
|
|
83
|
+
frontmatter: FrontmatterData;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the header starts in collapsed state.
|
|
86
|
+
* @default false
|
|
87
|
+
*/
|
|
88
|
+
isCollapsed?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Callback invoked when the user toggles the collapsed state.
|
|
91
|
+
* Receives the new collapsed value (`true` = collapsed).
|
|
92
|
+
*/
|
|
93
|
+
onToggleCollapse?: (collapsed: boolean) => void;
|
|
94
|
+
/** Additional CSS class names */
|
|
95
|
+
className?: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Custom component overrides for ArticleViewer sub-components.
|
|
99
|
+
* All keys are optional — omitting a key uses the default implementation.
|
|
100
|
+
*/
|
|
101
|
+
export interface ArticleViewerComponents {
|
|
102
|
+
/**
|
|
103
|
+
* Override for `note` callout components.
|
|
104
|
+
*/
|
|
105
|
+
note?: ComponentType<CalloutProps>;
|
|
106
|
+
/**
|
|
107
|
+
* Override for `reference` callout components.
|
|
108
|
+
*/
|
|
109
|
+
reference?: ComponentType<CalloutProps>;
|
|
110
|
+
/**
|
|
111
|
+
* Override for `warning` callout components.
|
|
112
|
+
*/
|
|
113
|
+
warning?: ComponentType<CalloutProps>;
|
|
114
|
+
/**
|
|
115
|
+
* Override for `info` callout components.
|
|
116
|
+
*/
|
|
117
|
+
info?: ComponentType<CalloutProps>;
|
|
118
|
+
/**
|
|
119
|
+
* Override for the FrontmatterHeader component rendered above article content.
|
|
120
|
+
* Receives the same `FrontmatterHeaderProps` as the default implementation.
|
|
121
|
+
*/
|
|
122
|
+
FrontmatterHeader?: ComponentType<FrontmatterHeaderProps>;
|
|
123
|
+
}
|
|
124
|
+
/** Supported callout directive types */
|
|
125
|
+
export type CalloutType = 'note' | 'reference' | 'warning' | 'info';
|
|
126
|
+
/** Props passed to each callout component */
|
|
127
|
+
export interface CalloutProps {
|
|
128
|
+
/** The directive type (note, reference, warning, info) */
|
|
129
|
+
type: CalloutType;
|
|
130
|
+
/** Rendered markdown content inside the callout */
|
|
131
|
+
children?: ReactNode;
|
|
132
|
+
/** Additional CSS class names */
|
|
133
|
+
className?: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Format of the incoming content string.
|
|
137
|
+
* - `"markdown"`: Always treat as markdown (render via remark pipeline)
|
|
138
|
+
* - `"html"`: Treat as pre-rendered HTML (renders as-is with dangerouslySetInnerHTML)
|
|
139
|
+
* - `"auto"`: Detect based on content heuristics (default)
|
|
140
|
+
*/
|
|
141
|
+
export type ContentFormat = 'markdown' | 'html' | 'auto';
|
|
142
|
+
/**
|
|
143
|
+
* Map of callout type → custom component.
|
|
144
|
+
* Allows Portal (and other consumers) to override default callout renderers.
|
|
145
|
+
*
|
|
146
|
+
* @deprecated Prefer `ArticleViewerComponents` which also supports `FrontmatterHeader` override.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```tsx
|
|
150
|
+
* <ArticleViewer
|
|
151
|
+
* content={markdown}
|
|
152
|
+
* components={{
|
|
153
|
+
* note: MyCustomNoteCallout,
|
|
154
|
+
* warning: MyWarningBanner,
|
|
155
|
+
* }}
|
|
156
|
+
* />
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export type CalloutComponents = Partial<Record<CalloutType, ComponentType<CalloutProps>>>;
|
|
160
|
+
/**
|
|
161
|
+
* Props for the ArticleViewer component.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```tsx
|
|
165
|
+
* <ArticleViewer
|
|
166
|
+
* content="# Hello\n\n::: note\nThis is a note\n:::"
|
|
167
|
+
* format="markdown"
|
|
168
|
+
* variant="editorial"
|
|
169
|
+
* frontmatter="show"
|
|
170
|
+
* onError={(err) => console.error(err)}
|
|
171
|
+
* />
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export interface ArticleViewerProps {
|
|
175
|
+
/**
|
|
176
|
+
* The raw content string to render.
|
|
177
|
+
* For markdown, the full remark pipeline (GFM + directives) is applied.
|
|
178
|
+
* YAML frontmatter (if present) is extracted and not rendered as content.
|
|
179
|
+
*/
|
|
180
|
+
content: string;
|
|
181
|
+
/**
|
|
182
|
+
* Format of the content. Defaults to `"auto"` which detects markdown
|
|
183
|
+
* by looking for common markdown patterns.
|
|
184
|
+
* @default "auto"
|
|
185
|
+
*/
|
|
186
|
+
format?: ContentFormat;
|
|
187
|
+
/**
|
|
188
|
+
* Typography variant. Applies a CSS class (`cv-variant-{name}`) to the root
|
|
189
|
+
* element. Each variant relies on CSS custom properties at document root.
|
|
190
|
+
* See the CSS-Variable Contract in the content-viewer README for the full
|
|
191
|
+
* list of expected `--cv-{variant}-*` variables.
|
|
192
|
+
* @default undefined (no variant class applied)
|
|
193
|
+
*/
|
|
194
|
+
variant?: ArticleVariant;
|
|
195
|
+
/**
|
|
196
|
+
* Controls visibility of the YAML frontmatter header above article content.
|
|
197
|
+
* - `"show"` — header rendered, expanded by default
|
|
198
|
+
* - `"collapse"` — header rendered, collapsed by default
|
|
199
|
+
* - `"hide"` — header omitted entirely
|
|
200
|
+
* Has no effect if the content has no frontmatter.
|
|
201
|
+
* @default "hide"
|
|
202
|
+
*/
|
|
203
|
+
frontmatter?: FrontmatterDisplayMode;
|
|
204
|
+
/**
|
|
205
|
+
* Override map for sub-components rendered by ArticleViewer.
|
|
206
|
+
* - Callout keys (`note`, `reference`, `warning`, `info`): override default callout renderers
|
|
207
|
+
* - `FrontmatterHeader`: override the default frontmatter header component
|
|
208
|
+
*
|
|
209
|
+
* Any key not provided falls back to the default implementation.
|
|
210
|
+
*/
|
|
211
|
+
components?: ArticleViewerComponents;
|
|
212
|
+
/**
|
|
213
|
+
* @deprecated Use `components` instead. Kept for single-component override
|
|
214
|
+
* compatibility. If `components.note` etc. are also provided, those take precedence.
|
|
215
|
+
*/
|
|
216
|
+
calloutComponent?: ComponentType<CalloutProps>;
|
|
217
|
+
/**
|
|
218
|
+
* Whether to sanitize HTML content (applies to `format="html"` and auto-detected HTML).
|
|
219
|
+
*
|
|
220
|
+
* When `true` (default for HTML input), `rehype-sanitize@6` strips XSS vectors:
|
|
221
|
+
* `<script>`, event handlers (`onclick`, `onerror`, …), `javascript:` and unsafe
|
|
222
|
+
* `data:` URLs, `<iframe>`, `<object>`, `<embed>`, `<svg>` containing scripts,
|
|
223
|
+
* and CSS `expression()` / `javascript:` values.
|
|
224
|
+
*
|
|
225
|
+
* Set to `false` **only** when the HTML source is fully trusted (e.g. content
|
|
226
|
+
* compiled by the MeatyWiki engine through the Portal's controlled pipeline).
|
|
227
|
+
* Never set `false` for user-supplied or third-party content.
|
|
228
|
+
*
|
|
229
|
+
* Has no effect on markdown input (the remark pipeline never emits raw HTML
|
|
230
|
+
* unless `allowDangerousHtml` is explicitly set, which this component does not do).
|
|
231
|
+
*
|
|
232
|
+
* @default true (for format="html" / auto-detected HTML), false (for format="markdown")
|
|
233
|
+
*/
|
|
234
|
+
sanitize?: boolean;
|
|
235
|
+
/**
|
|
236
|
+
* Whether to use `isomorphic-dompurify` as the HTML sanitizer instead of the
|
|
237
|
+
* default `rehype-sanitize`.
|
|
238
|
+
*
|
|
239
|
+
* `isomorphic-dompurify` is an **optional peer dependency** — it is NOT bundled
|
|
240
|
+
* with `@miethe/ui`. Consumers that want this path must install it separately:
|
|
241
|
+
* ```
|
|
242
|
+
* npm install isomorphic-dompurify
|
|
243
|
+
* ```
|
|
244
|
+
* If the package is unavailable at runtime, ArticleViewer logs a warning and
|
|
245
|
+
* falls back to `rehype-sanitize`. The `sanitize` prop must also be `true`
|
|
246
|
+
* for this prop to have any effect.
|
|
247
|
+
*
|
|
248
|
+
* @default false
|
|
249
|
+
*/
|
|
250
|
+
useDOMPurify?: boolean;
|
|
251
|
+
/**
|
|
252
|
+
* Enable opt-in syntax highlighting for fenced code blocks.
|
|
253
|
+
*
|
|
254
|
+
* When `false` (default), code blocks render as styled monospace plain text —
|
|
255
|
+
* zero additional bundle cost.
|
|
256
|
+
*
|
|
257
|
+
* When `true`, the component dynamically imports `lowlight` on first use and
|
|
258
|
+
* applies highlight.js-compatible hast transformations to `pre > code` blocks.
|
|
259
|
+
* `lowlight` is an **optional peer dependency** (~15KB gzip); install separately:
|
|
260
|
+
* ```
|
|
261
|
+
* npm install lowlight
|
|
262
|
+
* ```
|
|
263
|
+
* On the first render with a cold cache the highlighter loads asynchronously;
|
|
264
|
+
* code renders as plain text until the next render. Call `warmHighlightCache()`
|
|
265
|
+
* at app startup to eliminate this delay.
|
|
266
|
+
*
|
|
267
|
+
* To style highlighted code, add a highlight.js CSS theme in your app:
|
|
268
|
+
* ```
|
|
269
|
+
* import 'highlight.js/styles/github.css';
|
|
270
|
+
* ```
|
|
271
|
+
*
|
|
272
|
+
* @default false
|
|
273
|
+
*/
|
|
274
|
+
codeHighlight?: boolean;
|
|
275
|
+
/**
|
|
276
|
+
* Automatically generate `id` attributes on heading elements (h1–h6)
|
|
277
|
+
* using a GitHub-compatible slug algorithm.
|
|
278
|
+
*
|
|
279
|
+
* Generated IDs allow in-page anchor navigation (`#section-title`).
|
|
280
|
+
* The `id` attribute is preserved by the sanitization schema for HTML input.
|
|
281
|
+
*
|
|
282
|
+
* When `false`, headings are rendered without `id` attributes.
|
|
283
|
+
*
|
|
284
|
+
* @default true
|
|
285
|
+
*/
|
|
286
|
+
generateHeadingIds?: boolean;
|
|
287
|
+
/**
|
|
288
|
+
* When `true`, suppress all content rendering and show an accessible
|
|
289
|
+
* skeleton/placeholder instead. The `onError` callback is NOT invoked
|
|
290
|
+
* while loading is active.
|
|
291
|
+
*
|
|
292
|
+
* @default false
|
|
293
|
+
*/
|
|
294
|
+
isLoading?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Render an accessible error message instead of content.
|
|
297
|
+
* Accepts either a `string` message or an `Error` object.
|
|
298
|
+
*
|
|
299
|
+
* Priority: `error` prop > error boundary caught error > normal render.
|
|
300
|
+
* Does not crash the component; the error boundary still catches child throws.
|
|
301
|
+
*
|
|
302
|
+
* @default undefined
|
|
303
|
+
*/
|
|
304
|
+
error?: string | Error | null;
|
|
305
|
+
/**
|
|
306
|
+
* Callback invoked when a rendering error occurs.
|
|
307
|
+
* Receives the caught Error object.
|
|
308
|
+
*/
|
|
309
|
+
onError?: (error: Error) => void;
|
|
310
|
+
/**
|
|
311
|
+
* Additional CSS class names applied to the root wrapper element.
|
|
312
|
+
*/
|
|
313
|
+
className?: string;
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/content-viewer/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMtD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAEnE;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iDAAiD;IACjD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,+CAA+C;IAC/C,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;CACvB;AAMD;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AAElE;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,WAAW,EAAE,eAAe,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACnC;;OAEG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACxC;;OAEG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACtC;;OAEG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACnC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;CAC3D;AAMD,wCAAwC;AACxC,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpE,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,IAAI,EAAE,WAAW,CAAC;IAClB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AAE1F;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IAEvB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,sBAAsB,CAAC;IAErC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,uBAAuB,CAAC;IAErC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IAE/C;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;IAE9B;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/content-viewer/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @miethe/ui — ArticleViewer CSS-variable variant system (PU2-05)
|
|
3
|
+
*
|
|
4
|
+
* Maps `ArticleVariant` values to Tailwind-compatible CSS class names.
|
|
5
|
+
* Each class is expected to read CSS custom properties from document root,
|
|
6
|
+
* which the consumer (e.g., Portal `globals.css`) must define.
|
|
7
|
+
*
|
|
8
|
+
* ## CSS-Variable Contract
|
|
9
|
+
*
|
|
10
|
+
* When variant="editorial", apply the class `cv-variant-editorial` and
|
|
11
|
+
* define these variables at `:root` (or a suitable ancestor):
|
|
12
|
+
*
|
|
13
|
+
* ```css
|
|
14
|
+
* :root {
|
|
15
|
+
* --cv-editorial-h1-font: Fraunces, Georgia, serif;
|
|
16
|
+
* --cv-editorial-h1-size: 2.25rem;
|
|
17
|
+
* --cv-editorial-h2-font: Fraunces, Georgia, serif;
|
|
18
|
+
* --cv-editorial-h2-size: 1.875rem;
|
|
19
|
+
* --cv-editorial-body-font: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
20
|
+
* --cv-editorial-body-size: 1rem;
|
|
21
|
+
* --cv-editorial-body-line-height: 1.75;
|
|
22
|
+
* --cv-editorial-quote-color: #64748b;
|
|
23
|
+
* --cv-editorial-quote-font-style: italic;
|
|
24
|
+
* --cv-callout-note-accent: #0ea5e9;
|
|
25
|
+
* --cv-callout-note-bg: #f0f9ff;
|
|
26
|
+
* --cv-callout-reference-accent: #64748b;
|
|
27
|
+
* --cv-callout-reference-bg: #f1f5f9;
|
|
28
|
+
* --cv-callout-warning-accent: #f59e0b;
|
|
29
|
+
* --cv-callout-warning-bg: #fffbeb;
|
|
30
|
+
* --cv-callout-info-accent: #0ea5e9;
|
|
31
|
+
* --cv-callout-info-bg: #f0f9ff;
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Analogous `--cv-compact-*` and `--cv-technical-*` sets follow the same pattern.
|
|
36
|
+
* Missing variables are silently ignored — browser defaults apply.
|
|
37
|
+
*
|
|
38
|
+
* ## Light / Dark Mode
|
|
39
|
+
*
|
|
40
|
+
* Variables should be declared inside appropriate selectors at the consumer side:
|
|
41
|
+
*
|
|
42
|
+
* ```css
|
|
43
|
+
* :root { --cv-editorial-body-font: "Libre Baskerville", serif; }
|
|
44
|
+
* .dark { --cv-editorial-body-font: "Libre Baskerville", serif; }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* The component itself applies no color values — all theming is delegated to the consumer.
|
|
48
|
+
*/
|
|
49
|
+
import type { ArticleVariant, VariantTokenShape } from './types';
|
|
50
|
+
/**
|
|
51
|
+
* Mapping from `ArticleVariant` → the CSS class applied to the root element.
|
|
52
|
+
* The class is expected to read CSS custom properties from document root.
|
|
53
|
+
*/
|
|
54
|
+
export declare const VARIANT_CLASSES: Record<ArticleVariant, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the CSS custom property names expected for a given variant.
|
|
57
|
+
* Use this as documentation / tooling support — it does not read or set variables.
|
|
58
|
+
*
|
|
59
|
+
* @param variant - The `ArticleVariant` to query
|
|
60
|
+
* @returns An object whose values are the expected `--cv-*` variable names
|
|
61
|
+
*/
|
|
62
|
+
export declare function getVariantTokenNames(variant: ArticleVariant): VariantTokenShape;
|
|
63
|
+
/**
|
|
64
|
+
* Returns the CSS class name to apply for a given variant,
|
|
65
|
+
* or `undefined` when no variant is specified.
|
|
66
|
+
*
|
|
67
|
+
* @param variant - Optional `ArticleVariant`
|
|
68
|
+
* @returns CSS class string, or `undefined`
|
|
69
|
+
*/
|
|
70
|
+
export declare function variantClass(variant: ArticleVariant | undefined): string | undefined;
|
|
71
|
+
//# sourceMappingURL=variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variants.d.ts","sourceRoot":"","sources":["../../../src/components/content-viewer/variants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAMjE;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAI1D,CAAC;AAMF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,iBAAiB,CAqB/E;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGpF"}
|