@simon_he/vue-tui 1.0.2 → 1.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/README.md +84 -26
- package/dist/TMermaidText-frVr8Ysb.js +885 -0
- package/dist/{TSelect-DR0FomSu.js → TSelect-tJ87rKPM.js} +59 -242
- package/dist/{TTree-kzhlB5P0.js → TTree-CGdE-CYu.js} +138 -102
- package/dist/agent/mermaid.cjs +2 -0
- package/dist/agent/mermaid.d.cts +2 -0
- package/dist/agent/mermaid.d.ts +2 -0
- package/dist/agent/mermaid.js +1 -0
- package/dist/agent.cjs +31188 -24907
- package/dist/agent.d.cts +11 -1
- package/dist/agent.d.ts +11 -1
- package/dist/agent.js +516 -11
- package/dist/{block-source-CKUOnkgT.js → block-source-CrlmaBM5.js} +1039 -66
- package/dist/cli/input.d.ts +8 -0
- package/dist/cli/stdin-buffer.d.ts +1 -1
- package/dist/cli/video/ffmpeg-frame-source.d.ts +10 -0
- package/dist/cli/video/png-frame-parser.d.ts +4 -0
- package/dist/cli/video/raw-frame-parser.d.ts +1 -0
- package/dist/cli/video/yt-dlp-frame-source.d.ts +11 -0
- package/dist/cli.cjs +5146 -6767
- package/dist/cli.d.cts +5 -1
- package/dist/cli.d.ts +5 -1
- package/dist/cli.js +2869 -154
- package/dist/core/buffer/eaw-ranges-unicode-17.d.ts +14 -0
- package/dist/core/perf/instrumentation-noop.d.ts +48 -0
- package/dist/core/perf/instrumentation.d.ts +136 -0
- package/dist/core/terminal/create-terminal.d.ts +1 -0
- package/dist/core.cjs +843 -1469
- package/dist/core.js +4 -5
- package/dist/create-terminal-app.d.ts +24 -1
- package/dist/debug-logger-CmsGI4of.js +74 -0
- package/dist/{dom-renderer-BniJSQyX.js → dom-renderer-DXx8fWzD.js} +2 -3
- package/dist/experimental/3d/bun/contributor-avatar-atlas.d.ts +12 -0
- package/dist/experimental/3d/bun/png.d.ts +1 -0
- package/dist/experimental/3d/bun/terminal-badge-scene.d.ts +11 -0
- package/dist/experimental/3d/bun/terminal-badge.wgsl.d.ts +1 -0
- package/dist/experimental/3d/bun/webgpu-renderer.d.ts +26 -0
- package/dist/experimental/3d/bun.cjs +7271 -0
- package/dist/experimental/3d/bun.d.cts +4 -0
- package/dist/experimental/3d/bun.d.ts +4 -0
- package/dist/experimental/3d/bun.js +7390 -0
- package/dist/experimental/video/node.cjs +497 -0
- package/dist/experimental/video/node.d.cts +4 -0
- package/dist/experimental/video/node.d.ts +4 -0
- package/dist/experimental/video/node.js +610 -0
- package/dist/experimental.cjs +6876 -6192
- package/dist/experimental.d.cts +7 -0
- package/dist/experimental.d.ts +7 -0
- package/dist/experimental.js +3005 -9
- package/dist/frame-perf-CHpWhbC4.js +111 -0
- package/dist/{create-terminal-DBFNYr8f.js → hyperlink-_1FAj7Wq.js} +440 -13
- package/dist/index.cjs +5017 -10339
- package/dist/index.js +14 -13
- package/dist/{linkify-D_gzn9Xq.js → linkify-D68T7rdW.js} +193 -3
- package/dist/markdown.cjs +21295 -12691
- package/dist/markdown.d.cts +1 -1
- package/dist/markdown.d.ts +1 -1
- package/dist/markdown.js +211 -51
- package/dist/mermaid-BNhM1jAZ.js +123 -0
- package/dist/mermaid.cjs +1707 -0
- package/dist/mermaid.d.cts +3 -0
- package/dist/mermaid.d.ts +3 -0
- package/dist/mermaid.js +6 -0
- package/dist/observability/frame-perf-store.d.ts +2 -1
- package/dist/observability/frame-perf.d.ts +33 -0
- package/dist/observability.cjs +106 -107
- package/dist/observability.d.cts +2 -2
- package/dist/observability.d.ts +2 -2
- package/dist/observability.js +3 -3
- package/dist/{wheel-scroll-vBRaQ7p-.js → rect-DUpeDfBg.js} +253 -117
- package/dist/renderer/cli/stdout-metrics.d.ts +10 -0
- package/dist/renderer/cli/stdout-renderer.d.ts +3 -0
- package/dist/renderer/terminal-graphic-render-queue.d.ts +50 -0
- package/dist/renderer/terminal-graphics-trace.d.ts +53 -0
- package/dist/renderer/terminal-graphics.d.ts +147 -0
- package/dist/renderer-dom.cjs +364 -903
- package/dist/renderer-dom.js +4 -5
- package/dist/runtime/index.d.ts +2 -0
- package/dist/{runtime-rfcZCyJq.js → runtime-Byf8XXOT.js} +28 -15
- package/dist/runtime.cjs +427 -889
- package/dist/runtime.js +2 -2
- package/dist/terminal-graphics-Bbe83wSr.js +597 -0
- package/dist/{terminal-selection-DBinO6p5.js → terminal-selection-4W1SJj4c.js} +4 -1
- package/dist/{tlog-theme-CvLFNMwk.js → tlog-theme-Bul2Na3u.js} +1237 -12
- package/dist/{trace-CjzYeIxN.js → trace-Bh8WnoMo.js} +5 -0
- package/dist/vue/agent/create-png-terminal-graphic-renderer.d.ts +19 -0
- package/dist/vue/components/T3DViewport.d.ts +473 -0
- package/dist/vue/components/TAgentTerminalGraphic.d.ts +494 -0
- package/dist/vue/components/TCharts.d.ts +1121 -0
- package/dist/vue/components/TFeedback.d.ts +1 -1
- package/dist/vue/components/TFlex.d.ts +389 -0
- package/dist/vue/components/TForm.d.ts +4 -4
- package/dist/vue/components/TLogSearchBar.d.ts +3 -3
- package/dist/vue/components/TLogSearchPager.d.ts +2 -2
- package/dist/vue/components/TMarkdownText.d.ts +162 -4
- package/dist/vue/components/TMermaidText.d.ts +900 -0
- package/dist/vue/components/TVideo.d.ts +291 -0
- package/dist/vue/components/TVirtualList.d.ts +9 -0
- package/dist/vue/components/TVirtualMarkdown.d.ts +170 -9
- package/dist/vue/components/TVirtualRows.d.ts +9 -0
- package/dist/vue/components/TerminalProvider.d.ts +2 -0
- package/dist/vue/context.d.ts +24 -0
- package/dist/vue/index.d.ts +5 -0
- package/dist/vue/layout/flex.d.ts +67 -0
- package/dist/vue/markdown/ast.d.ts +5 -2
- package/dist/vue/markdown/document.d.ts +5 -0
- package/dist/vue/markdown/image-actions.d.ts +14 -0
- package/dist/vue/markdown/image.d.ts +11 -0
- package/dist/vue/markdown/link-actions.d.ts +14 -0
- package/dist/vue/markdown/math-actions.d.ts +14 -0
- package/dist/vue/markdown/math.d.ts +6 -0
- package/dist/vue/markdown/render.d.ts +28 -0
- package/dist/vue/markdown/types.d.ts +78 -0
- package/dist/vue/mermaid/beautiful-mermaid.d.ts +1436 -0
- package/dist/vue/render/render-manager.d.ts +7 -0
- package/dist/vue/video/types.d.ts +39 -0
- package/dist/vue.cjs +7207 -12437
- package/dist/vue.js +704 -38
- package/dist/wheel-scroll-DASkEZr-.js +106 -0
- package/dist/{hyperlink-Bn02UXJb.js → width-GqllnV8C.js} +166 -111
- package/package.json +119 -14
- package/dist/frame-perf-C6Xfg85O.js +0 -24
- package/dist/sgr-BmzxEjAO.js +0 -304
- /package/dist/{ansi-palette-CNXqFciC.js → ansi-palette-CH6jc2Z7.js} +0 -0
- /package/dist/{path-suggest-BNqcuE3-.js → path-suggest-Dndu4dTi.js} +0 -0
|
@@ -1,9 +1,296 @@
|
|
|
1
|
-
import { sanitizeDomHref } from "./hyperlink-
|
|
2
|
-
import { EventZIndexContextKey,
|
|
3
|
-
import { terminalSelectionRowSpans, terminalSelectionVisibleRowSpans } from "./terminal-selection-
|
|
1
|
+
import { hasEncodedControl, sanitizeDomHref } from "./hyperlink-_1FAj7Wq.js";
|
|
2
|
+
import { EventZIndexContextKey, currentTextWidthProvider, forEachTextCellSegment, intersectRect, normalizeCellRect, repeatChar, sanitizeInlineText, sanitizeTextBlock, sliceByCellsRange, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./rect-DUpeDfBg.js";
|
|
3
|
+
import { terminalSelectionRowSpans, terminalSelectionVisibleRowSpans } from "./terminal-selection-4W1SJj4c.js";
|
|
4
|
+
import { createDebugLogger, isDebugEnabled } from "./debug-logger-CmsGI4of.js";
|
|
5
|
+
import { applyWheelScroll, createWheelScrollState, resetWheelScrollState } from "./wheel-scroll-DASkEZr-.js";
|
|
6
|
+
import { createIterm2InlineImageSequence, createKittyDeleteGraphicsSequence, createKittyGraphicsSequence, createKittyPlacementSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, isTerminalGraphicsProtocol, stableTerminalGraphicNumericId, subscribeTerminalGraphicsOutput } from "./terminal-graphics-Bbe83wSr.js";
|
|
4
7
|
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, ref, shallowRef, watch, watchEffect } from "vue";
|
|
8
|
+
import katex from "katex";
|
|
5
9
|
import { getMarkdown, parseMarkdownToStructure } from "stream-markdown-parser";
|
|
6
10
|
|
|
11
|
+
//#region src/vue/markdown/image.ts
|
|
12
|
+
const DATA_IMAGE_RE = /^data:(image\/[A-Za-z0-9.+-]+);base64,([A-Za-z0-9+/=\s]+)$/i;
|
|
13
|
+
const SAFE_IMAGE_MIME_RE = /^image\/(?:png|jpeg|jpg|gif|webp)$/i;
|
|
14
|
+
function sanitizeMarkdownImageSource(src) {
|
|
15
|
+
if (typeof src !== "string") return null;
|
|
16
|
+
const raw = src.trim();
|
|
17
|
+
if (!raw || raw !== src) return null;
|
|
18
|
+
const data = raw.match(DATA_IMAGE_RE);
|
|
19
|
+
if (data) {
|
|
20
|
+
const mime = data[1].toLowerCase();
|
|
21
|
+
const base64 = data[2].replace(/\s+/g, "");
|
|
22
|
+
if (!SAFE_IMAGE_MIME_RE.test(mime)) return null;
|
|
23
|
+
if (!/^[A-Za-z0-9+/=]+$/.test(base64)) return null;
|
|
24
|
+
return {
|
|
25
|
+
src: raw,
|
|
26
|
+
mime,
|
|
27
|
+
base64
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (/^(?:blob|file):/i.test(raw)) {
|
|
31
|
+
if (raw.includes("\\") || /\s/u.test(raw) || hasEncodedControl(raw)) return null;
|
|
32
|
+
try {
|
|
33
|
+
const url = new URL(raw);
|
|
34
|
+
if (url.protocol === "blob:" || url.protocol === "file:") return { src: url.toString() };
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const safe = sanitizeDomHref(raw, { allowRelative: true });
|
|
40
|
+
return safe ? { src: safe } : null;
|
|
41
|
+
}
|
|
42
|
+
const BASE64_TABLE = (() => {
|
|
43
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
44
|
+
const table = new Map();
|
|
45
|
+
for (let i = 0; i < chars.length; i++) table.set(chars[i], i);
|
|
46
|
+
return table;
|
|
47
|
+
})();
|
|
48
|
+
function decodeBase64Prefix(value, maxBytes) {
|
|
49
|
+
const bytes = [];
|
|
50
|
+
let buffer = 0;
|
|
51
|
+
let bits = 0;
|
|
52
|
+
for (const ch of value.replace(/\s+/g, "")) {
|
|
53
|
+
if (ch === "=") break;
|
|
54
|
+
const sextet = BASE64_TABLE.get(ch);
|
|
55
|
+
if (sextet == null) continue;
|
|
56
|
+
buffer = buffer << 6 | sextet;
|
|
57
|
+
bits += 6;
|
|
58
|
+
while (bits >= 8) {
|
|
59
|
+
bits -= 8;
|
|
60
|
+
bytes.push(buffer >> bits & 255);
|
|
61
|
+
if (bytes.length >= maxBytes) return Uint8Array.from(bytes);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return Uint8Array.from(bytes);
|
|
65
|
+
}
|
|
66
|
+
function u16be(bytes, offset) {
|
|
67
|
+
return (bytes[offset] ?? 0) << 8 | (bytes[offset + 1] ?? 0);
|
|
68
|
+
}
|
|
69
|
+
function u16le(bytes, offset) {
|
|
70
|
+
return (bytes[offset] ?? 0) | (bytes[offset + 1] ?? 0) << 8;
|
|
71
|
+
}
|
|
72
|
+
function u24le(bytes, offset) {
|
|
73
|
+
return (bytes[offset] ?? 0) | (bytes[offset + 1] ?? 0) << 8 | (bytes[offset + 2] ?? 0) << 16;
|
|
74
|
+
}
|
|
75
|
+
function u32be(bytes, offset) {
|
|
76
|
+
return (bytes[offset] ?? 0) * 16777216 + ((bytes[offset + 1] ?? 0) << 16 | (bytes[offset + 2] ?? 0) << 8 | (bytes[offset + 3] ?? 0));
|
|
77
|
+
}
|
|
78
|
+
function validDimensions(width, height) {
|
|
79
|
+
return Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0 ? {
|
|
80
|
+
width,
|
|
81
|
+
height
|
|
82
|
+
} : null;
|
|
83
|
+
}
|
|
84
|
+
function readMarkdownImageDimensions(base64) {
|
|
85
|
+
if (!base64) return null;
|
|
86
|
+
const bytes = decodeBase64Prefix(base64, 4096);
|
|
87
|
+
if (bytes.length < 10) return null;
|
|
88
|
+
if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[12] === 73 && bytes[13] === 72 && bytes[14] === 68 && bytes[15] === 82) return validDimensions(u32be(bytes, 16), u32be(bytes, 20));
|
|
89
|
+
if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70) return validDimensions(u16le(bytes, 6), u16le(bytes, 8));
|
|
90
|
+
if (bytes[0] === 255 && bytes[1] === 216) for (let offset = 2; offset + 8 < bytes.length;) {
|
|
91
|
+
if (bytes[offset] !== 255) {
|
|
92
|
+
offset++;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const marker = bytes[offset + 1] ?? 0;
|
|
96
|
+
if (marker === 217 || marker === 218) break;
|
|
97
|
+
const length = u16be(bytes, offset + 2);
|
|
98
|
+
if (length < 2) break;
|
|
99
|
+
if (marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207) return validDimensions(u16be(bytes, offset + 7), u16be(bytes, offset + 5));
|
|
100
|
+
offset += 2 + length;
|
|
101
|
+
}
|
|
102
|
+
if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
|
|
103
|
+
const format = String.fromCharCode(bytes[12] ?? 0, bytes[13] ?? 0, bytes[14] ?? 0, bytes[15] ?? 0);
|
|
104
|
+
if (format === "VP8X" && bytes.length >= 30) return validDimensions(u24le(bytes, 24) + 1, u24le(bytes, 27) + 1);
|
|
105
|
+
if (format === "VP8 " && bytes.length >= 30) return validDimensions(u16le(bytes, 26) & 16383, u16le(bytes, 28) & 16383);
|
|
106
|
+
if (format === "VP8L" && bytes.length >= 25 && bytes[20] === 47) {
|
|
107
|
+
const b0 = bytes[21] ?? 0;
|
|
108
|
+
const b1 = bytes[22] ?? 0;
|
|
109
|
+
const b2 = bytes[23] ?? 0;
|
|
110
|
+
const b3 = bytes[24] ?? 0;
|
|
111
|
+
return validDimensions(1 + ((b1 & 63) << 8 | b0), 1 + (b3 << 6 | b2 >> 2));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/vue/markdown/math.ts
|
|
119
|
+
const KATEX_TEXT_OPTIONS = Object.freeze({
|
|
120
|
+
output: "mathml",
|
|
121
|
+
throwOnError: false,
|
|
122
|
+
strict: "ignore",
|
|
123
|
+
trust: false,
|
|
124
|
+
maxSize: 20,
|
|
125
|
+
maxExpand: 200
|
|
126
|
+
});
|
|
127
|
+
const KATEX_VALIDATE_OPTIONS = Object.freeze({
|
|
128
|
+
...KATEX_TEXT_OPTIONS,
|
|
129
|
+
throwOnError: true
|
|
130
|
+
});
|
|
131
|
+
const TEX_SYMBOLS = Object.freeze({
|
|
132
|
+
alpha: "α",
|
|
133
|
+
beta: "β",
|
|
134
|
+
gamma: "γ",
|
|
135
|
+
delta: "δ",
|
|
136
|
+
epsilon: "ε",
|
|
137
|
+
theta: "θ",
|
|
138
|
+
lambda: "λ",
|
|
139
|
+
mu: "μ",
|
|
140
|
+
pi: "π",
|
|
141
|
+
rho: "ρ",
|
|
142
|
+
sigma: "σ",
|
|
143
|
+
tau: "τ",
|
|
144
|
+
phi: "φ",
|
|
145
|
+
omega: "ω",
|
|
146
|
+
Gamma: "Γ",
|
|
147
|
+
Delta: "Δ",
|
|
148
|
+
Theta: "Θ",
|
|
149
|
+
Lambda: "Λ",
|
|
150
|
+
Pi: "Π",
|
|
151
|
+
Sigma: "Σ",
|
|
152
|
+
Phi: "Φ",
|
|
153
|
+
Omega: "Ω",
|
|
154
|
+
int: "∫",
|
|
155
|
+
sum: "∑",
|
|
156
|
+
prod: "∏",
|
|
157
|
+
infty: "∞",
|
|
158
|
+
infinity: "∞",
|
|
159
|
+
pm: "±",
|
|
160
|
+
times: "×",
|
|
161
|
+
cdot: "·",
|
|
162
|
+
div: "÷",
|
|
163
|
+
le: "≤",
|
|
164
|
+
leq: "≤",
|
|
165
|
+
ge: "≥",
|
|
166
|
+
geq: "≥",
|
|
167
|
+
neq: "≠",
|
|
168
|
+
ne: "≠",
|
|
169
|
+
approx: "≈",
|
|
170
|
+
equiv: "≡",
|
|
171
|
+
to: "→",
|
|
172
|
+
rightarrow: "→",
|
|
173
|
+
leftarrow: "←",
|
|
174
|
+
leftrightarrow: "↔",
|
|
175
|
+
partial: "∂",
|
|
176
|
+
nabla: "∇",
|
|
177
|
+
sqrt: "√"
|
|
178
|
+
});
|
|
179
|
+
const SUPPORTED_COMMANDS = new Set([
|
|
180
|
+
...Object.keys(TEX_SYMBOLS),
|
|
181
|
+
"frac",
|
|
182
|
+
"sqrt"
|
|
183
|
+
]);
|
|
184
|
+
function decodeXmlEntities(value) {
|
|
185
|
+
return value.replace(/&(?:#(\d+)|#x([0-9a-f]+)|([a-z]+));/gi, (match, dec, hex, named) => {
|
|
186
|
+
if (dec) return String.fromCodePoint(Number(dec));
|
|
187
|
+
if (hex) return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
188
|
+
switch (String(named).toLowerCase()) {
|
|
189
|
+
case "amp": return "&";
|
|
190
|
+
case "lt": return "<";
|
|
191
|
+
case "gt": return ">";
|
|
192
|
+
case "quot": return "\"";
|
|
193
|
+
case "apos": return "'";
|
|
194
|
+
default: return match;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function mathmlTagName(tag) {
|
|
199
|
+
const start = tag.startsWith("/") ? 1 : 0;
|
|
200
|
+
let end = start;
|
|
201
|
+
while (end < tag.length) {
|
|
202
|
+
const ch = tag[end];
|
|
203
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "/") break;
|
|
204
|
+
end++;
|
|
205
|
+
}
|
|
206
|
+
return tag.slice(start, end).toLowerCase();
|
|
207
|
+
}
|
|
208
|
+
function textFromKatexMathml(html) {
|
|
209
|
+
let readable = "";
|
|
210
|
+
let offset = 0;
|
|
211
|
+
const lowerHtml = html.toLowerCase();
|
|
212
|
+
while (offset < html.length) {
|
|
213
|
+
const tagStart = html.indexOf("<", offset);
|
|
214
|
+
if (tagStart < 0) {
|
|
215
|
+
readable += html.slice(offset);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
readable += html.slice(offset, tagStart);
|
|
219
|
+
const tagEnd = html.indexOf(">", tagStart + 1);
|
|
220
|
+
if (tagEnd < 0) break;
|
|
221
|
+
const tag = html.slice(tagStart + 1, tagEnd).trim();
|
|
222
|
+
const tagName = mathmlTagName(tag);
|
|
223
|
+
const closing = tag.startsWith("/");
|
|
224
|
+
if (!closing && tagName === "annotation") {
|
|
225
|
+
const annotationEnd = lowerHtml.indexOf("</annotation>", tagEnd + 1);
|
|
226
|
+
offset = annotationEnd < 0 ? tagEnd + 1 : annotationEnd + 13;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (!closing && tagName === "mfrac") readable += "(";
|
|
230
|
+
else if (closing && tagName === "mfrac") readable += ")";
|
|
231
|
+
else if (!closing && tagName === "mspace") readable += " ";
|
|
232
|
+
offset = tagEnd + 1;
|
|
233
|
+
}
|
|
234
|
+
const text = sanitizeInlineText(decodeXmlEntities(readable).replace(/[<>]/g, "").replace(/\s+/g, " ").trim());
|
|
235
|
+
return text;
|
|
236
|
+
}
|
|
237
|
+
function approximateTexToUnicode(tex) {
|
|
238
|
+
let out = String(tex ?? "");
|
|
239
|
+
out = out.replace(/\\frac\s*\{([^{}]*)\}\s*\{([^{}]*)\}/g, "$1/$2");
|
|
240
|
+
out = out.replace(/\\sqrt\s*\{([^{}]*)\}/g, "√($1)");
|
|
241
|
+
out = out.replace(/\\([A-Za-z]+)\b/g, (_match, command) => TEX_SYMBOLS[command] ?? command);
|
|
242
|
+
out = out.replace(/\\([^A-Za-z])/g, "$1");
|
|
243
|
+
out = out.replace(/[{}]/g, "");
|
|
244
|
+
out = out.replace(/\s+/g, " ").trim();
|
|
245
|
+
return sanitizeInlineText(out.replace(/[<>]/g, ""));
|
|
246
|
+
}
|
|
247
|
+
function canRenderTerminalMath(tex) {
|
|
248
|
+
const source = String(tex ?? "").trim();
|
|
249
|
+
if (!source || source.length > 160) return false;
|
|
250
|
+
if (/\\(?:begin|end|left|right|matrix|cases|array|align|color|href|html|includegraphics|class|style|tag|text|operatorname)\b/.test(source)) return false;
|
|
251
|
+
const commands = source.matchAll(/\\([A-Za-z]+)\b/g);
|
|
252
|
+
for (const match of commands) if (!SUPPORTED_COMMANDS.has(match[1] ?? "")) return false;
|
|
253
|
+
try {
|
|
254
|
+
katex.renderToString(source, KATEX_VALIDATE_OPTIONS);
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
function renderMarkdownInlineMathSegment(tex) {
|
|
261
|
+
const source = String(tex ?? "");
|
|
262
|
+
if (!source.trim()) return {
|
|
263
|
+
text: "",
|
|
264
|
+
supported: false
|
|
265
|
+
};
|
|
266
|
+
if (!canRenderTerminalMath(source)) return {
|
|
267
|
+
text: "",
|
|
268
|
+
supported: false
|
|
269
|
+
};
|
|
270
|
+
try {
|
|
271
|
+
const html = katex.renderToString(source, KATEX_TEXT_OPTIONS);
|
|
272
|
+
const rendered = /\\frac\b/.test(source) ? approximateTexToUnicode(source) : textFromKatexMathml(html);
|
|
273
|
+
if (rendered && !rendered.includes("\\")) return {
|
|
274
|
+
text: rendered,
|
|
275
|
+
supported: true
|
|
276
|
+
};
|
|
277
|
+
} catch {
|
|
278
|
+
return {
|
|
279
|
+
text: "",
|
|
280
|
+
supported: false
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const approximate = approximateTexToUnicode(source);
|
|
284
|
+
return approximate && !approximate.includes("\\") ? {
|
|
285
|
+
text: approximate,
|
|
286
|
+
supported: true
|
|
287
|
+
} : {
|
|
288
|
+
text: "",
|
|
289
|
+
supported: false
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
//#endregion
|
|
7
294
|
//#region src/vue/markdown/parser.ts
|
|
8
295
|
const markdownInstanceCache = new Map();
|
|
9
296
|
const MAX_MARKDOWN_INSTANCE_CACHE = 32;
|
|
@@ -13,6 +300,9 @@ function sanitizeMarkdownLink(url) {
|
|
|
13
300
|
function isSafeMarkdownLink(url) {
|
|
14
301
|
return sanitizeMarkdownLink(url) != null;
|
|
15
302
|
}
|
|
303
|
+
function isSafeMarkdownDestination(url) {
|
|
304
|
+
return isSafeMarkdownLink(url) || sanitizeMarkdownImageSource(url) != null;
|
|
305
|
+
}
|
|
16
306
|
function normalizeCustomHtmlTags(config) {
|
|
17
307
|
return Array.from(new Set(config?.customHtmlTags?.filter(Boolean) ?? [])).sort();
|
|
18
308
|
}
|
|
@@ -44,7 +334,7 @@ function createTuiMarkdownParser(config) {
|
|
|
44
334
|
final,
|
|
45
335
|
customHtmlTags,
|
|
46
336
|
requireClosingStrong: final || !config?.streaming,
|
|
47
|
-
validateLink:
|
|
337
|
+
validateLink: isSafeMarkdownDestination
|
|
48
338
|
});
|
|
49
339
|
}
|
|
50
340
|
return { parse };
|
|
@@ -57,6 +347,7 @@ const HARD_BREAK_SEGMENT = Object.freeze({
|
|
|
57
347
|
text: "",
|
|
58
348
|
hardBreak: true
|
|
59
349
|
});
|
|
350
|
+
const TERMINAL_CELL_WIDTH_TO_HEIGHT_RATIO = .5;
|
|
60
351
|
function mergeStyle$1(base, overlay) {
|
|
61
352
|
if (!base) return overlay;
|
|
62
353
|
if (!overlay) return base;
|
|
@@ -79,6 +370,32 @@ function stringProp(node, key) {
|
|
|
79
370
|
const value = node[key];
|
|
80
371
|
return typeof value === "string" ? value : "";
|
|
81
372
|
}
|
|
373
|
+
function imageSourceFromRaw(raw) {
|
|
374
|
+
if (!raw.startsWith(";
|
|
376
|
+
if (marker < 0 || !raw.endsWith(")")) return "";
|
|
377
|
+
const destination = raw.slice(marker + 2, -1).trim();
|
|
378
|
+
if (destination.startsWith("<")) {
|
|
379
|
+
const end = destination.indexOf(">");
|
|
380
|
+
return end > 1 ? destination.slice(1, end) : "";
|
|
381
|
+
}
|
|
382
|
+
return destination.split(/\s+/u)[0] ?? "";
|
|
383
|
+
}
|
|
384
|
+
function imageFallbackText(raw, rawSource, sourceSrc) {
|
|
385
|
+
if (raw) return raw;
|
|
386
|
+
const src = rawSource || sourceSrc;
|
|
387
|
+
return src ? `` : "image";
|
|
388
|
+
}
|
|
389
|
+
function normalizeImageResolverResult(result) {
|
|
390
|
+
if (typeof result === "string") return { base64: result.replace(/\s+/g, "") };
|
|
391
|
+
if (!result) return {};
|
|
392
|
+
return {
|
|
393
|
+
base64: result.base64.replace(/\s+/g, ""),
|
|
394
|
+
...result.originalBase64 ? { originalBase64: result.originalBase64.replace(/\s+/g, "") } : {},
|
|
395
|
+
...result.mime ? { mime: result.mime } : {},
|
|
396
|
+
...result.originalMime ? { originalMime: result.originalMime } : {}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
82
399
|
function booleanProp(node, key) {
|
|
83
400
|
return node[key] === true;
|
|
84
401
|
}
|
|
@@ -90,17 +407,51 @@ function nodeItems(node) {
|
|
|
90
407
|
const value = node.items;
|
|
91
408
|
return Array.isArray(value) ? value : [];
|
|
92
409
|
}
|
|
93
|
-
function
|
|
410
|
+
function clampImageCells(value, min, max) {
|
|
411
|
+
return Math.max(min, Math.min(max, Math.round(value)));
|
|
412
|
+
}
|
|
413
|
+
function resolveImageDisplaySize(alt, size, naturalWidth, naturalHeight) {
|
|
414
|
+
if (size?.minWidth == null && size?.maxWidth == null && size?.minHeight == null && size?.maxHeight == null) return {};
|
|
415
|
+
const minWidth = Math.max(1, Math.floor(size?.minWidth ?? 1));
|
|
416
|
+
const maxWidth = Math.max(minWidth, Math.floor(size?.maxWidth ?? Number.MAX_SAFE_INTEGER));
|
|
417
|
+
const minHeight = Math.max(1, Math.floor(size?.minHeight ?? 1));
|
|
418
|
+
const maxHeight = Math.max(minHeight, Math.floor(size?.maxHeight ?? Number.MAX_SAFE_INTEGER));
|
|
419
|
+
if (size?.preserveAspectRatio !== false && naturalWidth != null && naturalHeight != null && naturalWidth > 0 && naturalHeight > 0) {
|
|
420
|
+
const cellRatio = naturalWidth / naturalHeight / TERMINAL_CELL_WIDTH_TO_HEIGHT_RATIO;
|
|
421
|
+
let width = minWidth;
|
|
422
|
+
let height = Math.max(minHeight, Math.ceil(width / cellRatio));
|
|
423
|
+
width = Math.max(width, Math.ceil(height * cellRatio));
|
|
424
|
+
if (width > maxWidth) {
|
|
425
|
+
width = maxWidth;
|
|
426
|
+
height = Math.max(minHeight, Math.ceil(width / cellRatio));
|
|
427
|
+
}
|
|
428
|
+
if (height > maxHeight) {
|
|
429
|
+
height = maxHeight;
|
|
430
|
+
width = Math.max(minWidth, Math.floor(height * cellRatio));
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
displayWidth: clampImageCells(width, minWidth, maxWidth),
|
|
434
|
+
displayHeight: clampImageCells(height, minHeight, maxHeight)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
displayWidth: size?.maxWidth != null ? Math.min(maxWidth, Math.max(minWidth, textCellWidth(alt))) : Math.max(minWidth, textCellWidth(alt)),
|
|
439
|
+
displayHeight: size?.maxHeight != null ? Math.min(maxHeight, Math.max(minHeight, 1)) : minHeight
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function pushTextSegments(out, text, style, graphic, mathAction) {
|
|
94
443
|
if (!text) return;
|
|
95
444
|
const normalized = sanitizeTextBlock(text);
|
|
96
445
|
if (!normalized) return;
|
|
97
446
|
const parts = normalized.split("\n");
|
|
98
447
|
for (let i = 0; i < parts.length; i++) {
|
|
99
448
|
const part = sanitizeInlineText(parts[i] ?? "");
|
|
100
|
-
if (part) out.push(
|
|
449
|
+
if (part) out.push({
|
|
101
450
|
text: part,
|
|
102
|
-
style
|
|
103
|
-
|
|
451
|
+
...style ? { style } : {},
|
|
452
|
+
...i === 0 && graphic ? { graphic } : {},
|
|
453
|
+
...mathAction ? { mathAction } : {}
|
|
454
|
+
});
|
|
104
455
|
if (i < parts.length - 1) out.push(HARD_BREAK_SEGMENT);
|
|
105
456
|
}
|
|
106
457
|
}
|
|
@@ -108,7 +459,12 @@ function sanitizeCodeBlockText(text, tabSize = 4) {
|
|
|
108
459
|
const normalized = String(text ?? "").replace(/\r\n?/g, "\n").replace(/\t/g, spaces(tabSize)).replace(/\u00a0/g, " ");
|
|
109
460
|
return sanitizeTextBlock(normalized);
|
|
110
461
|
}
|
|
111
|
-
function
|
|
462
|
+
function codeBlockLanguage(node) {
|
|
463
|
+
const raw = stringProp(node, "lang") || stringProp(node, "language") || stringProp(node, "info") || stringProp(node, "meta");
|
|
464
|
+
const language = raw.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
465
|
+
return language || void 0;
|
|
466
|
+
}
|
|
467
|
+
function inlineNodeSegments(nodes, theme, inheritedStyle, options = {}) {
|
|
112
468
|
const out = [];
|
|
113
469
|
for (const node of nodes) switch (node.type) {
|
|
114
470
|
case "text":
|
|
@@ -119,22 +475,22 @@ function inlineNodeSegments(nodes, theme, inheritedStyle) {
|
|
|
119
475
|
case "heading":
|
|
120
476
|
case "list_item":
|
|
121
477
|
case "blockquote":
|
|
122
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
|
|
478
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
123
479
|
break;
|
|
124
480
|
case "strong":
|
|
125
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strong)));
|
|
481
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strong), options));
|
|
126
482
|
break;
|
|
127
483
|
case "emphasis":
|
|
128
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.emphasis)));
|
|
484
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.emphasis), options));
|
|
129
485
|
break;
|
|
130
486
|
case "strikethrough":
|
|
131
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough)));
|
|
487
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough), options));
|
|
132
488
|
break;
|
|
133
489
|
case "highlight":
|
|
134
490
|
case "insert":
|
|
135
491
|
case "subscript":
|
|
136
492
|
case "superscript":
|
|
137
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
|
|
493
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
138
494
|
break;
|
|
139
495
|
case "inline_code":
|
|
140
496
|
pushTextSegments(out, stringProp(node, "code"), mergeStyle$1(inheritedStyle, theme.inlineCode));
|
|
@@ -150,13 +506,55 @@ function inlineNodeSegments(nodes, theme, inheritedStyle) {
|
|
|
150
506
|
href: safeHref
|
|
151
507
|
}) : inheritedStyle;
|
|
152
508
|
const children = nodeChildren(node);
|
|
153
|
-
if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle));
|
|
509
|
+
if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle, options));
|
|
154
510
|
else pushTextSegments(out, stringProp(node, "text"), linkStyle);
|
|
155
511
|
break;
|
|
156
512
|
}
|
|
157
|
-
case "image":
|
|
158
|
-
|
|
513
|
+
case "image": {
|
|
514
|
+
const raw = stringProp(node, "raw");
|
|
515
|
+
const rawSource = stringProp(node, "src") || imageSourceFromRaw(raw);
|
|
516
|
+
const source = sanitizeMarkdownImageSource(rawSource);
|
|
517
|
+
const alt = stringProp(node, "alt") || imageFallbackText(raw, rawSource, source?.src);
|
|
518
|
+
if (source) {
|
|
519
|
+
const size = options.imageSize;
|
|
520
|
+
const imageLinkStyle = mergeStyle$1(inheritedStyle, {
|
|
521
|
+
...theme.link,
|
|
522
|
+
href: source.src
|
|
523
|
+
});
|
|
524
|
+
const sourceDimensions = readMarkdownImageDimensions(source.base64);
|
|
525
|
+
const graphicBase = {
|
|
526
|
+
kind: "image",
|
|
527
|
+
src: source.src,
|
|
528
|
+
alt,
|
|
529
|
+
...source.mime ? { mime: source.mime } : {},
|
|
530
|
+
...source.base64 ? { base64: source.base64 } : {},
|
|
531
|
+
...sourceDimensions ? {
|
|
532
|
+
naturalWidth: sourceDimensions.width,
|
|
533
|
+
naturalHeight: sourceDimensions.height
|
|
534
|
+
} : {},
|
|
535
|
+
...resolveImageDisplaySize(alt, size, sourceDimensions?.width, sourceDimensions?.height)
|
|
536
|
+
};
|
|
537
|
+
const resolved = normalizeImageResolverResult(options.imageResolver?.(graphicBase));
|
|
538
|
+
const base64 = resolved.base64 || source.base64;
|
|
539
|
+
if (base64) {
|
|
540
|
+
const dimensions = readMarkdownImageDimensions(base64) ?? sourceDimensions;
|
|
541
|
+
const graphic = {
|
|
542
|
+
...graphicBase,
|
|
543
|
+
base64,
|
|
544
|
+
...resolved.originalBase64 ? { originalBase64: resolved.originalBase64 } : {},
|
|
545
|
+
...resolved.mime ? { mime: resolved.mime } : {},
|
|
546
|
+
...resolved.originalMime ? { originalMime: resolved.originalMime } : {},
|
|
547
|
+
...dimensions ? {
|
|
548
|
+
naturalWidth: dimensions.width,
|
|
549
|
+
naturalHeight: dimensions.height
|
|
550
|
+
} : {},
|
|
551
|
+
...resolveImageDisplaySize(alt, size, dimensions?.width, dimensions?.height)
|
|
552
|
+
};
|
|
553
|
+
pushTextSegments(out, alt, imageLinkStyle, graphic);
|
|
554
|
+
} else pushTextSegments(out, alt, imageLinkStyle);
|
|
555
|
+
} else pushTextSegments(out, alt, mergeStyle$1(inheritedStyle, theme.link));
|
|
159
556
|
break;
|
|
557
|
+
}
|
|
160
558
|
case "checkbox":
|
|
161
559
|
case "checkbox_input":
|
|
162
560
|
pushTextSegments(out, booleanProp(node, "checked") ? "[x]" : "[ ]", inheritedStyle);
|
|
@@ -165,7 +563,16 @@ function inlineNodeSegments(nodes, theme, inheritedStyle) {
|
|
|
165
563
|
pushTextSegments(out, stringProp(node, "markup") || stringProp(node, "name"), inheritedStyle);
|
|
166
564
|
break;
|
|
167
565
|
case "math_inline":
|
|
168
|
-
|
|
566
|
+
{
|
|
567
|
+
const source = stringProp(node, "content");
|
|
568
|
+
const raw = stringProp(node, "raw") || `$${source}$`;
|
|
569
|
+
const rendered = renderMarkdownInlineMathSegment(source);
|
|
570
|
+
pushTextSegments(out, rendered.supported ? rendered.text : raw, mergeStyle$1(inheritedStyle, theme.inlineCode), void 0, {
|
|
571
|
+
source,
|
|
572
|
+
raw,
|
|
573
|
+
rendered: rendered.supported
|
|
574
|
+
});
|
|
575
|
+
}
|
|
169
576
|
break;
|
|
170
577
|
case "reference":
|
|
171
578
|
case "footnote_reference":
|
|
@@ -180,7 +587,7 @@ function inlineNodeSegments(nodes, theme, inheritedStyle) {
|
|
|
180
587
|
break;
|
|
181
588
|
default:
|
|
182
589
|
if (nodeChildren(node).length) {
|
|
183
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
|
|
590
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
184
591
|
break;
|
|
185
592
|
}
|
|
186
593
|
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
@@ -198,16 +605,23 @@ function inlineBlock(key, segments, context) {
|
|
|
198
605
|
};
|
|
199
606
|
}
|
|
200
607
|
function blockFromParagraph(node, key, context, theme) {
|
|
201
|
-
return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme
|
|
608
|
+
return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, void 0, {
|
|
609
|
+
imageResolver: context.imageResolver,
|
|
610
|
+
imageSize: context.imageSize
|
|
611
|
+
}), context);
|
|
202
612
|
}
|
|
203
613
|
function blockFromHeading(node, key, context, theme) {
|
|
204
614
|
const level = Math.min(6, Math.max(1, Math.floor(numberProp(node, "level") ?? 1)));
|
|
205
|
-
return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, theme.heading[level - 1]
|
|
615
|
+
return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, theme.heading[level - 1], {
|
|
616
|
+
imageResolver: context.imageResolver,
|
|
617
|
+
imageSize: context.imageSize
|
|
618
|
+
}), context);
|
|
206
619
|
}
|
|
207
620
|
function blockFromCodeBlock(node, key, context, theme) {
|
|
208
621
|
return {
|
|
209
622
|
type: "code_block",
|
|
210
623
|
key,
|
|
624
|
+
language: codeBlockLanguage(node),
|
|
211
625
|
lines: sanitizeCodeBlockText(stringProp(node, "code")).split("\n"),
|
|
212
626
|
style: theme.codeBlock,
|
|
213
627
|
prefixSegments: context.prefixSegments,
|
|
@@ -219,7 +633,7 @@ function blockFromHtmlBlock(node, key, context, theme) {
|
|
|
219
633
|
type: "html_block",
|
|
220
634
|
raw: stringProp(node, "raw"),
|
|
221
635
|
content: stringProp(node, "content")
|
|
222
|
-
}], theme), context);
|
|
636
|
+
}], theme, void 0, { imageResolver: context.imageResolver }), context);
|
|
223
637
|
}
|
|
224
638
|
function tableRows$1(node, key) {
|
|
225
639
|
const value = node[key];
|
|
@@ -237,11 +651,17 @@ function tableCellAlign(cell) {
|
|
|
237
651
|
function blockFromTable(node, key, context, theme) {
|
|
238
652
|
const headerRow = tableRows$1(node, "header")[0];
|
|
239
653
|
const header = tableCells(headerRow ?? { type: "table_row" }).map((cell) => ({
|
|
240
|
-
segments: inlineNodeSegments(nodeChildren(cell), theme, theme.strong
|
|
654
|
+
segments: inlineNodeSegments(nodeChildren(cell), theme, theme.strong, {
|
|
655
|
+
imageResolver: context.imageResolver,
|
|
656
|
+
imageSize: context.imageSize
|
|
657
|
+
}),
|
|
241
658
|
align: tableCellAlign(cell)
|
|
242
659
|
}));
|
|
243
660
|
const rows = tableRows$1(node, "rows").map((row) => tableCells(row).map((cell) => ({
|
|
244
|
-
segments: inlineNodeSegments(nodeChildren(cell), theme
|
|
661
|
+
segments: inlineNodeSegments(nodeChildren(cell), theme, void 0, {
|
|
662
|
+
imageResolver: context.imageResolver,
|
|
663
|
+
imageSize: context.imageSize
|
|
664
|
+
}),
|
|
245
665
|
align: tableCellAlign(cell)
|
|
246
666
|
})));
|
|
247
667
|
return {
|
|
@@ -339,7 +759,9 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
|
|
|
339
759
|
};
|
|
340
760
|
const quoteContext = {
|
|
341
761
|
prefixSegments: [...context.prefixSegments, quoteSegment],
|
|
342
|
-
continuationPrefixSegments: [...context.continuationPrefixSegments, quoteSegment]
|
|
762
|
+
continuationPrefixSegments: [...context.continuationPrefixSegments, quoteSegment],
|
|
763
|
+
imageResolver: context.imageResolver,
|
|
764
|
+
imageSize: context.imageSize
|
|
343
765
|
};
|
|
344
766
|
return childSequenceToBlocks(nodeChildren(node), quoteContext, theme, keyPrefix);
|
|
345
767
|
}
|
|
@@ -347,19 +769,24 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
|
|
|
347
769
|
if ("items" in node && Array.isArray(node.items)) return listBlocks(node, context, theme, keyPrefix);
|
|
348
770
|
break;
|
|
349
771
|
case "table": return [blockFromTable(node, keyPrefix, context, theme)];
|
|
350
|
-
case "inline": return [inlineBlock(keyPrefix, inlineNodeSegments(nodeChildren(node), theme
|
|
772
|
+
case "inline": return [inlineBlock(keyPrefix, inlineNodeSegments(nodeChildren(node), theme, void 0, {
|
|
773
|
+
imageResolver: context.imageResolver,
|
|
774
|
+
imageSize: context.imageSize
|
|
775
|
+
}), context)];
|
|
351
776
|
case "html_block":
|
|
352
777
|
if ("content" in node) return [blockFromHtmlBlock(node, keyPrefix, context, theme)];
|
|
353
778
|
break;
|
|
354
|
-
case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme), context)];
|
|
779
|
+
case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, { imageResolver: context.imageResolver }), context)];
|
|
355
780
|
}
|
|
356
781
|
if (nodeChildren(node).length) return childSequenceToBlocks(nodeChildren(node), context, theme, keyPrefix);
|
|
357
|
-
return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme), context)];
|
|
782
|
+
return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, { imageResolver: context.imageResolver }), context)];
|
|
358
783
|
}
|
|
359
|
-
function markdownAstToBlocks(nodes, theme) {
|
|
784
|
+
function markdownAstToBlocks(nodes, theme, options = {}) {
|
|
360
785
|
const blocks = trimBlockArray(childSequenceToBlocks(nodes, {
|
|
361
786
|
prefixSegments: EMPTY_PREFIX,
|
|
362
|
-
continuationPrefixSegments: EMPTY_PREFIX
|
|
787
|
+
continuationPrefixSegments: EMPTY_PREFIX,
|
|
788
|
+
imageResolver: options.imageResolver,
|
|
789
|
+
imageSize: options.imageSize
|
|
363
790
|
}, theme, "md"));
|
|
364
791
|
return blocks.length ? blocks : [{
|
|
365
792
|
type: "blank",
|
|
@@ -376,11 +803,21 @@ function toVisualSegment(segment) {
|
|
|
376
803
|
return {
|
|
377
804
|
text: segment.text,
|
|
378
805
|
style: segment.style,
|
|
379
|
-
cells
|
|
806
|
+
cells,
|
|
807
|
+
...segment.graphic ? { graphic: segment.graphic } : {},
|
|
808
|
+
...segment.mathAction ? { mathAction: segment.mathAction } : {}
|
|
380
809
|
};
|
|
381
810
|
}
|
|
382
811
|
function segmentsPlainText(segments) {
|
|
383
|
-
return segments.map((segment) => segment.text).join("");
|
|
812
|
+
return segments.map((segment) => segment.fallbackText ?? segment.text).join("");
|
|
813
|
+
}
|
|
814
|
+
function segmentsVisualHeight(segments) {
|
|
815
|
+
let height = 1;
|
|
816
|
+
for (const segment of segments) {
|
|
817
|
+
if (!segment.graphic) continue;
|
|
818
|
+
height = Math.max(height, Math.max(1, Math.floor(segment.graphic.displayHeight ?? 1)));
|
|
819
|
+
}
|
|
820
|
+
return height;
|
|
384
821
|
}
|
|
385
822
|
function segmentsCellWidth(segments) {
|
|
386
823
|
let cells = 0;
|
|
@@ -410,10 +847,15 @@ function clipInlineSegmentsToWidth(segments, width) {
|
|
|
410
847
|
const piece = sliceByCellsRange(segment.text, 0, remaining);
|
|
411
848
|
const cells = textCellWidth(piece);
|
|
412
849
|
if (!piece || cells <= 0) continue;
|
|
413
|
-
|
|
850
|
+
const clipped = {
|
|
414
851
|
text: piece,
|
|
415
|
-
style: segment.style
|
|
416
|
-
|
|
852
|
+
...segment.style ? { style: segment.style } : {},
|
|
853
|
+
...segment.mathAction ? { mathAction: segment.mathAction } : {}
|
|
854
|
+
};
|
|
855
|
+
out.push(piece === segment.text && segment.graphic ? {
|
|
856
|
+
...clipped,
|
|
857
|
+
graphic: segment.graphic
|
|
858
|
+
} : clipped);
|
|
417
859
|
remaining -= cells;
|
|
418
860
|
}
|
|
419
861
|
return out;
|
|
@@ -435,7 +877,9 @@ function clipVisualSegmentsToWidth(segments, width) {
|
|
|
435
877
|
out.push({
|
|
436
878
|
text,
|
|
437
879
|
style: segment.style,
|
|
438
|
-
cells
|
|
880
|
+
cells,
|
|
881
|
+
...text === segment.text && segment.graphic ? { graphic: segment.graphic } : {},
|
|
882
|
+
...segment.mathAction ? { mathAction: segment.mathAction } : {}
|
|
439
883
|
});
|
|
440
884
|
remaining -= cells;
|
|
441
885
|
}
|
|
@@ -477,6 +921,25 @@ function wrapLineSegments(source, prefixSegments, continuationPrefixSegments, wi
|
|
|
477
921
|
const prefixLengthForRow = () => row.useContinuation ? continuationPrefix.length : firstPrefix.length;
|
|
478
922
|
const rowHasBody = () => row.segments.length > prefixLengthForRow();
|
|
479
923
|
for (const segment of source) {
|
|
924
|
+
if (segment.graphic) {
|
|
925
|
+
const displayCells = Math.max(1, Math.floor(segment.graphic.displayWidth ?? 1));
|
|
926
|
+
if (row.remaining <= 0 && !rowHasBody()) {
|
|
927
|
+
if (!row.useContinuation) {
|
|
928
|
+
rows.push(row.segments);
|
|
929
|
+
row = openDegradedRow();
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const cells = Math.min(displayCells, Math.max(1, row.remaining || width));
|
|
933
|
+
row.segments.push({
|
|
934
|
+
text: spaces(cells),
|
|
935
|
+
style: segment.style,
|
|
936
|
+
cells,
|
|
937
|
+
graphic: segment.graphic,
|
|
938
|
+
fallbackText: segment.text
|
|
939
|
+
});
|
|
940
|
+
row.remaining -= cells;
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
480
943
|
let aborted = false;
|
|
481
944
|
forEachTextCellSegment(segment.text, (piece) => {
|
|
482
945
|
while (true) {
|
|
@@ -504,7 +967,8 @@ function wrapLineSegments(source, prefixSegments, continuationPrefixSegments, wi
|
|
|
504
967
|
row.segments.push({
|
|
505
968
|
text: piece.text,
|
|
506
969
|
style: segment.style,
|
|
507
|
-
cells: piece.cells
|
|
970
|
+
cells: piece.cells,
|
|
971
|
+
...segment.mathAction ? { mathAction: segment.mathAction } : {}
|
|
508
972
|
});
|
|
509
973
|
row.remaining -= piece.cells;
|
|
510
974
|
return void 0;
|
|
@@ -532,6 +996,17 @@ function inlineBlockRows(block, width) {
|
|
|
532
996
|
segments: visualSegments
|
|
533
997
|
});
|
|
534
998
|
rowInBlock++;
|
|
999
|
+
const height = segmentsVisualHeight(visualSegments);
|
|
1000
|
+
for (let extra = 1; extra < height; extra++) {
|
|
1001
|
+
rows.push({
|
|
1002
|
+
key: `${block.key}:${rowInBlock}`,
|
|
1003
|
+
blockKey: block.key,
|
|
1004
|
+
rowInBlock,
|
|
1005
|
+
plainText: "",
|
|
1006
|
+
segments: []
|
|
1007
|
+
});
|
|
1008
|
+
rowInBlock++;
|
|
1009
|
+
}
|
|
535
1010
|
}
|
|
536
1011
|
}
|
|
537
1012
|
return rows.length ? rows : [{
|
|
@@ -609,23 +1084,26 @@ function tableColumnWidths(block, columns, width) {
|
|
|
609
1084
|
}
|
|
610
1085
|
return widths;
|
|
611
1086
|
}
|
|
612
|
-
function appendVisualSegment(segments, text, style) {
|
|
1087
|
+
function appendVisualSegment(segments, text, style, graphic, mathAction) {
|
|
613
1088
|
if (!text) return;
|
|
614
1089
|
const cells = textCellWidth(text);
|
|
615
1090
|
if (cells <= 0) return;
|
|
616
1091
|
const prev = segments[segments.length - 1];
|
|
617
|
-
if (prev && prev.style === style) {
|
|
1092
|
+
if (prev && prev.style === style && prev.mathAction === mathAction && !prev.graphic && !graphic) {
|
|
618
1093
|
segments[segments.length - 1] = {
|
|
619
1094
|
text: `${prev.text}${text}`,
|
|
620
1095
|
style,
|
|
621
|
-
cells: prev.cells + cells
|
|
1096
|
+
cells: prev.cells + cells,
|
|
1097
|
+
...mathAction ? { mathAction } : {}
|
|
622
1098
|
};
|
|
623
1099
|
return;
|
|
624
1100
|
}
|
|
625
1101
|
segments.push({
|
|
626
1102
|
text,
|
|
627
1103
|
style,
|
|
628
|
-
cells
|
|
1104
|
+
cells,
|
|
1105
|
+
...graphic ? { graphic } : {},
|
|
1106
|
+
...mathAction ? { mathAction } : {}
|
|
629
1107
|
});
|
|
630
1108
|
}
|
|
631
1109
|
function appendInlineVisualSegments(out, segments) {
|
|
@@ -634,7 +1112,7 @@ function appendInlineVisualSegments(out, segments) {
|
|
|
634
1112
|
appendVisualSegment(out, " ");
|
|
635
1113
|
continue;
|
|
636
1114
|
}
|
|
637
|
-
appendVisualSegment(out, segment.text, segment.style);
|
|
1115
|
+
appendVisualSegment(out, segment.text, segment.style, segment.graphic, segment.mathAction);
|
|
638
1116
|
}
|
|
639
1117
|
}
|
|
640
1118
|
function tableBorderRow(block, widths, width, rowInBlock, left, join, right) {
|
|
@@ -723,10 +1201,26 @@ function styleSignature(style) {
|
|
|
723
1201
|
].join("");
|
|
724
1202
|
}
|
|
725
1203
|
function inlineSegmentSignature(segment) {
|
|
1204
|
+
const graphic = segment.graphic ? [
|
|
1205
|
+
segment.graphic.kind,
|
|
1206
|
+
segment.graphic.src,
|
|
1207
|
+
segment.graphic.alt ?? "",
|
|
1208
|
+
segment.graphic.mime ?? "",
|
|
1209
|
+
segment.graphic.base64 ?? "",
|
|
1210
|
+
segment.graphic.displayWidth ?? "",
|
|
1211
|
+
segment.graphic.displayHeight ?? ""
|
|
1212
|
+
].join("") : "";
|
|
1213
|
+
const mathAction = segment.mathAction ? [
|
|
1214
|
+
segment.mathAction.source,
|
|
1215
|
+
segment.mathAction.raw,
|
|
1216
|
+
segment.mathAction.rendered ? "1" : "0"
|
|
1217
|
+
].join("") : "";
|
|
726
1218
|
return [
|
|
727
1219
|
segment.text,
|
|
728
1220
|
segment.hardBreak ? "1" : "0",
|
|
729
|
-
styleSignature(segment.style)
|
|
1221
|
+
styleSignature(segment.style),
|
|
1222
|
+
graphic,
|
|
1223
|
+
mathAction
|
|
730
1224
|
].join("");
|
|
731
1225
|
}
|
|
732
1226
|
function inlineSegmentsSignature(segments) {
|
|
@@ -742,6 +1236,7 @@ function blockLayoutSignature(block) {
|
|
|
742
1236
|
].join("");
|
|
743
1237
|
case "code_block": return [
|
|
744
1238
|
block.type,
|
|
1239
|
+
block.language ?? "",
|
|
745
1240
|
block.lines.join(""),
|
|
746
1241
|
styleSignature(block.style),
|
|
747
1242
|
inlineSegmentsSignature(block.prefixSegments),
|
|
@@ -923,7 +1418,10 @@ function buildMarkdownBlocks(content, parser, options) {
|
|
|
923
1418
|
const theme = resolveTuiMarkdownTheme(options?.theme);
|
|
924
1419
|
try {
|
|
925
1420
|
const nodes = parser.parse(content, options?.final ?? true);
|
|
926
|
-
const blocks = markdownAstToBlocks(nodes, theme
|
|
1421
|
+
const blocks = markdownAstToBlocks(nodes, theme, {
|
|
1422
|
+
imageResolver: options?.imageResolver,
|
|
1423
|
+
imageSize: options?.imageSize
|
|
1424
|
+
});
|
|
927
1425
|
return {
|
|
928
1426
|
nodes,
|
|
929
1427
|
blocks
|
|
@@ -966,9 +1464,177 @@ function plainTextFallbackBlocks(content) {
|
|
|
966
1464
|
}];
|
|
967
1465
|
}
|
|
968
1466
|
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/vue/markdown/image-actions.ts
|
|
1469
|
+
function imageDisplayWidth(segment) {
|
|
1470
|
+
return Math.max(1, Math.floor(segment.graphic?.displayWidth ?? segment.cells));
|
|
1471
|
+
}
|
|
1472
|
+
function imageDisplayHeight(segment) {
|
|
1473
|
+
return Math.max(1, Math.floor(segment.graphic?.displayHeight ?? 1));
|
|
1474
|
+
}
|
|
1475
|
+
function findMarkdownImageActionAt(rows, point, options) {
|
|
1476
|
+
const r = options.screenRect;
|
|
1477
|
+
if (r.w <= 0 || r.h <= 0) return null;
|
|
1478
|
+
if (point.cellX < r.x || point.cellY < r.y || point.cellX >= r.x + r.w || point.cellY >= r.y + r.h) return null;
|
|
1479
|
+
const clipStart = Math.max(0, Math.floor(options.clipStart));
|
|
1480
|
+
const clipEnd = clipStart + r.w;
|
|
1481
|
+
const firstRow = Math.max(0, Math.floor(options.rowOffset));
|
|
1482
|
+
const lastRow = Math.min(rows.length, firstRow + r.h);
|
|
1483
|
+
for (let rowIndex = firstRow; rowIndex < lastRow; rowIndex++) {
|
|
1484
|
+
const row = rows[rowIndex];
|
|
1485
|
+
if (!row) continue;
|
|
1486
|
+
let logicalX = 0;
|
|
1487
|
+
for (let segmentIndex = 0; segmentIndex < row.segments.length; segmentIndex++) {
|
|
1488
|
+
const segment = row.segments[segmentIndex];
|
|
1489
|
+
const segmentStart = logicalX;
|
|
1490
|
+
const segmentEnd = logicalX + segment.cells;
|
|
1491
|
+
logicalX = segmentEnd;
|
|
1492
|
+
const image = segment.graphic;
|
|
1493
|
+
if (!image) continue;
|
|
1494
|
+
if (segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
|
|
1495
|
+
const imageX = r.x + segmentStart - clipStart;
|
|
1496
|
+
const imageY = r.y + rowIndex - firstRow;
|
|
1497
|
+
const imageRect = {
|
|
1498
|
+
x: imageX,
|
|
1499
|
+
y: imageY,
|
|
1500
|
+
w: imageDisplayWidth(segment),
|
|
1501
|
+
h: imageDisplayHeight(segment)
|
|
1502
|
+
};
|
|
1503
|
+
const visibleLeft = Math.max(r.x, imageRect.x);
|
|
1504
|
+
const visibleTop = Math.max(r.y, imageRect.y);
|
|
1505
|
+
const visibleRight = Math.min(r.x + r.w, imageRect.x + imageRect.w);
|
|
1506
|
+
const visibleBottom = Math.min(r.y + r.h, imageRect.y + imageRect.h);
|
|
1507
|
+
if (point.cellX < visibleLeft || point.cellY < visibleTop || point.cellX >= visibleRight || point.cellY >= visibleBottom) continue;
|
|
1508
|
+
return {
|
|
1509
|
+
image,
|
|
1510
|
+
rect: imageRect,
|
|
1511
|
+
cellX: point.cellX,
|
|
1512
|
+
cellY: point.cellY,
|
|
1513
|
+
rowIndex,
|
|
1514
|
+
segmentIndex
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
//#endregion
|
|
1522
|
+
//#region src/vue/markdown/link-actions.ts
|
|
1523
|
+
function findMarkdownLinkActionAt(rows, point, options) {
|
|
1524
|
+
const r = options.screenRect;
|
|
1525
|
+
if (r.w <= 0 || r.h <= 0) return null;
|
|
1526
|
+
if (point.cellX < r.x || point.cellY < r.y || point.cellX >= r.x + r.w || point.cellY >= r.y + r.h) return null;
|
|
1527
|
+
const clipStart = Math.max(0, Math.floor(options.clipStart));
|
|
1528
|
+
const clipEnd = clipStart + r.w;
|
|
1529
|
+
const firstRow = Math.max(0, Math.floor(options.rowOffset));
|
|
1530
|
+
const lastRow = Math.min(rows.length, firstRow + r.h);
|
|
1531
|
+
for (let rowIndex = firstRow; rowIndex < lastRow; rowIndex++) {
|
|
1532
|
+
const row = rows[rowIndex];
|
|
1533
|
+
if (!row) continue;
|
|
1534
|
+
let logicalX = 0;
|
|
1535
|
+
for (let segmentIndex = 0; segmentIndex < row.segments.length; segmentIndex++) {
|
|
1536
|
+
const segment = row.segments[segmentIndex];
|
|
1537
|
+
const segmentStart = logicalX;
|
|
1538
|
+
const segmentEnd = logicalX + segment.cells;
|
|
1539
|
+
logicalX = segmentEnd;
|
|
1540
|
+
const href = segment.style?.href;
|
|
1541
|
+
if (!href || segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
|
|
1542
|
+
const visibleStart = Math.max(segmentStart, clipStart);
|
|
1543
|
+
const visibleEnd = Math.min(segmentEnd, clipEnd);
|
|
1544
|
+
const rect = {
|
|
1545
|
+
x: r.x + visibleStart - clipStart,
|
|
1546
|
+
y: r.y + rowIndex - firstRow,
|
|
1547
|
+
w: visibleEnd - visibleStart,
|
|
1548
|
+
h: 1
|
|
1549
|
+
};
|
|
1550
|
+
if (point.cellX < rect.x || point.cellY < rect.y || point.cellX >= rect.x + rect.w || point.cellY >= rect.y + rect.h) continue;
|
|
1551
|
+
let groupStart = segmentStart;
|
|
1552
|
+
let groupEnd = segmentEnd;
|
|
1553
|
+
const textParts = [segment.fallbackText ?? segment.text];
|
|
1554
|
+
for (let index = segmentIndex - 1; index >= 0; index--) {
|
|
1555
|
+
const previous = row.segments[index];
|
|
1556
|
+
if (previous.style?.href !== href) break;
|
|
1557
|
+
groupStart -= previous.cells;
|
|
1558
|
+
textParts.unshift(previous.fallbackText ?? previous.text);
|
|
1559
|
+
}
|
|
1560
|
+
for (let index = segmentIndex + 1; index < row.segments.length; index++) {
|
|
1561
|
+
const next = row.segments[index];
|
|
1562
|
+
if (next.style?.href !== href) break;
|
|
1563
|
+
groupEnd += next.cells;
|
|
1564
|
+
textParts.push(next.fallbackText ?? next.text);
|
|
1565
|
+
}
|
|
1566
|
+
const visibleGroupStart = Math.max(groupStart, clipStart);
|
|
1567
|
+
const visibleGroupEnd = Math.min(groupEnd, clipEnd);
|
|
1568
|
+
return {
|
|
1569
|
+
href,
|
|
1570
|
+
text: textParts.join(""),
|
|
1571
|
+
rect: {
|
|
1572
|
+
x: r.x + visibleGroupStart - clipStart,
|
|
1573
|
+
y: rect.y,
|
|
1574
|
+
w: visibleGroupEnd - visibleGroupStart,
|
|
1575
|
+
h: 1
|
|
1576
|
+
},
|
|
1577
|
+
cellX: point.cellX,
|
|
1578
|
+
cellY: point.cellY,
|
|
1579
|
+
rowIndex,
|
|
1580
|
+
segmentIndex
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return null;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
//#endregion
|
|
1588
|
+
//#region src/vue/markdown/math-actions.ts
|
|
1589
|
+
function findMarkdownMathActionAt(rows, point, options) {
|
|
1590
|
+
const r = options.screenRect;
|
|
1591
|
+
if (r.w <= 0 || r.h <= 0) return null;
|
|
1592
|
+
if (point.cellX < r.x || point.cellY < r.y || point.cellX >= r.x + r.w || point.cellY >= r.y + r.h) return null;
|
|
1593
|
+
const clipStart = Math.max(0, Math.floor(options.clipStart));
|
|
1594
|
+
const clipEnd = clipStart + r.w;
|
|
1595
|
+
const firstRow = Math.max(0, Math.floor(options.rowOffset));
|
|
1596
|
+
const lastRow = Math.min(rows.length, firstRow + r.h);
|
|
1597
|
+
for (let rowIndex = firstRow; rowIndex < lastRow; rowIndex++) {
|
|
1598
|
+
const row = rows[rowIndex];
|
|
1599
|
+
if (!row) continue;
|
|
1600
|
+
let logicalX = 0;
|
|
1601
|
+
for (let segmentIndex = 0; segmentIndex < row.segments.length; segmentIndex++) {
|
|
1602
|
+
const segment = row.segments[segmentIndex];
|
|
1603
|
+
const segmentStart = logicalX;
|
|
1604
|
+
const segmentEnd = logicalX + segment.cells;
|
|
1605
|
+
logicalX = segmentEnd;
|
|
1606
|
+
const math = segment.mathAction;
|
|
1607
|
+
if (!math || segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
|
|
1608
|
+
const visibleStart = Math.max(segmentStart, clipStart);
|
|
1609
|
+
const visibleEnd = Math.min(segmentEnd, clipEnd);
|
|
1610
|
+
const rect = {
|
|
1611
|
+
x: r.x + visibleStart - clipStart,
|
|
1612
|
+
y: r.y + rowIndex - firstRow,
|
|
1613
|
+
w: visibleEnd - visibleStart,
|
|
1614
|
+
h: 1
|
|
1615
|
+
};
|
|
1616
|
+
if (point.cellX < rect.x || point.cellY < rect.y || point.cellX >= rect.x + rect.w || point.cellY >= rect.y + rect.h) continue;
|
|
1617
|
+
return {
|
|
1618
|
+
math,
|
|
1619
|
+
rect,
|
|
1620
|
+
cellX: point.cellX,
|
|
1621
|
+
cellY: point.cellY,
|
|
1622
|
+
rowIndex,
|
|
1623
|
+
segmentIndex
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
969
1630
|
//#endregion
|
|
970
1631
|
//#region src/vue/markdown/render.ts
|
|
971
1632
|
const mergedStyleCache = new WeakMap();
|
|
1633
|
+
const markdownImageDebugLog = createDebugLogger(isDebugEnabled());
|
|
1634
|
+
const markdownImageDebug = (...parts) => {
|
|
1635
|
+
if (!isDebugEnabled()) return;
|
|
1636
|
+
markdownImageDebugLog.render(`markdown image ${parts.join(" ")}`);
|
|
1637
|
+
};
|
|
972
1638
|
function mergeStyle(base, overlay) {
|
|
973
1639
|
if (!overlay) return base;
|
|
974
1640
|
let bucket = mergedStyleCache.get(base);
|
|
@@ -985,20 +1651,153 @@ function mergeStyle(base, overlay) {
|
|
|
985
1651
|
bucket.set(overlay, merged);
|
|
986
1652
|
return merged;
|
|
987
1653
|
}
|
|
1654
|
+
const activeMarkdownImageGraphics = new WeakMap();
|
|
1655
|
+
function markdownImageGraphicId(segment, identity) {
|
|
1656
|
+
return `md-image:${stableTerminalGraphicNumericId(`${identity}:${segment.src}:${segment.displayWidth ?? 0}:${segment.displayHeight ?? 0}:${segment.base64 ?? ""}`)}`;
|
|
1657
|
+
}
|
|
1658
|
+
function rememberMarkdownImageGraphic(terminal, id, rect) {
|
|
1659
|
+
let active = activeMarkdownImageGraphics.get(terminal);
|
|
1660
|
+
if (!active) {
|
|
1661
|
+
active = new Map();
|
|
1662
|
+
activeMarkdownImageGraphics.set(terminal, active);
|
|
1663
|
+
}
|
|
1664
|
+
active.set(id, rect);
|
|
1665
|
+
}
|
|
1666
|
+
function clearTrackedMarkdownImageGraphic(terminal, id) {
|
|
1667
|
+
activeMarkdownImageGraphics.get(terminal)?.delete(id);
|
|
1668
|
+
try {
|
|
1669
|
+
getTerminalGraphicsOutput(terminal)?.clear?.(id);
|
|
1670
|
+
} catch {}
|
|
1671
|
+
}
|
|
1672
|
+
function clearStaleMarkdownImageGraphicsForRow(terminal, y, keepIds, keepVisibleIds) {
|
|
1673
|
+
const active = activeMarkdownImageGraphics.get(terminal);
|
|
1674
|
+
if (!active?.size) return;
|
|
1675
|
+
for (const [id, rect] of active) {
|
|
1676
|
+
const intersectsRow = keepVisibleIds ? y >= rect.y && y < rect.y + rect.h : rect.y === y;
|
|
1677
|
+
if (!intersectsRow || keepIds.has(id) || keepVisibleIds?.has(id)) continue;
|
|
1678
|
+
clearTrackedMarkdownImageGraphic(terminal, id);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
function canAttemptMarkdownImageGraphic(terminal, segment) {
|
|
1682
|
+
if (segment.kind !== "image" || !segment.base64) return false;
|
|
1683
|
+
const output = getTerminalGraphicsOutput(terminal);
|
|
1684
|
+
const protocol = output?.capabilities.preferredProtocol;
|
|
1685
|
+
return Boolean(output?.capabilities.supported && isTerminalGraphicsProtocol(protocol));
|
|
1686
|
+
}
|
|
1687
|
+
function clearMarkdownImageGraphics(terminal, rect) {
|
|
1688
|
+
const active = activeMarkdownImageGraphics.get(terminal);
|
|
1689
|
+
if (!active?.size) return;
|
|
1690
|
+
for (const [id, item] of active) {
|
|
1691
|
+
if (rect && (item.x >= rect.x + rect.w || item.x + item.w <= rect.x || item.y >= rect.y + rect.h || item.y + item.h <= rect.y)) continue;
|
|
1692
|
+
clearTrackedMarkdownImageGraphic(terminal, id);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
function queueMarkdownImageGraphic(terminal, segment, identity, rect) {
|
|
1696
|
+
if (segment.kind !== "image" || !segment.base64) return "unavailable";
|
|
1697
|
+
const output = getTerminalGraphicsOutput(terminal);
|
|
1698
|
+
const protocol = output?.capabilities.preferredProtocol;
|
|
1699
|
+
if (!output?.capabilities.supported || !isTerminalGraphicsProtocol(protocol)) return "unavailable";
|
|
1700
|
+
const width = Math.max(1, Math.floor(segment.displayWidth ?? rect.w));
|
|
1701
|
+
const height = Math.max(1, Math.floor(segment.displayHeight ?? 1));
|
|
1702
|
+
const id = markdownImageGraphicId(segment, identity);
|
|
1703
|
+
const imageId = stableTerminalGraphicNumericId(`image:${id}`);
|
|
1704
|
+
const placementId = stableTerminalGraphicNumericId(`placement:${id}`);
|
|
1705
|
+
const sequence = protocol === "kitty" ? createKittyGraphicsSequence(segment.base64, {
|
|
1706
|
+
imageId,
|
|
1707
|
+
placementId,
|
|
1708
|
+
columns: width,
|
|
1709
|
+
rows: height,
|
|
1710
|
+
zIndex: -1
|
|
1711
|
+
}) : protocol === "iterm2" ? createIterm2InlineImageSequence(segment.base64, {
|
|
1712
|
+
width,
|
|
1713
|
+
height,
|
|
1714
|
+
preserveAspectRatio: true,
|
|
1715
|
+
doNotMoveCursor: true
|
|
1716
|
+
}) : "";
|
|
1717
|
+
if (!sequence) return "unavailable";
|
|
1718
|
+
const clearSequence = protocol === "kitty" ? createKittyDeleteGraphicsSequence({
|
|
1719
|
+
imageId,
|
|
1720
|
+
placementId
|
|
1721
|
+
}) : void 0;
|
|
1722
|
+
const resizeSequence = protocol === "kitty" ? createKittyPlacementSequence({
|
|
1723
|
+
imageId,
|
|
1724
|
+
placementId,
|
|
1725
|
+
columns: width,
|
|
1726
|
+
rows: height
|
|
1727
|
+
}) : void 0;
|
|
1728
|
+
const accepted = output.queue({
|
|
1729
|
+
id,
|
|
1730
|
+
x: rect.x,
|
|
1731
|
+
y: rect.y,
|
|
1732
|
+
w: width,
|
|
1733
|
+
h: height,
|
|
1734
|
+
protocol,
|
|
1735
|
+
sequence,
|
|
1736
|
+
resizeSequence,
|
|
1737
|
+
clearSequence,
|
|
1738
|
+
fallbackText: segment.alt ?? "image"
|
|
1739
|
+
});
|
|
1740
|
+
markdownImageDebug(`queue id=${id}`, `accepted=${accepted ? "true" : "false"}`, `x=${rect.x}`, `y=${rect.y}`, `w=${width}`, `h=${height}`, `visibleW=${rect.w}`, `displayW=${segment.displayWidth ?? ""}`, `displayH=${segment.displayHeight ?? ""}`);
|
|
1741
|
+
if (accepted) rememberMarkdownImageGraphic(terminal, id, {
|
|
1742
|
+
x: rect.x,
|
|
1743
|
+
y: rect.y,
|
|
1744
|
+
w: width,
|
|
1745
|
+
h: height
|
|
1746
|
+
});
|
|
1747
|
+
return accepted ? "queued" : "rejected";
|
|
1748
|
+
}
|
|
1749
|
+
function collectVisibleMarkdownImageGraphicIds(rows, options) {
|
|
1750
|
+
const keepIds = new Set();
|
|
1751
|
+
if (options.w <= 0 || options.h <= 0) return keepIds;
|
|
1752
|
+
const clipStart = Math.max(0, Math.floor(options.clipStart));
|
|
1753
|
+
const clipEnd = clipStart + options.w;
|
|
1754
|
+
const firstRow = Math.min(rows.length, Math.max(0, Math.floor(options.rowOffset)));
|
|
1755
|
+
const lastRow = Math.min(rows.length, firstRow + Math.max(0, Math.floor(options.h)));
|
|
1756
|
+
let scanStart = firstRow;
|
|
1757
|
+
while (scanStart > 0 && (rows[scanStart]?.segments.length ?? 0) === 0) scanStart--;
|
|
1758
|
+
for (let rowIndex = scanStart; rowIndex < lastRow; rowIndex++) {
|
|
1759
|
+
const row = rows[rowIndex];
|
|
1760
|
+
if (!row) continue;
|
|
1761
|
+
let logicalX = 0;
|
|
1762
|
+
for (const [segmentIndex, segment] of row.segments.entries()) {
|
|
1763
|
+
const segmentStart = logicalX;
|
|
1764
|
+
const segmentEnd = logicalX + segment.cells;
|
|
1765
|
+
logicalX = segmentEnd;
|
|
1766
|
+
if (!segment.graphic) continue;
|
|
1767
|
+
if (segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
|
|
1768
|
+
if (Math.max(segmentStart, clipStart) !== segmentStart) continue;
|
|
1769
|
+
const height = Math.max(1, Math.floor(segment.graphic.displayHeight ?? 1));
|
|
1770
|
+
if (rowIndex + height <= firstRow || rowIndex >= lastRow) continue;
|
|
1771
|
+
const rect = {
|
|
1772
|
+
x: options.x + segmentStart - clipStart,
|
|
1773
|
+
y: options.y + rowIndex - firstRow,
|
|
1774
|
+
w: Math.max(1, Math.floor(segment.graphic.displayWidth ?? segment.cells)),
|
|
1775
|
+
h: height
|
|
1776
|
+
};
|
|
1777
|
+
if (options.isGraphicCovered?.(rect)) continue;
|
|
1778
|
+
keepIds.add(markdownImageGraphicId(segment.graphic, `${row.key}:${segmentIndex}`));
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
return keepIds;
|
|
1782
|
+
}
|
|
988
1783
|
function paintMarkdownVisualRow(terminal, row, options) {
|
|
989
1784
|
const clipStart = Math.max(0, Math.floor(options.clipStart ?? 0));
|
|
990
1785
|
const clipEnd = clipStart + options.w;
|
|
991
1786
|
if (!row) {
|
|
992
|
-
if (options.clear !== false)
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1787
|
+
if (options.clear !== false) {
|
|
1788
|
+
terminal.write(spaces(options.w), {
|
|
1789
|
+
x: options.x,
|
|
1790
|
+
y: options.y,
|
|
1791
|
+
style: options.baseStyle
|
|
1792
|
+
});
|
|
1793
|
+
clearStaleMarkdownImageGraphicsForRow(terminal, options.y, new Set(), options.keepGraphicIds);
|
|
1794
|
+
}
|
|
997
1795
|
return;
|
|
998
1796
|
}
|
|
999
1797
|
let logicalX = 0;
|
|
1000
1798
|
let used = 0;
|
|
1001
|
-
|
|
1799
|
+
const queuedGraphics = new Set();
|
|
1800
|
+
for (const [segmentIndex, segment] of row.segments.entries()) {
|
|
1002
1801
|
if (used >= options.w || !segment.text) {
|
|
1003
1802
|
logicalX += segment.cells;
|
|
1004
1803
|
continue;
|
|
@@ -1008,6 +1807,46 @@ function paintMarkdownVisualRow(terminal, row, options) {
|
|
|
1008
1807
|
logicalX += segment.cells;
|
|
1009
1808
|
if (segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
|
|
1010
1809
|
const segmentStyle = mergeStyle(options.baseStyle, segment.style);
|
|
1810
|
+
if (segment.graphic) {
|
|
1811
|
+
const visibleStart = Math.max(segmentStart, clipStart);
|
|
1812
|
+
const visibleEnd = Math.min(segmentEnd, clipEnd);
|
|
1813
|
+
const visibleCells = visibleEnd - visibleStart;
|
|
1814
|
+
if (visibleCells <= 0) continue;
|
|
1815
|
+
const expectedUsed = visibleStart - clipStart;
|
|
1816
|
+
if (expectedUsed > used) {
|
|
1817
|
+
const pad = Math.min(expectedUsed - used, options.w - used);
|
|
1818
|
+
terminal.write(spaces(pad), {
|
|
1819
|
+
x: options.x + used,
|
|
1820
|
+
y: options.y,
|
|
1821
|
+
style: options.baseStyle
|
|
1822
|
+
});
|
|
1823
|
+
used += pad;
|
|
1824
|
+
}
|
|
1825
|
+
const cellX = options.x + used;
|
|
1826
|
+
const graphicRect = {
|
|
1827
|
+
x: cellX,
|
|
1828
|
+
y: options.y,
|
|
1829
|
+
w: Math.max(1, Math.floor(segment.graphic.displayWidth ?? visibleCells)),
|
|
1830
|
+
h: Math.max(1, Math.floor(segment.graphic.displayHeight ?? 1))
|
|
1831
|
+
};
|
|
1832
|
+
const graphicCovered = options.isGraphicCovered?.(graphicRect) === true;
|
|
1833
|
+
const queueResult = !graphicCovered && visibleStart === segmentStart && visibleCells > 0 && queueMarkdownImageGraphic(terminal, segment.graphic, `${row.key}:${segmentIndex}`, {
|
|
1834
|
+
x: cellX,
|
|
1835
|
+
y: options.y,
|
|
1836
|
+
w: visibleCells
|
|
1837
|
+
});
|
|
1838
|
+
const graphicAvailable = queueResult || canAttemptMarkdownImageGraphic(terminal, segment.graphic);
|
|
1839
|
+
const queuedGraphic = queueResult === "queued";
|
|
1840
|
+
const suppressFallback = queuedGraphic || queueResult === "rejected" || graphicAvailable === true;
|
|
1841
|
+
if (queuedGraphic) queuedGraphics.add(markdownImageGraphicId(segment.graphic, `${row.key}:${segmentIndex}`));
|
|
1842
|
+
terminal.write(suppressFallback ? spaces(visibleCells) : sliceByCellsRange(segment.fallbackText ?? segment.graphic.alt ?? "image", 0, visibleCells) || spaces(visibleCells), {
|
|
1843
|
+
x: cellX,
|
|
1844
|
+
y: options.y,
|
|
1845
|
+
style: suppressFallback ? options.baseStyle : segmentStyle
|
|
1846
|
+
});
|
|
1847
|
+
used += visibleCells;
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1011
1850
|
let pieceStart = segmentStart;
|
|
1012
1851
|
forEachTextCellSegment(segment.text, (piece) => {
|
|
1013
1852
|
if (used >= options.w) return false;
|
|
@@ -1037,7 +1876,8 @@ function paintMarkdownVisualRow(terminal, row, options) {
|
|
|
1037
1876
|
}
|
|
1038
1877
|
const pad = Math.min(visibleEnd - visibleStart, options.w - used);
|
|
1039
1878
|
if (pad > 0) {
|
|
1040
|
-
|
|
1879
|
+
const fallback = sliceByCellsRange(piece.text, visibleStart - (pieceStart - piece.cells), visibleEnd - (pieceStart - piece.cells));
|
|
1880
|
+
terminal.write(fallback || spaces(pad), {
|
|
1041
1881
|
x: options.x + used,
|
|
1042
1882
|
y: options.y,
|
|
1043
1883
|
style: segmentStyle
|
|
@@ -1046,6 +1886,7 @@ function paintMarkdownVisualRow(terminal, row, options) {
|
|
|
1046
1886
|
}
|
|
1047
1887
|
});
|
|
1048
1888
|
}
|
|
1889
|
+
clearStaleMarkdownImageGraphicsForRow(terminal, options.y, queuedGraphics, options.keepGraphicIds);
|
|
1049
1890
|
if (used < options.w && options.clear !== false) terminal.write(spaces(options.w - used), {
|
|
1050
1891
|
x: options.x + used,
|
|
1051
1892
|
y: options.y,
|
|
@@ -1076,7 +1917,7 @@ function markdownRowSignature(row) {
|
|
|
1076
1917
|
return [
|
|
1077
1918
|
row.key,
|
|
1078
1919
|
row.plainText,
|
|
1079
|
-
row.segments.map((segment) => `${segment.text}\u0001${segment.cells}\u0001${markdownStyleSignature(segment.style)}`).join("")
|
|
1920
|
+
row.segments.map((segment) => `${segment.text}\u0001${segment.cells}\u0001${markdownStyleSignature(segment.style)}\u0001${segment.graphic?.src ?? ""}\u0001${segment.graphic?.base64 ?? ""}\u0001${segment.fallbackText ?? ""}`).join("")
|
|
1080
1921
|
].join("");
|
|
1081
1922
|
}
|
|
1082
1923
|
function getWheelScrollInput(e) {
|
|
@@ -1099,6 +1940,10 @@ function getWheelScrollInput(e) {
|
|
|
1099
1940
|
mode: "auto"
|
|
1100
1941
|
};
|
|
1101
1942
|
}
|
|
1943
|
+
function rowGraphicSignature(row) {
|
|
1944
|
+
if (!row) return "";
|
|
1945
|
+
return row.segments.map((segment) => segment.graphic ? `${segment.graphic.kind}\u0001${segment.graphic.src}\u0001${segment.graphic.base64 ?? ""}` : "").join("");
|
|
1946
|
+
}
|
|
1102
1947
|
const TVirtualMarkdown = defineComponent({
|
|
1103
1948
|
name: "TVirtualMarkdown",
|
|
1104
1949
|
props: {
|
|
@@ -1161,15 +2006,58 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1161
2006
|
theme: {
|
|
1162
2007
|
type: Object,
|
|
1163
2008
|
default: void 0
|
|
2009
|
+
},
|
|
2010
|
+
imageRenderer: {
|
|
2011
|
+
type: Function,
|
|
2012
|
+
default: void 0
|
|
2013
|
+
},
|
|
2014
|
+
imageMinWidth: {
|
|
2015
|
+
type: Number,
|
|
2016
|
+
default: void 0
|
|
2017
|
+
},
|
|
2018
|
+
imageMaxWidth: {
|
|
2019
|
+
type: Number,
|
|
2020
|
+
default: void 0
|
|
2021
|
+
},
|
|
2022
|
+
imageMinHeight: {
|
|
2023
|
+
type: Number,
|
|
2024
|
+
default: void 0
|
|
2025
|
+
},
|
|
2026
|
+
imageMaxHeight: {
|
|
2027
|
+
type: Number,
|
|
2028
|
+
default: void 0
|
|
2029
|
+
},
|
|
2030
|
+
imagePreserveAspectRatio: {
|
|
2031
|
+
type: Boolean,
|
|
2032
|
+
default: true
|
|
2033
|
+
},
|
|
2034
|
+
imageActions: {
|
|
2035
|
+
type: Boolean,
|
|
2036
|
+
default: false
|
|
2037
|
+
},
|
|
2038
|
+
mathActions: {
|
|
2039
|
+
type: Boolean,
|
|
2040
|
+
default: false
|
|
2041
|
+
},
|
|
2042
|
+
linkActions: {
|
|
2043
|
+
type: Boolean,
|
|
2044
|
+
default: false
|
|
2045
|
+
},
|
|
2046
|
+
imageOcclusionRects: {
|
|
2047
|
+
type: Array,
|
|
2048
|
+
default: void 0
|
|
1164
2049
|
}
|
|
1165
2050
|
},
|
|
1166
|
-
emits:
|
|
1167
|
-
"update:scrollTop",
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
2051
|
+
emits: {
|
|
2052
|
+
"update:scrollTop": (_value) => true,
|
|
2053
|
+
scroll: (_value) => true,
|
|
2054
|
+
focus: () => true,
|
|
2055
|
+
blur: () => true,
|
|
2056
|
+
keydown: (_event) => true,
|
|
2057
|
+
imageAction: (_payload) => true,
|
|
2058
|
+
mathAction: (_payload) => true,
|
|
2059
|
+
linkAction: (_payload) => true
|
|
2060
|
+
},
|
|
1173
2061
|
setup(props, { emit }) {
|
|
1174
2062
|
const instance = getCurrentInstance();
|
|
1175
2063
|
const { terminal, defaultStyle, events, scheduler, selection, widthProvider } = useTerminal();
|
|
@@ -1190,6 +2078,10 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1190
2078
|
streaming: props.streaming,
|
|
1191
2079
|
customHtmlTags: props.customHtmlTags
|
|
1192
2080
|
})));
|
|
2081
|
+
const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
|
|
2082
|
+
const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
|
|
2083
|
+
graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
|
|
2084
|
+
});
|
|
1193
2085
|
watch(() => `${props.streaming ? 1 : 0}:${(props.customHtmlTags ?? []).join("\0")}`, () => {
|
|
1194
2086
|
parser.value = markRaw(createTuiMarkdownParser({
|
|
1195
2087
|
streaming: props.streaming,
|
|
@@ -1199,10 +2091,19 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1199
2091
|
function rebuildRows() {
|
|
1200
2092
|
const prevScrollTop = internalScrollTop.value;
|
|
1201
2093
|
const prevVisibleRows = visibleRowSignatures(rows.value, prevScrollTop);
|
|
2094
|
+
const prevVisibleGraphics = visibleRowGraphics(rows.value, prevScrollTop);
|
|
1202
2095
|
const nextLayout = withTextWidthProvider(widthProvider, () => {
|
|
1203
2096
|
const blocks = props.blocks ?? buildMarkdownBlocks(props.content, parser.value, {
|
|
1204
2097
|
final: props.final,
|
|
1205
|
-
theme: props.theme
|
|
2098
|
+
theme: props.theme,
|
|
2099
|
+
imageResolver: props.imageRenderer,
|
|
2100
|
+
imageSize: {
|
|
2101
|
+
minWidth: props.imageMinWidth,
|
|
2102
|
+
maxWidth: props.imageMaxWidth,
|
|
2103
|
+
minHeight: props.imageMinHeight,
|
|
2104
|
+
maxHeight: props.imageMaxHeight,
|
|
2105
|
+
preserveAspectRatio: props.imagePreserveAspectRatio
|
|
2106
|
+
}
|
|
1206
2107
|
}).blocks;
|
|
1207
2108
|
return layoutMarkdownBlocksCached(blocks, props.w, layoutCache);
|
|
1208
2109
|
});
|
|
@@ -1212,7 +2113,8 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1212
2113
|
reconcileScrollTop();
|
|
1213
2114
|
const nextScrollTop = internalScrollTop.value;
|
|
1214
2115
|
const nextVisibleRows = visibleRowSignatures(nextRows, nextScrollTop);
|
|
1215
|
-
const
|
|
2116
|
+
const nextVisibleGraphics = visibleRowGraphics(nextRows, nextScrollTop);
|
|
2117
|
+
const visibleChanged = prevVisibleRows.length !== nextVisibleRows.length || prevVisibleRows.some((row, index) => row !== nextVisibleRows[index]) || prevVisibleGraphics.some((row, index) => row !== nextVisibleGraphics[index]);
|
|
1216
2118
|
if (!builtOnce || prevScrollTop !== nextScrollTop || visibleChanged) {
|
|
1217
2119
|
documentVersion.value++;
|
|
1218
2120
|
selection.refresh();
|
|
@@ -1281,6 +2183,15 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1281
2183
|
for (let index = start; index < end; index++) out.push(markdownRowSignature(sourceRows[index]));
|
|
1282
2184
|
return out;
|
|
1283
2185
|
}
|
|
2186
|
+
function visibleRowGraphics(sourceRows, scrollTop) {
|
|
2187
|
+
const clip = normalizedRect();
|
|
2188
|
+
const { y: clipY } = clipOffsets();
|
|
2189
|
+
const start = Math.max(0, scrollTop + clipY);
|
|
2190
|
+
const end = Math.max(start, start + clip.h);
|
|
2191
|
+
const out = [];
|
|
2192
|
+
for (let index = start; index < end; index++) out.push(rowGraphicSignature(sourceRows[index]));
|
|
2193
|
+
return out;
|
|
2194
|
+
}
|
|
1284
2195
|
function hasControlledScrollTop() {
|
|
1285
2196
|
return Object.prototype.hasOwnProperty.call(instance?.vnode.props ?? {}, "scrollTop");
|
|
1286
2197
|
}
|
|
@@ -1329,7 +2240,13 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1329
2240
|
() => props.w,
|
|
1330
2241
|
() => parser.value,
|
|
1331
2242
|
() => props.final,
|
|
1332
|
-
() =>
|
|
2243
|
+
() => props.imageRenderer,
|
|
2244
|
+
() => markdownThemeSignature(props.theme),
|
|
2245
|
+
() => props.imageMinWidth,
|
|
2246
|
+
() => props.imageMaxWidth,
|
|
2247
|
+
() => props.imageMinHeight,
|
|
2248
|
+
() => props.imageMaxHeight,
|
|
2249
|
+
() => props.imagePreserveAspectRatio
|
|
1333
2250
|
], () => {
|
|
1334
2251
|
scheduleRebuild();
|
|
1335
2252
|
}, { immediate: true });
|
|
@@ -1462,7 +2379,45 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1462
2379
|
selectable: props.selectable,
|
|
1463
2380
|
selectionScrollBy: scrollSelectionBy,
|
|
1464
2381
|
handlers: {
|
|
1465
|
-
click: (
|
|
2382
|
+
click: (event) => {
|
|
2383
|
+
const r = normalizedRect();
|
|
2384
|
+
const { x: clipX, y: clipY } = clipOffsets();
|
|
2385
|
+
const hitOptions = {
|
|
2386
|
+
screenRect: r,
|
|
2387
|
+
rowOffset: internalScrollTop.value + clipY,
|
|
2388
|
+
clipStart: clipX
|
|
2389
|
+
};
|
|
2390
|
+
if (props.imageActions) {
|
|
2391
|
+
const hit = findMarkdownImageActionAt(rows.value, {
|
|
2392
|
+
cellX: event.cellX,
|
|
2393
|
+
cellY: event.cellY
|
|
2394
|
+
}, hitOptions);
|
|
2395
|
+
if (hit) {
|
|
2396
|
+
event.preventDefault();
|
|
2397
|
+
emit("imageAction", hit);
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
if (props.mathActions) {
|
|
2402
|
+
const math = findMarkdownMathActionAt(rows.value, {
|
|
2403
|
+
cellX: event.cellX,
|
|
2404
|
+
cellY: event.cellY
|
|
2405
|
+
}, hitOptions);
|
|
2406
|
+
if (math) {
|
|
2407
|
+
event.preventDefault();
|
|
2408
|
+
emit("mathAction", math);
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
if (!props.linkActions) return;
|
|
2413
|
+
const link = findMarkdownLinkActionAt(rows.value, {
|
|
2414
|
+
cellX: event.cellX,
|
|
2415
|
+
cellY: event.cellY
|
|
2416
|
+
}, hitOptions);
|
|
2417
|
+
if (!link) return;
|
|
2418
|
+
event.preventDefault();
|
|
2419
|
+
emit("linkAction", link);
|
|
2420
|
+
},
|
|
1466
2421
|
wheel: (event) => {
|
|
1467
2422
|
const { deltaY, mode } = getWheelScrollInput(event);
|
|
1468
2423
|
if (!deltaY) return;
|
|
@@ -1493,6 +2448,8 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1493
2448
|
onBeforeUnmount(() => {
|
|
1494
2449
|
alive = false;
|
|
1495
2450
|
rebuildVersion++;
|
|
2451
|
+
unsubscribeGraphicsOutput();
|
|
2452
|
+
clearMarkdownImageGraphics(terminal, fullRect.value);
|
|
1496
2453
|
});
|
|
1497
2454
|
useRenderNode(() => ({
|
|
1498
2455
|
zIndex: props.zIndex,
|
|
@@ -1509,7 +2466,9 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1509
2466
|
internalScrollTop.value,
|
|
1510
2467
|
props.style,
|
|
1511
2468
|
defaultStyle.value,
|
|
1512
|
-
documentVersion.value
|
|
2469
|
+
documentVersion.value,
|
|
2470
|
+
graphicsOutputVersion.value,
|
|
2471
|
+
props.imageOcclusionRects
|
|
1513
2472
|
],
|
|
1514
2473
|
paint: (dirtyRows) => {
|
|
1515
2474
|
withTextWidthProvider(widthProvider, () => {
|
|
@@ -1518,6 +2477,18 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1518
2477
|
if (r.w <= 0 || r.h <= 0) return;
|
|
1519
2478
|
const baseStyle = props.style ?? defaultStyle.value;
|
|
1520
2479
|
const { x: clipX, y: clipY } = clipOffsets();
|
|
2480
|
+
const isGraphicCovered = (rect) => {
|
|
2481
|
+
return props.imageOcclusionRects?.some((item) => intersectRect(rect, item)) === true;
|
|
2482
|
+
};
|
|
2483
|
+
const keepGraphicIds = collectVisibleMarkdownImageGraphicIds(rows.value, {
|
|
2484
|
+
x: r.x,
|
|
2485
|
+
y: r.y,
|
|
2486
|
+
w: r.w,
|
|
2487
|
+
h: r.h,
|
|
2488
|
+
rowOffset: internalScrollTop.value + clipY,
|
|
2489
|
+
clipStart: clipX,
|
|
2490
|
+
isGraphicCovered
|
|
2491
|
+
});
|
|
1521
2492
|
const paintRow = (y) => {
|
|
1522
2493
|
if (y < r.y || y >= r.y + r.h) return;
|
|
1523
2494
|
const visualIndex = internalScrollTop.value + clipY + (y - r.y);
|
|
@@ -1527,7 +2498,9 @@ const TVirtualMarkdown = defineComponent({
|
|
|
1527
2498
|
w: r.w,
|
|
1528
2499
|
clipStart: clipX,
|
|
1529
2500
|
baseStyle,
|
|
1530
|
-
clear: true
|
|
2501
|
+
clear: true,
|
|
2502
|
+
keepGraphicIds,
|
|
2503
|
+
isGraphicCovered
|
|
1531
2504
|
});
|
|
1532
2505
|
};
|
|
1533
2506
|
if (dirtyRows?.length) {
|
|
@@ -1660,4 +2633,4 @@ function createMarkdownBlockSource(options) {
|
|
|
1660
2633
|
}
|
|
1661
2634
|
|
|
1662
2635
|
//#endregion
|
|
1663
|
-
export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, createMarkdownBlockSource, createTuiMarkdownParser, isSafeMarkdownLink, layoutMarkdownBlocks, markdownThemeSignature, paintMarkdownVisualRow };
|
|
2636
|
+
export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownImageGraphics, collectVisibleMarkdownImageGraphicIds, createMarkdownBlockSource, createTuiMarkdownParser, findMarkdownImageActionAt, findMarkdownLinkActionAt, findMarkdownMathActionAt, isSafeMarkdownLink, layoutMarkdownBlocks, markdownThemeSignature, paintMarkdownVisualRow };
|