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.
@@ -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
- if (!window.confirm(message)) {
3723
- return;
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 = "Open PDF";
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 buildStudioPdfResourceUrl(options, useEditorResourceContext) {
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 params = new URLSearchParams({ token, path: pdfPath });
6850
- if (sourcePath) {
6851
- params.set("sourcePath", sourcePath);
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
- if (resourceDir) {
6854
- params.set("resourceDir", resourceDir);
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
- return "/pdf-resource?" + params.toString();
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 = "Open PDF";
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
- if (viewerUrl) openStudioPdfFocusViewer(viewerUrl, downloadName);
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 = window.confirm("Reset the editor to a fresh blank draft in this browser tab? Saved files and responses are not changed.");
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
- openStudioPdfFocusViewer(viewerUrl, title || href);
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 = window.confirm(
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 = window.confirm(prompt);
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 = window.confirm("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.");
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 = window.confirm("Delete this local comment?");
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 = window.confirm(
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
- replacingEditedResponse
21254
- && !window.confirm("Replace your edited response with a fresh copy? Existing edits and annotations will be lost.")
21255
- ) {
21256
- setStatus("Kept the current editor text.");
21257
- return false;
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 = window.prompt("Save editor content as:", suggested);
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
- if (!window.confirm("Overwrite " + effectivePath + "?")) {
21452
- return;
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 = window.confirm("Replace current editor contents with the latest version from disk?");
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 = window.confirm("Clear scratchpad text?");
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 = window.prompt("Save annotated editor content as:", suggested);
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 = window.confirm("Remove all [an: ...] markers from editor text? This cannot be undone.");
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
- setEditorText(text, { preserveScroll: false, preserveSelection: false });
22107
- setSourceState({
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
  });