pi-studio 0.9.40 → 0.9.42
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 +11 -0
- package/README.md +2 -1
- package/client/studio-client.js +284 -244
- package/client/studio-navigation-helpers.js +530 -0
- package/index.ts +86 -56
- package/package.json +2 -1
- package/shared/studio-browser-launcher.js +139 -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;
|
|
@@ -2046,8 +2052,8 @@
|
|
|
2046
2052
|
actionRequestId: null,
|
|
2047
2053
|
};
|
|
2048
2054
|
let fileBackedBaselineText = null;
|
|
2049
|
-
let activePane = "left";
|
|
2050
|
-
let paneFocusTarget =
|
|
2055
|
+
let activePane = initialPaneFocusTarget === "right" ? "right" : "left";
|
|
2056
|
+
let paneFocusTarget = initialPaneFocusTarget;
|
|
2051
2057
|
let paneSplitPercent = 50;
|
|
2052
2058
|
const PANE_SPLIT_STORAGE_KEY = "piStudio.paneSplitPercent";
|
|
2053
2059
|
const PANE_SPLIT_MIN_PERCENT = 20;
|
|
@@ -2575,17 +2581,12 @@
|
|
|
2575
2581
|
return;
|
|
2576
2582
|
}
|
|
2577
2583
|
closeStudioUiRefreshMenus();
|
|
2578
|
-
const targetUrl = buildAuthedStudioUrl("/", {
|
|
2579
|
-
mode: "editor-only",
|
|
2580
|
-
docSource: "file",
|
|
2581
|
-
docLabel: sourceState && sourceState.label ? sourceState.label : basenameForStudioPath(path),
|
|
2582
|
-
docPath: path,
|
|
2583
|
-
resourceDir: getCurrentResourceDirValue() || dirnameForDisplayPath(path),
|
|
2584
|
-
skipWorkspaceRestore: "1",
|
|
2585
|
-
});
|
|
2586
2584
|
try {
|
|
2587
|
-
|
|
2588
|
-
|
|
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.");
|
|
2589
2590
|
} catch (error) {
|
|
2590
2591
|
setStatus((error && error.message) ? error.message : String(error || "Could not open file tab."), "warning");
|
|
2591
2592
|
}
|
|
@@ -3864,6 +3865,14 @@
|
|
|
3864
3865
|
updatePaneFocusButtons();
|
|
3865
3866
|
}
|
|
3866
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
|
+
|
|
3867
3876
|
function snapshotStudioScrollablePositions() {
|
|
3868
3877
|
return [sourceTextEl, sourcePreviewEl, critiqueViewEl]
|
|
3869
3878
|
.filter((el) => el && typeof el.scrollTop === "number" && typeof el.scrollLeft === "number")
|
|
@@ -3918,6 +3927,7 @@
|
|
|
3918
3927
|
if (paneFocusTarget !== "off" && paneFocusTarget !== activePane) {
|
|
3919
3928
|
paneFocusTarget = activePane;
|
|
3920
3929
|
applyPaneFocusClasses();
|
|
3930
|
+
persistPaneFocusUrlState();
|
|
3921
3931
|
}
|
|
3922
3932
|
}
|
|
3923
3933
|
|
|
@@ -4058,6 +4068,7 @@
|
|
|
4058
4068
|
setActivePane(pane);
|
|
4059
4069
|
paneFocusTarget = pane;
|
|
4060
4070
|
applyPaneFocusClasses();
|
|
4071
|
+
persistPaneFocusUrlState();
|
|
4061
4072
|
setStatus("Focus mode: " + paneLabel(pane) + " pane. Toggle with F10 or Cmd/Ctrl+Esc.");
|
|
4062
4073
|
}
|
|
4063
4074
|
|
|
@@ -4065,6 +4076,7 @@
|
|
|
4065
4076
|
if (paneFocusTarget === activePane) {
|
|
4066
4077
|
paneFocusTarget = "off";
|
|
4067
4078
|
applyPaneFocusClasses();
|
|
4079
|
+
persistPaneFocusUrlState();
|
|
4068
4080
|
setStatus("Focus mode off.");
|
|
4069
4081
|
return;
|
|
4070
4082
|
}
|
|
@@ -4076,6 +4088,7 @@
|
|
|
4076
4088
|
if (paneFocusTarget === "off") return false;
|
|
4077
4089
|
paneFocusTarget = "off";
|
|
4078
4090
|
applyPaneFocusClasses();
|
|
4091
|
+
persistPaneFocusUrlState();
|
|
4079
4092
|
setStatus("Focus mode off.");
|
|
4080
4093
|
return true;
|
|
4081
4094
|
}
|
|
@@ -5718,7 +5731,7 @@
|
|
|
5718
5731
|
return source.replace(/<head\b[^>]*>/i, (match) => match + "\n" + headMarkup + "\n");
|
|
5719
5732
|
}
|
|
5720
5733
|
if (/<body\b[^>]*>/i.test(source)) {
|
|
5721
|
-
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");
|
|
5722
5735
|
}
|
|
5723
5736
|
if (/<html\b[^>]*>/i.test(source)) {
|
|
5724
5737
|
return source.replace(/<html\b[^>]*>/i, (match) => match + "\n<head>\n" + headMarkup + "\n</head>\n");
|
|
@@ -6055,8 +6068,7 @@
|
|
|
6055
6068
|
return;
|
|
6056
6069
|
}
|
|
6057
6070
|
if (kind === "text" || kind === "office") {
|
|
6058
|
-
|
|
6059
|
-
void openPreviewDocumentInNewEditor(context.href, pendingWindow, context).catch((error) => {
|
|
6071
|
+
void openPreviewDocumentInNewEditor(context.href, context).catch((error) => {
|
|
6060
6072
|
setStatus((error && error.message) ? error.message : String(error || "Could not open linked file."), "warning");
|
|
6061
6073
|
});
|
|
6062
6074
|
return;
|
|
@@ -8519,19 +8531,14 @@
|
|
|
8519
8531
|
async function exportRightPanePdf(options) {
|
|
8520
8532
|
const exportOptions = options && typeof options === "object" ? options : {};
|
|
8521
8533
|
const openTarget = exportOptions.openTarget === "studio" ? "studio" : "default";
|
|
8522
|
-
let
|
|
8523
|
-
if (openTarget === "studio") {
|
|
8524
|
-
studioPopup = openExportStudioPlaceholderWindow("PDF");
|
|
8525
|
-
}
|
|
8534
|
+
let studioLaunch = null;
|
|
8526
8535
|
if (uiBusy || previewExportInProgress) {
|
|
8527
|
-
closeExportStudioWindow(studioPopup);
|
|
8528
8536
|
setStatus("Studio is busy.", "warning");
|
|
8529
8537
|
return;
|
|
8530
8538
|
}
|
|
8531
8539
|
|
|
8532
8540
|
const token = getToken();
|
|
8533
8541
|
if (!token) {
|
|
8534
|
-
closeExportStudioWindow(studioPopup);
|
|
8535
8542
|
setStatus("Missing Studio token in URL. Re-run /studio.", "error");
|
|
8536
8543
|
return;
|
|
8537
8544
|
}
|
|
@@ -8539,20 +8546,17 @@
|
|
|
8539
8546
|
const exportingReplJournal = rightView === "repl";
|
|
8540
8547
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
8541
8548
|
if (!rightPaneShowsPreview && !exportingReplJournal) {
|
|
8542
|
-
closeExportStudioWindow(studioPopup);
|
|
8543
8549
|
setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export PDF.", "warning");
|
|
8544
8550
|
return;
|
|
8545
8551
|
}
|
|
8546
8552
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
8547
8553
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
8548
|
-
closeExportStudioWindow(studioPopup);
|
|
8549
8554
|
setStatus("No Studio REPL record entries to export for this session yet.", "warning");
|
|
8550
8555
|
return;
|
|
8551
8556
|
}
|
|
8552
8557
|
|
|
8553
8558
|
const htmlArtifactSource = exportingReplJournal ? "" : getRightPaneHtmlArtifactSource();
|
|
8554
8559
|
if (htmlArtifactSource) {
|
|
8555
|
-
closeExportStudioWindow(studioPopup);
|
|
8556
8560
|
setStatus("PDF export does not support interactive HTML previews yet. Export as HTML or use the browser print dialog inside the preview.", "warning");
|
|
8557
8561
|
return;
|
|
8558
8562
|
}
|
|
@@ -8563,7 +8567,6 @@
|
|
|
8563
8567
|
? prepareEditorTextForPdfExport(sourceTextEl.value)
|
|
8564
8568
|
: prepareEditorTextForPreview(latestResponseMarkdown));
|
|
8565
8569
|
if (!markdown || !markdown.trim()) {
|
|
8566
|
-
closeExportStudioWindow(studioPopup);
|
|
8567
8570
|
setStatus("Nothing to export yet.", "warning");
|
|
8568
8571
|
return;
|
|
8569
8572
|
}
|
|
@@ -8584,6 +8587,14 @@
|
|
|
8584
8587
|
filenameHint = stem + ".studio.pdf";
|
|
8585
8588
|
}
|
|
8586
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
|
+
}
|
|
8587
8598
|
previewExportInProgress = true;
|
|
8588
8599
|
updateResultActionButtons();
|
|
8589
8600
|
setStatus(openTarget === "studio" ? "Exporting PDF for Studio…" : "Exporting PDF…", "warning");
|
|
@@ -8641,30 +8652,30 @@
|
|
|
8641
8652
|
}
|
|
8642
8653
|
|
|
8643
8654
|
if (openTarget === "studio") {
|
|
8644
|
-
const targetUrl = typeof payload.relativeUrl === "string"
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
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.");
|
|
8650
8661
|
const viewerUrl = getStudioPdfViewerUrlForExportPayload(payload);
|
|
8651
8662
|
if (viewerUrl) openStudioPdfFocusViewer(viewerUrl, downloadName);
|
|
8652
8663
|
}
|
|
8653
8664
|
if (writeError) {
|
|
8654
8665
|
setStatus(openedStudio
|
|
8655
|
-
? "
|
|
8656
|
-
: "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,
|
|
8657
8668
|
"warning");
|
|
8658
8669
|
} else if (exportWarning) {
|
|
8659
8670
|
setStatus(openedStudio
|
|
8660
|
-
? "
|
|
8661
|
-
: "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,
|
|
8662
8673
|
"warning");
|
|
8663
8674
|
} else {
|
|
8664
8675
|
setStatus(openedStudio
|
|
8665
|
-
? "
|
|
8666
|
-
: "Exported PDF, but
|
|
8667
|
-
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");
|
|
8668
8679
|
}
|
|
8669
8680
|
return;
|
|
8670
8681
|
}
|
|
@@ -8704,6 +8715,9 @@
|
|
|
8704
8715
|
return;
|
|
8705
8716
|
}
|
|
8706
8717
|
|
|
8718
|
+
if (openTarget === "studio") {
|
|
8719
|
+
failPendingStudioTab(studioLaunch, "Studio returned a download instead of a PDF preview tab.");
|
|
8720
|
+
}
|
|
8707
8721
|
const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
|
|
8708
8722
|
const blob = await response.blob();
|
|
8709
8723
|
const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
|
|
@@ -8730,7 +8744,7 @@
|
|
|
8730
8744
|
setStatus("Exported PDF: " + downloadName, "success");
|
|
8731
8745
|
}
|
|
8732
8746
|
} catch (error) {
|
|
8733
|
-
|
|
8747
|
+
failPendingStudioTab(studioLaunch, "Studio could not prepare this PDF export tab. Return to the originating Studio page for details.");
|
|
8734
8748
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
8735
8749
|
setStatus("PDF export failed: " + detail, "error");
|
|
8736
8750
|
} finally {
|
|
@@ -8739,58 +8753,17 @@
|
|
|
8739
8753
|
}
|
|
8740
8754
|
}
|
|
8741
8755
|
|
|
8742
|
-
function openExportStudioPlaceholderWindow(formatLabel) {
|
|
8743
|
-
const label = String(formatLabel || "preview").trim() || "preview";
|
|
8744
|
-
let popup = null;
|
|
8745
|
-
try {
|
|
8746
|
-
popup = window.open("", "_blank");
|
|
8747
|
-
if (popup && popup.document && popup.document.body) {
|
|
8748
|
-
popup.document.title = "Opening " + label + " in Studio…";
|
|
8749
|
-
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>";
|
|
8750
|
-
}
|
|
8751
|
-
} catch {
|
|
8752
|
-
popup = null;
|
|
8753
|
-
}
|
|
8754
|
-
return popup;
|
|
8755
|
-
}
|
|
8756
|
-
|
|
8757
|
-
function navigateExportStudioWindow(popup, targetUrl) {
|
|
8758
|
-
if (!targetUrl) return false;
|
|
8759
|
-
if (popup && !popup.closed) {
|
|
8760
|
-
try {
|
|
8761
|
-
popup.opener = null;
|
|
8762
|
-
popup.location.href = targetUrl;
|
|
8763
|
-
return true;
|
|
8764
|
-
} catch {}
|
|
8765
|
-
}
|
|
8766
|
-
try {
|
|
8767
|
-
return Boolean(window.open(targetUrl, "_blank", "noopener"));
|
|
8768
|
-
} catch {
|
|
8769
|
-
return false;
|
|
8770
|
-
}
|
|
8771
|
-
}
|
|
8772
|
-
|
|
8773
|
-
function closeExportStudioWindow(popup) {
|
|
8774
|
-
if (!popup || popup.closed) return;
|
|
8775
|
-
try { popup.close(); } catch {}
|
|
8776
|
-
}
|
|
8777
|
-
|
|
8778
8756
|
async function exportRightPaneHtml(options) {
|
|
8779
8757
|
const exportOptions = options && typeof options === "object" ? options : {};
|
|
8780
8758
|
const openTarget = exportOptions.openTarget === "studio" ? "studio" : "browser";
|
|
8781
|
-
let
|
|
8782
|
-
if (openTarget === "studio") {
|
|
8783
|
-
studioPopup = openExportStudioPlaceholderWindow("HTML");
|
|
8784
|
-
}
|
|
8759
|
+
let studioLaunch = null;
|
|
8785
8760
|
if (uiBusy || previewExportInProgress) {
|
|
8786
|
-
closeExportStudioWindow(studioPopup);
|
|
8787
8761
|
setStatus("Studio is busy.", "warning");
|
|
8788
8762
|
return;
|
|
8789
8763
|
}
|
|
8790
8764
|
|
|
8791
8765
|
const token = getToken();
|
|
8792
8766
|
if (!token) {
|
|
8793
|
-
closeExportStudioWindow(studioPopup);
|
|
8794
8767
|
setStatus("Missing Studio token in URL. Re-run /studio.", "error");
|
|
8795
8768
|
return;
|
|
8796
8769
|
}
|
|
@@ -8798,13 +8771,11 @@
|
|
|
8798
8771
|
const exportingReplJournal = rightView === "repl";
|
|
8799
8772
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
8800
8773
|
if (!rightPaneShowsPreview && !exportingReplJournal) {
|
|
8801
|
-
closeExportStudioWindow(studioPopup);
|
|
8802
8774
|
setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export HTML.", "warning");
|
|
8803
8775
|
return;
|
|
8804
8776
|
}
|
|
8805
8777
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
8806
8778
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
8807
|
-
closeExportStudioWindow(studioPopup);
|
|
8808
8779
|
setStatus("No Studio REPL record entries to export for this session yet.", "warning");
|
|
8809
8780
|
return;
|
|
8810
8781
|
}
|
|
@@ -8814,7 +8785,6 @@
|
|
|
8814
8785
|
? prepareEditorTextForHtmlExport(sourceTextEl.value)
|
|
8815
8786
|
: prepareEditorTextForPreview(latestResponseMarkdown)));
|
|
8816
8787
|
if (!markdown || !markdown.trim()) {
|
|
8817
|
-
closeExportStudioWindow(studioPopup);
|
|
8818
8788
|
setStatus("Nothing to export yet.", "warning");
|
|
8819
8789
|
return;
|
|
8820
8790
|
}
|
|
@@ -8837,6 +8807,14 @@
|
|
|
8837
8807
|
titleHint = stem + " preview";
|
|
8838
8808
|
}
|
|
8839
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
|
+
}
|
|
8840
8818
|
previewExportInProgress = true;
|
|
8841
8819
|
updateResultActionButtons();
|
|
8842
8820
|
setStatus(openTarget === "studio" ? "Exporting HTML for Studio…" : "Exporting HTML…", "warning");
|
|
@@ -8895,26 +8873,28 @@
|
|
|
8895
8873
|
}
|
|
8896
8874
|
|
|
8897
8875
|
if (openTarget === "studio") {
|
|
8898
|
-
const targetUrl = typeof payload.relativeUrl === "string"
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
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
|
+
}
|
|
8903
8883
|
if (writeError) {
|
|
8904
8884
|
setStatus(openedStudio
|
|
8905
|
-
? "
|
|
8906
|
-
: "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,
|
|
8907
8887
|
"warning");
|
|
8908
8888
|
} else if (exportWarning) {
|
|
8909
8889
|
setStatus(openedStudio
|
|
8910
|
-
? "
|
|
8911
|
-
: "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,
|
|
8912
8892
|
"warning");
|
|
8913
8893
|
} else {
|
|
8914
8894
|
setStatus(openedStudio
|
|
8915
|
-
? "
|
|
8916
|
-
:
|
|
8917
|
-
openedStudio ?
|
|
8895
|
+
? "Opening exported HTML in Studio: " + (exportPath || downloadName)
|
|
8896
|
+
: "Exported HTML, but Studio did not receive an editor URL.",
|
|
8897
|
+
openedStudio ? undefined : "warning");
|
|
8918
8898
|
}
|
|
8919
8899
|
return;
|
|
8920
8900
|
}
|
|
@@ -8954,7 +8934,7 @@
|
|
|
8954
8934
|
return;
|
|
8955
8935
|
}
|
|
8956
8936
|
|
|
8957
|
-
|
|
8937
|
+
failPendingStudioTab(studioLaunch, "Studio returned a download instead of an HTML editor tab.");
|
|
8958
8938
|
const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
|
|
8959
8939
|
const blob = await response.blob();
|
|
8960
8940
|
const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
|
|
@@ -8981,7 +8961,7 @@
|
|
|
8981
8961
|
setStatus("Exported HTML: " + downloadName, "success");
|
|
8982
8962
|
}
|
|
8983
8963
|
} catch (error) {
|
|
8984
|
-
|
|
8964
|
+
failPendingStudioTab(studioLaunch, "Studio could not prepare this HTML export tab. Return to the originating Studio page for details.");
|
|
8985
8965
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
8986
8966
|
setStatus("HTML export failed: " + detail, "error");
|
|
8987
8967
|
} finally {
|
|
@@ -10359,11 +10339,19 @@
|
|
|
10359
10339
|
return;
|
|
10360
10340
|
}
|
|
10361
10341
|
if (action === "open-new") {
|
|
10362
|
-
|
|
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());
|
|
10363
10351
|
return;
|
|
10364
10352
|
}
|
|
10365
10353
|
if (action === "open-preview-new") {
|
|
10366
|
-
await openPreviewResourceInNewEditor(path,
|
|
10354
|
+
await openPreviewResourceInNewEditor(path, getFileBrowserLocalLinkContext());
|
|
10367
10355
|
return;
|
|
10368
10356
|
}
|
|
10369
10357
|
if (action === "copy-path" || action === "copy-root" || action === "copy-current") {
|
|
@@ -10673,13 +10661,8 @@
|
|
|
10673
10661
|
const hostname = parsed.hostname.toLowerCase();
|
|
10674
10662
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("Unsupported preview protocol.");
|
|
10675
10663
|
if (hostname !== "127.0.0.1" && hostname !== "localhost" && hostname !== "::1") throw new Error("Preview URL is not loopback-only.");
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
setStatus("Browser blocked the Quarto preview tab. Allow pop-ups and try again.", "warning");
|
|
10679
|
-
return false;
|
|
10680
|
-
}
|
|
10681
|
-
try { opened.opener = null; } catch {}
|
|
10682
|
-
setStatus("Opening Quarto preview in a browser tab.", "success");
|
|
10664
|
+
openStudioTabDirect(parsed.href);
|
|
10665
|
+
setStatus("Opening Quarto preview in a browser tab.");
|
|
10683
10666
|
return true;
|
|
10684
10667
|
} catch (error) {
|
|
10685
10668
|
setStatus("Could not open Quarto preview: " + ((error && error.message) ? error.message : String(error)), "error");
|
|
@@ -10689,12 +10672,8 @@
|
|
|
10689
10672
|
|
|
10690
10673
|
function openStudioQuartoInstallationInstructions() {
|
|
10691
10674
|
try {
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
setStatus("Browser blocked the Quarto installation page.", "warning");
|
|
10695
|
-
return;
|
|
10696
|
-
}
|
|
10697
|
-
try { opened.opener = null; } catch {}
|
|
10675
|
+
openStudioTabDirect("https://quarto.org/docs/get-started/");
|
|
10676
|
+
setStatus("Opening Quarto installation instructions.");
|
|
10698
10677
|
} catch (error) {
|
|
10699
10678
|
setStatus("Could not open Quarto installation instructions.", "warning");
|
|
10700
10679
|
}
|
|
@@ -12447,6 +12426,99 @@
|
|
|
12447
12426
|
return pathname + "?" + params.toString();
|
|
12448
12427
|
}
|
|
12449
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
|
+
|
|
12450
12522
|
function updateStudioDocumentUrlState(state) {
|
|
12451
12523
|
try {
|
|
12452
12524
|
const currentUrl = new URL(window.location.href);
|
|
@@ -12723,10 +12795,7 @@
|
|
|
12723
12795
|
return true;
|
|
12724
12796
|
}
|
|
12725
12797
|
|
|
12726
|
-
async function openPreviewImageLink(href, title, contextOverride
|
|
12727
|
-
if (pendingWindow && !pendingWindow.closed) {
|
|
12728
|
-
try { pendingWindow.close(); } catch {}
|
|
12729
|
-
}
|
|
12798
|
+
async function openPreviewImageLink(href, title, contextOverride) {
|
|
12730
12799
|
const payload = await fetchStudioJson("/html-preview-resource", {
|
|
12731
12800
|
query: getPreviewLinkResourceQuery(href, contextOverride),
|
|
12732
12801
|
});
|
|
@@ -12812,73 +12881,33 @@
|
|
|
12812
12881
|
: (path ? ("Opened file-backed document in editor: " + label) : ("Opened linked file copy in editor: " + label)), "success");
|
|
12813
12882
|
}
|
|
12814
12883
|
|
|
12815
|
-
async function openPreviewDocumentInNewEditor(href,
|
|
12816
|
-
if (!confirmPreviewOfficeConversion(href, "new"))
|
|
12817
|
-
|
|
12818
|
-
try { pendingWindow.close(); } catch {}
|
|
12819
|
-
}
|
|
12820
|
-
return;
|
|
12821
|
-
}
|
|
12822
|
-
const popup = pendingWindow || window.open("", "_blank");
|
|
12823
|
-
const openingLabel = getPreviewLocalLinkKind(href) === "office" ? "Opening converted document…" : "Opening file tab…";
|
|
12824
|
-
try {
|
|
12825
|
-
if (popup && popup.document && popup.document.body) {
|
|
12826
|
-
popup.document.title = openingLabel;
|
|
12827
|
-
popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">" + escapeHtml(openingLabel) + "</p>";
|
|
12828
|
-
}
|
|
12829
|
-
} catch {}
|
|
12884
|
+
async function openPreviewDocumentInNewEditor(href, contextOverride) {
|
|
12885
|
+
if (!confirmPreviewOfficeConversion(href, "new")) return;
|
|
12886
|
+
let launch = null;
|
|
12830
12887
|
try {
|
|
12888
|
+
launch = openPendingStudioTab("document");
|
|
12831
12889
|
const payload = await fetchPreviewLocalLink("editor-url", href, contextOverride);
|
|
12832
|
-
const
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
if (popup && !popup.closed) {
|
|
12837
|
-
try {
|
|
12838
|
-
popup.opener = null;
|
|
12839
|
-
popup.location.href = targetUrl;
|
|
12840
|
-
setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.", "success");
|
|
12841
|
-
return;
|
|
12842
|
-
} catch {}
|
|
12843
|
-
}
|
|
12844
|
-
window.open(targetUrl, "_blank", "noopener");
|
|
12845
|
-
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.");
|
|
12846
12894
|
} catch (error) {
|
|
12847
|
-
|
|
12848
|
-
try { popup.close(); } catch {}
|
|
12849
|
-
}
|
|
12895
|
+
failPendingStudioTab(launch, "Studio could not prepare this document tab. Return to the originating Studio page for details.");
|
|
12850
12896
|
throw error;
|
|
12851
12897
|
}
|
|
12852
12898
|
}
|
|
12853
12899
|
|
|
12854
|
-
async function openPreviewResourceInNewEditor(href,
|
|
12855
|
-
|
|
12856
|
-
try {
|
|
12857
|
-
if (popup && popup.document && popup.document.body) {
|
|
12858
|
-
popup.document.title = "Opening preview…";
|
|
12859
|
-
popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening preview…</p>";
|
|
12860
|
-
}
|
|
12861
|
-
} catch {}
|
|
12900
|
+
async function openPreviewResourceInNewEditor(href, contextOverride) {
|
|
12901
|
+
let launch = null;
|
|
12862
12902
|
try {
|
|
12903
|
+
launch = openPendingStudioTab("preview");
|
|
12863
12904
|
const payload = await fetchPreviewLocalLink("preview-url", href, contextOverride);
|
|
12864
|
-
const
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
if (popup && !popup.closed) {
|
|
12869
|
-
try {
|
|
12870
|
-
popup.opener = null;
|
|
12871
|
-
popup.location.href = targetUrl;
|
|
12872
|
-
setStatus("Opening preview in a new Studio tab.", "success");
|
|
12873
|
-
return;
|
|
12874
|
-
} catch {}
|
|
12875
|
-
}
|
|
12876
|
-
window.open(targetUrl, "_blank", "noopener");
|
|
12877
|
-
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.");
|
|
12878
12909
|
} catch (error) {
|
|
12879
|
-
|
|
12880
|
-
try { popup.close(); } catch {}
|
|
12881
|
-
}
|
|
12910
|
+
failPendingStudioTab(launch, "Studio could not prepare this preview tab. Return to the originating Studio page for details.");
|
|
12882
12911
|
throw error;
|
|
12883
12912
|
}
|
|
12884
12913
|
}
|
|
@@ -12910,11 +12939,11 @@
|
|
|
12910
12939
|
return;
|
|
12911
12940
|
}
|
|
12912
12941
|
if (action === "open-new") {
|
|
12913
|
-
await openPreviewDocumentInNewEditor(href,
|
|
12942
|
+
await openPreviewDocumentInNewEditor(href, context);
|
|
12914
12943
|
return;
|
|
12915
12944
|
}
|
|
12916
12945
|
if (action === "open-preview-new") {
|
|
12917
|
-
await openPreviewResourceInNewEditor(href,
|
|
12946
|
+
await openPreviewResourceInNewEditor(href, context);
|
|
12918
12947
|
return;
|
|
12919
12948
|
}
|
|
12920
12949
|
if (action === "open-here") {
|
|
@@ -12957,8 +12986,7 @@
|
|
|
12957
12986
|
return;
|
|
12958
12987
|
}
|
|
12959
12988
|
if (kind === "text" || kind === "office") {
|
|
12960
|
-
|
|
12961
|
-
void openPreviewDocumentInNewEditor(href, pendingWindow).catch((error) => {
|
|
12989
|
+
void openPreviewDocumentInNewEditor(href).catch((error) => {
|
|
12962
12990
|
setStatus((error && error.message) ? error.message : String(error || "Could not open linked file."), "warning");
|
|
12963
12991
|
});
|
|
12964
12992
|
return;
|
|
@@ -20120,24 +20148,31 @@
|
|
|
20120
20148
|
|
|
20121
20149
|
if (message.type === "editor_only_ready") {
|
|
20122
20150
|
const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
20123
|
-
if (responseRequestId
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
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;
|
|
20128
20155
|
}
|
|
20156
|
+
pendingRequestId = null;
|
|
20157
|
+
pendingKind = null;
|
|
20158
|
+
clearArmedTitleAttention(responseRequestId);
|
|
20159
|
+
stickyStudioKind = null;
|
|
20129
20160
|
setBusy(false);
|
|
20130
20161
|
setWsState("Ready");
|
|
20131
|
-
const
|
|
20132
|
-
|
|
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
|
+
}
|
|
20133
20170
|
const readyMessage = typeof message.message === "string" && message.message.trim()
|
|
20134
20171
|
? message.message.trim()
|
|
20135
|
-
: "
|
|
20172
|
+
: "Opening an editor tab with a detached copy of the current editor text.";
|
|
20136
20173
|
setStatus(
|
|
20137
|
-
|
|
20138
|
-
|
|
20139
|
-
: (targetUrl ? "Editor tab ready: " + targetUrl : "Editor tab is ready, but Studio did not receive a URL."),
|
|
20140
|
-
opened ? "success" : "warning",
|
|
20174
|
+
queued ? readyMessage : "Editor tab is ready, but Studio did not receive a valid relative URL.",
|
|
20175
|
+
queued ? undefined : "warning",
|
|
20141
20176
|
);
|
|
20142
20177
|
return;
|
|
20143
20178
|
}
|
|
@@ -20214,7 +20249,7 @@
|
|
|
20214
20249
|
|
|
20215
20250
|
if (message.type === "busy") {
|
|
20216
20251
|
if (typeof message.requestId === "string") {
|
|
20217
|
-
|
|
20252
|
+
failPendingCompanionLaunch(message.requestId, "Studio could not start the companion editor because another request was busy.");
|
|
20218
20253
|
}
|
|
20219
20254
|
if (message.requestId && pendingRequestId === message.requestId) {
|
|
20220
20255
|
if (pendingKind === "compact") {
|
|
@@ -20235,7 +20270,7 @@
|
|
|
20235
20270
|
|
|
20236
20271
|
if (message.type === "error") {
|
|
20237
20272
|
if (typeof message.requestId === "string") {
|
|
20238
|
-
|
|
20273
|
+
failPendingCompanionLaunch(message.requestId, "Studio could not prepare the companion editor. Return to the originating Studio page for details.");
|
|
20239
20274
|
}
|
|
20240
20275
|
if (message.requestId && pendingRequestId === message.requestId) {
|
|
20241
20276
|
if (pendingKind === "compact") {
|
|
@@ -20370,6 +20405,7 @@
|
|
|
20370
20405
|
quartoPreviewCheckRequestId = null;
|
|
20371
20406
|
quartoPreviewCheckSourcePath = "";
|
|
20372
20407
|
quartoPreviewActionRequestId = null;
|
|
20408
|
+
failAllPendingCompanionLaunches("The originating Studio connection was lost before the companion editor was ready.");
|
|
20373
20409
|
if (rightView === "editor-quarto-preview") renderQuartoPreviewView();
|
|
20374
20410
|
setBusy(true);
|
|
20375
20411
|
|
|
@@ -20460,73 +20496,70 @@
|
|
|
20460
20496
|
return requestId;
|
|
20461
20497
|
}
|
|
20462
20498
|
|
|
20463
|
-
function
|
|
20499
|
+
function openPendingCompanionLaunch(requestId) {
|
|
20464
20500
|
if (!requestId) return null;
|
|
20465
|
-
|
|
20466
|
-
|
|
20467
|
-
|
|
20468
|
-
if (companionWindow && companionWindow.document && companionWindow.document.body) {
|
|
20469
|
-
companionWindow.document.title = "Opening editor tab…";
|
|
20470
|
-
companionWindow.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening editor tab…</p>";
|
|
20471
|
-
}
|
|
20472
|
-
} catch {
|
|
20473
|
-
companionWindow = null;
|
|
20474
|
-
}
|
|
20475
|
-
if (companionWindow) {
|
|
20476
|
-
pendingCompanionWindows.set(requestId, companionWindow);
|
|
20477
|
-
}
|
|
20478
|
-
return companionWindow;
|
|
20501
|
+
const launch = openPendingStudioTab("document");
|
|
20502
|
+
pendingCompanionLaunches.set(requestId, launch);
|
|
20503
|
+
return launch;
|
|
20479
20504
|
}
|
|
20480
20505
|
|
|
20481
|
-
function
|
|
20482
|
-
if (!requestId || !
|
|
20483
|
-
const
|
|
20484
|
-
|
|
20485
|
-
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 };
|
|
20486
20511
|
}
|
|
20487
20512
|
|
|
20488
|
-
function
|
|
20489
|
-
const
|
|
20490
|
-
if (!
|
|
20491
|
-
|
|
20492
|
-
|
|
20493
|
-
|
|
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;
|
|
20494
20521
|
}
|
|
20495
20522
|
|
|
20496
|
-
function
|
|
20497
|
-
const
|
|
20498
|
-
|
|
20499
|
-
try {
|
|
20500
|
-
return new URL(relativeUrl, window.location.href).href;
|
|
20501
|
-
} catch {}
|
|
20502
|
-
}
|
|
20503
|
-
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));
|
|
20504
20526
|
}
|
|
20505
20527
|
|
|
20506
|
-
function
|
|
20507
|
-
|
|
20508
|
-
|
|
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
|
+
}
|
|
20509
20538
|
return false;
|
|
20510
20539
|
}
|
|
20511
|
-
const companionWindow = takePendingCompanionWindow(requestId);
|
|
20512
|
-
if (companionWindow && !companionWindow.closed) {
|
|
20513
|
-
try {
|
|
20514
|
-
companionWindow.opener = null;
|
|
20515
|
-
companionWindow.location.href = targetUrl;
|
|
20516
|
-
return true;
|
|
20517
|
-
} catch {}
|
|
20518
|
-
}
|
|
20519
20540
|
try {
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20541
|
+
navigatePendingStudioTab(pending.launch, relativeUrl);
|
|
20542
|
+
return true;
|
|
20543
|
+
} catch (error) {
|
|
20544
|
+
failPendingStudioTab(pending.launch, "Studio rejected the companion editor target.");
|
|
20545
|
+
throw error;
|
|
20523
20546
|
}
|
|
20524
20547
|
}
|
|
20525
20548
|
|
|
20526
20549
|
function requestOpenEditorOnlyDocument(content, options) {
|
|
20527
20550
|
const requestId = beginUiAction("open_editor_only");
|
|
20528
20551
|
if (!requestId) return false;
|
|
20529
|
-
|
|
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
|
+
}
|
|
20530
20563
|
const config = options && typeof options === "object" ? options : {};
|
|
20531
20564
|
const sent = sendMessage({
|
|
20532
20565
|
type: "open_editor_only_request",
|
|
@@ -20537,9 +20570,10 @@
|
|
|
20537
20570
|
resourceDir: config.resourceDir || undefined,
|
|
20538
20571
|
});
|
|
20539
20572
|
if (!sent) {
|
|
20540
|
-
|
|
20573
|
+
failPendingCompanionLaunch(requestId, "Studio could not send the companion editor request.");
|
|
20541
20574
|
pendingRequestId = null;
|
|
20542
20575
|
pendingKind = null;
|
|
20576
|
+
stickyStudioKind = null;
|
|
20543
20577
|
setBusy(false);
|
|
20544
20578
|
}
|
|
20545
20579
|
return sent;
|
|
@@ -20671,6 +20705,10 @@
|
|
|
20671
20705
|
|
|
20672
20706
|
updatePaneFocusButtons();
|
|
20673
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
|
+
});
|
|
20674
20712
|
window.addEventListener("beforeunload", () => {
|
|
20675
20713
|
stopFooterSpinner();
|
|
20676
20714
|
flushWorkspacePersistence();
|
|
@@ -21829,7 +21867,9 @@
|
|
|
21829
21867
|
if (initialSourceState.path) markFileBackedBaseline(sourceTextEl.value);
|
|
21830
21868
|
refreshResponseUi();
|
|
21831
21869
|
updateAnnotatedReplyHeaderButton();
|
|
21832
|
-
setActivePane("left");
|
|
21870
|
+
setActivePane(initialPaneFocusTarget === "off" ? "left" : initialPaneFocusTarget);
|
|
21871
|
+
applyPaneFocusClasses();
|
|
21872
|
+
persistPaneFocusUrlState();
|
|
21833
21873
|
|
|
21834
21874
|
const storedEditorHighlightEnabled = readStoredEditorHighlightEnabled();
|
|
21835
21875
|
const initialHighlightEnabled = storedEditorHighlightEnabled ?? Boolean(highlightSelect && highlightSelect.value !== "off");
|