pi-studio 0.9.48 → 0.9.50
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 +9 -5
- package/client/studio-client.js +494 -20
- package/client/studio-preview-resource-helpers.js +52 -0
- package/client/studio.css +86 -13
- package/index.ts +148 -75
- package/package.json +1 -1
- package/shared/studio-local-preview-path.js +46 -0
package/client/studio-client.js
CHANGED
|
@@ -116,6 +116,10 @@
|
|
|
116
116
|
const sendEditorBtn = document.getElementById("sendEditorBtn");
|
|
117
117
|
const openCompanionBtn = document.getElementById("openCompanionBtn");
|
|
118
118
|
const getEditorBtn = document.getElementById("getEditorBtn");
|
|
119
|
+
const studioHeaderEl = document.getElementById("studioHeader");
|
|
120
|
+
const hideStudioHeaderBtn = document.getElementById("hideStudioHeaderBtn");
|
|
121
|
+
const studioHeaderRevealEl = document.getElementById("studioHeaderReveal");
|
|
122
|
+
const studioHeaderRevealBtn = document.getElementById("studioHeaderRevealBtn");
|
|
119
123
|
const zenModeBtn = document.getElementById("zenModeBtn");
|
|
120
124
|
const sendRunBtn = document.getElementById("sendRunBtn");
|
|
121
125
|
const queueSteerBtn = document.getElementById("queueSteerBtn");
|
|
@@ -227,7 +231,10 @@
|
|
|
227
231
|
if (
|
|
228
232
|
!previewResourceHelpers
|
|
229
233
|
|| typeof previewResourceHelpers.areStudioPreviewResourceContextsEqual !== "function"
|
|
234
|
+
|| typeof previewResourceHelpers.buildStudioPdfVersionSignature !== "function"
|
|
235
|
+
|| typeof previewResourceHelpers.createStudioPdfVersionObservationState !== "function"
|
|
230
236
|
|| typeof previewResourceHelpers.hydrateStudioPreviewLocalImages !== "function"
|
|
237
|
+
|| typeof previewResourceHelpers.observeStudioPdfVersion !== "function"
|
|
231
238
|
) {
|
|
232
239
|
throw new Error("Studio preview resource helpers failed to load.");
|
|
233
240
|
}
|
|
@@ -271,11 +278,17 @@
|
|
|
271
278
|
let studioPdfFocusOpenLinkEl = null;
|
|
272
279
|
let studioPdfFocusSystemViewerBtn = null;
|
|
273
280
|
let studioPdfFocusRevealBtn = null;
|
|
281
|
+
let studioPdfFocusAutoRefreshBtn = null;
|
|
274
282
|
let studioPdfFocusFullscreenBtn = null;
|
|
275
283
|
let studioPdfFocusCloseBtn = null;
|
|
276
284
|
let studioPdfFocusLastFocusedEl = null;
|
|
277
285
|
let studioPdfFocusMovedFrameState = null;
|
|
278
286
|
let studioPdfFocusResourceQuery = null;
|
|
287
|
+
let studioPdfFocusSourceCard = null;
|
|
288
|
+
let studioPdfFocusStandaloneAutoRefreshState = null;
|
|
289
|
+
const studioPdfCardAutoRefreshStates = new WeakMap();
|
|
290
|
+
const STUDIO_PDF_AUTO_REFRESH_INTERVAL_MS = 1_000;
|
|
291
|
+
const STUDIO_PDF_AUTO_REFRESH_STABLE_OBSERVATIONS = 2;
|
|
279
292
|
let studioHtmlFocusOverlayEl = null;
|
|
280
293
|
let studioHtmlFocusShellEl = null;
|
|
281
294
|
let studioHtmlFocusFullscreenBtn = null;
|
|
@@ -2178,6 +2191,7 @@
|
|
|
2178
2191
|
let lineNumbersRenderRaf = null;
|
|
2179
2192
|
let annotationsEnabled = true;
|
|
2180
2193
|
const STUDIO_ZEN_MODE_STORAGE_KEY = "piStudio.zenMode";
|
|
2194
|
+
const STUDIO_HEADER_HIDDEN_STORAGE_KEY = "piStudio.headerHidden";
|
|
2181
2195
|
const studioUiRefreshEnabled = readStudioUiRefreshEnabled();
|
|
2182
2196
|
const EDITOR_FONT_SIZE_OPTIONS = [10, 11, 12, 13, 14, 15, 16, 18];
|
|
2183
2197
|
const RESPONSE_FONT_SIZE_OPTIONS = [11, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 18, 20];
|
|
@@ -2230,12 +2244,17 @@
|
|
|
2230
2244
|
let fileBrowserLoadNonce = 0;
|
|
2231
2245
|
let studioUiRefreshUi = null;
|
|
2232
2246
|
let studioZenModeEnabled = readStudioZenModeEnabled();
|
|
2247
|
+
let studioHeaderHidden = readStudioHeaderHiddenEnabled();
|
|
2248
|
+
let studioHeaderRevealTimer = null;
|
|
2233
2249
|
if (studioUiRefreshEnabled && document.body) {
|
|
2234
2250
|
document.body.classList.add("studio-ui-refresh");
|
|
2235
2251
|
}
|
|
2236
2252
|
if (studioZenModeEnabled && document.body) {
|
|
2237
2253
|
document.body.classList.add("studio-zen-mode");
|
|
2238
2254
|
}
|
|
2255
|
+
if ((studioZenModeEnabled || studioHeaderHidden) && document.body) {
|
|
2256
|
+
document.body.classList.add("studio-header-hidden");
|
|
2257
|
+
}
|
|
2239
2258
|
let scratchpadText = "";
|
|
2240
2259
|
let scratchpadReturnFocusEl = null;
|
|
2241
2260
|
let scratchpadPersistTimer = null;
|
|
@@ -2299,22 +2318,129 @@
|
|
|
2299
2318
|
}
|
|
2300
2319
|
}
|
|
2301
2320
|
|
|
2302
|
-
function
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2321
|
+
function readStudioHeaderHiddenEnabled() {
|
|
2322
|
+
const normalize = (value) => String(value == null ? "" : value).trim().toLowerCase();
|
|
2323
|
+
const isTruthy = (value) => ["1", "true", "yes", "on", "hidden"].indexOf(normalize(value)) !== -1;
|
|
2324
|
+
try {
|
|
2325
|
+
const stored = window.localStorage ? window.localStorage.getItem(STUDIO_HEADER_HIDDEN_STORAGE_KEY) : null;
|
|
2326
|
+
if (stored === null) return false;
|
|
2327
|
+
return isTruthy(stored);
|
|
2328
|
+
} catch {
|
|
2329
|
+
return false;
|
|
2330
|
+
}
|
|
2308
2331
|
}
|
|
2309
2332
|
|
|
2310
|
-
function
|
|
2311
|
-
|
|
2333
|
+
function persistStudioHeaderHiddenEnabled() {
|
|
2334
|
+
try {
|
|
2335
|
+
window.localStorage && window.localStorage.setItem(STUDIO_HEADER_HIDDEN_STORAGE_KEY, studioHeaderHidden ? "1" : "0");
|
|
2336
|
+
} catch {}
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
function focusStudioChromeControl(control) {
|
|
2340
|
+
if (!control || typeof control.focus !== "function") return;
|
|
2341
|
+
window.setTimeout(() => {
|
|
2342
|
+
try {
|
|
2343
|
+
control.focus({ preventScroll: true });
|
|
2344
|
+
} catch {
|
|
2345
|
+
try { control.focus(); } catch {}
|
|
2346
|
+
}
|
|
2347
|
+
}, 0);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
function showStudioHeaderRevealControl() {
|
|
2351
|
+
if (!studioHeaderRevealEl || !studioHeaderRevealBtn) return;
|
|
2352
|
+
studioHeaderRevealEl.classList.add("is-open");
|
|
2353
|
+
if (studioHeaderRevealTimer) window.clearTimeout(studioHeaderRevealTimer);
|
|
2354
|
+
studioHeaderRevealTimer = window.setTimeout(() => {
|
|
2355
|
+
studioHeaderRevealTimer = null;
|
|
2356
|
+
studioHeaderRevealEl.classList.remove("is-open");
|
|
2357
|
+
}, 1800);
|
|
2358
|
+
focusStudioChromeControl(studioHeaderRevealBtn);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
function syncStudioHeaderVisibilityUi() {
|
|
2362
|
+
const hidden = Boolean(studioZenModeEnabled || studioHeaderHidden);
|
|
2363
|
+
if (document.body) document.body.classList.toggle("studio-header-hidden", hidden);
|
|
2364
|
+
if (studioHeaderRevealEl) {
|
|
2365
|
+
studioHeaderRevealEl.hidden = !hidden;
|
|
2366
|
+
studioHeaderRevealEl.dataset.mode = studioZenModeEnabled ? "zen" : "header";
|
|
2367
|
+
if (!hidden) studioHeaderRevealEl.classList.remove("is-open");
|
|
2368
|
+
}
|
|
2369
|
+
if (!hidden && studioHeaderRevealTimer) {
|
|
2370
|
+
window.clearTimeout(studioHeaderRevealTimer);
|
|
2371
|
+
studioHeaderRevealTimer = null;
|
|
2372
|
+
}
|
|
2373
|
+
if (studioHeaderRevealBtn) {
|
|
2374
|
+
const label = studioZenModeEnabled ? "Exit Zen" : "Show header";
|
|
2375
|
+
studioHeaderRevealBtn.textContent = label;
|
|
2376
|
+
studioHeaderRevealBtn.setAttribute("aria-label", label);
|
|
2377
|
+
studioHeaderRevealBtn.title = studioZenModeEnabled
|
|
2378
|
+
? "Exit Zen and restore the Studio header. Shortcut: F9."
|
|
2379
|
+
: "Restore the Studio header.";
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
function syncStudioZenModeUi() {
|
|
2384
|
+
if (document.body) document.body.classList.toggle("studio-zen-mode", studioZenModeEnabled);
|
|
2385
|
+
if (zenModeBtn) {
|
|
2386
|
+
zenModeBtn.textContent = studioZenModeEnabled ? "Exit Zen" : "Zen";
|
|
2387
|
+
zenModeBtn.title = studioZenModeEnabled
|
|
2388
|
+
? "Exit Zen and restore the Studio header. Shortcut: F9."
|
|
2389
|
+
: "Hide the Studio header and secondary controls. Shortcut: F9.";
|
|
2390
|
+
zenModeBtn.setAttribute("aria-pressed", studioZenModeEnabled ? "true" : "false");
|
|
2391
|
+
}
|
|
2392
|
+
syncStudioHeaderVisibilityUi();
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
function setStudioHeaderHidden(enabled, options) {
|
|
2396
|
+
const nextHidden = Boolean(enabled);
|
|
2397
|
+
const moveFocusToReveal = Boolean(
|
|
2398
|
+
nextHidden
|
|
2399
|
+
&& (
|
|
2400
|
+
(options && options.focusReveal)
|
|
2401
|
+
|| (studioHeaderEl && document.activeElement && studioHeaderEl.contains(document.activeElement))
|
|
2402
|
+
)
|
|
2403
|
+
);
|
|
2404
|
+
studioHeaderHidden = nextHidden;
|
|
2405
|
+
persistStudioHeaderHiddenEnabled();
|
|
2406
|
+
closeStudioUiRefreshMenus();
|
|
2407
|
+
closeExportPreviewMenu();
|
|
2408
|
+
syncStudioHeaderVisibilityUi();
|
|
2409
|
+
if (moveFocusToReveal) showStudioHeaderRevealControl();
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
function setStudioZenMode(enabled, options) {
|
|
2413
|
+
const nextEnabled = Boolean(enabled);
|
|
2414
|
+
const moveFocusToReveal = Boolean(
|
|
2415
|
+
nextEnabled
|
|
2416
|
+
&& (
|
|
2417
|
+
(options && options.focusReveal)
|
|
2418
|
+
|| (studioHeaderEl && document.activeElement && studioHeaderEl.contains(document.activeElement))
|
|
2419
|
+
)
|
|
2420
|
+
);
|
|
2421
|
+
studioZenModeEnabled = nextEnabled;
|
|
2422
|
+
if (!studioZenModeEnabled) {
|
|
2423
|
+
studioHeaderHidden = false;
|
|
2424
|
+
persistStudioHeaderHiddenEnabled();
|
|
2425
|
+
}
|
|
2312
2426
|
try {
|
|
2313
2427
|
window.localStorage && window.localStorage.setItem(STUDIO_ZEN_MODE_STORAGE_KEY, studioZenModeEnabled ? "1" : "0");
|
|
2314
2428
|
} catch {}
|
|
2315
2429
|
closeStudioUiRefreshMenus();
|
|
2316
2430
|
closeExportPreviewMenu();
|
|
2317
2431
|
syncStudioZenModeUi();
|
|
2432
|
+
if (moveFocusToReveal) showStudioHeaderRevealControl();
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
function restoreStudioHeaderFromReveal() {
|
|
2436
|
+
const wasZenMode = studioZenModeEnabled;
|
|
2437
|
+
if (wasZenMode) {
|
|
2438
|
+
setStudioZenMode(false);
|
|
2439
|
+
} else {
|
|
2440
|
+
setStudioHeaderHidden(false);
|
|
2441
|
+
}
|
|
2442
|
+
focusStudioChromeControl(wasZenMode ? zenModeBtn : hideStudioHeaderBtn);
|
|
2443
|
+
setStatus(wasZenMode ? "Zen mode off. Studio header restored." : "Studio header shown.");
|
|
2318
2444
|
}
|
|
2319
2445
|
|
|
2320
2446
|
function makeStudioUiRefreshElement(tagName, className, text) {
|
|
@@ -4489,6 +4615,15 @@
|
|
|
4489
4615
|
|
|
4490
4616
|
if (handleStudioImageFocusShortcut(event)) return;
|
|
4491
4617
|
|
|
4618
|
+
const otherModalOwnsEvent = scratchpadOwnsEvent
|
|
4619
|
+
|| reviewNotesOwnsEvent
|
|
4620
|
+
|| outlineOwnsEvent
|
|
4621
|
+
|| shortcutsOwnsEvent
|
|
4622
|
+
|| htmlFocusOwnsEvent
|
|
4623
|
+
|| imageFocusOwnsEvent
|
|
4624
|
+
|| quizOwnsEvent;
|
|
4625
|
+
if (!otherModalOwnsEvent && handleStudioPdfRefreshShortcut(event)) return;
|
|
4626
|
+
|
|
4492
4627
|
if (isScratchpadOpen() && plainEscape) {
|
|
4493
4628
|
event.preventDefault();
|
|
4494
4629
|
closeScratchpad();
|
|
@@ -4640,8 +4775,17 @@
|
|
|
4640
4775
|
const isZenModeShortcut = key === "F9" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
|
|
4641
4776
|
if (isZenModeShortcut) {
|
|
4642
4777
|
event.preventDefault();
|
|
4643
|
-
|
|
4644
|
-
|
|
4778
|
+
const restoringHeaderOnly = Boolean(!studioZenModeEnabled && studioHeaderHidden);
|
|
4779
|
+
if (studioZenModeEnabled) {
|
|
4780
|
+
setStudioZenMode(false);
|
|
4781
|
+
} else if (studioHeaderHidden) {
|
|
4782
|
+
setStudioHeaderHidden(false);
|
|
4783
|
+
} else {
|
|
4784
|
+
setStudioZenMode(true);
|
|
4785
|
+
}
|
|
4786
|
+
setStatus(studioZenModeEnabled
|
|
4787
|
+
? "Zen mode on. Studio header hidden; press F9 to exit."
|
|
4788
|
+
: (restoringHeaderOnly ? "Studio header restored." : "Zen mode off. Studio header restored."));
|
|
4645
4789
|
return;
|
|
4646
4790
|
}
|
|
4647
4791
|
|
|
@@ -6851,7 +6995,7 @@
|
|
|
6851
6995
|
}
|
|
6852
6996
|
|
|
6853
6997
|
function parseStudioPdfBlockOptions(body) {
|
|
6854
|
-
const options = { path: "", title: "", caption: "", page: "", height: "" };
|
|
6998
|
+
const options = { path: "", title: "", caption: "", page: "", height: "", watch: "" };
|
|
6855
6999
|
String(body || "").split(/\r?\n/).forEach((line) => {
|
|
6856
7000
|
const raw = String(line || "").trim();
|
|
6857
7001
|
if (!raw || raw.startsWith("#")) return;
|
|
@@ -6864,6 +7008,7 @@
|
|
|
6864
7008
|
else if (key === "caption") options.caption = value;
|
|
6865
7009
|
else if (key === "page") options.page = value;
|
|
6866
7010
|
else if (key === "height") options.height = value;
|
|
7011
|
+
else if (key === "watch" || key === "auto-refresh" || key === "autorefresh") options.watch = value;
|
|
6867
7012
|
return;
|
|
6868
7013
|
}
|
|
6869
7014
|
if (!options.path) options.path = stripMatchingQuotes(raw);
|
|
@@ -6895,6 +7040,10 @@
|
|
|
6895
7040
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
6896
7041
|
}
|
|
6897
7042
|
|
|
7043
|
+
function normalizeStudioPdfAutoRefresh(value) {
|
|
7044
|
+
return /^(?:1|true|yes|on|watch)$/i.test(String(value || "").trim());
|
|
7045
|
+
}
|
|
7046
|
+
|
|
6898
7047
|
function isStudioPdfFocusOpen() {
|
|
6899
7048
|
return Boolean(studioPdfFocusOverlayEl && studioPdfFocusOverlayEl.hidden === false);
|
|
6900
7049
|
}
|
|
@@ -6969,11 +7118,21 @@
|
|
|
6969
7118
|
refreshBtn.type = "button";
|
|
6970
7119
|
refreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-refresh";
|
|
6971
7120
|
refreshBtn.textContent = "Refresh";
|
|
6972
|
-
refreshBtn.title = "Reload this PDF preview from disk.";
|
|
7121
|
+
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+R.";
|
|
6973
7122
|
refreshBtn.setAttribute("aria-label", "Refresh PDF preview from disk");
|
|
6974
7123
|
refreshBtn.addEventListener("click", () => refreshStudioPdfFocusViewer());
|
|
6975
7124
|
actions.appendChild(refreshBtn);
|
|
6976
7125
|
|
|
7126
|
+
const autoRefreshBtn = document.createElement("button");
|
|
7127
|
+
autoRefreshBtn.type = "button";
|
|
7128
|
+
autoRefreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-auto-refresh";
|
|
7129
|
+
autoRefreshBtn.textContent = "Auto-refresh";
|
|
7130
|
+
autoRefreshBtn.title = "Watch this local PDF and reload it after a changed file is stable on disk.";
|
|
7131
|
+
autoRefreshBtn.setAttribute("aria-label", "Enable PDF auto-refresh");
|
|
7132
|
+
autoRefreshBtn.setAttribute("aria-pressed", "false");
|
|
7133
|
+
autoRefreshBtn.addEventListener("click", () => toggleStudioPdfFocusAutoRefresh());
|
|
7134
|
+
actions.appendChild(autoRefreshBtn);
|
|
7135
|
+
|
|
6977
7136
|
const fullscreenBtn = document.createElement("button");
|
|
6978
7137
|
fullscreenBtn.type = "button";
|
|
6979
7138
|
fullscreenBtn.className = "studio-pdf-focus-btn studio-pdf-focus-fullscreen";
|
|
@@ -7030,6 +7189,7 @@
|
|
|
7030
7189
|
studioPdfFocusOpenLinkEl = openLink;
|
|
7031
7190
|
studioPdfFocusSystemViewerBtn = systemViewerBtn;
|
|
7032
7191
|
studioPdfFocusRevealBtn = revealBtn;
|
|
7192
|
+
studioPdfFocusAutoRefreshBtn = autoRefreshBtn;
|
|
7033
7193
|
studioPdfFocusFullscreenBtn = fullscreenBtn;
|
|
7034
7194
|
studioPdfFocusCloseBtn = closeBtn;
|
|
7035
7195
|
syncStudioPdfFocusFullscreenButton();
|
|
@@ -7037,11 +7197,12 @@
|
|
|
7037
7197
|
return overlay;
|
|
7038
7198
|
}
|
|
7039
7199
|
|
|
7040
|
-
function openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery) {
|
|
7200
|
+
function openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery, sourceCard) {
|
|
7041
7201
|
const src = String(viewerUrl || "").trim();
|
|
7042
7202
|
if (!src) return;
|
|
7043
7203
|
ensureStudioPdfFocusViewer();
|
|
7044
7204
|
studioPdfFocusResourceQuery = normalizeStudioPdfResourceQuery(resourceQuery);
|
|
7205
|
+
setStudioPdfFocusAutoRefreshSource(sourceCard, studioPdfFocusResourceQuery);
|
|
7045
7206
|
syncStudioPdfFocusResourceActions();
|
|
7046
7207
|
studioPdfFocusLastFocusedEl = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
7047
7208
|
if (studioPdfFocusTitleEl) studioPdfFocusTitleEl.textContent = String(title || "PDF preview").trim() || "PDF preview";
|
|
@@ -7071,6 +7232,7 @@
|
|
|
7071
7232
|
restoreStudioPdfFocusMovedFrame();
|
|
7072
7233
|
if (studioPdfFocusFrameEl) studioPdfFocusFrameEl.src = "about:blank";
|
|
7073
7234
|
if (document.body) document.body.classList.remove("studio-pdf-focus-open");
|
|
7235
|
+
clearStudioPdfFocusAutoRefreshSource();
|
|
7074
7236
|
studioPdfFocusResourceQuery = null;
|
|
7075
7237
|
syncStudioPdfFocusResourceActions();
|
|
7076
7238
|
syncStudioPdfFocusFullscreenButton();
|
|
@@ -7113,6 +7275,214 @@
|
|
|
7113
7275
|
return query;
|
|
7114
7276
|
}
|
|
7115
7277
|
|
|
7278
|
+
function syncStudioPdfAutoRefreshButton(button, enabled, available) {
|
|
7279
|
+
if (!button) return;
|
|
7280
|
+
const active = Boolean(enabled && available);
|
|
7281
|
+
button.disabled = !available;
|
|
7282
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
7283
|
+
button.setAttribute("aria-label", active ? "Disable PDF auto-refresh" : "Enable PDF auto-refresh");
|
|
7284
|
+
button.textContent = "Auto-refresh";
|
|
7285
|
+
button.title = active
|
|
7286
|
+
? "Auto-refresh is on. Studio checks this PDF while the tab is visible and reloads it after a changed file is stable on disk."
|
|
7287
|
+
: "Watch this local PDF and reload it after a changed file is stable on disk.";
|
|
7288
|
+
}
|
|
7289
|
+
|
|
7290
|
+
function createStudioPdfAutoRefreshState(resourceQuery, hooks) {
|
|
7291
|
+
return {
|
|
7292
|
+
resourceQuery: normalizeStudioPdfResourceQuery(resourceQuery),
|
|
7293
|
+
enabled: false,
|
|
7294
|
+
timer: null,
|
|
7295
|
+
inFlight: false,
|
|
7296
|
+
generation: 0,
|
|
7297
|
+
observation: previewResourceHelpers.createStudioPdfVersionObservationState(),
|
|
7298
|
+
isAlive: hooks && typeof hooks.isAlive === "function" ? hooks.isAlive : () => false,
|
|
7299
|
+
refresh: hooks && typeof hooks.refresh === "function" ? hooks.refresh : () => false,
|
|
7300
|
+
sync: hooks && typeof hooks.sync === "function" ? hooks.sync : () => {},
|
|
7301
|
+
};
|
|
7302
|
+
}
|
|
7303
|
+
|
|
7304
|
+
function resetStudioPdfAutoRefreshObservation(state) {
|
|
7305
|
+
if (!state) return;
|
|
7306
|
+
state.observation = previewResourceHelpers.createStudioPdfVersionObservationState();
|
|
7307
|
+
}
|
|
7308
|
+
|
|
7309
|
+
function scheduleStudioPdfAutoRefreshPoll(state, delayMs) {
|
|
7310
|
+
if (!state || !state.enabled) return;
|
|
7311
|
+
if (state.timer) window.clearTimeout(state.timer);
|
|
7312
|
+
state.timer = window.setTimeout(() => {
|
|
7313
|
+
state.timer = null;
|
|
7314
|
+
void pollStudioPdfAutoRefreshState(state);
|
|
7315
|
+
}, Math.max(0, Number(delayMs) || 0));
|
|
7316
|
+
}
|
|
7317
|
+
|
|
7318
|
+
function stopStudioPdfAutoRefreshState(state) {
|
|
7319
|
+
if (!state) return;
|
|
7320
|
+
state.enabled = false;
|
|
7321
|
+
state.inFlight = false;
|
|
7322
|
+
state.generation += 1;
|
|
7323
|
+
if (state.timer) window.clearTimeout(state.timer);
|
|
7324
|
+
state.timer = null;
|
|
7325
|
+
resetStudioPdfAutoRefreshObservation(state);
|
|
7326
|
+
state.sync();
|
|
7327
|
+
}
|
|
7328
|
+
|
|
7329
|
+
function setStudioPdfAutoRefreshEnabled(state, enabled, options) {
|
|
7330
|
+
if (!state) return false;
|
|
7331
|
+
const nextEnabled = Boolean(enabled && state.resourceQuery);
|
|
7332
|
+
if (state.enabled === nextEnabled) {
|
|
7333
|
+
state.sync();
|
|
7334
|
+
return nextEnabled;
|
|
7335
|
+
}
|
|
7336
|
+
if (state.timer) window.clearTimeout(state.timer);
|
|
7337
|
+
state.timer = null;
|
|
7338
|
+
state.enabled = nextEnabled;
|
|
7339
|
+
state.inFlight = false;
|
|
7340
|
+
state.generation += 1;
|
|
7341
|
+
resetStudioPdfAutoRefreshObservation(state);
|
|
7342
|
+
state.sync();
|
|
7343
|
+
if (nextEnabled) {
|
|
7344
|
+
if (!options || !options.silent) state.refresh();
|
|
7345
|
+
scheduleStudioPdfAutoRefreshPoll(state, 0);
|
|
7346
|
+
}
|
|
7347
|
+
if (!options || !options.silent) {
|
|
7348
|
+
setStatus(nextEnabled ? "PDF auto-refresh on." : "PDF auto-refresh off.", "success");
|
|
7349
|
+
}
|
|
7350
|
+
return nextEnabled;
|
|
7351
|
+
}
|
|
7352
|
+
|
|
7353
|
+
async function fetchStudioPdfVersionSignature(resourceQuery) {
|
|
7354
|
+
const url = buildStudioPdfResourceUrl(resourceQuery, false);
|
|
7355
|
+
if (!url) throw new Error("Could not resolve this PDF for auto-refresh.");
|
|
7356
|
+
const response = await fetchWithTimeout(url, {
|
|
7357
|
+
method: "HEAD",
|
|
7358
|
+
cache: "no-store",
|
|
7359
|
+
}, 4_000, "PDF auto-refresh check");
|
|
7360
|
+
if (!response.ok) throw new Error("PDF auto-refresh check failed with HTTP " + response.status + ".");
|
|
7361
|
+
return previewResourceHelpers.buildStudioPdfVersionSignature(response.headers);
|
|
7362
|
+
}
|
|
7363
|
+
|
|
7364
|
+
async function pollStudioPdfAutoRefreshState(state) {
|
|
7365
|
+
if (!state || !state.enabled || state.inFlight) return;
|
|
7366
|
+
if (!state.isAlive()) {
|
|
7367
|
+
stopStudioPdfAutoRefreshState(state);
|
|
7368
|
+
return;
|
|
7369
|
+
}
|
|
7370
|
+
if (document.hidden || document.visibilityState === "hidden") {
|
|
7371
|
+
scheduleStudioPdfAutoRefreshPoll(state, STUDIO_PDF_AUTO_REFRESH_INTERVAL_MS);
|
|
7372
|
+
return;
|
|
7373
|
+
}
|
|
7374
|
+
const generation = state.generation;
|
|
7375
|
+
state.inFlight = true;
|
|
7376
|
+
try {
|
|
7377
|
+
const signature = await fetchStudioPdfVersionSignature(state.resourceQuery);
|
|
7378
|
+
if (!state.enabled || state.generation !== generation) return;
|
|
7379
|
+
if (document.hidden || document.visibilityState === "hidden") return;
|
|
7380
|
+
if (!state.isAlive()) {
|
|
7381
|
+
stopStudioPdfAutoRefreshState(state);
|
|
7382
|
+
return;
|
|
7383
|
+
}
|
|
7384
|
+
const observed = previewResourceHelpers.observeStudioPdfVersion(
|
|
7385
|
+
state.observation,
|
|
7386
|
+
signature,
|
|
7387
|
+
STUDIO_PDF_AUTO_REFRESH_STABLE_OBSERVATIONS,
|
|
7388
|
+
);
|
|
7389
|
+
state.observation = observed.state;
|
|
7390
|
+
if (observed.changed) state.refresh();
|
|
7391
|
+
} catch {
|
|
7392
|
+
// LaTeX tools may briefly replace or lock the output PDF. Retry quietly.
|
|
7393
|
+
} finally {
|
|
7394
|
+
if (state.generation !== generation) return;
|
|
7395
|
+
state.inFlight = false;
|
|
7396
|
+
if (state.enabled) scheduleStudioPdfAutoRefreshPoll(state, STUDIO_PDF_AUTO_REFRESH_INTERVAL_MS);
|
|
7397
|
+
}
|
|
7398
|
+
}
|
|
7399
|
+
|
|
7400
|
+
function syncStudioPdfCardAutoRefreshButton(card) {
|
|
7401
|
+
if (!card) return;
|
|
7402
|
+
const state = studioPdfCardAutoRefreshStates.get(card) || null;
|
|
7403
|
+
const button = typeof card.querySelector === "function" ? card.querySelector(".studio-pdf-card-auto-refresh") : null;
|
|
7404
|
+
syncStudioPdfAutoRefreshButton(button, state && state.enabled, Boolean(state && state.resourceQuery));
|
|
7405
|
+
}
|
|
7406
|
+
|
|
7407
|
+
function ensureStudioPdfCardAutoRefreshState(card, resourceQuery) {
|
|
7408
|
+
if (!card) return null;
|
|
7409
|
+
let state = studioPdfCardAutoRefreshStates.get(card) || null;
|
|
7410
|
+
if (state) return state;
|
|
7411
|
+
state = createStudioPdfAutoRefreshState(resourceQuery, {
|
|
7412
|
+
isAlive: () => Boolean(card.isConnected || (isStudioPdfFocusOpen() && studioPdfFocusSourceCard === card)),
|
|
7413
|
+
refresh: () => {
|
|
7414
|
+
if (isStudioPdfFocusOpen() && studioPdfFocusSourceCard === card) {
|
|
7415
|
+
return refreshStudioPdfFocusViewer({ automatic: true });
|
|
7416
|
+
}
|
|
7417
|
+
return refreshStudioPdfCard(card, { automatic: true });
|
|
7418
|
+
},
|
|
7419
|
+
sync: () => {
|
|
7420
|
+
syncStudioPdfCardAutoRefreshButton(card);
|
|
7421
|
+
if (studioPdfFocusSourceCard === card) syncStudioPdfFocusAutoRefreshButton();
|
|
7422
|
+
},
|
|
7423
|
+
});
|
|
7424
|
+
studioPdfCardAutoRefreshStates.set(card, state);
|
|
7425
|
+
syncStudioPdfCardAutoRefreshButton(card);
|
|
7426
|
+
return state;
|
|
7427
|
+
}
|
|
7428
|
+
|
|
7429
|
+
function setStudioPdfCardAutoRefresh(card, enabled, options) {
|
|
7430
|
+
const state = studioPdfCardAutoRefreshStates.get(card) || null;
|
|
7431
|
+
return setStudioPdfAutoRefreshEnabled(state, enabled, options);
|
|
7432
|
+
}
|
|
7433
|
+
|
|
7434
|
+
function getStudioPdfFocusAutoRefreshState() {
|
|
7435
|
+
if (studioPdfFocusSourceCard) return studioPdfCardAutoRefreshStates.get(studioPdfFocusSourceCard) || null;
|
|
7436
|
+
return studioPdfFocusStandaloneAutoRefreshState;
|
|
7437
|
+
}
|
|
7438
|
+
|
|
7439
|
+
function syncStudioPdfFocusAutoRefreshButton() {
|
|
7440
|
+
const state = getStudioPdfFocusAutoRefreshState();
|
|
7441
|
+
syncStudioPdfAutoRefreshButton(
|
|
7442
|
+
studioPdfFocusAutoRefreshBtn,
|
|
7443
|
+
state && state.enabled,
|
|
7444
|
+
Boolean(state && state.resourceQuery),
|
|
7445
|
+
);
|
|
7446
|
+
}
|
|
7447
|
+
|
|
7448
|
+
function clearStudioPdfFocusAutoRefreshSource() {
|
|
7449
|
+
if (studioPdfFocusStandaloneAutoRefreshState) stopStudioPdfAutoRefreshState(studioPdfFocusStandaloneAutoRefreshState);
|
|
7450
|
+
studioPdfFocusStandaloneAutoRefreshState = null;
|
|
7451
|
+
studioPdfFocusSourceCard = null;
|
|
7452
|
+
syncStudioPdfFocusAutoRefreshButton();
|
|
7453
|
+
}
|
|
7454
|
+
|
|
7455
|
+
function setStudioPdfFocusAutoRefreshSource(sourceCard, resourceQuery) {
|
|
7456
|
+
clearStudioPdfFocusAutoRefreshSource();
|
|
7457
|
+
const card = sourceCard instanceof Element ? sourceCard : null;
|
|
7458
|
+
if (card) {
|
|
7459
|
+
studioPdfFocusSourceCard = card;
|
|
7460
|
+
ensureStudioPdfCardAutoRefreshState(card, resourceQuery);
|
|
7461
|
+
syncStudioPdfFocusAutoRefreshButton();
|
|
7462
|
+
return;
|
|
7463
|
+
}
|
|
7464
|
+
const query = normalizeStudioPdfResourceQuery(resourceQuery);
|
|
7465
|
+
if (!query) {
|
|
7466
|
+
syncStudioPdfFocusAutoRefreshButton();
|
|
7467
|
+
return;
|
|
7468
|
+
}
|
|
7469
|
+
studioPdfFocusStandaloneAutoRefreshState = createStudioPdfAutoRefreshState(query, {
|
|
7470
|
+
isAlive: () => Boolean(isStudioPdfFocusOpen() && !studioPdfFocusSourceCard),
|
|
7471
|
+
refresh: () => refreshStudioPdfFocusViewer({ automatic: true }),
|
|
7472
|
+
sync: () => syncStudioPdfFocusAutoRefreshButton(),
|
|
7473
|
+
});
|
|
7474
|
+
syncStudioPdfFocusAutoRefreshButton();
|
|
7475
|
+
}
|
|
7476
|
+
|
|
7477
|
+
function toggleStudioPdfFocusAutoRefresh() {
|
|
7478
|
+
const state = getStudioPdfFocusAutoRefreshState();
|
|
7479
|
+
if (!state) {
|
|
7480
|
+
setStatus("Could not resolve this PDF for auto-refresh.", "warning");
|
|
7481
|
+
return false;
|
|
7482
|
+
}
|
|
7483
|
+
return setStudioPdfAutoRefreshEnabled(state, !state.enabled);
|
|
7484
|
+
}
|
|
7485
|
+
|
|
7116
7486
|
async function runStudioPdfLocalAction(action, resourceQuery) {
|
|
7117
7487
|
const query = normalizeStudioPdfResourceQuery(resourceQuery);
|
|
7118
7488
|
if (!query) {
|
|
@@ -7174,7 +7544,7 @@
|
|
|
7174
7544
|
if (focusBtn && focusBtn.dataset) focusBtn.dataset.studioPdfViewerUrl = nextUrl;
|
|
7175
7545
|
}
|
|
7176
7546
|
|
|
7177
|
-
function refreshStudioPdfCard(card) {
|
|
7547
|
+
function refreshStudioPdfCard(card, options) {
|
|
7178
7548
|
if (!card) return false;
|
|
7179
7549
|
const frame = typeof card.querySelector === "function" ? card.querySelector("iframe.studio-pdf-frame") : null;
|
|
7180
7550
|
const currentUrl = String(card.dataset && card.dataset.studioPdfViewerUrl ? card.dataset.studioPdfViewerUrl : "").trim()
|
|
@@ -7182,7 +7552,14 @@
|
|
|
7182
7552
|
const nextUrl = buildRefreshedStudioPdfViewerUrl(currentUrl);
|
|
7183
7553
|
if (!nextUrl) return false;
|
|
7184
7554
|
syncStudioPdfCardViewerUrl(card, nextUrl);
|
|
7185
|
-
|
|
7555
|
+
if (!options || !options.automatic) {
|
|
7556
|
+
resetStudioPdfAutoRefreshObservation(studioPdfCardAutoRefreshStates.get(card) || null);
|
|
7557
|
+
}
|
|
7558
|
+
if (!options || !options.silent) {
|
|
7559
|
+
setStatus(options && options.automatic
|
|
7560
|
+
? "PDF changed on disk; refreshed preview."
|
|
7561
|
+
: "Refreshed PDF preview from disk.", "success");
|
|
7562
|
+
}
|
|
7186
7563
|
return true;
|
|
7187
7564
|
}
|
|
7188
7565
|
|
|
@@ -7193,7 +7570,7 @@
|
|
|
7193
7570
|
return studioPdfFocusFrameEl;
|
|
7194
7571
|
}
|
|
7195
7572
|
|
|
7196
|
-
function refreshStudioPdfFocusViewer() {
|
|
7573
|
+
function refreshStudioPdfFocusViewer(options) {
|
|
7197
7574
|
const frame = getStudioPdfFocusActiveFrame();
|
|
7198
7575
|
const currentUrl = String(frame && frame.src ? frame.src : "").trim()
|
|
7199
7576
|
|| String(studioPdfFocusOpenLinkEl && studioPdfFocusOpenLinkEl.href ? studioPdfFocusOpenLinkEl.href : "").trim();
|
|
@@ -7204,7 +7581,58 @@
|
|
|
7204
7581
|
}
|
|
7205
7582
|
if (frame) frame.src = nextUrl;
|
|
7206
7583
|
if (studioPdfFocusOpenLinkEl) studioPdfFocusOpenLinkEl.href = nextUrl;
|
|
7207
|
-
|
|
7584
|
+
if (studioPdfFocusSourceCard) syncStudioPdfCardViewerUrl(studioPdfFocusSourceCard, nextUrl);
|
|
7585
|
+
if (!options || !options.automatic) {
|
|
7586
|
+
resetStudioPdfAutoRefreshObservation(getStudioPdfFocusAutoRefreshState());
|
|
7587
|
+
}
|
|
7588
|
+
if (!options || !options.silent) {
|
|
7589
|
+
setStatus(options && options.automatic
|
|
7590
|
+
? "PDF changed on disk; refreshed preview."
|
|
7591
|
+
: "Refreshed PDF preview from disk.", "success");
|
|
7592
|
+
}
|
|
7593
|
+
return true;
|
|
7594
|
+
}
|
|
7595
|
+
|
|
7596
|
+
function getVisibleStudioPdfCards() {
|
|
7597
|
+
return Array.from(document.querySelectorAll(".studio-pdf-card")).filter((card) => {
|
|
7598
|
+
if (!card || !card.isConnected || card.hidden) return false;
|
|
7599
|
+
if (typeof card.getClientRects === "function" && card.getClientRects().length === 0) return false;
|
|
7600
|
+
try {
|
|
7601
|
+
const style = window.getComputedStyle(card);
|
|
7602
|
+
return style.display !== "none" && style.visibility !== "hidden";
|
|
7603
|
+
} catch {
|
|
7604
|
+
return true;
|
|
7605
|
+
}
|
|
7606
|
+
});
|
|
7607
|
+
}
|
|
7608
|
+
|
|
7609
|
+
function refreshVisibleStudioPdfPreviews() {
|
|
7610
|
+
if (isStudioPdfFocusOpen()) return refreshStudioPdfFocusViewer();
|
|
7611
|
+
const cards = getVisibleStudioPdfCards();
|
|
7612
|
+
let refreshed = 0;
|
|
7613
|
+
cards.forEach((card) => {
|
|
7614
|
+
if (refreshStudioPdfCard(card, { silent: true })) refreshed += 1;
|
|
7615
|
+
});
|
|
7616
|
+
if (refreshed > 0) {
|
|
7617
|
+
setStatus(refreshed === 1
|
|
7618
|
+
? "Refreshed PDF preview from disk."
|
|
7619
|
+
: ("Refreshed " + refreshed + " PDF previews from disk."), "success");
|
|
7620
|
+
return true;
|
|
7621
|
+
}
|
|
7622
|
+
return false;
|
|
7623
|
+
}
|
|
7624
|
+
|
|
7625
|
+
function handleStudioPdfRefreshShortcut(event) {
|
|
7626
|
+
if (!event) return false;
|
|
7627
|
+
const key = typeof event.key === "string" ? event.key.toLowerCase() : "";
|
|
7628
|
+
const code = typeof event.code === "string" ? event.code : "";
|
|
7629
|
+
const matches = (key === "r" || code === "KeyR")
|
|
7630
|
+
&& (event.metaKey || event.ctrlKey)
|
|
7631
|
+
&& event.altKey
|
|
7632
|
+
&& !event.shiftKey;
|
|
7633
|
+
if (!matches) return false;
|
|
7634
|
+
if (!refreshVisibleStudioPdfPreviews()) return false;
|
|
7635
|
+
event.preventDefault();
|
|
7208
7636
|
return true;
|
|
7209
7637
|
}
|
|
7210
7638
|
|
|
@@ -7285,7 +7713,7 @@
|
|
|
7285
7713
|
})
|
|
7286
7714
|
: null;
|
|
7287
7715
|
if (!viewerUrl) return false;
|
|
7288
|
-
openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery);
|
|
7716
|
+
openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery, card);
|
|
7289
7717
|
return true;
|
|
7290
7718
|
}
|
|
7291
7719
|
|
|
@@ -7698,6 +8126,7 @@
|
|
|
7698
8126
|
const caption = String(options.caption || "").trim();
|
|
7699
8127
|
const height = normalizeStudioPdfHeight(options.height);
|
|
7700
8128
|
const page = normalizeStudioPdfPage(options.page);
|
|
8129
|
+
const autoRefreshRequested = normalizeStudioPdfAutoRefresh(options.watch);
|
|
7701
8130
|
const resourceQuery = buildStudioPdfResourceQuery(options, useEditorResourceContext);
|
|
7702
8131
|
const resourceUrl = buildStudioPdfResourceUrl(options, useEditorResourceContext);
|
|
7703
8132
|
const viewerUrl = resourceUrl && page ? resourceUrl + "#page=" + encodeURIComponent(String(page)) : resourceUrl;
|
|
@@ -7778,7 +8207,7 @@
|
|
|
7778
8207
|
refreshBtn.type = "button";
|
|
7779
8208
|
refreshBtn.className = "studio-pdf-card-action studio-pdf-card-refresh";
|
|
7780
8209
|
refreshBtn.textContent = "Refresh";
|
|
7781
|
-
refreshBtn.title = "Reload this PDF preview from disk.";
|
|
8210
|
+
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+R.";
|
|
7782
8211
|
refreshBtn.addEventListener("click", (event) => {
|
|
7783
8212
|
event.preventDefault();
|
|
7784
8213
|
event.stopPropagation();
|
|
@@ -7786,6 +8215,25 @@
|
|
|
7786
8215
|
});
|
|
7787
8216
|
actions.appendChild(refreshBtn);
|
|
7788
8217
|
|
|
8218
|
+
const autoRefreshBtn = document.createElement("button");
|
|
8219
|
+
autoRefreshBtn.type = "button";
|
|
8220
|
+
autoRefreshBtn.className = "studio-pdf-card-action studio-pdf-card-auto-refresh";
|
|
8221
|
+
autoRefreshBtn.textContent = "Auto-refresh";
|
|
8222
|
+
autoRefreshBtn.title = "Watch this local PDF and reload it after a changed file is stable on disk.";
|
|
8223
|
+
autoRefreshBtn.setAttribute("aria-label", "Enable PDF auto-refresh");
|
|
8224
|
+
autoRefreshBtn.setAttribute("aria-pressed", "false");
|
|
8225
|
+
autoRefreshBtn.addEventListener("click", (event) => {
|
|
8226
|
+
event.preventDefault();
|
|
8227
|
+
event.stopPropagation();
|
|
8228
|
+
const state = ensureStudioPdfCardAutoRefreshState(card, resourceQuery);
|
|
8229
|
+
if (!state) {
|
|
8230
|
+
setStatus("Could not resolve this PDF for auto-refresh.", "warning");
|
|
8231
|
+
return;
|
|
8232
|
+
}
|
|
8233
|
+
setStudioPdfCardAutoRefresh(card, !state.enabled);
|
|
8234
|
+
});
|
|
8235
|
+
actions.appendChild(autoRefreshBtn);
|
|
8236
|
+
|
|
7789
8237
|
header.appendChild(actions);
|
|
7790
8238
|
}
|
|
7791
8239
|
card.appendChild(header);
|
|
@@ -7812,6 +8260,12 @@
|
|
|
7812
8260
|
iframe.loading = "lazy";
|
|
7813
8261
|
iframe.style.height = height + "px";
|
|
7814
8262
|
card.appendChild(iframe);
|
|
8263
|
+
const autoRefreshState = ensureStudioPdfCardAutoRefreshState(card, resourceQuery);
|
|
8264
|
+
if (autoRefreshRequested) {
|
|
8265
|
+
setStudioPdfAutoRefreshEnabled(autoRefreshState, true, { silent: true });
|
|
8266
|
+
} else {
|
|
8267
|
+
syncStudioPdfCardAutoRefreshButton(card);
|
|
8268
|
+
}
|
|
7815
8269
|
return card;
|
|
7816
8270
|
}
|
|
7817
8271
|
|
|
@@ -21926,9 +22380,29 @@
|
|
|
21926
22380
|
});
|
|
21927
22381
|
}
|
|
21928
22382
|
|
|
22383
|
+
if (hideStudioHeaderBtn) {
|
|
22384
|
+
hideStudioHeaderBtn.addEventListener("click", () => {
|
|
22385
|
+
setStudioHeaderHidden(true, { focusReveal: true });
|
|
22386
|
+
setStatus("Studio header hidden. Move to the top-right edge to restore it.");
|
|
22387
|
+
});
|
|
22388
|
+
}
|
|
22389
|
+
|
|
22390
|
+
if (studioHeaderRevealEl) {
|
|
22391
|
+
studioHeaderRevealEl.addEventListener("click", (event) => {
|
|
22392
|
+
if (event.target === studioHeaderRevealEl) restoreStudioHeaderFromReveal();
|
|
22393
|
+
});
|
|
22394
|
+
}
|
|
22395
|
+
|
|
22396
|
+
if (studioHeaderRevealBtn) {
|
|
22397
|
+
studioHeaderRevealBtn.addEventListener("click", restoreStudioHeaderFromReveal);
|
|
22398
|
+
}
|
|
22399
|
+
|
|
21929
22400
|
if (zenModeBtn) {
|
|
21930
22401
|
zenModeBtn.addEventListener("click", () => {
|
|
21931
|
-
setStudioZenMode(!studioZenModeEnabled);
|
|
22402
|
+
setStudioZenMode(!studioZenModeEnabled, { focusReveal: !studioZenModeEnabled });
|
|
22403
|
+
setStatus(studioZenModeEnabled
|
|
22404
|
+
? "Zen mode on. Studio header hidden; press F9 to exit."
|
|
22405
|
+
: "Zen mode off. Studio header restored.");
|
|
21932
22406
|
});
|
|
21933
22407
|
}
|
|
21934
22408
|
|