@marianmeres/stuic 3.135.0 → 3.136.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/dist/components/CommentInput/CommentInput.svelte +186 -348
- package/dist/components/CommentInput/CommentInput.svelte.d.ts +42 -44
- package/dist/components/CommentInput/README.md +104 -77
- package/dist/components/CommentInput/index.css +29 -259
- package/dist/components/CommentInput/index.d.ts +1 -2
- package/dist/components/CommentInput/index.js +1 -1
- package/dist/components/MarkdownEditor/MarkdownEditor.svelte +10 -0
- package/dist/components/MarkdownEditor/MarkdownEditor.svelte.d.ts +1 -0
- package/package.json +1 -1
- package/dist/components/CommentInput/_internal/render.d.ts +0 -24
- package/dist/components/CommentInput/_internal/render.js +0 -47
|
@@ -1,36 +1,23 @@
|
|
|
1
1
|
/******************************************************************************
|
|
2
2
|
CommentInput
|
|
3
3
|
|
|
4
|
-
A GitHub-style comment
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
A GitHub-style comment composer built on top of `MarkdownEditor` (a rich
|
|
5
|
+
WYSIWYG / source surface) with an avatar gutter and a submit/cancel footer.
|
|
6
|
+
|
|
7
|
+
The editor card, formatting toolbar, mode toggle and field chrome
|
|
8
|
+
(label / description / validation / sizing / theming) all come from
|
|
9
|
+
`MarkdownEditor` and its shared `InputWrap` — so the box inherits the
|
|
10
|
+
`--stuic-markdown-editor-*` / `--stuic-input-*` theming for free. This
|
|
11
|
+
stylesheet only lays out the gutter + footer and tightens the spacing so the
|
|
12
|
+
editor and its buttons read as a single composer unit.
|
|
12
13
|
******************************************************************************/
|
|
13
14
|
|
|
14
15
|
.stuic-comment-input {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
--
|
|
20
|
-
--stuic-comment-input-border,
|
|
21
|
-
var(--stuic-input-border, currentColor)
|
|
22
|
-
);
|
|
23
|
-
--_bg: var(--stuic-comment-input-bg, var(--stuic-input-bg, transparent));
|
|
24
|
-
--_pad-x: var(
|
|
25
|
-
--stuic-comment-input-padding-x,
|
|
26
|
-
var(--stuic-input-padding-x-md, 0.75rem)
|
|
27
|
-
);
|
|
28
|
-
--_pad-y: var(--stuic-comment-input-padding-y, var(--stuic-input-padding-y-md, 0.5rem));
|
|
29
|
-
--_min-height: var(--stuic-comment-input-min-height, 5rem);
|
|
30
|
-
--_font-size: var(
|
|
31
|
-
--stuic-comment-input-font-size,
|
|
32
|
-
var(--stuic-input-font-size-md, 0.9375rem)
|
|
33
|
-
);
|
|
16
|
+
/* The embedded MarkdownEditor defaults to a tall editing surface (12rem md);
|
|
17
|
+
for a comment composer we want the lighter, GitHub-like proportions of the
|
|
18
|
+
original box. Funnel a comment-scoped token into the editor's min-height var
|
|
19
|
+
so it stays themeable (and still overridable via the editor token directly). */
|
|
20
|
+
--stuic-markdown-editor-min-height: var(--stuic-comment-input-min-height, 5rem);
|
|
34
21
|
|
|
35
22
|
display: flex;
|
|
36
23
|
align-items: flex-start;
|
|
@@ -39,31 +26,24 @@
|
|
|
39
26
|
min-width: 0;
|
|
40
27
|
}
|
|
41
28
|
|
|
29
|
+
.stuic-comment-input[data-size="sm"] {
|
|
30
|
+
--stuic-comment-input-min-height: 4rem;
|
|
31
|
+
}
|
|
32
|
+
.stuic-comment-input[data-size="lg"] {
|
|
33
|
+
--stuic-comment-input-min-height: 7rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
42
36
|
.stuic-comment-input-avatar {
|
|
43
37
|
flex: 0 0 auto;
|
|
44
38
|
padding-top: 0.125rem;
|
|
45
39
|
}
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
.stuic-comment-input-editor {
|
|
41
|
+
.stuic-comment-input-main {
|
|
49
42
|
display: flex;
|
|
50
43
|
flex-direction: column;
|
|
51
44
|
flex: 1 1 auto;
|
|
52
45
|
min-width: 0;
|
|
53
46
|
max-width: 100%;
|
|
54
|
-
background: var(--_bg);
|
|
55
|
-
border: var(--stuic-border-width, 1px) solid var(--_border-color);
|
|
56
|
-
border-radius: var(--_radius);
|
|
57
|
-
color: var(--stuic-input-text, inherit);
|
|
58
|
-
overflow: hidden;
|
|
59
|
-
transition: border-color var(--stuic-transition, 150ms);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.stuic-comment-input-editor:focus-within {
|
|
63
|
-
border-color: var(
|
|
64
|
-
--stuic-comment-input-border-focus,
|
|
65
|
-
var(--stuic-input-border-focus, currentColor)
|
|
66
|
-
);
|
|
67
47
|
}
|
|
68
48
|
|
|
69
49
|
.stuic-comment-input.disabled {
|
|
@@ -71,196 +51,12 @@
|
|
|
71
51
|
pointer-events: none;
|
|
72
52
|
}
|
|
73
53
|
|
|
74
|
-
/*
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
padding: 0.25rem 0.375rem;
|
|
81
|
-
background: var(--stuic-comment-input-bar-bg, transparent);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.stuic-comment-input-tabs {
|
|
85
|
-
display: flex;
|
|
86
|
-
align-items: center;
|
|
87
|
-
gap: 0.125rem;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.stuic-comment-input-tabs button {
|
|
91
|
-
display: inline-flex;
|
|
92
|
-
align-items: center;
|
|
93
|
-
justify-content: center;
|
|
94
|
-
height: 2rem;
|
|
95
|
-
padding: 0 0.625rem;
|
|
96
|
-
font-size: 0.8125rem;
|
|
97
|
-
line-height: 1;
|
|
98
|
-
color: inherit;
|
|
99
|
-
opacity: 0.7;
|
|
100
|
-
background: transparent;
|
|
101
|
-
border: none;
|
|
102
|
-
border-radius: var(--stuic-radius, 0.375rem);
|
|
103
|
-
transition:
|
|
104
|
-
background-color var(--stuic-transition, 150ms),
|
|
105
|
-
opacity var(--stuic-transition, 150ms);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.stuic-comment-input-tabs button:hover:not(:disabled) {
|
|
109
|
-
background: var(--stuic-comment-input-tab-hover-bg, rgb(0 0 0 / 0.06));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.stuic-comment-input-tabs button[data-active="true"] {
|
|
113
|
-
opacity: 1;
|
|
114
|
-
font-weight: 600;
|
|
115
|
-
background: var(--stuic-comment-input-tab-active-bg, rgb(0 0 0 / 0.06));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.stuic-comment-input-tabs button:disabled {
|
|
119
|
-
opacity: 0.4;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.stuic-comment-input-hint {
|
|
123
|
-
font-size: 0.75rem;
|
|
124
|
-
opacity: 0.5;
|
|
125
|
-
padding-right: 0.25rem;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
:root.dark .stuic-comment-input-tabs button:hover:not(:disabled),
|
|
129
|
-
:root.dark .stuic-comment-input-tabs button[data-active="true"] {
|
|
130
|
-
background: var(--stuic-comment-input-tab-active-bg, rgb(255 255 255 / 0.08));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
@media (pointer: coarse) {
|
|
134
|
-
.stuic-comment-input-tabs button {
|
|
135
|
-
height: 2.5rem;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/* --- Surface (textarea + preview share the same box) ---------------------- */
|
|
140
|
-
.stuic-comment-input-surface {
|
|
141
|
-
min-width: 0;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* The inactive tab panel is removed via the `hidden` attribute; the explicit
|
|
145
|
-
rule guarantees it collapses regardless of any utility-layer display. The
|
|
146
|
-
write panel stays in the DOM while hidden so its textarea still submits and
|
|
147
|
-
validates. */
|
|
148
|
-
.stuic-comment-input-panel[hidden] {
|
|
149
|
-
display: none;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.stuic-comment-input-textarea {
|
|
153
|
-
display: block;
|
|
154
|
-
width: 100%;
|
|
155
|
-
min-height: var(--_min-height);
|
|
156
|
-
max-width: 100%;
|
|
157
|
-
margin: 0;
|
|
158
|
-
padding: var(--_pad-y) var(--_pad-x);
|
|
159
|
-
font-size: var(--_font-size);
|
|
160
|
-
line-height: 1.6;
|
|
161
|
-
color: inherit;
|
|
162
|
-
background: transparent;
|
|
163
|
-
border: none;
|
|
164
|
-
outline: none;
|
|
165
|
-
box-shadow: none;
|
|
166
|
-
resize: none;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/* --- Preview -------------------------------------------------------------- */
|
|
170
|
-
.stuic-comment-input-preview {
|
|
171
|
-
min-height: var(--_min-height);
|
|
172
|
-
padding: var(--_pad-y) var(--_pad-x);
|
|
173
|
-
font-size: var(--_font-size);
|
|
174
|
-
line-height: 1.6;
|
|
175
|
-
overflow-wrap: break-word;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.stuic-comment-input-preview-empty {
|
|
179
|
-
opacity: 0.5;
|
|
180
|
-
font-style: italic;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.stuic-comment-input-preview > * + * {
|
|
184
|
-
margin-top: 0.75em;
|
|
185
|
-
}
|
|
186
|
-
.stuic-comment-input-preview h1 {
|
|
187
|
-
font-size: 1.5em;
|
|
188
|
-
font-weight: 700;
|
|
189
|
-
line-height: 1.2;
|
|
190
|
-
}
|
|
191
|
-
.stuic-comment-input-preview h2 {
|
|
192
|
-
font-size: 1.3em;
|
|
193
|
-
font-weight: 700;
|
|
194
|
-
line-height: 1.25;
|
|
195
|
-
}
|
|
196
|
-
.stuic-comment-input-preview h3 {
|
|
197
|
-
font-size: 1.15em;
|
|
198
|
-
font-weight: 600;
|
|
199
|
-
}
|
|
200
|
-
.stuic-comment-input-preview ul,
|
|
201
|
-
.stuic-comment-input-preview ol {
|
|
202
|
-
padding-left: 1.5em;
|
|
203
|
-
}
|
|
204
|
-
.stuic-comment-input-preview ul {
|
|
205
|
-
list-style: disc;
|
|
206
|
-
}
|
|
207
|
-
.stuic-comment-input-preview ol {
|
|
208
|
-
list-style: decimal;
|
|
209
|
-
}
|
|
210
|
-
.stuic-comment-input-preview a {
|
|
211
|
-
text-decoration: underline;
|
|
212
|
-
}
|
|
213
|
-
.stuic-comment-input-preview blockquote {
|
|
214
|
-
padding-left: 1em;
|
|
215
|
-
border-left: 3px solid var(--_border-color);
|
|
216
|
-
opacity: 0.85;
|
|
217
|
-
}
|
|
218
|
-
.stuic-comment-input-preview code {
|
|
219
|
-
font-family: var(
|
|
220
|
-
--stuic-comment-input-font-mono,
|
|
221
|
-
ui-monospace,
|
|
222
|
-
SFMono-Regular,
|
|
223
|
-
Menlo,
|
|
224
|
-
monospace
|
|
225
|
-
);
|
|
226
|
-
font-size: 0.9em;
|
|
227
|
-
padding: 0.1em 0.3em;
|
|
228
|
-
border-radius: 0.25rem;
|
|
229
|
-
background: var(--stuic-comment-input-code-bg, rgb(0 0 0 / 0.06));
|
|
230
|
-
}
|
|
231
|
-
.stuic-comment-input-preview pre {
|
|
232
|
-
font-family: var(
|
|
233
|
-
--stuic-comment-input-font-mono,
|
|
234
|
-
ui-monospace,
|
|
235
|
-
SFMono-Regular,
|
|
236
|
-
Menlo,
|
|
237
|
-
monospace
|
|
238
|
-
);
|
|
239
|
-
padding: 0.75em 1em;
|
|
240
|
-
border-radius: var(--stuic-radius, 0.375rem);
|
|
241
|
-
background: var(--stuic-comment-input-code-bg, rgb(0 0 0 / 0.06));
|
|
242
|
-
overflow-x: auto;
|
|
243
|
-
}
|
|
244
|
-
.stuic-comment-input-preview pre code {
|
|
245
|
-
padding: 0;
|
|
246
|
-
background: transparent;
|
|
247
|
-
}
|
|
248
|
-
.stuic-comment-input-preview hr {
|
|
249
|
-
border: none;
|
|
250
|
-
border-top: var(--stuic-border-width, 1px) solid var(--_border-color);
|
|
251
|
-
}
|
|
252
|
-
.stuic-comment-input-preview table {
|
|
253
|
-
border-collapse: collapse;
|
|
254
|
-
}
|
|
255
|
-
.stuic-comment-input-preview th,
|
|
256
|
-
.stuic-comment-input-preview td {
|
|
257
|
-
border: var(--stuic-border-width, 1px) solid var(--_border-color);
|
|
258
|
-
padding: 0.25em 0.5em;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
:root.dark .stuic-comment-input-preview code,
|
|
262
|
-
:root.dark .stuic-comment-input-preview pre {
|
|
263
|
-
background: var(--stuic-comment-input-code-bg, rgb(255 255 255 / 0.08));
|
|
54
|
+
/* The embedded MarkdownEditor wraps itself in InputWrap, which adds a bottom
|
|
55
|
+
margin meant for standalone fields. Inside the composer we want the footer to
|
|
56
|
+
sit directly beneath the editor, so collapse that margin — the footer adds its
|
|
57
|
+
own small top gap instead. */
|
|
58
|
+
.stuic-comment-input-main > .stuic-input {
|
|
59
|
+
margin-bottom: 0;
|
|
264
60
|
}
|
|
265
61
|
|
|
266
62
|
/* --- Footer (button row) -------------------------------------------------- */
|
|
@@ -269,7 +65,7 @@
|
|
|
269
65
|
align-items: center;
|
|
270
66
|
justify-content: space-between;
|
|
271
67
|
gap: 0.5rem;
|
|
272
|
-
padding: 0.5rem;
|
|
68
|
+
padding-top: var(--stuic-comment-input-footer-gap, 0.5rem);
|
|
273
69
|
}
|
|
274
70
|
|
|
275
71
|
.stuic-comment-input-footer-start,
|
|
@@ -282,29 +78,3 @@
|
|
|
282
78
|
.stuic-comment-input-footer-end {
|
|
283
79
|
margin-left: auto;
|
|
284
80
|
}
|
|
285
|
-
|
|
286
|
-
/* --- Size variants -------------------------------------------------------- */
|
|
287
|
-
.stuic-comment-input[data-size="sm"] {
|
|
288
|
-
--_pad-x: var(--stuic-comment-input-padding-x, var(--stuic-input-padding-x-sm, 0.5rem));
|
|
289
|
-
--_pad-y: var(
|
|
290
|
-
--stuic-comment-input-padding-y,
|
|
291
|
-
var(--stuic-input-padding-y-sm, 0.375rem)
|
|
292
|
-
);
|
|
293
|
-
--_font-size: var(
|
|
294
|
-
--stuic-comment-input-font-size,
|
|
295
|
-
var(--stuic-input-font-size-sm, 0.8125rem)
|
|
296
|
-
);
|
|
297
|
-
--_min-height: var(--stuic-comment-input-min-height, 4rem);
|
|
298
|
-
}
|
|
299
|
-
.stuic-comment-input[data-size="lg"] {
|
|
300
|
-
--_pad-x: var(--stuic-comment-input-padding-x, var(--stuic-input-padding-x-lg, 1rem));
|
|
301
|
-
--_pad-y: var(
|
|
302
|
-
--stuic-comment-input-padding-y,
|
|
303
|
-
var(--stuic-input-padding-y-lg, 0.75rem)
|
|
304
|
-
);
|
|
305
|
-
--_font-size: var(
|
|
306
|
-
--stuic-comment-input-font-size,
|
|
307
|
-
var(--stuic-input-font-size-lg, 1.0625rem)
|
|
308
|
-
);
|
|
309
|
-
--_min-height: var(--stuic-comment-input-min-height, 7rem);
|
|
310
|
-
}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { default as CommentInput, type Props as CommentInputProps, type CommentInputMode, } from "./CommentInput.svelte";
|
|
2
|
-
export type { MarkdownRenderer } from "./_internal/render.js";
|
|
1
|
+
export { default as CommentInput, type Props as CommentInputProps, type CommentInputMode, DEFAULT_COMMENT_TOOLBAR, } from "./CommentInput.svelte";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as CommentInput, } from "./CommentInput.svelte";
|
|
1
|
+
export { default as CommentInput, DEFAULT_COMMENT_TOOLBAR, } from "./CommentInput.svelte";
|
|
@@ -529,6 +529,16 @@
|
|
|
529
529
|
export function toggleMode(): void {
|
|
530
530
|
toggleModeInternal();
|
|
531
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Imperatively run a toolbar command — same effect as clicking the matching
|
|
534
|
+
* toolbar button. Useful for wiring keyboard shortcuts (e.g. ⌘/Ctrl+B) from a
|
|
535
|
+
* host component. No-op while the backend is (re)loading. The `"|"` separator
|
|
536
|
+
* is not a command.
|
|
537
|
+
*/
|
|
538
|
+
export function command(item: Exclude<ToolbarItem, "|">): void {
|
|
539
|
+
const btn = TOOLBAR_REGISTRY[item];
|
|
540
|
+
if (btn && activeHandle) btn.run(activeHandle.commands);
|
|
541
|
+
}
|
|
532
542
|
</script>
|
|
533
543
|
|
|
534
544
|
<InputWrap
|
|
@@ -99,6 +99,7 @@ declare const MarkdownEditor: import("svelte").Component<Props, {
|
|
|
99
99
|
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
100
100
|
getMarkdown: () => string;
|
|
101
101
|
toggleMode: () => void;
|
|
102
|
+
command: (item: Exclude<ToolbarItem, "|">) => void;
|
|
102
103
|
}, "value" | "input" | "mode">;
|
|
103
104
|
type MarkdownEditor = ReturnType<typeof MarkdownEditor>;
|
|
104
105
|
export default MarkdownEditor;
|
package/package.json
CHANGED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default Preview renderer for {@link CommentInput}: turns a markdown string into
|
|
3
|
-
* **sanitized** HTML.
|
|
4
|
-
*
|
|
5
|
-
* `marked` and `dompurify` are declared as *optional* peer dependencies and are
|
|
6
|
-
* imported dynamically the first time a Preview tab is opened — consumers who
|
|
7
|
-
* never preview (or who pass their own `renderMarkdown`) don't pay for them, and
|
|
8
|
-
* a consumer who hasn't installed them gets a caught error (handled by the
|
|
9
|
-
* component) rather than a hard build failure. Mirrors the lazy-load convention
|
|
10
|
-
* used by `MarkdownEditor` for its Milkdown/CodeMirror backends.
|
|
11
|
-
*
|
|
12
|
-
* Sanitization is non-negotiable here: comment text is untrusted user content and
|
|
13
|
-
* `marked` emits raw HTML embedded in markdown verbatim (an XSS vector). Every
|
|
14
|
-
* code path that returns HTML runs it through DOMPurify first.
|
|
15
|
-
*/
|
|
16
|
-
/** A markdown-to-HTML renderer. Return value may be sync or async. */
|
|
17
|
-
export type MarkdownRenderer = (markdown: string) => string | Promise<string>;
|
|
18
|
-
/**
|
|
19
|
-
* Render markdown to sanitized HTML using the bundled (lazy) marked + DOMPurify
|
|
20
|
-
* pipeline. Throws if the optional peers aren't installed (the caller catches
|
|
21
|
-
* this and shows a fallback). SSR-safe: returns an empty string off the DOM,
|
|
22
|
-
* since DOMPurify needs a `window` to sanitize against.
|
|
23
|
-
*/
|
|
24
|
-
export declare function renderMarkdownSafe(markdown: string): Promise<string>;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default Preview renderer for {@link CommentInput}: turns a markdown string into
|
|
3
|
-
* **sanitized** HTML.
|
|
4
|
-
*
|
|
5
|
-
* `marked` and `dompurify` are declared as *optional* peer dependencies and are
|
|
6
|
-
* imported dynamically the first time a Preview tab is opened — consumers who
|
|
7
|
-
* never preview (or who pass their own `renderMarkdown`) don't pay for them, and
|
|
8
|
-
* a consumer who hasn't installed them gets a caught error (handled by the
|
|
9
|
-
* component) rather than a hard build failure. Mirrors the lazy-load convention
|
|
10
|
-
* used by `MarkdownEditor` for its Milkdown/CodeMirror backends.
|
|
11
|
-
*
|
|
12
|
-
* Sanitization is non-negotiable here: comment text is untrusted user content and
|
|
13
|
-
* `marked` emits raw HTML embedded in markdown verbatim (an XSS vector). Every
|
|
14
|
-
* code path that returns HTML runs it through DOMPurify first.
|
|
15
|
-
*/
|
|
16
|
-
// Cache the resolved (marked + DOMPurify) pipeline so the dynamic imports and
|
|
17
|
-
// DOMPurify instance creation happen at most once per page.
|
|
18
|
-
let cached = null;
|
|
19
|
-
async function load() {
|
|
20
|
-
if (cached)
|
|
21
|
-
return cached;
|
|
22
|
-
const [{ marked }, dompurify] = await Promise.all([
|
|
23
|
-
import("marked"),
|
|
24
|
-
import("dompurify"),
|
|
25
|
-
]);
|
|
26
|
-
// DOMPurify's default export is the instance in a browser/DOM context.
|
|
27
|
-
const DOMPurify = dompurify.default ?? dompurify;
|
|
28
|
-
cached = async (markdown) => {
|
|
29
|
-
// GitHub-flavored markdown + soft line breaks → <br>, matching how comment
|
|
30
|
-
// boxes (incl. GitHub's) treat single newlines.
|
|
31
|
-
const html = await marked.parse(markdown ?? "", { gfm: true, breaks: true });
|
|
32
|
-
return DOMPurify.sanitize(html);
|
|
33
|
-
};
|
|
34
|
-
return cached;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Render markdown to sanitized HTML using the bundled (lazy) marked + DOMPurify
|
|
38
|
-
* pipeline. Throws if the optional peers aren't installed (the caller catches
|
|
39
|
-
* this and shows a fallback). SSR-safe: returns an empty string off the DOM,
|
|
40
|
-
* since DOMPurify needs a `window` to sanitize against.
|
|
41
|
-
*/
|
|
42
|
-
export async function renderMarkdownSafe(markdown) {
|
|
43
|
-
if (typeof window === "undefined")
|
|
44
|
-
return "";
|
|
45
|
-
const render = await load();
|
|
46
|
-
return render(markdown);
|
|
47
|
-
}
|