pi-studio 0.9.16 → 0.9.18
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/CHANGELOG.md +20 -0
- package/README.md +5 -5
- package/client/studio-client.js +1000 -112
- package/client/studio.css +227 -11
- package/index.ts +389 -47
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -115,6 +115,8 @@
|
|
|
115
115
|
const replSendModeSelect = document.getElementById("replSendModeSelect");
|
|
116
116
|
const copyDraftBtn = document.getElementById("copyDraftBtn");
|
|
117
117
|
const suggestCompletionBtn = document.getElementById("suggestCompletionBtn");
|
|
118
|
+
const suggestCompletionOptionsBtn = document.getElementById("suggestCompletionOptionsBtn");
|
|
119
|
+
const completionContextSelect = document.getElementById("completionContextSelect");
|
|
118
120
|
const completionSuggestionPanelEl = document.getElementById("completionSuggestionPanel");
|
|
119
121
|
const completionSuggestionTextEl = document.getElementById("completionSuggestionText");
|
|
120
122
|
const completionSuggestionInsertBtn = document.getElementById("completionSuggestionInsertBtn");
|
|
@@ -129,6 +131,7 @@
|
|
|
129
131
|
const shortcutsBtn = document.getElementById("shortcutsBtn");
|
|
130
132
|
const shortcutsOverlayEl = document.getElementById("shortcutsOverlay");
|
|
131
133
|
const shortcutsDialogEl = document.getElementById("shortcutsDialog");
|
|
134
|
+
const shortcutsBodyEl = document.getElementById("shortcutsBody");
|
|
132
135
|
const shortcutsCloseBtn = document.getElementById("shortcutsCloseBtn");
|
|
133
136
|
const leftFocusBtn = document.getElementById("leftFocusBtn");
|
|
134
137
|
const rightFocusBtn = document.getElementById("rightFocusBtn");
|
|
@@ -209,6 +212,18 @@
|
|
|
209
212
|
let studioHtmlFocusFullscreenBtn = null;
|
|
210
213
|
let studioHtmlFocusLastFocusedEl = null;
|
|
211
214
|
let studioHtmlFocusRestoreState = null;
|
|
215
|
+
let studioImageFocusOverlayEl = null;
|
|
216
|
+
let studioImageFocusDialogEl = null;
|
|
217
|
+
let studioImageFocusSlotEl = null;
|
|
218
|
+
let studioImageFocusImgEl = null;
|
|
219
|
+
let studioImageFocusTitleEl = null;
|
|
220
|
+
let studioImageFocusOpenLinkEl = null;
|
|
221
|
+
let studioImageFocusFullscreenBtn = null;
|
|
222
|
+
let studioImageFocusCloseBtn = null;
|
|
223
|
+
let studioImageFocusZoomLabelEl = null;
|
|
224
|
+
let studioImageFocusLastFocusedEl = null;
|
|
225
|
+
let studioImageFocusZoomMode = "fit";
|
|
226
|
+
let studioImageFocusZoom = 1;
|
|
212
227
|
let pendingRequestId = null;
|
|
213
228
|
let pendingKind = null;
|
|
214
229
|
let stickyStudioKind = null;
|
|
@@ -256,6 +271,8 @@
|
|
|
256
271
|
const HTML_ARTIFACT_RESOURCE_FETCH_TIMEOUT_MS = 30_000;
|
|
257
272
|
const EDITOR_TAB_TEXT = " ";
|
|
258
273
|
const QUIZ_DEFAULT_COUNT = 5;
|
|
274
|
+
const COMPLETION_CONTEXT_STORAGE_KEY = "piStudio.completionContextMode";
|
|
275
|
+
const COMPLETION_CONTEXT_MAX_CHARS = 12000;
|
|
259
276
|
const QUIZ_SCOPES = ["editor", "selection", "file", "folder", "repo"];
|
|
260
277
|
const QUIZ_ANGLES = ["general", "scientist", "mathematician", "statistician", "developer", "reviewer"];
|
|
261
278
|
const QUIZ_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
@@ -1910,6 +1927,8 @@
|
|
|
1910
1927
|
matlab: { label: "MATLAB", exts: ["m"] },
|
|
1911
1928
|
latex: { label: "LaTeX", exts: ["tex", "latex"] },
|
|
1912
1929
|
diff: { label: "Diff", exts: ["diff", "patch"] },
|
|
1930
|
+
csv: { label: "CSV", exts: ["csv"] },
|
|
1931
|
+
tsv: { label: "TSV", exts: ["tsv"] },
|
|
1913
1932
|
// Languages accepted for upload/detect but without syntax highlighting
|
|
1914
1933
|
java: { label: "Java", exts: ["java"] },
|
|
1915
1934
|
go: { label: "Go", exts: ["go"] },
|
|
@@ -1937,6 +1956,9 @@
|
|
|
1937
1956
|
const ANNOTATION_MODE_STORAGE_KEY = "piStudio.annotationsEnabled";
|
|
1938
1957
|
const PREVIEW_INPUT_DEBOUNCE_MS = 0;
|
|
1939
1958
|
const PREVIEW_PENDING_BADGE_DELAY_MS = 220;
|
|
1959
|
+
const DELIMITED_PREVIEW_MAX_DATA_ROWS = 200;
|
|
1960
|
+
const DELIMITED_PREVIEW_MAX_COLUMNS = 50;
|
|
1961
|
+
const DELIMITED_PREVIEW_MAX_CELL_CHARS = 500;
|
|
1940
1962
|
const previewPendingTimers = new WeakMap();
|
|
1941
1963
|
const htmlArtifactFramesById = new Map();
|
|
1942
1964
|
let sourcePreviewRenderTimer = null;
|
|
@@ -1949,6 +1971,7 @@
|
|
|
1949
1971
|
let editorLanguage = "markdown";
|
|
1950
1972
|
let responseHighlightEnabled = false;
|
|
1951
1973
|
let completionSuggestionState = null;
|
|
1974
|
+
let completionSuggestionContextMode = readCompletionSuggestionContextMode();
|
|
1952
1975
|
let completionSuggestionInFlight = false;
|
|
1953
1976
|
let completionSuggestionRequestId = null;
|
|
1954
1977
|
let completionSuggestionPendingSnapshot = null;
|
|
@@ -2339,6 +2362,34 @@
|
|
|
2339
2362
|
appendStudioUiRefreshMenuSection(reviewMenu.menu, "Setting", [lensSelect]);
|
|
2340
2363
|
}
|
|
2341
2364
|
|
|
2365
|
+
let contextMenu = null;
|
|
2366
|
+
if (suggestCompletionOptionsBtn) {
|
|
2367
|
+
suggestCompletionOptionsBtn.hidden = false;
|
|
2368
|
+
if (completionContextSelect) completionContextSelect.hidden = true;
|
|
2369
|
+
contextMenu = makeStudioUiRefreshMenu(suggestCompletionOptionsBtn, "context", "studio-refresh-context-anchor");
|
|
2370
|
+
if (sourceBadgeEl) appendStudioUiRefreshMenuSection(contextMenu.menu, "Document", [sourceBadgeEl]);
|
|
2371
|
+
appendStudioUiRefreshMenuSection(contextMenu.menu, "Working directory", [resourceDirBtn, resourceDirLabel, resourceDirInputWrap]);
|
|
2372
|
+
const cursorContextBtn = makeStudioUiRefreshElement("button", "completion-context-option", "Editor only");
|
|
2373
|
+
cursorContextBtn.type = "button";
|
|
2374
|
+
cursorContextBtn.setAttribute("data-completion-context-mode", "cursor");
|
|
2375
|
+
const sessionContextBtn = makeStudioUiRefreshElement("button", "completion-context-option", "Editor + latest response");
|
|
2376
|
+
sessionContextBtn.type = "button";
|
|
2377
|
+
sessionContextBtn.setAttribute("data-completion-context-mode", "session");
|
|
2378
|
+
[cursorContextBtn, sessionContextBtn].forEach((button) => {
|
|
2379
|
+
button.addEventListener("click", (event) => {
|
|
2380
|
+
event.preventDefault();
|
|
2381
|
+
event.stopPropagation();
|
|
2382
|
+
setCompletionSuggestionContextMode(button.getAttribute("data-completion-context-mode") === "session" ? "session" : "cursor");
|
|
2383
|
+
syncActionButtons();
|
|
2384
|
+
});
|
|
2385
|
+
});
|
|
2386
|
+
appendStudioUiRefreshMenuSection(contextMenu.menu, "Suggestions", [cursorContextBtn, sessionContextBtn]);
|
|
2387
|
+
if (syncBadgeEl) {
|
|
2388
|
+
syncBadgeEl.hidden = false;
|
|
2389
|
+
appendStudioUiRefreshMenuSection(contextMenu.menu, "Status", [syncBadgeEl]);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2342
2393
|
const headerTopEl = makeStudioUiRefreshElement("div", "studio-refresh-header-top");
|
|
2343
2394
|
const titleGroupEl = makeStudioUiRefreshElement("div", "studio-refresh-title-group");
|
|
2344
2395
|
if (leftFocusBtn) {
|
|
@@ -2351,6 +2402,10 @@
|
|
|
2351
2402
|
} else if (editorViewSelect) {
|
|
2352
2403
|
titleGroupEl.appendChild(editorViewSelect);
|
|
2353
2404
|
}
|
|
2405
|
+
if (contextMenu) {
|
|
2406
|
+
titleGroupEl.appendChild(makeStudioUiRefreshSeparator());
|
|
2407
|
+
titleGroupEl.appendChild(contextMenu.anchor);
|
|
2408
|
+
}
|
|
2354
2409
|
headerTopEl.appendChild(titleGroupEl);
|
|
2355
2410
|
const headerToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
|
|
2356
2411
|
if (reviewNotesBtn) headerToolsEl.appendChild(reviewNotesBtn);
|
|
@@ -2359,18 +2414,7 @@
|
|
|
2359
2414
|
if (reviewMenu) headerToolsEl.appendChild(reviewMenu.anchor);
|
|
2360
2415
|
headerTopEl.appendChild(headerToolsEl);
|
|
2361
2416
|
|
|
2362
|
-
|
|
2363
|
-
const utilityLeftEl = makeStudioUiRefreshElement("div", "studio-refresh-utility-left");
|
|
2364
|
-
if (sourceBadgeEl) utilityLeftEl.appendChild(sourceBadgeEl);
|
|
2365
|
-
if (sourceBadgeEl && (resourceDirBtn || resourceDirLabel || resourceDirInputWrap || syncBadgeEl)) {
|
|
2366
|
-
utilityLeftEl.appendChild(makeStudioUiRefreshSeparator());
|
|
2367
|
-
}
|
|
2368
|
-
if (resourceDirBtn) utilityLeftEl.appendChild(resourceDirBtn);
|
|
2369
|
-
if (resourceDirLabel) utilityLeftEl.appendChild(resourceDirLabel);
|
|
2370
|
-
if (resourceDirInputWrap) utilityLeftEl.appendChild(resourceDirInputWrap);
|
|
2371
|
-
if (syncBadgeEl) utilityLeftEl.appendChild(syncBadgeEl);
|
|
2372
|
-
headerUtilityEl.appendChild(utilityLeftEl);
|
|
2373
|
-
leftHeaderEl.replaceChildren(headerTopEl, headerUtilityEl);
|
|
2417
|
+
leftHeaderEl.replaceChildren(headerTopEl);
|
|
2374
2418
|
|
|
2375
2419
|
const rightHeaderEl = document.getElementById("rightSectionHeader");
|
|
2376
2420
|
if (rightHeaderEl && rightViewSelect) {
|
|
@@ -2402,18 +2446,18 @@
|
|
|
2402
2446
|
const actionLineOneEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line");
|
|
2403
2447
|
if (!isEditorOnlyMode && sendRunBtn) actionLineOneEl.appendChild(sendRunBtn);
|
|
2404
2448
|
if (!isEditorOnlyMode && queueSteerBtn) actionLineOneEl.appendChild(queueSteerBtn);
|
|
2405
|
-
const
|
|
2406
|
-
replActionLineEl.hidden = true;
|
|
2407
|
-
if (!isEditorOnlyMode && sendReplBtn) replActionLineEl.appendChild(sendReplBtn);
|
|
2408
|
-
if (!isEditorOnlyMode && replSendModeSelect) replActionLineEl.appendChild(replSendModeSelect);
|
|
2409
|
-
const actionLineTwoEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line");
|
|
2449
|
+
const actionLineTwoEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line studio-refresh-utility-action-line");
|
|
2410
2450
|
actionLineTwoEl.appendChild(copyDraftBtn);
|
|
2411
2451
|
if (suggestCompletionBtn) actionLineTwoEl.appendChild(suggestCompletionBtn);
|
|
2412
2452
|
if (openCompanionBtn) actionLineTwoEl.appendChild(openCompanionBtn);
|
|
2413
2453
|
if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
|
|
2454
|
+
const replActionLineEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line repl-action-line");
|
|
2455
|
+
replActionLineEl.hidden = true;
|
|
2456
|
+
if (!isEditorOnlyMode && sendReplBtn) replActionLineEl.appendChild(sendReplBtn);
|
|
2457
|
+
if (!isEditorOnlyMode && replSendModeSelect) replActionLineEl.appendChild(replSendModeSelect);
|
|
2414
2458
|
if (actionLineOneEl.childNodes.length > 0) actionsEl.appendChild(actionLineOneEl);
|
|
2415
|
-
if (replActionLineEl.childNodes.length > 0) actionsEl.appendChild(replActionLineEl);
|
|
2416
2459
|
actionsEl.appendChild(actionLineTwoEl);
|
|
2460
|
+
if (replActionLineEl.childNodes.length > 0) actionsEl.appendChild(replActionLineEl);
|
|
2417
2461
|
|
|
2418
2462
|
const stateEl = makeStudioUiRefreshElement("div", "studio-refresh-toolbar-state");
|
|
2419
2463
|
const annotationsButton = makeStudioUiRefreshElement("button", "", "Annotations");
|
|
@@ -2435,7 +2479,9 @@
|
|
|
2435
2479
|
annotationsButton,
|
|
2436
2480
|
viewButton,
|
|
2437
2481
|
reviewButton: reviewMenu ? reviewMenu.button : null,
|
|
2438
|
-
menus: [annotationsMenu, viewMenu]
|
|
2482
|
+
menus: [annotationsMenu, viewMenu]
|
|
2483
|
+
.concat(contextMenu ? [contextMenu] : [])
|
|
2484
|
+
.concat(reviewMenu ? [reviewMenu] : []),
|
|
2439
2485
|
};
|
|
2440
2486
|
|
|
2441
2487
|
document.addEventListener("click", (event) => {
|
|
@@ -3638,6 +3684,12 @@
|
|
|
3638
3684
|
&& typeof studioHtmlFocusShellEl.contains === "function"
|
|
3639
3685
|
&& studioHtmlFocusShellEl.contains(event.target)
|
|
3640
3686
|
);
|
|
3687
|
+
const imageFocusOwnsEvent = Boolean(
|
|
3688
|
+
studioImageFocusDialogEl
|
|
3689
|
+
&& event.target
|
|
3690
|
+
&& typeof studioImageFocusDialogEl.contains === "function"
|
|
3691
|
+
&& studioImageFocusDialogEl.contains(event.target)
|
|
3692
|
+
);
|
|
3641
3693
|
const quizOwnsEvent = Boolean(
|
|
3642
3694
|
quizDialogEl
|
|
3643
3695
|
&& event.target
|
|
@@ -3663,6 +3715,14 @@
|
|
|
3663
3715
|
return;
|
|
3664
3716
|
}
|
|
3665
3717
|
|
|
3718
|
+
if (isStudioImageFocusOpen() && plainEscape) {
|
|
3719
|
+
event.preventDefault();
|
|
3720
|
+
closeStudioImageFocusViewer();
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
if (handleStudioImageFocusShortcut(event)) return;
|
|
3725
|
+
|
|
3666
3726
|
if (isScratchpadOpen() && plainEscape) {
|
|
3667
3727
|
event.preventDefault();
|
|
3668
3728
|
closeScratchpad();
|
|
@@ -3675,6 +3735,8 @@
|
|
|
3675
3735
|
return;
|
|
3676
3736
|
}
|
|
3677
3737
|
|
|
3738
|
+
if (handleShortcutsScrollShortcut(event)) return;
|
|
3739
|
+
|
|
3678
3740
|
if (isReviewNotesOpen() && plainEscape) {
|
|
3679
3741
|
event.preventDefault();
|
|
3680
3742
|
closeReviewNotes();
|
|
@@ -3687,7 +3749,15 @@
|
|
|
3687
3749
|
return;
|
|
3688
3750
|
}
|
|
3689
3751
|
|
|
3690
|
-
if (scratchpadOwnsEvent || reviewNotesOwnsEvent || outlineOwnsEvent || shortcutsOwnsEvent || pdfFocusOwnsEvent || htmlFocusOwnsEvent || quizOwnsEvent) {
|
|
3752
|
+
if (scratchpadOwnsEvent || reviewNotesOwnsEvent || outlineOwnsEvent || shortcutsOwnsEvent || pdfFocusOwnsEvent || htmlFocusOwnsEvent || imageFocusOwnsEvent || quizOwnsEvent) {
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
if (plainEscape && completionSuggestionState) {
|
|
3757
|
+
event.preventDefault();
|
|
3758
|
+
hideCompletionSuggestion();
|
|
3759
|
+
focusSourceTextNoScroll();
|
|
3760
|
+
setStatus("Dismissed completion suggestion.");
|
|
3691
3761
|
return;
|
|
3692
3762
|
}
|
|
3693
3763
|
|
|
@@ -3854,6 +3924,22 @@
|
|
|
3854
3924
|
}
|
|
3855
3925
|
}
|
|
3856
3926
|
|
|
3927
|
+
function formatStudioExportTimestamp(date) {
|
|
3928
|
+
const value = date instanceof Date ? date : new Date();
|
|
3929
|
+
const pad = (part) => String(part).padStart(2, "0");
|
|
3930
|
+
try {
|
|
3931
|
+
return String(value.getFullYear())
|
|
3932
|
+
+ pad(value.getMonth() + 1)
|
|
3933
|
+
+ pad(value.getDate())
|
|
3934
|
+
+ "-"
|
|
3935
|
+
+ pad(value.getHours())
|
|
3936
|
+
+ pad(value.getMinutes())
|
|
3937
|
+
+ pad(value.getSeconds());
|
|
3938
|
+
} catch {
|
|
3939
|
+
return String(Date.now());
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3857
3943
|
function normalizeHistoryKind(kind) {
|
|
3858
3944
|
return kind === "critique" ? "critique" : "annotation";
|
|
3859
3945
|
}
|
|
@@ -4221,9 +4307,202 @@
|
|
|
4221
4307
|
return marker + (lang ? lang : "") + newline + source + newline + marker;
|
|
4222
4308
|
}
|
|
4223
4309
|
|
|
4310
|
+
function getDelimitedTextPreviewConfig(language) {
|
|
4311
|
+
const lang = normalizeFenceLanguage(language || "");
|
|
4312
|
+
if (lang === "csv") return { kind: "csv", label: "CSV", delimiter: "," };
|
|
4313
|
+
if (lang === "tsv") return { kind: "tsv", label: "TSV", delimiter: "\t" };
|
|
4314
|
+
return null;
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
function parseDelimitedTextRows(text, delimiter, maxRows) {
|
|
4318
|
+
const source = String(text || "").replace(/^\uFEFF/, "");
|
|
4319
|
+
const limit = Math.max(1, Number(maxRows) || (DELIMITED_PREVIEW_MAX_DATA_ROWS + 1));
|
|
4320
|
+
const rows = [];
|
|
4321
|
+
let row = [];
|
|
4322
|
+
let cell = "";
|
|
4323
|
+
let inQuotes = false;
|
|
4324
|
+
let truncatedRows = false;
|
|
4325
|
+
|
|
4326
|
+
const pushCell = () => {
|
|
4327
|
+
row.push(cell);
|
|
4328
|
+
cell = "";
|
|
4329
|
+
};
|
|
4330
|
+
const pushRow = (index) => {
|
|
4331
|
+
pushCell();
|
|
4332
|
+
rows.push(row);
|
|
4333
|
+
row = [];
|
|
4334
|
+
if (rows.length >= limit) {
|
|
4335
|
+
truncatedRows = index < source.length - 1;
|
|
4336
|
+
return true;
|
|
4337
|
+
}
|
|
4338
|
+
return false;
|
|
4339
|
+
};
|
|
4340
|
+
|
|
4341
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
4342
|
+
if (rows.length >= limit) {
|
|
4343
|
+
truncatedRows = true;
|
|
4344
|
+
break;
|
|
4345
|
+
}
|
|
4346
|
+
const ch = source[i];
|
|
4347
|
+
if (inQuotes) {
|
|
4348
|
+
if (ch === '"') {
|
|
4349
|
+
if (source[i + 1] === '"') {
|
|
4350
|
+
cell += '"';
|
|
4351
|
+
i += 1;
|
|
4352
|
+
} else {
|
|
4353
|
+
inQuotes = false;
|
|
4354
|
+
}
|
|
4355
|
+
} else {
|
|
4356
|
+
cell += ch;
|
|
4357
|
+
}
|
|
4358
|
+
continue;
|
|
4359
|
+
}
|
|
4360
|
+
if (ch === '"' && cell === "") {
|
|
4361
|
+
inQuotes = true;
|
|
4362
|
+
continue;
|
|
4363
|
+
}
|
|
4364
|
+
if (ch === delimiter) {
|
|
4365
|
+
pushCell();
|
|
4366
|
+
continue;
|
|
4367
|
+
}
|
|
4368
|
+
if (ch === "\n") {
|
|
4369
|
+
if (pushRow(i)) break;
|
|
4370
|
+
continue;
|
|
4371
|
+
}
|
|
4372
|
+
if (ch === "\r") {
|
|
4373
|
+
if (source[i + 1] === "\n") i += 1;
|
|
4374
|
+
if (pushRow(i)) break;
|
|
4375
|
+
continue;
|
|
4376
|
+
}
|
|
4377
|
+
cell += ch;
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
if (!truncatedRows && rows.length < limit && (cell.length > 0 || row.length > 0)) {
|
|
4381
|
+
pushCell();
|
|
4382
|
+
rows.push(row);
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
return { rows, truncatedRows };
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
function buildDelimitedTextPreviewModel(text, language) {
|
|
4389
|
+
const config = getDelimitedTextPreviewConfig(language);
|
|
4390
|
+
if (!config) return null;
|
|
4391
|
+
const parsed = parseDelimitedTextRows(text, config.delimiter, DELIMITED_PREVIEW_MAX_DATA_ROWS + 1);
|
|
4392
|
+
const rows = parsed.rows;
|
|
4393
|
+
const rawColumnCount = rows.reduce((max, row) => Math.max(max, Array.isArray(row) ? row.length : 0), 0);
|
|
4394
|
+
const columnCount = Math.min(rawColumnCount, DELIMITED_PREVIEW_MAX_COLUMNS);
|
|
4395
|
+
const header = rows[0] || [];
|
|
4396
|
+
const dataRows = rows.slice(1);
|
|
4397
|
+
return {
|
|
4398
|
+
...config,
|
|
4399
|
+
rows,
|
|
4400
|
+
header,
|
|
4401
|
+
dataRows,
|
|
4402
|
+
rawColumnCount,
|
|
4403
|
+
columnCount,
|
|
4404
|
+
truncatedColumns: rawColumnCount > columnCount,
|
|
4405
|
+
truncatedRows: parsed.truncatedRows,
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
function getDelimitedHeaderLabel(header, index) {
|
|
4410
|
+
const value = String((header && header[index]) || "").trim();
|
|
4411
|
+
return value || ("Column " + (index + 1));
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
function formatDelimitedPreviewCellHtml(value) {
|
|
4415
|
+
const raw = String(value ?? "");
|
|
4416
|
+
if (raw.length <= DELIMITED_PREVIEW_MAX_CELL_CHARS) return escapeHtml(raw);
|
|
4417
|
+
return escapeHtml(raw.slice(0, DELIMITED_PREVIEW_MAX_CELL_CHARS)) + "<span class='delimited-preview-truncation'>…</span>";
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
function formatDelimitedMarkdownCell(value) {
|
|
4421
|
+
const raw = String(value ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
4422
|
+
const shortened = raw.length > DELIMITED_PREVIEW_MAX_CELL_CHARS
|
|
4423
|
+
? raw.slice(0, DELIMITED_PREVIEW_MAX_CELL_CHARS) + "…"
|
|
4424
|
+
: raw;
|
|
4425
|
+
return shortened.replace(/\n/g, "<br>").replace(/\|/g, "\\|").trim() || " ";
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
function buildDelimitedTextPreviewHtml(text, language) {
|
|
4429
|
+
const model = buildDelimitedTextPreviewModel(text, language);
|
|
4430
|
+
if (!model) return "";
|
|
4431
|
+
if (!model.rows.length || model.columnCount <= 0) {
|
|
4432
|
+
return "<div class='delimited-preview rendered-markdown'><div class='delimited-preview-header'><strong>" + escapeHtml(model.label) + " preview</strong></div><pre class='plain-markdown'>No tabular data to preview.</pre></div>";
|
|
4433
|
+
}
|
|
4434
|
+
const columnIndexes = Array.from({ length: model.columnCount }, (_, index) => index);
|
|
4435
|
+
const headerHtml = columnIndexes.map((index) => "<th scope='col'>" + escapeHtml(getDelimitedHeaderLabel(model.header, index)) + "</th>").join("");
|
|
4436
|
+
const bodyHtml = model.dataRows.length
|
|
4437
|
+
? model.dataRows.map((row, rowIndex) => {
|
|
4438
|
+
const cells = columnIndexes.map((index) => {
|
|
4439
|
+
const raw = String((row && row[index]) ?? "");
|
|
4440
|
+
const emptyClass = raw.length === 0 ? " delimited-preview-empty-cell" : "";
|
|
4441
|
+
return "<td class='" + emptyClass.trim() + "'>" + formatDelimitedPreviewCellHtml(raw) + "</td>";
|
|
4442
|
+
}).join("");
|
|
4443
|
+
return "<tr><th scope='row' class='delimited-preview-row-number'>" + String(rowIndex + 1) + "</th>" + cells + "</tr>";
|
|
4444
|
+
}).join("")
|
|
4445
|
+
: "<tr><td colspan='" + String(model.columnCount + 1) + "' class='delimited-preview-empty'>No data rows after the header.</td></tr>";
|
|
4446
|
+
const notices = [];
|
|
4447
|
+
if (model.truncatedRows) notices.push("Showing first " + String(Math.max(0, model.dataRows.length)) + " data rows.");
|
|
4448
|
+
if (model.truncatedColumns) notices.push("Showing first " + String(model.columnCount) + " of " + String(model.rawColumnCount) + " columns.");
|
|
4449
|
+
const noticeHtml = notices.length ? "<div class='preview-warning delimited-preview-notice'>" + escapeHtml(notices.join(" ")) + "</div>" : "";
|
|
4450
|
+
const summaryParts = [String(model.dataRows.length) + (model.truncatedRows ? "+" : "") + " data rows", String(model.rawColumnCount) + " columns"];
|
|
4451
|
+
return "<div class='delimited-preview rendered-markdown'>"
|
|
4452
|
+
+ "<div class='delimited-preview-header'><div><strong>" + escapeHtml(model.label) + " preview</strong><span>" + escapeHtml(summaryParts.join(" · ")) + "</span></div></div>"
|
|
4453
|
+
+ noticeHtml
|
|
4454
|
+
+ "<div class='delimited-preview-table-wrap'><table>"
|
|
4455
|
+
+ "<thead><tr><th scope='col' class='delimited-preview-row-number'>#</th>" + headerHtml + "</tr></thead>"
|
|
4456
|
+
+ "<tbody>" + bodyHtml + "</tbody>"
|
|
4457
|
+
+ "</table></div>"
|
|
4458
|
+
+ "</div>";
|
|
4459
|
+
}
|
|
4460
|
+
|
|
4461
|
+
function buildDelimitedTextPreviewMarkdown(text, language) {
|
|
4462
|
+
const model = buildDelimitedTextPreviewModel(text, language);
|
|
4463
|
+
if (!model) return "";
|
|
4464
|
+
if (!model.rows.length || model.columnCount <= 0) return "_No tabular data to preview._";
|
|
4465
|
+
const columnIndexes = Array.from({ length: model.columnCount }, (_, index) => index);
|
|
4466
|
+
const lines = ["**" + model.label + " preview**", ""];
|
|
4467
|
+
const notices = [];
|
|
4468
|
+
if (model.truncatedRows) notices.push("showing first " + String(Math.max(0, model.dataRows.length)) + " data rows");
|
|
4469
|
+
if (model.truncatedColumns) notices.push("showing first " + String(model.columnCount) + " of " + String(model.rawColumnCount) + " columns");
|
|
4470
|
+
if (notices.length) lines.push("_" + notices.join("; ") + "._", "");
|
|
4471
|
+
lines.push("| " + columnIndexes.map((index) => formatDelimitedMarkdownCell(getDelimitedHeaderLabel(model.header, index))).join(" | ") + " |");
|
|
4472
|
+
lines.push("| " + columnIndexes.map(() => "---").join(" | ") + " |");
|
|
4473
|
+
if (model.dataRows.length) {
|
|
4474
|
+
model.dataRows.forEach((row) => {
|
|
4475
|
+
lines.push("| " + columnIndexes.map((index) => formatDelimitedMarkdownCell(row && row[index])).join(" | ") + " |");
|
|
4476
|
+
});
|
|
4477
|
+
} else {
|
|
4478
|
+
lines.push("| " + columnIndexes.map(() => " ").join(" | ") + " |");
|
|
4479
|
+
}
|
|
4480
|
+
return lines.join("\n");
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4483
|
+
function renderDelimitedTextPreview(targetEl, text, pane) {
|
|
4484
|
+
const html = buildDelimitedTextPreviewHtml(text, editorLanguage || "");
|
|
4485
|
+
if (!html || !targetEl) return false;
|
|
4486
|
+
if (pane === "source") {
|
|
4487
|
+
sourcePreviewRenderNonce += 1;
|
|
4488
|
+
} else if (pane === "response") {
|
|
4489
|
+
responsePreviewRenderNonce += 1;
|
|
4490
|
+
}
|
|
4491
|
+
clearPreviewJumpHighlight(targetEl);
|
|
4492
|
+
finishPreviewRender(targetEl);
|
|
4493
|
+
targetEl.innerHTML = html;
|
|
4494
|
+
if (pane === "response") {
|
|
4495
|
+
applyPendingResponseScrollReset();
|
|
4496
|
+
scheduleResponsePaneRepaintNudge();
|
|
4497
|
+
}
|
|
4498
|
+
return true;
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4224
4501
|
function prepareEditorTextForPdfExport(text) {
|
|
4225
4502
|
const prepared = prepareEditorTextForPreview(text);
|
|
4226
4503
|
const lang = normalizeFenceLanguage(editorLanguage || "");
|
|
4504
|
+
const delimitedPreview = buildDelimitedTextPreviewMarkdown(prepared, lang);
|
|
4505
|
+
if (delimitedPreview) return delimitedPreview;
|
|
4227
4506
|
if (lang && lang !== "markdown" && lang !== "latex") {
|
|
4228
4507
|
return wrapAsFencedCodeBlock(prepared, lang);
|
|
4229
4508
|
}
|
|
@@ -4233,6 +4512,8 @@
|
|
|
4233
4512
|
function prepareEditorTextForHtmlExport(text) {
|
|
4234
4513
|
const prepared = prepareEditorTextForPreview(text);
|
|
4235
4514
|
const lang = normalizeFenceLanguage(editorLanguage || "");
|
|
4515
|
+
const delimitedPreview = buildDelimitedTextPreviewMarkdown(prepared, lang);
|
|
4516
|
+
if (delimitedPreview) return delimitedPreview;
|
|
4236
4517
|
if (lang && lang !== "markdown" && lang !== "latex") {
|
|
4237
4518
|
return wrapAsFencedCodeBlock(prepared, lang);
|
|
4238
4519
|
}
|
|
@@ -4244,20 +4525,15 @@
|
|
|
4244
4525
|
|
|
4245
4526
|
if (isEditorOnlyMode) {
|
|
4246
4527
|
syncBadgeEl.hidden = true;
|
|
4247
|
-
syncBadgeEl.
|
|
4248
|
-
|
|
4249
|
-
}
|
|
4250
|
-
|
|
4251
|
-
if (rightView === "trace") {
|
|
4252
|
-
syncBadgeEl.hidden = true;
|
|
4253
|
-
syncBadgeEl.classList.remove("sync");
|
|
4528
|
+
syncBadgeEl.textContent = "Editor-only tab";
|
|
4529
|
+
syncBadgeEl.classList.remove("sync", "out-of-sync");
|
|
4254
4530
|
return;
|
|
4255
4531
|
}
|
|
4256
4532
|
|
|
4257
4533
|
if (!latestResponseHasContent) {
|
|
4258
|
-
syncBadgeEl.hidden =
|
|
4259
|
-
syncBadgeEl.textContent = "
|
|
4260
|
-
syncBadgeEl.classList.remove("sync");
|
|
4534
|
+
syncBadgeEl.hidden = false;
|
|
4535
|
+
syncBadgeEl.textContent = "No latest response";
|
|
4536
|
+
syncBadgeEl.classList.remove("sync", "out-of-sync");
|
|
4261
4537
|
return;
|
|
4262
4538
|
}
|
|
4263
4539
|
|
|
@@ -4265,15 +4541,10 @@
|
|
|
4265
4541
|
? normalizedEditorText
|
|
4266
4542
|
: normalizeForCompare(sourceTextEl.value);
|
|
4267
4543
|
const inSync = normalizedEditor === latestResponseNormalized;
|
|
4268
|
-
syncBadgeEl.hidden =
|
|
4269
|
-
syncBadgeEl.textContent = "In sync with response";
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
syncBadgeEl.classList.add("sync");
|
|
4273
|
-
return;
|
|
4274
|
-
}
|
|
4275
|
-
|
|
4276
|
-
syncBadgeEl.classList.remove("sync");
|
|
4544
|
+
syncBadgeEl.hidden = false;
|
|
4545
|
+
syncBadgeEl.textContent = inSync ? "In sync with response" : "Editor differs from latest response";
|
|
4546
|
+
syncBadgeEl.classList.toggle("sync", inSync);
|
|
4547
|
+
syncBadgeEl.classList.toggle("out-of-sync", !inSync);
|
|
4277
4548
|
}
|
|
4278
4549
|
|
|
4279
4550
|
function buildPlainMarkdownHtml(markdown, options) {
|
|
@@ -5070,13 +5341,12 @@
|
|
|
5070
5341
|
return;
|
|
5071
5342
|
}
|
|
5072
5343
|
if (kind === "image") {
|
|
5073
|
-
|
|
5074
|
-
void openPreviewImageLink(context.href, context.title, context, pendingWindow).catch((error) => {
|
|
5344
|
+
void openPreviewImageLink(context.href, context.title, context).catch((error) => {
|
|
5075
5345
|
setStatus((error && error.message) ? error.message : String(error || "Could not open linked image."), "warning");
|
|
5076
5346
|
});
|
|
5077
5347
|
return;
|
|
5078
5348
|
}
|
|
5079
|
-
if (kind === "text") {
|
|
5349
|
+
if (kind === "text" || kind === "office") {
|
|
5080
5350
|
const pendingWindow = window.open("", "_blank");
|
|
5081
5351
|
void openPreviewDocumentInNewEditor(context.href, pendingWindow, context).catch((error) => {
|
|
5082
5352
|
setStatus((error && error.message) ? error.message : String(error || "Could not open linked file."), "warning");
|
|
@@ -5746,6 +6016,394 @@
|
|
|
5746
6016
|
}
|
|
5747
6017
|
}
|
|
5748
6018
|
|
|
6019
|
+
function isStudioImageFocusOpen() {
|
|
6020
|
+
return Boolean(studioImageFocusOverlayEl && studioImageFocusOverlayEl.hidden === false);
|
|
6021
|
+
}
|
|
6022
|
+
|
|
6023
|
+
function isStudioImageFocusSrcAllowed(src) {
|
|
6024
|
+
const value = String(src || "").trim();
|
|
6025
|
+
if (!value) return false;
|
|
6026
|
+
if (/^javascript:/i.test(value)) return false;
|
|
6027
|
+
return /^(?:data:image\/|blob:|https?:|file:|\/|\.\/|\.\.\/)/i.test(value);
|
|
6028
|
+
}
|
|
6029
|
+
|
|
6030
|
+
function clampStudioImageFocusZoom(value) {
|
|
6031
|
+
const parsed = Number(value);
|
|
6032
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return 1;
|
|
6033
|
+
return Math.max(0.1, Math.min(8, parsed));
|
|
6034
|
+
}
|
|
6035
|
+
|
|
6036
|
+
function getStudioImageFocusFitScale() {
|
|
6037
|
+
const img = studioImageFocusImgEl;
|
|
6038
|
+
const slot = studioImageFocusSlotEl;
|
|
6039
|
+
if (!img || !slot) return 1;
|
|
6040
|
+
const naturalWidth = Number(img.naturalWidth) || 0;
|
|
6041
|
+
const naturalHeight = Number(img.naturalHeight) || 0;
|
|
6042
|
+
if (naturalWidth <= 0 || naturalHeight <= 0) return 1;
|
|
6043
|
+
let paddingX = 0;
|
|
6044
|
+
let paddingY = 0;
|
|
6045
|
+
try {
|
|
6046
|
+
const style = window.getComputedStyle(slot);
|
|
6047
|
+
paddingX = (Number.parseFloat(style.paddingLeft) || 0) + (Number.parseFloat(style.paddingRight) || 0);
|
|
6048
|
+
paddingY = (Number.parseFloat(style.paddingTop) || 0) + (Number.parseFloat(style.paddingBottom) || 0);
|
|
6049
|
+
} catch {}
|
|
6050
|
+
const availableWidth = Math.max(1, (slot.clientWidth || 0) - paddingX);
|
|
6051
|
+
const availableHeight = Math.max(1, (slot.clientHeight || 0) - paddingY);
|
|
6052
|
+
return clampStudioImageFocusZoom(Math.min(1, availableWidth / naturalWidth, availableHeight / naturalHeight));
|
|
6053
|
+
}
|
|
6054
|
+
|
|
6055
|
+
function getStudioImageFocusDisplayScale() {
|
|
6056
|
+
return studioImageFocusZoomMode === "fit"
|
|
6057
|
+
? getStudioImageFocusFitScale()
|
|
6058
|
+
: clampStudioImageFocusZoom(studioImageFocusZoom);
|
|
6059
|
+
}
|
|
6060
|
+
|
|
6061
|
+
function syncStudioImageFocusZoom() {
|
|
6062
|
+
if (!studioImageFocusImgEl || !studioImageFocusSlotEl) return;
|
|
6063
|
+
const fitMode = studioImageFocusZoomMode === "fit";
|
|
6064
|
+
studioImageFocusSlotEl.classList.toggle("is-fit", fitMode);
|
|
6065
|
+
studioImageFocusSlotEl.classList.toggle("is-zoomed", !fitMode);
|
|
6066
|
+
if (fitMode) {
|
|
6067
|
+
studioImageFocusImgEl.style.width = "";
|
|
6068
|
+
studioImageFocusImgEl.style.height = "";
|
|
6069
|
+
studioImageFocusImgEl.style.maxWidth = "100%";
|
|
6070
|
+
studioImageFocusImgEl.style.maxHeight = "100%";
|
|
6071
|
+
} else {
|
|
6072
|
+
const zoom = clampStudioImageFocusZoom(studioImageFocusZoom);
|
|
6073
|
+
const naturalWidth = Number(studioImageFocusImgEl.naturalWidth) || 0;
|
|
6074
|
+
studioImageFocusImgEl.style.maxWidth = "none";
|
|
6075
|
+
studioImageFocusImgEl.style.maxHeight = "none";
|
|
6076
|
+
studioImageFocusImgEl.style.height = "auto";
|
|
6077
|
+
studioImageFocusImgEl.style.width = naturalWidth > 0 ? Math.max(1, Math.round(naturalWidth * zoom)) + "px" : Math.round(zoom * 100) + "%";
|
|
6078
|
+
}
|
|
6079
|
+
if (studioImageFocusZoomLabelEl) {
|
|
6080
|
+
studioImageFocusZoomLabelEl.textContent = Math.round(getStudioImageFocusDisplayScale() * 100) + "%";
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
|
|
6084
|
+
function getStudioImageFocusViewportCenter() {
|
|
6085
|
+
const slot = studioImageFocusSlotEl;
|
|
6086
|
+
if (!slot) return { x: 0.5, y: 0.5 };
|
|
6087
|
+
const scrollWidth = Math.max(slot.scrollWidth || 0, slot.clientWidth || 0, 1);
|
|
6088
|
+
const scrollHeight = Math.max(slot.scrollHeight || 0, slot.clientHeight || 0, 1);
|
|
6089
|
+
return {
|
|
6090
|
+
x: Math.max(0, Math.min(1, (slot.scrollLeft + (slot.clientWidth || 0) / 2) / scrollWidth)),
|
|
6091
|
+
y: Math.max(0, Math.min(1, (slot.scrollTop + (slot.clientHeight || 0) / 2) / scrollHeight)),
|
|
6092
|
+
};
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6095
|
+
function restoreStudioImageFocusViewportCenter(center) {
|
|
6096
|
+
const slot = studioImageFocusSlotEl;
|
|
6097
|
+
if (!slot || !center) return;
|
|
6098
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
6099
|
+
? window.requestAnimationFrame.bind(window)
|
|
6100
|
+
: (callback) => window.setTimeout(callback, 0);
|
|
6101
|
+
schedule(() => {
|
|
6102
|
+
if (!slot.isConnected || studioImageFocusZoomMode === "fit") return;
|
|
6103
|
+
const maxLeft = Math.max(0, (slot.scrollWidth || 0) - (slot.clientWidth || 0));
|
|
6104
|
+
const maxTop = Math.max(0, (slot.scrollHeight || 0) - (slot.clientHeight || 0));
|
|
6105
|
+
slot.scrollLeft = Math.max(0, Math.min(maxLeft, (slot.scrollWidth || 0) * center.x - (slot.clientWidth || 0) / 2));
|
|
6106
|
+
slot.scrollTop = Math.max(0, Math.min(maxTop, (slot.scrollHeight || 0) * center.y - (slot.clientHeight || 0) / 2));
|
|
6107
|
+
});
|
|
6108
|
+
}
|
|
6109
|
+
|
|
6110
|
+
function getStudioImageFocusPointerCenter(event) {
|
|
6111
|
+
const slot = studioImageFocusSlotEl;
|
|
6112
|
+
if (!slot || !event || typeof slot.getBoundingClientRect !== "function") return getStudioImageFocusViewportCenter();
|
|
6113
|
+
const rect = slot.getBoundingClientRect();
|
|
6114
|
+
const scrollWidth = Math.max(slot.scrollWidth || 0, slot.clientWidth || 0, 1);
|
|
6115
|
+
const scrollHeight = Math.max(slot.scrollHeight || 0, slot.clientHeight || 0, 1);
|
|
6116
|
+
return {
|
|
6117
|
+
x: Math.max(0, Math.min(1, (slot.scrollLeft + (Number(event.clientX) || rect.left + rect.width / 2) - rect.left) / scrollWidth)),
|
|
6118
|
+
y: Math.max(0, Math.min(1, (slot.scrollTop + (Number(event.clientY) || rect.top + rect.height / 2) - rect.top) / scrollHeight)),
|
|
6119
|
+
};
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
function setStudioImageFocusZoom(mode, zoom, options) {
|
|
6123
|
+
const center = options && options.center ? options.center : getStudioImageFocusViewportCenter();
|
|
6124
|
+
studioImageFocusZoomMode = mode === "fit" ? "fit" : "custom";
|
|
6125
|
+
studioImageFocusZoom = clampStudioImageFocusZoom(zoom);
|
|
6126
|
+
syncStudioImageFocusZoom();
|
|
6127
|
+
if (studioImageFocusZoomMode !== "fit") restoreStudioImageFocusViewportCenter(center);
|
|
6128
|
+
}
|
|
6129
|
+
|
|
6130
|
+
function zoomStudioImageFocus(factor, options) {
|
|
6131
|
+
const base = studioImageFocusZoomMode === "fit" ? getStudioImageFocusFitScale() : studioImageFocusZoom;
|
|
6132
|
+
setStudioImageFocusZoom("custom", clampStudioImageFocusZoom(base * factor), options);
|
|
6133
|
+
}
|
|
6134
|
+
|
|
6135
|
+
function handleStudioImageFocusWheel(event) {
|
|
6136
|
+
if (!isStudioImageFocusOpen() || !event) return;
|
|
6137
|
+
if (!event.altKey && !event.ctrlKey && !event.metaKey) return;
|
|
6138
|
+
event.preventDefault();
|
|
6139
|
+
event.stopPropagation();
|
|
6140
|
+
const delta = Number(event.deltaY) || 0;
|
|
6141
|
+
const factor = delta < 0 ? 1.12 : 1 / 1.12;
|
|
6142
|
+
zoomStudioImageFocus(factor, { center: getStudioImageFocusPointerCenter(event) });
|
|
6143
|
+
}
|
|
6144
|
+
|
|
6145
|
+
function handleStudioImageFocusShortcut(event) {
|
|
6146
|
+
if (!isStudioImageFocusOpen() || !event) return false;
|
|
6147
|
+
if (isTextEntryShortcutTarget(event.target)) return false;
|
|
6148
|
+
const key = typeof event.key === "string" ? event.key : "";
|
|
6149
|
+
const code = typeof event.code === "string" ? event.code : "";
|
|
6150
|
+
if (!event.altKey || event.metaKey || event.ctrlKey) return false;
|
|
6151
|
+
if (code === "Equal" || code === "NumpadAdd" || key === "=" || key === "+") {
|
|
6152
|
+
event.preventDefault();
|
|
6153
|
+
zoomStudioImageFocus(1.25);
|
|
6154
|
+
return true;
|
|
6155
|
+
}
|
|
6156
|
+
if (code === "Minus" || code === "NumpadSubtract" || key === "-" || key === "_") {
|
|
6157
|
+
event.preventDefault();
|
|
6158
|
+
zoomStudioImageFocus(1 / 1.25);
|
|
6159
|
+
return true;
|
|
6160
|
+
}
|
|
6161
|
+
if (code === "Digit0" || code === "Numpad0" || key === "0") {
|
|
6162
|
+
event.preventDefault();
|
|
6163
|
+
setStudioImageFocusZoom("fit", 1);
|
|
6164
|
+
return true;
|
|
6165
|
+
}
|
|
6166
|
+
return false;
|
|
6167
|
+
}
|
|
6168
|
+
|
|
6169
|
+
function syncStudioImageFocusFullscreenButton() {
|
|
6170
|
+
if (!studioImageFocusFullscreenBtn) return;
|
|
6171
|
+
const isFullscreen = Boolean(document.fullscreenElement && studioImageFocusDialogEl && document.fullscreenElement === studioImageFocusDialogEl);
|
|
6172
|
+
studioImageFocusFullscreenBtn.replaceChildren(makeStudioUiRefreshIcon(isFullscreen ? "fullscreen-exit" : "fullscreen"));
|
|
6173
|
+
const label = isFullscreen ? "Exit fullscreen" : "Fullscreen";
|
|
6174
|
+
studioImageFocusFullscreenBtn.title = isFullscreen
|
|
6175
|
+
? "Exit browser fullscreen and keep the image focus viewer open."
|
|
6176
|
+
: "Ask the browser to make this image viewer fullscreen.";
|
|
6177
|
+
studioImageFocusFullscreenBtn.setAttribute("aria-label", label);
|
|
6178
|
+
studioImageFocusFullscreenBtn.setAttribute("aria-pressed", isFullscreen ? "true" : "false");
|
|
6179
|
+
}
|
|
6180
|
+
|
|
6181
|
+
async function toggleStudioImageFocusFullscreen() {
|
|
6182
|
+
const dialog = studioImageFocusDialogEl;
|
|
6183
|
+
if (!dialog) return;
|
|
6184
|
+
const isFullscreen = Boolean(document.fullscreenElement && document.fullscreenElement === dialog);
|
|
6185
|
+
if (isFullscreen) {
|
|
6186
|
+
try {
|
|
6187
|
+
if (typeof document.exitFullscreen === "function") await document.exitFullscreen();
|
|
6188
|
+
} catch (error) {
|
|
6189
|
+
setStatus("Could not exit image fullscreen: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
6190
|
+
} finally {
|
|
6191
|
+
syncStudioImageFocusFullscreenButton();
|
|
6192
|
+
}
|
|
6193
|
+
return;
|
|
6194
|
+
}
|
|
6195
|
+
if (typeof dialog.requestFullscreen !== "function") {
|
|
6196
|
+
setStatus("Browser fullscreen is not available for this image viewer.", "warning");
|
|
6197
|
+
return;
|
|
6198
|
+
}
|
|
6199
|
+
try {
|
|
6200
|
+
await dialog.requestFullscreen();
|
|
6201
|
+
} catch (error) {
|
|
6202
|
+
setStatus("Could not enter image fullscreen: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
6203
|
+
} finally {
|
|
6204
|
+
syncStudioImageFocusFullscreenButton();
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6208
|
+
function appendStudioImageFocusTextButton(parent, label, title, onClick) {
|
|
6209
|
+
const button = document.createElement("button");
|
|
6210
|
+
button.type = "button";
|
|
6211
|
+
button.className = "studio-pdf-focus-btn studio-image-focus-zoom-btn";
|
|
6212
|
+
button.textContent = label;
|
|
6213
|
+
button.title = title;
|
|
6214
|
+
button.addEventListener("click", onClick);
|
|
6215
|
+
parent.appendChild(button);
|
|
6216
|
+
return button;
|
|
6217
|
+
}
|
|
6218
|
+
|
|
6219
|
+
function ensureStudioImageFocusViewer() {
|
|
6220
|
+
if (studioImageFocusOverlayEl) return studioImageFocusOverlayEl;
|
|
6221
|
+
|
|
6222
|
+
const overlay = document.createElement("div");
|
|
6223
|
+
overlay.className = "studio-pdf-focus-overlay studio-image-focus-overlay";
|
|
6224
|
+
overlay.hidden = true;
|
|
6225
|
+
overlay.setAttribute("role", "dialog");
|
|
6226
|
+
overlay.setAttribute("aria-modal", "true");
|
|
6227
|
+
overlay.setAttribute("aria-labelledby", "studioImageFocusTitle");
|
|
6228
|
+
|
|
6229
|
+
const dialog = document.createElement("div");
|
|
6230
|
+
dialog.className = "studio-pdf-focus-dialog studio-image-focus-dialog";
|
|
6231
|
+
|
|
6232
|
+
const header = document.createElement("div");
|
|
6233
|
+
header.className = "studio-pdf-focus-header studio-image-focus-header";
|
|
6234
|
+
|
|
6235
|
+
const titleGroup = document.createElement("div");
|
|
6236
|
+
titleGroup.className = "studio-pdf-focus-title-group";
|
|
6237
|
+
|
|
6238
|
+
const closeBtn = document.createElement("button");
|
|
6239
|
+
closeBtn.type = "button";
|
|
6240
|
+
closeBtn.className = "studio-pdf-focus-btn studio-pdf-focus-close";
|
|
6241
|
+
closeBtn.title = "Exit image focus view.";
|
|
6242
|
+
closeBtn.setAttribute("aria-label", "Exit image focus view");
|
|
6243
|
+
closeBtn.appendChild(makeStudioUiRefreshIcon("focus-exit"));
|
|
6244
|
+
closeBtn.addEventListener("click", () => closeStudioImageFocusViewer());
|
|
6245
|
+
titleGroup.appendChild(closeBtn);
|
|
6246
|
+
|
|
6247
|
+
const titleEl = document.createElement("div");
|
|
6248
|
+
titleEl.id = "studioImageFocusTitle";
|
|
6249
|
+
titleEl.className = "studio-pdf-focus-title";
|
|
6250
|
+
titleEl.textContent = "Image preview";
|
|
6251
|
+
titleGroup.appendChild(titleEl);
|
|
6252
|
+
header.appendChild(titleGroup);
|
|
6253
|
+
|
|
6254
|
+
const actions = document.createElement("div");
|
|
6255
|
+
actions.className = "studio-pdf-focus-actions studio-image-focus-actions";
|
|
6256
|
+
|
|
6257
|
+
const openLink = document.createElement("a");
|
|
6258
|
+
openLink.className = "studio-pdf-focus-link";
|
|
6259
|
+
openLink.target = "_blank";
|
|
6260
|
+
openLink.rel = "noopener noreferrer";
|
|
6261
|
+
openLink.textContent = "Open image";
|
|
6262
|
+
actions.appendChild(openLink);
|
|
6263
|
+
|
|
6264
|
+
appendStudioImageFocusTextButton(actions, "Fit", "Fit the image to the viewer.", () => setStudioImageFocusZoom("fit", 1));
|
|
6265
|
+
appendStudioImageFocusTextButton(actions, "100%", "Show the image at its natural pixel size.", () => setStudioImageFocusZoom("custom", 1));
|
|
6266
|
+
appendStudioImageFocusTextButton(actions, "−", "Zoom out.", () => zoomStudioImageFocus(1 / 1.25));
|
|
6267
|
+
const zoomLabel = document.createElement("span");
|
|
6268
|
+
zoomLabel.className = "studio-image-focus-zoom-label";
|
|
6269
|
+
zoomLabel.textContent = "100%";
|
|
6270
|
+
actions.appendChild(zoomLabel);
|
|
6271
|
+
appendStudioImageFocusTextButton(actions, "+", "Zoom in.", () => zoomStudioImageFocus(1.25));
|
|
6272
|
+
appendStudioImageFocusTextButton(actions, "Reset", "Reset image zoom to fit.", () => setStudioImageFocusZoom("fit", 1));
|
|
6273
|
+
|
|
6274
|
+
const fullscreenBtn = document.createElement("button");
|
|
6275
|
+
fullscreenBtn.type = "button";
|
|
6276
|
+
fullscreenBtn.className = "studio-pdf-focus-btn studio-pdf-focus-fullscreen";
|
|
6277
|
+
fullscreenBtn.addEventListener("click", () => {
|
|
6278
|
+
void toggleStudioImageFocusFullscreen();
|
|
6279
|
+
});
|
|
6280
|
+
actions.appendChild(fullscreenBtn);
|
|
6281
|
+
|
|
6282
|
+
header.appendChild(actions);
|
|
6283
|
+
dialog.appendChild(header);
|
|
6284
|
+
|
|
6285
|
+
const slot = document.createElement("div");
|
|
6286
|
+
slot.className = "studio-image-focus-slot is-fit";
|
|
6287
|
+
const img = document.createElement("img");
|
|
6288
|
+
img.className = "studio-image-focus-img";
|
|
6289
|
+
img.alt = "Image preview";
|
|
6290
|
+
img.addEventListener("load", syncStudioImageFocusZoom);
|
|
6291
|
+
slot.addEventListener("wheel", handleStudioImageFocusWheel, { passive: false });
|
|
6292
|
+
slot.appendChild(img);
|
|
6293
|
+
dialog.appendChild(slot);
|
|
6294
|
+
|
|
6295
|
+
overlay.appendChild(dialog);
|
|
6296
|
+
overlay.addEventListener("click", (event) => {
|
|
6297
|
+
if (event.target === overlay) closeStudioImageFocusViewer();
|
|
6298
|
+
});
|
|
6299
|
+
document.addEventListener("fullscreenchange", syncStudioImageFocusFullscreenButton);
|
|
6300
|
+
|
|
6301
|
+
document.body.appendChild(overlay);
|
|
6302
|
+
studioImageFocusOverlayEl = overlay;
|
|
6303
|
+
studioImageFocusDialogEl = dialog;
|
|
6304
|
+
studioImageFocusSlotEl = slot;
|
|
6305
|
+
studioImageFocusImgEl = img;
|
|
6306
|
+
studioImageFocusTitleEl = titleEl;
|
|
6307
|
+
studioImageFocusOpenLinkEl = openLink;
|
|
6308
|
+
studioImageFocusFullscreenBtn = fullscreenBtn;
|
|
6309
|
+
studioImageFocusCloseBtn = closeBtn;
|
|
6310
|
+
studioImageFocusZoomLabelEl = zoomLabel;
|
|
6311
|
+
syncStudioImageFocusFullscreenButton();
|
|
6312
|
+
return overlay;
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6315
|
+
function openStudioImageFocusViewer(src, title) {
|
|
6316
|
+
const imageSrc = String(src || "").trim();
|
|
6317
|
+
if (!isStudioImageFocusSrcAllowed(imageSrc)) return false;
|
|
6318
|
+
ensureStudioImageFocusViewer();
|
|
6319
|
+
studioImageFocusLastFocusedEl = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
6320
|
+
const label = String(title || "Image preview").trim() || "Image preview";
|
|
6321
|
+
if (studioImageFocusTitleEl) studioImageFocusTitleEl.textContent = label;
|
|
6322
|
+
if (studioImageFocusOpenLinkEl) studioImageFocusOpenLinkEl.href = imageSrc;
|
|
6323
|
+
if (studioImageFocusImgEl) {
|
|
6324
|
+
studioImageFocusImgEl.alt = label;
|
|
6325
|
+
studioImageFocusImgEl.src = imageSrc;
|
|
6326
|
+
}
|
|
6327
|
+
studioImageFocusZoomMode = "fit";
|
|
6328
|
+
studioImageFocusZoom = 1;
|
|
6329
|
+
syncStudioImageFocusZoom();
|
|
6330
|
+
if (document.body) document.body.classList.add("studio-image-focus-open");
|
|
6331
|
+
if (studioImageFocusOverlayEl) studioImageFocusOverlayEl.hidden = false;
|
|
6332
|
+
syncStudioImageFocusFullscreenButton();
|
|
6333
|
+
closeStudioUiRefreshMenus();
|
|
6334
|
+
closeExportPreviewMenu();
|
|
6335
|
+
closePreviewLinkMenu();
|
|
6336
|
+
window.setTimeout(() => {
|
|
6337
|
+
if (studioImageFocusCloseBtn && typeof studioImageFocusCloseBtn.focus === "function") {
|
|
6338
|
+
studioImageFocusCloseBtn.focus();
|
|
6339
|
+
}
|
|
6340
|
+
}, 0);
|
|
6341
|
+
return true;
|
|
6342
|
+
}
|
|
6343
|
+
|
|
6344
|
+
function closeStudioImageFocusViewer() {
|
|
6345
|
+
if (!isStudioImageFocusOpen()) return false;
|
|
6346
|
+
if (document.fullscreenElement && studioImageFocusDialogEl && studioImageFocusDialogEl.contains(document.fullscreenElement)) {
|
|
6347
|
+
try {
|
|
6348
|
+
const exitResult = document.exitFullscreen && document.exitFullscreen();
|
|
6349
|
+
if (exitResult && typeof exitResult.catch === "function") exitResult.catch(() => {});
|
|
6350
|
+
} catch {}
|
|
6351
|
+
}
|
|
6352
|
+
if (studioImageFocusOverlayEl) studioImageFocusOverlayEl.hidden = true;
|
|
6353
|
+
if (studioImageFocusImgEl) studioImageFocusImgEl.removeAttribute("src");
|
|
6354
|
+
if (studioImageFocusOpenLinkEl) studioImageFocusOpenLinkEl.removeAttribute("href");
|
|
6355
|
+
if (document.body) document.body.classList.remove("studio-image-focus-open");
|
|
6356
|
+
syncStudioImageFocusFullscreenButton();
|
|
6357
|
+
const focusTarget = studioImageFocusLastFocusedEl;
|
|
6358
|
+
studioImageFocusLastFocusedEl = null;
|
|
6359
|
+
if (focusTarget && typeof focusTarget.focus === "function" && document.contains(focusTarget)) {
|
|
6360
|
+
window.setTimeout(() => focusTarget.focus(), 0);
|
|
6361
|
+
}
|
|
6362
|
+
return true;
|
|
6363
|
+
}
|
|
6364
|
+
|
|
6365
|
+
function getPreviewImageElementTitle(imageEl) {
|
|
6366
|
+
if (!imageEl) return "Image preview";
|
|
6367
|
+
const alt = typeof imageEl.getAttribute === "function" ? String(imageEl.getAttribute("alt") || "").trim() : "";
|
|
6368
|
+
const title = typeof imageEl.getAttribute === "function" ? String(imageEl.getAttribute("title") || "").trim() : "";
|
|
6369
|
+
const src = typeof imageEl.getAttribute === "function" ? String(imageEl.getAttribute("src") || "").trim() : "";
|
|
6370
|
+
const srcLabel = /^data:image\//i.test(src) ? "" : (src.length > 120 ? src.slice(0, 117) + "…" : src);
|
|
6371
|
+
return alt || title || srcLabel || "Image preview";
|
|
6372
|
+
}
|
|
6373
|
+
|
|
6374
|
+
function openPreviewImageElementInFocus(imageEl) {
|
|
6375
|
+
if (!imageEl) return false;
|
|
6376
|
+
const src = String(imageEl.currentSrc || imageEl.src || imageEl.getAttribute("src") || "").trim();
|
|
6377
|
+
if (!src) return false;
|
|
6378
|
+
return openStudioImageFocusViewer(src, getPreviewImageElementTitle(imageEl));
|
|
6379
|
+
}
|
|
6380
|
+
|
|
6381
|
+
function decoratePreviewImages(targetEl) {
|
|
6382
|
+
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
6383
|
+
const images = Array.from(targetEl.querySelectorAll("img[src]"));
|
|
6384
|
+
images.forEach((imageEl) => {
|
|
6385
|
+
if (!(imageEl instanceof HTMLImageElement)) return;
|
|
6386
|
+
if (imageEl.dataset && imageEl.dataset.studioImageFocusDecorated === "1") return;
|
|
6387
|
+
if (imageEl.closest && imageEl.closest("a[href], button, .studio-html-artifact-shell, .studio-pdf-card")) return;
|
|
6388
|
+
if (!isStudioImageFocusSrcAllowed(imageEl.currentSrc || imageEl.src || imageEl.getAttribute("src") || "")) return;
|
|
6389
|
+
imageEl.classList.add("studio-image-focus-target");
|
|
6390
|
+
imageEl.tabIndex = imageEl.tabIndex >= 0 ? imageEl.tabIndex : 0;
|
|
6391
|
+
imageEl.setAttribute("role", "button");
|
|
6392
|
+
imageEl.setAttribute("aria-label", "Open image focus viewer");
|
|
6393
|
+
if (imageEl.dataset) imageEl.dataset.studioImageFocusDecorated = "1";
|
|
6394
|
+
imageEl.addEventListener("click", (event) => {
|
|
6395
|
+
event.preventDefault();
|
|
6396
|
+
event.stopPropagation();
|
|
6397
|
+
if (!openPreviewImageElementInFocus(imageEl)) setStatus("Could not open image focus view.", "warning");
|
|
6398
|
+
});
|
|
6399
|
+
imageEl.addEventListener("keydown", (event) => {
|
|
6400
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
6401
|
+
event.preventDefault();
|
|
6402
|
+
if (!openPreviewImageElementInFocus(imageEl)) setStatus("Could not open image focus view.", "warning");
|
|
6403
|
+
});
|
|
6404
|
+
});
|
|
6405
|
+
}
|
|
6406
|
+
|
|
5749
6407
|
function createStudioPdfCard(block, useEditorResourceContext) {
|
|
5750
6408
|
const options = block && block.options ? block.options : {};
|
|
5751
6409
|
const path = String(options.path || "").trim();
|
|
@@ -6977,15 +7635,16 @@
|
|
|
6977
7635
|
const sourcePath = exportingReplJournal ? "" : (effectivePath || sourceState.path || "");
|
|
6978
7636
|
const resourceDir = (!sourcePath && resourceDirInput) ? getCurrentResourceDirValue() : "";
|
|
6979
7637
|
const isEditorPreview = rightView === "editor-preview";
|
|
6980
|
-
const
|
|
7638
|
+
const editorIsDelimitedPreview = isEditorPreview && Boolean(getDelimitedTextPreviewConfig(editorLanguage || ""));
|
|
7639
|
+
const editorPdfLanguage = isEditorPreview ? (editorIsDelimitedPreview ? "markdown" : normalizeFenceLanguage(editorLanguage || "")) : "";
|
|
6981
7640
|
const isLatex = isEditorPreview
|
|
6982
7641
|
? editorPdfLanguage === "latex"
|
|
6983
7642
|
: /\\documentclass\b|\\begin\{document\}/.test(markdown);
|
|
6984
|
-
let filenameHint = exportingReplJournal ? "repl-studio.pdf" : (isEditorPreview ? "studio-editor-preview.pdf" : "studio-response-
|
|
7643
|
+
let filenameHint = exportingReplJournal ? "repl-studio.pdf" : (isEditorPreview ? "studio-editor-preview.pdf" : ("studio-response-" + formatStudioExportTimestamp() + ".studio.pdf"));
|
|
6985
7644
|
if (sourcePath) {
|
|
6986
7645
|
const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
|
|
6987
7646
|
const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
|
|
6988
|
-
filenameHint = stem + "
|
|
7647
|
+
filenameHint = stem + ".studio.pdf";
|
|
6989
7648
|
}
|
|
6990
7649
|
|
|
6991
7650
|
previewExportInProgress = true;
|
|
@@ -7033,6 +7692,8 @@
|
|
|
7033
7692
|
|
|
7034
7693
|
const exportWarning = typeof payload.warning === "string" ? payload.warning.trim() : "";
|
|
7035
7694
|
const openError = typeof payload.openError === "string" ? payload.openError.trim() : "";
|
|
7695
|
+
const writeError = typeof payload.writeError === "string" ? payload.writeError.trim() : "";
|
|
7696
|
+
const exportPath = typeof payload.path === "string" ? payload.path.trim() : "";
|
|
7036
7697
|
const openedExternal = payload.openedExternal === true;
|
|
7037
7698
|
let downloadName = typeof payload.filename === "string" && payload.filename.trim()
|
|
7038
7699
|
? payload.filename.trim()
|
|
@@ -7042,10 +7703,12 @@
|
|
|
7042
7703
|
}
|
|
7043
7704
|
|
|
7044
7705
|
if (openedExternal) {
|
|
7045
|
-
if (
|
|
7706
|
+
if (writeError) {
|
|
7707
|
+
setStatus("Opened PDF in default viewer, but could not write project file: " + writeError, "warning");
|
|
7708
|
+
} else if (exportWarning) {
|
|
7046
7709
|
setStatus("Opened PDF in default viewer with warning: " + exportWarning, "warning");
|
|
7047
7710
|
} else {
|
|
7048
|
-
setStatus("Opened PDF in default viewer: " + downloadName, "success");
|
|
7711
|
+
setStatus("Opened PDF in default viewer: " + (exportPath || downloadName), "success");
|
|
7049
7712
|
}
|
|
7050
7713
|
return;
|
|
7051
7714
|
}
|
|
@@ -7064,10 +7727,12 @@
|
|
|
7064
7727
|
} else {
|
|
7065
7728
|
setStatus("Opened browser fallback because external viewer failed (" + openError + ").", "warning");
|
|
7066
7729
|
}
|
|
7730
|
+
} else if (writeError) {
|
|
7731
|
+
setStatus("Exported PDF to browser fallback; could not write project file: " + writeError, "warning");
|
|
7067
7732
|
} else if (exportWarning) {
|
|
7068
|
-
setStatus("Exported PDF with warning: " + exportWarning, "warning");
|
|
7733
|
+
setStatus("Exported PDF with warning" + (exportPath ? " to " + exportPath : ": " + exportWarning), "warning");
|
|
7069
7734
|
} else {
|
|
7070
|
-
setStatus("Exported PDF: " + downloadName, "success");
|
|
7735
|
+
setStatus("Exported PDF: " + (exportPath || downloadName), "success");
|
|
7071
7736
|
}
|
|
7072
7737
|
return;
|
|
7073
7738
|
}
|
|
@@ -7143,16 +7808,17 @@
|
|
|
7143
7808
|
const sourcePath = exportingReplJournal ? "" : (effectivePath || sourceState.path || "");
|
|
7144
7809
|
const resourceDir = (!sourcePath && resourceDirInput) ? getCurrentResourceDirValue() : "";
|
|
7145
7810
|
const isEditorPreview = rightView === "editor-preview";
|
|
7146
|
-
const
|
|
7811
|
+
const editorIsDelimitedPreview = isEditorPreview && Boolean(getDelimitedTextPreviewConfig(editorLanguage || ""));
|
|
7812
|
+
const editorHtmlLanguage = htmlArtifactSource ? "html" : (isEditorPreview ? (editorIsDelimitedPreview ? "markdown" : normalizeFenceLanguage(editorLanguage || "")) : "");
|
|
7147
7813
|
const isLatex = htmlArtifactSource ? false : (isEditorPreview
|
|
7148
7814
|
? editorHtmlLanguage === "latex"
|
|
7149
7815
|
: /\\documentclass\b|\\begin\{document\}/.test(markdown));
|
|
7150
|
-
let filenameHint = exportingReplJournal ? "repl-studio.html" : (isEditorPreview ? "studio-editor-preview.html" : "studio-response-
|
|
7816
|
+
let filenameHint = exportingReplJournal ? "repl-studio.html" : (isEditorPreview ? "studio-editor-preview.html" : ("studio-response-" + formatStudioExportTimestamp() + ".studio.html"));
|
|
7151
7817
|
let titleHint = exportingReplJournal ? "Studio REPL Record" : (isEditorPreview ? "Studio editor preview" : "Studio response preview");
|
|
7152
7818
|
if (sourcePath) {
|
|
7153
7819
|
const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
|
|
7154
7820
|
const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
|
|
7155
|
-
filenameHint = stem + "
|
|
7821
|
+
filenameHint = stem + ".studio.html";
|
|
7156
7822
|
titleHint = stem + " preview";
|
|
7157
7823
|
}
|
|
7158
7824
|
|
|
@@ -7202,6 +7868,8 @@
|
|
|
7202
7868
|
|
|
7203
7869
|
const exportWarning = typeof payload.warning === "string" ? payload.warning.trim() : "";
|
|
7204
7870
|
const openError = typeof payload.openError === "string" ? payload.openError.trim() : "";
|
|
7871
|
+
const writeError = typeof payload.writeError === "string" ? payload.writeError.trim() : "";
|
|
7872
|
+
const exportPath = typeof payload.path === "string" ? payload.path.trim() : "";
|
|
7205
7873
|
const openedExternal = payload.openedExternal === true;
|
|
7206
7874
|
let downloadName = typeof payload.filename === "string" && payload.filename.trim()
|
|
7207
7875
|
? payload.filename.trim()
|
|
@@ -7211,10 +7879,12 @@
|
|
|
7211
7879
|
}
|
|
7212
7880
|
|
|
7213
7881
|
if (openedExternal) {
|
|
7214
|
-
if (
|
|
7882
|
+
if (writeError) {
|
|
7883
|
+
setStatus("Opened HTML in default browser, but could not write project file: " + writeError, "warning");
|
|
7884
|
+
} else if (exportWarning) {
|
|
7215
7885
|
setStatus("Opened HTML in default browser with warning: " + exportWarning, "warning");
|
|
7216
7886
|
} else {
|
|
7217
|
-
setStatus("Opened HTML in default browser: " + downloadName, "success");
|
|
7887
|
+
setStatus("Opened HTML in default browser: " + (exportPath || downloadName), "success");
|
|
7218
7888
|
}
|
|
7219
7889
|
return;
|
|
7220
7890
|
}
|
|
@@ -7233,10 +7903,12 @@
|
|
|
7233
7903
|
} else {
|
|
7234
7904
|
setStatus("Opened browser fallback because external viewer failed (" + openError + ").", "warning");
|
|
7235
7905
|
}
|
|
7906
|
+
} else if (writeError) {
|
|
7907
|
+
setStatus("Exported HTML to browser fallback; could not write project file: " + writeError, "warning");
|
|
7236
7908
|
} else if (exportWarning) {
|
|
7237
|
-
setStatus("Exported HTML with warning: " + exportWarning, "warning");
|
|
7909
|
+
setStatus("Exported HTML with warning" + (exportPath ? " to " + exportPath : ": " + exportWarning), "warning");
|
|
7238
7910
|
} else {
|
|
7239
|
-
setStatus("Exported HTML: " + downloadName, "success");
|
|
7911
|
+
setStatus("Exported HTML: " + (exportPath || downloadName), "success");
|
|
7240
7912
|
}
|
|
7241
7913
|
return;
|
|
7242
7914
|
}
|
|
@@ -7528,6 +8200,7 @@
|
|
|
7528
8200
|
decorateRenderedEditorPreviewComments(targetEl, sourceTextEl.value || "");
|
|
7529
8201
|
}
|
|
7530
8202
|
decorateCopyablePreviewBlocks(targetEl);
|
|
8203
|
+
decoratePreviewImages(targetEl);
|
|
7531
8204
|
|
|
7532
8205
|
// Warn if relative images are present but unlikely to resolve (non-file-backed content)
|
|
7533
8206
|
if (!sourceState.path && !getCurrentResourceDirValue()) {
|
|
@@ -7567,6 +8240,9 @@
|
|
|
7567
8240
|
renderHtmlArtifactPreview(sourcePreviewEl, text, "source", { title: "Editor HTML preview", ...getHtmlPreviewResourceContextOptions() });
|
|
7568
8241
|
return;
|
|
7569
8242
|
}
|
|
8243
|
+
if (renderDelimitedTextPreview(sourcePreviewEl, text, "source")) {
|
|
8244
|
+
return;
|
|
8245
|
+
}
|
|
7570
8246
|
if (supportsCodePreviewCommentsForCurrentEditor()) {
|
|
7571
8247
|
renderCodePreviewWithCommentBlocks(sourcePreviewEl, text, "source");
|
|
7572
8248
|
return;
|
|
@@ -8115,6 +8791,7 @@
|
|
|
8115
8791
|
const previousScrollTop = critiqueViewEl.scrollTop;
|
|
8116
8792
|
finishPreviewRender(critiqueViewEl);
|
|
8117
8793
|
critiqueViewEl.innerHTML = buildTracePanelHtml();
|
|
8794
|
+
decoratePreviewImages(critiqueViewEl);
|
|
8118
8795
|
critiqueViewEl.classList.remove("response-scroll-resetting");
|
|
8119
8796
|
if (shouldStick) {
|
|
8120
8797
|
critiqueViewEl.scrollTop = critiqueViewEl.scrollHeight;
|
|
@@ -8171,6 +8848,7 @@
|
|
|
8171
8848
|
function getFileBrowserKindLabel(entry) {
|
|
8172
8849
|
if (!entry || entry.type === "directory") return "folder";
|
|
8173
8850
|
if (entry.kind === "text") return "document";
|
|
8851
|
+
if (entry.kind === "office") return "document";
|
|
8174
8852
|
if (entry.kind === "pdf") return "PDF";
|
|
8175
8853
|
if (entry.kind === "image") return "image";
|
|
8176
8854
|
return entry.extension ? entry.extension.replace(/^\./, "") : "file";
|
|
@@ -8187,18 +8865,21 @@
|
|
|
8187
8865
|
? entries.map((entry) => {
|
|
8188
8866
|
const type = entry.type === "directory" ? "directory" : "file";
|
|
8189
8867
|
const kind = entry.kind || (type === "directory" ? "directory" : "other");
|
|
8190
|
-
const icon = type === "directory" ? "📁" : (kind === "pdf" ? "📄" : (kind === "image" ? "🖼️" : (kind === "text" ? "📝" : "📦")));
|
|
8868
|
+
const icon = type === "directory" ? "📁" : (kind === "pdf" ? "📄" : (kind === "image" ? "🖼️" : (kind === "text" || kind === "office" ? "📝" : "📦")));
|
|
8191
8869
|
const metaParts = [];
|
|
8192
8870
|
metaParts.push(getFileBrowserKindLabel(entry));
|
|
8193
8871
|
if (type === "file") metaParts.push(formatFileBrowserSize(entry.size));
|
|
8194
8872
|
const time = formatFileBrowserTime(entry.mtimeMs);
|
|
8195
8873
|
if (time) metaParts.push(time);
|
|
8196
|
-
const
|
|
8197
|
-
? "
|
|
8874
|
+
const newTabAction = kind === "text" || kind === "office"
|
|
8875
|
+
? "open-new"
|
|
8876
|
+
: ((kind === "pdf" || kind === "image") ? "open-preview-new" : "");
|
|
8877
|
+
const textActions = newTabAction
|
|
8878
|
+
? "<button type='button' data-files-action='" + escapeHtml(newTabAction) + "' data-files-path='" + escapeHtml(entry.path) + "'>New tab</button>"
|
|
8198
8879
|
: "";
|
|
8199
8880
|
const openTitle = type === "directory"
|
|
8200
8881
|
? "Open folder"
|
|
8201
|
-
: (kind === "text" ? "Open in editor" : (kind === "pdf" ? "Open PDF preview" : (kind === "image" ? "Open image preview" : "Copy or reveal this file")));
|
|
8882
|
+
: (kind === "text" ? "Open in editor" : (kind === "office" ? "Convert to Markdown in editor" : (kind === "pdf" ? "Open PDF preview" : (kind === "image" ? "Open image preview" : "Copy or reveal this file"))));
|
|
8202
8883
|
return "<div class='files-row files-row-" + escapeHtml(type) + " files-kind-" + escapeHtml(kind) + "'>"
|
|
8203
8884
|
+ "<button type='button' class='files-open-btn' data-files-action='" + (type === "directory" ? "open-dir" : "open") + "' data-files-path='" + escapeHtml(entry.path) + "' data-files-kind='" + escapeHtml(kind) + "' title='" + escapeHtml(openTitle) + "'>"
|
|
8204
8885
|
+ "<span class='files-icon' aria-hidden='true'>" + icon + "</span>"
|
|
@@ -8223,6 +8904,8 @@
|
|
|
8223
8904
|
+ "<div class='files-toolbar-actions'>"
|
|
8224
8905
|
+ "<button type='button' data-files-action='parent'" + parentDisabled + ">Parent</button>"
|
|
8225
8906
|
+ "<button type='button' data-files-action='refresh'>Refresh</button>"
|
|
8907
|
+
+ (currentDir ? "<button type='button' data-files-action='copy-current' data-files-path='" + escapeHtml(currentDir) + "'>Copy path</button>" : "")
|
|
8908
|
+
+ (currentDir ? "<button type='button' data-files-action='use-working-dir' data-files-path='" + escapeHtml(currentDir) + "'>Use as working dir</button>" : "")
|
|
8226
8909
|
+ (rootDir ? "<button type='button' data-files-action='copy-root' data-files-path='" + escapeHtml(rootDir) + "'>Copy root</button>" : "")
|
|
8227
8910
|
+ "</div>"
|
|
8228
8911
|
+ "</div>"
|
|
@@ -8317,7 +9000,7 @@
|
|
|
8317
9000
|
|
|
8318
9001
|
async function openFileBrowserEntry(path, kind) {
|
|
8319
9002
|
const context = getFileBrowserLocalLinkContext();
|
|
8320
|
-
if (kind === "text") {
|
|
9003
|
+
if (kind === "text" || kind === "office") {
|
|
8321
9004
|
await openPreviewDocumentHere(path, context);
|
|
8322
9005
|
return;
|
|
8323
9006
|
}
|
|
@@ -8332,6 +9015,19 @@
|
|
|
8332
9015
|
setStatus("No Studio preview for this file type. Use Copy path or Reveal.", "warning");
|
|
8333
9016
|
}
|
|
8334
9017
|
|
|
9018
|
+
function setFileBrowserCurrentDirectoryAsWorkingDir(path) {
|
|
9019
|
+
const nextDir = normalizeStudioResourceDirValue(path || fileBrowserState.currentDir || "");
|
|
9020
|
+
if (!nextDir) {
|
|
9021
|
+
setStatus("No current folder to use as working directory.", "warning");
|
|
9022
|
+
return;
|
|
9023
|
+
}
|
|
9024
|
+
if (resourceDirInput) resourceDirInput.value = nextDir;
|
|
9025
|
+
applyResourceDir();
|
|
9026
|
+
fileBrowserState = { ...fileBrowserState, contextKey: "" };
|
|
9027
|
+
if (rightView === "files") renderFilesView();
|
|
9028
|
+
setStatus("Working dir set to current folder.", "success");
|
|
9029
|
+
}
|
|
9030
|
+
|
|
8335
9031
|
async function handleFilesPaneClick(event) {
|
|
8336
9032
|
if (rightView !== "files") return;
|
|
8337
9033
|
const target = event.target;
|
|
@@ -8362,11 +9058,19 @@
|
|
|
8362
9058
|
await openPreviewDocumentInNewEditor(path, null, getFileBrowserLocalLinkContext());
|
|
8363
9059
|
return;
|
|
8364
9060
|
}
|
|
8365
|
-
if (action === "
|
|
9061
|
+
if (action === "open-preview-new") {
|
|
9062
|
+
await openPreviewResourceInNewEditor(path, null, getFileBrowserLocalLinkContext());
|
|
9063
|
+
return;
|
|
9064
|
+
}
|
|
9065
|
+
if (action === "copy-path" || action === "copy-root" || action === "copy-current") {
|
|
8366
9066
|
const ok = await writeTextToClipboard(path);
|
|
8367
9067
|
setStatus(ok ? "Copied path." : "Clipboard write failed.", ok ? "success" : "warning");
|
|
8368
9068
|
return;
|
|
8369
9069
|
}
|
|
9070
|
+
if (action === "use-working-dir") {
|
|
9071
|
+
setFileBrowserCurrentDirectoryAsWorkingDir(path);
|
|
9072
|
+
return;
|
|
9073
|
+
}
|
|
8370
9074
|
if (action === "reveal") {
|
|
8371
9075
|
await revealPreviewLocalLink(path, getFileBrowserLocalLinkContext());
|
|
8372
9076
|
}
|
|
@@ -8403,6 +9107,9 @@
|
|
|
8403
9107
|
renderHtmlArtifactPreview(critiqueViewEl, editorText, "response", { title: "Editor HTML preview", ...getHtmlPreviewResourceContextOptions() });
|
|
8404
9108
|
return;
|
|
8405
9109
|
}
|
|
9110
|
+
if (renderDelimitedTextPreview(critiqueViewEl, editorText, "response")) {
|
|
9111
|
+
return;
|
|
9112
|
+
}
|
|
8406
9113
|
if (supportsCodePreviewCommentsForCurrentEditor()) {
|
|
8407
9114
|
renderCodePreviewWithCommentBlocks(critiqueViewEl, editorText, "response");
|
|
8408
9115
|
return;
|
|
@@ -8667,9 +9374,14 @@
|
|
|
8667
9374
|
syncRunAndCritiqueButtons();
|
|
8668
9375
|
copyDraftBtn.disabled = uiBusy;
|
|
8669
9376
|
if (suggestCompletionBtn) {
|
|
8670
|
-
suggestCompletionBtn.disabled =
|
|
8671
|
-
suggestCompletionBtn.textContent = completionSuggestionInFlight ? "
|
|
8672
|
-
|
|
9377
|
+
suggestCompletionBtn.disabled = wsState !== "Ready" || (!completionSuggestionInFlight && (uiBusy || !String(sourceTextEl.value || "").trim()));
|
|
9378
|
+
suggestCompletionBtn.textContent = completionSuggestionInFlight ? "Stop" : "Suggest";
|
|
9379
|
+
suggestCompletionBtn.title = completionSuggestionInFlight
|
|
9380
|
+
? "Stop the current suggestion request."
|
|
9381
|
+
: "Ask the current model for a short completion at the editor cursor. Shortcut: Option/Alt+Tab where available, or Cmd/Ctrl+Shift+Space from the editor.";
|
|
9382
|
+
}
|
|
9383
|
+
if (suggestCompletionOptionsBtn) suggestCompletionOptionsBtn.disabled = uiBusy || completionSuggestionInFlight;
|
|
9384
|
+
syncCompletionSuggestionContextUi();
|
|
8673
9385
|
if (openCompanionBtn) openCompanionBtn.disabled = uiBusy || wsState !== "Ready";
|
|
8674
9386
|
if (highlightSelect) highlightSelect.disabled = uiBusy;
|
|
8675
9387
|
if (lineNumbersSelect) lineNumbersSelect.disabled = uiBusy;
|
|
@@ -8857,7 +9569,10 @@
|
|
|
8857
9569
|
resourceDirInput.value = nextResourceDir;
|
|
8858
9570
|
updateSourceBadge();
|
|
8859
9571
|
}
|
|
8860
|
-
|
|
9572
|
+
const detectedPersistedPathLanguage = detectLanguageFromName(nextSourceState.path || nextSourceState.label || "");
|
|
9573
|
+
if (getDelimitedTextPreviewConfig(detectedPersistedPathLanguage)) {
|
|
9574
|
+
setEditorLanguage(detectedPersistedPathLanguage);
|
|
9575
|
+
} else if (typeof state.editorLanguage === "string" && state.editorLanguage.trim()) {
|
|
8861
9576
|
setEditorLanguage(state.editorLanguage.trim());
|
|
8862
9577
|
}
|
|
8863
9578
|
editorView = state.editorView === "preview" ? "preview" : "markdown";
|
|
@@ -9003,6 +9718,69 @@
|
|
|
9003
9718
|
}
|
|
9004
9719
|
}
|
|
9005
9720
|
|
|
9721
|
+
function readCompletionSuggestionContextMode() {
|
|
9722
|
+
try {
|
|
9723
|
+
const stored = window.localStorage ? String(window.localStorage.getItem(COMPLETION_CONTEXT_STORAGE_KEY) || "") : "";
|
|
9724
|
+
return stored === "session" ? "session" : "cursor";
|
|
9725
|
+
} catch {
|
|
9726
|
+
return "cursor";
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
9729
|
+
|
|
9730
|
+
function setCompletionSuggestionContextMode(mode) {
|
|
9731
|
+
completionSuggestionContextMode = mode === "session" ? "session" : "cursor";
|
|
9732
|
+
if (completionContextSelect) completionContextSelect.value = completionSuggestionContextMode;
|
|
9733
|
+
try {
|
|
9734
|
+
if (window.localStorage) window.localStorage.setItem(COMPLETION_CONTEXT_STORAGE_KEY, completionSuggestionContextMode);
|
|
9735
|
+
} catch {}
|
|
9736
|
+
setStatus(completionSuggestionContextMode === "session"
|
|
9737
|
+
? "Suggestions will include the latest response as context."
|
|
9738
|
+
: "Suggestions will use cursor-local editor context only.");
|
|
9739
|
+
}
|
|
9740
|
+
|
|
9741
|
+
function syncCompletionSuggestionContextUi() {
|
|
9742
|
+
if (completionContextSelect) completionContextSelect.value = completionSuggestionContextMode;
|
|
9743
|
+
if (suggestCompletionOptionsBtn) {
|
|
9744
|
+
suggestCompletionOptionsBtn.textContent = "Source & context";
|
|
9745
|
+
suggestCompletionOptionsBtn.title = completionSuggestionContextMode === "session"
|
|
9746
|
+
? "Document source, working directory, status, and suggestion context. Suggestions include editor plus latest response."
|
|
9747
|
+
: "Document source, working directory, status, and suggestion context. Suggestions use editor-only context.";
|
|
9748
|
+
suggestCompletionOptionsBtn.setAttribute("aria-label", suggestCompletionOptionsBtn.title);
|
|
9749
|
+
}
|
|
9750
|
+
document.querySelectorAll("[data-completion-context-mode]").forEach((button) => {
|
|
9751
|
+
if (!(button instanceof HTMLElement)) return;
|
|
9752
|
+
const mode = button.getAttribute("data-completion-context-mode") === "session" ? "session" : "cursor";
|
|
9753
|
+
const selected = mode === completionSuggestionContextMode;
|
|
9754
|
+
button.classList.toggle("is-selected", selected);
|
|
9755
|
+
button.setAttribute("aria-pressed", selected ? "true" : "false");
|
|
9756
|
+
button.textContent = (selected ? "✓ " : " ") + (mode === "session" ? "Editor + latest response" : "Editor only");
|
|
9757
|
+
});
|
|
9758
|
+
}
|
|
9759
|
+
|
|
9760
|
+
function trimCompletionContextText(text) {
|
|
9761
|
+
const value = String(text || "").trim();
|
|
9762
|
+
if (value.length <= COMPLETION_CONTEXT_MAX_CHARS) return value;
|
|
9763
|
+
return value.slice(value.length - COMPLETION_CONTEXT_MAX_CHARS);
|
|
9764
|
+
}
|
|
9765
|
+
|
|
9766
|
+
function getCompletionSuggestionContextText() {
|
|
9767
|
+
if (completionSuggestionContextMode !== "session") return "";
|
|
9768
|
+
const selected = getSelectedHistoryItem ? getSelectedHistoryItem() : null;
|
|
9769
|
+
const responseText = selected && typeof selected.markdown === "string" && selected.markdown.trim()
|
|
9770
|
+
? selected.markdown
|
|
9771
|
+
: latestResponseMarkdown;
|
|
9772
|
+
const parts = [];
|
|
9773
|
+
if (selected && typeof selected.promptTriggerText === "string" && selected.promptTriggerText.trim()) {
|
|
9774
|
+
parts.push("Latest request/steering:\n" + trimCompletionContextText(selected.promptTriggerText));
|
|
9775
|
+
} else if (selected && typeof selected.prompt === "string" && selected.prompt.trim()) {
|
|
9776
|
+
parts.push("Latest prompt:\n" + trimCompletionContextText(selected.prompt));
|
|
9777
|
+
}
|
|
9778
|
+
if (String(responseText || "").trim()) {
|
|
9779
|
+
parts.push("Latest response:\n" + trimCompletionContextText(responseText));
|
|
9780
|
+
}
|
|
9781
|
+
return trimCompletionContextText(parts.join("\n\n---\n\n"));
|
|
9782
|
+
}
|
|
9783
|
+
|
|
9006
9784
|
function hideCompletionSuggestion() {
|
|
9007
9785
|
completionSuggestionState = null;
|
|
9008
9786
|
if (completionSuggestionTextEl) completionSuggestionTextEl.textContent = "";
|
|
@@ -9073,10 +9851,29 @@
|
|
|
9073
9851
|
|| Boolean(completionSuggestionPanelEl && activeEl instanceof Element && completionSuggestionPanelEl.contains(activeEl));
|
|
9074
9852
|
}
|
|
9075
9853
|
|
|
9854
|
+
function cancelCompletionSuggestion() {
|
|
9855
|
+
if (!completionSuggestionInFlight || !completionSuggestionRequestId) {
|
|
9856
|
+
setStatus("No suggestion request is running.", "warning");
|
|
9857
|
+
return;
|
|
9858
|
+
}
|
|
9859
|
+
setStatus("Stopping suggestion…", "warning");
|
|
9860
|
+
const sent = sendMessage({
|
|
9861
|
+
type: "completion_suggestion_cancel_request",
|
|
9862
|
+
requestId: completionSuggestionRequestId,
|
|
9863
|
+
});
|
|
9864
|
+
if (!sent) {
|
|
9865
|
+
completionSuggestionInFlight = false;
|
|
9866
|
+
completionSuggestionRequestId = null;
|
|
9867
|
+
completionSuggestionPendingSnapshot = null;
|
|
9868
|
+
completionSuggestionRefocusEditorOnResult = false;
|
|
9869
|
+
syncActionButtons();
|
|
9870
|
+
}
|
|
9871
|
+
}
|
|
9872
|
+
|
|
9076
9873
|
function requestCompletionSuggestion() {
|
|
9077
9874
|
if (isEditorOnlyMode && !sourceTextEl) return;
|
|
9078
9875
|
if (completionSuggestionInFlight) {
|
|
9079
|
-
|
|
9876
|
+
cancelCompletionSuggestion();
|
|
9080
9877
|
return;
|
|
9081
9878
|
}
|
|
9082
9879
|
const text = String(sourceTextEl.value || "");
|
|
@@ -9086,6 +9883,7 @@
|
|
|
9086
9883
|
}
|
|
9087
9884
|
const selectionStart = typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : text.length;
|
|
9088
9885
|
const selectionEnd = typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : selectionStart;
|
|
9886
|
+
const contextText = getCompletionSuggestionContextText();
|
|
9089
9887
|
const requestId = makeRequestId();
|
|
9090
9888
|
completionSuggestionInFlight = true;
|
|
9091
9889
|
completionSuggestionRequestId = requestId;
|
|
@@ -9103,6 +9901,8 @@
|
|
|
9103
9901
|
language: editorLanguage || "",
|
|
9104
9902
|
label: sourceState && sourceState.label ? sourceState.label : "Studio editor",
|
|
9105
9903
|
path: sourceState && sourceState.path ? sourceState.path : undefined,
|
|
9904
|
+
contextMode: completionSuggestionContextMode,
|
|
9905
|
+
contextText: contextText || undefined,
|
|
9106
9906
|
});
|
|
9107
9907
|
if (!sent) {
|
|
9108
9908
|
completionSuggestionInFlight = false;
|
|
@@ -9675,6 +10475,7 @@
|
|
|
9675
10475
|
".diff", ".patch",
|
|
9676
10476
|
]);
|
|
9677
10477
|
const PREVIEW_LOCAL_IMAGE_LINK_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
10478
|
+
const PREVIEW_LOCAL_OFFICE_LINK_EXTENSIONS = new Set([".docx", ".odt"]);
|
|
9678
10479
|
const PREVIEW_LOCAL_TEXT_LINK_FILENAMES = new Set([
|
|
9679
10480
|
".dockerignore", ".editorconfig", ".env", ".env.example", ".eslintignore", ".gitattributes",
|
|
9680
10481
|
".gitignore", ".gitmodules", ".npmignore", ".prettierignore", "dockerfile", "gemfile",
|
|
@@ -9739,6 +10540,7 @@
|
|
|
9739
10540
|
if (ext === ".pdf") return "pdf";
|
|
9740
10541
|
if (PREVIEW_LOCAL_TEXT_LINK_EXTENSIONS.has(ext) || PREVIEW_LOCAL_TEXT_LINK_FILENAMES.has(name)) return "text";
|
|
9741
10542
|
if (PREVIEW_LOCAL_IMAGE_LINK_EXTENSIONS.has(ext)) return "image";
|
|
10543
|
+
if (PREVIEW_LOCAL_OFFICE_LINK_EXTENSIONS.has(ext)) return "office";
|
|
9742
10544
|
return "other";
|
|
9743
10545
|
}
|
|
9744
10546
|
|
|
@@ -9832,11 +10634,16 @@
|
|
|
9832
10634
|
};
|
|
9833
10635
|
if (kind === "pdf") {
|
|
9834
10636
|
appendPreviewLinkMenuButton(menu, "Open PDF preview", "open-pdf");
|
|
10637
|
+
appendPreviewLinkMenuButton(menu, "Open in new Studio tab", "open-preview-new");
|
|
9835
10638
|
} else if (kind === "text") {
|
|
9836
10639
|
appendPreviewLinkMenuButton(menu, "Open in new editor", "open-new");
|
|
9837
10640
|
appendPreviewLinkMenuButton(menu, "Open here", "open-here");
|
|
10641
|
+
} else if (kind === "office") {
|
|
10642
|
+
appendPreviewLinkMenuButton(menu, "Convert in new editor", "open-new");
|
|
10643
|
+
appendPreviewLinkMenuButton(menu, "Convert here", "open-here");
|
|
9838
10644
|
} else if (kind === "image") {
|
|
9839
10645
|
appendPreviewLinkMenuButton(menu, "Open image preview", "open-image");
|
|
10646
|
+
appendPreviewLinkMenuButton(menu, "Open in new Studio tab", "open-preview-new");
|
|
9840
10647
|
}
|
|
9841
10648
|
appendPreviewLinkMenuButton(menu, "Reveal in file manager", "reveal");
|
|
9842
10649
|
appendPreviewLinkMenuButton(menu, "Copy path", "copy-path");
|
|
@@ -9873,40 +10680,18 @@
|
|
|
9873
10680
|
}
|
|
9874
10681
|
|
|
9875
10682
|
async function openPreviewImageLink(href, title, contextOverride, pendingWindow) {
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
if (popup && popup.document && popup.document.body) {
|
|
9879
|
-
popup.document.title = "Opening image…";
|
|
9880
|
-
popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening image…</p>";
|
|
9881
|
-
}
|
|
9882
|
-
} catch {}
|
|
9883
|
-
try {
|
|
9884
|
-
const payload = await fetchStudioJson("/html-preview-resource", {
|
|
9885
|
-
query: getPreviewLinkResourceQuery(href, contextOverride),
|
|
9886
|
-
});
|
|
9887
|
-
const dataUrl = payload && typeof payload.dataUrl === "string" ? payload.dataUrl : "";
|
|
9888
|
-
if (!dataUrl) throw new Error("Studio did not return image data.");
|
|
9889
|
-
const safeTitle = escapeHtml(String(title || href || "Local image"));
|
|
9890
|
-
const safeSrc = escapeHtml(dataUrl);
|
|
9891
|
-
const html = "<!doctype html><html><head><meta charset='utf-8'><title>" + safeTitle + "</title>"
|
|
9892
|
-
+ "<style>body{margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee;font:13px -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;}img{max-width:100vw;max-height:100vh;object-fit:contain;}header{position:fixed;left:0;right:0;top:0;padding:8px 10px;background:rgba(0,0,0,.55);backdrop-filter:blur(6px);}</style>"
|
|
9893
|
-
+ "</head><body><header>" + safeTitle + "</header><img src='" + safeSrc + "' alt='" + safeTitle + "'></body></html>";
|
|
9894
|
-
if (popup && !popup.closed && popup.document) {
|
|
9895
|
-
popup.document.open();
|
|
9896
|
-
popup.document.write(html);
|
|
9897
|
-
popup.document.close();
|
|
9898
|
-
setStatus("Opened local image preview.", "success");
|
|
9899
|
-
return;
|
|
9900
|
-
}
|
|
9901
|
-
const opened = window.open(dataUrl, "_blank");
|
|
9902
|
-
if (!opened) throw new Error("Popup blocked while opening image preview.");
|
|
9903
|
-
setStatus("Opened local image preview.", "success");
|
|
9904
|
-
} catch (error) {
|
|
9905
|
-
if (popup && !popup.closed) {
|
|
9906
|
-
try { popup.close(); } catch {}
|
|
9907
|
-
}
|
|
9908
|
-
throw error;
|
|
10683
|
+
if (pendingWindow && !pendingWindow.closed) {
|
|
10684
|
+
try { pendingWindow.close(); } catch {}
|
|
9909
10685
|
}
|
|
10686
|
+
const payload = await fetchStudioJson("/html-preview-resource", {
|
|
10687
|
+
query: getPreviewLinkResourceQuery(href, contextOverride),
|
|
10688
|
+
});
|
|
10689
|
+
const dataUrl = payload && typeof payload.dataUrl === "string" ? payload.dataUrl : "";
|
|
10690
|
+
if (!dataUrl) throw new Error("Studio did not return image data.");
|
|
10691
|
+
if (!openStudioImageFocusViewer(dataUrl, title || href || "Local image")) {
|
|
10692
|
+
throw new Error("Could not open image focus view.");
|
|
10693
|
+
}
|
|
10694
|
+
setStatus("Opened local image preview.", "success");
|
|
9910
10695
|
}
|
|
9911
10696
|
|
|
9912
10697
|
function editorHasPotentialUnsavedContent() {
|
|
@@ -9916,7 +10701,33 @@
|
|
|
9916
10701
|
return true;
|
|
9917
10702
|
}
|
|
9918
10703
|
|
|
10704
|
+
function getPreviewOfficeConversionLabel(href) {
|
|
10705
|
+
const cleanPath = stripPreviewLocalLinkUrlSuffix(href || "");
|
|
10706
|
+
const rawName = cleanPath.split(/[\\/]/).pop() || cleanPath || "this document";
|
|
10707
|
+
try {
|
|
10708
|
+
return decodeURIComponent(rawName) || rawName;
|
|
10709
|
+
} catch {
|
|
10710
|
+
return rawName;
|
|
10711
|
+
}
|
|
10712
|
+
}
|
|
10713
|
+
|
|
10714
|
+
function confirmPreviewOfficeConversion(href, destination) {
|
|
10715
|
+
if (getPreviewLocalLinkKind(href) !== "office") return true;
|
|
10716
|
+
const label = getPreviewOfficeConversionLabel(href);
|
|
10717
|
+
const target = destination === "here"
|
|
10718
|
+
? "replace the current editor contents with an editable Markdown copy"
|
|
10719
|
+
: "open an editable Markdown copy in a new Studio tab";
|
|
10720
|
+
const confirmed = window.confirm(
|
|
10721
|
+
"Convert " + label + " to Markdown?\n\n"
|
|
10722
|
+
+ "Studio will use Pandoc to " + target + ". Some layout or formatting may change. "
|
|
10723
|
+
+ "The original DOCX/ODT file will not be overwritten, and edits will not round-trip back to it."
|
|
10724
|
+
);
|
|
10725
|
+
if (!confirmed) setStatus("Document conversion cancelled.", "warning");
|
|
10726
|
+
return confirmed;
|
|
10727
|
+
}
|
|
10728
|
+
|
|
9919
10729
|
async function openPreviewDocumentHere(href, contextOverride) {
|
|
10730
|
+
if (!confirmPreviewOfficeConversion(href, "here")) return;
|
|
9920
10731
|
if (editorHasPotentialUnsavedContent()) {
|
|
9921
10732
|
const confirmed = window.confirm("Replace the current editor contents with this linked file? Unsaved editor changes may be lost.");
|
|
9922
10733
|
if (!confirmed) return;
|
|
@@ -9926,18 +10737,29 @@
|
|
|
9926
10737
|
const path = typeof payload.path === "string" ? payload.path : "";
|
|
9927
10738
|
const label = typeof payload.label === "string" && payload.label.trim() ? payload.label.trim() : (path || "linked file");
|
|
9928
10739
|
const nextResourceDir = typeof payload.resourceDir === "string" ? normalizeStudioResourceDirValue(payload.resourceDir) : "";
|
|
10740
|
+
const converted = payload && payload.converted === true;
|
|
9929
10741
|
if (resourceDirInput && nextResourceDir) resourceDirInput.value = nextResourceDir;
|
|
9930
10742
|
setEditorText(payload.text, { preserveScroll: false, preserveSelection: false });
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
10743
|
+
if (converted) {
|
|
10744
|
+
setSourceState({ source: "blank", label, path: null });
|
|
10745
|
+
} else {
|
|
10746
|
+
setSourceState({ source: "file", label, path });
|
|
10747
|
+
markFileBackedBaseline(payload.text);
|
|
10748
|
+
}
|
|
10749
|
+
const detected = converted ? "markdown" : detectLanguageFromName(path || label);
|
|
9934
10750
|
if (detected) setEditorLanguage(detected);
|
|
9935
10751
|
setEditorView("markdown");
|
|
9936
10752
|
setActivePane("left");
|
|
9937
|
-
setStatus("Opened linked file in editor: " + label, "success");
|
|
10753
|
+
setStatus(converted ? ("Converted document into editor: " + label) : ("Opened linked file in editor: " + label), "success");
|
|
9938
10754
|
}
|
|
9939
10755
|
|
|
9940
10756
|
async function openPreviewDocumentInNewEditor(href, pendingWindow, contextOverride) {
|
|
10757
|
+
if (!confirmPreviewOfficeConversion(href, "new")) {
|
|
10758
|
+
if (pendingWindow && !pendingWindow.closed) {
|
|
10759
|
+
try { pendingWindow.close(); } catch {}
|
|
10760
|
+
}
|
|
10761
|
+
return;
|
|
10762
|
+
}
|
|
9941
10763
|
const popup = pendingWindow || window.open("", "_blank");
|
|
9942
10764
|
try {
|
|
9943
10765
|
if (popup && popup.document && popup.document.body) {
|
|
@@ -9955,12 +10777,44 @@
|
|
|
9955
10777
|
try {
|
|
9956
10778
|
popup.opener = null;
|
|
9957
10779
|
popup.location.href = targetUrl;
|
|
9958
|
-
setStatus("Opening linked file in a new editor.", "success");
|
|
10780
|
+
setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening linked file in a new editor.", "success");
|
|
10781
|
+
return;
|
|
10782
|
+
} catch {}
|
|
10783
|
+
}
|
|
10784
|
+
window.open(targetUrl, "_blank", "noopener");
|
|
10785
|
+
setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening linked file in a new editor.", "success");
|
|
10786
|
+
} catch (error) {
|
|
10787
|
+
if (popup && !popup.closed) {
|
|
10788
|
+
try { popup.close(); } catch {}
|
|
10789
|
+
}
|
|
10790
|
+
throw error;
|
|
10791
|
+
}
|
|
10792
|
+
}
|
|
10793
|
+
|
|
10794
|
+
async function openPreviewResourceInNewEditor(href, pendingWindow, contextOverride) {
|
|
10795
|
+
const popup = pendingWindow || window.open("", "_blank");
|
|
10796
|
+
try {
|
|
10797
|
+
if (popup && popup.document && popup.document.body) {
|
|
10798
|
+
popup.document.title = "Opening preview…";
|
|
10799
|
+
popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening preview…</p>";
|
|
10800
|
+
}
|
|
10801
|
+
} catch {}
|
|
10802
|
+
try {
|
|
10803
|
+
const payload = await fetchPreviewLocalLink("preview-url", href, contextOverride);
|
|
10804
|
+
const targetUrl = payload && typeof payload.relativeUrl === "string"
|
|
10805
|
+
? new URL(payload.relativeUrl, window.location.href).href
|
|
10806
|
+
: (payload && typeof payload.url === "string" ? payload.url : "");
|
|
10807
|
+
if (!targetUrl) throw new Error("Studio did not return a preview URL.");
|
|
10808
|
+
if (popup && !popup.closed) {
|
|
10809
|
+
try {
|
|
10810
|
+
popup.opener = null;
|
|
10811
|
+
popup.location.href = targetUrl;
|
|
10812
|
+
setStatus("Opening preview in a new Studio tab.", "success");
|
|
9959
10813
|
return;
|
|
9960
10814
|
} catch {}
|
|
9961
10815
|
}
|
|
9962
10816
|
window.open(targetUrl, "_blank", "noopener");
|
|
9963
|
-
setStatus("Opening
|
|
10817
|
+
setStatus("Opening preview in a new Studio tab.", "success");
|
|
9964
10818
|
} catch (error) {
|
|
9965
10819
|
if (popup && !popup.closed) {
|
|
9966
10820
|
try { popup.close(); } catch {}
|
|
@@ -9999,6 +10853,10 @@
|
|
|
9999
10853
|
await openPreviewDocumentInNewEditor(href, null, context);
|
|
10000
10854
|
return;
|
|
10001
10855
|
}
|
|
10856
|
+
if (action === "open-preview-new") {
|
|
10857
|
+
await openPreviewResourceInNewEditor(href, null, context);
|
|
10858
|
+
return;
|
|
10859
|
+
}
|
|
10002
10860
|
if (action === "open-here") {
|
|
10003
10861
|
await openPreviewDocumentHere(href, context);
|
|
10004
10862
|
return;
|
|
@@ -10033,14 +10891,13 @@
|
|
|
10033
10891
|
return;
|
|
10034
10892
|
}
|
|
10035
10893
|
if (kind === "image") {
|
|
10036
|
-
|
|
10037
|
-
void openPreviewImageLink(href, title, null, pendingWindow).catch((error) => {
|
|
10894
|
+
void openPreviewImageLink(href, title).catch((error) => {
|
|
10038
10895
|
setStatus((error && error.message) ? error.message : String(error || "Could not open linked image."), "warning");
|
|
10039
10896
|
});
|
|
10040
10897
|
return;
|
|
10041
10898
|
}
|
|
10042
|
-
if (kind === "text") {
|
|
10043
|
-
const pendingWindow = window.open("", "_blank");
|
|
10899
|
+
if (kind === "text" || kind === "office") {
|
|
10900
|
+
const pendingWindow = kind === "office" ? null : window.open("", "_blank");
|
|
10044
10901
|
void openPreviewDocumentInNewEditor(href, pendingWindow).catch((error) => {
|
|
10045
10902
|
setStatus((error && error.message) ? error.message : String(error || "Could not open linked file."), "warning");
|
|
10046
10903
|
});
|
|
@@ -11289,7 +12146,7 @@
|
|
|
11289
12146
|
}
|
|
11290
12147
|
|
|
11291
12148
|
function supportsCodePreviewCommentsForCurrentEditor() {
|
|
11292
|
-
return Boolean(editorLanguage) && editorLanguage !== "markdown" && editorLanguage !== "latex";
|
|
12149
|
+
return Boolean(editorLanguage) && editorLanguage !== "markdown" && editorLanguage !== "latex" && !getDelimitedTextPreviewConfig(editorLanguage);
|
|
11293
12150
|
}
|
|
11294
12151
|
|
|
11295
12152
|
function getCodePreviewCommentKind(language) {
|
|
@@ -11490,6 +12347,26 @@
|
|
|
11490
12347
|
return Boolean(shortcutsOverlayEl && !shortcutsOverlayEl.hidden);
|
|
11491
12348
|
}
|
|
11492
12349
|
|
|
12350
|
+
function handleShortcutsScrollShortcut(event) {
|
|
12351
|
+
if (!isShortcutsOpen() || !shortcutsBodyEl || !event) return false;
|
|
12352
|
+
if (isTextEntryShortcutTarget(event.target)) return false;
|
|
12353
|
+
const key = typeof event.key === "string" ? event.key : "";
|
|
12354
|
+
let delta = 0;
|
|
12355
|
+
let targetTop = null;
|
|
12356
|
+
if (key === "ArrowDown") delta = 42;
|
|
12357
|
+
else if (key === "ArrowUp") delta = -42;
|
|
12358
|
+
else if (key === "PageDown") delta = Math.max(120, Math.round((shortcutsBodyEl.clientHeight || 0) * 0.85));
|
|
12359
|
+
else if (key === "PageUp") delta = -Math.max(120, Math.round((shortcutsBodyEl.clientHeight || 0) * 0.85));
|
|
12360
|
+
else if (key === "Home") targetTop = 0;
|
|
12361
|
+
else if (key === "End") targetTop = shortcutsBodyEl.scrollHeight;
|
|
12362
|
+
else return false;
|
|
12363
|
+
event.preventDefault();
|
|
12364
|
+
event.stopPropagation();
|
|
12365
|
+
if (targetTop !== null) shortcutsBodyEl.scrollTop = targetTop;
|
|
12366
|
+
else shortcutsBodyEl.scrollTop += delta;
|
|
12367
|
+
return true;
|
|
12368
|
+
}
|
|
12369
|
+
|
|
11493
12370
|
function isScratchpadOpen() {
|
|
11494
12371
|
return Boolean(scratchpadOverlayEl && !scratchpadOverlayEl.hidden);
|
|
11495
12372
|
}
|
|
@@ -15725,7 +16602,8 @@
|
|
|
15725
16602
|
? window.requestAnimationFrame.bind(window)
|
|
15726
16603
|
: (cb) => window.setTimeout(cb, 16);
|
|
15727
16604
|
schedule(() => {
|
|
15728
|
-
if (
|
|
16605
|
+
if (shortcutsBodyEl && typeof shortcutsBodyEl.focus === "function") shortcutsBodyEl.focus({ preventScroll: true });
|
|
16606
|
+
else if (shortcutsCloseBtn && typeof shortcutsCloseBtn.focus === "function") shortcutsCloseBtn.focus();
|
|
15729
16607
|
});
|
|
15730
16608
|
}
|
|
15731
16609
|
|
|
@@ -15923,6 +16801,9 @@
|
|
|
15923
16801
|
if (editorView === "preview") {
|
|
15924
16802
|
scheduleSourcePreviewRender(0);
|
|
15925
16803
|
}
|
|
16804
|
+
if (rightView === "editor-preview") {
|
|
16805
|
+
scheduleResponseEditorPreviewRender(0);
|
|
16806
|
+
}
|
|
15926
16807
|
updateOutlineUi();
|
|
15927
16808
|
scheduleWorkspacePersistence();
|
|
15928
16809
|
}
|
|
@@ -17947,6 +18828,13 @@
|
|
|
17947
18828
|
requestCompletionSuggestion();
|
|
17948
18829
|
});
|
|
17949
18830
|
}
|
|
18831
|
+
if (completionContextSelect) {
|
|
18832
|
+
completionContextSelect.value = completionSuggestionContextMode;
|
|
18833
|
+
completionContextSelect.addEventListener("change", () => {
|
|
18834
|
+
setCompletionSuggestionContextMode(completionContextSelect.value);
|
|
18835
|
+
syncActionButtons();
|
|
18836
|
+
});
|
|
18837
|
+
}
|
|
17950
18838
|
if (completionSuggestionInsertBtn) {
|
|
17951
18839
|
completionSuggestionInsertBtn.addEventListener("click", () => {
|
|
17952
18840
|
insertCompletionSuggestion();
|