pi-studio 0.9.46 → 0.9.48
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 +15 -0
- package/README.md +3 -1
- package/client/studio-client.js +512 -75
- package/client/studio.css +116 -17
- package/index.ts +220 -37
- package/package.json +1 -1
- package/shared/studio-latex-pandoc-compat.js +122 -0
- package/shared/studio-pandoc-resource-flag.js +34 -0
package/client/studio-client.js
CHANGED
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
const showMeResponseBtn = document.getElementById("showMeResponseBtn");
|
|
86
86
|
const quizBtn = document.getElementById("quizBtn");
|
|
87
87
|
const lensSelect = document.getElementById("lensSelect");
|
|
88
|
+
const importFileBtn = document.getElementById("importFileBtn");
|
|
88
89
|
const fileInput = document.getElementById("fileInput");
|
|
89
90
|
const resourceDirBtn = document.getElementById("resourceDirBtn");
|
|
90
91
|
const resourceDirLabel = document.getElementById("resourceDirLabel");
|
|
@@ -268,10 +269,13 @@
|
|
|
268
269
|
let studioPdfFocusFrameEl = null;
|
|
269
270
|
let studioPdfFocusTitleEl = null;
|
|
270
271
|
let studioPdfFocusOpenLinkEl = null;
|
|
272
|
+
let studioPdfFocusSystemViewerBtn = null;
|
|
273
|
+
let studioPdfFocusRevealBtn = null;
|
|
271
274
|
let studioPdfFocusFullscreenBtn = null;
|
|
272
275
|
let studioPdfFocusCloseBtn = null;
|
|
273
276
|
let studioPdfFocusLastFocusedEl = null;
|
|
274
277
|
let studioPdfFocusMovedFrameState = null;
|
|
278
|
+
let studioPdfFocusResourceQuery = null;
|
|
275
279
|
let studioHtmlFocusOverlayEl = null;
|
|
276
280
|
let studioHtmlFocusShellEl = null;
|
|
277
281
|
let studioHtmlFocusFullscreenBtn = null;
|
|
@@ -289,6 +293,16 @@
|
|
|
289
293
|
let studioImageFocusLastFocusedEl = null;
|
|
290
294
|
let studioImageFocusZoomMode = "fit";
|
|
291
295
|
let studioImageFocusZoom = 1;
|
|
296
|
+
let studioDecisionOverlayEl = null;
|
|
297
|
+
let studioDecisionDialogEl = null;
|
|
298
|
+
let studioDecisionTitleEl = null;
|
|
299
|
+
let studioDecisionMessageEl = null;
|
|
300
|
+
let studioDecisionInputEl = null;
|
|
301
|
+
let studioDecisionCancelBtn = null;
|
|
302
|
+
let studioDecisionSecondaryBtn = null;
|
|
303
|
+
let studioDecisionConfirmBtn = null;
|
|
304
|
+
let studioDecisionState = null;
|
|
305
|
+
let studioImportDecisionOpen = false;
|
|
292
306
|
let pendingRequestId = null;
|
|
293
307
|
let pendingKind = null;
|
|
294
308
|
let stickyStudioKind = null;
|
|
@@ -2595,7 +2609,7 @@
|
|
|
2595
2609
|
event.preventDefault();
|
|
2596
2610
|
event.stopPropagation();
|
|
2597
2611
|
closeStudioUiRefreshMenus();
|
|
2598
|
-
resetEditorOrigin();
|
|
2612
|
+
void resetEditorOrigin();
|
|
2599
2613
|
});
|
|
2600
2614
|
sourceOpenCurrentFileTabBtn = makeStudioUiRefreshElement("button", "source-open-file-tab-btn", "Open current file in new editor tab");
|
|
2601
2615
|
sourceOpenCurrentFileTabBtn.type = "button";
|
|
@@ -3625,6 +3639,207 @@
|
|
|
3625
3639
|
});
|
|
3626
3640
|
}
|
|
3627
3641
|
|
|
3642
|
+
function finishStudioDecision(value, restoreFocus) {
|
|
3643
|
+
const state = studioDecisionState;
|
|
3644
|
+
if (!state) return;
|
|
3645
|
+
studioDecisionState = null;
|
|
3646
|
+
if (studioDecisionOverlayEl) studioDecisionOverlayEl.hidden = true;
|
|
3647
|
+
if (document.body) document.body.classList.remove("studio-decision-open");
|
|
3648
|
+
const returnFocusEl = state.returnFocusEl;
|
|
3649
|
+
state.resolve(value);
|
|
3650
|
+
if (restoreFocus !== false && returnFocusEl && typeof returnFocusEl.focus === "function") {
|
|
3651
|
+
window.setTimeout(() => {
|
|
3652
|
+
if (returnFocusEl.isConnected) returnFocusEl.focus({ preventScroll: true });
|
|
3653
|
+
}, 0);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
function getStudioDecisionFocusableElements() {
|
|
3658
|
+
return [studioDecisionInputEl, studioDecisionCancelBtn, studioDecisionSecondaryBtn, studioDecisionConfirmBtn]
|
|
3659
|
+
.filter((element) => element && !element.hidden && !element.disabled);
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3662
|
+
function ensureStudioDecisionDialog() {
|
|
3663
|
+
if (studioDecisionOverlayEl) return studioDecisionOverlayEl;
|
|
3664
|
+
|
|
3665
|
+
const overlay = document.createElement("div");
|
|
3666
|
+
overlay.className = "studio-decision-overlay";
|
|
3667
|
+
overlay.hidden = true;
|
|
3668
|
+
|
|
3669
|
+
const dialog = document.createElement("div");
|
|
3670
|
+
dialog.className = "studio-decision-dialog";
|
|
3671
|
+
dialog.setAttribute("role", "alertdialog");
|
|
3672
|
+
dialog.setAttribute("aria-modal", "true");
|
|
3673
|
+
dialog.setAttribute("aria-labelledby", "studioDecisionTitle");
|
|
3674
|
+
dialog.setAttribute("aria-describedby", "studioDecisionMessage");
|
|
3675
|
+
|
|
3676
|
+
const title = document.createElement("h2");
|
|
3677
|
+
title.id = "studioDecisionTitle";
|
|
3678
|
+
title.className = "studio-decision-title";
|
|
3679
|
+
dialog.appendChild(title);
|
|
3680
|
+
|
|
3681
|
+
const message = document.createElement("div");
|
|
3682
|
+
message.id = "studioDecisionMessage";
|
|
3683
|
+
message.className = "studio-decision-message";
|
|
3684
|
+
dialog.appendChild(message);
|
|
3685
|
+
|
|
3686
|
+
const input = document.createElement("input");
|
|
3687
|
+
input.type = "text";
|
|
3688
|
+
input.className = "studio-decision-input";
|
|
3689
|
+
input.hidden = true;
|
|
3690
|
+
input.autocomplete = "off";
|
|
3691
|
+
input.spellcheck = false;
|
|
3692
|
+
input.setAttribute("autocapitalize", "off");
|
|
3693
|
+
input.setAttribute("aria-label", "Value");
|
|
3694
|
+
dialog.appendChild(input);
|
|
3695
|
+
|
|
3696
|
+
const actions = document.createElement("div");
|
|
3697
|
+
actions.className = "studio-decision-actions";
|
|
3698
|
+
|
|
3699
|
+
const cancelBtn = document.createElement("button");
|
|
3700
|
+
cancelBtn.type = "button";
|
|
3701
|
+
cancelBtn.className = "studio-decision-cancel";
|
|
3702
|
+
cancelBtn.textContent = "Cancel";
|
|
3703
|
+
cancelBtn.addEventListener("click", () => finishStudioDecision(null));
|
|
3704
|
+
actions.appendChild(cancelBtn);
|
|
3705
|
+
|
|
3706
|
+
const secondaryBtn = document.createElement("button");
|
|
3707
|
+
secondaryBtn.type = "button";
|
|
3708
|
+
secondaryBtn.className = "studio-decision-secondary";
|
|
3709
|
+
secondaryBtn.hidden = true;
|
|
3710
|
+
secondaryBtn.addEventListener("click", () => {
|
|
3711
|
+
const handler = studioDecisionState && studioDecisionState.onSecondary;
|
|
3712
|
+
if (typeof handler !== "function") return;
|
|
3713
|
+
try {
|
|
3714
|
+
const result = handler();
|
|
3715
|
+
if (result && typeof result.catch === "function") {
|
|
3716
|
+
result.catch((error) => {
|
|
3717
|
+
setStatus("Action failed: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
} catch (error) {
|
|
3721
|
+
setStatus("Action failed: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
3722
|
+
}
|
|
3723
|
+
});
|
|
3724
|
+
actions.appendChild(secondaryBtn);
|
|
3725
|
+
|
|
3726
|
+
const confirmBtn = document.createElement("button");
|
|
3727
|
+
confirmBtn.type = "button";
|
|
3728
|
+
confirmBtn.className = "studio-decision-confirm";
|
|
3729
|
+
confirmBtn.textContent = "Confirm";
|
|
3730
|
+
confirmBtn.addEventListener("click", () => {
|
|
3731
|
+
if (!studioDecisionState) return;
|
|
3732
|
+
finishStudioDecision(studioDecisionState.mode === "prompt" ? input.value : true);
|
|
3733
|
+
});
|
|
3734
|
+
actions.appendChild(confirmBtn);
|
|
3735
|
+
|
|
3736
|
+
dialog.appendChild(actions);
|
|
3737
|
+
overlay.appendChild(dialog);
|
|
3738
|
+
document.body.appendChild(overlay);
|
|
3739
|
+
|
|
3740
|
+
overlay.addEventListener("click", (event) => {
|
|
3741
|
+
if (event.target === overlay) finishStudioDecision(null);
|
|
3742
|
+
});
|
|
3743
|
+
dialog.addEventListener("keydown", (event) => {
|
|
3744
|
+
event.stopPropagation();
|
|
3745
|
+
if (event.key === "Escape") {
|
|
3746
|
+
event.preventDefault();
|
|
3747
|
+
finishStudioDecision(null);
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3750
|
+
if (event.key === "Enter" && event.target === input) {
|
|
3751
|
+
event.preventDefault();
|
|
3752
|
+
confirmBtn.click();
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
if (event.key !== "Tab") return;
|
|
3756
|
+
const focusable = getStudioDecisionFocusableElements();
|
|
3757
|
+
if (focusable.length < 2) return;
|
|
3758
|
+
const currentIndex = focusable.indexOf(document.activeElement);
|
|
3759
|
+
const nextIndex = event.shiftKey
|
|
3760
|
+
? (currentIndex <= 0 ? focusable.length - 1 : currentIndex - 1)
|
|
3761
|
+
: (currentIndex < 0 || currentIndex === focusable.length - 1 ? 0 : currentIndex + 1);
|
|
3762
|
+
event.preventDefault();
|
|
3763
|
+
focusable[nextIndex].focus();
|
|
3764
|
+
});
|
|
3765
|
+
|
|
3766
|
+
studioDecisionOverlayEl = overlay;
|
|
3767
|
+
studioDecisionDialogEl = dialog;
|
|
3768
|
+
studioDecisionTitleEl = title;
|
|
3769
|
+
studioDecisionMessageEl = message;
|
|
3770
|
+
studioDecisionInputEl = input;
|
|
3771
|
+
studioDecisionCancelBtn = cancelBtn;
|
|
3772
|
+
studioDecisionSecondaryBtn = secondaryBtn;
|
|
3773
|
+
studioDecisionConfirmBtn = confirmBtn;
|
|
3774
|
+
return overlay;
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
function openStudioDecision(options) {
|
|
3778
|
+
const settings = options && typeof options === "object" ? options : {};
|
|
3779
|
+
const mode = settings.mode === "prompt" ? "prompt" : "confirm";
|
|
3780
|
+
ensureStudioDecisionDialog();
|
|
3781
|
+
if (studioDecisionState) finishStudioDecision(null, false);
|
|
3782
|
+
const returnFocusEl = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
3783
|
+
|
|
3784
|
+
const secondaryLabel = String(settings.secondaryLabel || "").trim();
|
|
3785
|
+
studioDecisionTitleEl.textContent = String(settings.title || (mode === "prompt" ? "Enter a value" : "Confirm action"));
|
|
3786
|
+
studioDecisionMessageEl.textContent = String(settings.message || "");
|
|
3787
|
+
studioDecisionInputEl.hidden = mode !== "prompt";
|
|
3788
|
+
studioDecisionInputEl.value = mode === "prompt" ? String(settings.defaultValue || "") : "";
|
|
3789
|
+
studioDecisionInputEl.placeholder = mode === "prompt" ? String(settings.placeholder || "") : "";
|
|
3790
|
+
studioDecisionInputEl.setAttribute("aria-label", String(settings.inputLabel || "Value"));
|
|
3791
|
+
studioDecisionCancelBtn.textContent = String(settings.cancelLabel || "Cancel");
|
|
3792
|
+
studioDecisionSecondaryBtn.hidden = !secondaryLabel;
|
|
3793
|
+
studioDecisionSecondaryBtn.disabled = settings.secondaryDisabled === true;
|
|
3794
|
+
studioDecisionSecondaryBtn.textContent = secondaryLabel;
|
|
3795
|
+
studioDecisionConfirmBtn.textContent = String(settings.confirmLabel || (mode === "prompt" ? "Continue" : "Confirm"));
|
|
3796
|
+
studioDecisionConfirmBtn.classList.toggle("is-destructive", settings.destructive === true);
|
|
3797
|
+
studioDecisionDialogEl.classList.toggle("is-destructive", settings.destructive === true);
|
|
3798
|
+
studioDecisionOverlayEl.hidden = false;
|
|
3799
|
+
if (document.body) document.body.classList.add("studio-decision-open");
|
|
3800
|
+
|
|
3801
|
+
return new Promise((resolve) => {
|
|
3802
|
+
const decisionState = {
|
|
3803
|
+
mode,
|
|
3804
|
+
resolve,
|
|
3805
|
+
returnFocusEl,
|
|
3806
|
+
onSecondary: typeof settings.onSecondary === "function" ? settings.onSecondary : null,
|
|
3807
|
+
};
|
|
3808
|
+
studioDecisionState = decisionState;
|
|
3809
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
3810
|
+
? window.requestAnimationFrame.bind(window)
|
|
3811
|
+
: (callback) => window.setTimeout(callback, 16);
|
|
3812
|
+
schedule(() => {
|
|
3813
|
+
if (studioDecisionState !== decisionState) return;
|
|
3814
|
+
if (mode === "prompt") {
|
|
3815
|
+
studioDecisionInputEl.focus();
|
|
3816
|
+
studioDecisionInputEl.select();
|
|
3817
|
+
} else {
|
|
3818
|
+
studioDecisionCancelBtn.focus();
|
|
3819
|
+
}
|
|
3820
|
+
});
|
|
3821
|
+
});
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
async function requestStudioConfirmation(message, options) {
|
|
3825
|
+
const value = await openStudioDecision({
|
|
3826
|
+
...(options && typeof options === "object" ? options : {}),
|
|
3827
|
+
mode: "confirm",
|
|
3828
|
+
message: String(message || ""),
|
|
3829
|
+
});
|
|
3830
|
+
return value === true;
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
async function requestStudioTextInput(message, defaultValue, options) {
|
|
3834
|
+
const value = await openStudioDecision({
|
|
3835
|
+
...(options && typeof options === "object" ? options : {}),
|
|
3836
|
+
mode: "prompt",
|
|
3837
|
+
message: String(message || ""),
|
|
3838
|
+
defaultValue: String(defaultValue || ""),
|
|
3839
|
+
});
|
|
3840
|
+
return typeof value === "string" ? value : null;
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3628
3843
|
renderStatus();
|
|
3629
3844
|
|
|
3630
3845
|
window.addEventListener("focus", () => {
|
|
@@ -3714,14 +3929,17 @@
|
|
|
3714
3929
|
}
|
|
3715
3930
|
}
|
|
3716
3931
|
|
|
3717
|
-
function resetEditorOrigin() {
|
|
3932
|
+
async function resetEditorOrigin() {
|
|
3718
3933
|
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
3719
3934
|
const message = descriptor.fileBacked
|
|
3720
3935
|
? ("Reset editor origin and detach the current text from\n\n" + descriptor.label + "\n\ninto a new draft? The file on disk will not be changed, and the current scratchpad/review notes will carry into the new draft.")
|
|
3721
3936
|
: ("Reset editor origin and start a new independent draft? The current editor text, scratchpad, and review notes will carry into the new draft.");
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3937
|
+
const confirmed = await requestStudioConfirmation(message, {
|
|
3938
|
+
title: descriptor.fileBacked ? "Detach editor from file?" : "Reset editor origin?",
|
|
3939
|
+
confirmLabel: descriptor.fileBacked ? "Detach" : "Reset origin",
|
|
3940
|
+
destructive: true,
|
|
3941
|
+
});
|
|
3942
|
+
if (!confirmed) return;
|
|
3725
3943
|
const nextLabel = String(sourceTextEl.value || "").trim() ? "draft" : "blank";
|
|
3726
3944
|
setSourceState({
|
|
3727
3945
|
source: "blank",
|
|
@@ -6723,9 +6941,30 @@
|
|
|
6723
6941
|
openLink.className = "studio-pdf-focus-link";
|
|
6724
6942
|
openLink.target = "_blank";
|
|
6725
6943
|
openLink.rel = "noopener noreferrer";
|
|
6726
|
-
openLink.textContent = "
|
|
6944
|
+
openLink.textContent = "Browser tab";
|
|
6945
|
+
openLink.title = "Open this PDF in a browser tab.";
|
|
6727
6946
|
actions.appendChild(openLink);
|
|
6728
6947
|
|
|
6948
|
+
const systemViewerBtn = document.createElement("button");
|
|
6949
|
+
systemViewerBtn.type = "button";
|
|
6950
|
+
systemViewerBtn.className = "studio-pdf-focus-btn studio-pdf-focus-system-viewer";
|
|
6951
|
+
systemViewerBtn.textContent = "System viewer";
|
|
6952
|
+
systemViewerBtn.title = "Open the local PDF in the operating system's default PDF viewer.";
|
|
6953
|
+
systemViewerBtn.addEventListener("click", () => {
|
|
6954
|
+
void runStudioPdfLocalAction("system-viewer", studioPdfFocusResourceQuery);
|
|
6955
|
+
});
|
|
6956
|
+
actions.appendChild(systemViewerBtn);
|
|
6957
|
+
|
|
6958
|
+
const revealBtn = document.createElement("button");
|
|
6959
|
+
revealBtn.type = "button";
|
|
6960
|
+
revealBtn.className = "studio-pdf-focus-btn studio-pdf-focus-reveal";
|
|
6961
|
+
revealBtn.textContent = "Show in folder";
|
|
6962
|
+
revealBtn.title = "Reveal the local PDF in Finder or the system file manager.";
|
|
6963
|
+
revealBtn.addEventListener("click", () => {
|
|
6964
|
+
void runStudioPdfLocalAction("reveal", studioPdfFocusResourceQuery);
|
|
6965
|
+
});
|
|
6966
|
+
actions.appendChild(revealBtn);
|
|
6967
|
+
|
|
6729
6968
|
const refreshBtn = document.createElement("button");
|
|
6730
6969
|
refreshBtn.type = "button";
|
|
6731
6970
|
refreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-refresh";
|
|
@@ -6789,16 +7028,21 @@
|
|
|
6789
7028
|
studioPdfFocusFrameEl = frame;
|
|
6790
7029
|
studioPdfFocusTitleEl = titleEl;
|
|
6791
7030
|
studioPdfFocusOpenLinkEl = openLink;
|
|
7031
|
+
studioPdfFocusSystemViewerBtn = systemViewerBtn;
|
|
7032
|
+
studioPdfFocusRevealBtn = revealBtn;
|
|
6792
7033
|
studioPdfFocusFullscreenBtn = fullscreenBtn;
|
|
6793
7034
|
studioPdfFocusCloseBtn = closeBtn;
|
|
6794
7035
|
syncStudioPdfFocusFullscreenButton();
|
|
7036
|
+
syncStudioPdfFocusResourceActions();
|
|
6795
7037
|
return overlay;
|
|
6796
7038
|
}
|
|
6797
7039
|
|
|
6798
|
-
function openStudioPdfFocusViewer(viewerUrl, title, sourceFrame) {
|
|
7040
|
+
function openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery) {
|
|
6799
7041
|
const src = String(viewerUrl || "").trim();
|
|
6800
7042
|
if (!src) return;
|
|
6801
7043
|
ensureStudioPdfFocusViewer();
|
|
7044
|
+
studioPdfFocusResourceQuery = normalizeStudioPdfResourceQuery(resourceQuery);
|
|
7045
|
+
syncStudioPdfFocusResourceActions();
|
|
6802
7046
|
studioPdfFocusLastFocusedEl = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
6803
7047
|
if (studioPdfFocusTitleEl) studioPdfFocusTitleEl.textContent = String(title || "PDF preview").trim() || "PDF preview";
|
|
6804
7048
|
if (studioPdfFocusOpenLinkEl) studioPdfFocusOpenLinkEl.href = src;
|
|
@@ -6827,6 +7071,8 @@
|
|
|
6827
7071
|
restoreStudioPdfFocusMovedFrame();
|
|
6828
7072
|
if (studioPdfFocusFrameEl) studioPdfFocusFrameEl.src = "about:blank";
|
|
6829
7073
|
if (document.body) document.body.classList.remove("studio-pdf-focus-open");
|
|
7074
|
+
studioPdfFocusResourceQuery = null;
|
|
7075
|
+
syncStudioPdfFocusResourceActions();
|
|
6830
7076
|
syncStudioPdfFocusFullscreenButton();
|
|
6831
7077
|
const focusTarget = studioPdfFocusLastFocusedEl;
|
|
6832
7078
|
studioPdfFocusLastFocusedEl = null;
|
|
@@ -6836,24 +7082,66 @@
|
|
|
6836
7082
|
return true;
|
|
6837
7083
|
}
|
|
6838
7084
|
|
|
6839
|
-
function
|
|
6840
|
-
const token = getToken();
|
|
6841
|
-
if (!token) return "";
|
|
7085
|
+
function buildStudioPdfResourceQuery(options, useEditorResourceContext) {
|
|
6842
7086
|
const pdfPath = String(options && options.path ? options.path : "").trim();
|
|
6843
|
-
if (!pdfPath) return
|
|
7087
|
+
if (!pdfPath) return null;
|
|
6844
7088
|
const explicitSourcePath = options && typeof options.sourcePath === "string" ? options.sourcePath.trim() : "";
|
|
6845
7089
|
const explicitResourceDir = options && typeof options.resourceDir === "string" ? normalizeStudioResourceDirValue(options.resourceDir) : "";
|
|
6846
7090
|
const effectivePath = getEffectiveSavePath();
|
|
6847
7091
|
const sourcePath = explicitSourcePath || (useEditorResourceContext ? (effectivePath || sourceState.path || "") : "");
|
|
6848
7092
|
const resourceDir = explicitResourceDir || getCurrentResourceDirValue();
|
|
6849
|
-
const
|
|
6850
|
-
if (sourcePath)
|
|
6851
|
-
|
|
7093
|
+
const query = { path: pdfPath };
|
|
7094
|
+
if (sourcePath) query.sourcePath = sourcePath;
|
|
7095
|
+
if (resourceDir) query.resourceDir = resourceDir;
|
|
7096
|
+
return query;
|
|
7097
|
+
}
|
|
7098
|
+
|
|
7099
|
+
function buildStudioPdfResourceUrl(options, useEditorResourceContext) {
|
|
7100
|
+
const token = getToken();
|
|
7101
|
+
const query = buildStudioPdfResourceQuery(options, useEditorResourceContext);
|
|
7102
|
+
if (!token || !query) return "";
|
|
7103
|
+
return "/pdf-resource?" + new URLSearchParams({ token, ...query }).toString();
|
|
7104
|
+
}
|
|
7105
|
+
|
|
7106
|
+
function normalizeStudioPdfResourceQuery(value) {
|
|
7107
|
+
if (!value || typeof value !== "object") return null;
|
|
7108
|
+
const path = typeof value.path === "string" ? value.path.trim() : "";
|
|
7109
|
+
if (!path) return null;
|
|
7110
|
+
const query = { path };
|
|
7111
|
+
if (typeof value.sourcePath === "string" && value.sourcePath.trim()) query.sourcePath = value.sourcePath.trim();
|
|
7112
|
+
if (typeof value.resourceDir === "string" && value.resourceDir.trim()) query.resourceDir = value.resourceDir.trim();
|
|
7113
|
+
return query;
|
|
7114
|
+
}
|
|
7115
|
+
|
|
7116
|
+
async function runStudioPdfLocalAction(action, resourceQuery) {
|
|
7117
|
+
const query = normalizeStudioPdfResourceQuery(resourceQuery);
|
|
7118
|
+
if (!query) {
|
|
7119
|
+
setStatus("Could not resolve this PDF's local path.", "warning");
|
|
7120
|
+
return false;
|
|
6852
7121
|
}
|
|
6853
|
-
|
|
6854
|
-
|
|
7122
|
+
const openInSystemViewer = action === "system-viewer";
|
|
7123
|
+
const endpoint = openInSystemViewer ? "/open-local-resource" : "/reveal-local-resource";
|
|
7124
|
+
try {
|
|
7125
|
+
const payload = await fetchStudioJson(endpoint, {
|
|
7126
|
+
method: "POST",
|
|
7127
|
+
body: JSON.stringify(query),
|
|
7128
|
+
});
|
|
7129
|
+
setStatus(payload && payload.message
|
|
7130
|
+
? payload.message
|
|
7131
|
+
: (openInSystemViewer ? "Opened PDF in the system viewer." : "Revealed PDF in the file manager."), "success");
|
|
7132
|
+
return true;
|
|
7133
|
+
} catch (error) {
|
|
7134
|
+
setStatus((error && error.message)
|
|
7135
|
+
? error.message
|
|
7136
|
+
: (openInSystemViewer ? "Could not open PDF in the system viewer." : "Could not reveal PDF in the file manager."), "warning");
|
|
7137
|
+
return false;
|
|
6855
7138
|
}
|
|
6856
|
-
|
|
7139
|
+
}
|
|
7140
|
+
|
|
7141
|
+
function syncStudioPdfFocusResourceActions() {
|
|
7142
|
+
const available = Boolean(normalizeStudioPdfResourceQuery(studioPdfFocusResourceQuery));
|
|
7143
|
+
if (studioPdfFocusSystemViewerBtn) studioPdfFocusSystemViewerBtn.disabled = !available;
|
|
7144
|
+
if (studioPdfFocusRevealBtn) studioPdfFocusRevealBtn.disabled = !available;
|
|
6857
7145
|
}
|
|
6858
7146
|
|
|
6859
7147
|
function buildRefreshedStudioPdfViewerUrl(value) {
|
|
@@ -6989,8 +7277,15 @@
|
|
|
6989
7277
|
|| String(card && card.dataset ? (card.dataset.studioPdfTitle || "") : "").trim()
|
|
6990
7278
|
|| "PDF preview";
|
|
6991
7279
|
const sourceFrame = card && typeof card.querySelector === "function" ? card.querySelector("iframe.studio-pdf-frame") : null;
|
|
7280
|
+
const resourceQuery = card && card.dataset
|
|
7281
|
+
? normalizeStudioPdfResourceQuery({
|
|
7282
|
+
path: card.dataset.studioPdfPath || "",
|
|
7283
|
+
sourcePath: card.dataset.studioPdfSourcePath || "",
|
|
7284
|
+
resourceDir: card.dataset.studioPdfResourceDir || "",
|
|
7285
|
+
})
|
|
7286
|
+
: null;
|
|
6992
7287
|
if (!viewerUrl) return false;
|
|
6993
|
-
openStudioPdfFocusViewer(viewerUrl, title, sourceFrame);
|
|
7288
|
+
openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery);
|
|
6994
7289
|
return true;
|
|
6995
7290
|
}
|
|
6996
7291
|
|
|
@@ -7403,6 +7698,7 @@
|
|
|
7403
7698
|
const caption = String(options.caption || "").trim();
|
|
7404
7699
|
const height = normalizeStudioPdfHeight(options.height);
|
|
7405
7700
|
const page = normalizeStudioPdfPage(options.page);
|
|
7701
|
+
const resourceQuery = buildStudioPdfResourceQuery(options, useEditorResourceContext);
|
|
7406
7702
|
const resourceUrl = buildStudioPdfResourceUrl(options, useEditorResourceContext);
|
|
7407
7703
|
const viewerUrl = resourceUrl && page ? resourceUrl + "#page=" + encodeURIComponent(String(page)) : resourceUrl;
|
|
7408
7704
|
|
|
@@ -7411,6 +7707,9 @@
|
|
|
7411
7707
|
if (card.dataset) {
|
|
7412
7708
|
card.dataset.studioPdfViewerUrl = viewerUrl || "";
|
|
7413
7709
|
card.dataset.studioPdfTitle = title;
|
|
7710
|
+
card.dataset.studioPdfPath = resourceQuery && resourceQuery.path ? resourceQuery.path : "";
|
|
7711
|
+
card.dataset.studioPdfSourcePath = resourceQuery && resourceQuery.sourcePath ? resourceQuery.sourcePath : "";
|
|
7712
|
+
card.dataset.studioPdfResourceDir = resourceQuery && resourceQuery.resourceDir ? resourceQuery.resourceDir : "";
|
|
7414
7713
|
}
|
|
7415
7714
|
|
|
7416
7715
|
const header = document.createElement("figcaption");
|
|
@@ -7447,9 +7746,34 @@
|
|
|
7447
7746
|
openLink.href = viewerUrl;
|
|
7448
7747
|
openLink.target = "_blank";
|
|
7449
7748
|
openLink.rel = "noopener noreferrer";
|
|
7450
|
-
openLink.textContent = "
|
|
7749
|
+
openLink.textContent = "Browser tab";
|
|
7750
|
+
openLink.title = "Open this PDF in a browser tab.";
|
|
7451
7751
|
actions.appendChild(openLink);
|
|
7452
7752
|
|
|
7753
|
+
const systemViewerBtn = document.createElement("button");
|
|
7754
|
+
systemViewerBtn.type = "button";
|
|
7755
|
+
systemViewerBtn.className = "studio-pdf-card-action studio-pdf-card-system-viewer";
|
|
7756
|
+
systemViewerBtn.textContent = "System viewer";
|
|
7757
|
+
systemViewerBtn.title = "Open the local PDF in the operating system's default PDF viewer.";
|
|
7758
|
+
systemViewerBtn.addEventListener("click", (event) => {
|
|
7759
|
+
event.preventDefault();
|
|
7760
|
+
event.stopPropagation();
|
|
7761
|
+
void runStudioPdfLocalAction("system-viewer", resourceQuery);
|
|
7762
|
+
});
|
|
7763
|
+
actions.appendChild(systemViewerBtn);
|
|
7764
|
+
|
|
7765
|
+
const revealBtn = document.createElement("button");
|
|
7766
|
+
revealBtn.type = "button";
|
|
7767
|
+
revealBtn.className = "studio-pdf-card-action studio-pdf-card-reveal";
|
|
7768
|
+
revealBtn.textContent = "Show in folder";
|
|
7769
|
+
revealBtn.title = "Reveal the local PDF in Finder or the system file manager.";
|
|
7770
|
+
revealBtn.addEventListener("click", (event) => {
|
|
7771
|
+
event.preventDefault();
|
|
7772
|
+
event.stopPropagation();
|
|
7773
|
+
void runStudioPdfLocalAction("reveal", resourceQuery);
|
|
7774
|
+
});
|
|
7775
|
+
actions.appendChild(revealBtn);
|
|
7776
|
+
|
|
7453
7777
|
const refreshBtn = document.createElement("button");
|
|
7454
7778
|
refreshBtn.type = "button";
|
|
7455
7779
|
refreshBtn.className = "studio-pdf-card-action studio-pdf-card-refresh";
|
|
@@ -8777,7 +9101,13 @@
|
|
|
8777
9101
|
} else {
|
|
8778
9102
|
failPendingStudioTab(studioLaunch, "Studio did not return a preview-tab URL for this PDF export.");
|
|
8779
9103
|
const viewerUrl = getStudioPdfViewerUrlForExportPayload(payload);
|
|
8780
|
-
|
|
9104
|
+
const resourceQuery = exportPath
|
|
9105
|
+
? buildStudioPdfResourceQuery({
|
|
9106
|
+
path: exportPath,
|
|
9107
|
+
resourceDir: exportPath.split(/[\\/]/).slice(0, -1).join("/"),
|
|
9108
|
+
}, false)
|
|
9109
|
+
: null;
|
|
9110
|
+
if (viewerUrl) openStudioPdfFocusViewer(viewerUrl, downloadName, null, resourceQuery);
|
|
8781
9111
|
}
|
|
8782
9112
|
if (writeError) {
|
|
8783
9113
|
setStatus(openedStudio
|
|
@@ -11399,6 +11729,7 @@
|
|
|
11399
11729
|
const canRefreshFromDisk = hasRefreshableFilePath();
|
|
11400
11730
|
|
|
11401
11731
|
fileInput.disabled = uiBusy;
|
|
11732
|
+
if (importFileBtn) importFileBtn.disabled = uiBusy;
|
|
11402
11733
|
if (sourceBadgeEl) sourceBadgeEl.disabled = uiBusy;
|
|
11403
11734
|
if (sourceResetOriginBtn) sourceResetOriginBtn.disabled = uiBusy;
|
|
11404
11735
|
if (sourceOpenCurrentFileTabBtn) {
|
|
@@ -11714,12 +12045,15 @@
|
|
|
11714
12045
|
return true;
|
|
11715
12046
|
}
|
|
11716
12047
|
|
|
11717
|
-
function clearStudioWorkspace() {
|
|
12048
|
+
async function clearStudioWorkspace() {
|
|
11718
12049
|
if (uiBusy) {
|
|
11719
12050
|
setStatus("Studio is busy.", "warning");
|
|
11720
12051
|
return;
|
|
11721
12052
|
}
|
|
11722
|
-
const confirmed =
|
|
12053
|
+
const confirmed = await requestStudioConfirmation(
|
|
12054
|
+
"Reset the editor to a fresh blank draft in this browser tab? Saved files and responses are not changed.",
|
|
12055
|
+
{ title: "Reset editor?", confirmLabel: "Reset editor", destructive: true },
|
|
12056
|
+
);
|
|
11723
12057
|
if (!confirmed) return;
|
|
11724
12058
|
const preservedResponseState = {
|
|
11725
12059
|
responseHistory: Array.isArray(responseHistory) ? responseHistory.slice() : [],
|
|
@@ -12863,7 +13197,7 @@
|
|
|
12863
13197
|
if (!raw || raw.charAt(0) === "#") return false;
|
|
12864
13198
|
if (/^\/\//.test(raw)) return false;
|
|
12865
13199
|
if (/^(?:https?|mailto|tel|data|blob|javascript|about):/i.test(raw)) return false;
|
|
12866
|
-
if (/^\/(?:pdf-resource|html-preview-resource|export-pdf|export-html|render-preview|render-math|local-preview-link|reveal-local-resource)(?:[?#/]|$)/i.test(raw)) return false;
|
|
13200
|
+
if (/^\/(?:pdf-resource|html-preview-resource|export-pdf|export-html|render-preview|render-math|import-file-copy|local-preview-link|reveal-local-resource|open-local-resource)(?:[?#/]|$)/i.test(raw)) return false;
|
|
12867
13201
|
return true;
|
|
12868
13202
|
}
|
|
12869
13203
|
|
|
@@ -12949,6 +13283,7 @@
|
|
|
12949
13283
|
if (kind === "pdf") {
|
|
12950
13284
|
appendPreviewLinkMenuButton(menu, "Open PDF preview", "open-pdf");
|
|
12951
13285
|
appendPreviewLinkMenuButton(menu, "Open in new Studio tab", "open-preview-new");
|
|
13286
|
+
appendPreviewLinkMenuButton(menu, "Open in system viewer", "open-system");
|
|
12952
13287
|
} else if (kind === "text") {
|
|
12953
13288
|
appendPreviewLinkMenuButton(menu, "Open file tab", "open-new");
|
|
12954
13289
|
appendPreviewLinkMenuButton(menu, "Open here", "open-here");
|
|
@@ -12989,7 +13324,14 @@
|
|
|
12989
13324
|
setStatus("Could not resolve this PDF link. Open the source file or set a working directory first.", "warning");
|
|
12990
13325
|
return false;
|
|
12991
13326
|
}
|
|
12992
|
-
|
|
13327
|
+
const cleanPath = stripPreviewLocalLinkUrlSuffix(href);
|
|
13328
|
+
const context = contextOverride && typeof contextOverride === "object" ? contextOverride : {};
|
|
13329
|
+
const resourceQuery = buildStudioPdfResourceQuery({
|
|
13330
|
+
path: cleanPath,
|
|
13331
|
+
sourcePath: context.sourcePath || "",
|
|
13332
|
+
resourceDir: context.resourceDir || "",
|
|
13333
|
+
}, true);
|
|
13334
|
+
openStudioPdfFocusViewer(viewerUrl, title || href, null, resourceQuery);
|
|
12993
13335
|
return true;
|
|
12994
13336
|
}
|
|
12995
13337
|
|
|
@@ -13022,16 +13364,17 @@
|
|
|
13022
13364
|
}
|
|
13023
13365
|
}
|
|
13024
13366
|
|
|
13025
|
-
function confirmPreviewOfficeConversion(href, destination) {
|
|
13367
|
+
async function confirmPreviewOfficeConversion(href, destination) {
|
|
13026
13368
|
if (getPreviewLocalLinkKind(href) !== "office") return true;
|
|
13027
13369
|
const label = getPreviewOfficeConversionLabel(href);
|
|
13028
13370
|
const target = destination === "here"
|
|
13029
13371
|
? "replace the current editor contents with an editable Markdown copy"
|
|
13030
13372
|
: "open an editable Markdown copy in a new Studio tab";
|
|
13031
|
-
const confirmed =
|
|
13373
|
+
const confirmed = await requestStudioConfirmation(
|
|
13032
13374
|
"Convert " + label + " to Markdown?\n\n"
|
|
13033
13375
|
+ "Studio will use Pandoc to " + target + ". Some layout or formatting may change. "
|
|
13034
|
-
+ "The original DOCX/ODT file will not be overwritten, and edits will not round-trip back to it."
|
|
13376
|
+
+ "The original DOCX/ODT file will not be overwritten, and edits will not round-trip back to it.",
|
|
13377
|
+
{ title: "Convert document?", confirmLabel: "Convert" },
|
|
13035
13378
|
);
|
|
13036
13379
|
if (!confirmed) setStatus("Document conversion cancelled.", "warning");
|
|
13037
13380
|
return confirmed;
|
|
@@ -13043,13 +13386,17 @@
|
|
|
13043
13386
|
}
|
|
13044
13387
|
|
|
13045
13388
|
async function openPreviewDocumentHere(href, contextOverride, options) {
|
|
13046
|
-
if (!confirmPreviewOfficeConversion(href, "here")) return;
|
|
13389
|
+
if (!(await confirmPreviewOfficeConversion(href, "here"))) return;
|
|
13047
13390
|
if (editorHasPotentialUnsavedContent()) {
|
|
13048
13391
|
const kind = getPreviewLocalLinkKind(href);
|
|
13049
13392
|
const prompt = kind === "office"
|
|
13050
13393
|
? "Replace the current editor contents with this converted Markdown copy? Unsaved editor changes may be lost."
|
|
13051
13394
|
: "Open this file-backed document in the current editor?\n\nThis will replace the current editor contents and attach the editor to the file on disk, so Save editor and Refresh from disk use that file. Unsaved editor changes may be lost.";
|
|
13052
|
-
const confirmed =
|
|
13395
|
+
const confirmed = await requestStudioConfirmation(prompt, {
|
|
13396
|
+
title: "Replace editor contents?",
|
|
13397
|
+
confirmLabel: "Replace",
|
|
13398
|
+
destructive: true,
|
|
13399
|
+
});
|
|
13053
13400
|
if (!confirmed) return;
|
|
13054
13401
|
}
|
|
13055
13402
|
const payload = await fetchPreviewLocalLink("document", href, contextOverride);
|
|
@@ -13080,7 +13427,7 @@
|
|
|
13080
13427
|
}
|
|
13081
13428
|
|
|
13082
13429
|
async function openPreviewDocumentInNewEditor(href, contextOverride) {
|
|
13083
|
-
if (!confirmPreviewOfficeConversion(href, "new")) return;
|
|
13430
|
+
if (!(await confirmPreviewOfficeConversion(href, "new"))) return;
|
|
13084
13431
|
let launch = null;
|
|
13085
13432
|
try {
|
|
13086
13433
|
launch = openPendingStudioTab("document");
|
|
@@ -13136,6 +13483,10 @@
|
|
|
13136
13483
|
openPreviewPdfLink(href, context.title || href, context);
|
|
13137
13484
|
return;
|
|
13138
13485
|
}
|
|
13486
|
+
if (action === "open-system") {
|
|
13487
|
+
await runStudioPdfLocalAction("system-viewer", getPreviewLinkResourceQuery(href, context));
|
|
13488
|
+
return;
|
|
13489
|
+
}
|
|
13139
13490
|
if (action === "open-new") {
|
|
13140
13491
|
await openPreviewDocumentInNewEditor(href, context);
|
|
13141
13492
|
return;
|
|
@@ -14824,7 +15175,10 @@
|
|
|
14824
15175
|
return;
|
|
14825
15176
|
}
|
|
14826
15177
|
if (String(scratchpadText || "").trim() && String(scratchpadText || "") !== String(text || "")) {
|
|
14827
|
-
const confirmed =
|
|
15178
|
+
const confirmed = await requestStudioConfirmation(
|
|
15179
|
+
"Replace the current scratchpad with this recent scratchpad? Current scratchpad text will remain saved under its current document/draft identity, but this panel will show the loaded text for the current document.",
|
|
15180
|
+
{ title: "Replace scratchpad?", confirmLabel: "Replace", destructive: true },
|
|
15181
|
+
);
|
|
14828
15182
|
if (!confirmed) return;
|
|
14829
15183
|
}
|
|
14830
15184
|
setScratchpadText(text);
|
|
@@ -18646,7 +19000,7 @@
|
|
|
18646
19000
|
deleteBtn.textContent = "Delete";
|
|
18647
19001
|
deleteBtn.title = "Delete this local comment.";
|
|
18648
19002
|
deleteBtn.addEventListener("click", () => {
|
|
18649
|
-
deleteReviewNote(note.id);
|
|
19003
|
+
void deleteReviewNote(note.id);
|
|
18650
19004
|
});
|
|
18651
19005
|
actions.appendChild(deleteBtn);
|
|
18652
19006
|
|
|
@@ -19010,24 +19364,29 @@
|
|
|
19010
19364
|
});
|
|
19011
19365
|
}
|
|
19012
19366
|
|
|
19013
|
-
function deleteReviewNote(noteId) {
|
|
19367
|
+
async function deleteReviewNote(noteId) {
|
|
19014
19368
|
const note = reviewNotes.find((entry) => entry && entry.id === noteId);
|
|
19015
19369
|
if (!note) return;
|
|
19016
|
-
const confirmed =
|
|
19370
|
+
const confirmed = await requestStudioConfirmation("Delete this local comment?", {
|
|
19371
|
+
title: "Delete comment?",
|
|
19372
|
+
confirmLabel: "Delete",
|
|
19373
|
+
destructive: true,
|
|
19374
|
+
});
|
|
19017
19375
|
if (!confirmed) return;
|
|
19018
19376
|
setReviewNotes(reviewNotes.filter((entry) => entry && entry.id !== noteId));
|
|
19019
19377
|
setStatus("Deleted local comment.", "success");
|
|
19020
19378
|
}
|
|
19021
19379
|
|
|
19022
|
-
function deleteAllReviewNotes() {
|
|
19380
|
+
async function deleteAllReviewNotes() {
|
|
19023
19381
|
if (!reviewNotes.length) {
|
|
19024
19382
|
setStatus("No local comments to delete.", "warning");
|
|
19025
19383
|
return;
|
|
19026
19384
|
}
|
|
19027
19385
|
const count = reviewNotes.length;
|
|
19028
|
-
const confirmed =
|
|
19386
|
+
const confirmed = await requestStudioConfirmation(
|
|
19029
19387
|
"Delete all " + count + " local comment" + (count === 1 ? "" : "s") + " for this document?\n\n"
|
|
19030
19388
|
+ "Existing inline [an: ...] annotations in the editor text will not be removed.",
|
|
19389
|
+
{ title: "Delete all comments?", confirmLabel: "Delete all", destructive: true },
|
|
19031
19390
|
);
|
|
19032
19391
|
if (!confirmed) return;
|
|
19033
19392
|
setReviewNotes([]);
|
|
@@ -21238,7 +21597,7 @@
|
|
|
21238
21597
|
});
|
|
21239
21598
|
}
|
|
21240
21599
|
|
|
21241
|
-
function loadSelectedResponseIntoEditor(options) {
|
|
21600
|
+
async function loadSelectedResponseIntoEditor(options) {
|
|
21242
21601
|
if (!latestResponseMarkdown.trim()) {
|
|
21243
21602
|
setStatus("No response available yet.", "warning");
|
|
21244
21603
|
return false;
|
|
@@ -21249,12 +21608,15 @@
|
|
|
21249
21608
|
&& sourceState.source === "last-response"
|
|
21250
21609
|
&& Boolean(currentEditorText.trim())
|
|
21251
21610
|
&& normalizeForCompare(currentEditorText) !== latestResponseNormalized;
|
|
21252
|
-
if (
|
|
21253
|
-
|
|
21254
|
-
|
|
21255
|
-
|
|
21256
|
-
|
|
21257
|
-
|
|
21611
|
+
if (replacingEditedResponse) {
|
|
21612
|
+
const confirmed = await requestStudioConfirmation(
|
|
21613
|
+
"Replace your edited response with a fresh copy? Existing edits and annotations will be lost.",
|
|
21614
|
+
{ title: "Replace edited response?", confirmLabel: "Replace", destructive: true },
|
|
21615
|
+
);
|
|
21616
|
+
if (!confirmed) {
|
|
21617
|
+
setStatus("Kept the current editor text.");
|
|
21618
|
+
return false;
|
|
21619
|
+
}
|
|
21258
21620
|
}
|
|
21259
21621
|
setEditorText(latestResponseMarkdown, { preserveScroll: false, preserveSelection: false });
|
|
21260
21622
|
setSourceState({ source: "last-response", label: "last model response", path: null });
|
|
@@ -21272,11 +21634,11 @@
|
|
|
21272
21634
|
}
|
|
21273
21635
|
|
|
21274
21636
|
loadResponseBtn.addEventListener("click", () => {
|
|
21275
|
-
loadSelectedResponseIntoEditor();
|
|
21637
|
+
void loadSelectedResponseIntoEditor();
|
|
21276
21638
|
});
|
|
21277
21639
|
|
|
21278
21640
|
annotateResponseBtn.addEventListener("click", () => {
|
|
21279
|
-
loadSelectedResponseIntoEditor({ annotate: true });
|
|
21641
|
+
void loadSelectedResponseIntoEditor({ annotate: true });
|
|
21280
21642
|
});
|
|
21281
21643
|
|
|
21282
21644
|
loadCritiqueNotesBtn.addEventListener("click", () => {
|
|
@@ -21411,7 +21773,7 @@
|
|
|
21411
21773
|
setFooterThemeMenuOpen(false);
|
|
21412
21774
|
});
|
|
21413
21775
|
|
|
21414
|
-
saveAsBtn.addEventListener("click", () => {
|
|
21776
|
+
saveAsBtn.addEventListener("click", async () => {
|
|
21415
21777
|
const content = sourceTextEl.value;
|
|
21416
21778
|
if (!content.trim()) {
|
|
21417
21779
|
setStatus("Editor is empty. Nothing to save.", "warning");
|
|
@@ -21421,7 +21783,10 @@
|
|
|
21421
21783
|
var suggestedName = sourceState.label ? stripImportedFileLabel(sourceState.label) : "draft.md";
|
|
21422
21784
|
var suggestedDir = getCurrentResourceDirValue() ? getCurrentResourceDirValue().replace(/\/$/, "") + "/" : "./";
|
|
21423
21785
|
const suggested = sourceState.path || (suggestedDir + suggestedName);
|
|
21424
|
-
const path =
|
|
21786
|
+
const path = await requestStudioTextInput("Save editor content as:", suggested, {
|
|
21787
|
+
title: "Save editor as",
|
|
21788
|
+
confirmLabel: "Save",
|
|
21789
|
+
});
|
|
21425
21790
|
if (!path) return;
|
|
21426
21791
|
|
|
21427
21792
|
const requestId = beginUiAction("save_as");
|
|
@@ -21441,16 +21806,19 @@
|
|
|
21441
21806
|
}
|
|
21442
21807
|
});
|
|
21443
21808
|
|
|
21444
|
-
saveOverBtn.addEventListener("click", () => {
|
|
21809
|
+
saveOverBtn.addEventListener("click", async () => {
|
|
21445
21810
|
var effectivePath = getEffectiveSavePath();
|
|
21446
21811
|
if (!effectivePath) {
|
|
21447
21812
|
setStatus("Save editor requires a file path. Open via /studio <path>, set a working dir, or use Save editor as…", "warning");
|
|
21448
21813
|
return;
|
|
21449
21814
|
}
|
|
21450
21815
|
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
21816
|
+
const confirmed = await requestStudioConfirmation("Overwrite " + effectivePath + "?", {
|
|
21817
|
+
title: "Overwrite file?",
|
|
21818
|
+
confirmLabel: "Overwrite",
|
|
21819
|
+
destructive: true,
|
|
21820
|
+
});
|
|
21821
|
+
if (!confirmed) return;
|
|
21454
21822
|
|
|
21455
21823
|
const requestId = beginUiAction("save_over");
|
|
21456
21824
|
if (!requestId) return;
|
|
@@ -21471,14 +21839,17 @@
|
|
|
21471
21839
|
});
|
|
21472
21840
|
|
|
21473
21841
|
if (refreshFromDiskBtn) {
|
|
21474
|
-
refreshFromDiskBtn.addEventListener("click", () => {
|
|
21842
|
+
refreshFromDiskBtn.addEventListener("click", async () => {
|
|
21475
21843
|
if (!hasRefreshableFilePath()) {
|
|
21476
21844
|
setStatus("Refresh from disk needs a file path. Use Files → Open here, Files → Open file tab, or /studio-editor-only <path> for a refreshable editor tab.", "warning");
|
|
21477
21845
|
return;
|
|
21478
21846
|
}
|
|
21479
21847
|
|
|
21480
21848
|
if (editorDiffersFromFileBackedBaseline()) {
|
|
21481
|
-
const confirmed =
|
|
21849
|
+
const confirmed = await requestStudioConfirmation(
|
|
21850
|
+
"Replace current editor contents with the latest version from disk?",
|
|
21851
|
+
{ title: "Refresh from disk?", confirmLabel: "Replace", destructive: true },
|
|
21852
|
+
);
|
|
21482
21853
|
if (!confirmed) return;
|
|
21483
21854
|
}
|
|
21484
21855
|
|
|
@@ -21501,7 +21872,7 @@
|
|
|
21501
21872
|
|
|
21502
21873
|
if (clearWorkspaceBtn) {
|
|
21503
21874
|
clearWorkspaceBtn.addEventListener("click", () => {
|
|
21504
|
-
clearStudioWorkspace();
|
|
21875
|
+
void clearStudioWorkspace();
|
|
21505
21876
|
});
|
|
21506
21877
|
}
|
|
21507
21878
|
|
|
@@ -21764,7 +22135,7 @@
|
|
|
21764
22135
|
|
|
21765
22136
|
if (reviewNotesDeleteAllBtn) {
|
|
21766
22137
|
reviewNotesDeleteAllBtn.addEventListener("click", () => {
|
|
21767
|
-
deleteAllReviewNotes();
|
|
22138
|
+
void deleteAllReviewNotes();
|
|
21768
22139
|
});
|
|
21769
22140
|
}
|
|
21770
22141
|
|
|
@@ -21966,9 +22337,13 @@
|
|
|
21966
22337
|
}
|
|
21967
22338
|
|
|
21968
22339
|
if (scratchpadClearBtn) {
|
|
21969
|
-
scratchpadClearBtn.addEventListener("click", () => {
|
|
22340
|
+
scratchpadClearBtn.addEventListener("click", async () => {
|
|
21970
22341
|
if (!String(scratchpadText || "").length) return;
|
|
21971
|
-
const confirmed =
|
|
22342
|
+
const confirmed = await requestStudioConfirmation("Clear scratchpad text?", {
|
|
22343
|
+
title: "Clear scratchpad?",
|
|
22344
|
+
confirmLabel: "Clear",
|
|
22345
|
+
destructive: true,
|
|
22346
|
+
});
|
|
21972
22347
|
if (!confirmed) return;
|
|
21973
22348
|
setScratchpadText("");
|
|
21974
22349
|
if (scratchpadTextEl) scratchpadTextEl.focus();
|
|
@@ -21977,7 +22352,7 @@
|
|
|
21977
22352
|
}
|
|
21978
22353
|
|
|
21979
22354
|
if (saveAnnotatedBtn) {
|
|
21980
|
-
saveAnnotatedBtn.addEventListener("click", () => {
|
|
22355
|
+
saveAnnotatedBtn.addEventListener("click", async () => {
|
|
21981
22356
|
const content = sourceTextEl.value;
|
|
21982
22357
|
if (!content.trim()) {
|
|
21983
22358
|
setStatus("Editor is empty. Nothing to save.", "warning");
|
|
@@ -21985,7 +22360,10 @@
|
|
|
21985
22360
|
}
|
|
21986
22361
|
|
|
21987
22362
|
const suggested = buildAnnotatedSaveSuggestion();
|
|
21988
|
-
const path =
|
|
22363
|
+
const path = await requestStudioTextInput("Save annotated editor content as:", suggested, {
|
|
22364
|
+
title: "Save annotated editor as",
|
|
22365
|
+
confirmLabel: "Save",
|
|
22366
|
+
});
|
|
21989
22367
|
if (!path) return;
|
|
21990
22368
|
|
|
21991
22369
|
const requestId = beginUiAction("save_as");
|
|
@@ -22007,14 +22385,17 @@
|
|
|
22007
22385
|
}
|
|
22008
22386
|
|
|
22009
22387
|
if (stripAnnotationsBtn) {
|
|
22010
|
-
stripAnnotationsBtn.addEventListener("click", () => {
|
|
22388
|
+
stripAnnotationsBtn.addEventListener("click", async () => {
|
|
22011
22389
|
const content = sourceTextEl.value;
|
|
22012
22390
|
if (!hasAnnotationMarkers(content)) {
|
|
22013
22391
|
setStatus("No [an: ...] markers found in editor.", "warning");
|
|
22014
22392
|
return;
|
|
22015
22393
|
}
|
|
22016
22394
|
|
|
22017
|
-
const confirmed =
|
|
22395
|
+
const confirmed = await requestStudioConfirmation(
|
|
22396
|
+
"Remove all [an: ...] markers from editor text? This cannot be undone.",
|
|
22397
|
+
{ title: "Remove all annotations?", confirmLabel: "Remove", destructive: true },
|
|
22398
|
+
);
|
|
22018
22399
|
if (!confirmed) return;
|
|
22019
22400
|
|
|
22020
22401
|
const strippedContent = stripAnnotationMarkers(content);
|
|
@@ -22049,7 +22430,7 @@
|
|
|
22049
22430
|
}
|
|
22050
22431
|
if (sourceBadgeEl) {
|
|
22051
22432
|
sourceBadgeEl.addEventListener("click", () => {
|
|
22052
|
-
if (!studioUiRefreshEnabled) resetEditorOrigin();
|
|
22433
|
+
if (!studioUiRefreshEnabled) void resetEditorOrigin();
|
|
22053
22434
|
});
|
|
22054
22435
|
}
|
|
22055
22436
|
if (resourceDirBtn) {
|
|
@@ -22092,6 +22473,72 @@
|
|
|
22092
22473
|
});
|
|
22093
22474
|
}
|
|
22094
22475
|
|
|
22476
|
+
function applyImportedFileCopy(text, filename) {
|
|
22477
|
+
const name = String(filename || "imported file").trim() || "imported file";
|
|
22478
|
+
setEditorText(String(text || ""), { preserveScroll: false, preserveSelection: false });
|
|
22479
|
+
setSourceState({
|
|
22480
|
+
source: "upload",
|
|
22481
|
+
label: "imported copy: " + name,
|
|
22482
|
+
path: null,
|
|
22483
|
+
});
|
|
22484
|
+
refreshResponseUi();
|
|
22485
|
+
const detectedLang = detectLanguageFromName(name);
|
|
22486
|
+
if (detectedLang) setEditorLanguage(detectedLang);
|
|
22487
|
+
setStatus("Imported file copy: " + name + ".", "success");
|
|
22488
|
+
}
|
|
22489
|
+
|
|
22490
|
+
function chooseStudioFileCopyWithBrowser() {
|
|
22491
|
+
fileInput.value = "";
|
|
22492
|
+
setStatus("If no file picker appeared, enter the file path and select Import from path.");
|
|
22493
|
+
try {
|
|
22494
|
+
fileInput.click();
|
|
22495
|
+
} catch {
|
|
22496
|
+
setStatus("This browser could not open its file picker. Enter the file path and select Import from path.", "warning");
|
|
22497
|
+
}
|
|
22498
|
+
}
|
|
22499
|
+
|
|
22500
|
+
async function openStudioFileCopyDialog() {
|
|
22501
|
+
const resourceDir = getCurrentResourceDirValue();
|
|
22502
|
+
const suggestedPath = resourceDir ? resourceDir.replace(/[\\/]$/, "") + "/" : "./";
|
|
22503
|
+
let path = null;
|
|
22504
|
+
studioImportDecisionOpen = true;
|
|
22505
|
+
try {
|
|
22506
|
+
path = await requestStudioTextInput(
|
|
22507
|
+
"Enter the path to a file you want to import, or use Browse to open your browser’s file picker.",
|
|
22508
|
+
suggestedPath,
|
|
22509
|
+
{
|
|
22510
|
+
title: "Import file copy",
|
|
22511
|
+
confirmLabel: "Import from path",
|
|
22512
|
+
secondaryLabel: "Browse…",
|
|
22513
|
+
onSecondary: chooseStudioFileCopyWithBrowser,
|
|
22514
|
+
inputLabel: "File path on computer running Pi",
|
|
22515
|
+
placeholder: "/path/to/file.md",
|
|
22516
|
+
},
|
|
22517
|
+
);
|
|
22518
|
+
} finally {
|
|
22519
|
+
studioImportDecisionOpen = false;
|
|
22520
|
+
}
|
|
22521
|
+
if (!path) return;
|
|
22522
|
+
try {
|
|
22523
|
+
const payload = await fetchStudioJson("/import-file-copy", {
|
|
22524
|
+
method: "POST",
|
|
22525
|
+
body: JSON.stringify({ path }),
|
|
22526
|
+
});
|
|
22527
|
+
if (typeof payload.text !== "string") throw new Error("Studio did not return file text.");
|
|
22528
|
+
applyImportedFileCopy(payload.text, typeof payload.filename === "string" ? payload.filename : path);
|
|
22529
|
+
} catch (error) {
|
|
22530
|
+
setStatus("Could not import file copy: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
22531
|
+
}
|
|
22532
|
+
}
|
|
22533
|
+
|
|
22534
|
+
if (importFileBtn) {
|
|
22535
|
+
importFileBtn.addEventListener("click", (event) => {
|
|
22536
|
+
event.preventDefault();
|
|
22537
|
+
event.stopPropagation();
|
|
22538
|
+
void openStudioFileCopyDialog();
|
|
22539
|
+
});
|
|
22540
|
+
}
|
|
22541
|
+
|
|
22095
22542
|
fileInput.addEventListener("change", () => {
|
|
22096
22543
|
const file = fileInput.files && fileInput.files[0];
|
|
22097
22544
|
if (!file) return;
|
|
@@ -22103,21 +22550,11 @@
|
|
|
22103
22550
|
const reader = new FileReader();
|
|
22104
22551
|
reader.onload = () => {
|
|
22105
22552
|
const text = typeof reader.result === "string" ? reader.result : "";
|
|
22106
|
-
|
|
22107
|
-
|
|
22108
|
-
source: "upload",
|
|
22109
|
-
label: "imported copy: " + file.name,
|
|
22110
|
-
path: null,
|
|
22111
|
-
});
|
|
22112
|
-
refreshResponseUi();
|
|
22113
|
-
const detectedLang = detectLanguageFromName(file.name);
|
|
22114
|
-
if (detectedLang) {
|
|
22115
|
-
setEditorLanguage(detectedLang);
|
|
22116
|
-
}
|
|
22117
|
-
setStatus("Imported file copy: " + file.name + ".", "success");
|
|
22553
|
+
if (studioImportDecisionOpen) finishStudioDecision(null);
|
|
22554
|
+
applyImportedFileCopy(text, file.name);
|
|
22118
22555
|
};
|
|
22119
22556
|
reader.onerror = () => {
|
|
22120
|
-
setStatus("Failed to read file.", "error");
|
|
22557
|
+
setStatus("Failed to read file. Enter its path in the import dialog or choose another file.", "error");
|
|
22121
22558
|
};
|
|
22122
22559
|
reader.readAsText(file);
|
|
22123
22560
|
});
|