UNPKG

react-markdown-fences

Version:

Render interactive fence dialects (buttons, charts, diagrams) inside Markdown for React applications

526 lines (520 loc) 16.1 kB
'use strict'; var ReactMarkdown = require('react-markdown'); var remarkGfm = require('remark-gfm'); var React2 = require('react'); var jsxRuntime = require('react/jsx-runtime'); var zod = require('zod'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown); var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm); var React2__namespace = /*#__PURE__*/_interopNamespace(React2); // src/ChatMarkdown.tsx var Button = ({ label, action, variant = "primary", size = "md", payload, onAction, buttonDefaults }) => { const getVariantStyles = () => { switch (variant) { case "primary": return { backgroundColor: "#000000", color: "#ffffff", borderColor: "#000000" }; case "success": return { backgroundColor: "#16a34a", color: "#ffffff", borderColor: "#16a34a" }; case "warning": return { backgroundColor: "#eab308", color: "#000000", borderColor: "#eab308" }; case "destructive": return { backgroundColor: "#dc2626", color: "#ffffff", borderColor: "#dc2626" }; default: return { backgroundColor: "#000000", color: "#ffffff", borderColor: "#000000" }; } }; const getSizeStyles = () => { switch (size) { case "xs": return { fontSize: "12px", padding: "4px 8px" }; case "sm": return { fontSize: "14px", padding: "6px 12px" }; case "lg": return { fontSize: "16px", padding: "12px 16px" }; default: return { fontSize: "14px", padding: "8px 12px" }; } }; const baseStyles = { display: "inline-flex", alignItems: "center", border: "1px solid", fontWeight: "500", cursor: "pointer", transition: "all 0.2s ease", textDecoration: "none", outline: "none" }; const buttonStyles = { ...baseStyles, ...getVariantStyles(), ...getSizeStyles(), // Developer defaults override everything ...buttonDefaults?.backgroundColor && { backgroundColor: buttonDefaults.backgroundColor }, ...buttonDefaults?.color && { color: buttonDefaults.color }, ...buttonDefaults?.borderColor && { borderColor: buttonDefaults.borderColor }, ...buttonDefaults?.borderRadius && { borderRadius: buttonDefaults.borderRadius }, ...buttonDefaults?.fontSize && { fontSize: buttonDefaults.fontSize }, ...buttonDefaults?.padding && { padding: buttonDefaults.padding }, ...buttonDefaults?.fontWeight && { fontWeight: buttonDefaults.fontWeight } }; const defaultHoverStyles = { opacity: 0.9, transform: "translateY(-1px)" }; const hoverStyles = { ...defaultHoverStyles, ...buttonDefaults?.hoverBackgroundColor && { backgroundColor: buttonDefaults.hoverBackgroundColor }, ...buttonDefaults?.hoverColor && { color: buttonDefaults.hoverColor }, ...buttonDefaults?.hoverTransform && { transform: buttonDefaults.hoverTransform } }; const [isHovered, setIsHovered] = React2__namespace.useState(false); const handleClick = () => { onAction?.(action, payload); }; return /* @__PURE__ */ jsxRuntime.jsx( "button", { style: { ...buttonStyles, ...isHovered ? hoverStyles : {} }, onClick: handleClick, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: label } ); }; var ButtonGroup = ({ buttons, onAction, buttonDefaults }) => { const containerStyles = { display: "flex", flexWrap: "wrap", gap: "8px", alignItems: "flex-start" }; return /* @__PURE__ */ jsxRuntime.jsx("div", { style: containerStyles, children: buttons.map((button, index) => /* @__PURE__ */ jsxRuntime.jsx( Button, { ...button, onAction, buttonDefaults }, index )) }); }; var Chart; var SUPPORTED_CHART_TYPES = ["bar", "line"]; var ChartJSBlock = ({ spec, chartDefaults }) => { const canvasRef = React2__namespace.useRef(null); const chartRef = React2__namespace.useRef(null); const [isLoading, setIsLoading] = React2__namespace.useState(true); const [error, setError] = React2__namespace.useState(null); if (!SUPPORTED_CHART_TYPES.includes(spec.type)) { return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "16px", border: "1px solid #fca5a5", borderRadius: "8px", backgroundColor: "#fee2e2", color: "#dc2626", fontSize: "14px" }, children: [ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Chart type not supported:" }), " '", spec.type, "'. Only 'bar' and 'line' charts are supported." ] }); } React2__namespace.useEffect(() => { let mounted = true; const loadChart = async () => { try { if (!Chart) { const mod = await import('chart.js/auto'); Chart = mod.Chart || mod.default; } if (!mounted || !canvasRef.current) return; if (chartRef.current) { chartRef.current.destroy(); } const mergedOptions = { ...spec.options, // Apply developer defaults to chart options ...chartDefaults?.backgroundColor && { backgroundColor: chartDefaults.backgroundColor }, plugins: { ...spec.options?.plugins, legend: { ...spec.options?.plugins?.legend, labels: { ...spec.options?.plugins?.legend?.labels, ...chartDefaults?.fontFamily && { font: { family: chartDefaults.fontFamily } }, ...chartDefaults?.fontSize && { font: { size: chartDefaults.fontSize } } } } }, scales: { ...spec.options?.scales, ...chartDefaults?.gridColor && { x: { ...spec.options?.scales?.x, grid: { ...spec.options?.scales?.x?.grid, color: chartDefaults.gridColor } }, y: { ...spec.options?.scales?.y, grid: { ...spec.options?.scales?.y?.grid, color: chartDefaults.gridColor } } } } }; const mergedSpec = { ...spec, options: mergedOptions, ...chartDefaults?.borderColor && { data: { ...spec.data, datasets: spec.data.datasets?.map((dataset) => ({ ...dataset, borderColor: dataset.borderColor || chartDefaults.borderColor })) } } }; chartRef.current = new Chart(canvasRef.current, mergedSpec); setIsLoading(false); } catch (err) { if (mounted) { setError(err instanceof Error ? err.message : "Failed to load chart"); setIsLoading(false); } } }; loadChart(); return () => { mounted = false; if (chartRef.current) { chartRef.current.destroy(); } }; }, [spec, chartDefaults]); if (error) { return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "16px", border: "1px solid #e5e7eb", borderRadius: "8px", backgroundColor: "#f9fafb", color: "#dc2626", fontSize: "14px" }, children: [ "Chart Error: ", error ] }); } if (isLoading) { return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "16px", border: "1px solid #e5e7eb", borderRadius: "8px", backgroundColor: "#f9fafb", color: "#6b7280", fontSize: "14px", textAlign: "center" }, children: "Loading chart..." }); } const containerStyle = { width: chartDefaults?.width || "100%", height: chartDefaults?.height || "320px", position: "relative", ...chartDefaults?.backgroundColor && { backgroundColor: chartDefaults.backgroundColor } }; return /* @__PURE__ */ jsxRuntime.jsx("div", { style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx( "canvas", { ref: canvasRef, style: { width: "100%", height: "100%", maxWidth: "100%", maxHeight: "100%" } } ) }); }; var mermaidLib; var MermaidBlock = ({ code }) => { const id = React2__namespace.useMemo(() => "m" + Math.random().toString(36).slice(2), []); const hostRef = React2__namespace.useRef(null); const [isLoading, setIsLoading] = React2__namespace.useState(true); const [error, setError] = React2__namespace.useState(null); React2__namespace.useEffect(() => { let mounted = true; const loadMermaid = async () => { try { if (!mermaidLib) { const mod = await import('mermaid'); mermaidLib = mod.default || mod; mermaidLib.initialize({ startOnLoad: false, securityLevel: "strict", theme: "default" }); } if (!mounted || !hostRef.current) return; if (hostRef.current) { hostRef.current.innerHTML = ""; } const { svg } = await mermaidLib.render(id, code); if (hostRef.current && mounted) { hostRef.current.innerHTML = svg; setIsLoading(false); } } catch (err) { if (mounted) { setError(err instanceof Error ? err.message : "Failed to render diagram"); setIsLoading(false); } } }; loadMermaid(); return () => { mounted = false; }; }, [code, id]); if (error) { return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "16px", border: "1px solid #e5e7eb", borderRadius: "8px", backgroundColor: "#f9fafb", color: "#dc2626", fontSize: "14px" }, children: [ "Mermaid Error: ", error ] }); } if (isLoading) { return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "16px", border: "1px solid #e5e7eb", borderRadius: "8px", backgroundColor: "#f9fafb", color: "#6b7280", fontSize: "14px", textAlign: "center" }, children: "Loading diagram..." }); } return /* @__PURE__ */ jsxRuntime.jsx( "div", { ref: hostRef, style: { width: "100%", display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px" } } ); }; var ButtonSpec = zod.z.object({ label: zod.z.string(), action: zod.z.string(), variant: zod.z.enum(["primary", "success", "warning", "destructive"]).optional(), size: zod.z.enum(["xs", "sm", "md", "lg"]).optional(), payload: zod.z.unknown().optional() }); var ButtonGroupSpec = zod.z.array(ButtonSpec); var ButtonDefaults = zod.z.object({ backgroundColor: zod.z.string().optional(), color: zod.z.string().optional(), borderColor: zod.z.string().optional(), borderRadius: zod.z.string().optional(), fontSize: zod.z.string().optional(), padding: zod.z.string().optional(), fontWeight: zod.z.string().optional(), hoverBackgroundColor: zod.z.string().optional(), hoverColor: zod.z.string().optional(), hoverTransform: zod.z.string().optional() }).optional(); var ChartDefaults = zod.z.object({ width: zod.z.string().optional(), height: zod.z.string().optional(), backgroundColor: zod.z.string().optional(), borderColor: zod.z.string().optional(), gridColor: zod.z.string().optional(), fontFamily: zod.z.string().optional(), fontSize: zod.z.number().optional() }).optional(); var ChartJSSpec = zod.z.object({ type: zod.z.enum(["bar", "line"]), data: zod.z.record(zod.z.string(), zod.z.any()), options: zod.z.record(zod.z.string(), zod.z.any()).optional() }); var safeJSON = (raw, parse) => { try { return parse(JSON.parse(raw)); } catch { return null; } }; var defaultFences = { button: ({ raw, onAction, buttonDefaults }) => { const spec = safeJSON( raw, (v) => ButtonSpec.safeParse(v).success ? v : null ); return spec ? /* @__PURE__ */ jsxRuntime.jsx(Button, { ...spec, onAction, buttonDefaults }) : /* @__PURE__ */ jsxRuntime.jsx("pre", { children: raw }); }, buttongroup: ({ raw, onAction, buttonDefaults }) => { const spec = safeJSON( raw, (v) => ButtonGroupSpec.safeParse(v).success ? v : null ); return spec ? /* @__PURE__ */ jsxRuntime.jsx(ButtonGroup, { buttons: spec, onAction, buttonDefaults }) : /* @__PURE__ */ jsxRuntime.jsx("pre", { children: raw }); }, chartjs: ({ raw, chartDefaults }) => { const spec = safeJSON( raw, (v) => ChartJSSpec.safeParse(v).success ? v : null ); return spec ? /* @__PURE__ */ jsxRuntime.jsx(ChartJSBlock, { spec, chartDefaults }) : /* @__PURE__ */ jsxRuntime.jsx("pre", { children: raw }); }, mermaid: ({ raw }) => /* @__PURE__ */ jsxRuntime.jsx(MermaidBlock, { code: raw }) }; var ChatMarkdown = ({ markdown, fences = defaultFences, onAction, showUnknown = true, buttonDefaults, chartDefaults }) => { return /* @__PURE__ */ jsxRuntime.jsx( ReactMarkdown__default.default, { remarkPlugins: [remarkGfm__default.default], components: { code({ inline, className, children, ...props }) { if (inline) { return /* @__PURE__ */ jsxRuntime.jsx( "code", { className, style: { backgroundColor: "#f1f5f9", padding: "2px 4px", borderRadius: "4px", fontSize: "0.875em", fontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace" }, ...props, children } ); } const lang = (className || "").replace("language-", "").trim().toLowerCase(); const raw = String(children ?? "").trim(); const render = fences[lang]; if (render) { return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { margin: "8px 0" }, children: render({ raw, onAction, buttonDefaults, chartDefaults }) }); } return showUnknown ? /* @__PURE__ */ jsxRuntime.jsx( "pre", { className, style: { backgroundColor: "#f8fafc", border: "1px solid #e2e8f0", borderRadius: "8px", padding: "16px", overflow: "auto", fontSize: "14px", fontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace", lineHeight: "1.5", margin: "8px 0" }, children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: raw }) } ) : null; } }, children: markdown } ); }; exports.ButtonDefaults = ButtonDefaults; exports.ButtonGroupSpec = ButtonGroupSpec; exports.ButtonSpec = ButtonSpec; exports.ChartDefaults = ChartDefaults; exports.ChartJSSpec = ChartJSSpec; exports.ChatMarkdown = ChatMarkdown; exports.defaultFences = defaultFences; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map