pi-studio 0.5.35 → 0.5.37
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 +18 -0
- package/README.md +1 -0
- package/client/studio-annotation-helpers.js +543 -0
- package/client/studio-client.js +283 -139
- package/client/studio.css +133 -0
- package/index.ts +196 -93
- package/package.json +5 -1
- package/shared/studio-annotation-scanner.js +370 -0
- package/shared/studio-pdf-escape.js +34 -0
package/client/studio-client.js
CHANGED
|
@@ -88,6 +88,16 @@
|
|
|
88
88
|
const compactBtn = document.getElementById("compactBtn");
|
|
89
89
|
const leftFocusBtn = document.getElementById("leftFocusBtn");
|
|
90
90
|
const rightFocusBtn = document.getElementById("rightFocusBtn");
|
|
91
|
+
const scratchpadBtn = document.getElementById("scratchpadBtn");
|
|
92
|
+
const scratchpadOverlayEl = document.getElementById("scratchpadOverlay");
|
|
93
|
+
const scratchpadDialogEl = document.getElementById("scratchpadDialog");
|
|
94
|
+
const scratchpadTextEl = document.getElementById("scratchpadText");
|
|
95
|
+
const scratchpadMetaEl = document.getElementById("scratchpadMeta");
|
|
96
|
+
const scratchpadInsertBtn = document.getElementById("scratchpadInsertBtn");
|
|
97
|
+
const scratchpadCopyBtn = document.getElementById("scratchpadCopyBtn");
|
|
98
|
+
const scratchpadClearBtn = document.getElementById("scratchpadClearBtn");
|
|
99
|
+
const scratchpadCloseBtn = document.getElementById("scratchpadCloseBtn");
|
|
100
|
+
const scratchpadDoneBtn = document.getElementById("scratchpadDoneBtn");
|
|
91
101
|
|
|
92
102
|
const initialSourceState = {
|
|
93
103
|
source: (document.body && document.body.dataset && document.body.dataset.initialSource) || "blank",
|
|
@@ -227,6 +237,7 @@
|
|
|
227
237
|
const RESPONSE_HIGHLIGHT_MAX_CHARS = 120_000;
|
|
228
238
|
const RESPONSE_HIGHLIGHT_STORAGE_KEY = "piStudio.responseHighlightEnabled";
|
|
229
239
|
const ANNOTATION_MODE_STORAGE_KEY = "piStudio.annotationsEnabled";
|
|
240
|
+
const SCRATCHPAD_STORAGE_KEY = "piStudio.scratchpad";
|
|
230
241
|
const PREVIEW_INPUT_DEBOUNCE_MS = 0;
|
|
231
242
|
const PREVIEW_PENDING_BADGE_DELAY_MS = 220;
|
|
232
243
|
const previewPendingTimers = new WeakMap();
|
|
@@ -241,8 +252,13 @@
|
|
|
241
252
|
let responseHighlightEnabled = false;
|
|
242
253
|
let editorHighlightRenderRaf = null;
|
|
243
254
|
let annotationsEnabled = true;
|
|
244
|
-
|
|
255
|
+
let scratchpadText = "";
|
|
256
|
+
let scratchpadReturnFocusEl = null;
|
|
245
257
|
const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
|
|
258
|
+
const annotationHelpers = globalThis.PiStudioAnnotationHelpers;
|
|
259
|
+
if (!annotationHelpers || typeof annotationHelpers.collectInlineAnnotationMarkers !== "function") {
|
|
260
|
+
throw new Error("Studio annotation helpers failed to load.");
|
|
261
|
+
}
|
|
246
262
|
const EMPTY_OVERLAY_LINE = "\u200b";
|
|
247
263
|
const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
248
264
|
const MATHJAX_CDN_URL = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js";
|
|
@@ -861,6 +877,28 @@
|
|
|
861
877
|
if (!event || event.defaultPrevented) return;
|
|
862
878
|
|
|
863
879
|
const key = typeof event.key === "string" ? event.key : "";
|
|
880
|
+
const plainEscape = key === "Escape"
|
|
881
|
+
&& !event.metaKey
|
|
882
|
+
&& !event.ctrlKey
|
|
883
|
+
&& !event.altKey
|
|
884
|
+
&& !event.shiftKey;
|
|
885
|
+
const scratchpadOwnsEvent = Boolean(
|
|
886
|
+
scratchpadDialogEl
|
|
887
|
+
&& event.target
|
|
888
|
+
&& typeof scratchpadDialogEl.contains === "function"
|
|
889
|
+
&& scratchpadDialogEl.contains(event.target)
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
if (isScratchpadOpen() && plainEscape) {
|
|
893
|
+
event.preventDefault();
|
|
894
|
+
closeScratchpad();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (scratchpadOwnsEvent) {
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
|
|
864
902
|
const isToggleShortcut =
|
|
865
903
|
(key === "Escape" && (event.metaKey || event.ctrlKey))
|
|
866
904
|
|| key === "F10";
|
|
@@ -871,13 +909,7 @@
|
|
|
871
909
|
return;
|
|
872
910
|
}
|
|
873
911
|
|
|
874
|
-
if (
|
|
875
|
-
key === "Escape"
|
|
876
|
-
&& !event.metaKey
|
|
877
|
-
&& !event.ctrlKey
|
|
878
|
-
&& !event.altKey
|
|
879
|
-
&& !event.shiftKey
|
|
880
|
-
) {
|
|
912
|
+
if (plainEscape) {
|
|
881
913
|
const activeKind = getAbortablePendingKind();
|
|
882
914
|
if (activeKind === "direct" || activeKind === "critique") {
|
|
883
915
|
event.preventDefault();
|
|
@@ -1163,15 +1195,11 @@
|
|
|
1163
1195
|
}
|
|
1164
1196
|
|
|
1165
1197
|
function hasAnnotationMarkers(text) {
|
|
1166
|
-
|
|
1167
|
-
ANNOTATION_MARKER_REGEX.lastIndex = 0;
|
|
1168
|
-
const hasMarker = ANNOTATION_MARKER_REGEX.test(source);
|
|
1169
|
-
ANNOTATION_MARKER_REGEX.lastIndex = 0;
|
|
1170
|
-
return hasMarker;
|
|
1198
|
+
return annotationHelpers.hasAnnotationMarkers(text);
|
|
1171
1199
|
}
|
|
1172
1200
|
|
|
1173
1201
|
function stripAnnotationMarkers(text) {
|
|
1174
|
-
return
|
|
1202
|
+
return annotationHelpers.stripAnnotationMarkers(text);
|
|
1175
1203
|
}
|
|
1176
1204
|
|
|
1177
1205
|
function prepareEditorTextForSend(text) {
|
|
@@ -1184,78 +1212,8 @@
|
|
|
1184
1212
|
return annotationsEnabled ? raw : stripAnnotationMarkers(raw);
|
|
1185
1213
|
}
|
|
1186
1214
|
|
|
1187
|
-
function normalizePreviewAnnotationLabel(text) {
|
|
1188
|
-
return String(text || "")
|
|
1189
|
-
.replace(/\r\n/g, "\n")
|
|
1190
|
-
.replace(/\s*\n\s*/g, " ")
|
|
1191
|
-
.replace(/\s{2,}/g, " ")
|
|
1192
|
-
.trim();
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
1215
|
function prepareMarkdownForPandocPreview(markdown) {
|
|
1196
|
-
|
|
1197
|
-
const placeholders = [];
|
|
1198
|
-
if (!source) {
|
|
1199
|
-
return { markdown: source, placeholders: placeholders };
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
const lines = source.split("\n");
|
|
1203
|
-
const out = [];
|
|
1204
|
-
let plainBuffer = [];
|
|
1205
|
-
let inFence = false;
|
|
1206
|
-
let fenceChar = null;
|
|
1207
|
-
let fenceLength = 0;
|
|
1208
|
-
|
|
1209
|
-
function flushPlain() {
|
|
1210
|
-
if (plainBuffer.length === 0) return;
|
|
1211
|
-
const segment = plainBuffer.join("\n").replace(/\[an:\s*([^\]]+?)\]/gi, function(_match, markerText) {
|
|
1212
|
-
const label = normalizePreviewAnnotationLabel(markerText);
|
|
1213
|
-
if (!label) return "";
|
|
1214
|
-
const token = PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX + placeholders.length + "TOKEN";
|
|
1215
|
-
placeholders.push({ token: token, text: label, title: "[an: " + label + "]" });
|
|
1216
|
-
return token;
|
|
1217
|
-
});
|
|
1218
|
-
out.push(segment);
|
|
1219
|
-
plainBuffer = [];
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
for (const line of lines) {
|
|
1223
|
-
const trimmed = line.trimStart();
|
|
1224
|
-
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
1225
|
-
|
|
1226
|
-
if (fenceMatch) {
|
|
1227
|
-
const marker = fenceMatch[1] || "";
|
|
1228
|
-
const markerChar = marker.charAt(0);
|
|
1229
|
-
const markerLength = marker.length;
|
|
1230
|
-
|
|
1231
|
-
if (!inFence) {
|
|
1232
|
-
flushPlain();
|
|
1233
|
-
inFence = true;
|
|
1234
|
-
fenceChar = markerChar;
|
|
1235
|
-
fenceLength = markerLength;
|
|
1236
|
-
out.push(line);
|
|
1237
|
-
continue;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
1241
|
-
inFence = false;
|
|
1242
|
-
fenceChar = null;
|
|
1243
|
-
fenceLength = 0;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
out.push(line);
|
|
1247
|
-
continue;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
if (inFence) {
|
|
1251
|
-
out.push(line);
|
|
1252
|
-
} else {
|
|
1253
|
-
plainBuffer.push(line);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
flushPlain();
|
|
1258
|
-
return { markdown: out.join("\n"), placeholders: placeholders };
|
|
1216
|
+
return annotationHelpers.prepareMarkdownForPandocPreview(markdown, PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX);
|
|
1259
1217
|
}
|
|
1260
1218
|
|
|
1261
1219
|
function wrapAsFencedCodeBlock(text, language) {
|
|
@@ -1774,8 +1732,9 @@
|
|
|
1774
1732
|
if (entry) {
|
|
1775
1733
|
const markerEl = document.createElement("span");
|
|
1776
1734
|
markerEl.className = "annotation-preview-marker";
|
|
1777
|
-
|
|
1778
|
-
markerEl.title = typeof entry.title === "string" ? entry.title :
|
|
1735
|
+
const markerText = typeof entry.text === "string" ? entry.text : token;
|
|
1736
|
+
markerEl.title = typeof entry.title === "string" ? entry.title : markerText;
|
|
1737
|
+
setAnnotationPreviewMarkerContent(markerEl, markerText);
|
|
1779
1738
|
fragment.appendChild(markerEl);
|
|
1780
1739
|
} else {
|
|
1781
1740
|
fragment.appendChild(document.createTextNode(token));
|
|
@@ -1819,33 +1778,28 @@
|
|
|
1819
1778
|
for (const textNode of textNodes) {
|
|
1820
1779
|
const text = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
|
|
1821
1780
|
if (!text) continue;
|
|
1822
|
-
|
|
1823
|
-
if (
|
|
1824
|
-
ANNOTATION_MARKER_REGEX.lastIndex = 0;
|
|
1781
|
+
const markers = annotationHelpers.collectInlineAnnotationMarkers(text);
|
|
1782
|
+
if (markers.length === 0) continue;
|
|
1825
1783
|
|
|
1826
1784
|
const fragment = document.createDocumentFragment();
|
|
1827
1785
|
let lastIndex = 0;
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
if (start > lastIndex) {
|
|
1833
|
-
fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)));
|
|
1786
|
+
markers.forEach(function(marker) {
|
|
1787
|
+
const token = marker.raw || "";
|
|
1788
|
+
if (marker.start > lastIndex) {
|
|
1789
|
+
fragment.appendChild(document.createTextNode(text.slice(lastIndex, marker.start)));
|
|
1834
1790
|
}
|
|
1835
1791
|
|
|
1836
1792
|
if (mode === "highlight") {
|
|
1837
1793
|
const markerEl = document.createElement("span");
|
|
1838
1794
|
markerEl.className = "annotation-preview-marker";
|
|
1839
|
-
|
|
1795
|
+
const markerText = annotationHelpers.normalizePreviewAnnotationLabel(marker.body) || token;
|
|
1840
1796
|
markerEl.title = token;
|
|
1797
|
+
setAnnotationPreviewMarkerContent(markerEl, markerText);
|
|
1841
1798
|
fragment.appendChild(markerEl);
|
|
1842
1799
|
}
|
|
1843
1800
|
|
|
1844
|
-
lastIndex =
|
|
1845
|
-
|
|
1846
|
-
ANNOTATION_MARKER_REGEX.lastIndex += 1;
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1801
|
+
lastIndex = marker.end;
|
|
1802
|
+
});
|
|
1849
1803
|
|
|
1850
1804
|
if (lastIndex < text.length) {
|
|
1851
1805
|
fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
|
|
@@ -2732,50 +2686,44 @@
|
|
|
2732
2686
|
return "<span class='" + className + "'>" + escapeHtml(String(text || "")) + "</span>";
|
|
2733
2687
|
}
|
|
2734
2688
|
|
|
2735
|
-
function
|
|
2689
|
+
function buildAnnotationPreviewMarkerHtml(text, title) {
|
|
2736
2690
|
const titleAttr = title ? " title='" + escapeHtml(String(title)) + "'" : "";
|
|
2737
|
-
|
|
2691
|
+
const rendered = typeof annotationHelpers.renderPreviewAnnotationHtml === "function"
|
|
2692
|
+
? annotationHelpers.renderPreviewAnnotationHtml(text)
|
|
2693
|
+
: escapeHtml(String(text || ""));
|
|
2694
|
+
return "<span class='annotation-preview-marker'" + titleAttr + ">" + rendered + "</span>";
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
function setAnnotationPreviewMarkerContent(markerEl, text) {
|
|
2698
|
+
if (!markerEl) return;
|
|
2699
|
+
const rendered = typeof annotationHelpers.renderPreviewAnnotationHtml === "function"
|
|
2700
|
+
? annotationHelpers.renderPreviewAnnotationHtml(text)
|
|
2701
|
+
: escapeHtml(String(text || ""));
|
|
2702
|
+
markerEl.innerHTML = rendered;
|
|
2738
2703
|
}
|
|
2739
2704
|
|
|
2740
2705
|
function highlightInlineAnnotations(text, mode) {
|
|
2741
2706
|
const source = String(text || "");
|
|
2742
2707
|
const renderMode = mode === "preview" ? "preview" : "overlay";
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
}
|
|
2756
|
-
|
|
2757
|
-
if (renderMode === "preview") {
|
|
2758
|
-
out += wrapHighlightWithTitle("annotation-preview-marker", markerText || token, token);
|
|
2759
|
-
} else {
|
|
2760
|
-
out += wrapHighlight(annotationsEnabled ? "hl-annotation" : "hl-annotation-muted", token);
|
|
2761
|
-
}
|
|
2762
|
-
lastIndex = start + token.length;
|
|
2763
|
-
if (token.length === 0) {
|
|
2764
|
-
ANNOTATION_MARKER_REGEX.lastIndex += 1;
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
ANNOTATION_MARKER_REGEX.lastIndex = 0;
|
|
2769
|
-
if (lastIndex < source.length) {
|
|
2770
|
-
out += escapeHtml(source.slice(lastIndex));
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
|
-
return out;
|
|
2708
|
+
return annotationHelpers.replaceInlineAnnotationMarkers(
|
|
2709
|
+
source,
|
|
2710
|
+
function(marker) {
|
|
2711
|
+
const token = marker.raw || "";
|
|
2712
|
+
const markerText = annotationHelpers.normalizePreviewAnnotationLabel(marker.body) || token;
|
|
2713
|
+
if (renderMode === "preview") {
|
|
2714
|
+
return buildAnnotationPreviewMarkerHtml(markerText, token);
|
|
2715
|
+
}
|
|
2716
|
+
return wrapHighlight(annotationsEnabled ? "hl-annotation" : "hl-annotation-muted", token);
|
|
2717
|
+
},
|
|
2718
|
+
function(segment) {
|
|
2719
|
+
return escapeHtml(segment);
|
|
2720
|
+
},
|
|
2721
|
+
);
|
|
2774
2722
|
}
|
|
2775
2723
|
|
|
2776
|
-
function
|
|
2724
|
+
function highlightInlineMarkdownWithoutAnnotations(text) {
|
|
2777
2725
|
const source = String(text || "");
|
|
2778
|
-
const pattern = /(\x60[^\x60]*\x60)|(\[[^\]]+\]\([^)]+\))
|
|
2726
|
+
const pattern = /(\x60[^\x60]*\x60)|(\[[^\]]+\]\([^)]+\))/g;
|
|
2779
2727
|
let lastIndex = 0;
|
|
2780
2728
|
let out = "";
|
|
2781
2729
|
|
|
@@ -2798,8 +2746,6 @@
|
|
|
2798
2746
|
} else {
|
|
2799
2747
|
out += escapeHtml(token);
|
|
2800
2748
|
}
|
|
2801
|
-
} else if (match[3]) {
|
|
2802
|
-
out += highlightInlineAnnotations(token);
|
|
2803
2749
|
} else {
|
|
2804
2750
|
out += escapeHtml(token);
|
|
2805
2751
|
}
|
|
@@ -2814,6 +2760,18 @@
|
|
|
2814
2760
|
return out;
|
|
2815
2761
|
}
|
|
2816
2762
|
|
|
2763
|
+
function highlightInlineMarkdown(text) {
|
|
2764
|
+
return annotationHelpers.replaceInlineAnnotationMarkers(
|
|
2765
|
+
String(text || ""),
|
|
2766
|
+
function(marker) {
|
|
2767
|
+
return highlightInlineAnnotations(marker.raw || "");
|
|
2768
|
+
},
|
|
2769
|
+
function(segment) {
|
|
2770
|
+
return highlightInlineMarkdownWithoutAnnotations(segment);
|
|
2771
|
+
},
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2817
2775
|
function normalizeFenceLanguage(info) {
|
|
2818
2776
|
const raw = String(info || "").trim();
|
|
2819
2777
|
if (!raw) return "";
|
|
@@ -3280,6 +3238,126 @@
|
|
|
3280
3238
|
persistStoredToggle(ANNOTATION_MODE_STORAGE_KEY, enabled);
|
|
3281
3239
|
}
|
|
3282
3240
|
|
|
3241
|
+
function readStoredText(storageKey) {
|
|
3242
|
+
if (!window.localStorage) return null;
|
|
3243
|
+
try {
|
|
3244
|
+
const value = window.localStorage.getItem(storageKey);
|
|
3245
|
+
return typeof value === "string" ? value : null;
|
|
3246
|
+
} catch {
|
|
3247
|
+
return null;
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
function persistStoredText(storageKey, value) {
|
|
3252
|
+
if (!window.localStorage) return;
|
|
3253
|
+
try {
|
|
3254
|
+
window.localStorage.setItem(storageKey, String(value ?? ""));
|
|
3255
|
+
} catch {
|
|
3256
|
+
// ignore storage failures
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
function isScratchpadOpen() {
|
|
3261
|
+
return Boolean(scratchpadOverlayEl && !scratchpadOverlayEl.hidden);
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
function readStoredScratchpadText() {
|
|
3265
|
+
return readStoredText(SCRATCHPAD_STORAGE_KEY);
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
function persistScratchpadText(value) {
|
|
3269
|
+
persistStoredText(SCRATCHPAD_STORAGE_KEY, value);
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
function updateScratchpadUi() {
|
|
3273
|
+
const normalized = String(scratchpadText || "");
|
|
3274
|
+
const hasContent = Boolean(normalized.trim());
|
|
3275
|
+
if (scratchpadBtn) {
|
|
3276
|
+
scratchpadBtn.textContent = hasContent ? "Scratchpad •" : "Scratchpad";
|
|
3277
|
+
scratchpadBtn.classList.toggle("has-content", hasContent);
|
|
3278
|
+
scratchpadBtn.title = hasContent
|
|
3279
|
+
? "Open your local persistent scratchpad. Current notes persist after closing until you edit or clear them."
|
|
3280
|
+
: "Open a local persistent scratchpad for quick notes. Anything you type will persist after closing until you edit or clear it.";
|
|
3281
|
+
}
|
|
3282
|
+
if (scratchpadMetaEl) {
|
|
3283
|
+
scratchpadMetaEl.textContent = hasContent
|
|
3284
|
+
? "Saved locally · persists after close · " + normalized.length + " chars"
|
|
3285
|
+
: "Empty · local only";
|
|
3286
|
+
}
|
|
3287
|
+
if (scratchpadInsertBtn) scratchpadInsertBtn.disabled = !hasContent;
|
|
3288
|
+
if (scratchpadCopyBtn) scratchpadCopyBtn.disabled = !hasContent;
|
|
3289
|
+
if (scratchpadClearBtn) scratchpadClearBtn.disabled = !normalized.length;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
function setScratchpadText(nextText, options) {
|
|
3293
|
+
scratchpadText = String(nextText || "");
|
|
3294
|
+
if (scratchpadTextEl && scratchpadTextEl.value !== scratchpadText) {
|
|
3295
|
+
scratchpadTextEl.value = scratchpadText;
|
|
3296
|
+
}
|
|
3297
|
+
if (!options || options.persist !== false) {
|
|
3298
|
+
persistScratchpadText(scratchpadText);
|
|
3299
|
+
}
|
|
3300
|
+
updateScratchpadUi();
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
function closeScratchpad(options) {
|
|
3304
|
+
if (!scratchpadOverlayEl || scratchpadOverlayEl.hidden) return;
|
|
3305
|
+
scratchpadOverlayEl.hidden = true;
|
|
3306
|
+
document.body.classList.remove("scratchpad-open");
|
|
3307
|
+
const focusTarget = options && Object.prototype.hasOwnProperty.call(options, "focusTarget")
|
|
3308
|
+
? options.focusTarget
|
|
3309
|
+
: (scratchpadReturnFocusEl || scratchpadBtn || sourceTextEl);
|
|
3310
|
+
scratchpadReturnFocusEl = null;
|
|
3311
|
+
if (focusTarget && typeof focusTarget.focus === "function") {
|
|
3312
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
3313
|
+
? window.requestAnimationFrame.bind(window)
|
|
3314
|
+
: (cb) => window.setTimeout(cb, 16);
|
|
3315
|
+
schedule(() => focusTarget.focus());
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
function openScratchpad() {
|
|
3320
|
+
if (!scratchpadOverlayEl) return;
|
|
3321
|
+
scratchpadReturnFocusEl = document.activeElement && document.activeElement !== document.body
|
|
3322
|
+
? document.activeElement
|
|
3323
|
+
: sourceTextEl;
|
|
3324
|
+
scratchpadOverlayEl.hidden = false;
|
|
3325
|
+
document.body.classList.add("scratchpad-open");
|
|
3326
|
+
if (scratchpadTextEl && typeof scratchpadTextEl.focus === "function") {
|
|
3327
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
3328
|
+
? window.requestAnimationFrame.bind(window)
|
|
3329
|
+
: (cb) => window.setTimeout(cb, 16);
|
|
3330
|
+
schedule(() => {
|
|
3331
|
+
scratchpadTextEl.focus();
|
|
3332
|
+
if (typeof scratchpadTextEl.selectionStart === "number") {
|
|
3333
|
+
const end = scratchpadTextEl.value.length;
|
|
3334
|
+
scratchpadTextEl.setSelectionRange(end, end);
|
|
3335
|
+
}
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
function insertScratchpadIntoEditor() {
|
|
3341
|
+
const content = String(scratchpadText || "");
|
|
3342
|
+
if (!content.trim()) {
|
|
3343
|
+
setStatus("Scratchpad is empty.", "warning");
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
const current = sourceTextEl.value || "";
|
|
3348
|
+
const start = typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : current.length;
|
|
3349
|
+
const end = typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : start;
|
|
3350
|
+
const safeStart = Math.max(0, Math.min(start, current.length));
|
|
3351
|
+
const safeEnd = Math.max(safeStart, Math.min(end, current.length));
|
|
3352
|
+
const next = current.slice(0, safeStart) + content + current.slice(safeEnd);
|
|
3353
|
+
setEditorText(next, { preserveScroll: false, preserveSelection: false });
|
|
3354
|
+
const caret = safeStart + content.length;
|
|
3355
|
+
sourceTextEl.setSelectionRange(caret, caret);
|
|
3356
|
+
setActivePane("left");
|
|
3357
|
+
closeScratchpad({ focusTarget: sourceTextEl });
|
|
3358
|
+
setStatus("Inserted scratchpad into editor.", "success");
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3283
3361
|
function updateEditorHighlightState() {
|
|
3284
3362
|
const enabled = editorHighlightEnabled && editorView === "markdown";
|
|
3285
3363
|
|
|
@@ -4786,6 +4864,71 @@
|
|
|
4786
4864
|
}
|
|
4787
4865
|
});
|
|
4788
4866
|
|
|
4867
|
+
if (scratchpadBtn) {
|
|
4868
|
+
scratchpadBtn.addEventListener("click", () => {
|
|
4869
|
+
openScratchpad();
|
|
4870
|
+
});
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
if (scratchpadCloseBtn) {
|
|
4874
|
+
scratchpadCloseBtn.addEventListener("click", () => {
|
|
4875
|
+
closeScratchpad();
|
|
4876
|
+
});
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
if (scratchpadDoneBtn) {
|
|
4880
|
+
scratchpadDoneBtn.addEventListener("click", () => {
|
|
4881
|
+
closeScratchpad();
|
|
4882
|
+
});
|
|
4883
|
+
}
|
|
4884
|
+
|
|
4885
|
+
if (scratchpadOverlayEl) {
|
|
4886
|
+
scratchpadOverlayEl.addEventListener("click", (event) => {
|
|
4887
|
+
if (event.target === scratchpadOverlayEl) {
|
|
4888
|
+
closeScratchpad();
|
|
4889
|
+
}
|
|
4890
|
+
});
|
|
4891
|
+
}
|
|
4892
|
+
|
|
4893
|
+
if (scratchpadTextEl) {
|
|
4894
|
+
scratchpadTextEl.addEventListener("input", () => {
|
|
4895
|
+
setScratchpadText(scratchpadTextEl.value);
|
|
4896
|
+
});
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4899
|
+
if (scratchpadInsertBtn) {
|
|
4900
|
+
scratchpadInsertBtn.addEventListener("click", () => {
|
|
4901
|
+
insertScratchpadIntoEditor();
|
|
4902
|
+
});
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
if (scratchpadCopyBtn) {
|
|
4906
|
+
scratchpadCopyBtn.addEventListener("click", async () => {
|
|
4907
|
+
if (!String(scratchpadText || "").trim()) {
|
|
4908
|
+
setStatus("Scratchpad is empty.", "warning");
|
|
4909
|
+
return;
|
|
4910
|
+
}
|
|
4911
|
+
|
|
4912
|
+
try {
|
|
4913
|
+
await navigator.clipboard.writeText(String(scratchpadText || ""));
|
|
4914
|
+
setStatus("Copied scratchpad text.", "success");
|
|
4915
|
+
} catch (error) {
|
|
4916
|
+
setStatus("Clipboard write failed.", "warning");
|
|
4917
|
+
}
|
|
4918
|
+
});
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
if (scratchpadClearBtn) {
|
|
4922
|
+
scratchpadClearBtn.addEventListener("click", () => {
|
|
4923
|
+
if (!String(scratchpadText || "").length) return;
|
|
4924
|
+
const confirmed = window.confirm("Clear scratchpad text?");
|
|
4925
|
+
if (!confirmed) return;
|
|
4926
|
+
setScratchpadText("");
|
|
4927
|
+
if (scratchpadTextEl) scratchpadTextEl.focus();
|
|
4928
|
+
setStatus("Cleared scratchpad.", "success");
|
|
4929
|
+
});
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4789
4932
|
if (saveAnnotatedBtn) {
|
|
4790
4933
|
saveAnnotatedBtn.addEventListener("click", () => {
|
|
4791
4934
|
const content = sourceTextEl.value;
|
|
@@ -4924,6 +5067,7 @@
|
|
|
4924
5067
|
refreshResponseUi();
|
|
4925
5068
|
updateAnnotatedReplyHeaderButton();
|
|
4926
5069
|
setActivePane("left");
|
|
5070
|
+
setScratchpadText(readStoredScratchpadText() || "", { persist: false });
|
|
4927
5071
|
|
|
4928
5072
|
const storedEditorHighlightEnabled = readStoredEditorHighlightEnabled();
|
|
4929
5073
|
const initialHighlightEnabled = storedEditorHighlightEnabled ?? Boolean(highlightSelect && highlightSelect.value === "on");
|