@docfuse/plugins 0.1.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/LICENSE +21 -0
- package/README.md +52 -0
- package/README.zh-CN.md +52 -0
- package/dist/client/kroki.js +221 -0
- package/dist/client/mermaid.js +366 -0
- package/dist/client/plantuml.js +221 -0
- package/dist/diagram.css +195 -0
- package/dist/external-links.d.ts +10 -0
- package/dist/external-links.js +49 -0
- package/dist/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +757 -0
- package/dist/kroki.d.ts +12 -0
- package/dist/kroki.js +293 -0
- package/dist/link-card.d.ts +9 -0
- package/dist/link-card.js +87 -0
- package/dist/math.css +3 -0
- package/dist/math.d.ts +19 -0
- package/dist/math.js +140 -0
- package/dist/mermaid.d.ts +10 -0
- package/dist/mermaid.js +290 -0
- package/dist/pagefind.d.ts +66 -0
- package/dist/pagefind.js +84 -0
- package/dist/plantuml.d.ts +10 -0
- package/dist/plantuml.js +297 -0
- package/dist/reading-time.d.ts +20 -0
- package/dist/reading-time.js +90 -0
- package/package.json +122 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// src/client/shared.ts
|
|
2
|
+
var ICONS = {
|
|
3
|
+
copy: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>',
|
|
4
|
+
check: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg>',
|
|
5
|
+
source: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 8-4 4 4 4M18 8l4 4-4 4M14.5 4l-5 16"/></svg>',
|
|
6
|
+
expand: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>',
|
|
7
|
+
"zoom-out": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/></svg>',
|
|
8
|
+
"zoom-reset": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>',
|
|
9
|
+
"zoom-in": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>'
|
|
10
|
+
};
|
|
11
|
+
var DIAGRAM_SCALE_MIN = 0.5;
|
|
12
|
+
var DIAGRAM_SCALE_MAX = 2;
|
|
13
|
+
var DIAGRAM_SCALE_STEP = 0.25;
|
|
14
|
+
async function copyText(value) {
|
|
15
|
+
if (navigator.clipboard) {
|
|
16
|
+
try {
|
|
17
|
+
await navigator.clipboard.writeText(value);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (typeof document.execCommand !== "function") return false;
|
|
23
|
+
const textarea = document.createElement("textarea");
|
|
24
|
+
textarea.value = value;
|
|
25
|
+
textarea.setAttribute("readonly", "");
|
|
26
|
+
textarea.style.position = "fixed";
|
|
27
|
+
textarea.style.opacity = "0";
|
|
28
|
+
document.body.append(textarea);
|
|
29
|
+
textarea.select();
|
|
30
|
+
try {
|
|
31
|
+
return document.execCommand("copy");
|
|
32
|
+
} finally {
|
|
33
|
+
textarea.remove();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function setupActions(figure) {
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
const copyTimers = /* @__PURE__ */ new Map();
|
|
39
|
+
const dialogs = /* @__PURE__ */ new Set();
|
|
40
|
+
const source = figure.dataset.dfSource ?? "";
|
|
41
|
+
const preview = figure.querySelector(".df-diagram-preview");
|
|
42
|
+
const sourcePanel = figure.querySelector(".df-diagram-source");
|
|
43
|
+
const zoomControls = figure.querySelector(".df-diagram-zoom-controls");
|
|
44
|
+
const buttons = figure.querySelectorAll("[data-df-diagram-action]");
|
|
45
|
+
const previewHidden = preview?.hidden;
|
|
46
|
+
const sourcePanelHidden = sourcePanel?.hidden;
|
|
47
|
+
const zoomControlsHidden = zoomControls?.hidden;
|
|
48
|
+
const previewZoomWidth = preview?.style.getPropertyValue("--df-diagram-zoom-width") ?? "";
|
|
49
|
+
const previewZoomMaxWidth = preview?.style.getPropertyValue("--df-diagram-zoom-max-width") ?? "";
|
|
50
|
+
const previewScale = preview?.getAttribute("data-df-scale");
|
|
51
|
+
let scale = 1;
|
|
52
|
+
const buttonStates = [...buttons].map((button) => ({
|
|
53
|
+
button,
|
|
54
|
+
innerHTML: button.innerHTML,
|
|
55
|
+
ariaLabel: button.getAttribute("aria-label"),
|
|
56
|
+
copied: button.getAttribute("data-copied"),
|
|
57
|
+
actionError: button.getAttribute("data-df-action-error"),
|
|
58
|
+
disabled: button.disabled
|
|
59
|
+
}));
|
|
60
|
+
const updateScale = () => {
|
|
61
|
+
if (!preview) return;
|
|
62
|
+
preview.style.setProperty("--df-diagram-zoom-width", `${scale * 100}%`);
|
|
63
|
+
preview.style.setProperty("--df-diagram-zoom-max-width", `${56.25 * scale}rem`);
|
|
64
|
+
preview.dataset.dfScale = String(scale);
|
|
65
|
+
for (const button of buttons) {
|
|
66
|
+
const action = button.dataset.dfDiagramAction;
|
|
67
|
+
if (action === "zoom-out") button.disabled = scale <= DIAGRAM_SCALE_MIN;
|
|
68
|
+
if (action === "zoom-in") button.disabled = scale >= DIAGRAM_SCALE_MAX;
|
|
69
|
+
if (action === "zoom-reset") button.disabled = scale === 1;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
if (zoomControls) updateScale();
|
|
73
|
+
for (const button of buttons) {
|
|
74
|
+
const action = button.dataset.dfDiagramAction;
|
|
75
|
+
button.innerHTML = ICONS[action] ?? "";
|
|
76
|
+
button.addEventListener(
|
|
77
|
+
"click",
|
|
78
|
+
async () => {
|
|
79
|
+
try {
|
|
80
|
+
if (action === "copy") {
|
|
81
|
+
if (!await copyText(source)) throw new Error("Copy command failed");
|
|
82
|
+
button.innerHTML = ICONS.check;
|
|
83
|
+
button.dataset.copied = "true";
|
|
84
|
+
const activeTimer = copyTimers.get(button);
|
|
85
|
+
if (activeTimer !== void 0) window.clearTimeout(activeTimer);
|
|
86
|
+
const timer = window.setTimeout(() => {
|
|
87
|
+
if (copyTimers.get(button) !== timer) return;
|
|
88
|
+
copyTimers.delete(button);
|
|
89
|
+
button.innerHTML = ICONS.copy;
|
|
90
|
+
delete button.dataset.copied;
|
|
91
|
+
}, 1500);
|
|
92
|
+
copyTimers.set(button, timer);
|
|
93
|
+
} else if (action === "source" && preview && sourcePanel) {
|
|
94
|
+
const showingSource = !sourcePanel.hidden;
|
|
95
|
+
sourcePanel.hidden = showingSource;
|
|
96
|
+
preview.hidden = !showingSource;
|
|
97
|
+
if (zoomControls) zoomControls.hidden = !showingSource;
|
|
98
|
+
button.setAttribute(
|
|
99
|
+
"aria-label",
|
|
100
|
+
showingSource ? buttonStates.find((state) => state.button === button)?.ariaLabel ?? "Show source" : button.dataset.dfDiagramPreviewLabel ?? "Show preview"
|
|
101
|
+
);
|
|
102
|
+
} else if (action === "expand" && preview) {
|
|
103
|
+
const dialog = document.createElement("dialog");
|
|
104
|
+
dialogs.add(dialog);
|
|
105
|
+
dialog.className = "df-diagram-dialog";
|
|
106
|
+
const close = document.createElement("button");
|
|
107
|
+
close.type = "button";
|
|
108
|
+
close.className = "df-diagram-dialog-close";
|
|
109
|
+
close.setAttribute("aria-label", button.dataset.dfDiagramCloseLabel ?? "Close expanded diagram");
|
|
110
|
+
close.textContent = "\xD7";
|
|
111
|
+
close.addEventListener("click", () => dialog.close(), { signal: controller.signal });
|
|
112
|
+
const expandedPreview = preview.cloneNode(true);
|
|
113
|
+
expandedPreview.style.removeProperty("--df-diagram-zoom-width");
|
|
114
|
+
expandedPreview.style.removeProperty("--df-diagram-zoom-max-width");
|
|
115
|
+
delete expandedPreview.dataset.dfScale;
|
|
116
|
+
dialog.append(close, expandedPreview);
|
|
117
|
+
dialog.addEventListener(
|
|
118
|
+
"close",
|
|
119
|
+
() => {
|
|
120
|
+
dialogs.delete(dialog);
|
|
121
|
+
dialog.remove();
|
|
122
|
+
},
|
|
123
|
+
{ signal: controller.signal }
|
|
124
|
+
);
|
|
125
|
+
document.body.append(dialog);
|
|
126
|
+
dialog.showModal();
|
|
127
|
+
} else if (action === "zoom-out") {
|
|
128
|
+
scale = Math.max(DIAGRAM_SCALE_MIN, scale - DIAGRAM_SCALE_STEP);
|
|
129
|
+
updateScale();
|
|
130
|
+
} else if (action === "zoom-reset") {
|
|
131
|
+
scale = 1;
|
|
132
|
+
updateScale();
|
|
133
|
+
} else if (action === "zoom-in") {
|
|
134
|
+
scale = Math.min(DIAGRAM_SCALE_MAX, scale + DIAGRAM_SCALE_STEP);
|
|
135
|
+
updateScale();
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
button.dataset.dfActionError = "true";
|
|
139
|
+
console.error("[docfuse] Diagram action failed:", error);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{ signal: controller.signal }
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return () => {
|
|
146
|
+
controller.abort();
|
|
147
|
+
copyTimers.forEach((timer) => window.clearTimeout(timer));
|
|
148
|
+
dialogs.forEach((dialog) => dialog.remove());
|
|
149
|
+
copyTimers.clear();
|
|
150
|
+
dialogs.clear();
|
|
151
|
+
if (preview && previewHidden !== void 0) preview.hidden = previewHidden;
|
|
152
|
+
if (sourcePanel && sourcePanelHidden !== void 0) sourcePanel.hidden = sourcePanelHidden;
|
|
153
|
+
if (zoomControls && zoomControlsHidden !== void 0) zoomControls.hidden = zoomControlsHidden;
|
|
154
|
+
if (preview) {
|
|
155
|
+
if (previewZoomWidth) preview.style.setProperty("--df-diagram-zoom-width", previewZoomWidth);
|
|
156
|
+
else preview.style.removeProperty("--df-diagram-zoom-width");
|
|
157
|
+
if (previewZoomMaxWidth) preview.style.setProperty("--df-diagram-zoom-max-width", previewZoomMaxWidth);
|
|
158
|
+
else preview.style.removeProperty("--df-diagram-zoom-max-width");
|
|
159
|
+
if (previewScale == null) preview.removeAttribute("data-df-scale");
|
|
160
|
+
else preview.setAttribute("data-df-scale", previewScale);
|
|
161
|
+
}
|
|
162
|
+
buttonStates.forEach(({ button, innerHTML, ariaLabel, copied, actionError, disabled }) => {
|
|
163
|
+
button.innerHTML = innerHTML;
|
|
164
|
+
button.disabled = disabled;
|
|
165
|
+
if (ariaLabel === null) button.removeAttribute("aria-label");
|
|
166
|
+
else button.setAttribute("aria-label", ariaLabel);
|
|
167
|
+
if (copied === null) button.removeAttribute("data-copied");
|
|
168
|
+
else button.setAttribute("data-copied", copied);
|
|
169
|
+
if (actionError === null) button.removeAttribute("data-df-action-error");
|
|
170
|
+
else button.setAttribute("data-df-action-error", actionError);
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function enhanceDiagrams(root, kind, render) {
|
|
175
|
+
let active = true;
|
|
176
|
+
let disposed = false;
|
|
177
|
+
const figures = Array.from(root.querySelectorAll(`[data-df-plugin-diagram="${kind}"]`)).filter(
|
|
178
|
+
(figure) => figure.dataset.dfEnhanced !== "true"
|
|
179
|
+
);
|
|
180
|
+
const renderErrorStates = figures.map(
|
|
181
|
+
(figure) => [figure, figure.getAttribute("data-df-render-error")]
|
|
182
|
+
);
|
|
183
|
+
const cleanups = [];
|
|
184
|
+
const renders = [];
|
|
185
|
+
for (const figure of figures) {
|
|
186
|
+
figure.dataset.dfEnhanced = "true";
|
|
187
|
+
cleanups.push(setupActions(figure));
|
|
188
|
+
if (render) {
|
|
189
|
+
renders.push(
|
|
190
|
+
render(figure).catch((error) => {
|
|
191
|
+
if (!active) return;
|
|
192
|
+
figure.dataset.dfRenderError = "true";
|
|
193
|
+
console.error(`[docfuse] ${kind} render failed:`, error);
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const dispose = () => {
|
|
199
|
+
if (disposed) return;
|
|
200
|
+
disposed = true;
|
|
201
|
+
active = false;
|
|
202
|
+
cleanups.reverse().forEach((cleanup) => cleanup());
|
|
203
|
+
renderErrorStates.forEach(([figure, renderError]) => {
|
|
204
|
+
delete figure.dataset.dfEnhanced;
|
|
205
|
+
if (renderError === null) figure.removeAttribute("data-df-render-error");
|
|
206
|
+
else figure.setAttribute("data-df-render-error", renderError);
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
return Object.assign(dispose, {
|
|
210
|
+
ready: Promise.all(renders).then(() => void 0),
|
|
211
|
+
dispose
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/client/mermaid.ts
|
|
216
|
+
var sequence = 0;
|
|
217
|
+
var remoteModules = /* @__PURE__ */ new Map();
|
|
218
|
+
var renderRevisions = /* @__PURE__ */ new WeakMap();
|
|
219
|
+
var renderQueues = /* @__PURE__ */ new WeakMap();
|
|
220
|
+
var cssSrgbComponent = String.raw`[-+]?(?:\d*\.?\d+)(?:e[-+]?\d+)?%?`;
|
|
221
|
+
var cssSrgbPattern = new RegExp(
|
|
222
|
+
String.raw`^color\(\s*srgb\s+(${cssSrgbComponent})\s+(${cssSrgbComponent})\s+(${cssSrgbComponent})(?:\s*\/\s*(${cssSrgbComponent}))?\s*\)$`,
|
|
223
|
+
"i"
|
|
224
|
+
);
|
|
225
|
+
function clamp(value, minimum, maximum) {
|
|
226
|
+
return Math.min(maximum, Math.max(minimum, value));
|
|
227
|
+
}
|
|
228
|
+
function normalizedSrgbComponent(component) {
|
|
229
|
+
const percentage = component.endsWith("%");
|
|
230
|
+
const value = Number(percentage ? component.slice(0, -1) : component);
|
|
231
|
+
return clamp(value, 0, percentage ? 100 : 1) / (percentage ? 100 : 1);
|
|
232
|
+
}
|
|
233
|
+
function normalizeMermaidColor(value, fallback) {
|
|
234
|
+
const color = value.trim();
|
|
235
|
+
const match = color.match(cssSrgbPattern);
|
|
236
|
+
if (!match) return /^(?:color|oklch|oklab|lab|lch)\(/i.test(color) ? fallback : color || fallback;
|
|
237
|
+
const channels = match.slice(1, 4).map((channel) => Math.round(normalizedSrgbComponent(channel) * 255));
|
|
238
|
+
if (channels.some((channel) => !Number.isFinite(channel))) return fallback;
|
|
239
|
+
const alpha = match[4] === void 0 ? 1 : normalizedSrgbComponent(match[4]);
|
|
240
|
+
if (!Number.isFinite(alpha)) return fallback;
|
|
241
|
+
return alpha < 1 ? `rgba(${channels.join(", ")}, ${alpha})` : `rgb(${channels.join(", ")})`;
|
|
242
|
+
}
|
|
243
|
+
function queueMermaidRender(mermaidApi, render) {
|
|
244
|
+
const previous = renderQueues.get(mermaidApi) ?? Promise.resolve();
|
|
245
|
+
const request = previous.then(render);
|
|
246
|
+
const tail = request.then(
|
|
247
|
+
() => void 0,
|
|
248
|
+
() => void 0
|
|
249
|
+
);
|
|
250
|
+
renderQueues.set(mermaidApi, tail);
|
|
251
|
+
void tail.then(() => {
|
|
252
|
+
if (renderQueues.get(mermaidApi) === tail) renderQueues.delete(mermaidApi);
|
|
253
|
+
});
|
|
254
|
+
return request;
|
|
255
|
+
}
|
|
256
|
+
function resolveThemeColor(figure, token, fallback) {
|
|
257
|
+
const probe = figure.ownerDocument.createElement("span");
|
|
258
|
+
probe.style.color = `var(${token}, ${fallback})`;
|
|
259
|
+
probe.style.display = "none";
|
|
260
|
+
figure.append(probe);
|
|
261
|
+
const color = normalizeMermaidColor(getComputedStyle(probe).color, fallback);
|
|
262
|
+
probe.remove();
|
|
263
|
+
return color;
|
|
264
|
+
}
|
|
265
|
+
async function loadMermaidModule(moduleUrl, importer = (url) => import(
|
|
266
|
+
/* @vite-ignore */
|
|
267
|
+
url
|
|
268
|
+
)) {
|
|
269
|
+
const cached = remoteModules.get(moduleUrl);
|
|
270
|
+
if (cached) return cached;
|
|
271
|
+
const pending = importer(moduleUrl).then((module) => "default" in module ? module.default : module);
|
|
272
|
+
remoteModules.set(moduleUrl, pending);
|
|
273
|
+
void pending.catch(() => {
|
|
274
|
+
if (remoteModules.get(moduleUrl) === pending) remoteModules.delete(moduleUrl);
|
|
275
|
+
});
|
|
276
|
+
return pending;
|
|
277
|
+
}
|
|
278
|
+
async function resolveMermaid(figure) {
|
|
279
|
+
const moduleUrl = figure.dataset.dfModuleUrl?.trim() || new URL("./mermaid/mermaid.esm.min.mjs", import.meta.url).href;
|
|
280
|
+
return loadMermaidModule(moduleUrl);
|
|
281
|
+
}
|
|
282
|
+
async function renderMermaid(figure) {
|
|
283
|
+
const preview = figure.querySelector(".df-diagram-preview");
|
|
284
|
+
if (!preview) return;
|
|
285
|
+
const revision = (renderRevisions.get(figure) ?? 0) + 1;
|
|
286
|
+
renderRevisions.set(figure, revision);
|
|
287
|
+
const isCurrent = () => figure.dataset.dfEnhanced === "true" && renderRevisions.get(figure) === revision;
|
|
288
|
+
const mermaidApi = await resolveMermaid(figure);
|
|
289
|
+
if (!isCurrent()) return;
|
|
290
|
+
const surface = resolveThemeColor(figure, "--df-diagram-surface", "#ffffff");
|
|
291
|
+
const foreground = resolveThemeColor(figure, "--df-diagram-foreground", "#1d1d1f");
|
|
292
|
+
const accent = resolveThemeColor(figure, "--df-diagram-accent", "#0071e3");
|
|
293
|
+
const muted = resolveThemeColor(figure, "--df-diagram-muted", "#6e6e73");
|
|
294
|
+
const line = resolveThemeColor(figure, "--df-diagram-line", "#d2d2d7");
|
|
295
|
+
const result = await queueMermaidRender(mermaidApi, async () => {
|
|
296
|
+
if (!isCurrent()) return void 0;
|
|
297
|
+
mermaidApi.initialize({
|
|
298
|
+
startOnLoad: false,
|
|
299
|
+
securityLevel: "strict",
|
|
300
|
+
theme: "base",
|
|
301
|
+
themeVariables: {
|
|
302
|
+
background: surface,
|
|
303
|
+
primaryColor: surface,
|
|
304
|
+
primaryTextColor: foreground,
|
|
305
|
+
primaryBorderColor: accent,
|
|
306
|
+
secondaryColor: surface,
|
|
307
|
+
secondaryTextColor: foreground,
|
|
308
|
+
secondaryBorderColor: line,
|
|
309
|
+
tertiaryColor: surface,
|
|
310
|
+
tertiaryTextColor: foreground,
|
|
311
|
+
tertiaryBorderColor: line,
|
|
312
|
+
lineColor: line,
|
|
313
|
+
textColor: foreground,
|
|
314
|
+
noteBkgColor: surface,
|
|
315
|
+
noteTextColor: foreground,
|
|
316
|
+
noteBorderColor: line,
|
|
317
|
+
actorTextColor: foreground,
|
|
318
|
+
actorBkg: surface,
|
|
319
|
+
actorBorder: line,
|
|
320
|
+
signalColor: muted,
|
|
321
|
+
signalTextColor: foreground
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return mermaidApi.render(`df-mermaid-${++sequence}`, figure.dataset.dfSource ?? "");
|
|
325
|
+
});
|
|
326
|
+
if (!result || !isCurrent()) return;
|
|
327
|
+
preview.innerHTML = result.svg;
|
|
328
|
+
delete figure.dataset.dfRenderError;
|
|
329
|
+
}
|
|
330
|
+
function enhance(root = document) {
|
|
331
|
+
const diagrams = enhanceDiagrams(root, "mermaid", renderMermaid);
|
|
332
|
+
const document2 = root instanceof Document ? root : root.ownerDocument;
|
|
333
|
+
if (!document2) return diagrams;
|
|
334
|
+
let scheduledFrame;
|
|
335
|
+
const observer = new MutationObserver(() => {
|
|
336
|
+
if (scheduledFrame !== void 0) return;
|
|
337
|
+
scheduledFrame = requestAnimationFrame(() => {
|
|
338
|
+
scheduledFrame = void 0;
|
|
339
|
+
const figures = root.querySelectorAll('[data-df-plugin-diagram="mermaid"]');
|
|
340
|
+
for (const figure of figures) {
|
|
341
|
+
void renderMermaid(figure).catch((error) => {
|
|
342
|
+
figure.dataset.dfRenderError = "true";
|
|
343
|
+
console.error("[docfuse] Mermaid render failed:", error);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
observer.observe(document2.documentElement, {
|
|
349
|
+
attributes: true,
|
|
350
|
+
attributeFilter: ["class", "data-theme", "style"]
|
|
351
|
+
});
|
|
352
|
+
let disposed = false;
|
|
353
|
+
const dispose = () => {
|
|
354
|
+
if (disposed) return;
|
|
355
|
+
disposed = true;
|
|
356
|
+
if (scheduledFrame !== void 0) cancelAnimationFrame(scheduledFrame);
|
|
357
|
+
observer.disconnect();
|
|
358
|
+
diagrams();
|
|
359
|
+
};
|
|
360
|
+
return Object.assign(dispose, { ready: diagrams.ready, dispose });
|
|
361
|
+
}
|
|
362
|
+
export {
|
|
363
|
+
enhance,
|
|
364
|
+
loadMermaidModule,
|
|
365
|
+
normalizeMermaidColor
|
|
366
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// src/client/shared.ts
|
|
2
|
+
var ICONS = {
|
|
3
|
+
copy: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>',
|
|
4
|
+
check: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg>',
|
|
5
|
+
source: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 8-4 4 4 4M18 8l4 4-4 4M14.5 4l-5 16"/></svg>',
|
|
6
|
+
expand: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>',
|
|
7
|
+
"zoom-out": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/></svg>',
|
|
8
|
+
"zoom-reset": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>',
|
|
9
|
+
"zoom-in": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>'
|
|
10
|
+
};
|
|
11
|
+
var DIAGRAM_SCALE_MIN = 0.5;
|
|
12
|
+
var DIAGRAM_SCALE_MAX = 2;
|
|
13
|
+
var DIAGRAM_SCALE_STEP = 0.25;
|
|
14
|
+
async function copyText(value) {
|
|
15
|
+
if (navigator.clipboard) {
|
|
16
|
+
try {
|
|
17
|
+
await navigator.clipboard.writeText(value);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (typeof document.execCommand !== "function") return false;
|
|
23
|
+
const textarea = document.createElement("textarea");
|
|
24
|
+
textarea.value = value;
|
|
25
|
+
textarea.setAttribute("readonly", "");
|
|
26
|
+
textarea.style.position = "fixed";
|
|
27
|
+
textarea.style.opacity = "0";
|
|
28
|
+
document.body.append(textarea);
|
|
29
|
+
textarea.select();
|
|
30
|
+
try {
|
|
31
|
+
return document.execCommand("copy");
|
|
32
|
+
} finally {
|
|
33
|
+
textarea.remove();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function setupActions(figure) {
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
const copyTimers = /* @__PURE__ */ new Map();
|
|
39
|
+
const dialogs = /* @__PURE__ */ new Set();
|
|
40
|
+
const source = figure.dataset.dfSource ?? "";
|
|
41
|
+
const preview = figure.querySelector(".df-diagram-preview");
|
|
42
|
+
const sourcePanel = figure.querySelector(".df-diagram-source");
|
|
43
|
+
const zoomControls = figure.querySelector(".df-diagram-zoom-controls");
|
|
44
|
+
const buttons = figure.querySelectorAll("[data-df-diagram-action]");
|
|
45
|
+
const previewHidden = preview?.hidden;
|
|
46
|
+
const sourcePanelHidden = sourcePanel?.hidden;
|
|
47
|
+
const zoomControlsHidden = zoomControls?.hidden;
|
|
48
|
+
const previewZoomWidth = preview?.style.getPropertyValue("--df-diagram-zoom-width") ?? "";
|
|
49
|
+
const previewZoomMaxWidth = preview?.style.getPropertyValue("--df-diagram-zoom-max-width") ?? "";
|
|
50
|
+
const previewScale = preview?.getAttribute("data-df-scale");
|
|
51
|
+
let scale = 1;
|
|
52
|
+
const buttonStates = [...buttons].map((button) => ({
|
|
53
|
+
button,
|
|
54
|
+
innerHTML: button.innerHTML,
|
|
55
|
+
ariaLabel: button.getAttribute("aria-label"),
|
|
56
|
+
copied: button.getAttribute("data-copied"),
|
|
57
|
+
actionError: button.getAttribute("data-df-action-error"),
|
|
58
|
+
disabled: button.disabled
|
|
59
|
+
}));
|
|
60
|
+
const updateScale = () => {
|
|
61
|
+
if (!preview) return;
|
|
62
|
+
preview.style.setProperty("--df-diagram-zoom-width", `${scale * 100}%`);
|
|
63
|
+
preview.style.setProperty("--df-diagram-zoom-max-width", `${56.25 * scale}rem`);
|
|
64
|
+
preview.dataset.dfScale = String(scale);
|
|
65
|
+
for (const button of buttons) {
|
|
66
|
+
const action = button.dataset.dfDiagramAction;
|
|
67
|
+
if (action === "zoom-out") button.disabled = scale <= DIAGRAM_SCALE_MIN;
|
|
68
|
+
if (action === "zoom-in") button.disabled = scale >= DIAGRAM_SCALE_MAX;
|
|
69
|
+
if (action === "zoom-reset") button.disabled = scale === 1;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
if (zoomControls) updateScale();
|
|
73
|
+
for (const button of buttons) {
|
|
74
|
+
const action = button.dataset.dfDiagramAction;
|
|
75
|
+
button.innerHTML = ICONS[action] ?? "";
|
|
76
|
+
button.addEventListener(
|
|
77
|
+
"click",
|
|
78
|
+
async () => {
|
|
79
|
+
try {
|
|
80
|
+
if (action === "copy") {
|
|
81
|
+
if (!await copyText(source)) throw new Error("Copy command failed");
|
|
82
|
+
button.innerHTML = ICONS.check;
|
|
83
|
+
button.dataset.copied = "true";
|
|
84
|
+
const activeTimer = copyTimers.get(button);
|
|
85
|
+
if (activeTimer !== void 0) window.clearTimeout(activeTimer);
|
|
86
|
+
const timer = window.setTimeout(() => {
|
|
87
|
+
if (copyTimers.get(button) !== timer) return;
|
|
88
|
+
copyTimers.delete(button);
|
|
89
|
+
button.innerHTML = ICONS.copy;
|
|
90
|
+
delete button.dataset.copied;
|
|
91
|
+
}, 1500);
|
|
92
|
+
copyTimers.set(button, timer);
|
|
93
|
+
} else if (action === "source" && preview && sourcePanel) {
|
|
94
|
+
const showingSource = !sourcePanel.hidden;
|
|
95
|
+
sourcePanel.hidden = showingSource;
|
|
96
|
+
preview.hidden = !showingSource;
|
|
97
|
+
if (zoomControls) zoomControls.hidden = !showingSource;
|
|
98
|
+
button.setAttribute(
|
|
99
|
+
"aria-label",
|
|
100
|
+
showingSource ? buttonStates.find((state) => state.button === button)?.ariaLabel ?? "Show source" : button.dataset.dfDiagramPreviewLabel ?? "Show preview"
|
|
101
|
+
);
|
|
102
|
+
} else if (action === "expand" && preview) {
|
|
103
|
+
const dialog = document.createElement("dialog");
|
|
104
|
+
dialogs.add(dialog);
|
|
105
|
+
dialog.className = "df-diagram-dialog";
|
|
106
|
+
const close = document.createElement("button");
|
|
107
|
+
close.type = "button";
|
|
108
|
+
close.className = "df-diagram-dialog-close";
|
|
109
|
+
close.setAttribute("aria-label", button.dataset.dfDiagramCloseLabel ?? "Close expanded diagram");
|
|
110
|
+
close.textContent = "\xD7";
|
|
111
|
+
close.addEventListener("click", () => dialog.close(), { signal: controller.signal });
|
|
112
|
+
const expandedPreview = preview.cloneNode(true);
|
|
113
|
+
expandedPreview.style.removeProperty("--df-diagram-zoom-width");
|
|
114
|
+
expandedPreview.style.removeProperty("--df-diagram-zoom-max-width");
|
|
115
|
+
delete expandedPreview.dataset.dfScale;
|
|
116
|
+
dialog.append(close, expandedPreview);
|
|
117
|
+
dialog.addEventListener(
|
|
118
|
+
"close",
|
|
119
|
+
() => {
|
|
120
|
+
dialogs.delete(dialog);
|
|
121
|
+
dialog.remove();
|
|
122
|
+
},
|
|
123
|
+
{ signal: controller.signal }
|
|
124
|
+
);
|
|
125
|
+
document.body.append(dialog);
|
|
126
|
+
dialog.showModal();
|
|
127
|
+
} else if (action === "zoom-out") {
|
|
128
|
+
scale = Math.max(DIAGRAM_SCALE_MIN, scale - DIAGRAM_SCALE_STEP);
|
|
129
|
+
updateScale();
|
|
130
|
+
} else if (action === "zoom-reset") {
|
|
131
|
+
scale = 1;
|
|
132
|
+
updateScale();
|
|
133
|
+
} else if (action === "zoom-in") {
|
|
134
|
+
scale = Math.min(DIAGRAM_SCALE_MAX, scale + DIAGRAM_SCALE_STEP);
|
|
135
|
+
updateScale();
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
button.dataset.dfActionError = "true";
|
|
139
|
+
console.error("[docfuse] Diagram action failed:", error);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{ signal: controller.signal }
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return () => {
|
|
146
|
+
controller.abort();
|
|
147
|
+
copyTimers.forEach((timer) => window.clearTimeout(timer));
|
|
148
|
+
dialogs.forEach((dialog) => dialog.remove());
|
|
149
|
+
copyTimers.clear();
|
|
150
|
+
dialogs.clear();
|
|
151
|
+
if (preview && previewHidden !== void 0) preview.hidden = previewHidden;
|
|
152
|
+
if (sourcePanel && sourcePanelHidden !== void 0) sourcePanel.hidden = sourcePanelHidden;
|
|
153
|
+
if (zoomControls && zoomControlsHidden !== void 0) zoomControls.hidden = zoomControlsHidden;
|
|
154
|
+
if (preview) {
|
|
155
|
+
if (previewZoomWidth) preview.style.setProperty("--df-diagram-zoom-width", previewZoomWidth);
|
|
156
|
+
else preview.style.removeProperty("--df-diagram-zoom-width");
|
|
157
|
+
if (previewZoomMaxWidth) preview.style.setProperty("--df-diagram-zoom-max-width", previewZoomMaxWidth);
|
|
158
|
+
else preview.style.removeProperty("--df-diagram-zoom-max-width");
|
|
159
|
+
if (previewScale == null) preview.removeAttribute("data-df-scale");
|
|
160
|
+
else preview.setAttribute("data-df-scale", previewScale);
|
|
161
|
+
}
|
|
162
|
+
buttonStates.forEach(({ button, innerHTML, ariaLabel, copied, actionError, disabled }) => {
|
|
163
|
+
button.innerHTML = innerHTML;
|
|
164
|
+
button.disabled = disabled;
|
|
165
|
+
if (ariaLabel === null) button.removeAttribute("aria-label");
|
|
166
|
+
else button.setAttribute("aria-label", ariaLabel);
|
|
167
|
+
if (copied === null) button.removeAttribute("data-copied");
|
|
168
|
+
else button.setAttribute("data-copied", copied);
|
|
169
|
+
if (actionError === null) button.removeAttribute("data-df-action-error");
|
|
170
|
+
else button.setAttribute("data-df-action-error", actionError);
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function enhanceDiagrams(root, kind, render) {
|
|
175
|
+
let active = true;
|
|
176
|
+
let disposed = false;
|
|
177
|
+
const figures = Array.from(root.querySelectorAll(`[data-df-plugin-diagram="${kind}"]`)).filter(
|
|
178
|
+
(figure) => figure.dataset.dfEnhanced !== "true"
|
|
179
|
+
);
|
|
180
|
+
const renderErrorStates = figures.map(
|
|
181
|
+
(figure) => [figure, figure.getAttribute("data-df-render-error")]
|
|
182
|
+
);
|
|
183
|
+
const cleanups = [];
|
|
184
|
+
const renders = [];
|
|
185
|
+
for (const figure of figures) {
|
|
186
|
+
figure.dataset.dfEnhanced = "true";
|
|
187
|
+
cleanups.push(setupActions(figure));
|
|
188
|
+
if (render) {
|
|
189
|
+
renders.push(
|
|
190
|
+
render(figure).catch((error) => {
|
|
191
|
+
if (!active) return;
|
|
192
|
+
figure.dataset.dfRenderError = "true";
|
|
193
|
+
console.error(`[docfuse] ${kind} render failed:`, error);
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const dispose = () => {
|
|
199
|
+
if (disposed) return;
|
|
200
|
+
disposed = true;
|
|
201
|
+
active = false;
|
|
202
|
+
cleanups.reverse().forEach((cleanup) => cleanup());
|
|
203
|
+
renderErrorStates.forEach(([figure, renderError]) => {
|
|
204
|
+
delete figure.dataset.dfEnhanced;
|
|
205
|
+
if (renderError === null) figure.removeAttribute("data-df-render-error");
|
|
206
|
+
else figure.setAttribute("data-df-render-error", renderError);
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
return Object.assign(dispose, {
|
|
210
|
+
ready: Promise.all(renders).then(() => void 0),
|
|
211
|
+
dispose
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/client/plantuml.ts
|
|
216
|
+
function enhance(root = document) {
|
|
217
|
+
return enhanceDiagrams(root, "plantuml");
|
|
218
|
+
}
|
|
219
|
+
export {
|
|
220
|
+
enhance
|
|
221
|
+
};
|