@mieweb/ui 0.6.1-dev.132 → 0.6.1-dev.134
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/README.md +46 -10
- package/dist/CsvBlock-7K6XVRYO.cjs +15 -0
- package/dist/CsvBlock-7K6XVRYO.cjs.map +1 -0
- package/dist/CsvBlock-AAV4QMSF.js +6 -0
- package/dist/CsvBlock-AAV4QMSF.js.map +1 -0
- package/dist/HtmlPreviewBlock-74XQF3KT.cjs +15 -0
- package/dist/HtmlPreviewBlock-74XQF3KT.cjs.map +1 -0
- package/dist/HtmlPreviewBlock-DNINC7RW.js +6 -0
- package/dist/HtmlPreviewBlock-DNINC7RW.js.map +1 -0
- package/dist/MermaidBlock-PKXGFGU6.js +6 -0
- package/dist/MermaidBlock-PKXGFGU6.js.map +1 -0
- package/dist/MermaidBlock-R4U7IHSZ.cjs +15 -0
- package/dist/MermaidBlock-R4U7IHSZ.cjs.map +1 -0
- package/dist/SurveyBlock-E52I4PW3.cjs +15 -0
- package/dist/SurveyBlock-E52I4PW3.cjs.map +1 -0
- package/dist/SurveyBlock-IULSBG22.js +6 -0
- package/dist/SurveyBlock-IULSBG22.js.map +1 -0
- package/dist/brands/index.cjs +12 -12
- package/dist/brands/index.js +3 -3
- package/dist/{chunk-2DS3RJ2D.js → chunk-33PO3J4O.js} +35 -3
- package/dist/chunk-33PO3J4O.js.map +1 -0
- package/dist/chunk-6RV7HQ4U.cjs +198 -0
- package/dist/chunk-6RV7HQ4U.cjs.map +1 -0
- package/dist/chunk-74NOFB34.cjs +137 -0
- package/dist/chunk-74NOFB34.cjs.map +1 -0
- package/dist/chunk-CPJIOOBM.js +196 -0
- package/dist/chunk-CPJIOOBM.js.map +1 -0
- package/dist/{chunk-R6PBBPU3.js → chunk-CYMZ2QS5.js} +2 -2
- package/dist/{chunk-R6PBBPU3.js.map → chunk-CYMZ2QS5.js.map} +1 -1
- package/dist/chunk-FGIIXD37.js +182 -0
- package/dist/chunk-FGIIXD37.js.map +1 -0
- package/dist/chunk-FZQAF3WA.cjs +154 -0
- package/dist/chunk-FZQAF3WA.cjs.map +1 -0
- package/dist/chunk-MHJMUKLA.js +89 -0
- package/dist/chunk-MHJMUKLA.js.map +1 -0
- package/dist/chunk-NCRYCG5G.js +135 -0
- package/dist/chunk-NCRYCG5G.js.map +1 -0
- package/dist/chunk-NSLR3B7K.js +318 -0
- package/dist/chunk-NSLR3B7K.js.map +1 -0
- package/dist/{chunk-Z6NRP4Z5.cjs → chunk-SNEQXSC5.cjs} +2 -2
- package/dist/{chunk-Z6NRP4Z5.cjs.map → chunk-SNEQXSC5.cjs.map} +1 -1
- package/dist/chunk-UL3PQ7HL.cjs +91 -0
- package/dist/chunk-UL3PQ7HL.cjs.map +1 -0
- package/dist/chunk-VO3NWSCQ.cjs +184 -0
- package/dist/chunk-VO3NWSCQ.cjs.map +1 -0
- package/dist/chunk-WGC3KOP7.js +148 -0
- package/dist/chunk-WGC3KOP7.js.map +1 -0
- package/dist/{chunk-IBZXDX4L.cjs → chunk-WHUD3XHR.cjs} +35 -3
- package/dist/chunk-WHUD3XHR.cjs.map +1 -0
- package/dist/chunk-Y65SK5Y2.cjs +338 -0
- package/dist/chunk-Y65SK5Y2.cjs.map +1 -0
- package/dist/components/Markdown/index.cjs +51 -0
- package/dist/components/Markdown/index.cjs.map +1 -0
- package/dist/components/Markdown/index.css +331 -0
- package/dist/components/Markdown/index.css.map +1 -0
- package/dist/components/Markdown/index.d.cts +82 -0
- package/dist/components/Markdown/index.d.ts +82 -0
- package/dist/components/Markdown/index.js +10 -0
- package/dist/components/Markdown/index.js.map +1 -0
- package/dist/components/Markdown/styles.css +351 -0
- package/dist/index.cjs +1244 -570
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +331 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +137 -1
- package/dist/index.d.ts +137 -1
- package/dist/index.js +1091 -456
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/tailwind-preset.cjs +4 -4
- package/dist/tailwind-preset.js +1 -1
- package/package.json +23 -2
- package/dist/chunk-2DS3RJ2D.js.map +0 -1
- package/dist/chunk-IBZXDX4L.cjs.map +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkUL3PQ7HL_cjs = require('./chunk-UL3PQ7HL.cjs');
|
|
4
|
+
var DOMPurify = require('dompurify');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
|
|
11
|
+
|
|
12
|
+
var mermaidInstance = null;
|
|
13
|
+
var mermaidReady = null;
|
|
14
|
+
var mermaidTheme = "default";
|
|
15
|
+
function currentTheme() {
|
|
16
|
+
return document.documentElement.classList.contains("dark") || document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "default";
|
|
17
|
+
}
|
|
18
|
+
function buildConfig(theme) {
|
|
19
|
+
const base = {
|
|
20
|
+
startOnLoad: false,
|
|
21
|
+
theme,
|
|
22
|
+
securityLevel: "strict"
|
|
23
|
+
};
|
|
24
|
+
if (theme === "dark") {
|
|
25
|
+
base.themeVariables = {
|
|
26
|
+
nodeBorder: "#6b7280",
|
|
27
|
+
mainBkg: "#4a4a4a",
|
|
28
|
+
nodeTextColor: "#ffffff",
|
|
29
|
+
clusterBkg: "#374151",
|
|
30
|
+
titleColor: "#ffffff",
|
|
31
|
+
edgeLabelBackground: "#374151",
|
|
32
|
+
lineColor: "#9ca3af"
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return base;
|
|
36
|
+
}
|
|
37
|
+
async function getMermaid() {
|
|
38
|
+
const theme = currentTheme();
|
|
39
|
+
if (mermaidInstance) {
|
|
40
|
+
if (theme !== mermaidTheme) {
|
|
41
|
+
mermaidTheme = theme;
|
|
42
|
+
mermaidInstance.initialize(buildConfig(theme));
|
|
43
|
+
}
|
|
44
|
+
return mermaidInstance;
|
|
45
|
+
}
|
|
46
|
+
if (!mermaidReady) {
|
|
47
|
+
mermaidReady = import(
|
|
48
|
+
/* @vite-ignore */
|
|
49
|
+
'mermaid'
|
|
50
|
+
).then((mod) => {
|
|
51
|
+
const m = mod.default;
|
|
52
|
+
mermaidTheme = currentTheme();
|
|
53
|
+
m.initialize(buildConfig(mermaidTheme));
|
|
54
|
+
mermaidInstance = m;
|
|
55
|
+
return m;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return mermaidReady;
|
|
59
|
+
}
|
|
60
|
+
function sanitiseSvg(svg) {
|
|
61
|
+
return DOMPurify__default.default.sanitize(svg, {
|
|
62
|
+
USE_PROFILES: { svg: true, svgFilters: true },
|
|
63
|
+
// Allow <style> so mermaid's embedded CSS applies, and <foreignObject>
|
|
64
|
+
// so node label text renders (mermaid escapes its content in strict mode).
|
|
65
|
+
ADD_TAGS: ["style", "foreignObject"],
|
|
66
|
+
ADD_ATTR: [
|
|
67
|
+
"style",
|
|
68
|
+
"class",
|
|
69
|
+
"dominant-baseline",
|
|
70
|
+
"text-anchor",
|
|
71
|
+
"font-size",
|
|
72
|
+
"font-family",
|
|
73
|
+
"marker-end",
|
|
74
|
+
"marker-start",
|
|
75
|
+
"marker-mid",
|
|
76
|
+
"stroke-dasharray",
|
|
77
|
+
"stroke-width"
|
|
78
|
+
],
|
|
79
|
+
FORBID_TAGS: ["script"],
|
|
80
|
+
FORBID_ATTR: ["onload", "onerror", "onclick", "onmouseover"]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
var MermaidBlock = ({ code, id }) => {
|
|
84
|
+
const [svg, setSvg] = react.useState("");
|
|
85
|
+
const [error, setError] = react.useState("");
|
|
86
|
+
const [loading, setLoading] = react.useState(true);
|
|
87
|
+
const diagramLabel = react.useMemo(() => {
|
|
88
|
+
const firstWord = code.trim().split(/[\s\n]/)[0]?.toLowerCase() ?? "";
|
|
89
|
+
const typeMap = {
|
|
90
|
+
graph: "Flowchart",
|
|
91
|
+
flowchart: "Flowchart",
|
|
92
|
+
sequencediagram: "Sequence diagram",
|
|
93
|
+
classdiagram: "Class diagram",
|
|
94
|
+
statediagram: "State diagram",
|
|
95
|
+
"statediagram-v2": "State diagram",
|
|
96
|
+
erdiagram: "Entity relationship diagram",
|
|
97
|
+
gantt: "Gantt chart",
|
|
98
|
+
pie: "Pie chart",
|
|
99
|
+
journey: "User journey diagram",
|
|
100
|
+
gitgraph: "Git graph",
|
|
101
|
+
mindmap: "Mind map",
|
|
102
|
+
timeline: "Timeline",
|
|
103
|
+
quadrantchart: "Quadrant chart"
|
|
104
|
+
};
|
|
105
|
+
return `${typeMap[firstWord] ?? "Mermaid"} diagram`;
|
|
106
|
+
}, [code]);
|
|
107
|
+
const render = react.useCallback(async () => {
|
|
108
|
+
setLoading(true);
|
|
109
|
+
setError("");
|
|
110
|
+
try {
|
|
111
|
+
const mermaid = await getMermaid();
|
|
112
|
+
await mermaid.parse(code);
|
|
113
|
+
const { svg: rendered } = await mermaid.render(`mermaid-${id}`, code);
|
|
114
|
+
setSvg(sanitiseSvg(rendered));
|
|
115
|
+
} catch (err) {
|
|
116
|
+
setError(err.message ?? "Failed to render diagram");
|
|
117
|
+
} finally {
|
|
118
|
+
setLoading(false);
|
|
119
|
+
}
|
|
120
|
+
}, [code, id]);
|
|
121
|
+
react.useEffect(() => {
|
|
122
|
+
void render();
|
|
123
|
+
}, [render]);
|
|
124
|
+
react.useEffect(() => {
|
|
125
|
+
const observer = new MutationObserver(() => void render());
|
|
126
|
+
observer.observe(document.documentElement, {
|
|
127
|
+
attributes: true,
|
|
128
|
+
attributeFilter: ["class", "data-theme"]
|
|
129
|
+
});
|
|
130
|
+
return () => observer.disconnect();
|
|
131
|
+
}, [render]);
|
|
132
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
133
|
+
chunkUL3PQ7HL_cjs.FenceBlock,
|
|
134
|
+
{
|
|
135
|
+
code,
|
|
136
|
+
language: "mermaid",
|
|
137
|
+
supportsRawView: true,
|
|
138
|
+
error: error || void 0,
|
|
139
|
+
children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-primary-500 h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
className: "mermaid-svg flex justify-center p-4",
|
|
143
|
+
role: "img",
|
|
144
|
+
"aria-label": diagramLabel,
|
|
145
|
+
dangerouslySetInnerHTML: { __html: svg }
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
exports.MermaidBlock = MermaidBlock;
|
|
153
|
+
//# sourceMappingURL=chunk-FZQAF3WA.cjs.map
|
|
154
|
+
//# sourceMappingURL=chunk-FZQAF3WA.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Markdown/MermaidBlock.tsx"],"names":["DOMPurify","useState","useMemo","useCallback","useEffect","jsx","FenceBlock"],"mappings":"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","file":"chunk-FZQAF3WA.cjs","sourcesContent":["/**\n * MermaidBlock — Renders Mermaid diagrams.\n * Requires `mermaid` to be installed by the consumer (optional peer dependency).\n */\nimport DOMPurify from 'dompurify';\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { FenceBlock } from './FenceBlock';\n\ninterface MermaidBlockProps {\n code: string;\n id: string;\n}\n\ntype MermaidApi = {\n initialize: (config: Record<string, unknown>) => void;\n parse: (text: string) => Promise<unknown>;\n render: (id: string, text: string) => Promise<{ svg: string }>;\n};\n\nlet mermaidInstance: MermaidApi | null = null;\nlet mermaidReady: Promise<MermaidApi> | null = null;\nlet mermaidTheme: 'dark' | 'default' = 'default';\n\nfunction currentTheme(): 'dark' | 'default' {\n return document.documentElement.classList.contains('dark') ||\n document.documentElement.getAttribute('data-theme') === 'dark'\n ? 'dark'\n : 'default';\n}\n\nfunction buildConfig(theme: 'dark' | 'default'): Record<string, unknown> {\n const base: Record<string, unknown> = {\n startOnLoad: false,\n theme,\n securityLevel: 'strict',\n };\n if (theme === 'dark') {\n base.themeVariables = {\n nodeBorder: '#6b7280',\n mainBkg: '#4a4a4a',\n nodeTextColor: '#ffffff',\n clusterBkg: '#374151',\n titleColor: '#ffffff',\n edgeLabelBackground: '#374151',\n lineColor: '#9ca3af',\n };\n }\n return base;\n}\n\nasync function getMermaid(): Promise<MermaidApi> {\n const theme = currentTheme();\n if (mermaidInstance) {\n if (theme !== mermaidTheme) {\n mermaidTheme = theme;\n mermaidInstance.initialize(buildConfig(theme));\n }\n return mermaidInstance;\n }\n if (!mermaidReady) {\n mermaidReady = import(/* @vite-ignore */ 'mermaid').then((mod) => {\n const m = (mod as { default: MermaidApi }).default;\n mermaidTheme = currentTheme();\n m.initialize(buildConfig(mermaidTheme));\n mermaidInstance = m;\n return m;\n });\n }\n return mermaidReady;\n}\n\nfunction sanitiseSvg(svg: string): string {\n return DOMPurify.sanitize(svg, {\n USE_PROFILES: { svg: true, svgFilters: true },\n // Allow <style> so mermaid's embedded CSS applies, and <foreignObject>\n // so node label text renders (mermaid escapes its content in strict mode).\n ADD_TAGS: ['style', 'foreignObject'],\n ADD_ATTR: [\n 'style',\n 'class',\n 'dominant-baseline',\n 'text-anchor',\n 'font-size',\n 'font-family',\n 'marker-end',\n 'marker-start',\n 'marker-mid',\n 'stroke-dasharray',\n 'stroke-width',\n ],\n FORBID_TAGS: ['script'],\n FORBID_ATTR: ['onload', 'onerror', 'onclick', 'onmouseover'],\n }) as string;\n}\n\nexport const MermaidBlock: React.FC<MermaidBlockProps> = ({ code, id }) => {\n const [svg, setSvg] = useState<string>('');\n const [error, setError] = useState<string>('');\n const [loading, setLoading] = useState(true);\n\n // Derive an accessible label from the diagram's declared type (first keyword).\n const diagramLabel = useMemo(() => {\n const firstWord =\n code\n .trim()\n .split(/[\\s\\n]/)[0]\n ?.toLowerCase() ?? '';\n const typeMap: Record<string, string> = {\n graph: 'Flowchart',\n flowchart: 'Flowchart',\n sequencediagram: 'Sequence diagram',\n classdiagram: 'Class diagram',\n statediagram: 'State diagram',\n 'statediagram-v2': 'State diagram',\n erdiagram: 'Entity relationship diagram',\n gantt: 'Gantt chart',\n pie: 'Pie chart',\n journey: 'User journey diagram',\n gitgraph: 'Git graph',\n mindmap: 'Mind map',\n timeline: 'Timeline',\n quadrantchart: 'Quadrant chart',\n };\n return `${typeMap[firstWord] ?? 'Mermaid'} diagram`;\n }, [code]);\n\n const render = useCallback(async () => {\n setLoading(true);\n setError('');\n try {\n const mermaid = await getMermaid();\n await mermaid.parse(code);\n const { svg: rendered } = await mermaid.render(`mermaid-${id}`, code);\n setSvg(sanitiseSvg(rendered));\n } catch (err) {\n setError((err as Error).message ?? 'Failed to render diagram');\n } finally {\n setLoading(false);\n }\n }, [code, id]);\n\n useEffect(() => {\n void render();\n }, [render]);\n\n // Re-render when the page theme changes (dark ↔ light)\n useEffect(() => {\n const observer = new MutationObserver(() => void render());\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class', 'data-theme'],\n });\n return () => observer.disconnect();\n }, [render]);\n\n return (\n <FenceBlock\n code={code}\n language=\"mermaid\"\n supportsRawView\n error={error || undefined}\n >\n {loading ? (\n <div className=\"flex items-center justify-center p-8\">\n <div className=\"border-primary-500 h-6 w-6 animate-spin rounded-full border-2 border-t-transparent\" />\n </div>\n ) : (\n <div\n className=\"mermaid-svg flex justify-center p-4\"\n role=\"img\"\n aria-label={diagramLabel}\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n )}\n </FenceBlock>\n );\n};\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Button } from './chunk-ZVSW2KS6.js';
|
|
2
|
+
import { Eye, Code, Check, ClipboardCopy } from 'lucide-react';
|
|
3
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var FenceBlock = ({
|
|
7
|
+
code,
|
|
8
|
+
language,
|
|
9
|
+
supportsRawView = false,
|
|
10
|
+
error,
|
|
11
|
+
children,
|
|
12
|
+
headerActions
|
|
13
|
+
}) => {
|
|
14
|
+
const [copyState, setCopyState] = useState("idle");
|
|
15
|
+
const [showRaw, setShowRaw] = useState(false);
|
|
16
|
+
const timerRef = useRef(null);
|
|
17
|
+
useEffect(
|
|
18
|
+
() => () => {
|
|
19
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
20
|
+
},
|
|
21
|
+
[]
|
|
22
|
+
);
|
|
23
|
+
const scheduleReset = useCallback(() => {
|
|
24
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
25
|
+
timerRef.current = setTimeout(() => setCopyState("idle"), 2e3);
|
|
26
|
+
}, []);
|
|
27
|
+
const handleCopy = useCallback(async () => {
|
|
28
|
+
if (copyState === "copying") return;
|
|
29
|
+
setCopyState("copying");
|
|
30
|
+
try {
|
|
31
|
+
await navigator.clipboard.writeText(code);
|
|
32
|
+
setCopyState("success");
|
|
33
|
+
scheduleReset();
|
|
34
|
+
} catch {
|
|
35
|
+
try {
|
|
36
|
+
const textarea = document.createElement("textarea");
|
|
37
|
+
textarea.value = code;
|
|
38
|
+
textarea.style.position = "fixed";
|
|
39
|
+
textarea.style.opacity = "0";
|
|
40
|
+
document.body.appendChild(textarea);
|
|
41
|
+
textarea.select();
|
|
42
|
+
document.execCommand("copy");
|
|
43
|
+
document.body.removeChild(textarea);
|
|
44
|
+
setCopyState("success");
|
|
45
|
+
} catch {
|
|
46
|
+
setCopyState("error");
|
|
47
|
+
}
|
|
48
|
+
scheduleReset();
|
|
49
|
+
}
|
|
50
|
+
}, [code, copyState, scheduleReset]);
|
|
51
|
+
return /* @__PURE__ */ jsxs("div", { className: "group relative my-3 overflow-hidden rounded-lg border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-[#1a1d24]", children: [
|
|
52
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-neutral-200 px-3 py-1.5 dark:border-white/[0.08]", children: [
|
|
53
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-neutral-500 dark:text-neutral-400", children: language ? language.charAt(0).toUpperCase() + language.slice(1) : "Text" }),
|
|
54
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
55
|
+
headerActions,
|
|
56
|
+
supportsRawView && /* @__PURE__ */ jsx(
|
|
57
|
+
Button,
|
|
58
|
+
{
|
|
59
|
+
variant: "ghost",
|
|
60
|
+
size: "sm",
|
|
61
|
+
onClick: () => setShowRaw((v) => !v),
|
|
62
|
+
"aria-label": showRaw ? "Show rendered view" : "Show raw code",
|
|
63
|
+
className: "h-7 w-7 p-0",
|
|
64
|
+
children: showRaw ? /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Code, { className: "h-3.5 w-3.5" })
|
|
65
|
+
}
|
|
66
|
+
),
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
Button,
|
|
69
|
+
{
|
|
70
|
+
variant: "ghost",
|
|
71
|
+
size: "sm",
|
|
72
|
+
onClick: handleCopy,
|
|
73
|
+
"aria-label": "Copy code",
|
|
74
|
+
className: "h-7 w-7 p-0",
|
|
75
|
+
children: copyState === "success" ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(ClipboardCopy, { className: "h-3.5 w-3.5" })
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
] })
|
|
79
|
+
] }),
|
|
80
|
+
error ? /* @__PURE__ */ jsxs("div", { className: "p-3", children: [
|
|
81
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-md bg-red-50 p-3 text-sm text-red-800 dark:bg-red-900/20 dark:text-red-300", children: error }),
|
|
82
|
+
/* @__PURE__ */ jsx("pre", { className: "mt-2 overflow-x-auto text-xs whitespace-pre-wrap text-neutral-600 dark:text-neutral-400", children: /* @__PURE__ */ jsx("code", { children: code }) })
|
|
83
|
+
] }) : showRaw ? /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto p-3 text-sm", children: /* @__PURE__ */ jsx("code", { children: code }) }) : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children })
|
|
84
|
+
] });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export { FenceBlock };
|
|
88
|
+
//# sourceMappingURL=chunk-MHJMUKLA.js.map
|
|
89
|
+
//# sourceMappingURL=chunk-MHJMUKLA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Markdown/FenceBlock.tsx"],"names":[],"mappings":"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","file":"chunk-MHJMUKLA.js","sourcesContent":["/**\n * FenceBlock — Base wrapper for code blocks with copy, raw view toggle, and errors.\n */\nimport { Check, ClipboardCopy, Code, Eye } from 'lucide-react';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { Button } from '../Button';\n\nexport interface FenceBlockProps {\n code: string;\n language?: string;\n supportsRawView?: boolean;\n error?: string;\n children: React.ReactNode;\n /** Extra buttons rendered in the header before the copy button */\n headerActions?: React.ReactNode;\n}\n\ntype CopyState = 'idle' | 'copying' | 'success' | 'error';\n\nexport const FenceBlock: React.FC<FenceBlockProps> = ({\n code,\n language,\n supportsRawView = false,\n error,\n children,\n headerActions,\n}) => {\n const [copyState, setCopyState] = useState<CopyState>('idle');\n const [showRaw, setShowRaw] = useState(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(\n () => () => {\n if (timerRef.current) clearTimeout(timerRef.current);\n },\n []\n );\n\n const scheduleReset = useCallback(() => {\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setCopyState('idle'), 2000);\n }, []);\n\n const handleCopy = useCallback(async () => {\n if (copyState === 'copying') return;\n setCopyState('copying');\n try {\n await navigator.clipboard.writeText(code);\n setCopyState('success');\n scheduleReset();\n } catch {\n try {\n const textarea = document.createElement('textarea');\n textarea.value = code;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n document.body.appendChild(textarea);\n textarea.select();\n document.execCommand('copy');\n document.body.removeChild(textarea);\n setCopyState('success');\n } catch {\n setCopyState('error');\n }\n scheduleReset();\n }\n }, [code, copyState, scheduleReset]);\n\n return (\n <div className=\"group relative my-3 overflow-hidden rounded-lg border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-[#1a1d24]\">\n <div className=\"flex items-center justify-between border-b border-neutral-200 px-3 py-1.5 dark:border-white/[0.08]\">\n <span className=\"text-xs font-medium text-neutral-500 dark:text-neutral-400\">\n {language\n ? language.charAt(0).toUpperCase() + language.slice(1)\n : 'Text'}\n </span>\n <div className=\"flex items-center gap-1\">\n {headerActions}\n {supportsRawView && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setShowRaw((v) => !v)}\n aria-label={showRaw ? 'Show rendered view' : 'Show raw code'}\n className=\"h-7 w-7 p-0\"\n >\n {showRaw ? (\n <Eye className=\"h-3.5 w-3.5\" />\n ) : (\n <Code className=\"h-3.5 w-3.5\" />\n )}\n </Button>\n )}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleCopy}\n aria-label=\"Copy code\"\n className=\"h-7 w-7 p-0\"\n >\n {copyState === 'success' ? (\n <Check className=\"h-3.5 w-3.5 text-green-500\" />\n ) : (\n <ClipboardCopy className=\"h-3.5 w-3.5\" />\n )}\n </Button>\n </div>\n </div>\n\n {error ? (\n <div className=\"p-3\">\n <div className=\"rounded-md bg-red-50 p-3 text-sm text-red-800 dark:bg-red-900/20 dark:text-red-300\">\n {error}\n </div>\n <pre className=\"mt-2 overflow-x-auto text-xs whitespace-pre-wrap text-neutral-600 dark:text-neutral-400\">\n <code>{code}</code>\n </pre>\n </div>\n ) : showRaw ? (\n <pre className=\"overflow-x-auto p-3 text-sm\">\n <code>{code}</code>\n </pre>\n ) : (\n <div className=\"overflow-x-auto\">{children}</div>\n )}\n </div>\n );\n};\n"]}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { FenceBlock } from './chunk-MHJMUKLA.js';
|
|
2
|
+
import { Button } from './chunk-ZVSW2KS6.js';
|
|
3
|
+
import { Code, Eye, Maximize2 } from 'lucide-react';
|
|
4
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
5
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function isFullDocument(html) {
|
|
8
|
+
return /<!DOCTYPE|<html/i.test(html);
|
|
9
|
+
}
|
|
10
|
+
function wrapFragment(html) {
|
|
11
|
+
return `<!DOCTYPE html>
|
|
12
|
+
<html>
|
|
13
|
+
<head><meta charset="utf-8"><style>body{margin:0;padding:8px;font-family:system-ui,sans-serif;font-size:14px}</style></head>
|
|
14
|
+
<body>${html}</body>
|
|
15
|
+
</html>`;
|
|
16
|
+
}
|
|
17
|
+
var HtmlPreviewBlock = ({
|
|
18
|
+
code,
|
|
19
|
+
id
|
|
20
|
+
}) => {
|
|
21
|
+
const [showPreview, setShowPreview] = useState(false);
|
|
22
|
+
const [expanded, setExpanded] = useState(false);
|
|
23
|
+
const iframeRef = useRef(null);
|
|
24
|
+
const [iframeHeight, setIframeHeight] = useState(200);
|
|
25
|
+
const srcdoc = isFullDocument(code) ? code : wrapFragment(code);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
function handleMessage(event) {
|
|
28
|
+
if (event.origin !== "null" && event.origin !== window.location.origin)
|
|
29
|
+
return;
|
|
30
|
+
if (event.source !== iframeRef.current?.contentWindow) return;
|
|
31
|
+
if (event.data?.type === "HTML_PREVIEW_RESIZE" && event.data?.id === id) {
|
|
32
|
+
setIframeHeight(Math.min(Math.max(event.data.height, 200), 4e3));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
window.addEventListener("message", handleMessage);
|
|
36
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
37
|
+
}, [id]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!expanded) return;
|
|
40
|
+
function handleKey(e) {
|
|
41
|
+
if (e.key === "Escape") setExpanded(false);
|
|
42
|
+
}
|
|
43
|
+
document.addEventListener("keydown", handleKey);
|
|
44
|
+
return () => document.removeEventListener("keydown", handleKey);
|
|
45
|
+
}, [expanded]);
|
|
46
|
+
const togglePreview = useCallback(() => setShowPreview((v) => !v), []);
|
|
47
|
+
const headerActions = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
48
|
+
/* @__PURE__ */ jsxs(
|
|
49
|
+
Button,
|
|
50
|
+
{
|
|
51
|
+
variant: "ghost",
|
|
52
|
+
size: "sm",
|
|
53
|
+
onClick: togglePreview,
|
|
54
|
+
className: "h-7 gap-1 px-2 text-xs",
|
|
55
|
+
children: [
|
|
56
|
+
showPreview ? /* @__PURE__ */ jsx(Code, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3 w-3" }),
|
|
57
|
+
showPreview ? "Code" : "Preview"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
showPreview && /* @__PURE__ */ jsx(
|
|
62
|
+
Button,
|
|
63
|
+
{
|
|
64
|
+
variant: "ghost",
|
|
65
|
+
size: "sm",
|
|
66
|
+
onClick: () => setExpanded(true),
|
|
67
|
+
className: "h-7 w-7 p-0",
|
|
68
|
+
"aria-label": "Expand preview",
|
|
69
|
+
children: /* @__PURE__ */ jsx(Maximize2, { className: "h-3 w-3" })
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
] });
|
|
73
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
74
|
+
/* @__PURE__ */ jsx(FenceBlock, { code, language: "html", headerActions, children: /* @__PURE__ */ jsx("div", { className: "relative", children: showPreview ? /* @__PURE__ */ jsx(
|
|
75
|
+
"iframe",
|
|
76
|
+
{
|
|
77
|
+
ref: iframeRef,
|
|
78
|
+
srcDoc: srcdoc,
|
|
79
|
+
sandbox: "allow-scripts allow-forms",
|
|
80
|
+
referrerPolicy: "no-referrer",
|
|
81
|
+
className: "w-full border-0",
|
|
82
|
+
style: { height: iframeHeight },
|
|
83
|
+
title: "HTML Preview"
|
|
84
|
+
}
|
|
85
|
+
) : /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto p-3 text-sm", children: /* @__PURE__ */ jsx("code", { children: code }) }) }) }),
|
|
86
|
+
expanded && /* @__PURE__ */ jsxs(
|
|
87
|
+
"div",
|
|
88
|
+
{
|
|
89
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50",
|
|
90
|
+
role: "dialog",
|
|
91
|
+
"aria-modal": "true",
|
|
92
|
+
"aria-label": "HTML Preview",
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
"button",
|
|
96
|
+
{
|
|
97
|
+
type: "button",
|
|
98
|
+
"aria-label": "Close preview",
|
|
99
|
+
className: "absolute inset-0 h-full w-full cursor-default border-0 bg-transparent p-0",
|
|
100
|
+
onClick: () => setExpanded(false)
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { className: "relative h-[90vh] w-[90vw] overflow-hidden rounded-lg bg-white shadow-xl dark:bg-neutral-900", children: [
|
|
104
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-neutral-200 px-4 py-2 dark:border-neutral-700", children: [
|
|
105
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "HTML Preview" }),
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
Button,
|
|
108
|
+
{
|
|
109
|
+
variant: "ghost",
|
|
110
|
+
size: "sm",
|
|
111
|
+
onClick: () => setExpanded(false),
|
|
112
|
+
children: "Close"
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
] }),
|
|
116
|
+
/* @__PURE__ */ jsx(
|
|
117
|
+
"iframe",
|
|
118
|
+
{
|
|
119
|
+
srcDoc: srcdoc,
|
|
120
|
+
sandbox: "allow-scripts allow-forms",
|
|
121
|
+
referrerPolicy: "no-referrer",
|
|
122
|
+
className: "h-full w-full border-0",
|
|
123
|
+
title: "HTML Preview (expanded)"
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
] })
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
] });
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export { HtmlPreviewBlock };
|
|
134
|
+
//# sourceMappingURL=chunk-NCRYCG5G.js.map
|
|
135
|
+
//# sourceMappingURL=chunk-NCRYCG5G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Markdown/HtmlPreviewBlock.tsx"],"names":[],"mappings":"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","file":"chunk-NCRYCG5G.js","sourcesContent":["/** Sandboxed iframe preview for HTML code blocks. */\nimport { Code, Eye, Maximize2 } from 'lucide-react';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { Button } from '../Button';\nimport { FenceBlock } from './FenceBlock';\n\ninterface HtmlPreviewBlockProps {\n code: string;\n id: string;\n}\n\nfunction isFullDocument(html: string): boolean {\n return /<!DOCTYPE|<html/i.test(html);\n}\n\nfunction wrapFragment(html: string): string {\n return `<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\"><style>body{margin:0;padding:8px;font-family:system-ui,sans-serif;font-size:14px}</style></head>\n<body>${html}</body>\n</html>`;\n}\n\nexport const HtmlPreviewBlock: React.FC<HtmlPreviewBlockProps> = ({\n code,\n id,\n}) => {\n const [showPreview, setShowPreview] = useState(false);\n const [expanded, setExpanded] = useState(false);\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const [iframeHeight, setIframeHeight] = useState(200);\n\n const srcdoc = isFullDocument(code) ? code : wrapFragment(code);\n\n useEffect(() => {\n function handleMessage(event: globalThis.MessageEvent) {\n // Sandboxed srcDoc iframes report origin \"null\"; validate source to prevent spoofing.\n if (event.origin !== 'null' && event.origin !== window.location.origin)\n return;\n if (event.source !== iframeRef.current?.contentWindow) return;\n if (event.data?.type === 'HTML_PREVIEW_RESIZE' && event.data?.id === id) {\n setIframeHeight(Math.min(Math.max(event.data.height, 200), 4000));\n }\n }\n window.addEventListener('message', handleMessage);\n return () => window.removeEventListener('message', handleMessage);\n }, [id]);\n\n useEffect(() => {\n if (!expanded) return;\n function handleKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setExpanded(false);\n }\n document.addEventListener('keydown', handleKey);\n return () => document.removeEventListener('keydown', handleKey);\n }, [expanded]);\n\n const togglePreview = useCallback(() => setShowPreview((v) => !v), []);\n\n const headerActions = (\n <>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={togglePreview}\n className=\"h-7 gap-1 px-2 text-xs\"\n >\n {showPreview ? (\n <Code className=\"h-3 w-3\" />\n ) : (\n <Eye className=\"h-3 w-3\" />\n )}\n {showPreview ? 'Code' : 'Preview'}\n </Button>\n {showPreview && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setExpanded(true)}\n className=\"h-7 w-7 p-0\"\n aria-label=\"Expand preview\"\n >\n <Maximize2 className=\"h-3 w-3\" />\n </Button>\n )}\n </>\n );\n\n return (\n <>\n <FenceBlock code={code} language=\"html\" headerActions={headerActions}>\n <div className=\"relative\">\n {showPreview ? (\n <iframe\n ref={iframeRef}\n srcDoc={srcdoc}\n // `allow-scripts` lets previewed HTML run its own JS (charts, demos,\n // and the optional resize-postMessage handshake). It is intentionally\n // NOT paired with `allow-same-origin`, so the content runs in an\n // opaque (null) origin with no access to this page, cookies, or storage.\n sandbox=\"allow-scripts allow-forms\"\n referrerPolicy=\"no-referrer\"\n className=\"w-full border-0\"\n style={{ height: iframeHeight }}\n title=\"HTML Preview\"\n />\n ) : (\n <pre className=\"overflow-x-auto p-3 text-sm\">\n <code>{code}</code>\n </pre>\n )}\n </div>\n </FenceBlock>\n\n {expanded && (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"HTML Preview\"\n >\n <button\n type=\"button\"\n aria-label=\"Close preview\"\n className=\"absolute inset-0 h-full w-full cursor-default border-0 bg-transparent p-0\"\n onClick={() => setExpanded(false)}\n />\n <div className=\"relative h-[90vh] w-[90vw] overflow-hidden rounded-lg bg-white shadow-xl dark:bg-neutral-900\">\n <div className=\"flex items-center justify-between border-b border-neutral-200 px-4 py-2 dark:border-neutral-700\">\n <span className=\"text-sm font-medium\">HTML Preview</span>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setExpanded(false)}\n >\n Close\n </Button>\n </div>\n <iframe\n srcDoc={srcdoc}\n sandbox=\"allow-scripts allow-forms\"\n referrerPolicy=\"no-referrer\"\n className=\"h-full w-full border-0\"\n title=\"HTML Preview (expanded)\"\n />\n </div>\n </div>\n )}\n </>\n );\n};\n"]}
|