@windrun-huaiin/third-ui 21.0.0 → 22.0.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/ai/ai-markdown.js +1 -1
- package/dist/ai/ai-markdown.mjs +1 -1
- package/dist/clerk/clerk-page-context-generator.js +3 -2
- package/dist/clerk/clerk-page-context-generator.mjs +3 -2
- package/dist/clerk/clerk-page-generator.js +4 -3
- package/dist/clerk/clerk-page-generator.mjs +4 -3
- package/dist/fuma/base/custom-header.d.ts +1 -1
- package/dist/fuma/base/custom-header.js +38 -36
- package/dist/fuma/base/custom-header.mjs +25 -23
- package/dist/fuma/base/custom-home-layout.d.ts +1 -1
- package/dist/fuma/base/custom-home-layout.js +1 -1
- package/dist/fuma/base/custom-home-layout.mjs +1 -1
- package/dist/fuma/base/header-theme-switch.d.ts +5 -0
- package/dist/fuma/base/header-theme-switch.js +42 -0
- package/dist/fuma/base/header-theme-switch.mjs +40 -0
- package/dist/fuma/base/index.d.ts +1 -0
- package/dist/fuma/base/index.js +7 -0
- package/dist/fuma/base/index.mjs +1 -0
- package/dist/fuma/base/site-layout.d.ts +116 -0
- package/dist/fuma/base/site-layout.js +72 -0
- package/dist/fuma/base/site-layout.mjs +65 -0
- package/dist/fuma/fuma-banner-suit.js +9 -6
- package/dist/fuma/fuma-banner-suit.mjs +10 -7
- package/dist/fuma/fuma-page-genarator.js +1 -1
- package/dist/fuma/fuma-page-genarator.mjs +1 -1
- package/dist/fuma/heavy/image-grid.d.ts +6 -0
- package/dist/fuma/heavy/image-grid.js +17 -0
- package/dist/fuma/heavy/image-grid.mjs +15 -0
- package/dist/fuma/heavy/image-zoom.d.ts +22 -0
- package/dist/fuma/heavy/image-zoom.js +39 -0
- package/dist/fuma/heavy/image-zoom.mjs +37 -0
- package/dist/fuma/heavy/index.d.ts +4 -0
- package/dist/fuma/heavy/index.js +15 -0
- package/dist/fuma/heavy/index.mjs +5 -0
- package/dist/fuma/heavy/math.d.ts +17 -0
- package/dist/fuma/heavy/math.js +60 -0
- package/dist/fuma/heavy/math.mjs +57 -0
- package/dist/fuma/heavy/mermaid.d.ts +13 -0
- package/dist/fuma/heavy/mermaid.js +360 -0
- package/dist/fuma/heavy/mermaid.mjs +358 -0
- package/dist/fuma/mdx/features.d.ts +8 -0
- package/dist/fuma/mdx/features.js +92 -0
- package/dist/fuma/mdx/features.mjs +85 -0
- package/dist/fuma/mdx/index.d.ts +0 -5
- package/dist/fuma/mdx/index.js +0 -11
- package/dist/fuma/mdx/index.mjs +0 -5
- package/dist/fuma/mdx/markdown-component-map.js +7 -1
- package/dist/fuma/mdx/markdown-component-map.mjs +7 -1
- package/dist/fuma/mdx/site-mdx-components.d.ts +13 -0
- package/dist/fuma/mdx/site-mdx-components.js +19 -0
- package/dist/fuma/mdx/site-mdx-components.mjs +17 -0
- package/dist/fuma/mdx/site-mdx-presets.d.ts +13 -0
- package/dist/fuma/mdx/site-mdx-presets.js +49 -0
- package/dist/fuma/mdx/site-mdx-presets.mjs +45 -0
- package/dist/fuma/mdx/toc-clerk-portable.js +9 -5
- package/dist/fuma/mdx/toc-clerk-portable.mjs +8 -4
- package/dist/fuma/mdx/zia-file.js +1 -0
- package/dist/fuma/mdx/zia-file.mjs +1 -0
- package/dist/fuma/server/optional-features.d.ts +8 -0
- package/dist/fuma/server/optional-features.js +111 -0
- package/dist/fuma/server/optional-features.mjs +104 -0
- package/dist/fuma/server/site-mdx-components.d.ts +13 -0
- package/dist/fuma/server/site-mdx-components.js +19 -0
- package/dist/fuma/server/site-mdx-components.mjs +17 -0
- package/dist/fuma/server/site-mdx-presets.d.ts +194 -0
- package/dist/fuma/server/site-mdx-presets.js +46 -0
- package/dist/fuma/server/site-mdx-presets.mjs +42 -0
- package/dist/fuma/share/index.d.ts +1 -0
- package/dist/fuma/share/index.js +7 -0
- package/dist/fuma/share/index.mjs +1 -0
- package/dist/fuma/share/markdown-component-map.d.ts +3 -0
- package/dist/fuma/share/markdown-component-map.js +79 -0
- package/dist/fuma/share/markdown-component-map.mjs +77 -0
- package/dist/lib/fuma-schema-check-util.js +19 -5
- package/dist/lib/fuma-schema-check-util.mjs +19 -5
- package/dist/lib/seo-metadata.d.ts +10 -0
- package/dist/main/x-button.js +2 -2
- package/dist/main/x-button.mjs +2 -2
- package/package.json +31 -8
- package/src/ai/ai-markdown.tsx +1 -1
- package/src/clerk/clerk-page-context-generator.tsx +6 -3
- package/src/clerk/clerk-page-generator.tsx +7 -4
- package/src/fuma/base/custom-header.tsx +32 -35
- package/src/fuma/base/custom-home-layout.tsx +2 -2
- package/src/fuma/base/header-theme-switch.tsx +88 -0
- package/src/fuma/base/index.ts +1 -0
- package/src/fuma/base/site-layout.tsx +289 -0
- package/src/fuma/fuma-banner-suit.tsx +30 -28
- package/src/fuma/fuma-page-genarator.tsx +1 -1
- package/src/fuma/{mdx → heavy}/image-grid.tsx +1 -1
- package/src/fuma/heavy/index.ts +7 -0
- package/src/fuma/mdx/index.ts +0 -5
- package/src/fuma/mdx/toc-clerk-portable.tsx +27 -24
- package/src/fuma/mdx/zia-file.tsx +3 -1
- package/src/fuma/server/optional-features.tsx +168 -0
- package/src/fuma/server/site-mdx-components.tsx +48 -0
- package/src/fuma/server/site-mdx-presets.ts +80 -0
- package/src/fuma/share/index.ts +1 -0
- package/src/fuma/{mdx → share}/markdown-component-map.tsx +1 -1
- package/src/lib/fuma-schema-check-util.ts +22 -6
- package/src/lib/seo-metadata.ts +47 -0
- package/src/main/x-button.tsx +2 -2
- package/src/styles/fuma.css +3 -7
- package/src/styles/third-ui.css +0 -4
- /package/src/fuma/{mdx → heavy}/image-zoom.tsx +0 -0
- /package/src/fuma/{mdx → heavy}/math.tsx +0 -0
- /package/src/fuma/{mdx → heavy}/mermaid.tsx +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { __rest } from 'tslib';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import katex from 'katex';
|
|
4
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
5
|
+
|
|
6
|
+
const alignClassMap = {
|
|
7
|
+
left: 'text-left justify-start',
|
|
8
|
+
center: 'text-center justify-center',
|
|
9
|
+
right: 'text-right justify-end',
|
|
10
|
+
};
|
|
11
|
+
const textAlignClassMap = {
|
|
12
|
+
left: 'text-left',
|
|
13
|
+
center: 'text-center',
|
|
14
|
+
right: 'text-right',
|
|
15
|
+
};
|
|
16
|
+
function getMathSource({ children, math, formula }) {
|
|
17
|
+
if (typeof math === 'string' && math.trim() !== '')
|
|
18
|
+
return math.trim();
|
|
19
|
+
if (typeof formula === 'string' && formula.trim() !== '')
|
|
20
|
+
return formula.trim();
|
|
21
|
+
if (typeof children === 'string' && children.trim() !== '')
|
|
22
|
+
return children.trim();
|
|
23
|
+
if (Array.isArray(children)) {
|
|
24
|
+
const text = children
|
|
25
|
+
.map((item) => (typeof item === 'string' ? item : ''))
|
|
26
|
+
.join('')
|
|
27
|
+
.trim();
|
|
28
|
+
if (text !== '')
|
|
29
|
+
return text;
|
|
30
|
+
}
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
function renderMath(source, displayMode) {
|
|
34
|
+
if (source === '')
|
|
35
|
+
return '';
|
|
36
|
+
return katex.renderToString(source, {
|
|
37
|
+
displayMode,
|
|
38
|
+
throwOnError: false,
|
|
39
|
+
output: 'html',
|
|
40
|
+
strict: 'ignore',
|
|
41
|
+
trust: false,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function MathBlock(_a) {
|
|
45
|
+
var { title, titleAlign = 'center', children, math, formula, className } = _a, props = __rest(_a, ["title", "titleAlign", "children", "math", "formula", "className"]);
|
|
46
|
+
const source = getMathSource({ children, math, formula });
|
|
47
|
+
const html = renderMath(source, true);
|
|
48
|
+
return (jsxs("div", Object.assign({}, props, { className: cn('not-prose my-6 overflow-x-auto rounded-xl border bg-fd-card p-4 text-fd-card-foreground', className), children: [title ? (jsx("div", { className: cn('mb-3 text-sm font-medium text-fd-muted-foreground', alignClassMap[titleAlign].split(' ')[0]), children: title })) : null, html ? (jsx("div", { className: "min-w-fit [&_.katex-display]:my-0 [&_.katex-display]:overflow-x-auto [&_.katex-display]:overflow-y-hidden", dangerouslySetInnerHTML: { __html: html } })) : (jsx("div", { className: "text-sm text-fd-muted-foreground", children: "Empty math block." }))] })));
|
|
49
|
+
}
|
|
50
|
+
function InlineMath(_a) {
|
|
51
|
+
var { children, math, formula, align = 'center', className } = _a, props = __rest(_a, ["children", "math", "formula", "align", "className"]);
|
|
52
|
+
const source = getMathSource({ children, math, formula });
|
|
53
|
+
const html = renderMath(source, false);
|
|
54
|
+
return (jsx("span", Object.assign({}, props, { className: cn('mx-1 inline-flex max-w-full align-middle rounded-md bg-neutral-200 px-2 py-0.5 text-sm leading-none dark:bg-white/20 [&_.katex]:text-inherit', textAlignClassMap[align], className), children: jsx("span", { dangerouslySetInnerHTML: { __html: html } }) })));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { InlineMath, MathBlock };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface MermaidProps {
|
|
2
|
+
chart: string;
|
|
3
|
+
title?: string;
|
|
4
|
+
watermarkEnabled?: boolean;
|
|
5
|
+
watermarkText?: string;
|
|
6
|
+
handDrawn?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* enable preview dialog by clicking the chart, default is true
|
|
9
|
+
*/
|
|
10
|
+
enablePreview?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function Mermaid({ chart, title, watermarkEnabled, watermarkText, handDrawn, enablePreview }: MermaidProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var tslib = require('tslib');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var icons = require('@windrun-huaiin/base-ui/icons');
|
|
7
|
+
var utils = require('@windrun-huaiin/lib/utils');
|
|
8
|
+
var nextThemes = require('next-themes');
|
|
9
|
+
var rough = require('roughjs');
|
|
10
|
+
var React = require('react');
|
|
11
|
+
var lib = require('@windrun-huaiin/base-ui/lib');
|
|
12
|
+
|
|
13
|
+
function sanitizeFilename(name) {
|
|
14
|
+
return name
|
|
15
|
+
.replace(/[\/:*?"<>|]/g, '_')
|
|
16
|
+
.replace(/\s+/g, '_')
|
|
17
|
+
.slice(0, 120);
|
|
18
|
+
}
|
|
19
|
+
function Mermaid({ chart, title, watermarkEnabled, watermarkText, handDrawn = true, enablePreview = true }) {
|
|
20
|
+
const id = React.useId();
|
|
21
|
+
const [svg, setSvg] = React.useState('');
|
|
22
|
+
const { resolvedTheme } = nextThemes.useTheme();
|
|
23
|
+
const [open, setOpen] = React.useState(false);
|
|
24
|
+
// zoom & pan states for preview dialog
|
|
25
|
+
const [scale, setScale] = React.useState(1);
|
|
26
|
+
const [translate, setTranslate] = React.useState({ x: 0, y: 0 });
|
|
27
|
+
const isPanningRef = React.useRef(false);
|
|
28
|
+
const startPointRef = React.useRef({ x: 0, y: 0 });
|
|
29
|
+
const startTranslateRef = React.useRef({ x: 0, y: 0 });
|
|
30
|
+
const activePointersRef = React.useRef(new Map());
|
|
31
|
+
const pinchStartDistanceRef = React.useRef(0);
|
|
32
|
+
const pinchStartScaleRef = React.useRef(1);
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
let isMounted = true;
|
|
35
|
+
void renderChart();
|
|
36
|
+
function renderChart() {
|
|
37
|
+
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
const mermaidConfig = {
|
|
39
|
+
startOnLoad: false,
|
|
40
|
+
securityLevel: 'loose',
|
|
41
|
+
fontFamily: 'inherit',
|
|
42
|
+
themeCSS: 'margin: 1.5rem auto 0;',
|
|
43
|
+
theme: resolvedTheme === 'dark' ? 'dark' : 'default',
|
|
44
|
+
};
|
|
45
|
+
const { default: mermaid } = yield import('mermaid');
|
|
46
|
+
try {
|
|
47
|
+
mermaid.initialize(mermaidConfig);
|
|
48
|
+
const { svg } = yield mermaid.render(id.replaceAll(':', ''), chart.replaceAll('\\n', '\n'));
|
|
49
|
+
let svgWithWatermark = handDrawn ? applyHandDrawnStyle(svg) : svg;
|
|
50
|
+
if (watermarkEnabled && watermarkText) {
|
|
51
|
+
svgWithWatermark = addWatermarkToSvg(svgWithWatermark, watermarkText, lib.themeSvgIconColor);
|
|
52
|
+
}
|
|
53
|
+
if (isMounted)
|
|
54
|
+
setSvg(svgWithWatermark);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('Error while rendering mermaid', error);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return () => {
|
|
62
|
+
isMounted = false;
|
|
63
|
+
setSvg('');
|
|
64
|
+
};
|
|
65
|
+
}, [chart, id, resolvedTheme, watermarkEnabled, watermarkText, handDrawn]);
|
|
66
|
+
// helpers for preview zoom
|
|
67
|
+
const clamp = (v, min, max) => Math.min(Math.max(v, min), max);
|
|
68
|
+
const resetTransform = React.useCallback(() => {
|
|
69
|
+
setScale(4); // 400%
|
|
70
|
+
setTranslate({ x: 0, y: 0 });
|
|
71
|
+
}, []);
|
|
72
|
+
const zoomBy = React.useCallback((delta) => {
|
|
73
|
+
// zoom by center: keep the zoom center at the center of the canvas, without introducing displacement
|
|
74
|
+
setScale((prev) => clamp(prev + delta, 0.25, 10));
|
|
75
|
+
}, []);
|
|
76
|
+
const onWheel = React.useCallback((e) => {
|
|
77
|
+
// Cmd/Ctrl + wheel zoom (around the center point), otherwise up and down panning
|
|
78
|
+
if (e.metaKey || e.ctrlKey) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
e.stopPropagation();
|
|
81
|
+
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
82
|
+
setScale((prev) => clamp(prev + delta, 0.25, 10));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// two-finger pan on touchpad: support both horizontal (deltaX) and vertical (deltaY)
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
setTranslate((prev) => ({ x: prev.x - e.deltaX, y: prev.y - e.deltaY }));
|
|
89
|
+
}
|
|
90
|
+
}, []);
|
|
91
|
+
const onPointerDown = React.useCallback((e) => {
|
|
92
|
+
activePointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
93
|
+
if (activePointersRef.current.size === 2) {
|
|
94
|
+
const [first, second] = Array.from(activePointersRef.current.values());
|
|
95
|
+
pinchStartDistanceRef.current = Math.hypot(second.x - first.x, second.y - first.y);
|
|
96
|
+
pinchStartScaleRef.current = scale;
|
|
97
|
+
isPanningRef.current = false;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
isPanningRef.current = true;
|
|
101
|
+
startPointRef.current = { x: e.clientX, y: e.clientY };
|
|
102
|
+
startTranslateRef.current = Object.assign({}, translate);
|
|
103
|
+
}
|
|
104
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
105
|
+
}, [scale, translate]);
|
|
106
|
+
const onPointerMove = React.useCallback((e) => {
|
|
107
|
+
if (!activePointersRef.current.has(e.pointerId))
|
|
108
|
+
return;
|
|
109
|
+
activePointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
110
|
+
if (activePointersRef.current.size === 2) {
|
|
111
|
+
const [first, second] = Array.from(activePointersRef.current.values());
|
|
112
|
+
const distance = Math.hypot(second.x - first.x, second.y - first.y);
|
|
113
|
+
if (pinchStartDistanceRef.current > 0) {
|
|
114
|
+
setScale(clamp((distance / pinchStartDistanceRef.current) * pinchStartScaleRef.current, 0.25, 10));
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!isPanningRef.current)
|
|
119
|
+
return;
|
|
120
|
+
const dx = e.clientX - startPointRef.current.x;
|
|
121
|
+
const dy = e.clientY - startPointRef.current.y;
|
|
122
|
+
setTranslate({ x: startTranslateRef.current.x + dx, y: startTranslateRef.current.y + dy });
|
|
123
|
+
}, []);
|
|
124
|
+
const onPointerUp = React.useCallback((e) => {
|
|
125
|
+
activePointersRef.current.delete(e.pointerId);
|
|
126
|
+
isPanningRef.current = false;
|
|
127
|
+
if (activePointersRef.current.size === 1) {
|
|
128
|
+
const remaining = Array.from(activePointersRef.current.values())[0];
|
|
129
|
+
startPointRef.current = remaining;
|
|
130
|
+
startTranslateRef.current = Object.assign({}, translate);
|
|
131
|
+
isPanningRef.current = true;
|
|
132
|
+
}
|
|
133
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
134
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
135
|
+
}
|
|
136
|
+
}, [translate]);
|
|
137
|
+
const onPointerCancel = React.useCallback((e) => {
|
|
138
|
+
activePointersRef.current.delete(e.pointerId);
|
|
139
|
+
isPanningRef.current = false;
|
|
140
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
141
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
142
|
+
}
|
|
143
|
+
}, []);
|
|
144
|
+
const handleDownload = React.useCallback(() => {
|
|
145
|
+
if (!svg)
|
|
146
|
+
return;
|
|
147
|
+
const fileName = `${sanitizeFilename(title !== null && title !== void 0 ? title : 'mermaid')}.svg`;
|
|
148
|
+
const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
|
|
149
|
+
const url = URL.createObjectURL(blob);
|
|
150
|
+
const a = document.createElement('a');
|
|
151
|
+
a.href = url;
|
|
152
|
+
a.download = fileName;
|
|
153
|
+
document.body.appendChild(a);
|
|
154
|
+
a.click();
|
|
155
|
+
a.remove();
|
|
156
|
+
URL.revokeObjectURL(url);
|
|
157
|
+
}, [svg, title]);
|
|
158
|
+
// prevent browser-level zoom (touchpad pinch/shortcut) from taking effect when the dialog is open
|
|
159
|
+
React.useEffect(() => {
|
|
160
|
+
if (!open)
|
|
161
|
+
return;
|
|
162
|
+
// 初次打开时,默认放大到 400%
|
|
163
|
+
resetTransform();
|
|
164
|
+
const onGlobalWheel = (ev) => {
|
|
165
|
+
if (ev.ctrlKey || ev.metaKey) {
|
|
166
|
+
ev.preventDefault();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const onKeyDown = (ev) => {
|
|
170
|
+
if (!(ev.ctrlKey || ev.metaKey))
|
|
171
|
+
return;
|
|
172
|
+
const k = ev.key;
|
|
173
|
+
if (k === '=' || k === '+') {
|
|
174
|
+
ev.preventDefault();
|
|
175
|
+
setScale((prev) => clamp(prev + 0.2, 0.25, 10));
|
|
176
|
+
}
|
|
177
|
+
else if (k === '-') {
|
|
178
|
+
ev.preventDefault();
|
|
179
|
+
setScale((prev) => clamp(prev - 0.2, 0.25, 10));
|
|
180
|
+
}
|
|
181
|
+
else if (k === '0') {
|
|
182
|
+
ev.preventDefault();
|
|
183
|
+
resetTransform();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
window.addEventListener('wheel', onGlobalWheel, { passive: false, capture: true });
|
|
187
|
+
window.addEventListener('keydown', onKeyDown, { capture: true });
|
|
188
|
+
return () => {
|
|
189
|
+
window.removeEventListener('wheel', onGlobalWheel, true);
|
|
190
|
+
window.removeEventListener('keydown', onKeyDown, true);
|
|
191
|
+
};
|
|
192
|
+
}, [open, resetTransform]);
|
|
193
|
+
// Lock background scroll when dialog is open
|
|
194
|
+
React.useEffect(() => {
|
|
195
|
+
if (!open)
|
|
196
|
+
return;
|
|
197
|
+
const previousPosition = document.body.style.position;
|
|
198
|
+
const previousTop = document.body.style.top;
|
|
199
|
+
const previousLeft = document.body.style.left;
|
|
200
|
+
const previousRight = document.body.style.right;
|
|
201
|
+
const previousWidth = document.body.style.width;
|
|
202
|
+
const scrollY = window.scrollY;
|
|
203
|
+
document.body.style.position = 'fixed';
|
|
204
|
+
document.body.style.top = `-${scrollY}px`;
|
|
205
|
+
document.body.style.left = '0';
|
|
206
|
+
document.body.style.right = '0';
|
|
207
|
+
document.body.style.width = '100%';
|
|
208
|
+
return () => {
|
|
209
|
+
document.body.style.position = previousPosition;
|
|
210
|
+
document.body.style.top = previousTop;
|
|
211
|
+
document.body.style.left = previousLeft;
|
|
212
|
+
document.body.style.right = previousRight;
|
|
213
|
+
document.body.style.width = previousWidth;
|
|
214
|
+
window.scrollTo(0, scrollY);
|
|
215
|
+
};
|
|
216
|
+
}, [open]);
|
|
217
|
+
return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: enablePreview ? 'group relative cursor-zoom-in' : undefined, onClick: () => enablePreview && svg && setOpen(true), children: [jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: svg } }), enablePreview && svg && (jsxRuntime.jsx("div", { className: "pointer-events-none absolute right-2 top-2 hidden rounded bg-black/50 px-2 py-0.5 text-[12px] text-white group-hover:block", children: "Preview Chart" }))] }), title && (jsxRuntime.jsxs("div", { className: utils.cn("mt-2 flex items-center justify-center text-center text-[13px] font-italic", lib.themeIconColor), children: [jsxRuntime.jsx(icons.MmdIcon, { className: 'mr-1 h-4 w-4' }), jsxRuntime.jsx("span", { children: title })] })), enablePreview && open && (jsxRuntime.jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": typeof title === 'string' ? title : 'Mermaid Preview', className: "fixed inset-0 z-9999 flex items-center justify-center", children: [jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/60", onClick: () => { setOpen(false); resetTransform(); }, onWheel: (e) => { e.preventDefault(); e.stopPropagation(); }, onTouchMove: (e) => { e.preventDefault(); e.stopPropagation(); } }), jsxRuntime.jsxs("div", { className: "relative z-1 max-w-[95vw] w-[95vw] h-[88vh] p-0 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-md shadow-2xl overflow-hidden", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 px-3 py-2 border-b border-neutral-200 dark:border-neutral-700", children: [jsxRuntime.jsxs("div", { className: utils.cn("min-w-0 flex items-center gap-2 text-sm", lib.themeIconColor), children: [jsxRuntime.jsx(icons.MmdIcon, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "truncate max-w-[50vw]", children: title !== null && title !== void 0 ? title : 'Mermaid Preview' })] }), jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [jsxRuntime.jsx("button", { "aria-label": "Zoom out", className: "hidden h-6 w-6 items-center justify-center rounded border border-neutral-300 text-[13px] transition-colors hover:bg-neutral-100 active:bg-neutral-200 hover:border-neutral-400 active:border-neutral-500 dark:border-neutral-600 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 dark:hover:border-neutral-500 dark:active:border-neutral-400 sm:flex", onClick: () => zoomBy(-0.5), children: "\uFF0D" }), jsxRuntime.jsxs("span", { className: "mx-0.5 w-12 text-center text-[12px] select-none", children: [Math.round(scale * 100), "%"] }), jsxRuntime.jsx("button", { "aria-label": "Zoom in", className: "hidden h-6 w-6 items-center justify-center rounded border border-neutral-300 text-[13px] transition-colors hover:bg-neutral-100 active:bg-neutral-200 hover:border-neutral-400 active:border-neutral-500 dark:border-neutral-600 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 dark:hover:border-neutral-500 dark:active:border-neutral-400 sm:flex", onClick: () => zoomBy(0.5), children: "\uFF0B" }), jsxRuntime.jsx("div", { className: "mx-1 hidden h-4 w-px bg-neutral-300 dark:bg-neutral-700 sm:block" }), jsxRuntime.jsx("button", { "aria-label": "Zoom 100%", className: "hidden h-6 min-w-8 items-center justify-center rounded border border-neutral-300 px-1.5 text-[12px] transition-colors hover:bg-neutral-100 active:bg-neutral-200 hover:border-neutral-400 active:border-neutral-500 dark:border-neutral-600 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 dark:hover:border-neutral-500 dark:active:border-neutral-400 sm:inline-flex", onClick: () => setScale(1), children: "X1" }), jsxRuntime.jsx("button", { "aria-label": "Zoom 200%", className: "ml-1 hidden h-6 min-w-8 items-center justify-center rounded border border-neutral-300 px-1.5 text-[12px] transition-colors hover:bg-neutral-100 active:bg-neutral-200 hover:border-neutral-400 active:border-neutral-500 dark:border-neutral-600 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 dark:hover:border-neutral-500 dark:active:border-neutral-400 sm:inline-flex", onClick: () => setScale(2), children: "X2" }), jsxRuntime.jsx("button", { "aria-label": "Zoom 300%", className: "ml-1 hidden h-6 min-w-8 items-center justify-center rounded border border-neutral-300 px-1.5 text-[12px] transition-colors hover:bg-neutral-100 active:bg-neutral-200 hover:border-neutral-400 active:border-neutral-500 dark:border-neutral-600 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 dark:hover:border-neutral-500 dark:active:border-neutral-400 sm:inline-flex", onClick: () => setScale(3), children: "X3" }), jsxRuntime.jsx("button", { "aria-label": "Zoom 1000%", className: "ml-1 hidden h-6 min-w-10 items-center justify-center rounded border border-neutral-300 px-1.5 text-[12px] transition-colors hover:bg-neutral-100 active:bg-neutral-200 hover:border-neutral-400 active:border-neutral-500 dark:border-neutral-600 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 dark:hover:border-neutral-500 dark:active:border-neutral-400 sm:inline-flex", onClick: () => setScale(10), children: "X10" }), jsxRuntime.jsx("button", { "aria-label": "Reset", className: utils.cn("ml-1 flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600", lib.themeIconColor), onClick: resetTransform, children: jsxRuntime.jsx(icons.RefreshCcwIcon, { className: "h-3.5 w-3.5" }) }), jsxRuntime.jsx("button", { "aria-label": "Download SVG", className: utils.cn("ml-1 flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600", lib.themeIconColor), onClick: handleDownload, children: jsxRuntime.jsx(icons.DownloadIcon, { className: "h-3.5 w-3.5" }) }), jsxRuntime.jsx("button", { "aria-label": "Close", className: utils.cn("ml-1 flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600", lib.themeIconColor), onClick: () => { setOpen(false); resetTransform(); }, children: jsxRuntime.jsx(icons.XIcon, { className: "h-3.5 w-3.5" }) })] })] }), jsxRuntime.jsxs("div", { className: "relative h-[calc(88vh-40px)] w-full overflow-hidden bg-white dark:bg-neutral-900 overscroll-contain touch-none", onWheel: onWheel, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerCancel, children: [jsxRuntime.jsx("div", { className: "absolute left-1/2 top-1/2", style: { transform: `translate(-50%, -50%) translate(${translate.x}px, ${translate.y}px)` }, children: jsxRuntime.jsx("div", { style: { transform: `scale(${scale})`, transformOrigin: '50% 50%' }, dangerouslySetInnerHTML: { __html: svg } }) }), jsxRuntime.jsxs("div", { className: "absolute inset-x-3 bottom-3 rounded-md bg-white/92 px-3 py-2 shadow-sm backdrop-blur sm:hidden dark:bg-neutral-900/92", children: [jsxRuntime.jsxs("label", { className: "mb-1 flex items-center justify-between text-[11px] text-neutral-600 dark:text-neutral-300", children: [jsxRuntime.jsx("span", { children: "Zoom" }), jsxRuntime.jsxs("span", { children: [Math.round(scale * 100), "%"] })] }), jsxRuntime.jsx("input", { "aria-label": "Zoom slider", className: "block w-full", type: "range", min: "25", max: "1000", step: "5", value: Math.round(scale * 100), style: { accentColor: lib.themeSvgIconColor }, onChange: (e) => setScale(clamp(Number(e.target.value) / 100, 0.25, 10)) })] }), jsxRuntime.jsx("div", { className: "pointer-events-none absolute bottom-2 right-3 hidden rounded bg-black/40 px-2 py-1 text-xs text-white sm:block", children: "Drag to pan, click button to zoom-out or zoom-in" }), jsxRuntime.jsx("div", { className: "pointer-events-none absolute left-3 top-3 rounded bg-black/40 px-2 py-1 text-[11px] text-white sm:hidden", children: "Drag to pan, pinch to zoom-out or zoom-in" })] })] })] }))] }));
|
|
218
|
+
}
|
|
219
|
+
function addWatermarkToSvg(svg, watermark, watermarkColor) {
|
|
220
|
+
const watermarkText = `
|
|
221
|
+
<text
|
|
222
|
+
x="100%"
|
|
223
|
+
y="98%"
|
|
224
|
+
text-anchor="end"
|
|
225
|
+
font-size="12"
|
|
226
|
+
font-style="italic"
|
|
227
|
+
fill="${watermarkColor}"
|
|
228
|
+
opacity="0.40"
|
|
229
|
+
class="pointer-events-none"
|
|
230
|
+
dx="-8"
|
|
231
|
+
dy="-4"
|
|
232
|
+
>${watermark}</text>
|
|
233
|
+
`;
|
|
234
|
+
return svg.replace('</svg>', `${watermarkText}</svg>`);
|
|
235
|
+
}
|
|
236
|
+
function applyHandDrawnStyle(svg) {
|
|
237
|
+
if (typeof window === 'undefined')
|
|
238
|
+
return svg;
|
|
239
|
+
try {
|
|
240
|
+
const parser = new DOMParser();
|
|
241
|
+
const doc = parser.parseFromString(svg, 'image/svg+xml');
|
|
242
|
+
const svgElement = doc.documentElement;
|
|
243
|
+
if (!svgElement || svgElement.tagName.toLowerCase() !== 'svg')
|
|
244
|
+
return svg;
|
|
245
|
+
const rc = rough.svg(svgElement);
|
|
246
|
+
const serializer = new XMLSerializer();
|
|
247
|
+
const getNumber = (value) => Number.parseFloat(value !== null && value !== void 0 ? value : '') || 0;
|
|
248
|
+
const getStyleValue = (element, name) => {
|
|
249
|
+
const inlineStyle = element.getAttribute('style');
|
|
250
|
+
if (inlineStyle) {
|
|
251
|
+
const match = inlineStyle.match(new RegExp(`(?:^|;)\\s*${name}\\s*:\\s*([^;]+)`));
|
|
252
|
+
if (match === null || match === void 0 ? void 0 : match[1])
|
|
253
|
+
return match[1].trim();
|
|
254
|
+
}
|
|
255
|
+
return element.getAttribute(name);
|
|
256
|
+
};
|
|
257
|
+
const applyAttributes = (source, target) => {
|
|
258
|
+
var _a;
|
|
259
|
+
for (const attr of source.getAttributeNames()) {
|
|
260
|
+
if (attr === 'x' || attr === 'y' || attr === 'x1' || attr === 'y1' || attr === 'x2' || attr === 'y2' || attr === 'width' || attr === 'height' || attr === 'rx' || attr === 'ry' || attr === 'points' || attr === 'd')
|
|
261
|
+
continue;
|
|
262
|
+
target.setAttribute(attr, (_a = source.getAttribute(attr)) !== null && _a !== void 0 ? _a : '');
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
const createOptions = (element) => {
|
|
266
|
+
var _a, _b;
|
|
267
|
+
const stroke = (_a = getStyleValue(element, 'stroke')) !== null && _a !== void 0 ? _a : '#000';
|
|
268
|
+
const fill = (_b = getStyleValue(element, 'fill')) !== null && _b !== void 0 ? _b : 'none';
|
|
269
|
+
const strokeWidth = getNumber(getStyleValue(element, 'stroke-width')) || 1.5;
|
|
270
|
+
return {
|
|
271
|
+
stroke,
|
|
272
|
+
fill: fill === 'none' ? undefined : fill,
|
|
273
|
+
strokeWidth,
|
|
274
|
+
roughness: 1.6,
|
|
275
|
+
bowing: 1.25,
|
|
276
|
+
fillStyle: fill === 'none' ? 'hachure' : 'solid',
|
|
277
|
+
fillWeight: 0.8,
|
|
278
|
+
hachureGap: 10,
|
|
279
|
+
preserveVertices: true,
|
|
280
|
+
seed: 7,
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
const replaceShape = (element, node) => {
|
|
284
|
+
var _a, _b;
|
|
285
|
+
if (!node || !element.parentNode)
|
|
286
|
+
return;
|
|
287
|
+
applyAttributes(element, node);
|
|
288
|
+
if (element.getAttribute('class')) {
|
|
289
|
+
node.setAttribute('class', (_a = element.getAttribute('class')) !== null && _a !== void 0 ? _a : '');
|
|
290
|
+
}
|
|
291
|
+
if (element.getAttribute('style')) {
|
|
292
|
+
node.setAttribute('style', (_b = element.getAttribute('style')) !== null && _b !== void 0 ? _b : '');
|
|
293
|
+
}
|
|
294
|
+
element.parentNode.replaceChild(node, element);
|
|
295
|
+
};
|
|
296
|
+
svgElement.querySelectorAll('rect').forEach((element) => {
|
|
297
|
+
const x = getNumber(element.getAttribute('x'));
|
|
298
|
+
const y = getNumber(element.getAttribute('y'));
|
|
299
|
+
const width = getNumber(element.getAttribute('width'));
|
|
300
|
+
const height = getNumber(element.getAttribute('height'));
|
|
301
|
+
const rx = getNumber(element.getAttribute('rx'));
|
|
302
|
+
const ry = getNumber(element.getAttribute('ry'));
|
|
303
|
+
const node = rx > 0 || ry > 0
|
|
304
|
+
? rc.path(`M ${x + rx} ${y}
|
|
305
|
+
H ${x + width - rx}
|
|
306
|
+
Q ${x + width} ${y} ${x + width} ${y + ry}
|
|
307
|
+
V ${y + height - ry}
|
|
308
|
+
Q ${x + width} ${y + height} ${x + width - rx} ${y + height}
|
|
309
|
+
H ${x + rx}
|
|
310
|
+
Q ${x} ${y + height} ${x} ${y + height - ry}
|
|
311
|
+
V ${y + ry}
|
|
312
|
+
Q ${x} ${y} ${x + rx} ${y}
|
|
313
|
+
Z`, createOptions(element))
|
|
314
|
+
: rc.rectangle(x, y, width, height, createOptions(element));
|
|
315
|
+
replaceShape(element, node);
|
|
316
|
+
});
|
|
317
|
+
svgElement.querySelectorAll('line').forEach((element) => {
|
|
318
|
+
const node = rc.line(getNumber(element.getAttribute('x1')), getNumber(element.getAttribute('y1')), getNumber(element.getAttribute('x2')), getNumber(element.getAttribute('y2')), createOptions(element));
|
|
319
|
+
replaceShape(element, node);
|
|
320
|
+
});
|
|
321
|
+
svgElement.querySelectorAll('polyline').forEach((element) => {
|
|
322
|
+
var _a;
|
|
323
|
+
const points = ((_a = element.getAttribute('points')) !== null && _a !== void 0 ? _a : '')
|
|
324
|
+
.trim()
|
|
325
|
+
.split(/\s+/)
|
|
326
|
+
.map((pair) => pair.split(',').map(Number))
|
|
327
|
+
.filter((point) => point.length === 2 && Number.isFinite(point[0]) && Number.isFinite(point[1]));
|
|
328
|
+
if (points.length < 2)
|
|
329
|
+
return;
|
|
330
|
+
const node = rc.linearPath(points, createOptions(element));
|
|
331
|
+
replaceShape(element, node);
|
|
332
|
+
});
|
|
333
|
+
svgElement.querySelectorAll('polygon').forEach((element) => {
|
|
334
|
+
var _a;
|
|
335
|
+
const points = ((_a = element.getAttribute('points')) !== null && _a !== void 0 ? _a : '')
|
|
336
|
+
.trim()
|
|
337
|
+
.split(/\s+/)
|
|
338
|
+
.map((pair) => pair.split(',').map(Number))
|
|
339
|
+
.filter((point) => point.length === 2 && Number.isFinite(point[0]) && Number.isFinite(point[1]));
|
|
340
|
+
if (points.length < 2)
|
|
341
|
+
return;
|
|
342
|
+
const node = rc.polygon(points, createOptions(element));
|
|
343
|
+
replaceShape(element, node);
|
|
344
|
+
});
|
|
345
|
+
svgElement.querySelectorAll('path').forEach((element) => {
|
|
346
|
+
const d = element.getAttribute('d');
|
|
347
|
+
if (!d)
|
|
348
|
+
return;
|
|
349
|
+
const node = rc.path(d, createOptions(element));
|
|
350
|
+
replaceShape(element, node);
|
|
351
|
+
});
|
|
352
|
+
return serializer.serializeToString(svgElement);
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
console.error('Error while applying hand-drawn mermaid style', error);
|
|
356
|
+
return svg;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
exports.Mermaid = Mermaid;
|