@svadmin/ui 0.40.0 → 0.40.1
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.
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/* eslint-disable svelte/no-at-html-tags */
|
|
3
|
-
import
|
|
4
|
-
import * as markedHighlightPkg from "marked-highlight";
|
|
5
|
-
import * as hljsPkg from "highlight.js";
|
|
6
|
-
import * as DOMPurifyPkg from "isomorphic-dompurify";
|
|
7
|
-
|
|
8
|
-
const Marked = markedPkg.Marked;
|
|
9
|
-
const markedHighlight = markedHighlightPkg.markedHighlight;
|
|
10
|
-
const hljsModule = hljsPkg.default || hljsPkg;
|
|
11
|
-
const DOMPurify = 'default' in DOMPurifyPkg ? DOMPurifyPkg.default : DOMPurifyPkg;
|
|
12
|
-
|
|
13
|
-
// Only safely import css if hljs exists
|
|
14
|
-
if (hljsModule && Object.keys(hljsModule).length > 0) {
|
|
15
|
-
import("highlight.js/styles/github-dark.css").catch(() => {});
|
|
16
|
-
}
|
|
3
|
+
import { onMount } from "svelte";
|
|
17
4
|
|
|
18
5
|
interface Props {
|
|
19
6
|
/** The raw markdown string to render */
|
|
@@ -26,25 +13,81 @@
|
|
|
26
13
|
|
|
27
14
|
let { content, streaming = false, class: className = "" }: Props = $props();
|
|
28
15
|
|
|
29
|
-
|
|
16
|
+
type MarkedConstructor = typeof import("marked").Marked;
|
|
17
|
+
type MarkedHighlight = typeof import("marked-highlight").markedHighlight;
|
|
18
|
+
type HighlightApi = typeof import("highlight.js").default;
|
|
19
|
+
type Sanitizer = typeof import("isomorphic-dompurify").default;
|
|
20
|
+
|
|
21
|
+
// Lazily loaded optional peer dependencies (marked, marked-highlight,
|
|
22
|
+
// highlight.js, isomorphic-dompurify). They are declared as optional peer
|
|
23
|
+
// deps; statically importing them would crash consumers that have not
|
|
24
|
+
// installed them, even when MarkdownRenderer is never rendered. Load them
|
|
25
|
+
// dynamically so the module graph resolves without them and the component
|
|
26
|
+
// degrades to escaped-text rendering when they are absent.
|
|
27
|
+
let MarkedCtor = $state<MarkedConstructor | null>(null);
|
|
28
|
+
let markedHighlightFn = $state<MarkedHighlight | null>(null);
|
|
29
|
+
let hljs = $state<HighlightApi | null>(null);
|
|
30
|
+
let DOMPurify = $state<Sanitizer | null>(null);
|
|
31
|
+
|
|
32
|
+
onMount(() => {
|
|
33
|
+
let cancelled = false;
|
|
34
|
+
(async () => {
|
|
35
|
+
try {
|
|
36
|
+
const [markedPkg, markedHighlightPkg, hljsPkg, DOMPurifyPkg] = await Promise.all([
|
|
37
|
+
import("marked"),
|
|
38
|
+
import("marked-highlight"),
|
|
39
|
+
import("highlight.js"),
|
|
40
|
+
import("isomorphic-dompurify"),
|
|
41
|
+
]);
|
|
42
|
+
if (cancelled) return;
|
|
43
|
+
MarkedCtor = markedPkg.Marked;
|
|
44
|
+
markedHighlightFn = markedHighlightPkg.markedHighlight;
|
|
45
|
+
hljs = hljsPkg.default;
|
|
46
|
+
DOMPurify = DOMPurifyPkg.default;
|
|
47
|
+
// Only import the theme css when highlight.js is available
|
|
48
|
+
if (hljs && Object.keys(hljs).length > 0) {
|
|
49
|
+
import("highlight.js/styles/github-dark.css").catch(() => {});
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// Optional deps not installed; fall back to escaped-text rendering.
|
|
53
|
+
if (cancelled) return;
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
56
|
+
return () => { cancelled = true; };
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const hasMarkdownDeps = $derived(
|
|
60
|
+
typeof MarkedCtor === "function" &&
|
|
61
|
+
typeof DOMPurify?.sanitize === "function" &&
|
|
62
|
+
typeof markedHighlightFn === "function",
|
|
63
|
+
);
|
|
30
64
|
|
|
31
65
|
// Configure marked with syntax highlighting if available
|
|
32
|
-
const markedObj =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
const markedObj = $derived.by(() => {
|
|
67
|
+
const Constructor = MarkedCtor;
|
|
68
|
+
const highlightExtension = markedHighlightFn;
|
|
69
|
+
if (!Constructor || !highlightExtension) return null;
|
|
70
|
+
|
|
71
|
+
return new Constructor(
|
|
72
|
+
highlightExtension({
|
|
73
|
+
langPrefix: "hljs language-",
|
|
74
|
+
highlight(code: string, lang: string) {
|
|
75
|
+
const language = hljs?.getLanguage && hljs.getLanguage(lang) ? lang : "plaintext";
|
|
76
|
+
return hljs?.highlight ? hljs.highlight(code, { language }).value : code;
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
});
|
|
41
81
|
|
|
42
82
|
// Render HTML safely
|
|
43
|
-
const html = $derived(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
83
|
+
const html = $derived.by(() => {
|
|
84
|
+
const purifier = DOMPurify;
|
|
85
|
+
const parser = markedObj;
|
|
86
|
+
if (hasMarkdownDeps && purifier && parser) {
|
|
87
|
+
return purifier.sanitize(parser.parse(content || ""));
|
|
88
|
+
}
|
|
89
|
+
return `<div style="white-space: pre-wrap">${String(content || "").replace(/</g, "<").replace(/>/g, ">")}</div>`;
|
|
90
|
+
});
|
|
48
91
|
|
|
49
92
|
// Handle copy code blocks
|
|
50
93
|
let copiedBlock = $state<string | null>(null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/ui",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.1",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"./components/*": "./dist/components/*"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@svadmin/core": "^0.34.
|
|
42
|
+
"@svadmin/core": "^0.34.1",
|
|
43
43
|
"@tanstack/svelte-query": "^6.1.38",
|
|
44
44
|
"highlight.js": "^11.11.1",
|
|
45
45
|
"isomorphic-dompurify": "^3.20.0",
|