pi-studio 0.9.1 → 0.9.3
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 +17 -0
- package/README.md +3 -1
- package/client/studio-client.js +389 -57
- package/client/studio.css +287 -3
- package/index.ts +288 -323
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
const sendEditorBtn = document.getElementById("sendEditorBtn");
|
|
105
105
|
const openCompanionBtn = document.getElementById("openCompanionBtn");
|
|
106
106
|
const getEditorBtn = document.getElementById("getEditorBtn");
|
|
107
|
+
const zenModeBtn = document.getElementById("zenModeBtn");
|
|
107
108
|
const loadGitDiffBtn = document.getElementById("loadGitDiffBtn");
|
|
108
109
|
const sendRunBtn = document.getElementById("sendRunBtn");
|
|
109
110
|
const queueSteerBtn = document.getElementById("queueSteerBtn");
|
|
@@ -180,6 +181,16 @@
|
|
|
180
181
|
let statusLevel = "";
|
|
181
182
|
let reconnectTimer = null;
|
|
182
183
|
let reconnectAttempt = 0;
|
|
184
|
+
let studioPdfFocusOverlayEl = null;
|
|
185
|
+
let studioPdfFocusDialogEl = null;
|
|
186
|
+
let studioPdfFocusFrameSlotEl = null;
|
|
187
|
+
let studioPdfFocusFrameEl = null;
|
|
188
|
+
let studioPdfFocusTitleEl = null;
|
|
189
|
+
let studioPdfFocusOpenLinkEl = null;
|
|
190
|
+
let studioPdfFocusFullscreenBtn = null;
|
|
191
|
+
let studioPdfFocusCloseBtn = null;
|
|
192
|
+
let studioPdfFocusLastFocusedEl = null;
|
|
193
|
+
let studioPdfFocusMovedFrameState = null;
|
|
183
194
|
let pendingRequestId = null;
|
|
184
195
|
let pendingKind = null;
|
|
185
196
|
let stickyStudioKind = null;
|
|
@@ -221,6 +232,8 @@
|
|
|
221
232
|
const REPL_TRANSCRIPT_MAX_CHARS = 200_000;
|
|
222
233
|
const REPL_JOURNAL_OUTPUT_MAX_CHARS = 80_000;
|
|
223
234
|
const REPL_JOURNAL_MAX_ENTRIES = 80;
|
|
235
|
+
const PDF_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
236
|
+
const HTML_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
224
237
|
const EDITOR_TAB_TEXT = " ";
|
|
225
238
|
let replTmuxAvailable = null;
|
|
226
239
|
let replSessions = [];
|
|
@@ -1666,7 +1679,7 @@
|
|
|
1666
1679
|
let lineNumbersEnabled = false;
|
|
1667
1680
|
let lineNumbersRenderRaf = null;
|
|
1668
1681
|
let annotationsEnabled = true;
|
|
1669
|
-
const
|
|
1682
|
+
const STUDIO_ZEN_MODE_STORAGE_KEY = "piStudio.zenMode";
|
|
1670
1683
|
const studioUiRefreshEnabled = readStudioUiRefreshEnabled();
|
|
1671
1684
|
const EDITOR_FONT_SIZE_OPTIONS = [10, 11, 12, 13, 14, 15, 16, 18];
|
|
1672
1685
|
const RESPONSE_FONT_SIZE_OPTIONS = [11, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 18, 20];
|
|
@@ -1675,9 +1688,13 @@
|
|
|
1675
1688
|
let editorFontSize = DEFAULT_EDITOR_FONT_SIZE;
|
|
1676
1689
|
let responseFontSize = DEFAULT_RESPONSE_FONT_SIZE;
|
|
1677
1690
|
let studioUiRefreshUi = null;
|
|
1691
|
+
let studioZenModeEnabled = readStudioZenModeEnabled();
|
|
1678
1692
|
if (studioUiRefreshEnabled && document.body) {
|
|
1679
1693
|
document.body.classList.add("studio-ui-refresh");
|
|
1680
1694
|
}
|
|
1695
|
+
if (studioZenModeEnabled && document.body) {
|
|
1696
|
+
document.body.classList.add("studio-zen-mode");
|
|
1697
|
+
}
|
|
1681
1698
|
let scratchpadText = "";
|
|
1682
1699
|
let scratchpadReturnFocusEl = null;
|
|
1683
1700
|
let scratchpadPersistTimer = null;
|
|
@@ -1704,19 +1721,51 @@
|
|
|
1704
1721
|
: (initialQueryParams.has("studioUiRefresh") ? initialQueryParams.get("studioUiRefresh") : null);
|
|
1705
1722
|
const isTruthy = (value) => ["1", "true", "yes", "on", "v2", "refresh", "fresh"].indexOf(normalize(value)) !== -1;
|
|
1706
1723
|
const isFalsey = (value) => ["0", "false", "no", "off", "classic"].indexOf(normalize(value)) !== -1;
|
|
1724
|
+
if (queryValue !== null) {
|
|
1725
|
+
const normalizedQuery = normalize(queryValue);
|
|
1726
|
+
return isTruthy(queryValue) || (!isFalsey(queryValue) && normalizedQuery !== "");
|
|
1727
|
+
}
|
|
1728
|
+
return true;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function readStudioZenModeEnabled() {
|
|
1732
|
+
const normalize = (value) => String(value == null ? "" : value).trim().toLowerCase();
|
|
1733
|
+
const isTruthy = (value) => ["1", "true", "yes", "on", "zen"].indexOf(normalize(value)) !== -1;
|
|
1734
|
+
const isFalsey = (value) => ["0", "false", "no", "off"].indexOf(normalize(value)) !== -1;
|
|
1735
|
+
const queryValue = initialQueryParams.has("zen") ? initialQueryParams.get("zen") : null;
|
|
1707
1736
|
if (queryValue !== null) {
|
|
1708
1737
|
const normalizedQuery = normalize(queryValue);
|
|
1709
1738
|
const enabled = isTruthy(queryValue) || (!isFalsey(queryValue) && normalizedQuery !== "");
|
|
1710
1739
|
try {
|
|
1711
|
-
window.localStorage && window.localStorage.setItem(
|
|
1740
|
+
window.localStorage && window.localStorage.setItem(STUDIO_ZEN_MODE_STORAGE_KEY, enabled ? "1" : "0");
|
|
1712
1741
|
} catch {}
|
|
1713
1742
|
return enabled;
|
|
1714
1743
|
}
|
|
1715
1744
|
try {
|
|
1716
|
-
const stored = window.localStorage ? window.localStorage.getItem(
|
|
1717
|
-
if (stored
|
|
1745
|
+
const stored = window.localStorage ? window.localStorage.getItem(STUDIO_ZEN_MODE_STORAGE_KEY) : null;
|
|
1746
|
+
if (stored === null) return false;
|
|
1747
|
+
return isTruthy(stored) || (!isFalsey(stored) && normalize(stored) !== "");
|
|
1748
|
+
} catch {
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function syncStudioZenModeUi() {
|
|
1754
|
+
if (document.body) document.body.classList.toggle("studio-zen-mode", studioZenModeEnabled);
|
|
1755
|
+
if (!zenModeBtn) return;
|
|
1756
|
+
zenModeBtn.textContent = studioZenModeEnabled ? "Exit Zen" : "Zen";
|
|
1757
|
+
zenModeBtn.title = studioZenModeEnabled ? "Show full Studio controls." : "Hide secondary Studio controls.";
|
|
1758
|
+
zenModeBtn.setAttribute("aria-pressed", studioZenModeEnabled ? "true" : "false");
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function setStudioZenMode(enabled) {
|
|
1762
|
+
studioZenModeEnabled = Boolean(enabled);
|
|
1763
|
+
try {
|
|
1764
|
+
window.localStorage && window.localStorage.setItem(STUDIO_ZEN_MODE_STORAGE_KEY, studioZenModeEnabled ? "1" : "0");
|
|
1718
1765
|
} catch {}
|
|
1719
|
-
|
|
1766
|
+
closeStudioUiRefreshMenus();
|
|
1767
|
+
closeExportPreviewMenu();
|
|
1768
|
+
syncStudioZenModeUi();
|
|
1720
1769
|
}
|
|
1721
1770
|
|
|
1722
1771
|
function makeStudioUiRefreshElement(tagName, className, text) {
|
|
@@ -1735,9 +1784,16 @@
|
|
|
1735
1784
|
svg.setAttribute("viewBox", "0 0 24 24");
|
|
1736
1785
|
svg.setAttribute("aria-hidden", "true");
|
|
1737
1786
|
svg.classList.add("studio-refresh-icon");
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1787
|
+
let paths;
|
|
1788
|
+
if (kind === "focus-exit") {
|
|
1789
|
+
paths = ["M4 4l6 6", "M10 4v6H4", "M20 20l-6-6", "M14 20v-6h6"];
|
|
1790
|
+
} else if (kind === "fullscreen") {
|
|
1791
|
+
paths = ["M8 4H4v4", "M16 4h4v4", "M20 16v4h-4", "M4 16v4h4"];
|
|
1792
|
+
} else if (kind === "fullscreen-exit") {
|
|
1793
|
+
paths = ["M9 5v4H5", "M15 5v4h4", "M19 15h-4v4", "M5 15h4v4"];
|
|
1794
|
+
} else {
|
|
1795
|
+
paths = ["M14 4h6v6", "M20 4l-6 6", "M10 20H4v-6", "M4 20l6-6"];
|
|
1796
|
+
}
|
|
1741
1797
|
for (const d of paths) {
|
|
1742
1798
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1743
1799
|
path.setAttribute("d", d);
|
|
@@ -1936,37 +1992,6 @@
|
|
|
1936
1992
|
return { name, anchor: anchorEl, button: buttonEl, menu: menuEl };
|
|
1937
1993
|
}
|
|
1938
1994
|
|
|
1939
|
-
function setStudioUiRefreshPreference(enabled) {
|
|
1940
|
-
try {
|
|
1941
|
-
window.localStorage && window.localStorage.setItem(STUDIO_UI_REFRESH_STORAGE_KEY, enabled ? "1" : "0");
|
|
1942
|
-
} catch {}
|
|
1943
|
-
try {
|
|
1944
|
-
const url = new URL(window.location.href);
|
|
1945
|
-
url.searchParams.set("uiRefresh", enabled ? "1" : "0");
|
|
1946
|
-
window.location.assign(url.toString());
|
|
1947
|
-
} catch {
|
|
1948
|
-
window.location.reload();
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
function setupStudioUiRefreshToggleButton() {
|
|
1953
|
-
if (!footerMetaEl || document.getElementById("studioUiRefreshToggleBtn")) return;
|
|
1954
|
-
const button = makeStudioUiRefreshElement("button", "footer-compact-btn studio-ui-refresh-toggle", studioUiRefreshEnabled ? "UI: Fresh" : "UI: Classic");
|
|
1955
|
-
button.id = "studioUiRefreshToggleBtn";
|
|
1956
|
-
button.type = "button";
|
|
1957
|
-
button.title = studioUiRefreshEnabled
|
|
1958
|
-
? "Switch Studio to the classic layout."
|
|
1959
|
-
: "Switch Studio to the refreshed layout.";
|
|
1960
|
-
button.addEventListener("click", () => {
|
|
1961
|
-
setStudioUiRefreshPreference(!studioUiRefreshEnabled);
|
|
1962
|
-
});
|
|
1963
|
-
if (compactBtn && compactBtn.parentNode === footerMetaEl) {
|
|
1964
|
-
compactBtn.insertAdjacentElement("afterend", button);
|
|
1965
|
-
} else {
|
|
1966
|
-
footerMetaEl.appendChild(button);
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
1995
|
function setupStudioUiRefreshPrototype() {
|
|
1971
1996
|
if (!studioUiRefreshEnabled || studioUiRefreshUi) return;
|
|
1972
1997
|
const leftHeaderEl = document.getElementById("leftSectionHeader");
|
|
@@ -2096,8 +2121,8 @@
|
|
|
2096
2121
|
syncStudioUiRefreshSummaries();
|
|
2097
2122
|
}
|
|
2098
2123
|
|
|
2099
|
-
setupStudioUiRefreshToggleButton();
|
|
2100
2124
|
setupStudioUiRefreshPrototype();
|
|
2125
|
+
syncStudioZenModeUi();
|
|
2101
2126
|
const annotationHelpers = globalThis.PiStudioAnnotationHelpers;
|
|
2102
2127
|
if (!annotationHelpers || typeof annotationHelpers.collectInlineAnnotationMarkers !== "function") {
|
|
2103
2128
|
throw new Error("Studio annotation helpers failed to load.");
|
|
@@ -2979,6 +3004,18 @@
|
|
|
2979
3004
|
&& typeof outlineDialogEl.contains === "function"
|
|
2980
3005
|
&& outlineDialogEl.contains(event.target)
|
|
2981
3006
|
);
|
|
3007
|
+
const pdfFocusOwnsEvent = Boolean(
|
|
3008
|
+
studioPdfFocusDialogEl
|
|
3009
|
+
&& event.target
|
|
3010
|
+
&& typeof studioPdfFocusDialogEl.contains === "function"
|
|
3011
|
+
&& studioPdfFocusDialogEl.contains(event.target)
|
|
3012
|
+
);
|
|
3013
|
+
|
|
3014
|
+
if (isStudioPdfFocusOpen() && plainEscape) {
|
|
3015
|
+
event.preventDefault();
|
|
3016
|
+
closeStudioPdfFocusViewer();
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
2982
3019
|
|
|
2983
3020
|
if (isScratchpadOpen() && plainEscape) {
|
|
2984
3021
|
event.preventDefault();
|
|
@@ -2998,7 +3035,7 @@
|
|
|
2998
3035
|
return;
|
|
2999
3036
|
}
|
|
3000
3037
|
|
|
3001
|
-
if (scratchpadOwnsEvent || reviewNotesOwnsEvent || outlineOwnsEvent) {
|
|
3038
|
+
if (scratchpadOwnsEvent || reviewNotesOwnsEvent || outlineOwnsEvent || pdfFocusOwnsEvent) {
|
|
3002
3039
|
return;
|
|
3003
3040
|
}
|
|
3004
3041
|
|
|
@@ -3847,50 +3884,316 @@
|
|
|
3847
3884
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
3848
3885
|
}
|
|
3849
3886
|
|
|
3850
|
-
function
|
|
3887
|
+
function isStudioPdfFocusOpen() {
|
|
3888
|
+
return Boolean(studioPdfFocusOverlayEl && studioPdfFocusOverlayEl.hidden === false);
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
function ensureStudioPdfFocusViewer() {
|
|
3892
|
+
if (studioPdfFocusOverlayEl) return studioPdfFocusOverlayEl;
|
|
3893
|
+
|
|
3894
|
+
const overlay = document.createElement("div");
|
|
3895
|
+
overlay.className = "studio-pdf-focus-overlay";
|
|
3896
|
+
overlay.hidden = true;
|
|
3897
|
+
overlay.setAttribute("role", "dialog");
|
|
3898
|
+
overlay.setAttribute("aria-modal", "true");
|
|
3899
|
+
overlay.setAttribute("aria-labelledby", "studioPdfFocusTitle");
|
|
3900
|
+
|
|
3901
|
+
const dialog = document.createElement("div");
|
|
3902
|
+
dialog.className = "studio-pdf-focus-dialog";
|
|
3903
|
+
|
|
3904
|
+
const header = document.createElement("div");
|
|
3905
|
+
header.className = "studio-pdf-focus-header";
|
|
3906
|
+
|
|
3907
|
+
const titleGroup = document.createElement("div");
|
|
3908
|
+
titleGroup.className = "studio-pdf-focus-title-group";
|
|
3909
|
+
|
|
3910
|
+
const closeBtn = document.createElement("button");
|
|
3911
|
+
closeBtn.type = "button";
|
|
3912
|
+
closeBtn.className = "studio-pdf-focus-btn studio-pdf-focus-close";
|
|
3913
|
+
closeBtn.title = "Exit PDF focus view.";
|
|
3914
|
+
closeBtn.setAttribute("aria-label", "Exit PDF focus view");
|
|
3915
|
+
closeBtn.appendChild(makeStudioUiRefreshIcon("focus-exit"));
|
|
3916
|
+
closeBtn.addEventListener("click", () => closeStudioPdfFocusViewer());
|
|
3917
|
+
titleGroup.appendChild(closeBtn);
|
|
3918
|
+
|
|
3919
|
+
const titleEl = document.createElement("div");
|
|
3920
|
+
titleEl.id = "studioPdfFocusTitle";
|
|
3921
|
+
titleEl.className = "studio-pdf-focus-title";
|
|
3922
|
+
titleEl.textContent = "PDF preview";
|
|
3923
|
+
titleGroup.appendChild(titleEl);
|
|
3924
|
+
header.appendChild(titleGroup);
|
|
3925
|
+
|
|
3926
|
+
const actions = document.createElement("div");
|
|
3927
|
+
actions.className = "studio-pdf-focus-actions";
|
|
3928
|
+
|
|
3929
|
+
const openLink = document.createElement("a");
|
|
3930
|
+
openLink.className = "studio-pdf-focus-link";
|
|
3931
|
+
openLink.target = "_blank";
|
|
3932
|
+
openLink.rel = "noopener noreferrer";
|
|
3933
|
+
openLink.textContent = "Open PDF";
|
|
3934
|
+
actions.appendChild(openLink);
|
|
3935
|
+
|
|
3936
|
+
const fullscreenBtn = document.createElement("button");
|
|
3937
|
+
fullscreenBtn.type = "button";
|
|
3938
|
+
fullscreenBtn.className = "studio-pdf-focus-btn studio-pdf-focus-fullscreen";
|
|
3939
|
+
fullscreenBtn.addEventListener("click", async () => {
|
|
3940
|
+
const isFullscreen = Boolean(document.fullscreenElement && studioPdfFocusDialogEl && document.fullscreenElement === studioPdfFocusDialogEl);
|
|
3941
|
+
if (isFullscreen) {
|
|
3942
|
+
try {
|
|
3943
|
+
if (typeof document.exitFullscreen === "function") await document.exitFullscreen();
|
|
3944
|
+
} catch (error) {
|
|
3945
|
+
setStatus("Could not exit PDF fullscreen: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
3946
|
+
} finally {
|
|
3947
|
+
syncStudioPdfFocusFullscreenButton();
|
|
3948
|
+
}
|
|
3949
|
+
return;
|
|
3950
|
+
}
|
|
3951
|
+
if (!studioPdfFocusDialogEl || typeof studioPdfFocusDialogEl.requestFullscreen !== "function") {
|
|
3952
|
+
setStatus("Browser fullscreen is not available for this PDF viewer.", "warning");
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
try {
|
|
3956
|
+
await studioPdfFocusDialogEl.requestFullscreen();
|
|
3957
|
+
} catch (error) {
|
|
3958
|
+
setStatus("Could not enter PDF fullscreen: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
3959
|
+
} finally {
|
|
3960
|
+
syncStudioPdfFocusFullscreenButton();
|
|
3961
|
+
}
|
|
3962
|
+
});
|
|
3963
|
+
actions.appendChild(fullscreenBtn);
|
|
3964
|
+
|
|
3965
|
+
header.appendChild(actions);
|
|
3966
|
+
dialog.appendChild(header);
|
|
3967
|
+
|
|
3968
|
+
const frameSlot = document.createElement("div");
|
|
3969
|
+
frameSlot.className = "studio-pdf-focus-frame-slot";
|
|
3970
|
+
const frame = document.createElement("iframe");
|
|
3971
|
+
frame.className = "studio-pdf-focus-frame";
|
|
3972
|
+
frame.title = "PDF focus viewer";
|
|
3973
|
+
frame.loading = "eager";
|
|
3974
|
+
frameSlot.appendChild(frame);
|
|
3975
|
+
dialog.appendChild(frameSlot);
|
|
3976
|
+
|
|
3977
|
+
overlay.appendChild(dialog);
|
|
3978
|
+
overlay.addEventListener("click", (event) => {
|
|
3979
|
+
if (event.target === overlay) closeStudioPdfFocusViewer();
|
|
3980
|
+
});
|
|
3981
|
+
document.addEventListener("fullscreenchange", syncStudioPdfFocusFullscreenButton);
|
|
3982
|
+
|
|
3983
|
+
document.body.appendChild(overlay);
|
|
3984
|
+
studioPdfFocusOverlayEl = overlay;
|
|
3985
|
+
studioPdfFocusDialogEl = dialog;
|
|
3986
|
+
studioPdfFocusFrameSlotEl = frameSlot;
|
|
3987
|
+
studioPdfFocusFrameEl = frame;
|
|
3988
|
+
studioPdfFocusTitleEl = titleEl;
|
|
3989
|
+
studioPdfFocusOpenLinkEl = openLink;
|
|
3990
|
+
studioPdfFocusFullscreenBtn = fullscreenBtn;
|
|
3991
|
+
studioPdfFocusCloseBtn = closeBtn;
|
|
3992
|
+
syncStudioPdfFocusFullscreenButton();
|
|
3993
|
+
return overlay;
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
function openStudioPdfFocusViewer(viewerUrl, title, sourceFrame) {
|
|
3997
|
+
const src = String(viewerUrl || "").trim();
|
|
3998
|
+
if (!src) return;
|
|
3999
|
+
ensureStudioPdfFocusViewer();
|
|
4000
|
+
studioPdfFocusLastFocusedEl = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
4001
|
+
if (studioPdfFocusTitleEl) studioPdfFocusTitleEl.textContent = String(title || "PDF preview").trim() || "PDF preview";
|
|
4002
|
+
if (studioPdfFocusOpenLinkEl) studioPdfFocusOpenLinkEl.href = src;
|
|
4003
|
+
setStudioPdfFocusFrameSource(src, title, sourceFrame);
|
|
4004
|
+
if (document.body) document.body.classList.add("studio-pdf-focus-open");
|
|
4005
|
+
if (studioPdfFocusOverlayEl) studioPdfFocusOverlayEl.hidden = false;
|
|
4006
|
+
syncStudioPdfFocusFullscreenButton();
|
|
4007
|
+
closeStudioUiRefreshMenus();
|
|
4008
|
+
closeExportPreviewMenu();
|
|
4009
|
+
window.setTimeout(() => {
|
|
4010
|
+
if (studioPdfFocusCloseBtn && typeof studioPdfFocusCloseBtn.focus === "function") {
|
|
4011
|
+
studioPdfFocusCloseBtn.focus();
|
|
4012
|
+
}
|
|
4013
|
+
}, 0);
|
|
4014
|
+
}
|
|
4015
|
+
|
|
4016
|
+
function closeStudioPdfFocusViewer() {
|
|
4017
|
+
if (!isStudioPdfFocusOpen()) return false;
|
|
4018
|
+
if (document.fullscreenElement && studioPdfFocusDialogEl && studioPdfFocusDialogEl.contains(document.fullscreenElement)) {
|
|
4019
|
+
try {
|
|
4020
|
+
const exitResult = document.exitFullscreen && document.exitFullscreen();
|
|
4021
|
+
if (exitResult && typeof exitResult.catch === "function") exitResult.catch(() => {});
|
|
4022
|
+
} catch {}
|
|
4023
|
+
}
|
|
4024
|
+
if (studioPdfFocusOverlayEl) studioPdfFocusOverlayEl.hidden = true;
|
|
4025
|
+
restoreStudioPdfFocusMovedFrame();
|
|
4026
|
+
if (studioPdfFocusFrameEl) studioPdfFocusFrameEl.src = "about:blank";
|
|
4027
|
+
if (document.body) document.body.classList.remove("studio-pdf-focus-open");
|
|
4028
|
+
syncStudioPdfFocusFullscreenButton();
|
|
4029
|
+
const focusTarget = studioPdfFocusLastFocusedEl;
|
|
4030
|
+
studioPdfFocusLastFocusedEl = null;
|
|
4031
|
+
if (focusTarget && typeof focusTarget.focus === "function" && document.contains(focusTarget)) {
|
|
4032
|
+
window.setTimeout(() => focusTarget.focus(), 0);
|
|
4033
|
+
}
|
|
4034
|
+
return true;
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
function buildStudioPdfResourceUrl(options, useEditorResourceContext) {
|
|
3851
4038
|
const token = getToken();
|
|
3852
4039
|
if (!token) return "";
|
|
3853
4040
|
const pdfPath = String(options && options.path ? options.path : "").trim();
|
|
3854
4041
|
if (!pdfPath) return "";
|
|
3855
4042
|
const effectivePath = getEffectiveSavePath();
|
|
3856
|
-
const sourcePath = effectivePath || sourceState.path || "";
|
|
4043
|
+
const sourcePath = useEditorResourceContext ? (effectivePath || sourceState.path || "") : "";
|
|
4044
|
+
const resourceDir = resourceDirInput && resourceDirInput.value.trim() ? resourceDirInput.value.trim() : "";
|
|
3857
4045
|
const params = new URLSearchParams({ token, path: pdfPath });
|
|
3858
4046
|
if (sourcePath) {
|
|
3859
4047
|
params.set("sourcePath", sourcePath);
|
|
3860
|
-
} else if (
|
|
3861
|
-
params.set("resourceDir",
|
|
4048
|
+
} else if (resourceDir) {
|
|
4049
|
+
params.set("resourceDir", resourceDir);
|
|
3862
4050
|
}
|
|
3863
4051
|
return "/pdf-resource?" + params.toString();
|
|
3864
4052
|
}
|
|
3865
4053
|
|
|
3866
|
-
function
|
|
4054
|
+
function syncStudioPdfFocusFullscreenButton() {
|
|
4055
|
+
if (!studioPdfFocusFullscreenBtn) return;
|
|
4056
|
+
const isFullscreen = Boolean(document.fullscreenElement && studioPdfFocusDialogEl && document.fullscreenElement === studioPdfFocusDialogEl);
|
|
4057
|
+
studioPdfFocusFullscreenBtn.replaceChildren(makeStudioUiRefreshIcon(isFullscreen ? "fullscreen-exit" : "fullscreen"));
|
|
4058
|
+
const label = isFullscreen ? "Exit fullscreen" : "Fullscreen";
|
|
4059
|
+
studioPdfFocusFullscreenBtn.title = isFullscreen
|
|
4060
|
+
? "Exit browser fullscreen and keep the PDF focus viewer open."
|
|
4061
|
+
: "Ask the browser to make this PDF viewer fullscreen.";
|
|
4062
|
+
studioPdfFocusFullscreenBtn.setAttribute("aria-label", label);
|
|
4063
|
+
studioPdfFocusFullscreenBtn.setAttribute("aria-pressed", isFullscreen ? "true" : "false");
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
function restoreStudioPdfFocusMovedFrame() {
|
|
4067
|
+
const state = studioPdfFocusMovedFrameState;
|
|
4068
|
+
studioPdfFocusMovedFrameState = null;
|
|
4069
|
+
if (!state || !state.frame) return;
|
|
4070
|
+
const frame = state.frame;
|
|
4071
|
+
frame.className = state.className;
|
|
4072
|
+
frame.style.cssText = state.styleCssText;
|
|
4073
|
+
if (state.title !== null) frame.setAttribute("title", state.title);
|
|
4074
|
+
else frame.removeAttribute("title");
|
|
4075
|
+
if (state.placeholder && state.placeholder.parentNode) {
|
|
4076
|
+
state.placeholder.parentNode.insertBefore(frame, state.placeholder);
|
|
4077
|
+
state.placeholder.remove();
|
|
4078
|
+
} else if (state.parent && state.parent.isConnected) {
|
|
4079
|
+
state.parent.insertBefore(frame, state.nextSibling && state.nextSibling.parentNode === state.parent ? state.nextSibling : null);
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
function setStudioPdfFocusFrameSource(src, title, sourceFrame) {
|
|
4084
|
+
if (!studioPdfFocusFrameSlotEl || !studioPdfFocusFrameEl) return;
|
|
4085
|
+
restoreStudioPdfFocusMovedFrame();
|
|
4086
|
+
const sourceIframe = sourceFrame instanceof HTMLIFrameElement ? sourceFrame : null;
|
|
4087
|
+
if (sourceIframe && sourceIframe.isConnected) {
|
|
4088
|
+
const placeholder = document.createElement("span");
|
|
4089
|
+
placeholder.hidden = true;
|
|
4090
|
+
const parent = sourceIframe.parentNode;
|
|
4091
|
+
parent && parent.insertBefore(placeholder, sourceIframe);
|
|
4092
|
+
studioPdfFocusMovedFrameState = {
|
|
4093
|
+
frame: sourceIframe,
|
|
4094
|
+
parent,
|
|
4095
|
+
nextSibling: placeholder.nextSibling,
|
|
4096
|
+
placeholder,
|
|
4097
|
+
className: sourceIframe.className,
|
|
4098
|
+
styleCssText: sourceIframe.style.cssText,
|
|
4099
|
+
title: sourceIframe.getAttribute("title"),
|
|
4100
|
+
};
|
|
4101
|
+
if (studioPdfFocusFrameEl.parentNode) studioPdfFocusFrameEl.parentNode.removeChild(studioPdfFocusFrameEl);
|
|
4102
|
+
sourceIframe.classList.add("studio-pdf-focus-frame");
|
|
4103
|
+
sourceIframe.style.height = "auto";
|
|
4104
|
+
sourceIframe.style.flex = "1 1 auto";
|
|
4105
|
+
sourceIframe.title = String(title || "PDF focus viewer").trim() || "PDF focus viewer";
|
|
4106
|
+
studioPdfFocusFrameSlotEl.appendChild(sourceIframe);
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
if (!studioPdfFocusFrameEl.parentNode) studioPdfFocusFrameSlotEl.appendChild(studioPdfFocusFrameEl);
|
|
4110
|
+
studioPdfFocusFrameEl.src = src;
|
|
4111
|
+
studioPdfFocusFrameEl.title = String(title || "PDF focus viewer").trim() || "PDF focus viewer";
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
function openStudioPdfFocusFromButton(buttonEl) {
|
|
4115
|
+
if (!buttonEl) return false;
|
|
4116
|
+
const card = buttonEl.closest && buttonEl.closest(".studio-pdf-card");
|
|
4117
|
+
const viewerUrl = String(buttonEl.dataset && buttonEl.dataset.studioPdfViewerUrl ? buttonEl.dataset.studioPdfViewerUrl : "").trim()
|
|
4118
|
+
|| String(card && card.dataset ? (card.dataset.studioPdfViewerUrl || "") : "").trim();
|
|
4119
|
+
const title = String(buttonEl.dataset && buttonEl.dataset.studioPdfTitle ? buttonEl.dataset.studioPdfTitle : "").trim()
|
|
4120
|
+
|| String(card && card.dataset ? (card.dataset.studioPdfTitle || "") : "").trim()
|
|
4121
|
+
|| "PDF preview";
|
|
4122
|
+
const sourceFrame = card && typeof card.querySelector === "function" ? card.querySelector("iframe.studio-pdf-frame") : null;
|
|
4123
|
+
if (!viewerUrl) return false;
|
|
4124
|
+
openStudioPdfFocusViewer(viewerUrl, title, sourceFrame);
|
|
4125
|
+
return true;
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
function handleStudioPdfFocusButtonClick(event) {
|
|
4129
|
+
const target = event && event.target;
|
|
4130
|
+
const buttonEl = target instanceof Element ? target.closest(".studio-pdf-card-focus") : null;
|
|
4131
|
+
if (!buttonEl) return;
|
|
4132
|
+
event.preventDefault();
|
|
4133
|
+
event.stopPropagation();
|
|
4134
|
+
if (typeof event.stopImmediatePropagation === "function") {
|
|
4135
|
+
event.stopImmediatePropagation();
|
|
4136
|
+
}
|
|
4137
|
+
if (!openStudioPdfFocusFromButton(buttonEl)) {
|
|
4138
|
+
setStatus("Could not open PDF focus view for this card.", "warning");
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
function createStudioPdfCard(block, useEditorResourceContext) {
|
|
3867
4143
|
const options = block && block.options ? block.options : {};
|
|
3868
4144
|
const path = String(options.path || "").trim();
|
|
3869
4145
|
const title = String(options.title || path || "Embedded PDF").trim();
|
|
3870
4146
|
const caption = String(options.caption || "").trim();
|
|
3871
4147
|
const height = normalizeStudioPdfHeight(options.height);
|
|
3872
4148
|
const page = normalizeStudioPdfPage(options.page);
|
|
3873
|
-
const resourceUrl = buildStudioPdfResourceUrl(options);
|
|
4149
|
+
const resourceUrl = buildStudioPdfResourceUrl(options, useEditorResourceContext);
|
|
3874
4150
|
const viewerUrl = resourceUrl && page ? resourceUrl + "#page=" + encodeURIComponent(String(page)) : resourceUrl;
|
|
3875
4151
|
|
|
3876
4152
|
const card = document.createElement("figure");
|
|
3877
4153
|
card.className = "studio-pdf-card";
|
|
4154
|
+
if (card.dataset) {
|
|
4155
|
+
card.dataset.studioPdfViewerUrl = viewerUrl || "";
|
|
4156
|
+
card.dataset.studioPdfTitle = title;
|
|
4157
|
+
}
|
|
3878
4158
|
|
|
3879
4159
|
const header = document.createElement("figcaption");
|
|
3880
4160
|
header.className = "studio-pdf-card-header";
|
|
4161
|
+
|
|
4162
|
+
const titleGroup = document.createElement("div");
|
|
4163
|
+
titleGroup.className = "studio-pdf-card-title-group";
|
|
4164
|
+
if (resourceUrl) {
|
|
4165
|
+
const focusBtn = document.createElement("button");
|
|
4166
|
+
focusBtn.type = "button";
|
|
4167
|
+
focusBtn.className = "studio-pdf-card-action studio-pdf-card-focus";
|
|
4168
|
+
focusBtn.title = "Open this PDF in a larger Studio overlay.";
|
|
4169
|
+
focusBtn.setAttribute("aria-label", "Focus PDF");
|
|
4170
|
+
if (focusBtn.dataset) {
|
|
4171
|
+
focusBtn.dataset.studioPdfViewerUrl = viewerUrl;
|
|
4172
|
+
focusBtn.dataset.studioPdfTitle = title;
|
|
4173
|
+
}
|
|
4174
|
+
focusBtn.appendChild(makeStudioUiRefreshIcon("focus"));
|
|
4175
|
+
focusBtn.addEventListener("click", handleStudioPdfFocusButtonClick);
|
|
4176
|
+
titleGroup.appendChild(focusBtn);
|
|
4177
|
+
}
|
|
3881
4178
|
const label = document.createElement("div");
|
|
3882
4179
|
label.className = "studio-pdf-card-title";
|
|
3883
4180
|
label.textContent = title;
|
|
3884
|
-
|
|
4181
|
+
titleGroup.appendChild(label);
|
|
4182
|
+
header.appendChild(titleGroup);
|
|
3885
4183
|
|
|
3886
4184
|
if (resourceUrl) {
|
|
4185
|
+
const actions = document.createElement("div");
|
|
4186
|
+
actions.className = "studio-pdf-card-actions";
|
|
4187
|
+
|
|
3887
4188
|
const openLink = document.createElement("a");
|
|
3888
|
-
openLink.className = "studio-pdf-card-link";
|
|
4189
|
+
openLink.className = "studio-pdf-card-link studio-pdf-card-action";
|
|
3889
4190
|
openLink.href = viewerUrl;
|
|
3890
4191
|
openLink.target = "_blank";
|
|
3891
4192
|
openLink.rel = "noopener noreferrer";
|
|
3892
4193
|
openLink.textContent = "Open PDF";
|
|
3893
|
-
|
|
4194
|
+
actions.appendChild(openLink);
|
|
4195
|
+
|
|
4196
|
+
header.appendChild(actions);
|
|
3894
4197
|
}
|
|
3895
4198
|
card.appendChild(header);
|
|
3896
4199
|
|
|
@@ -3919,7 +4222,7 @@
|
|
|
3919
4222
|
return card;
|
|
3920
4223
|
}
|
|
3921
4224
|
|
|
3922
|
-
function renderStudioPdfBlocksInElement(targetEl, blocks) {
|
|
4225
|
+
function renderStudioPdfBlocksInElement(targetEl, blocks, useEditorResourceContext) {
|
|
3923
4226
|
if (!targetEl || !Array.isArray(blocks) || blocks.length === 0) return;
|
|
3924
4227
|
const candidates = Array.from(targetEl.querySelectorAll("p, pre, div"));
|
|
3925
4228
|
blocks.forEach((block) => {
|
|
@@ -3927,7 +4230,7 @@
|
|
|
3927
4230
|
if (!placeholder) return;
|
|
3928
4231
|
const match = candidates.find((el) => String(el.textContent || "").trim() === placeholder);
|
|
3929
4232
|
if (match && match.parentNode) {
|
|
3930
|
-
match.replaceWith(createStudioPdfCard(block));
|
|
4233
|
+
match.replaceWith(createStudioPdfCard(block, useEditorResourceContext));
|
|
3931
4234
|
}
|
|
3932
4235
|
});
|
|
3933
4236
|
}
|
|
@@ -4952,6 +5255,22 @@
|
|
|
4952
5255
|
return "";
|
|
4953
5256
|
}
|
|
4954
5257
|
|
|
5258
|
+
async function fetchWithTimeout(url, options, timeoutMs, timeoutLabel) {
|
|
5259
|
+
if (typeof AbortController === "undefined") return fetch(url, options);
|
|
5260
|
+
const controller = new AbortController();
|
|
5261
|
+
const timer = window.setTimeout(() => controller.abort(), Math.max(1000, Number(timeoutMs) || PDF_EXPORT_FETCH_TIMEOUT_MS));
|
|
5262
|
+
try {
|
|
5263
|
+
return await fetch(url, { ...(options || {}), signal: controller.signal });
|
|
5264
|
+
} catch (error) {
|
|
5265
|
+
if (error && error.name === "AbortError") {
|
|
5266
|
+
throw new Error((timeoutLabel || "Request") + " timed out. Try a smaller export or check the PDF toolchain.");
|
|
5267
|
+
}
|
|
5268
|
+
throw error;
|
|
5269
|
+
} finally {
|
|
5270
|
+
window.clearTimeout(timer);
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
|
|
4955
5274
|
async function exportRightPanePdf() {
|
|
4956
5275
|
if (uiBusy || previewExportInProgress) {
|
|
4957
5276
|
setStatus("Studio is busy.", "warning");
|
|
@@ -5011,7 +5330,7 @@
|
|
|
5011
5330
|
setStatus("Exporting PDF…", "warning");
|
|
5012
5331
|
|
|
5013
5332
|
try {
|
|
5014
|
-
const response = await
|
|
5333
|
+
const response = await fetchWithTimeout("/export-pdf?token=" + encodeURIComponent(token), {
|
|
5015
5334
|
method: "POST",
|
|
5016
5335
|
headers: {
|
|
5017
5336
|
"Content-Type": "application/json",
|
|
@@ -5024,7 +5343,7 @@
|
|
|
5024
5343
|
editorPdfLanguage: editorPdfLanguage,
|
|
5025
5344
|
filenameHint: filenameHint,
|
|
5026
5345
|
}),
|
|
5027
|
-
});
|
|
5346
|
+
}, PDF_EXPORT_FETCH_TIMEOUT_MS, "PDF export");
|
|
5028
5347
|
|
|
5029
5348
|
const contentType = String(response.headers.get("content-type") || "").toLowerCase();
|
|
5030
5349
|
if (!response.ok) {
|
|
@@ -5178,7 +5497,7 @@
|
|
|
5178
5497
|
setStatus("Exporting HTML…", "warning");
|
|
5179
5498
|
|
|
5180
5499
|
try {
|
|
5181
|
-
const response = await
|
|
5500
|
+
const response = await fetchWithTimeout("/export-html?token=" + encodeURIComponent(token), {
|
|
5182
5501
|
method: "POST",
|
|
5183
5502
|
headers: {
|
|
5184
5503
|
"Content-Type": "application/json",
|
|
@@ -5192,7 +5511,7 @@
|
|
|
5192
5511
|
filenameHint: filenameHint,
|
|
5193
5512
|
title: titleHint,
|
|
5194
5513
|
}),
|
|
5195
|
-
});
|
|
5514
|
+
}, HTML_EXPORT_FETCH_TIMEOUT_MS, "HTML export");
|
|
5196
5515
|
|
|
5197
5516
|
const contentType = String(response.headers.get("content-type") || "").toLowerCase();
|
|
5198
5517
|
if (!response.ok) {
|
|
@@ -5521,7 +5840,7 @@
|
|
|
5521
5840
|
clearPreviewJumpHighlight(targetEl);
|
|
5522
5841
|
finishPreviewRender(targetEl);
|
|
5523
5842
|
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown, previewFallbackOptions);
|
|
5524
|
-
renderStudioPdfBlocksInElement(targetEl, pdfPrepared.blocks);
|
|
5843
|
+
renderStudioPdfBlocksInElement(targetEl, pdfPrepared.blocks, previewingEditorText);
|
|
5525
5844
|
applyPreviewAnnotationPlaceholdersToElement(targetEl, previewPrepared.placeholders);
|
|
5526
5845
|
await renderAnnotationMathInElement(targetEl);
|
|
5527
5846
|
decoratePdfEmbeds(targetEl);
|
|
@@ -13876,6 +14195,12 @@
|
|
|
13876
14195
|
});
|
|
13877
14196
|
}
|
|
13878
14197
|
|
|
14198
|
+
if (zenModeBtn) {
|
|
14199
|
+
zenModeBtn.addEventListener("click", () => {
|
|
14200
|
+
setStudioZenMode(!studioZenModeEnabled);
|
|
14201
|
+
});
|
|
14202
|
+
}
|
|
14203
|
+
|
|
13879
14204
|
sendRunBtn.addEventListener("click", () => {
|
|
13880
14205
|
if (getAbortablePendingKind() === "direct") {
|
|
13881
14206
|
requestCancelForPendingRequest("direct");
|
|
@@ -14057,6 +14382,13 @@
|
|
|
14057
14382
|
});
|
|
14058
14383
|
}
|
|
14059
14384
|
|
|
14385
|
+
document.addEventListener("click", (event) => {
|
|
14386
|
+
const target = event.target;
|
|
14387
|
+
const focusBtn = target instanceof Element ? target.closest(".studio-pdf-card-focus") : null;
|
|
14388
|
+
if (!focusBtn) return;
|
|
14389
|
+
handleStudioPdfFocusButtonClick(event);
|
|
14390
|
+
}, true);
|
|
14391
|
+
|
|
14060
14392
|
document.addEventListener("click", (event) => {
|
|
14061
14393
|
const target = event.target;
|
|
14062
14394
|
const copyBtn = target instanceof Element ? target.closest(".studio-copy-block-btn") : null;
|