pi-studio 0.9.39 → 0.9.41
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 +12 -0
- package/client/studio-client.js +300 -249
- package/client/studio-navigation-helpers.js +530 -0
- package/index.ts +85 -27
- package/package.json +2 -1
- package/shared/studio-repl-tmux.js +28 -0
- package/shared/studio-tab-launcher.js +86 -0
package/client/studio-client.js
CHANGED
|
@@ -208,7 +208,12 @@
|
|
|
208
208
|
Numpad7: "repl",
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
+
const navigationHelpers = globalThis.PiStudioNavigationHelpers;
|
|
212
|
+
if (!navigationHelpers || typeof navigationHelpers.readPaneFocusTarget !== "function") {
|
|
213
|
+
throw new Error("Studio navigation helpers failed to load.");
|
|
214
|
+
}
|
|
211
215
|
const initialQueryParams = new URLSearchParams(window.location.search || "");
|
|
216
|
+
const initialPaneFocusTarget = navigationHelpers.readPaneFocusTarget(window.location);
|
|
212
217
|
const skipInitialWorkspaceRestore = initialQueryParams.get("skipWorkspaceRestore") === "1";
|
|
213
218
|
const explicitDocumentIdentityFromUrl = initialQueryParams.has("docId")
|
|
214
219
|
|| initialQueryParams.has("docSource")
|
|
@@ -264,7 +269,8 @@
|
|
|
264
269
|
let pendingRequestId = null;
|
|
265
270
|
let pendingKind = null;
|
|
266
271
|
let stickyStudioKind = null;
|
|
267
|
-
const
|
|
272
|
+
const pendingCompanionLaunches = new Map();
|
|
273
|
+
const activeStudioTabLaunches = new Set();
|
|
268
274
|
let sourceOriginSummaryEl = null;
|
|
269
275
|
let sourceResetOriginBtn = null;
|
|
270
276
|
let sourceOpenCurrentFileTabBtn = null;
|
|
@@ -1210,11 +1216,21 @@
|
|
|
1210
1216
|
|
|
1211
1217
|
function getStudioShortcutLabel(kind) {
|
|
1212
1218
|
const mac = isMacShortcutPlatform();
|
|
1213
|
-
if (kind === "repl-send") return mac ? "
|
|
1219
|
+
if (kind === "repl-send") return mac ? "⌃⇧↵" : "Ctrl+Shift+Enter";
|
|
1214
1220
|
if (kind === "run") return mac ? "⌘↵" : "Ctrl+Enter";
|
|
1215
1221
|
return "";
|
|
1216
1222
|
}
|
|
1217
1223
|
|
|
1224
|
+
function getStudioShortcutDescription(kind) {
|
|
1225
|
+
if (kind === "repl-send") {
|
|
1226
|
+
return isMacShortcutPlatform()
|
|
1227
|
+
? "Ctrl+Shift+Enter (Cmd+Shift+Enter also works where available)"
|
|
1228
|
+
: "Ctrl+Shift+Enter";
|
|
1229
|
+
}
|
|
1230
|
+
if (kind === "run") return "Cmd/Ctrl+Enter";
|
|
1231
|
+
return "";
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1218
1234
|
function withStudioShortcutLabel(label, kind) {
|
|
1219
1235
|
const shortcut = getStudioShortcutLabel(kind);
|
|
1220
1236
|
return shortcut ? (label + " " + shortcut) : label;
|
|
@@ -2036,8 +2052,8 @@
|
|
|
2036
2052
|
actionRequestId: null,
|
|
2037
2053
|
};
|
|
2038
2054
|
let fileBackedBaselineText = null;
|
|
2039
|
-
let activePane = "left";
|
|
2040
|
-
let paneFocusTarget =
|
|
2055
|
+
let activePane = initialPaneFocusTarget === "right" ? "right" : "left";
|
|
2056
|
+
let paneFocusTarget = initialPaneFocusTarget;
|
|
2041
2057
|
let paneSplitPercent = 50;
|
|
2042
2058
|
const PANE_SPLIT_STORAGE_KEY = "piStudio.paneSplitPercent";
|
|
2043
2059
|
const PANE_SPLIT_MIN_PERCENT = 20;
|
|
@@ -2565,17 +2581,12 @@
|
|
|
2565
2581
|
return;
|
|
2566
2582
|
}
|
|
2567
2583
|
closeStudioUiRefreshMenus();
|
|
2568
|
-
const targetUrl = buildAuthedStudioUrl("/", {
|
|
2569
|
-
mode: "editor-only",
|
|
2570
|
-
docSource: "file",
|
|
2571
|
-
docLabel: sourceState && sourceState.label ? sourceState.label : basenameForStudioPath(path),
|
|
2572
|
-
docPath: path,
|
|
2573
|
-
resourceDir: getCurrentResourceDirValue() || dirnameForDisplayPath(path),
|
|
2574
|
-
skipWorkspaceRestore: "1",
|
|
2575
|
-
});
|
|
2576
2584
|
try {
|
|
2577
|
-
|
|
2578
|
-
|
|
2585
|
+
openFileBackedStudioEditorTab(path, {
|
|
2586
|
+
label: sourceState && sourceState.label ? sourceState.label : basenameForStudioPath(path),
|
|
2587
|
+
resourceDir: getCurrentResourceDirValue() || dirnameForDisplayPath(path),
|
|
2588
|
+
});
|
|
2589
|
+
setStatus("Opening current file in a new editor tab.");
|
|
2579
2590
|
} catch (error) {
|
|
2580
2591
|
setStatus((error && error.message) ? error.message : String(error || "Could not open file tab."), "warning");
|
|
2581
2592
|
}
|
|
@@ -3854,6 +3865,14 @@
|
|
|
3854
3865
|
updatePaneFocusButtons();
|
|
3855
3866
|
}
|
|
3856
3867
|
|
|
3868
|
+
function persistPaneFocusUrlState() {
|
|
3869
|
+
try {
|
|
3870
|
+
navigationHelpers.replacePaneFocusUrlState(window, paneFocusTarget);
|
|
3871
|
+
} catch {
|
|
3872
|
+
// Ignore URL-state update failures; focus mode still works for this page lifetime.
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3857
3876
|
function snapshotStudioScrollablePositions() {
|
|
3858
3877
|
return [sourceTextEl, sourcePreviewEl, critiqueViewEl]
|
|
3859
3878
|
.filter((el) => el && typeof el.scrollTop === "number" && typeof el.scrollLeft === "number")
|
|
@@ -3908,6 +3927,7 @@
|
|
|
3908
3927
|
if (paneFocusTarget !== "off" && paneFocusTarget !== activePane) {
|
|
3909
3928
|
paneFocusTarget = activePane;
|
|
3910
3929
|
applyPaneFocusClasses();
|
|
3930
|
+
persistPaneFocusUrlState();
|
|
3911
3931
|
}
|
|
3912
3932
|
}
|
|
3913
3933
|
|
|
@@ -4048,6 +4068,7 @@
|
|
|
4048
4068
|
setActivePane(pane);
|
|
4049
4069
|
paneFocusTarget = pane;
|
|
4050
4070
|
applyPaneFocusClasses();
|
|
4071
|
+
persistPaneFocusUrlState();
|
|
4051
4072
|
setStatus("Focus mode: " + paneLabel(pane) + " pane. Toggle with F10 or Cmd/Ctrl+Esc.");
|
|
4052
4073
|
}
|
|
4053
4074
|
|
|
@@ -4055,6 +4076,7 @@
|
|
|
4055
4076
|
if (paneFocusTarget === activePane) {
|
|
4056
4077
|
paneFocusTarget = "off";
|
|
4057
4078
|
applyPaneFocusClasses();
|
|
4079
|
+
persistPaneFocusUrlState();
|
|
4058
4080
|
setStatus("Focus mode off.");
|
|
4059
4081
|
return;
|
|
4060
4082
|
}
|
|
@@ -4066,6 +4088,7 @@
|
|
|
4066
4088
|
if (paneFocusTarget === "off") return false;
|
|
4067
4089
|
paneFocusTarget = "off";
|
|
4068
4090
|
applyPaneFocusClasses();
|
|
4091
|
+
persistPaneFocusUrlState();
|
|
4069
4092
|
setStatus("Focus mode off.");
|
|
4070
4093
|
return true;
|
|
4071
4094
|
}
|
|
@@ -5708,7 +5731,7 @@
|
|
|
5708
5731
|
return source.replace(/<head\b[^>]*>/i, (match) => match + "\n" + headMarkup + "\n");
|
|
5709
5732
|
}
|
|
5710
5733
|
if (/<body\b[^>]*>/i.test(source)) {
|
|
5711
|
-
return source.replace(/<body\b/i, "<head>\n" + headMarkup + "\n</head>\n<body");
|
|
5734
|
+
return source.replace(/<body\b/i, () => "<head>\n" + headMarkup + "\n</head>\n<body");
|
|
5712
5735
|
}
|
|
5713
5736
|
if (/<html\b[^>]*>/i.test(source)) {
|
|
5714
5737
|
return source.replace(/<html\b[^>]*>/i, (match) => match + "\n<head>\n" + headMarkup + "\n</head>\n");
|
|
@@ -6045,8 +6068,7 @@
|
|
|
6045
6068
|
return;
|
|
6046
6069
|
}
|
|
6047
6070
|
if (kind === "text" || kind === "office") {
|
|
6048
|
-
|
|
6049
|
-
void openPreviewDocumentInNewEditor(context.href, pendingWindow, context).catch((error) => {
|
|
6071
|
+
void openPreviewDocumentInNewEditor(context.href, context).catch((error) => {
|
|
6050
6072
|
setStatus((error && error.message) ? error.message : String(error || "Could not open linked file."), "warning");
|
|
6051
6073
|
});
|
|
6052
6074
|
return;
|
|
@@ -8509,19 +8531,14 @@
|
|
|
8509
8531
|
async function exportRightPanePdf(options) {
|
|
8510
8532
|
const exportOptions = options && typeof options === "object" ? options : {};
|
|
8511
8533
|
const openTarget = exportOptions.openTarget === "studio" ? "studio" : "default";
|
|
8512
|
-
let
|
|
8513
|
-
if (openTarget === "studio") {
|
|
8514
|
-
studioPopup = openExportStudioPlaceholderWindow("PDF");
|
|
8515
|
-
}
|
|
8534
|
+
let studioLaunch = null;
|
|
8516
8535
|
if (uiBusy || previewExportInProgress) {
|
|
8517
|
-
closeExportStudioWindow(studioPopup);
|
|
8518
8536
|
setStatus("Studio is busy.", "warning");
|
|
8519
8537
|
return;
|
|
8520
8538
|
}
|
|
8521
8539
|
|
|
8522
8540
|
const token = getToken();
|
|
8523
8541
|
if (!token) {
|
|
8524
|
-
closeExportStudioWindow(studioPopup);
|
|
8525
8542
|
setStatus("Missing Studio token in URL. Re-run /studio.", "error");
|
|
8526
8543
|
return;
|
|
8527
8544
|
}
|
|
@@ -8529,20 +8546,17 @@
|
|
|
8529
8546
|
const exportingReplJournal = rightView === "repl";
|
|
8530
8547
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
8531
8548
|
if (!rightPaneShowsPreview && !exportingReplJournal) {
|
|
8532
|
-
closeExportStudioWindow(studioPopup);
|
|
8533
8549
|
setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export PDF.", "warning");
|
|
8534
8550
|
return;
|
|
8535
8551
|
}
|
|
8536
8552
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
8537
8553
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
8538
|
-
closeExportStudioWindow(studioPopup);
|
|
8539
8554
|
setStatus("No Studio REPL record entries to export for this session yet.", "warning");
|
|
8540
8555
|
return;
|
|
8541
8556
|
}
|
|
8542
8557
|
|
|
8543
8558
|
const htmlArtifactSource = exportingReplJournal ? "" : getRightPaneHtmlArtifactSource();
|
|
8544
8559
|
if (htmlArtifactSource) {
|
|
8545
|
-
closeExportStudioWindow(studioPopup);
|
|
8546
8560
|
setStatus("PDF export does not support interactive HTML previews yet. Export as HTML or use the browser print dialog inside the preview.", "warning");
|
|
8547
8561
|
return;
|
|
8548
8562
|
}
|
|
@@ -8553,7 +8567,6 @@
|
|
|
8553
8567
|
? prepareEditorTextForPdfExport(sourceTextEl.value)
|
|
8554
8568
|
: prepareEditorTextForPreview(latestResponseMarkdown));
|
|
8555
8569
|
if (!markdown || !markdown.trim()) {
|
|
8556
|
-
closeExportStudioWindow(studioPopup);
|
|
8557
8570
|
setStatus("Nothing to export yet.", "warning");
|
|
8558
8571
|
return;
|
|
8559
8572
|
}
|
|
@@ -8574,6 +8587,14 @@
|
|
|
8574
8587
|
filenameHint = stem + ".studio.pdf";
|
|
8575
8588
|
}
|
|
8576
8589
|
|
|
8590
|
+
if (openTarget === "studio") {
|
|
8591
|
+
try {
|
|
8592
|
+
studioLaunch = openPendingStudioTab("export");
|
|
8593
|
+
} catch (error) {
|
|
8594
|
+
setStatus((error && error.message) ? error.message : "Could not prepare a Studio export tab.", "error");
|
|
8595
|
+
return;
|
|
8596
|
+
}
|
|
8597
|
+
}
|
|
8577
8598
|
previewExportInProgress = true;
|
|
8578
8599
|
updateResultActionButtons();
|
|
8579
8600
|
setStatus(openTarget === "studio" ? "Exporting PDF for Studio…" : "Exporting PDF…", "warning");
|
|
@@ -8631,30 +8652,30 @@
|
|
|
8631
8652
|
}
|
|
8632
8653
|
|
|
8633
8654
|
if (openTarget === "studio") {
|
|
8634
|
-
const targetUrl = typeof payload.relativeUrl === "string"
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8655
|
+
const targetUrl = typeof payload.relativeUrl === "string" ? payload.relativeUrl : "";
|
|
8656
|
+
const openedStudio = Boolean(targetUrl);
|
|
8657
|
+
if (openedStudio) {
|
|
8658
|
+
navigatePendingStudioTab(studioLaunch, targetUrl);
|
|
8659
|
+
} else {
|
|
8660
|
+
failPendingStudioTab(studioLaunch, "Studio did not return a preview-tab URL for this PDF export.");
|
|
8640
8661
|
const viewerUrl = getStudioPdfViewerUrlForExportPayload(payload);
|
|
8641
8662
|
if (viewerUrl) openStudioPdfFocusViewer(viewerUrl, downloadName);
|
|
8642
8663
|
}
|
|
8643
8664
|
if (writeError) {
|
|
8644
8665
|
setStatus(openedStudio
|
|
8645
|
-
? "
|
|
8646
|
-
: "Exported PDF, but
|
|
8666
|
+
? "Opening exported PDF in a Studio preview tab, but could not write project file: " + writeError
|
|
8667
|
+
: "Exported PDF, but Studio did not return a preview-tab URL and could not write the project file: " + writeError,
|
|
8647
8668
|
"warning");
|
|
8648
8669
|
} else if (exportWarning) {
|
|
8649
8670
|
setStatus(openedStudio
|
|
8650
|
-
? "
|
|
8651
|
-
: "Exported PDF, but
|
|
8671
|
+
? "Opening exported PDF in a Studio preview tab with warning: " + exportWarning
|
|
8672
|
+
: "Exported PDF, but Studio did not return a preview-tab URL. Warning: " + exportWarning,
|
|
8652
8673
|
"warning");
|
|
8653
8674
|
} else {
|
|
8654
8675
|
setStatus(openedStudio
|
|
8655
|
-
? "
|
|
8656
|
-
: "Exported PDF, but
|
|
8657
|
-
openedStudio ?
|
|
8676
|
+
? "Opening exported PDF in a Studio preview tab: " + (exportPath || downloadName)
|
|
8677
|
+
: "Exported PDF, but Studio did not return a preview-tab URL.",
|
|
8678
|
+
openedStudio ? undefined : "warning");
|
|
8658
8679
|
}
|
|
8659
8680
|
return;
|
|
8660
8681
|
}
|
|
@@ -8694,6 +8715,9 @@
|
|
|
8694
8715
|
return;
|
|
8695
8716
|
}
|
|
8696
8717
|
|
|
8718
|
+
if (openTarget === "studio") {
|
|
8719
|
+
failPendingStudioTab(studioLaunch, "Studio returned a download instead of a PDF preview tab.");
|
|
8720
|
+
}
|
|
8697
8721
|
const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
|
|
8698
8722
|
const blob = await response.blob();
|
|
8699
8723
|
const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
|
|
@@ -8720,7 +8744,7 @@
|
|
|
8720
8744
|
setStatus("Exported PDF: " + downloadName, "success");
|
|
8721
8745
|
}
|
|
8722
8746
|
} catch (error) {
|
|
8723
|
-
|
|
8747
|
+
failPendingStudioTab(studioLaunch, "Studio could not prepare this PDF export tab. Return to the originating Studio page for details.");
|
|
8724
8748
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
8725
8749
|
setStatus("PDF export failed: " + detail, "error");
|
|
8726
8750
|
} finally {
|
|
@@ -8729,58 +8753,17 @@
|
|
|
8729
8753
|
}
|
|
8730
8754
|
}
|
|
8731
8755
|
|
|
8732
|
-
function openExportStudioPlaceholderWindow(formatLabel) {
|
|
8733
|
-
const label = String(formatLabel || "preview").trim() || "preview";
|
|
8734
|
-
let popup = null;
|
|
8735
|
-
try {
|
|
8736
|
-
popup = window.open("", "_blank");
|
|
8737
|
-
if (popup && popup.document && popup.document.body) {
|
|
8738
|
-
popup.document.title = "Opening " + label + " in Studio…";
|
|
8739
|
-
popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Exporting " + escapeHtml(label) + " and opening it in Studio…</p>";
|
|
8740
|
-
}
|
|
8741
|
-
} catch {
|
|
8742
|
-
popup = null;
|
|
8743
|
-
}
|
|
8744
|
-
return popup;
|
|
8745
|
-
}
|
|
8746
|
-
|
|
8747
|
-
function navigateExportStudioWindow(popup, targetUrl) {
|
|
8748
|
-
if (!targetUrl) return false;
|
|
8749
|
-
if (popup && !popup.closed) {
|
|
8750
|
-
try {
|
|
8751
|
-
popup.opener = null;
|
|
8752
|
-
popup.location.href = targetUrl;
|
|
8753
|
-
return true;
|
|
8754
|
-
} catch {}
|
|
8755
|
-
}
|
|
8756
|
-
try {
|
|
8757
|
-
return Boolean(window.open(targetUrl, "_blank", "noopener"));
|
|
8758
|
-
} catch {
|
|
8759
|
-
return false;
|
|
8760
|
-
}
|
|
8761
|
-
}
|
|
8762
|
-
|
|
8763
|
-
function closeExportStudioWindow(popup) {
|
|
8764
|
-
if (!popup || popup.closed) return;
|
|
8765
|
-
try { popup.close(); } catch {}
|
|
8766
|
-
}
|
|
8767
|
-
|
|
8768
8756
|
async function exportRightPaneHtml(options) {
|
|
8769
8757
|
const exportOptions = options && typeof options === "object" ? options : {};
|
|
8770
8758
|
const openTarget = exportOptions.openTarget === "studio" ? "studio" : "browser";
|
|
8771
|
-
let
|
|
8772
|
-
if (openTarget === "studio") {
|
|
8773
|
-
studioPopup = openExportStudioPlaceholderWindow("HTML");
|
|
8774
|
-
}
|
|
8759
|
+
let studioLaunch = null;
|
|
8775
8760
|
if (uiBusy || previewExportInProgress) {
|
|
8776
|
-
closeExportStudioWindow(studioPopup);
|
|
8777
8761
|
setStatus("Studio is busy.", "warning");
|
|
8778
8762
|
return;
|
|
8779
8763
|
}
|
|
8780
8764
|
|
|
8781
8765
|
const token = getToken();
|
|
8782
8766
|
if (!token) {
|
|
8783
|
-
closeExportStudioWindow(studioPopup);
|
|
8784
8767
|
setStatus("Missing Studio token in URL. Re-run /studio.", "error");
|
|
8785
8768
|
return;
|
|
8786
8769
|
}
|
|
@@ -8788,13 +8771,11 @@
|
|
|
8788
8771
|
const exportingReplJournal = rightView === "repl";
|
|
8789
8772
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
8790
8773
|
if (!rightPaneShowsPreview && !exportingReplJournal) {
|
|
8791
|
-
closeExportStudioWindow(studioPopup);
|
|
8792
8774
|
setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export HTML.", "warning");
|
|
8793
8775
|
return;
|
|
8794
8776
|
}
|
|
8795
8777
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
8796
8778
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
8797
|
-
closeExportStudioWindow(studioPopup);
|
|
8798
8779
|
setStatus("No Studio REPL record entries to export for this session yet.", "warning");
|
|
8799
8780
|
return;
|
|
8800
8781
|
}
|
|
@@ -8804,7 +8785,6 @@
|
|
|
8804
8785
|
? prepareEditorTextForHtmlExport(sourceTextEl.value)
|
|
8805
8786
|
: prepareEditorTextForPreview(latestResponseMarkdown)));
|
|
8806
8787
|
if (!markdown || !markdown.trim()) {
|
|
8807
|
-
closeExportStudioWindow(studioPopup);
|
|
8808
8788
|
setStatus("Nothing to export yet.", "warning");
|
|
8809
8789
|
return;
|
|
8810
8790
|
}
|
|
@@ -8827,6 +8807,14 @@
|
|
|
8827
8807
|
titleHint = stem + " preview";
|
|
8828
8808
|
}
|
|
8829
8809
|
|
|
8810
|
+
if (openTarget === "studio") {
|
|
8811
|
+
try {
|
|
8812
|
+
studioLaunch = openPendingStudioTab("export");
|
|
8813
|
+
} catch (error) {
|
|
8814
|
+
setStatus((error && error.message) ? error.message : "Could not prepare a Studio export tab.", "error");
|
|
8815
|
+
return;
|
|
8816
|
+
}
|
|
8817
|
+
}
|
|
8830
8818
|
previewExportInProgress = true;
|
|
8831
8819
|
updateResultActionButtons();
|
|
8832
8820
|
setStatus(openTarget === "studio" ? "Exporting HTML for Studio…" : "Exporting HTML…", "warning");
|
|
@@ -8885,26 +8873,28 @@
|
|
|
8885
8873
|
}
|
|
8886
8874
|
|
|
8887
8875
|
if (openTarget === "studio") {
|
|
8888
|
-
const targetUrl = typeof payload.relativeUrl === "string"
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8876
|
+
const targetUrl = typeof payload.relativeUrl === "string" ? payload.relativeUrl : "";
|
|
8877
|
+
const openedStudio = Boolean(targetUrl);
|
|
8878
|
+
if (openedStudio) {
|
|
8879
|
+
navigatePendingStudioTab(studioLaunch, targetUrl);
|
|
8880
|
+
} else {
|
|
8881
|
+
failPendingStudioTab(studioLaunch, "Studio did not return an editor URL for this HTML export.");
|
|
8882
|
+
}
|
|
8893
8883
|
if (writeError) {
|
|
8894
8884
|
setStatus(openedStudio
|
|
8895
|
-
? "
|
|
8896
|
-
: "Exported HTML for Studio, but
|
|
8885
|
+
? "Opening exported HTML in Studio as an unsaved copy; could not write project file: " + writeError
|
|
8886
|
+
: "Exported HTML for Studio, but Studio did not return an editor URL and the project file could not be written: " + writeError,
|
|
8897
8887
|
"warning");
|
|
8898
8888
|
} else if (exportWarning) {
|
|
8899
8889
|
setStatus(openedStudio
|
|
8900
|
-
? "
|
|
8901
|
-
: "Exported HTML for Studio, but
|
|
8890
|
+
? "Opening exported HTML in Studio with warning: " + exportWarning
|
|
8891
|
+
: "Exported HTML for Studio, but Studio did not return an editor URL. Warning: " + exportWarning,
|
|
8902
8892
|
"warning");
|
|
8903
8893
|
} else {
|
|
8904
8894
|
setStatus(openedStudio
|
|
8905
|
-
? "
|
|
8906
|
-
:
|
|
8907
|
-
openedStudio ?
|
|
8895
|
+
? "Opening exported HTML in Studio: " + (exportPath || downloadName)
|
|
8896
|
+
: "Exported HTML, but Studio did not receive an editor URL.",
|
|
8897
|
+
openedStudio ? undefined : "warning");
|
|
8908
8898
|
}
|
|
8909
8899
|
return;
|
|
8910
8900
|
}
|
|
@@ -8944,7 +8934,7 @@
|
|
|
8944
8934
|
return;
|
|
8945
8935
|
}
|
|
8946
8936
|
|
|
8947
|
-
|
|
8937
|
+
failPendingStudioTab(studioLaunch, "Studio returned a download instead of an HTML editor tab.");
|
|
8948
8938
|
const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
|
|
8949
8939
|
const blob = await response.blob();
|
|
8950
8940
|
const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
|
|
@@ -8971,7 +8961,7 @@
|
|
|
8971
8961
|
setStatus("Exported HTML: " + downloadName, "success");
|
|
8972
8962
|
}
|
|
8973
8963
|
} catch (error) {
|
|
8974
|
-
|
|
8964
|
+
failPendingStudioTab(studioLaunch, "Studio could not prepare this HTML export tab. Return to the originating Studio page for details.");
|
|
8975
8965
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
8976
8966
|
setStatus("HTML export failed: " + detail, "error");
|
|
8977
8967
|
} finally {
|
|
@@ -10349,11 +10339,19 @@
|
|
|
10349
10339
|
return;
|
|
10350
10340
|
}
|
|
10351
10341
|
if (action === "open-new") {
|
|
10352
|
-
|
|
10342
|
+
if (kind === "text" && isLikelyAbsoluteStudioPath(path)) {
|
|
10343
|
+
openFileBackedStudioEditorTab(path, {
|
|
10344
|
+
label: basenameForStudioPath(path),
|
|
10345
|
+
resourceDir: fileBrowserState.rootDir || getCurrentResourceDirValue() || dirnameForDisplayPath(path),
|
|
10346
|
+
});
|
|
10347
|
+
setStatus("Opening file-backed document in a new editor.");
|
|
10348
|
+
return;
|
|
10349
|
+
}
|
|
10350
|
+
await openPreviewDocumentInNewEditor(path, getFileBrowserLocalLinkContext());
|
|
10353
10351
|
return;
|
|
10354
10352
|
}
|
|
10355
10353
|
if (action === "open-preview-new") {
|
|
10356
|
-
await openPreviewResourceInNewEditor(path,
|
|
10354
|
+
await openPreviewResourceInNewEditor(path, getFileBrowserLocalLinkContext());
|
|
10357
10355
|
return;
|
|
10358
10356
|
}
|
|
10359
10357
|
if (action === "copy-path" || action === "copy-root" || action === "copy-current") {
|
|
@@ -10663,13 +10661,8 @@
|
|
|
10663
10661
|
const hostname = parsed.hostname.toLowerCase();
|
|
10664
10662
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("Unsupported preview protocol.");
|
|
10665
10663
|
if (hostname !== "127.0.0.1" && hostname !== "localhost" && hostname !== "::1") throw new Error("Preview URL is not loopback-only.");
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
setStatus("Browser blocked the Quarto preview tab. Allow pop-ups and try again.", "warning");
|
|
10669
|
-
return false;
|
|
10670
|
-
}
|
|
10671
|
-
try { opened.opener = null; } catch {}
|
|
10672
|
-
setStatus("Opening Quarto preview in a browser tab.", "success");
|
|
10664
|
+
openStudioTabDirect(parsed.href);
|
|
10665
|
+
setStatus("Opening Quarto preview in a browser tab.");
|
|
10673
10666
|
return true;
|
|
10674
10667
|
} catch (error) {
|
|
10675
10668
|
setStatus("Could not open Quarto preview: " + ((error && error.message) ? error.message : String(error)), "error");
|
|
@@ -10679,12 +10672,8 @@
|
|
|
10679
10672
|
|
|
10680
10673
|
function openStudioQuartoInstallationInstructions() {
|
|
10681
10674
|
try {
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
setStatus("Browser blocked the Quarto installation page.", "warning");
|
|
10685
|
-
return;
|
|
10686
|
-
}
|
|
10687
|
-
try { opened.opener = null; } catch {}
|
|
10675
|
+
openStudioTabDirect("https://quarto.org/docs/get-started/");
|
|
10676
|
+
setStatus("Opening Quarto installation instructions.");
|
|
10688
10677
|
} catch (error) {
|
|
10689
10678
|
setStatus("Could not open Quarto installation instructions.", "warning");
|
|
10690
10679
|
}
|
|
@@ -12437,6 +12426,99 @@
|
|
|
12437
12426
|
return pathname + "?" + params.toString();
|
|
12438
12427
|
}
|
|
12439
12428
|
|
|
12429
|
+
function openStudioTabDirect(targetUrl) {
|
|
12430
|
+
navigationHelpers.openStudioTabDirect(window, targetUrl);
|
|
12431
|
+
debugTrace("studio_tab_launch", { event: "direct-open-requested" });
|
|
12432
|
+
}
|
|
12433
|
+
|
|
12434
|
+
function removeActiveStudioTabLaunch(launch) {
|
|
12435
|
+
if (launch) activeStudioTabLaunches.delete(launch);
|
|
12436
|
+
}
|
|
12437
|
+
|
|
12438
|
+
function openPendingStudioTab(kind) {
|
|
12439
|
+
const token = getToken();
|
|
12440
|
+
if (!token) throw new Error("Missing Studio token in URL.");
|
|
12441
|
+
const launch = navigationHelpers.createPendingStudioLaunch({
|
|
12442
|
+
window,
|
|
12443
|
+
token,
|
|
12444
|
+
kind,
|
|
12445
|
+
onEvent(event, detail) {
|
|
12446
|
+
debugTrace("studio_tab_launch", {
|
|
12447
|
+
event,
|
|
12448
|
+
launchId: detail && detail.launchId ? detail.launchId : null,
|
|
12449
|
+
kind: detail && detail.kind ? detail.kind : kind,
|
|
12450
|
+
state: detail && detail.state ? detail.state : null,
|
|
12451
|
+
terminalType: detail && detail.terminalType ? detail.terminalType : null,
|
|
12452
|
+
ok: detail && typeof detail.ok === "boolean" ? detail.ok : undefined,
|
|
12453
|
+
});
|
|
12454
|
+
},
|
|
12455
|
+
onReadyTimeout() {
|
|
12456
|
+
setStatus("No new tab has acknowledged yet. The operation is still running; Studio will not open a fallback tab.", "warning");
|
|
12457
|
+
},
|
|
12458
|
+
onDeliveryTimeout(controller) {
|
|
12459
|
+
removeActiveStudioTabLaunch(controller);
|
|
12460
|
+
setStatus("Studio could not confirm delivery to the new tab. No replacement tab was opened.", "warning");
|
|
12461
|
+
},
|
|
12462
|
+
onAccepted(result) {
|
|
12463
|
+
removeActiveStudioTabLaunch(result && result.controller);
|
|
12464
|
+
if (result && result.ok === false) {
|
|
12465
|
+
setStatus("The pending tab rejected the Studio navigation target.", "error");
|
|
12466
|
+
}
|
|
12467
|
+
},
|
|
12468
|
+
onOpenError() {
|
|
12469
|
+
setStatus("The browser rejected the new-tab request. Studio will not open a fallback tab.", "warning");
|
|
12470
|
+
},
|
|
12471
|
+
});
|
|
12472
|
+
if (!launch) {
|
|
12473
|
+
throw new Error("This browser could not prepare a Studio tab.");
|
|
12474
|
+
}
|
|
12475
|
+
activeStudioTabLaunches.add(launch);
|
|
12476
|
+
return launch;
|
|
12477
|
+
}
|
|
12478
|
+
|
|
12479
|
+
function navigatePendingStudioTab(launch, relativeUrl) {
|
|
12480
|
+
if (!launch || typeof launch.navigate !== "function") {
|
|
12481
|
+
throw new Error("Studio did not prepare a pending tab for this operation.");
|
|
12482
|
+
}
|
|
12483
|
+
const token = getToken();
|
|
12484
|
+
const normalized = navigationHelpers.normalizeStudioRelativeTarget(relativeUrl, window.location, token);
|
|
12485
|
+
return launch.navigate(normalized);
|
|
12486
|
+
}
|
|
12487
|
+
|
|
12488
|
+
function failPendingStudioTab(launch, message) {
|
|
12489
|
+
if (!launch) return false;
|
|
12490
|
+
return launch.fail(message || "Studio could not prepare this tab. Return to the originating Studio page for details.");
|
|
12491
|
+
}
|
|
12492
|
+
|
|
12493
|
+
function abandonAllStudioTabLaunches(message) {
|
|
12494
|
+
const launches = Array.from(activeStudioTabLaunches);
|
|
12495
|
+
activeStudioTabLaunches.clear();
|
|
12496
|
+
launches.forEach((launch) => {
|
|
12497
|
+
try { launch.abandon(message || "The originating Studio page was closed."); } catch {}
|
|
12498
|
+
});
|
|
12499
|
+
}
|
|
12500
|
+
|
|
12501
|
+
function buildFileBackedStudioEditorUrl(path, options) {
|
|
12502
|
+
const cleanPath = stripPreviewLocalLinkUrlSuffix(path || "").trim();
|
|
12503
|
+
if (!isLikelyAbsoluteStudioPath(cleanPath)) {
|
|
12504
|
+
throw new Error("Opening a file-backed Studio tab requires an absolute local path.");
|
|
12505
|
+
}
|
|
12506
|
+
const config = options && typeof options === "object" ? options : {};
|
|
12507
|
+
return buildAuthedStudioUrl("/", {
|
|
12508
|
+
mode: "editor-only",
|
|
12509
|
+
docSource: "file",
|
|
12510
|
+
docLabel: config.label || basenameForStudioPath(cleanPath),
|
|
12511
|
+
docPath: cleanPath,
|
|
12512
|
+
resourceDir: normalizeStudioResourceDirValue(config.resourceDir || "") || dirnameForDisplayPath(cleanPath),
|
|
12513
|
+
skipWorkspaceRestore: "1",
|
|
12514
|
+
});
|
|
12515
|
+
}
|
|
12516
|
+
|
|
12517
|
+
function openFileBackedStudioEditorTab(path, options) {
|
|
12518
|
+
const targetUrl = buildFileBackedStudioEditorUrl(path, options);
|
|
12519
|
+
openStudioTabDirect(targetUrl);
|
|
12520
|
+
}
|
|
12521
|
+
|
|
12440
12522
|
function updateStudioDocumentUrlState(state) {
|
|
12441
12523
|
try {
|
|
12442
12524
|
const currentUrl = new URL(window.location.href);
|
|
@@ -12713,10 +12795,7 @@
|
|
|
12713
12795
|
return true;
|
|
12714
12796
|
}
|
|
12715
12797
|
|
|
12716
|
-
async function openPreviewImageLink(href, title, contextOverride
|
|
12717
|
-
if (pendingWindow && !pendingWindow.closed) {
|
|
12718
|
-
try { pendingWindow.close(); } catch {}
|
|
12719
|
-
}
|
|
12798
|
+
async function openPreviewImageLink(href, title, contextOverride) {
|
|
12720
12799
|
const payload = await fetchStudioJson("/html-preview-resource", {
|
|
12721
12800
|
query: getPreviewLinkResourceQuery(href, contextOverride),
|
|
12722
12801
|
});
|
|
@@ -12802,73 +12881,33 @@
|
|
|
12802
12881
|
: (path ? ("Opened file-backed document in editor: " + label) : ("Opened linked file copy in editor: " + label)), "success");
|
|
12803
12882
|
}
|
|
12804
12883
|
|
|
12805
|
-
async function openPreviewDocumentInNewEditor(href,
|
|
12806
|
-
if (!confirmPreviewOfficeConversion(href, "new"))
|
|
12807
|
-
|
|
12808
|
-
try { pendingWindow.close(); } catch {}
|
|
12809
|
-
}
|
|
12810
|
-
return;
|
|
12811
|
-
}
|
|
12812
|
-
const popup = pendingWindow || window.open("", "_blank");
|
|
12813
|
-
const openingLabel = getPreviewLocalLinkKind(href) === "office" ? "Opening converted document…" : "Opening file tab…";
|
|
12814
|
-
try {
|
|
12815
|
-
if (popup && popup.document && popup.document.body) {
|
|
12816
|
-
popup.document.title = openingLabel;
|
|
12817
|
-
popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">" + escapeHtml(openingLabel) + "</p>";
|
|
12818
|
-
}
|
|
12819
|
-
} catch {}
|
|
12884
|
+
async function openPreviewDocumentInNewEditor(href, contextOverride) {
|
|
12885
|
+
if (!confirmPreviewOfficeConversion(href, "new")) return;
|
|
12886
|
+
let launch = null;
|
|
12820
12887
|
try {
|
|
12888
|
+
launch = openPendingStudioTab("document");
|
|
12821
12889
|
const payload = await fetchPreviewLocalLink("editor-url", href, contextOverride);
|
|
12822
|
-
const
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
if (popup && !popup.closed) {
|
|
12827
|
-
try {
|
|
12828
|
-
popup.opener = null;
|
|
12829
|
-
popup.location.href = targetUrl;
|
|
12830
|
-
setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.", "success");
|
|
12831
|
-
return;
|
|
12832
|
-
} catch {}
|
|
12833
|
-
}
|
|
12834
|
-
window.open(targetUrl, "_blank", "noopener");
|
|
12835
|
-
setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.", "success");
|
|
12890
|
+
const relativeUrl = payload && typeof payload.relativeUrl === "string" ? payload.relativeUrl : "";
|
|
12891
|
+
if (!relativeUrl) throw new Error("Studio did not return an editor URL.");
|
|
12892
|
+
navigatePendingStudioTab(launch, relativeUrl);
|
|
12893
|
+
setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.");
|
|
12836
12894
|
} catch (error) {
|
|
12837
|
-
|
|
12838
|
-
try { popup.close(); } catch {}
|
|
12839
|
-
}
|
|
12895
|
+
failPendingStudioTab(launch, "Studio could not prepare this document tab. Return to the originating Studio page for details.");
|
|
12840
12896
|
throw error;
|
|
12841
12897
|
}
|
|
12842
12898
|
}
|
|
12843
12899
|
|
|
12844
|
-
async function openPreviewResourceInNewEditor(href,
|
|
12845
|
-
|
|
12846
|
-
try {
|
|
12847
|
-
if (popup && popup.document && popup.document.body) {
|
|
12848
|
-
popup.document.title = "Opening preview…";
|
|
12849
|
-
popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening preview…</p>";
|
|
12850
|
-
}
|
|
12851
|
-
} catch {}
|
|
12900
|
+
async function openPreviewResourceInNewEditor(href, contextOverride) {
|
|
12901
|
+
let launch = null;
|
|
12852
12902
|
try {
|
|
12903
|
+
launch = openPendingStudioTab("preview");
|
|
12853
12904
|
const payload = await fetchPreviewLocalLink("preview-url", href, contextOverride);
|
|
12854
|
-
const
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
if (popup && !popup.closed) {
|
|
12859
|
-
try {
|
|
12860
|
-
popup.opener = null;
|
|
12861
|
-
popup.location.href = targetUrl;
|
|
12862
|
-
setStatus("Opening preview in a new Studio tab.", "success");
|
|
12863
|
-
return;
|
|
12864
|
-
} catch {}
|
|
12865
|
-
}
|
|
12866
|
-
window.open(targetUrl, "_blank", "noopener");
|
|
12867
|
-
setStatus("Opening preview in a new Studio tab.", "success");
|
|
12905
|
+
const relativeUrl = payload && typeof payload.relativeUrl === "string" ? payload.relativeUrl : "";
|
|
12906
|
+
if (!relativeUrl) throw new Error("Studio did not return a preview URL.");
|
|
12907
|
+
navigatePendingStudioTab(launch, relativeUrl);
|
|
12908
|
+
setStatus("Opening preview in a new Studio tab.");
|
|
12868
12909
|
} catch (error) {
|
|
12869
|
-
|
|
12870
|
-
try { popup.close(); } catch {}
|
|
12871
|
-
}
|
|
12910
|
+
failPendingStudioTab(launch, "Studio could not prepare this preview tab. Return to the originating Studio page for details.");
|
|
12872
12911
|
throw error;
|
|
12873
12912
|
}
|
|
12874
12913
|
}
|
|
@@ -12900,11 +12939,11 @@
|
|
|
12900
12939
|
return;
|
|
12901
12940
|
}
|
|
12902
12941
|
if (action === "open-new") {
|
|
12903
|
-
await openPreviewDocumentInNewEditor(href,
|
|
12942
|
+
await openPreviewDocumentInNewEditor(href, context);
|
|
12904
12943
|
return;
|
|
12905
12944
|
}
|
|
12906
12945
|
if (action === "open-preview-new") {
|
|
12907
|
-
await openPreviewResourceInNewEditor(href,
|
|
12946
|
+
await openPreviewResourceInNewEditor(href, context);
|
|
12908
12947
|
return;
|
|
12909
12948
|
}
|
|
12910
12949
|
if (action === "open-here") {
|
|
@@ -12947,8 +12986,7 @@
|
|
|
12947
12986
|
return;
|
|
12948
12987
|
}
|
|
12949
12988
|
if (kind === "text" || kind === "office") {
|
|
12950
|
-
|
|
12951
|
-
void openPreviewDocumentInNewEditor(href, pendingWindow).catch((error) => {
|
|
12989
|
+
void openPreviewDocumentInNewEditor(href).catch((error) => {
|
|
12952
12990
|
setStatus((error && error.message) ? error.message : String(error || "Could not open linked file."), "warning");
|
|
12953
12991
|
});
|
|
12954
12992
|
return;
|
|
@@ -19188,6 +19226,7 @@
|
|
|
19188
19226
|
const canQueueSteering = studioRunChainActive && !critiqueIsStop;
|
|
19189
19227
|
const hasReplSession = Boolean(getActiveReplSessionForCurrentRuntime());
|
|
19190
19228
|
const showReplSend = rightView === "repl";
|
|
19229
|
+
const replSendShortcut = getStudioShortcutDescription("repl-send");
|
|
19191
19230
|
|
|
19192
19231
|
if (isEditorOnlyMode) {
|
|
19193
19232
|
if (sendRunBtn) {
|
|
@@ -19209,8 +19248,8 @@
|
|
|
19209
19248
|
sendReplBtn.textContent = showReplSend ? withStudioShortcutLabel(replSendMode === "literate" ? "Send selection/chunks" : "Send to REPL", "repl-send") : "Send to REPL";
|
|
19210
19249
|
sendReplBtn.title = hasReplSession
|
|
19211
19250
|
? (replSendMode === "literate"
|
|
19212
|
-
? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut:
|
|
19213
|
-
: "Raw send: selection, or full editor if no selection. Shortcut:
|
|
19251
|
+
? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut: " + replSendShortcut + "."
|
|
19252
|
+
: "Raw send: selection, or full editor if no selection. Shortcut: " + replSendShortcut + ".")
|
|
19214
19253
|
: "Start or select a REPL session in the right pane first.";
|
|
19215
19254
|
const replActionLine = sendReplBtn.closest(".repl-action-line");
|
|
19216
19255
|
if (replActionLine instanceof HTMLElement) replActionLine.hidden = !showReplSend;
|
|
@@ -19270,8 +19309,8 @@
|
|
|
19270
19309
|
sendReplBtn.textContent = showReplSend ? withStudioShortcutLabel(replSendMode === "literate" ? "Send selection/chunks" : "Send to REPL", "repl-send") : "Send to REPL";
|
|
19271
19310
|
sendReplBtn.title = hasReplSession
|
|
19272
19311
|
? (replSendMode === "literate"
|
|
19273
|
-
? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut:
|
|
19274
|
-
: "Raw send: selection, or full editor if no selection. Shortcut:
|
|
19312
|
+
? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut: " + replSendShortcut + "."
|
|
19313
|
+
: "Raw send: selection, or full editor if no selection. Shortcut: " + replSendShortcut + ".")
|
|
19275
19314
|
: "Start or select a REPL session in the right pane first.";
|
|
19276
19315
|
const replActionLine = sendReplBtn.closest(".repl-action-line");
|
|
19277
19316
|
if (replActionLine instanceof HTMLElement) replActionLine.hidden = !showReplSend;
|
|
@@ -20109,24 +20148,31 @@
|
|
|
20109
20148
|
|
|
20110
20149
|
if (message.type === "editor_only_ready") {
|
|
20111
20150
|
const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
20112
|
-
if (responseRequestId
|
|
20113
|
-
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
stickyStudioKind = null;
|
|
20151
|
+
if (!responseRequestId || !pendingCompanionLaunches.has(responseRequestId)) return;
|
|
20152
|
+
if (pendingRequestId !== responseRequestId || pendingKind !== "open_editor_only") {
|
|
20153
|
+
failPendingCompanionLaunch(responseRequestId, "This companion editor request was superseded.");
|
|
20154
|
+
return;
|
|
20117
20155
|
}
|
|
20156
|
+
pendingRequestId = null;
|
|
20157
|
+
pendingKind = null;
|
|
20158
|
+
clearArmedTitleAttention(responseRequestId);
|
|
20159
|
+
stickyStudioKind = null;
|
|
20118
20160
|
setBusy(false);
|
|
20119
20161
|
setWsState("Ready");
|
|
20120
|
-
const
|
|
20121
|
-
|
|
20162
|
+
const relativeUrl = resolveCompanionEditorRelativeUrl(message);
|
|
20163
|
+
let queued = false;
|
|
20164
|
+
try {
|
|
20165
|
+
queued = navigatePendingCompanionLaunch(responseRequestId, relativeUrl);
|
|
20166
|
+
} catch (error) {
|
|
20167
|
+
setStatus((error && error.message) ? error.message : "Studio rejected the companion editor URL.", "error");
|
|
20168
|
+
return;
|
|
20169
|
+
}
|
|
20122
20170
|
const readyMessage = typeof message.message === "string" && message.message.trim()
|
|
20123
20171
|
? message.message.trim()
|
|
20124
|
-
: "
|
|
20172
|
+
: "Opening an editor tab with a detached copy of the current editor text.";
|
|
20125
20173
|
setStatus(
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
: (targetUrl ? "Editor tab ready: " + targetUrl : "Editor tab is ready, but Studio did not receive a URL."),
|
|
20129
|
-
opened ? "success" : "warning",
|
|
20174
|
+
queued ? readyMessage : "Editor tab is ready, but Studio did not receive a valid relative URL.",
|
|
20175
|
+
queued ? undefined : "warning",
|
|
20130
20176
|
);
|
|
20131
20177
|
return;
|
|
20132
20178
|
}
|
|
@@ -20203,7 +20249,7 @@
|
|
|
20203
20249
|
|
|
20204
20250
|
if (message.type === "busy") {
|
|
20205
20251
|
if (typeof message.requestId === "string") {
|
|
20206
|
-
|
|
20252
|
+
failPendingCompanionLaunch(message.requestId, "Studio could not start the companion editor because another request was busy.");
|
|
20207
20253
|
}
|
|
20208
20254
|
if (message.requestId && pendingRequestId === message.requestId) {
|
|
20209
20255
|
if (pendingKind === "compact") {
|
|
@@ -20224,7 +20270,7 @@
|
|
|
20224
20270
|
|
|
20225
20271
|
if (message.type === "error") {
|
|
20226
20272
|
if (typeof message.requestId === "string") {
|
|
20227
|
-
|
|
20273
|
+
failPendingCompanionLaunch(message.requestId, "Studio could not prepare the companion editor. Return to the originating Studio page for details.");
|
|
20228
20274
|
}
|
|
20229
20275
|
if (message.requestId && pendingRequestId === message.requestId) {
|
|
20230
20276
|
if (pendingKind === "compact") {
|
|
@@ -20359,6 +20405,7 @@
|
|
|
20359
20405
|
quartoPreviewCheckRequestId = null;
|
|
20360
20406
|
quartoPreviewCheckSourcePath = "";
|
|
20361
20407
|
quartoPreviewActionRequestId = null;
|
|
20408
|
+
failAllPendingCompanionLaunches("The originating Studio connection was lost before the companion editor was ready.");
|
|
20362
20409
|
if (rightView === "editor-quarto-preview") renderQuartoPreviewView();
|
|
20363
20410
|
setBusy(true);
|
|
20364
20411
|
|
|
@@ -20449,73 +20496,70 @@
|
|
|
20449
20496
|
return requestId;
|
|
20450
20497
|
}
|
|
20451
20498
|
|
|
20452
|
-
function
|
|
20499
|
+
function openPendingCompanionLaunch(requestId) {
|
|
20453
20500
|
if (!requestId) return null;
|
|
20454
|
-
|
|
20455
|
-
|
|
20456
|
-
|
|
20457
|
-
if (companionWindow && companionWindow.document && companionWindow.document.body) {
|
|
20458
|
-
companionWindow.document.title = "Opening editor tab…";
|
|
20459
|
-
companionWindow.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening editor tab…</p>";
|
|
20460
|
-
}
|
|
20461
|
-
} catch {
|
|
20462
|
-
companionWindow = null;
|
|
20463
|
-
}
|
|
20464
|
-
if (companionWindow) {
|
|
20465
|
-
pendingCompanionWindows.set(requestId, companionWindow);
|
|
20466
|
-
}
|
|
20467
|
-
return companionWindow;
|
|
20501
|
+
const launch = openPendingStudioTab("document");
|
|
20502
|
+
pendingCompanionLaunches.set(requestId, launch);
|
|
20503
|
+
return launch;
|
|
20468
20504
|
}
|
|
20469
20505
|
|
|
20470
|
-
function
|
|
20471
|
-
if (!requestId || !
|
|
20472
|
-
const
|
|
20473
|
-
|
|
20474
|
-
return
|
|
20506
|
+
function takePendingCompanionLaunch(requestId) {
|
|
20507
|
+
if (!requestId || !pendingCompanionLaunches.has(requestId)) return { found: false, launch: null };
|
|
20508
|
+
const launch = pendingCompanionLaunches.get(requestId) || null;
|
|
20509
|
+
pendingCompanionLaunches.delete(requestId);
|
|
20510
|
+
return { found: true, launch };
|
|
20475
20511
|
}
|
|
20476
20512
|
|
|
20477
|
-
function
|
|
20478
|
-
const
|
|
20479
|
-
if (!
|
|
20480
|
-
|
|
20481
|
-
|
|
20482
|
-
|
|
20513
|
+
function failPendingCompanionLaunch(requestId, message) {
|
|
20514
|
+
const pending = takePendingCompanionLaunch(requestId);
|
|
20515
|
+
if (!pending.found) return false;
|
|
20516
|
+
failPendingStudioTab(
|
|
20517
|
+
pending.launch,
|
|
20518
|
+
message || "Studio could not prepare this companion editor. Return to the originating Studio page for details.",
|
|
20519
|
+
);
|
|
20520
|
+
return true;
|
|
20483
20521
|
}
|
|
20484
20522
|
|
|
20485
|
-
function
|
|
20486
|
-
const
|
|
20487
|
-
|
|
20488
|
-
try {
|
|
20489
|
-
return new URL(relativeUrl, window.location.href).href;
|
|
20490
|
-
} catch {}
|
|
20491
|
-
}
|
|
20492
|
-
return message && typeof message.url === "string" ? message.url : "";
|
|
20523
|
+
function failAllPendingCompanionLaunches(message) {
|
|
20524
|
+
const requestIds = Array.from(pendingCompanionLaunches.keys());
|
|
20525
|
+
requestIds.forEach((requestId) => failPendingCompanionLaunch(requestId, message));
|
|
20493
20526
|
}
|
|
20494
20527
|
|
|
20495
|
-
function
|
|
20496
|
-
|
|
20497
|
-
|
|
20528
|
+
function resolveCompanionEditorRelativeUrl(message) {
|
|
20529
|
+
return message && typeof message.relativeUrl === "string" ? message.relativeUrl : "";
|
|
20530
|
+
}
|
|
20531
|
+
|
|
20532
|
+
function navigatePendingCompanionLaunch(requestId, relativeUrl) {
|
|
20533
|
+
const pending = takePendingCompanionLaunch(requestId);
|
|
20534
|
+
if (!pending.found || !relativeUrl) {
|
|
20535
|
+
if (pending.found) {
|
|
20536
|
+
failPendingStudioTab(pending.launch, "Studio did not return a companion editor target.");
|
|
20537
|
+
}
|
|
20498
20538
|
return false;
|
|
20499
20539
|
}
|
|
20500
|
-
const companionWindow = takePendingCompanionWindow(requestId);
|
|
20501
|
-
if (companionWindow && !companionWindow.closed) {
|
|
20502
|
-
try {
|
|
20503
|
-
companionWindow.opener = null;
|
|
20504
|
-
companionWindow.location.href = targetUrl;
|
|
20505
|
-
return true;
|
|
20506
|
-
} catch {}
|
|
20507
|
-
}
|
|
20508
20540
|
try {
|
|
20509
|
-
|
|
20510
|
-
|
|
20511
|
-
|
|
20541
|
+
navigatePendingStudioTab(pending.launch, relativeUrl);
|
|
20542
|
+
return true;
|
|
20543
|
+
} catch (error) {
|
|
20544
|
+
failPendingStudioTab(pending.launch, "Studio rejected the companion editor target.");
|
|
20545
|
+
throw error;
|
|
20512
20546
|
}
|
|
20513
20547
|
}
|
|
20514
20548
|
|
|
20515
20549
|
function requestOpenEditorOnlyDocument(content, options) {
|
|
20516
20550
|
const requestId = beginUiAction("open_editor_only");
|
|
20517
20551
|
if (!requestId) return false;
|
|
20518
|
-
|
|
20552
|
+
try {
|
|
20553
|
+
openPendingCompanionLaunch(requestId);
|
|
20554
|
+
} catch (error) {
|
|
20555
|
+
pendingRequestId = null;
|
|
20556
|
+
pendingKind = null;
|
|
20557
|
+
stickyStudioKind = null;
|
|
20558
|
+
setBusy(false);
|
|
20559
|
+
setWsState("Ready");
|
|
20560
|
+
setStatus((error && error.message) ? error.message : "Could not prepare a companion editor tab.", "error");
|
|
20561
|
+
return false;
|
|
20562
|
+
}
|
|
20519
20563
|
const config = options && typeof options === "object" ? options : {};
|
|
20520
20564
|
const sent = sendMessage({
|
|
20521
20565
|
type: "open_editor_only_request",
|
|
@@ -20526,9 +20570,10 @@
|
|
|
20526
20570
|
resourceDir: config.resourceDir || undefined,
|
|
20527
20571
|
});
|
|
20528
20572
|
if (!sent) {
|
|
20529
|
-
|
|
20573
|
+
failPendingCompanionLaunch(requestId, "Studio could not send the companion editor request.");
|
|
20530
20574
|
pendingRequestId = null;
|
|
20531
20575
|
pendingKind = null;
|
|
20576
|
+
stickyStudioKind = null;
|
|
20532
20577
|
setBusy(false);
|
|
20533
20578
|
}
|
|
20534
20579
|
return sent;
|
|
@@ -20660,6 +20705,10 @@
|
|
|
20660
20705
|
|
|
20661
20706
|
updatePaneFocusButtons();
|
|
20662
20707
|
window.addEventListener("keydown", handlePaneShortcut);
|
|
20708
|
+
window.addEventListener("pagehide", () => {
|
|
20709
|
+
pendingCompanionLaunches.clear();
|
|
20710
|
+
abandonAllStudioTabLaunches("The originating Studio page was closed before this tab finished opening.");
|
|
20711
|
+
});
|
|
20663
20712
|
window.addEventListener("beforeunload", () => {
|
|
20664
20713
|
stopFooterSpinner();
|
|
20665
20714
|
flushWorkspacePersistence();
|
|
@@ -21818,7 +21867,9 @@
|
|
|
21818
21867
|
if (initialSourceState.path) markFileBackedBaseline(sourceTextEl.value);
|
|
21819
21868
|
refreshResponseUi();
|
|
21820
21869
|
updateAnnotatedReplyHeaderButton();
|
|
21821
|
-
setActivePane("left");
|
|
21870
|
+
setActivePane(initialPaneFocusTarget === "off" ? "left" : initialPaneFocusTarget);
|
|
21871
|
+
applyPaneFocusClasses();
|
|
21872
|
+
persistPaneFocusUrlState();
|
|
21822
21873
|
|
|
21823
21874
|
const storedEditorHighlightEnabled = readStoredEditorHighlightEnabled();
|
|
21824
21875
|
const initialHighlightEnabled = storedEditorHighlightEnabled ?? Boolean(highlightSelect && highlightSelect.value !== "off");
|