@simon_he/vue-tui 1.1.3 → 1.1.5
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 +18 -18
- package/dist/{TMermaidText-frVr8Ysb.js → TMermaidText-wLnMMvVS.js} +1 -1
- package/dist/{TSelect-Dx34M1pF.js → TSelect-CMBOOFwK.js} +5 -5
- package/dist/{TTree-DdAjCPHu.js → TTree-kk8ntrpg.js} +6 -5
- package/dist/agent/mermaid.d.cts +2 -2
- package/dist/agent/mermaid.d.ts +2 -2
- package/dist/agent.cjs +542 -266
- package/dist/agent.js +25 -82
- package/dist/{block-source-Crro3mxi.js → block-source-DhzajB9H.js} +299 -239
- package/dist/cli.cjs +57 -16
- package/dist/cli.js +70 -15
- package/dist/core.js +3 -3
- package/dist/{hyperlink-_1FAj7Wq.js → create-terminal-Db_-UGVQ.js} +474 -102
- package/dist/{dom-renderer-DXx8fWzD.js → dom-renderer-BfwAEQMf.js} +3 -2
- package/dist/experimental.cjs +103 -66
- package/dist/experimental.js +20 -74
- package/dist/hyperlink-clbb_ha_.js +100 -0
- package/dist/index.js +8 -8
- package/dist/{linkify-BjI4zVfY.js → linkify-CL9wwnZN.js} +3 -3
- package/dist/markdown.cjs +521 -212
- package/dist/markdown.d.cts +3 -0
- package/dist/markdown.d.ts +3 -0
- package/dist/markdown.js +75 -8
- package/dist/math-image-BZhmfUeu.js +308 -0
- package/dist/mermaid-N3kGTHFy.js +1697 -0
- package/dist/mermaid.cjs +1460 -6
- package/dist/mermaid.d.cts +4 -0
- package/dist/mermaid.d.ts +4 -0
- package/dist/mermaid.js +7 -5
- package/dist/{rect-DUpeDfBg.js → rect-JzWMJxxc.js} +1 -1
- package/dist/renderer/cli/stdout-renderer.d.ts +44 -0
- package/dist/renderer/terminal-graphics.d.ts +61 -0
- package/dist/renderer-dom.js +4 -4
- package/dist/{terminal-graphics-Bbe83wSr.js → terminal-graphics-2FTRg9x3.js} +90 -2
- package/dist/{tlog-theme-i6S_Oftd.js → tlog-theme-B91Wv7Zj.js} +5 -4
- package/dist/vue/components/TAgentTerminalGraphic.d.ts +1 -1
- package/dist/vue/components/TMarkdownText.d.ts +70 -2
- package/dist/vue/components/TMermaidImage.d.ts +920 -0
- package/dist/vue/components/TMermaidText.d.ts +2 -2
- package/dist/vue/components/TVirtualMarkdown.d.ts +70 -2
- package/dist/vue/markdown/ast.d.ts +5 -0
- package/dist/vue/markdown/document.d.ts +3 -0
- package/dist/vue/markdown/math-image.d.ts +68 -0
- package/dist/vue/markdown/theme.d.ts +12 -0
- package/dist/vue/markdown/types.d.ts +12 -2
- package/dist/vue/mermaid/beautiful-mermaid.d.ts +284 -240
- package/dist/vue/mermaid/mermaid-image.d.ts +73 -0
- package/dist/vue.js +9 -9
- package/package.json +24 -10
- package/dist/mermaid-BNhM1jAZ.js +0 -123
- package/dist/width-GqllnV8C.js +0 -474
- /package/dist/{ansi-palette-CH6jc2Z7.js → ansi-palette-BHF1rN7R.js} +0 -0
|
@@ -0,0 +1,1697 @@
|
|
|
1
|
+
import { EventZIndexContextKey, intersectRect, padEndByCells$1 as padEndByCells, repeatChar, sanitizeInlineText, sanitizeTextBlock, sliceByCells, sliceByCellsRange, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./rect-JzWMJxxc.js";
|
|
2
|
+
import { TMermaidText$1 as TMermaidText, isSimpleMermaidFlowchartSource, markMermaidRenderErrorFatal, tMermaidTextProps } from "./TMermaidText-wLnMMvVS.js";
|
|
3
|
+
import { createTerminalGraphicPngSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, stableTerminalGraphicNumericId, subscribeTerminalGraphicsOutput } from "./terminal-graphics-2FTRg9x3.js";
|
|
4
|
+
import { resolveMarkdownMathColor } from "./math-image-BZhmfUeu.js";
|
|
5
|
+
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, shallowRef, watch } from "vue";
|
|
6
|
+
|
|
7
|
+
//#region src/vue/mermaid/mermaid-image.ts
|
|
8
|
+
const DEFAULT_MERMAID_IMAGE_OPTIONS = Object.freeze({
|
|
9
|
+
cellWidthPx: 8,
|
|
10
|
+
cellHeightPx: 16,
|
|
11
|
+
scale: 2,
|
|
12
|
+
bg: "#1e1e2e",
|
|
13
|
+
fg: "#f8f8f2",
|
|
14
|
+
maxWidthCells: 0,
|
|
15
|
+
maxHeightCells: 0,
|
|
16
|
+
padding: 40
|
|
17
|
+
});
|
|
18
|
+
const MAX_CACHE_ENTRIES = 48;
|
|
19
|
+
const MAX_FAILED_ENTRIES = 96;
|
|
20
|
+
const mermaidImageCache = new Map();
|
|
21
|
+
const mermaidImageFailed = new Set();
|
|
22
|
+
const mermaidImageInflight = new Map();
|
|
23
|
+
const mermaidImageListeners = new Set();
|
|
24
|
+
let customRasterizer = null;
|
|
25
|
+
let builtinRasterizerLoad = null;
|
|
26
|
+
let builtinRasterizerReady = false;
|
|
27
|
+
function normalizeMermaidImageOptions(options) {
|
|
28
|
+
return {
|
|
29
|
+
cellWidthPx: Math.max(1, Math.floor(options?.cellWidthPx ?? DEFAULT_MERMAID_IMAGE_OPTIONS.cellWidthPx)),
|
|
30
|
+
cellHeightPx: Math.max(1, Math.floor(options?.cellHeightPx ?? DEFAULT_MERMAID_IMAGE_OPTIONS.cellHeightPx)),
|
|
31
|
+
scale: Math.max(1, Math.floor(options?.scale ?? DEFAULT_MERMAID_IMAGE_OPTIONS.scale)),
|
|
32
|
+
bg: options?.bg ?? DEFAULT_MERMAID_IMAGE_OPTIONS.bg,
|
|
33
|
+
fg: options?.fg ?? DEFAULT_MERMAID_IMAGE_OPTIONS.fg,
|
|
34
|
+
maxWidthCells: Math.max(0, Math.floor(options?.maxWidthCells ?? 0)),
|
|
35
|
+
maxHeightCells: Math.max(0, Math.floor(options?.maxHeightCells ?? 0)),
|
|
36
|
+
padding: Math.max(0, Math.floor(options?.padding ?? DEFAULT_MERMAID_IMAGE_OPTIONS.padding))
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a diagram foreground/background from a terminal style value.
|
|
41
|
+
* Reuses the named-color table shared with the math rasterizer.
|
|
42
|
+
*/
|
|
43
|
+
function resolveMermaidImageColor(fg) {
|
|
44
|
+
return resolveMarkdownMathColor(fg);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Install a custom mermaid -> PNG rasterizer (e.g. consumers that render
|
|
48
|
+
* mermaid through their own pipeline). Passing `null` restores the built-in
|
|
49
|
+
* lazy loader (beautiful-mermaid SVG + @resvg/resvg-js).
|
|
50
|
+
*/
|
|
51
|
+
function setMermaidImageRasterizer(rasterizer) {
|
|
52
|
+
customRasterizer = rasterizer;
|
|
53
|
+
}
|
|
54
|
+
function subscribeMermaidImage(listener) {
|
|
55
|
+
mermaidImageListeners.add(listener);
|
|
56
|
+
return () => mermaidImageListeners.delete(listener);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* True when a rasterizer is confirmed available (custom one installed, or the
|
|
60
|
+
* built-in beautiful-mermaid + resvg stack finished loading). Components gate
|
|
61
|
+
* image production on this so they never re-rasterize while the engine is
|
|
62
|
+
* still loading or missing.
|
|
63
|
+
*/
|
|
64
|
+
function isMermaidImageRendererReady() {
|
|
65
|
+
return customRasterizer != null || builtinRasterizerReady;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ensure the mermaid rasterizer is loaded (or resolved as unavailable).
|
|
69
|
+
* Resolves `true` once a rasterizer is usable. Safe to call repeatedly; the
|
|
70
|
+
* underlying load is shared and its outcome is cached.
|
|
71
|
+
*/
|
|
72
|
+
async function loadMermaidImageRenderer() {
|
|
73
|
+
if (isMermaidImageRendererReady()) return true;
|
|
74
|
+
const rasterizer = customRasterizer ?? await loadBuiltinMermaidRasterizer();
|
|
75
|
+
builtinRasterizerReady = rasterizer != null;
|
|
76
|
+
return builtinRasterizerReady;
|
|
77
|
+
}
|
|
78
|
+
function hashMermaidImageKey(value) {
|
|
79
|
+
let hash = 2166136261;
|
|
80
|
+
for (let i = 0; i < value.length; i++) {
|
|
81
|
+
hash ^= value.charCodeAt(i);
|
|
82
|
+
hash = Math.imul(hash, 16777619);
|
|
83
|
+
}
|
|
84
|
+
return (hash >>> 0).toString(36);
|
|
85
|
+
}
|
|
86
|
+
function mermaidImageCacheKey(code, options) {
|
|
87
|
+
return hashMermaidImageKey([
|
|
88
|
+
code,
|
|
89
|
+
options.cellWidthPx,
|
|
90
|
+
options.cellHeightPx,
|
|
91
|
+
options.scale,
|
|
92
|
+
options.bg,
|
|
93
|
+
options.fg,
|
|
94
|
+
options.maxWidthCells,
|
|
95
|
+
options.maxHeightCells,
|
|
96
|
+
options.padding
|
|
97
|
+
].join(""));
|
|
98
|
+
}
|
|
99
|
+
function notifyMermaidImageListeners() {
|
|
100
|
+
for (const listener of mermaidImageListeners) try {
|
|
101
|
+
listener();
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
104
|
+
/** Synchronous cache lookup used by components before deciding to re-render. */
|
|
105
|
+
function getCachedMermaidImage(code, options) {
|
|
106
|
+
return mermaidImageCache.get(mermaidImageCacheKey(code, normalizeMermaidImageOptions(options))) ?? null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Drop all cached mermaid images (PNG + failed lookups). Useful for long
|
|
110
|
+
* running processes that change cell metrics, and for tests.
|
|
111
|
+
*/
|
|
112
|
+
function clearMermaidImageCache() {
|
|
113
|
+
mermaidImageCache.clear();
|
|
114
|
+
mermaidImageFailed.clear();
|
|
115
|
+
}
|
|
116
|
+
function evictOldestMermaidImageEntry() {
|
|
117
|
+
const oldest = mermaidImageCache.keys().next().value;
|
|
118
|
+
if (oldest != null) mermaidImageCache.delete(oldest);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolve a mermaid diagram to a PNG base64 + cell size. Cached per
|
|
122
|
+
* (code, cell metrics, colors). In-flight requests are deduped. When a new
|
|
123
|
+
* image finishes, `subscribeMermaidImage` listeners are notified so components
|
|
124
|
+
* can rebuild/re-paint with the real size.
|
|
125
|
+
*/
|
|
126
|
+
async function getMermaidImage(code, options) {
|
|
127
|
+
const source = String(code ?? "").trim();
|
|
128
|
+
if (!source) return null;
|
|
129
|
+
const normalized = normalizeMermaidImageOptions(options);
|
|
130
|
+
const key = mermaidImageCacheKey(source, normalized);
|
|
131
|
+
const cached = mermaidImageCache.get(key);
|
|
132
|
+
if (cached) return cached;
|
|
133
|
+
if (mermaidImageFailed.has(key)) return null;
|
|
134
|
+
const inflight = mermaidImageInflight.get(key);
|
|
135
|
+
if (inflight) return inflight;
|
|
136
|
+
const pending = (async () => {
|
|
137
|
+
try {
|
|
138
|
+
const rasterizer = customRasterizer ?? await loadBuiltinMermaidRasterizer();
|
|
139
|
+
if (!rasterizer) return null;
|
|
140
|
+
const result = await rasterizer(source, normalized);
|
|
141
|
+
if (result && result.base64) {
|
|
142
|
+
mermaidImageCache.set(key, result);
|
|
143
|
+
if (mermaidImageCache.size > MAX_CACHE_ENTRIES) evictOldestMermaidImageEntry();
|
|
144
|
+
notifyMermaidImageListeners();
|
|
145
|
+
} else if (result === null) {
|
|
146
|
+
mermaidImageFailed.add(key);
|
|
147
|
+
if (mermaidImageFailed.size > MAX_FAILED_ENTRIES) {
|
|
148
|
+
const oldest = mermaidImageFailed.values().next().value;
|
|
149
|
+
if (oldest != null) mermaidImageFailed.delete(oldest);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
} finally {
|
|
156
|
+
mermaidImageInflight.delete(key);
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
mermaidImageInflight.set(key, pending);
|
|
160
|
+
return pending;
|
|
161
|
+
}
|
|
162
|
+
function base64FromBytes(bytes) {
|
|
163
|
+
const chunk = 32768;
|
|
164
|
+
let binary = "";
|
|
165
|
+
for (let offset = 0; offset < bytes.length; offset += chunk) binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
|
|
166
|
+
return typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64");
|
|
167
|
+
}
|
|
168
|
+
function positiveInt(value) {
|
|
169
|
+
const n = Math.floor(Number(value));
|
|
170
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
171
|
+
}
|
|
172
|
+
function clampCellsToLimits(widthCells, heightCells, maxWidthCells, maxHeightCells) {
|
|
173
|
+
let w = widthCells;
|
|
174
|
+
let h$1 = heightCells;
|
|
175
|
+
if (maxWidthCells > 0 && w > maxWidthCells) {
|
|
176
|
+
h$1 = Math.max(1, Math.round(h$1 * maxWidthCells / w));
|
|
177
|
+
w = maxWidthCells;
|
|
178
|
+
}
|
|
179
|
+
if (maxHeightCells > 0 && h$1 > maxHeightCells) {
|
|
180
|
+
w = Math.max(1, Math.round(w * maxHeightCells / h$1));
|
|
181
|
+
h$1 = maxHeightCells;
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
widthCells: Math.max(1, w),
|
|
185
|
+
heightCells: Math.max(1, h$1)
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* @resvg/resvg-js is a Node-only native addon. Keeping the specifier in a
|
|
190
|
+
* variable (a non-literal dynamic import) stops browser bundlers
|
|
191
|
+
* (vite/rolldown) from statically resolving it and failing on its platform
|
|
192
|
+
* `.node` binding; at runtime a browser load fails and the rasterizer falls
|
|
193
|
+
* back to raw source.
|
|
194
|
+
*/
|
|
195
|
+
const RESVG_MODULE_ID = "@resvg/resvg-js";
|
|
196
|
+
function svgRenderFunction(mod) {
|
|
197
|
+
const candidates = [
|
|
198
|
+
mod.renderMermaidSVGAsync,
|
|
199
|
+
mod.renderMermaidSVG,
|
|
200
|
+
mod.renderMermaid,
|
|
201
|
+
mod.default?.renderMermaidSVGAsync,
|
|
202
|
+
mod.default?.renderMermaidSVG
|
|
203
|
+
];
|
|
204
|
+
for (const candidate of candidates) if (typeof candidate === "function") return candidate;
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* @resvg/resvg-js (usvg) does not support CSS custom properties (`var()`) or
|
|
209
|
+
* `color-mix()`. beautiful-mermaid 1.x emits both, so feeding the raw SVG to
|
|
210
|
+
* resvg renders every shape black. This resolver inlines all `var(--x)` /
|
|
211
|
+
* `color-mix(in srgb, ...)` color expressions into concrete hex values before
|
|
212
|
+
* rasterization. Exported for tests.
|
|
213
|
+
*/
|
|
214
|
+
function resolveMermaidSvgForResvg(svg, fallbackBg, fallbackFg) {
|
|
215
|
+
const { bg, fg } = extractSvgRootStyleColors(svg, fallbackBg, fallbackFg);
|
|
216
|
+
const variables = extractSvgCssVariables(svg);
|
|
217
|
+
const root = {
|
|
218
|
+
bg,
|
|
219
|
+
fg
|
|
220
|
+
};
|
|
221
|
+
let resolved = svg;
|
|
222
|
+
let guard = 0;
|
|
223
|
+
while ((resolved.includes("var(") || resolved.includes("color-mix(")) && guard++ < 500) {
|
|
224
|
+
const varIndex = resolved.lastIndexOf("var(");
|
|
225
|
+
const mixIndex = resolved.lastIndexOf("color-mix(");
|
|
226
|
+
const at = Math.max(varIndex, mixIndex);
|
|
227
|
+
if (at < 0) break;
|
|
228
|
+
const open = resolved.indexOf("(", at);
|
|
229
|
+
const close = findMatchingSvgParen(resolved, open);
|
|
230
|
+
if (open < 0 || close < 0) break;
|
|
231
|
+
const expr = resolved.slice(at, close + 1);
|
|
232
|
+
const color = resolveSvgColorExpr(expr, root, variables);
|
|
233
|
+
resolved = resolved.slice(0, at) + color + resolved.slice(close + 1);
|
|
234
|
+
}
|
|
235
|
+
return stripSvgStyleBlocks(resolved);
|
|
236
|
+
}
|
|
237
|
+
function extractSvgRootStyleColors(svg, fallbackBg, fallbackFg) {
|
|
238
|
+
let bg = fallbackBg;
|
|
239
|
+
let fg = fallbackFg;
|
|
240
|
+
const styleMatch = /<svg\b[^>]*\bstyle="([^"]*)"/i.exec(svg);
|
|
241
|
+
if (styleMatch?.[1]) {
|
|
242
|
+
const bgMatch = /(?:^|;)\s*--bg\s*:\s*([^;]+)/i.exec(styleMatch[1]);
|
|
243
|
+
const fgMatch = /(?:^|;)\s*--fg\s*:\s*([^;]+)/i.exec(styleMatch[1]);
|
|
244
|
+
if (bgMatch?.[1]) bg = bgMatch[1].trim();
|
|
245
|
+
if (fgMatch?.[1]) fg = fgMatch[1].trim();
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
bg,
|
|
249
|
+
fg
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function extractSvgCssVariables(svg) {
|
|
253
|
+
const map = new Map();
|
|
254
|
+
const styleMatch = /<style[^>]*>([\s\S]*?)<\/style>/i.exec(svg);
|
|
255
|
+
if (!styleMatch?.[1]) return map;
|
|
256
|
+
const declaration = /(--[A-Za-z0-9_-]+)\s*:\s*([^;{}]+);/g;
|
|
257
|
+
let match;
|
|
258
|
+
while ((match = declaration.exec(styleMatch[1])) !== null) map.set(match[1], match[2].trim());
|
|
259
|
+
return map;
|
|
260
|
+
}
|
|
261
|
+
function stripSvgStyleBlocks(svg) {
|
|
262
|
+
return svg.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
263
|
+
}
|
|
264
|
+
function findMatchingSvgParen(input, openIndex) {
|
|
265
|
+
let depth = 0;
|
|
266
|
+
for (let i = openIndex; i < input.length; i++) {
|
|
267
|
+
const ch = input[i];
|
|
268
|
+
if (ch === "(") depth++;
|
|
269
|
+
else if (ch === ")") {
|
|
270
|
+
depth--;
|
|
271
|
+
if (depth === 0) return i;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return -1;
|
|
275
|
+
}
|
|
276
|
+
function findTopLevelSvgComma(input) {
|
|
277
|
+
let depth = 0;
|
|
278
|
+
for (let i = 0; i < input.length; i++) {
|
|
279
|
+
const ch = input[i];
|
|
280
|
+
if (ch === "(") depth++;
|
|
281
|
+
else if (ch === ")") depth--;
|
|
282
|
+
else if (ch === "," && depth === 0) return i;
|
|
283
|
+
}
|
|
284
|
+
return -1;
|
|
285
|
+
}
|
|
286
|
+
function normalizeHexColor(value) {
|
|
287
|
+
const match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value.trim());
|
|
288
|
+
if (!match) return null;
|
|
289
|
+
let hex = match[1];
|
|
290
|
+
if (hex.length === 3) hex = hex.split("").map((ch) => ch + ch).join("");
|
|
291
|
+
return `#${hex.toLowerCase()}`;
|
|
292
|
+
}
|
|
293
|
+
function mixSrgbColors(aHex, bHex, percent) {
|
|
294
|
+
const a = parseHexRgb(aHex);
|
|
295
|
+
const b = parseHexRgb(bHex);
|
|
296
|
+
const p = Math.max(0, Math.min(100, percent)) / 100;
|
|
297
|
+
const mix = (x, y) => Math.round(x * p + y * (1 - p));
|
|
298
|
+
const toHex = (value) => value.toString(16).padStart(2, "0");
|
|
299
|
+
return `#${toHex(mix(a[0], b[0]))}${toHex(mix(a[1], b[1]))}${toHex(mix(a[2], b[2]))}`;
|
|
300
|
+
}
|
|
301
|
+
function parseHexRgb(hex) {
|
|
302
|
+
return [
|
|
303
|
+
parseInt(hex.slice(1, 3), 16),
|
|
304
|
+
parseInt(hex.slice(3, 5), 16),
|
|
305
|
+
parseInt(hex.slice(5, 7), 16)
|
|
306
|
+
];
|
|
307
|
+
}
|
|
308
|
+
function resolveSvgColorExpr(expr, root, variables) {
|
|
309
|
+
const value = expr.trim();
|
|
310
|
+
if (value.startsWith("var(")) {
|
|
311
|
+
const close = findMatchingSvgParen(value, 3);
|
|
312
|
+
if (close < 0) return value;
|
|
313
|
+
const inner = value.slice(4, close).trim();
|
|
314
|
+
const comma = findTopLevelSvgComma(inner);
|
|
315
|
+
const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();
|
|
316
|
+
const fallback = comma < 0 ? "" : inner.slice(comma + 1).trim();
|
|
317
|
+
const direct = name === "--fg" ? root.fg : name === "--bg" ? root.bg : variables.get(name);
|
|
318
|
+
if (direct != null) return resolveSvgColorExpr(direct, root, variables);
|
|
319
|
+
if (fallback) return resolveSvgColorExpr(fallback, root, variables);
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
if (value.startsWith("color-mix(")) {
|
|
323
|
+
const close = findMatchingSvgParen(value, 9);
|
|
324
|
+
if (close < 0) return value;
|
|
325
|
+
const inner = value.slice(10, close).trim();
|
|
326
|
+
const afterMethod = inner.replace(/^in\s+srgb\s*,\s*/i, "");
|
|
327
|
+
const comma = findTopLevelSvgComma(afterMethod);
|
|
328
|
+
if (comma < 0) return value;
|
|
329
|
+
const left = afterMethod.slice(0, comma).trim();
|
|
330
|
+
const right = afterMethod.slice(comma + 1).trim();
|
|
331
|
+
const leftMatch = /^([^%\s]+)\s+([\d.]+)%$/.exec(left) ?? /^([\d.]+)%\s+([^%\s]+)$/.exec(left);
|
|
332
|
+
if (!leftMatch) return value;
|
|
333
|
+
const a = normalizeHexColor(resolveSvgColorExpr(leftMatch[1], root, variables));
|
|
334
|
+
const b = normalizeHexColor(resolveSvgColorExpr(right, root, variables));
|
|
335
|
+
if (!a || !b) return value;
|
|
336
|
+
return mixSrgbColors(a, b, Number(leftMatch[2]));
|
|
337
|
+
}
|
|
338
|
+
return normalizeHexColor(value) ?? value;
|
|
339
|
+
}
|
|
340
|
+
function loadBuiltinMermaidRasterizer() {
|
|
341
|
+
if (!builtinRasterizerLoad) builtinRasterizerLoad = (async () => {
|
|
342
|
+
try {
|
|
343
|
+
const [mermaidMod, resvgMod] = await Promise.all([import("beautiful-mermaid").then((mod) => mod, () => null), import(RESVG_MODULE_ID).then((mod) => mod, () => null)]);
|
|
344
|
+
const renderSvg = svgRenderFunction(mermaidMod ?? {});
|
|
345
|
+
const Resvg = resvgMod?.Resvg;
|
|
346
|
+
if (!renderSvg || typeof Resvg !== "function") return null;
|
|
347
|
+
return async (code, options) => {
|
|
348
|
+
const svg = await renderSvg(code, {
|
|
349
|
+
bg: options.bg,
|
|
350
|
+
fg: options.fg,
|
|
351
|
+
padding: options.padding,
|
|
352
|
+
transparent: true
|
|
353
|
+
});
|
|
354
|
+
const svgText = String(svg ?? "");
|
|
355
|
+
if (!svgText.includes("<svg")) return null;
|
|
356
|
+
const resolvedSvg = resolveMermaidSvgForResvg(svgText, options.bg, options.fg);
|
|
357
|
+
const rendered = new Resvg(resolvedSvg, { background: "rgba(0,0,0,0)" }).render();
|
|
358
|
+
const naturalWidth = positiveInt(rendered?.width);
|
|
359
|
+
const naturalHeight = positiveInt(rendered?.height);
|
|
360
|
+
const png = typeof rendered?.asPng === "function" ? rendered.asPng() : void 0;
|
|
361
|
+
if (!png || png.length < 8 || naturalWidth == null || naturalHeight == null) return null;
|
|
362
|
+
const rawWidthCells = Math.max(1, Math.round(naturalWidth / (options.cellWidthPx * options.scale)));
|
|
363
|
+
const rawHeightCells = Math.max(1, Math.round(naturalHeight / (options.cellHeightPx * options.scale)));
|
|
364
|
+
const { widthCells, heightCells } = clampCellsToLimits(rawWidthCells, rawHeightCells, options.maxWidthCells, options.maxHeightCells);
|
|
365
|
+
return {
|
|
366
|
+
base64: base64FromBytes(png),
|
|
367
|
+
widthCells,
|
|
368
|
+
heightCells,
|
|
369
|
+
naturalWidth,
|
|
370
|
+
naturalHeight
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
} catch {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
})();
|
|
377
|
+
return builtinRasterizerLoad;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/vue/components/TMermaidImage.ts
|
|
382
|
+
const UNICODE_MERMAID_IMAGE_BOX_CHARS = Object.freeze({
|
|
383
|
+
tl: "┌",
|
|
384
|
+
tr: "┐",
|
|
385
|
+
bl: "└",
|
|
386
|
+
br: "┘",
|
|
387
|
+
h: "─",
|
|
388
|
+
v: "│"
|
|
389
|
+
});
|
|
390
|
+
const ASCII_MERMAID_IMAGE_BOX_CHARS = Object.freeze({
|
|
391
|
+
tl: "+",
|
|
392
|
+
tr: "+",
|
|
393
|
+
bl: "+",
|
|
394
|
+
br: "+",
|
|
395
|
+
h: "-",
|
|
396
|
+
v: "|"
|
|
397
|
+
});
|
|
398
|
+
const DEFAULT_MERMAID_IMAGE_COPIED_DURATION_MS = 1200;
|
|
399
|
+
const DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_CHARS = 2e4;
|
|
400
|
+
const DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_LINES = 400;
|
|
401
|
+
const ESC = String.fromCharCode(27);
|
|
402
|
+
const BEL = String.fromCharCode(7);
|
|
403
|
+
const TERMINAL_ESCAPE_RE = new RegExp([
|
|
404
|
+
`${ESC}\\][\\s\\S]*?(?:${BEL}|${ESC}\\\\)`,
|
|
405
|
+
`${ESC}[PX^_][\\s\\S]*?${ESC}\\\\`,
|
|
406
|
+
`${ESC}\\[[0-?]*[ -/]*[@-~]`,
|
|
407
|
+
`${ESC}[@-Z\\\\-_]`
|
|
408
|
+
].join("|"), "g");
|
|
409
|
+
function stripTerminalEscapes(value) {
|
|
410
|
+
return value.replace(TERMINAL_ESCAPE_RE, "");
|
|
411
|
+
}
|
|
412
|
+
function normalizeNonNegativeInt(value, fallback) {
|
|
413
|
+
const n = Number(value);
|
|
414
|
+
if (!Number.isFinite(n)) return fallback;
|
|
415
|
+
return Math.max(0, Math.floor(n));
|
|
416
|
+
}
|
|
417
|
+
function countLinesUpTo(value, max) {
|
|
418
|
+
if (max <= 0) return 0;
|
|
419
|
+
let lines = 1;
|
|
420
|
+
for (let index = 0; index < value.length; index++) {
|
|
421
|
+
const code = value.charCodeAt(index);
|
|
422
|
+
if (code !== 10 && code !== 13) continue;
|
|
423
|
+
lines++;
|
|
424
|
+
if (code === 13 && value.charCodeAt(index + 1) === 10) index++;
|
|
425
|
+
if (lines > max) return lines;
|
|
426
|
+
}
|
|
427
|
+
return lines;
|
|
428
|
+
}
|
|
429
|
+
function splitRenderedOutput(value) {
|
|
430
|
+
const normalized = sanitizeTextBlock(stripTerminalEscapes(String(value ?? "")).replace(/\r\n?/g, "\n"));
|
|
431
|
+
const lines = normalized.split("\n");
|
|
432
|
+
return lines.length ? lines : [""];
|
|
433
|
+
}
|
|
434
|
+
function fitImageCells(cells, maxW, maxH) {
|
|
435
|
+
if (maxW <= 0 || maxH <= 0) return null;
|
|
436
|
+
let w = Math.max(1, Math.floor(cells.widthCells));
|
|
437
|
+
let h$1 = Math.max(1, Math.floor(cells.heightCells));
|
|
438
|
+
if (w > maxW) {
|
|
439
|
+
h$1 = Math.max(1, Math.round(h$1 * maxW / w));
|
|
440
|
+
w = maxW;
|
|
441
|
+
}
|
|
442
|
+
if (h$1 > maxH) {
|
|
443
|
+
w = Math.max(1, Math.round(w * maxH / h$1));
|
|
444
|
+
h$1 = maxH;
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
displayW: Math.max(1, w),
|
|
448
|
+
displayH: Math.max(1, h$1)
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const tMermaidImageProps = {
|
|
452
|
+
x: {
|
|
453
|
+
type: Number,
|
|
454
|
+
required: true
|
|
455
|
+
},
|
|
456
|
+
y: {
|
|
457
|
+
type: Number,
|
|
458
|
+
required: true
|
|
459
|
+
},
|
|
460
|
+
w: {
|
|
461
|
+
type: Number,
|
|
462
|
+
required: true
|
|
463
|
+
},
|
|
464
|
+
h: {
|
|
465
|
+
type: Number,
|
|
466
|
+
default: void 0
|
|
467
|
+
},
|
|
468
|
+
zIndex: {
|
|
469
|
+
type: Number,
|
|
470
|
+
default: 0
|
|
471
|
+
},
|
|
472
|
+
content: {
|
|
473
|
+
type: String,
|
|
474
|
+
default: ""
|
|
475
|
+
},
|
|
476
|
+
code: {
|
|
477
|
+
type: String,
|
|
478
|
+
default: void 0
|
|
479
|
+
},
|
|
480
|
+
style: {
|
|
481
|
+
type: Object,
|
|
482
|
+
default: void 0
|
|
483
|
+
},
|
|
484
|
+
clear: {
|
|
485
|
+
type: Boolean,
|
|
486
|
+
default: true
|
|
487
|
+
},
|
|
488
|
+
final: {
|
|
489
|
+
type: Boolean,
|
|
490
|
+
default: true
|
|
491
|
+
},
|
|
492
|
+
streaming: {
|
|
493
|
+
type: Boolean,
|
|
494
|
+
default: false
|
|
495
|
+
},
|
|
496
|
+
renderer: {
|
|
497
|
+
type: Function,
|
|
498
|
+
default: void 0
|
|
499
|
+
},
|
|
500
|
+
cellWidthPx: {
|
|
501
|
+
type: Number,
|
|
502
|
+
default: void 0
|
|
503
|
+
},
|
|
504
|
+
cellHeightPx: {
|
|
505
|
+
type: Number,
|
|
506
|
+
default: void 0
|
|
507
|
+
},
|
|
508
|
+
scale: {
|
|
509
|
+
type: Number,
|
|
510
|
+
default: void 0
|
|
511
|
+
},
|
|
512
|
+
bg: {
|
|
513
|
+
type: String,
|
|
514
|
+
default: void 0
|
|
515
|
+
},
|
|
516
|
+
fg: {
|
|
517
|
+
type: String,
|
|
518
|
+
default: void 0
|
|
519
|
+
},
|
|
520
|
+
maxWidthCells: {
|
|
521
|
+
type: Number,
|
|
522
|
+
default: void 0
|
|
523
|
+
},
|
|
524
|
+
maxHeightCells: {
|
|
525
|
+
type: Number,
|
|
526
|
+
default: void 0
|
|
527
|
+
},
|
|
528
|
+
padding: {
|
|
529
|
+
type: Number,
|
|
530
|
+
default: void 0
|
|
531
|
+
},
|
|
532
|
+
textRenderer: {
|
|
533
|
+
type: Function,
|
|
534
|
+
default: void 0
|
|
535
|
+
},
|
|
536
|
+
shouldRenderSource: {
|
|
537
|
+
type: Function,
|
|
538
|
+
default: void 0
|
|
539
|
+
},
|
|
540
|
+
textOptions: {
|
|
541
|
+
type: Object,
|
|
542
|
+
default: void 0
|
|
543
|
+
},
|
|
544
|
+
box: {
|
|
545
|
+
type: Boolean,
|
|
546
|
+
default: true
|
|
547
|
+
},
|
|
548
|
+
title: {
|
|
549
|
+
type: String,
|
|
550
|
+
default: "mermaid"
|
|
551
|
+
},
|
|
552
|
+
copyButton: {
|
|
553
|
+
type: Boolean,
|
|
554
|
+
default: true
|
|
555
|
+
},
|
|
556
|
+
copyText: {
|
|
557
|
+
type: String,
|
|
558
|
+
default: "copy"
|
|
559
|
+
},
|
|
560
|
+
copiedText: {
|
|
561
|
+
type: String,
|
|
562
|
+
default: "copied"
|
|
563
|
+
},
|
|
564
|
+
copiedDurationMs: {
|
|
565
|
+
type: Number,
|
|
566
|
+
default: DEFAULT_MERMAID_IMAGE_COPIED_DURATION_MS
|
|
567
|
+
},
|
|
568
|
+
renderTimeoutMs: {
|
|
569
|
+
type: Number,
|
|
570
|
+
default: 0
|
|
571
|
+
},
|
|
572
|
+
maxRenderSourceChars: {
|
|
573
|
+
type: Number,
|
|
574
|
+
default: DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_CHARS
|
|
575
|
+
},
|
|
576
|
+
maxRenderSourceLines: {
|
|
577
|
+
type: Number,
|
|
578
|
+
default: DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_LINES
|
|
579
|
+
},
|
|
580
|
+
copyOnClick: {
|
|
581
|
+
type: Boolean,
|
|
582
|
+
default: true
|
|
583
|
+
},
|
|
584
|
+
ascii: {
|
|
585
|
+
type: Boolean,
|
|
586
|
+
default: false
|
|
587
|
+
},
|
|
588
|
+
zoomOnWheel: {
|
|
589
|
+
type: Boolean,
|
|
590
|
+
default: true
|
|
591
|
+
},
|
|
592
|
+
zoomModifier: {
|
|
593
|
+
type: String,
|
|
594
|
+
default: "metaCtrl"
|
|
595
|
+
},
|
|
596
|
+
minZoom: {
|
|
597
|
+
type: Number,
|
|
598
|
+
default: 1
|
|
599
|
+
},
|
|
600
|
+
maxZoom: {
|
|
601
|
+
type: Number,
|
|
602
|
+
default: 6
|
|
603
|
+
},
|
|
604
|
+
zoomSensitivity: {
|
|
605
|
+
type: Number,
|
|
606
|
+
default: .002
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
const TMermaidImage = defineComponent({
|
|
610
|
+
name: "TMermaidImage",
|
|
611
|
+
props: tMermaidImageProps,
|
|
612
|
+
emits: { copy: (_payload) => true },
|
|
613
|
+
setup(props, { emit }) {
|
|
614
|
+
const instance = getCurrentInstance();
|
|
615
|
+
const terminalContext = useTerminal();
|
|
616
|
+
const { terminal, defaultStyle, scheduler, widthProvider } = terminalContext;
|
|
617
|
+
const layout = useLayout();
|
|
618
|
+
const { visible, rootProps } = useVisibility();
|
|
619
|
+
const parentEventZ = inject(EventZIndexContextKey, computed(() => 0));
|
|
620
|
+
const imageCells = shallowRef(null);
|
|
621
|
+
const textSnapshot = shallowRef(null);
|
|
622
|
+
const zoomScale = shallowRef(1);
|
|
623
|
+
const panX = shallowRef(0);
|
|
624
|
+
const panY = shallowRef(0);
|
|
625
|
+
const documentVersion = shallowRef(0);
|
|
626
|
+
const copied = shallowRef(false);
|
|
627
|
+
let dragState = null;
|
|
628
|
+
let dragMoved = false;
|
|
629
|
+
let lastDragAt = 0;
|
|
630
|
+
const DRAG_CLICK_SUPPRESS_MS = 250;
|
|
631
|
+
let builtOnce = false;
|
|
632
|
+
let renderVersion = 0;
|
|
633
|
+
let alive = true;
|
|
634
|
+
const frameTaskId = `TMermaidImage:${instance?.uid ?? "unknown"}:mermaid-image`;
|
|
635
|
+
const uid = instance?.uid ?? 0;
|
|
636
|
+
let copiedTimer = null;
|
|
637
|
+
let copyRequestVersion = 0;
|
|
638
|
+
let activeGraphicId = null;
|
|
639
|
+
let activeImageSignature = "";
|
|
640
|
+
const source = computed(() => props.code ?? props.content ?? "");
|
|
641
|
+
const waitingForStreamingSourceToFinish = computed(() => props.streaming && !props.final);
|
|
642
|
+
const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
|
|
643
|
+
const graphicsOutput = computed(() => {
|
|
644
|
+
graphicsOutputVersion.value;
|
|
645
|
+
return getTerminalGraphicsOutput(terminal);
|
|
646
|
+
});
|
|
647
|
+
const graphicsProtocol = computed(() => {
|
|
648
|
+
const output = graphicsOutput.value;
|
|
649
|
+
if (!output?.capabilities.supported) return null;
|
|
650
|
+
const protocol = output.capabilities.preferredProtocol;
|
|
651
|
+
if (protocol === "kitty" || protocol === "iterm2") return protocol;
|
|
652
|
+
return null;
|
|
653
|
+
});
|
|
654
|
+
function bump() {
|
|
655
|
+
documentVersion.value++;
|
|
656
|
+
}
|
|
657
|
+
function clearCopiedTimer() {
|
|
658
|
+
if (copiedTimer == null) return;
|
|
659
|
+
clearTimeout(copiedTimer);
|
|
660
|
+
copiedTimer = null;
|
|
661
|
+
}
|
|
662
|
+
function setCopied(next, repaint = true) {
|
|
663
|
+
if (!next) clearCopiedTimer();
|
|
664
|
+
if (copied.value === next) return;
|
|
665
|
+
copied.value = next;
|
|
666
|
+
if (repaint) bump();
|
|
667
|
+
}
|
|
668
|
+
function resetCopyFeedback(repaint = true) {
|
|
669
|
+
copyRequestVersion++;
|
|
670
|
+
setCopied(false, repaint);
|
|
671
|
+
}
|
|
672
|
+
function showCopiedFeedback() {
|
|
673
|
+
clearCopiedTimer();
|
|
674
|
+
const duration = normalizeNonNegativeInt(props.copiedDurationMs, DEFAULT_MERMAID_IMAGE_COPIED_DURATION_MS);
|
|
675
|
+
if (duration <= 0) {
|
|
676
|
+
setCopied(false);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
setCopied(true);
|
|
680
|
+
copiedTimer = setTimeout(() => {
|
|
681
|
+
copiedTimer = null;
|
|
682
|
+
if (!alive) return;
|
|
683
|
+
setCopied(false);
|
|
684
|
+
}, duration);
|
|
685
|
+
}
|
|
686
|
+
function queueClearGraphic() {
|
|
687
|
+
if (activeGraphicId == null) return;
|
|
688
|
+
const id = activeGraphicId;
|
|
689
|
+
activeGraphicId = null;
|
|
690
|
+
try {
|
|
691
|
+
graphicsOutput.value?.clear?.(id);
|
|
692
|
+
} catch {}
|
|
693
|
+
}
|
|
694
|
+
function clampZoom(zoom) {
|
|
695
|
+
const min = Number.isFinite(Number(props.minZoom)) ? Math.max(.05, Number(props.minZoom)) : 1;
|
|
696
|
+
const max = Number.isFinite(Number(props.maxZoom)) ? Math.max(min, Number(props.maxZoom)) : Math.max(min, 6);
|
|
697
|
+
return Math.min(max, Math.max(min, zoom));
|
|
698
|
+
}
|
|
699
|
+
function zoomSensitivity() {
|
|
700
|
+
const value = Number(props.zoomSensitivity);
|
|
701
|
+
return Number.isFinite(value) && value > 0 ? value : .002;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Wheel delta -> multiplicative zoom factor. Discrete notches (CLI wheel
|
|
705
|
+
* reporting and browser line-mode deltas) map to one step each; trackpad
|
|
706
|
+
* pixel deltas use a continuous exponential so small movements stay smooth.
|
|
707
|
+
*/
|
|
708
|
+
function wheelZoomFactor(deltaY, deltaMode) {
|
|
709
|
+
const sign = deltaY > 0 ? -1 : 1;
|
|
710
|
+
const sensitivity = zoomSensitivity();
|
|
711
|
+
const abs = Math.abs(deltaY);
|
|
712
|
+
const mode = deltaMode ?? 0;
|
|
713
|
+
const isNotch = mode === 1 || abs > 0 && abs <= 3 && Number.isInteger(deltaY);
|
|
714
|
+
const magnitude = isNotch ? 100 : abs;
|
|
715
|
+
return Math.exp(sign * magnitude * sensitivity);
|
|
716
|
+
}
|
|
717
|
+
function resetZoomState() {
|
|
718
|
+
if (zoomScale.value === 1 && panX.value === 0 && panY.value === 0) return;
|
|
719
|
+
zoomScale.value = 1;
|
|
720
|
+
panX.value = 0;
|
|
721
|
+
panY.value = 0;
|
|
722
|
+
dragState = null;
|
|
723
|
+
}
|
|
724
|
+
function hasZoomModifier(event) {
|
|
725
|
+
const modifier = props.zoomModifier ?? "metaCtrl";
|
|
726
|
+
if (modifier === "none") return true;
|
|
727
|
+
if (modifier === "meta") return event.metaKey === true;
|
|
728
|
+
if (modifier === "ctrl") return event.ctrlKey === true;
|
|
729
|
+
return event.ctrlKey === true || event.metaKey === true;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Clamp the pan so the zoomed image always covers the container area
|
|
733
|
+
* (no empty gaps, no over-scroll beyond the image edges).
|
|
734
|
+
*/
|
|
735
|
+
function clampPan() {
|
|
736
|
+
const inner = innerRectFrom(fullRect.value);
|
|
737
|
+
const fit = displayFit.value;
|
|
738
|
+
if (!fit || inner.w <= 0 || inner.h <= 0) return;
|
|
739
|
+
const zoom = clampZoom(zoomScale.value);
|
|
740
|
+
const fullW = fit.displayW * zoom;
|
|
741
|
+
const fullH = fit.displayH * zoom;
|
|
742
|
+
const fitOriginX = inner.x + (inner.w > fit.displayW ? Math.floor((inner.w - fit.displayW) / 2) : 0);
|
|
743
|
+
let nextX = panX.value;
|
|
744
|
+
let nextY = panY.value;
|
|
745
|
+
if (fullW <= inner.w) nextX = 0;
|
|
746
|
+
else nextX = Math.min(inner.x - fitOriginX, Math.max(inner.x + inner.w - fullW - fitOriginX, nextX));
|
|
747
|
+
if (fullH <= inner.h) nextY = 0;
|
|
748
|
+
else nextY = Math.min(0, Math.max(inner.h - fullH, nextY));
|
|
749
|
+
if (nextX !== panX.value) panX.value = nextX;
|
|
750
|
+
if (nextY !== panY.value) panY.value = nextY;
|
|
751
|
+
}
|
|
752
|
+
/** Set the current image, resetting wheel zoom whenever the PNG changes. */
|
|
753
|
+
function setImageCells(cells) {
|
|
754
|
+
const signature = cells ? cells.base64 : "";
|
|
755
|
+
if (signature !== activeImageSignature) {
|
|
756
|
+
activeImageSignature = signature;
|
|
757
|
+
resetZoomState();
|
|
758
|
+
}
|
|
759
|
+
imageCells.value = cells;
|
|
760
|
+
}
|
|
761
|
+
const hasBox = computed(() => props.box !== false);
|
|
762
|
+
const normalizedWidth = computed(() => {
|
|
763
|
+
const width = Math.floor(Number(props.w));
|
|
764
|
+
return Number.isFinite(width) ? Math.max(0, width) : 0;
|
|
765
|
+
});
|
|
766
|
+
const reservesBoxRows = computed(() => hasBox.value && normalizedWidth.value >= 2);
|
|
767
|
+
const boxChars = computed(() => props.ascii ? ASCII_MERMAID_IMAGE_BOX_CHARS : UNICODE_MERMAID_IMAGE_BOX_CHARS);
|
|
768
|
+
const sourceLines = computed(() => splitRenderedOutput(source.value));
|
|
769
|
+
const displayLines = computed(() => {
|
|
770
|
+
const snapshot = textSnapshot.value;
|
|
771
|
+
if (snapshot && snapshot.source === source.value && hasVisibleTextOutput(snapshot.lines)) return snapshot.lines;
|
|
772
|
+
return sourceLines.value;
|
|
773
|
+
});
|
|
774
|
+
const currentStyle = computed(() => props.style ?? defaultStyle.value);
|
|
775
|
+
const innerWidth = computed(() => {
|
|
776
|
+
const w = normalizedWidth.value;
|
|
777
|
+
return reservesBoxRows.value ? Math.max(0, w - 2) : w;
|
|
778
|
+
});
|
|
779
|
+
const fixedInnerHeight = computed(() => {
|
|
780
|
+
if (props.h == null) return 0;
|
|
781
|
+
const h$1 = Math.floor(Number(props.h));
|
|
782
|
+
if (!Number.isFinite(h$1)) return 0;
|
|
783
|
+
const value = Math.max(0, h$1);
|
|
784
|
+
return reservesBoxRows.value ? Math.max(0, value - 2) : value;
|
|
785
|
+
});
|
|
786
|
+
const autoHeight = computed(() => props.h == null);
|
|
787
|
+
const displayFit = computed(() => {
|
|
788
|
+
const cells = imageCells.value;
|
|
789
|
+
if (!cells) return null;
|
|
790
|
+
if (autoHeight.value) return fitImageCells(cells, innerWidth.value, Number.MAX_SAFE_INTEGER);
|
|
791
|
+
return fitImageCells(cells, innerWidth.value, fixedInnerHeight.value);
|
|
792
|
+
});
|
|
793
|
+
const contentHeight = computed(() => {
|
|
794
|
+
if (!autoHeight.value) return fixedInnerHeight.value;
|
|
795
|
+
return displayFit.value?.displayH ?? Math.max(1, displayLines.value.length);
|
|
796
|
+
});
|
|
797
|
+
const fullHeight = computed(() => {
|
|
798
|
+
if (props.h != null) {
|
|
799
|
+
const h$1 = Math.floor(Number(props.h));
|
|
800
|
+
return Number.isFinite(h$1) ? Math.max(0, h$1) : 0;
|
|
801
|
+
}
|
|
802
|
+
return reservesBoxRows.value ? contentHeight.value + 2 : contentHeight.value;
|
|
803
|
+
});
|
|
804
|
+
const fullRect = computed(() => {
|
|
805
|
+
return translateRect({
|
|
806
|
+
x: props.x,
|
|
807
|
+
y: props.y,
|
|
808
|
+
w: props.w,
|
|
809
|
+
h: fullHeight.value
|
|
810
|
+
}, layout.originX, layout.originY);
|
|
811
|
+
});
|
|
812
|
+
const absRect = computed(() => {
|
|
813
|
+
const translated = fullRect.value;
|
|
814
|
+
if (!layout.clipRect) return translated;
|
|
815
|
+
return intersectRect(translated, layout.clipRect) ?? {
|
|
816
|
+
x: 0,
|
|
817
|
+
y: 0,
|
|
818
|
+
w: 0,
|
|
819
|
+
h: 0
|
|
820
|
+
};
|
|
821
|
+
});
|
|
822
|
+
const copyLabel = computed(() => copied.value ? props.copiedText : props.copyText);
|
|
823
|
+
function cellWidth(value) {
|
|
824
|
+
return withTextWidthProvider(widthProvider, () => textCellWidth(value));
|
|
825
|
+
}
|
|
826
|
+
function sliceCells(text, maxCells) {
|
|
827
|
+
return withTextWidthProvider(widthProvider, () => sliceByCells(text, maxCells));
|
|
828
|
+
}
|
|
829
|
+
function sliceCellsRange(text, startCells, endCells) {
|
|
830
|
+
return withTextWidthProvider(widthProvider, () => sliceByCellsRange(text, startCells, endCells));
|
|
831
|
+
}
|
|
832
|
+
function padCells(text, width) {
|
|
833
|
+
return withTextWidthProvider(widthProvider, () => padEndByCells(text, width));
|
|
834
|
+
}
|
|
835
|
+
function resolveImageOptions() {
|
|
836
|
+
const resolvedFg = props.fg ?? resolveMermaidImageColor(currentStyle.value.fg) ?? void 0;
|
|
837
|
+
return normalizeMermaidImageOptions({
|
|
838
|
+
cellWidthPx: props.cellWidthPx,
|
|
839
|
+
cellHeightPx: props.cellHeightPx,
|
|
840
|
+
scale: props.scale,
|
|
841
|
+
bg: props.bg,
|
|
842
|
+
fg: resolvedFg,
|
|
843
|
+
maxWidthCells: props.maxWidthCells,
|
|
844
|
+
maxHeightCells: props.maxHeightCells,
|
|
845
|
+
padding: props.padding
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
function hasVisibleTextOutput(lines) {
|
|
849
|
+
return lines.some((line) => line.trim().length > 0);
|
|
850
|
+
}
|
|
851
|
+
function resolveTextOptions() {
|
|
852
|
+
const base = props.textOptions ?? {};
|
|
853
|
+
return {
|
|
854
|
+
...base,
|
|
855
|
+
useAscii: props.ascii,
|
|
856
|
+
colorMode: "none"
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
function textRenderSignature(code) {
|
|
860
|
+
return [
|
|
861
|
+
code,
|
|
862
|
+
props.ascii ? "a" : "",
|
|
863
|
+
JSON.stringify(props.textOptions ?? null),
|
|
864
|
+
props.textRenderer != null ? "r" : "",
|
|
865
|
+
props.shouldRenderSource != null ? "g" : ""
|
|
866
|
+
].join("");
|
|
867
|
+
}
|
|
868
|
+
async function renderTextWithTimeout(renderer, code, options) {
|
|
869
|
+
const timeoutMs = normalizeNonNegativeInt(props.renderTimeoutMs, 0);
|
|
870
|
+
if (timeoutMs <= 0) return await renderer(code, options);
|
|
871
|
+
let timer = null;
|
|
872
|
+
try {
|
|
873
|
+
return await Promise.race([Promise.resolve().then(() => renderer(code, options)), new Promise((_resolve, reject) => {
|
|
874
|
+
timer = setTimeout(() => reject(new Error("Mermaid text render timeout")), timeoutMs);
|
|
875
|
+
})]);
|
|
876
|
+
} finally {
|
|
877
|
+
if (timer != null) clearTimeout(timer);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
async function runCustomRasterizer(code, options) {
|
|
881
|
+
const renderer = props.renderer;
|
|
882
|
+
if (!renderer) return null;
|
|
883
|
+
const timeoutMs = normalizeNonNegativeInt(props.renderTimeoutMs, 0);
|
|
884
|
+
const run = () => renderer(code, options);
|
|
885
|
+
if (timeoutMs <= 0) return await run() ?? null;
|
|
886
|
+
let timer = null;
|
|
887
|
+
try {
|
|
888
|
+
return await Promise.race([run(), new Promise((resolve) => {
|
|
889
|
+
timer = setTimeout(() => resolve(null), timeoutMs);
|
|
890
|
+
})]) ?? null;
|
|
891
|
+
} finally {
|
|
892
|
+
if (timer != null) clearTimeout(timer);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function shouldSkipRenderForSize(code) {
|
|
896
|
+
const maxChars = normalizeNonNegativeInt(props.maxRenderSourceChars, DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_CHARS);
|
|
897
|
+
if (maxChars > 0 && code.length > maxChars) return true;
|
|
898
|
+
const maxLines = normalizeNonNegativeInt(props.maxRenderSourceLines, DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_LINES);
|
|
899
|
+
if (maxLines > 0 && countLinesUpTo(code, maxLines) > maxLines) return true;
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
902
|
+
async function rasterizeWithTimeout(code, options) {
|
|
903
|
+
const timeoutMs = normalizeNonNegativeInt(props.renderTimeoutMs, 0);
|
|
904
|
+
if (timeoutMs <= 0) return await getMermaidImage(code, options);
|
|
905
|
+
let timer = null;
|
|
906
|
+
try {
|
|
907
|
+
return await Promise.race([getMermaidImage(code, options), new Promise((resolve) => {
|
|
908
|
+
timer = setTimeout(() => resolve(null), timeoutMs);
|
|
909
|
+
})]);
|
|
910
|
+
} finally {
|
|
911
|
+
if (timer != null) clearTimeout(timer);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async function renderTextFallback(version) {
|
|
915
|
+
const code = source.value;
|
|
916
|
+
const renderer = props.textRenderer;
|
|
917
|
+
if (!renderer) return;
|
|
918
|
+
const guard = props.shouldRenderSource;
|
|
919
|
+
if (guard) {
|
|
920
|
+
let eligible = false;
|
|
921
|
+
try {
|
|
922
|
+
eligible = guard(code, {
|
|
923
|
+
final: props.final,
|
|
924
|
+
streaming: props.streaming
|
|
925
|
+
});
|
|
926
|
+
} catch {
|
|
927
|
+
eligible = false;
|
|
928
|
+
}
|
|
929
|
+
if (!eligible) return;
|
|
930
|
+
}
|
|
931
|
+
try {
|
|
932
|
+
const rendered = await renderTextWithTimeout(renderer, code, resolveTextOptions());
|
|
933
|
+
if (!alive || version !== renderVersion) return;
|
|
934
|
+
const lines = splitRenderedOutput(rendered);
|
|
935
|
+
if (!hasVisibleTextOutput(lines)) return;
|
|
936
|
+
textSnapshot.value = markRaw({
|
|
937
|
+
source: code,
|
|
938
|
+
signature: textRenderSignature(code),
|
|
939
|
+
lines: markRaw(lines)
|
|
940
|
+
});
|
|
941
|
+
bump();
|
|
942
|
+
} catch {}
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Adaptive presentation state machine:
|
|
946
|
+
*
|
|
947
|
+
* raw source
|
|
948
|
+
* -> image (terminal supports Kitty/iTerm2 graphics and the PNG
|
|
949
|
+
* rasterizer succeeds), else
|
|
950
|
+
* -> ANSI text (a textRenderer is available and the source passes the
|
|
951
|
+
* eligibility guard), else
|
|
952
|
+
* -> raw source
|
|
953
|
+
*
|
|
954
|
+
* While any pipeline is pending the raw source stays visible (source-first).
|
|
955
|
+
*/
|
|
956
|
+
async function renderPresentation(version) {
|
|
957
|
+
const code = source.value;
|
|
958
|
+
if (!code.trim()) {
|
|
959
|
+
if (!alive || version !== renderVersion) return;
|
|
960
|
+
setImageCells(null);
|
|
961
|
+
textSnapshot.value = null;
|
|
962
|
+
bump();
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (waitingForStreamingSourceToFinish.value || shouldSkipRenderForSize(code)) {
|
|
966
|
+
if (!alive || version !== renderVersion) return;
|
|
967
|
+
setImageCells(null);
|
|
968
|
+
textSnapshot.value = null;
|
|
969
|
+
bump();
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const protocol = graphicsProtocol.value;
|
|
973
|
+
const options = resolveImageOptions();
|
|
974
|
+
const cached = protocol && !props.renderer ? getCachedMermaidImage(code, options) : null;
|
|
975
|
+
if (cached) {
|
|
976
|
+
if (!alive || version !== renderVersion) return;
|
|
977
|
+
setImageCells(cached);
|
|
978
|
+
textSnapshot.value = null;
|
|
979
|
+
bump();
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const currentSignature = textRenderSignature(code);
|
|
983
|
+
const keepText = textSnapshot.value != null && textSnapshot.value.source === code && textSnapshot.value.signature === currentSignature;
|
|
984
|
+
if (!keepText) {
|
|
985
|
+
setImageCells(null);
|
|
986
|
+
textSnapshot.value = null;
|
|
987
|
+
bump();
|
|
988
|
+
}
|
|
989
|
+
if (protocol) {
|
|
990
|
+
let result = null;
|
|
991
|
+
if (props.renderer) result = await runCustomRasterizer(code, options);
|
|
992
|
+
else if (isMermaidImageRendererReady()) result = await rasterizeWithTimeout(code, options);
|
|
993
|
+
else {
|
|
994
|
+
loadMermaidImageRenderer().then((ready) => {
|
|
995
|
+
if (!alive || version !== renderVersion || !ready) return;
|
|
996
|
+
renderPresentation(version);
|
|
997
|
+
});
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (!alive || version !== renderVersion) return;
|
|
1001
|
+
if (result) {
|
|
1002
|
+
setImageCells(result);
|
|
1003
|
+
textSnapshot.value = null;
|
|
1004
|
+
bump();
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (keepText) return;
|
|
1009
|
+
await renderTextFallback(version);
|
|
1010
|
+
}
|
|
1011
|
+
function scheduleRender() {
|
|
1012
|
+
const version = ++renderVersion;
|
|
1013
|
+
resetCopyFeedback(false);
|
|
1014
|
+
setImageCells(null);
|
|
1015
|
+
if (waitingForStreamingSourceToFinish.value) {
|
|
1016
|
+
builtOnce = true;
|
|
1017
|
+
scheduler.cancelFrameTask?.(frameTaskId);
|
|
1018
|
+
bump();
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (!builtOnce || !props.streaming) {
|
|
1022
|
+
builtOnce = true;
|
|
1023
|
+
renderPresentation(version);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const accepted = scheduler.queueFrameTask({
|
|
1027
|
+
id: frameTaskId,
|
|
1028
|
+
reason: "stream",
|
|
1029
|
+
priority: "low",
|
|
1030
|
+
sync: false,
|
|
1031
|
+
run: () => {
|
|
1032
|
+
if (!alive) return;
|
|
1033
|
+
if (version !== renderVersion) return;
|
|
1034
|
+
renderPresentation(version);
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
if (accepted === false) renderPresentation(version);
|
|
1038
|
+
}
|
|
1039
|
+
watch([
|
|
1040
|
+
source,
|
|
1041
|
+
() => props.renderer,
|
|
1042
|
+
() => props.cellWidthPx,
|
|
1043
|
+
() => props.cellHeightPx,
|
|
1044
|
+
() => props.scale,
|
|
1045
|
+
() => props.bg,
|
|
1046
|
+
() => props.fg,
|
|
1047
|
+
() => props.maxWidthCells,
|
|
1048
|
+
() => props.maxHeightCells,
|
|
1049
|
+
() => props.padding,
|
|
1050
|
+
() => props.streaming,
|
|
1051
|
+
() => props.final,
|
|
1052
|
+
() => props.maxRenderSourceChars,
|
|
1053
|
+
() => props.maxRenderSourceLines,
|
|
1054
|
+
() => props.textRenderer,
|
|
1055
|
+
() => props.shouldRenderSource,
|
|
1056
|
+
() => props.textOptions,
|
|
1057
|
+
() => props.ascii,
|
|
1058
|
+
() => props.zoomModifier
|
|
1059
|
+
], () => {
|
|
1060
|
+
scheduleRender();
|
|
1061
|
+
}, {
|
|
1062
|
+
immediate: true,
|
|
1063
|
+
deep: true
|
|
1064
|
+
});
|
|
1065
|
+
const unsubscribeMermaidImage = subscribeMermaidImage(() => {
|
|
1066
|
+
if (!alive) return;
|
|
1067
|
+
if (!graphicsProtocol.value || props.renderer) return;
|
|
1068
|
+
const cached = getCachedMermaidImage(source.value, resolveImageOptions());
|
|
1069
|
+
if (!cached) return;
|
|
1070
|
+
if (!alive) return;
|
|
1071
|
+
setImageCells(cached);
|
|
1072
|
+
textSnapshot.value = null;
|
|
1073
|
+
bump();
|
|
1074
|
+
});
|
|
1075
|
+
const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
|
|
1076
|
+
if (!alive) return;
|
|
1077
|
+
graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
|
|
1078
|
+
scheduleRender();
|
|
1079
|
+
});
|
|
1080
|
+
onBeforeUnmount(() => {
|
|
1081
|
+
alive = false;
|
|
1082
|
+
renderVersion++;
|
|
1083
|
+
clearCopiedTimer();
|
|
1084
|
+
queueClearGraphic();
|
|
1085
|
+
unsubscribeMermaidImage();
|
|
1086
|
+
unsubscribeGraphicsOutput();
|
|
1087
|
+
scheduler.cancelFrameTask?.(frameTaskId);
|
|
1088
|
+
});
|
|
1089
|
+
function canDrawBox(width, height) {
|
|
1090
|
+
return hasBox.value && Math.floor(width) >= 2 && Math.floor(height) >= 2;
|
|
1091
|
+
}
|
|
1092
|
+
function headerCopySegment(width) {
|
|
1093
|
+
if (!hasBox.value || !props.copyButton) return null;
|
|
1094
|
+
const rowWidth = Math.max(0, Math.floor(width));
|
|
1095
|
+
if (rowWidth < 4) return null;
|
|
1096
|
+
const label = sanitizeInlineText(copyLabel.value);
|
|
1097
|
+
if (!label) return null;
|
|
1098
|
+
const maxCells = Math.max(0, rowWidth - 2);
|
|
1099
|
+
const text = sliceCells(` ${label} `, maxCells);
|
|
1100
|
+
const cells = cellWidth(text);
|
|
1101
|
+
if (!text || cells <= 0) return null;
|
|
1102
|
+
return {
|
|
1103
|
+
text,
|
|
1104
|
+
start: Math.max(1, rowWidth - cells - 1),
|
|
1105
|
+
cells
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
function headerTitleSegment(width, copy) {
|
|
1109
|
+
if (!hasBox.value) return null;
|
|
1110
|
+
const rowWidth = Math.max(0, Math.floor(width));
|
|
1111
|
+
if (rowWidth < 4) return null;
|
|
1112
|
+
const title = sanitizeInlineText(props.title);
|
|
1113
|
+
if (!title) return null;
|
|
1114
|
+
const titleStart = 1;
|
|
1115
|
+
const titleEnd = copy ? Math.max(titleStart, copy.start - 1) : rowWidth - 1;
|
|
1116
|
+
const maxCells = Math.max(0, titleEnd - titleStart);
|
|
1117
|
+
if (maxCells <= 0) return null;
|
|
1118
|
+
const text = sliceCells(` ${title} `, maxCells);
|
|
1119
|
+
const cells = cellWidth(text);
|
|
1120
|
+
if (!text || cells <= 0) return null;
|
|
1121
|
+
return {
|
|
1122
|
+
text,
|
|
1123
|
+
start: titleStart,
|
|
1124
|
+
cells
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function overlayCells(row, segment, width) {
|
|
1128
|
+
const rowWidth = Math.max(0, Math.floor(width));
|
|
1129
|
+
const start = Math.max(0, Math.floor(segment.start));
|
|
1130
|
+
if (rowWidth <= 1 || start >= rowWidth - 1 || segment.cells <= 0) return row;
|
|
1131
|
+
const maxCells = Math.max(0, rowWidth - 1 - start);
|
|
1132
|
+
const text = sliceCells(segment.text, maxCells);
|
|
1133
|
+
const cells = cellWidth(text);
|
|
1134
|
+
if (!text || cells <= 0) return row;
|
|
1135
|
+
return `${sliceCells(row, start)}${text}${sliceCellsRange(row, start + cells, rowWidth)}`;
|
|
1136
|
+
}
|
|
1137
|
+
function boxRow(rowIndex, width, height) {
|
|
1138
|
+
const rowWidth = Math.max(0, Math.floor(width));
|
|
1139
|
+
const rowHeight = Math.max(0, Math.floor(height));
|
|
1140
|
+
if (!canDrawBox(rowWidth, rowHeight)) return contentLine(rowIndex, rowWidth, props.clear);
|
|
1141
|
+
const chars = boxChars.value;
|
|
1142
|
+
const innerW = Math.max(0, rowWidth - 2);
|
|
1143
|
+
if (rowIndex === 0) {
|
|
1144
|
+
let row = `${chars.tl}${repeatChar(chars.h, innerW)}${chars.tr}`;
|
|
1145
|
+
const copy = headerCopySegment(rowWidth);
|
|
1146
|
+
const title = headerTitleSegment(rowWidth, copy);
|
|
1147
|
+
if (title) row = overlayCells(row, title, rowWidth);
|
|
1148
|
+
if (copy) row = overlayCells(row, copy, rowWidth);
|
|
1149
|
+
return row;
|
|
1150
|
+
}
|
|
1151
|
+
if (rowIndex === rowHeight - 1) return `${chars.bl}${repeatChar(chars.h, innerW)}${chars.br}`;
|
|
1152
|
+
return `${chars.v}${contentLine(rowIndex - 1, innerW, true)}${chars.v}`;
|
|
1153
|
+
}
|
|
1154
|
+
function contentLine(rowIndex, width, pad) {
|
|
1155
|
+
const showImage = imageCells.value != null && displayFit.value != null && graphicsProtocol.value != null;
|
|
1156
|
+
const src = showImage ? spaces(width) : displayLines.value[rowIndex] ?? "";
|
|
1157
|
+
const clipped = sliceCells(src, width);
|
|
1158
|
+
return pad ? padCells(clipped, width) : clipped;
|
|
1159
|
+
}
|
|
1160
|
+
function innerRectFrom(full) {
|
|
1161
|
+
const x = Math.floor(full.x) + (reservesBoxRows.value ? 1 : 0);
|
|
1162
|
+
const y = Math.floor(full.y) + (reservesBoxRows.value ? 1 : 0);
|
|
1163
|
+
const w = Math.max(0, Math.floor(full.w) - (reservesBoxRows.value ? 2 : 0));
|
|
1164
|
+
const h$1 = Math.max(0, Math.floor(full.h) - (reservesBoxRows.value ? 2 : 0));
|
|
1165
|
+
return {
|
|
1166
|
+
x,
|
|
1167
|
+
y,
|
|
1168
|
+
w,
|
|
1169
|
+
h: h$1
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
function queueImageGraphic(full, fit) {
|
|
1173
|
+
const protocol = graphicsProtocol.value;
|
|
1174
|
+
const cells = imageCells.value;
|
|
1175
|
+
if (!protocol || !cells) {
|
|
1176
|
+
queueClearGraphic();
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const inner = innerRectFrom(full);
|
|
1180
|
+
if (inner.w <= 0 || inner.h <= 0) {
|
|
1181
|
+
queueClearGraphic();
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const zoom = clampZoom(zoomScale.value);
|
|
1185
|
+
const fitW = fit.displayW;
|
|
1186
|
+
const fitH = fit.displayH;
|
|
1187
|
+
const fitOriginX = inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0);
|
|
1188
|
+
const fullRect$1 = {
|
|
1189
|
+
x: fitOriginX + panX.value,
|
|
1190
|
+
y: inner.y + panY.value,
|
|
1191
|
+
w: fitW * zoom,
|
|
1192
|
+
h: fitH * zoom
|
|
1193
|
+
};
|
|
1194
|
+
const visible$1 = intersectRect(fullRect$1, inner);
|
|
1195
|
+
if (!visible$1 || visible$1.w < 1 || visible$1.h < 1) {
|
|
1196
|
+
queueClearGraphic();
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
const rectX = Math.max(inner.x, Math.round(visible$1.x));
|
|
1200
|
+
const rectY = Math.max(inner.y, Math.round(visible$1.y));
|
|
1201
|
+
const rect = {
|
|
1202
|
+
x: rectX,
|
|
1203
|
+
y: rectY,
|
|
1204
|
+
w: Math.max(1, Math.min(inner.x + inner.w, rectX + Math.round(visible$1.w)) - rectX),
|
|
1205
|
+
h: Math.max(1, Math.min(inner.y + inner.h, rectY + Math.round(visible$1.h)) - rectY)
|
|
1206
|
+
};
|
|
1207
|
+
const imageId = stableTerminalGraphicNumericId(`mermaid-image:${uid}:${renderVersion}:img`);
|
|
1208
|
+
const placementId = stableTerminalGraphicNumericId(`mermaid-image:${uid}:${renderVersion}:plc`);
|
|
1209
|
+
const built = createTerminalGraphicPngSequence({
|
|
1210
|
+
protocol,
|
|
1211
|
+
base64: cells.base64,
|
|
1212
|
+
imageId,
|
|
1213
|
+
placementId,
|
|
1214
|
+
cols: cells.widthCells,
|
|
1215
|
+
rows: cells.heightCells,
|
|
1216
|
+
sourceWidth: cells.naturalWidth,
|
|
1217
|
+
sourceHeight: cells.naturalHeight,
|
|
1218
|
+
placementColumns: rect.w,
|
|
1219
|
+
placementRows: rect.h,
|
|
1220
|
+
transmissionColumns: fitW,
|
|
1221
|
+
transmissionRows: fitH,
|
|
1222
|
+
cropTransmission: false,
|
|
1223
|
+
rect,
|
|
1224
|
+
fullRect: fullRect$1,
|
|
1225
|
+
zIndex: -1,
|
|
1226
|
+
fallback: source.value
|
|
1227
|
+
});
|
|
1228
|
+
if (!built) {
|
|
1229
|
+
queueClearGraphic();
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const id = `mermaid-image:${uid}:${renderVersion}`;
|
|
1233
|
+
const output = graphicsOutput.value;
|
|
1234
|
+
if (!output) {
|
|
1235
|
+
queueClearGraphic();
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (activeGraphicId != null && activeGraphicId !== id) {
|
|
1239
|
+
try {
|
|
1240
|
+
output.clear?.(activeGraphicId);
|
|
1241
|
+
} catch {}
|
|
1242
|
+
activeGraphicId = null;
|
|
1243
|
+
}
|
|
1244
|
+
const accepted = output.queue({
|
|
1245
|
+
id,
|
|
1246
|
+
x: rect.x,
|
|
1247
|
+
y: rect.y,
|
|
1248
|
+
w: rect.w,
|
|
1249
|
+
h: rect.h,
|
|
1250
|
+
protocol,
|
|
1251
|
+
sequence: built.sequence,
|
|
1252
|
+
resizeSequence: built.resizeSequence,
|
|
1253
|
+
clearSequence: built.clearSequence,
|
|
1254
|
+
fallbackText: source.value,
|
|
1255
|
+
placementMoveWithoutClear: protocol === "kitty"
|
|
1256
|
+
});
|
|
1257
|
+
if (accepted) activeGraphicId = id;
|
|
1258
|
+
else queueClearGraphic();
|
|
1259
|
+
}
|
|
1260
|
+
const copyHitRect = computed(() => {
|
|
1261
|
+
const full = fullRect.value;
|
|
1262
|
+
if (!visible.value || !props.copyButton || !canDrawBox(full.w, full.h)) return {
|
|
1263
|
+
x: 0,
|
|
1264
|
+
y: 0,
|
|
1265
|
+
w: 0,
|
|
1266
|
+
h: 0
|
|
1267
|
+
};
|
|
1268
|
+
const copy = headerCopySegment(Math.max(0, Math.floor(full.w)));
|
|
1269
|
+
if (!copy) return {
|
|
1270
|
+
x: 0,
|
|
1271
|
+
y: 0,
|
|
1272
|
+
w: 0,
|
|
1273
|
+
h: 0
|
|
1274
|
+
};
|
|
1275
|
+
const raw = {
|
|
1276
|
+
x: Math.floor(full.x) + copy.start,
|
|
1277
|
+
y: Math.floor(full.y),
|
|
1278
|
+
w: copy.cells,
|
|
1279
|
+
h: 1
|
|
1280
|
+
};
|
|
1281
|
+
if (!layout.clipRect) return raw;
|
|
1282
|
+
return intersectRect(raw, layout.clipRect) ?? {
|
|
1283
|
+
x: 0,
|
|
1284
|
+
y: 0,
|
|
1285
|
+
w: 0,
|
|
1286
|
+
h: 0
|
|
1287
|
+
};
|
|
1288
|
+
});
|
|
1289
|
+
const contentHitRect = computed(() => {
|
|
1290
|
+
if (!visible.value) return {
|
|
1291
|
+
x: 0,
|
|
1292
|
+
y: 0,
|
|
1293
|
+
w: 0,
|
|
1294
|
+
h: 0
|
|
1295
|
+
};
|
|
1296
|
+
const full = fullRect.value;
|
|
1297
|
+
if (full.w <= 0 || full.h <= 0) return {
|
|
1298
|
+
x: 0,
|
|
1299
|
+
y: 0,
|
|
1300
|
+
w: 0,
|
|
1301
|
+
h: 0
|
|
1302
|
+
};
|
|
1303
|
+
const inner = innerRectFrom(full);
|
|
1304
|
+
if (inner.w <= 0 || inner.h <= 0) return {
|
|
1305
|
+
x: 0,
|
|
1306
|
+
y: 0,
|
|
1307
|
+
w: 0,
|
|
1308
|
+
h: 0
|
|
1309
|
+
};
|
|
1310
|
+
if (!layout.clipRect) return inner;
|
|
1311
|
+
return intersectRect(inner, layout.clipRect) ?? {
|
|
1312
|
+
x: 0,
|
|
1313
|
+
y: 0,
|
|
1314
|
+
w: 0,
|
|
1315
|
+
h: 0
|
|
1316
|
+
};
|
|
1317
|
+
});
|
|
1318
|
+
function clipboardCanWrite(api) {
|
|
1319
|
+
return api.canWrite ?? api.supported;
|
|
1320
|
+
}
|
|
1321
|
+
async function writeClipboardText(text) {
|
|
1322
|
+
const contextClipboard = terminalContext.clipboard;
|
|
1323
|
+
if (contextClipboard) {
|
|
1324
|
+
if (!clipboardCanWrite(contextClipboard)) throw new Error("Clipboard write not available in this runtime");
|
|
1325
|
+
await contextClipboard.writeText(text);
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
const navClipboard = globalThis.navigator?.clipboard;
|
|
1329
|
+
if (navClipboard && typeof navClipboard.writeText === "function") {
|
|
1330
|
+
await navClipboard.writeText(text);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
throw new Error("Clipboard write not available in this runtime");
|
|
1334
|
+
}
|
|
1335
|
+
async function copySource() {
|
|
1336
|
+
const text = source.value;
|
|
1337
|
+
const requestVersion = ++copyRequestVersion;
|
|
1338
|
+
let ok = false;
|
|
1339
|
+
let copyError;
|
|
1340
|
+
try {
|
|
1341
|
+
await writeClipboardText(text);
|
|
1342
|
+
ok = true;
|
|
1343
|
+
} catch (err) {
|
|
1344
|
+
copyError = err;
|
|
1345
|
+
}
|
|
1346
|
+
if (!alive) return;
|
|
1347
|
+
const isLatestForCurrentSource = requestVersion === copyRequestVersion && source.value === text;
|
|
1348
|
+
if (isLatestForCurrentSource) if (ok) showCopiedFeedback();
|
|
1349
|
+
else setCopied(false);
|
|
1350
|
+
emit("copy", ok ? {
|
|
1351
|
+
text,
|
|
1352
|
+
ok
|
|
1353
|
+
} : {
|
|
1354
|
+
text,
|
|
1355
|
+
ok,
|
|
1356
|
+
error: copyError
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
function onCopyClick(event) {
|
|
1360
|
+
event.preventDefault();
|
|
1361
|
+
event.stopPropagation();
|
|
1362
|
+
if (Date.now() - lastDragAt < DRAG_CLICK_SUPPRESS_MS) return;
|
|
1363
|
+
copySource();
|
|
1364
|
+
}
|
|
1365
|
+
function onCopyKeydown(event) {
|
|
1366
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1367
|
+
event.preventDefault();
|
|
1368
|
+
event.stopPropagation();
|
|
1369
|
+
copySource();
|
|
1370
|
+
}
|
|
1371
|
+
const zoomEnabled = computed(() => props.zoomOnWheel !== false && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
|
|
1372
|
+
const dragEnabled = computed(() => props.zoomOnWheel !== false && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && zoomScale.value > 1 && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
|
|
1373
|
+
function onWheel(event) {
|
|
1374
|
+
if (!zoomEnabled.value) return;
|
|
1375
|
+
if (!hasZoomModifier(event)) return;
|
|
1376
|
+
event.preventDefault();
|
|
1377
|
+
event.stopPropagation();
|
|
1378
|
+
const deltaY = event.deltaY ?? 0;
|
|
1379
|
+
if (!Number.isFinite(deltaY) || deltaY === 0) return;
|
|
1380
|
+
const inner = innerRectFrom(fullRect.value);
|
|
1381
|
+
const fit = displayFit.value;
|
|
1382
|
+
if (!fit || inner.w <= 0 || inner.h <= 0) return;
|
|
1383
|
+
const currentZoom = clampZoom(zoomScale.value);
|
|
1384
|
+
const fitW = fit.displayW;
|
|
1385
|
+
const fitH = fit.displayH;
|
|
1386
|
+
const fitOriginX = inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0);
|
|
1387
|
+
const fullX = fitOriginX + panX.value;
|
|
1388
|
+
const fullY = inner.y + panY.value;
|
|
1389
|
+
const mouseX = event.cellX;
|
|
1390
|
+
const mouseY = event.cellY;
|
|
1391
|
+
const fx = currentZoom > 0 ? (mouseX - fullX) / (fitW * currentZoom) : .5;
|
|
1392
|
+
const fy = currentZoom > 0 ? (mouseY - fullY) / (fitH * currentZoom) : .5;
|
|
1393
|
+
const nextZoom = clampZoom(currentZoom * wheelZoomFactor(deltaY, event.deltaMode));
|
|
1394
|
+
if (nextZoom === currentZoom) return;
|
|
1395
|
+
const nextW = fitW * nextZoom;
|
|
1396
|
+
const nextH = fitH * nextZoom;
|
|
1397
|
+
let nextX = mouseX - fx * nextW;
|
|
1398
|
+
let nextY = mouseY - fy * nextH;
|
|
1399
|
+
if (nextW <= inner.w) nextX = inner.x + (inner.w - nextW) / 2;
|
|
1400
|
+
else nextX = Math.min(inner.x, Math.max(inner.x + inner.w - nextW, nextX));
|
|
1401
|
+
if (nextH <= inner.h) nextY = inner.y + (inner.h - nextH) / 2;
|
|
1402
|
+
else nextY = Math.min(inner.y, Math.max(inner.y + inner.h - nextH, nextY));
|
|
1403
|
+
zoomScale.value = nextZoom;
|
|
1404
|
+
panX.value = nextX - fitOriginX;
|
|
1405
|
+
panY.value = nextY - inner.y;
|
|
1406
|
+
clampPan();
|
|
1407
|
+
bump();
|
|
1408
|
+
}
|
|
1409
|
+
function onPointerDown(event) {
|
|
1410
|
+
if (!dragEnabled.value) return;
|
|
1411
|
+
event.preventDefault();
|
|
1412
|
+
event.stopPropagation();
|
|
1413
|
+
dragState = {
|
|
1414
|
+
startX: event.cellX,
|
|
1415
|
+
startY: event.cellY,
|
|
1416
|
+
panStartX: panX.value,
|
|
1417
|
+
panStartY: panY.value
|
|
1418
|
+
};
|
|
1419
|
+
dragMoved = false;
|
|
1420
|
+
}
|
|
1421
|
+
function onPointerMove(event) {
|
|
1422
|
+
const drag = dragState;
|
|
1423
|
+
if (!drag) return;
|
|
1424
|
+
event.preventDefault();
|
|
1425
|
+
event.stopPropagation();
|
|
1426
|
+
const dx = event.cellX - drag.startX;
|
|
1427
|
+
const dy = event.cellY - drag.startY;
|
|
1428
|
+
if (!dragMoved && Math.abs(dx) + Math.abs(dy) >= 1) dragMoved = true;
|
|
1429
|
+
if (!dragMoved) return;
|
|
1430
|
+
panX.value = drag.panStartX + dx;
|
|
1431
|
+
panY.value = drag.panStartY + dy;
|
|
1432
|
+
clampPan();
|
|
1433
|
+
bump();
|
|
1434
|
+
}
|
|
1435
|
+
function onPointerUp(event) {
|
|
1436
|
+
const drag = dragState;
|
|
1437
|
+
if (!drag) return;
|
|
1438
|
+
event.preventDefault();
|
|
1439
|
+
event.stopPropagation();
|
|
1440
|
+
dragState = null;
|
|
1441
|
+
if (dragMoved) lastDragAt = Date.now();
|
|
1442
|
+
}
|
|
1443
|
+
const copyNodeActive = computed(() => visible.value && props.copyButton && copyHitRect.value.w > 0 && copyHitRect.value.h > 0);
|
|
1444
|
+
useTerminalNode(() => ({
|
|
1445
|
+
rect: copyHitRect.value,
|
|
1446
|
+
zIndex: (parentEventZ.value ?? 0) + props.zIndex + 1,
|
|
1447
|
+
visible: copyNodeActive.value,
|
|
1448
|
+
focusable: copyNodeActive.value,
|
|
1449
|
+
selectable: false,
|
|
1450
|
+
handlers: copyNodeActive.value ? {
|
|
1451
|
+
click: onCopyClick,
|
|
1452
|
+
keydown: onCopyKeydown
|
|
1453
|
+
} : {}
|
|
1454
|
+
}));
|
|
1455
|
+
const contentNodeActive = computed(() => visible.value && (props.copyOnClick || zoomEnabled.value || dragEnabled.value) && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
|
|
1456
|
+
useTerminalNode(() => ({
|
|
1457
|
+
rect: contentHitRect.value,
|
|
1458
|
+
zIndex: (parentEventZ.value ?? 0) + props.zIndex,
|
|
1459
|
+
visible: contentNodeActive.value,
|
|
1460
|
+
focusable: false,
|
|
1461
|
+
selectable: false,
|
|
1462
|
+
handlers: contentNodeActive.value ? {
|
|
1463
|
+
click: props.copyOnClick ? onCopyClick : void 0,
|
|
1464
|
+
wheel: zoomEnabled.value ? onWheel : void 0,
|
|
1465
|
+
pointerdown: dragEnabled.value ? onPointerDown : void 0,
|
|
1466
|
+
pointermove: dragEnabled.value ? onPointerMove : void 0,
|
|
1467
|
+
pointerup: dragEnabled.value ? onPointerUp : void 0
|
|
1468
|
+
} : {}
|
|
1469
|
+
}));
|
|
1470
|
+
useRenderNode(() => ({
|
|
1471
|
+
zIndex: props.zIndex,
|
|
1472
|
+
rect: visible.value ? absRect.value : {
|
|
1473
|
+
x: 0,
|
|
1474
|
+
y: 0,
|
|
1475
|
+
w: 0,
|
|
1476
|
+
h: 0
|
|
1477
|
+
},
|
|
1478
|
+
deps: [
|
|
1479
|
+
visible.value,
|
|
1480
|
+
absRect.value,
|
|
1481
|
+
fullRect.value,
|
|
1482
|
+
imageCells.value,
|
|
1483
|
+
displayFit.value,
|
|
1484
|
+
zoomScale.value,
|
|
1485
|
+
panX.value,
|
|
1486
|
+
panY.value,
|
|
1487
|
+
textSnapshot.value,
|
|
1488
|
+
displayLines.value,
|
|
1489
|
+
sourceLines.value,
|
|
1490
|
+
currentStyle.value,
|
|
1491
|
+
props.clear,
|
|
1492
|
+
hasBox.value,
|
|
1493
|
+
boxChars.value,
|
|
1494
|
+
props.title,
|
|
1495
|
+
props.copyButton,
|
|
1496
|
+
copyLabel.value,
|
|
1497
|
+
graphicsOutputVersion.value,
|
|
1498
|
+
documentVersion.value
|
|
1499
|
+
],
|
|
1500
|
+
paint: (dirtyRows) => {
|
|
1501
|
+
withTextWidthProvider(widthProvider, () => {
|
|
1502
|
+
if (!visible.value) {
|
|
1503
|
+
queueClearGraphic();
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
const r = absRect.value;
|
|
1507
|
+
const full = fullRect.value;
|
|
1508
|
+
if (r.w <= 0 || r.h <= 0) {
|
|
1509
|
+
queueClearGraphic();
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
const style = currentStyle.value;
|
|
1513
|
+
const dx = Math.max(0, Math.floor(r.x - full.x));
|
|
1514
|
+
const fullY = Math.floor(full.y);
|
|
1515
|
+
const fullW = Math.max(0, Math.floor(full.w));
|
|
1516
|
+
const fullH = Math.max(0, Math.floor(full.h));
|
|
1517
|
+
const blank = props.clear ? spaces(r.w) : "";
|
|
1518
|
+
const paintRow = (y) => {
|
|
1519
|
+
if (y < r.y || y >= r.y + r.h) return;
|
|
1520
|
+
const rowIndex = y - fullY;
|
|
1521
|
+
if (rowIndex < 0 || rowIndex >= fullH) {
|
|
1522
|
+
if (props.clear) terminal.write(blank, {
|
|
1523
|
+
x: r.x,
|
|
1524
|
+
y,
|
|
1525
|
+
style
|
|
1526
|
+
});
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
const src = boxRow(rowIndex, fullW, fullH);
|
|
1530
|
+
const clipped = dx > 0 ? sliceCellsRange(src, dx, dx + r.w) : sliceCells(src, r.w);
|
|
1531
|
+
const value = props.clear ? padCells(clipped, r.w) : clipped;
|
|
1532
|
+
if (value || props.clear) terminal.write(value, {
|
|
1533
|
+
x: r.x,
|
|
1534
|
+
y,
|
|
1535
|
+
style
|
|
1536
|
+
});
|
|
1537
|
+
};
|
|
1538
|
+
if (imageCells.value && displayFit.value) queueImageGraphic(full, displayFit.value);
|
|
1539
|
+
else queueClearGraphic();
|
|
1540
|
+
if (dirtyRows?.length) {
|
|
1541
|
+
for (const y of dirtyRows) paintRow(y);
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
for (let y = r.y; y < r.y + r.h; y++) paintRow(y);
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
}));
|
|
1548
|
+
return () => h("span", rootProps);
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
//#endregion
|
|
1553
|
+
//#region src/vue/mermaid/beautiful-mermaid.ts
|
|
1554
|
+
const BEAUTIFUL_MERMAID_INSTALL_HINT = "Install beautiful-mermaid and use TMermaidText from @simon_he/vue-tui/mermaid or @simon_he/vue-tui/agent/mermaid, or pass a custom renderer prop.";
|
|
1555
|
+
const MISSING_BEAUTIFUL_MERMAID_ERROR_CODE = "VUE_TUI_MISSING_BEAUTIFUL_MERMAID";
|
|
1556
|
+
const INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE = "VUE_TUI_INVALID_BEAUTIFUL_MERMAID_EXPORT";
|
|
1557
|
+
let cachedBeautifulMermaid = null;
|
|
1558
|
+
function functionValue(value) {
|
|
1559
|
+
return typeof value === "function" ? value : null;
|
|
1560
|
+
}
|
|
1561
|
+
function functionProp(target, key) {
|
|
1562
|
+
if (!target || typeof target !== "object") return null;
|
|
1563
|
+
if (!(key in target)) return null;
|
|
1564
|
+
const value = target[key];
|
|
1565
|
+
return typeof value === "function" ? value : null;
|
|
1566
|
+
}
|
|
1567
|
+
function errorMessage(error) {
|
|
1568
|
+
return error instanceof Error ? error.message : String(error);
|
|
1569
|
+
}
|
|
1570
|
+
function codedError(message, code) {
|
|
1571
|
+
const error = new Error(message);
|
|
1572
|
+
error.code = code;
|
|
1573
|
+
return error;
|
|
1574
|
+
}
|
|
1575
|
+
function missingBeautifulMermaidError(error) {
|
|
1576
|
+
if (error && typeof error === "object" && errorCode(error) === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE) return markMermaidRenderErrorFatal(error);
|
|
1577
|
+
const detail = errorMessage(error);
|
|
1578
|
+
return markMermaidRenderErrorFatal(codedError(`${BEAUTIFUL_MERMAID_INSTALL_HINT} (${detail})`, MISSING_BEAUTIFUL_MERMAID_ERROR_CODE));
|
|
1579
|
+
}
|
|
1580
|
+
function errorCode(error) {
|
|
1581
|
+
if (!error || typeof error !== "object") return "";
|
|
1582
|
+
const value = error.code;
|
|
1583
|
+
return typeof value === "string" ? value : "";
|
|
1584
|
+
}
|
|
1585
|
+
function errorCause(error) {
|
|
1586
|
+
if (!error || typeof error !== "object") return void 0;
|
|
1587
|
+
return error.cause;
|
|
1588
|
+
}
|
|
1589
|
+
function isMissingBeautifulMermaid(error) {
|
|
1590
|
+
return isMissingBeautifulMermaidWithSeen(error, new WeakSet());
|
|
1591
|
+
}
|
|
1592
|
+
function isMissingBeautifulMermaidWithSeen(error, seen) {
|
|
1593
|
+
if (error && typeof error === "object") {
|
|
1594
|
+
if (seen.has(error)) return false;
|
|
1595
|
+
seen.add(error);
|
|
1596
|
+
}
|
|
1597
|
+
const message = errorMessage(error);
|
|
1598
|
+
const code = errorCode(error);
|
|
1599
|
+
if (code === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE) return true;
|
|
1600
|
+
const mentionsBeautifulMermaid = /Cannot find package ['"]beautiful-mermaid['"]/.test(message) || /Cannot find module ['"]beautiful-mermaid['"]/.test(message) || /Cannot resolve module ['"]beautiful-mermaid['"]/.test(message) || /Can't resolve ['"]beautiful-mermaid['"]/.test(message) || /Failed to resolve module specifier ['"]beautiful-mermaid['"]/.test(message) || /Failed to resolve import ['"]beautiful-mermaid['"]/.test(message) || /Could not resolve ['"]beautiful-mermaid['"]/.test(message);
|
|
1601
|
+
if (mentionsBeautifulMermaid && (code === "" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "UNRESOLVED_IMPORT")) return true;
|
|
1602
|
+
const cause = errorCause(error);
|
|
1603
|
+
return cause !== void 0 && isMissingBeautifulMermaidWithSeen(cause, seen);
|
|
1604
|
+
}
|
|
1605
|
+
function resolveBeautifulMermaidRenderer(mod) {
|
|
1606
|
+
const renderer = functionProp(mod, "renderMermaidASCII") ?? functionProp(mod, "renderMermaidAscii") ?? functionValue(mod.default) ?? functionProp(mod.default, "renderMermaidASCII") ?? functionProp(mod.default, "renderMermaidAscii");
|
|
1607
|
+
if (!renderer) throw markMermaidRenderErrorFatal(codedError("beautiful-mermaid is installed but does not export renderMermaidASCII.", INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE));
|
|
1608
|
+
return renderer;
|
|
1609
|
+
}
|
|
1610
|
+
function hasErrorCode(error, code, seen = new WeakSet()) {
|
|
1611
|
+
if (!error || typeof error !== "object") return false;
|
|
1612
|
+
if (seen.has(error)) return false;
|
|
1613
|
+
seen.add(error);
|
|
1614
|
+
if (errorCode(error) === code) return true;
|
|
1615
|
+
const cause = errorCause(error);
|
|
1616
|
+
return cause !== void 0 && hasErrorCode(cause, code, seen);
|
|
1617
|
+
}
|
|
1618
|
+
function isBeautifulMermaidExportError(error) {
|
|
1619
|
+
return hasErrorCode(error, INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE);
|
|
1620
|
+
}
|
|
1621
|
+
const isTransientBeautifulMermaidRenderError = (error) => {
|
|
1622
|
+
if (isMissingBeautifulMermaid(error)) return false;
|
|
1623
|
+
if (isBeautifulMermaidExportError(error)) return false;
|
|
1624
|
+
return true;
|
|
1625
|
+
};
|
|
1626
|
+
async function loadBeautifulMermaid() {
|
|
1627
|
+
if (!cachedBeautifulMermaid) cachedBeautifulMermaid = import("beautiful-mermaid").then((mod) => mod).catch((error) => {
|
|
1628
|
+
cachedBeautifulMermaid = null;
|
|
1629
|
+
if (isMissingBeautifulMermaid(error)) throw missingBeautifulMermaidError(error);
|
|
1630
|
+
throw markMermaidRenderErrorFatal(error);
|
|
1631
|
+
});
|
|
1632
|
+
return cachedBeautifulMermaid;
|
|
1633
|
+
}
|
|
1634
|
+
const beautifulMermaidRenderer = async (code, options) => {
|
|
1635
|
+
try {
|
|
1636
|
+
const mod = await loadBeautifulMermaid();
|
|
1637
|
+
const renderer = resolveBeautifulMermaidRenderer(mod);
|
|
1638
|
+
return await renderer(code, options);
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
if (isMissingBeautifulMermaid(error)) throw missingBeautifulMermaidError(error);
|
|
1641
|
+
throw error;
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
function createBeautifulMermaidRenderer() {
|
|
1645
|
+
return beautifulMermaidRenderer;
|
|
1646
|
+
}
|
|
1647
|
+
const TMermaidText$1 = defineComponent({
|
|
1648
|
+
name: "TMermaidText",
|
|
1649
|
+
inheritAttrs: false,
|
|
1650
|
+
props: tMermaidTextProps,
|
|
1651
|
+
emits: { copy: (_payload) => true },
|
|
1652
|
+
setup(props, { attrs, emit, slots }) {
|
|
1653
|
+
return () => {
|
|
1654
|
+
const renderer = props.renderer ?? beautifulMermaidRenderer;
|
|
1655
|
+
const shouldRenderSource = props.shouldRenderSource ?? isSimpleMermaidFlowchartSource;
|
|
1656
|
+
return h(TMermaidText, {
|
|
1657
|
+
...attrs,
|
|
1658
|
+
...props,
|
|
1659
|
+
renderer,
|
|
1660
|
+
shouldRenderSource,
|
|
1661
|
+
isTransientError: props.isTransientError ?? isTransientBeautifulMermaidRenderError,
|
|
1662
|
+
onCopy: (payload) => emit("copy", payload)
|
|
1663
|
+
}, slots);
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
/**
|
|
1668
|
+
* Adaptive default mermaid component: renders the diagram as a terminal image
|
|
1669
|
+
* (Kitty/iTerm2 graphics) when the terminal supports it, otherwise falls back
|
|
1670
|
+
* to the beautiful-mermaid ASCII renderer, otherwise shows the raw source.
|
|
1671
|
+
* While any pipeline is pending the raw source stays visible (source-first);
|
|
1672
|
+
* streaming sources are only rendered once `final` is true.
|
|
1673
|
+
*/
|
|
1674
|
+
const TMermaid = defineComponent({
|
|
1675
|
+
name: "TMermaid",
|
|
1676
|
+
inheritAttrs: false,
|
|
1677
|
+
props: tMermaidImageProps,
|
|
1678
|
+
emits: { copy: (_payload) => true },
|
|
1679
|
+
setup(props, { attrs, emit, slots }) {
|
|
1680
|
+
return () => {
|
|
1681
|
+
const { renderer: _legacyTextRenderer,...rest } = props;
|
|
1682
|
+
const textRenderer = props.textRenderer ?? props.renderer ?? beautifulMermaidRenderer;
|
|
1683
|
+
return h(TMermaidImage, {
|
|
1684
|
+
...attrs,
|
|
1685
|
+
...rest,
|
|
1686
|
+
textRenderer,
|
|
1687
|
+
shouldRenderSource: props.shouldRenderSource ?? isSimpleMermaidFlowchartSource,
|
|
1688
|
+
onCopy: (payload) => emit("copy", payload)
|
|
1689
|
+
}, slots);
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
const TBeautifulMermaidText = TMermaidText$1;
|
|
1694
|
+
const TBeautifulMermaid = TMermaid;
|
|
1695
|
+
|
|
1696
|
+
//#endregion
|
|
1697
|
+
export { TBeautifulMermaid, TBeautifulMermaidText, TMermaid, TMermaidImage, TMermaidText$1 as TMermaidText, beautifulMermaidRenderer, clearMermaidImageCache, createBeautifulMermaidRenderer, getCachedMermaidImage, getMermaidImage, isMermaidImageRendererReady, loadMermaidImageRenderer, normalizeMermaidImageOptions, resolveMermaidImageColor, resolveMermaidSvgForResvg, setMermaidImageRasterizer, subscribeMermaidImage };
|