pi-studio 0.9.18 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +2 -2
- package/client/studio-client.js +662 -100
- package/client/studio.css +49 -4
- package/index.ts +88 -14
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -229,6 +229,35 @@
|
|
|
229
229
|
let stickyStudioKind = null;
|
|
230
230
|
const pendingCompanionWindows = new Map();
|
|
231
231
|
let initialDocumentApplied = false;
|
|
232
|
+
function normalizeRightViewValue(nextView) {
|
|
233
|
+
const raw = String(nextView || "").trim();
|
|
234
|
+
const normalized = raw === "preview"
|
|
235
|
+
? "preview"
|
|
236
|
+
: (raw === "editor-preview"
|
|
237
|
+
? "editor-preview"
|
|
238
|
+
: (raw === "repl"
|
|
239
|
+
? "repl"
|
|
240
|
+
: (raw === "files"
|
|
241
|
+
? "files"
|
|
242
|
+
: ((raw === "trace" || raw === "thinking") ? "trace" : "markdown"))));
|
|
243
|
+
if (isEditorOnlyMode && normalized !== "editor-preview" && normalized !== "files" && normalized !== "repl") {
|
|
244
|
+
return "editor-preview";
|
|
245
|
+
}
|
|
246
|
+
return normalized;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function syncRightViewModeOptions() {
|
|
250
|
+
if (!rightViewSelect || !rightViewSelect.options) return;
|
|
251
|
+
const editorOnlyAllowed = new Set(["editor-preview", "files", "repl"]);
|
|
252
|
+
Array.from(rightViewSelect.options).forEach((option) => {
|
|
253
|
+
if (!option) return;
|
|
254
|
+
option.disabled = isEditorOnlyMode && !editorOnlyAllowed.has(option.value);
|
|
255
|
+
});
|
|
256
|
+
rightViewSelect.title = isEditorOnlyMode
|
|
257
|
+
? "Editor-only views: editor preview, Files, or REPL. Shortcut: F7 when the right pane is active; F6 switches panes."
|
|
258
|
+
: "Right pane view mode. Shortcut: F7 when the right pane is active; F6 switches panes.";
|
|
259
|
+
}
|
|
260
|
+
|
|
232
261
|
function getInitialRightView(source) {
|
|
233
262
|
if (isEditorOnlyMode) return "editor-preview";
|
|
234
263
|
return String(source || "").trim() === "file" ? "editor-preview" : "preview";
|
|
@@ -580,6 +609,7 @@
|
|
|
580
609
|
toolName: typeof entry.toolName === "string" ? entry.toolName : "tool",
|
|
581
610
|
label: parseNonEmptyString(entry.label),
|
|
582
611
|
argsSummary: parseNonEmptyString(entry.argsSummary),
|
|
612
|
+
args: parseNonEmptyString(entry.args),
|
|
583
613
|
output: typeof entry.output === "string" ? entry.output : "",
|
|
584
614
|
images: Array.isArray(entry.images)
|
|
585
615
|
? entry.images.map((image, imageIndex) => normalizeTraceImage(image, imageIndex)).filter(Boolean)
|
|
@@ -860,8 +890,9 @@
|
|
|
860
890
|
? ("Tool: " + String(entry.toolName || "tool") + " — " + entry.label)
|
|
861
891
|
: ("Tool: " + String(entry.toolName || "tool"));
|
|
862
892
|
const parts = [header];
|
|
863
|
-
|
|
864
|
-
|
|
893
|
+
const inputText = String(entry.args || entry.argsSummary || "").trim();
|
|
894
|
+
if (inputText) {
|
|
895
|
+
parts.push("Input:\n" + inputText);
|
|
865
896
|
}
|
|
866
897
|
if (String(entry.output || "").trim()) {
|
|
867
898
|
parts.push("Output:\n" + String(entry.output || "").trim());
|
|
@@ -2425,11 +2456,7 @@
|
|
|
2425
2456
|
rightTitleGroupEl.appendChild(rightFocusBtn);
|
|
2426
2457
|
rightTitleGroupEl.appendChild(makeStudioUiRefreshSeparator());
|
|
2427
2458
|
}
|
|
2428
|
-
|
|
2429
|
-
rightTitleGroupEl.appendChild(makeStudioUiRefreshElement("span", "studio-refresh-static-title", "Editor (Preview)"));
|
|
2430
|
-
} else {
|
|
2431
|
-
rightTitleGroupEl.appendChild(rightViewSelect);
|
|
2432
|
-
}
|
|
2459
|
+
rightTitleGroupEl.appendChild(rightViewSelect);
|
|
2433
2460
|
rightIdentityEl.appendChild(rightTitleGroupEl);
|
|
2434
2461
|
const rightToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
|
|
2435
2462
|
if (exportPreviewControlsEl) {
|
|
@@ -2453,8 +2480,8 @@
|
|
|
2453
2480
|
if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
|
|
2454
2481
|
const replActionLineEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line repl-action-line");
|
|
2455
2482
|
replActionLineEl.hidden = true;
|
|
2456
|
-
if (
|
|
2457
|
-
if (
|
|
2483
|
+
if (sendReplBtn) replActionLineEl.appendChild(sendReplBtn);
|
|
2484
|
+
if (replSendModeSelect) replActionLineEl.appendChild(replSendModeSelect);
|
|
2458
2485
|
if (actionLineOneEl.childNodes.length > 0) actionsEl.appendChild(actionLineOneEl);
|
|
2459
2486
|
actionsEl.appendChild(actionLineTwoEl);
|
|
2460
2487
|
if (replActionLineEl.childNodes.length > 0) actionsEl.appendChild(replActionLineEl);
|
|
@@ -2609,6 +2636,9 @@
|
|
|
2609
2636
|
}
|
|
2610
2637
|
|
|
2611
2638
|
function getIdleStatus() {
|
|
2639
|
+
if (isEditorOnlyMode) {
|
|
2640
|
+
return "Editor-only mode: edit, browse files, annotate, preview, save, suggest, refresh file-backed text, or send to a REPL.";
|
|
2641
|
+
}
|
|
2612
2642
|
return "Edit, load, or annotate text, then run, save, send to pi editor, or critique.";
|
|
2613
2643
|
}
|
|
2614
2644
|
|
|
@@ -3176,7 +3206,7 @@
|
|
|
3176
3206
|
|
|
3177
3207
|
function updateSourceBadge() {
|
|
3178
3208
|
const label = sourceState && sourceState.label ? sourceState.label : "blank";
|
|
3179
|
-
sourceBadgeEl.textContent = (studioUiRefreshEnabled ? "Origin: " : "Editor origin: ") + label;
|
|
3209
|
+
sourceBadgeEl.textContent = (studioUiRefreshEnabled ? "Origin: " : "Editor origin: ") + label + (hasRefreshableFilePath() ? " · file" : "");
|
|
3180
3210
|
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
3181
3211
|
if (sourceBadgeEl) {
|
|
3182
3212
|
sourceBadgeEl.title = descriptor.fileBacked
|
|
@@ -3188,9 +3218,11 @@
|
|
|
3188
3218
|
if (isFileBacked) {
|
|
3189
3219
|
var fileBackedResourceDir = getCurrentResourceDirValue() || dirnameForDisplayPath(sourceState.path);
|
|
3190
3220
|
if (resourceDirInput) resourceDirInput.value = fileBackedResourceDir;
|
|
3191
|
-
if (resourceDirLabel)
|
|
3221
|
+
if (resourceDirLabel) {
|
|
3222
|
+
resourceDirLabel.textContent = fileBackedResourceDir ? ("Resource dir: " + fileBackedResourceDir) : "Resource dir: file directory";
|
|
3223
|
+
resourceDirLabel.hidden = false;
|
|
3224
|
+
}
|
|
3192
3225
|
if (resourceDirBtn) resourceDirBtn.hidden = true;
|
|
3193
|
-
if (resourceDirLabel) resourceDirLabel.hidden = true;
|
|
3194
3226
|
if (resourceDirInputWrap) resourceDirInputWrap.classList.remove("visible");
|
|
3195
3227
|
} else {
|
|
3196
3228
|
// Restore to label if dir is set, otherwise show button
|
|
@@ -3523,7 +3555,7 @@
|
|
|
3523
3555
|
|
|
3524
3556
|
function cycleActivePaneView(direction) {
|
|
3525
3557
|
if (activePane === "right") {
|
|
3526
|
-
if (
|
|
3558
|
+
if (!rightViewSelect || rightViewSelect.disabled) {
|
|
3527
3559
|
setStatus("The right-pane view selector is unavailable.", "warning");
|
|
3528
3560
|
return;
|
|
3529
3561
|
}
|
|
@@ -3879,7 +3911,6 @@
|
|
|
3879
3911
|
&& !event.altKey
|
|
3880
3912
|
&& event.shiftKey
|
|
3881
3913
|
&& activePane === "left"
|
|
3882
|
-
&& !isEditorOnlyMode
|
|
3883
3914
|
&& rightView === "repl"
|
|
3884
3915
|
) {
|
|
3885
3916
|
event.preventDefault();
|
|
@@ -4480,8 +4511,8 @@
|
|
|
4480
4511
|
return lines.join("\n");
|
|
4481
4512
|
}
|
|
4482
4513
|
|
|
4483
|
-
function renderDelimitedTextPreview(targetEl, text, pane) {
|
|
4484
|
-
const html = buildDelimitedTextPreviewHtml(text, editorLanguage || "");
|
|
4514
|
+
function renderDelimitedTextPreview(targetEl, text, pane, language) {
|
|
4515
|
+
const html = buildDelimitedTextPreviewHtml(text, language || editorLanguage || "");
|
|
4485
4516
|
if (!html || !targetEl) return false;
|
|
4486
4517
|
if (pane === "source") {
|
|
4487
4518
|
sourcePreviewRenderNonce += 1;
|
|
@@ -4946,6 +4977,132 @@
|
|
|
4946
4977
|
+ " });\n"
|
|
4947
4978
|
+ " scheduleHeight();\n"
|
|
4948
4979
|
+ " }\n"
|
|
4980
|
+
+ " let htmlCommentMode = false;\n"
|
|
4981
|
+
+ " let htmlCommentHoverEl = null;\n"
|
|
4982
|
+
+ " let htmlCommentHighlightTimer = null;\n"
|
|
4983
|
+
+ " let htmlCommentLastPostAt = 0;\n"
|
|
4984
|
+
+ " function htmlCommentCssEscape(value) {\n"
|
|
4985
|
+
+ " const text = String(value || '');\n"
|
|
4986
|
+
+ " try { if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text); } catch {}\n"
|
|
4987
|
+
+ " return text.replace(/[^A-Za-z0-9_-]/g, function(ch) { return '\\\\' + ch; });\n"
|
|
4988
|
+
+ " }\n"
|
|
4989
|
+
+ " function getHtmlCommentSelector(element) {\n"
|
|
4990
|
+
+ " if (!element || element.nodeType !== 1) return '';\n"
|
|
4991
|
+
+ " if (element.id) return '#' + htmlCommentCssEscape(element.id);\n"
|
|
4992
|
+
+ " const parts = [];\n"
|
|
4993
|
+
+ " let el = element;\n"
|
|
4994
|
+
+ " while (el && el.nodeType === 1 && el !== document.documentElement) {\n"
|
|
4995
|
+
+ " const tag = el.tagName ? el.tagName.toLowerCase() : '';\n"
|
|
4996
|
+
+ " if (!tag) break;\n"
|
|
4997
|
+
+ " if (el.id) { parts.unshift(tag + '#' + htmlCommentCssEscape(el.id)); break; }\n"
|
|
4998
|
+
+ " let index = 1;\n"
|
|
4999
|
+
+ " let sibling = el.previousElementSibling;\n"
|
|
5000
|
+
+ " while (sibling) { if ((sibling.tagName || '').toLowerCase() === tag) index += 1; sibling = sibling.previousElementSibling; }\n"
|
|
5001
|
+
+ " parts.unshift(tag + ':nth-of-type(' + index + ')');\n"
|
|
5002
|
+
+ " if (tag === 'body') break;\n"
|
|
5003
|
+
+ " el = el.parentElement;\n"
|
|
5004
|
+
+ " }\n"
|
|
5005
|
+
+ " return parts.join(' > ');\n"
|
|
5006
|
+
+ " }\n"
|
|
5007
|
+
+ " function normalizeHtmlCommentText(value, maxLength) {\n"
|
|
5008
|
+
+ " const text = String(value || '').replace(/\\s+/g, ' ').trim();\n"
|
|
5009
|
+
+ " const limit = Math.max(24, Number(maxLength) || 200);\n"
|
|
5010
|
+
+ " return text.length > limit ? text.slice(0, limit - 1).trimEnd() + '…' : text;\n"
|
|
5011
|
+
+ " }\n"
|
|
5012
|
+
+ " function getHtmlCommentElementLabel(element) {\n"
|
|
5013
|
+
+ " if (!element || element.nodeType !== 1) return '';\n"
|
|
5014
|
+
+ " const attrText = element.getAttribute('aria-label') || element.getAttribute('alt') || element.getAttribute('title') || '';\n"
|
|
5015
|
+
+ " if (attrText) return normalizeHtmlCommentText(attrText, 220);\n"
|
|
5016
|
+
+ " const tag = (element.tagName || '').toLowerCase();\n"
|
|
5017
|
+
+ " if (tag === 'img') {\n"
|
|
5018
|
+
+ " const src = String(element.getAttribute('src') || '').split(/[?#]/)[0].split('/').pop() || 'image';\n"
|
|
5019
|
+
+ " return normalizeHtmlCommentText(src, 220);\n"
|
|
5020
|
+
+ " }\n"
|
|
5021
|
+
+ " return normalizeHtmlCommentText(element.innerText || element.textContent || '', 220);\n"
|
|
5022
|
+
+ " }\n"
|
|
5023
|
+
+ " function getHtmlCommentTarget(target) {\n"
|
|
5024
|
+
+ " let node = target;\n"
|
|
5025
|
+
+ " if (node && node.nodeType === 3) node = node.parentElement;\n"
|
|
5026
|
+
+ " if (!node || node.nodeType !== 1) return document.body || document.documentElement;\n"
|
|
5027
|
+
+ " if (typeof node.closest === 'function') {\n"
|
|
5028
|
+
+ " return node.closest('img,figure,table,section,article,main,aside,nav,header,footer,pre,blockquote,ul,ol,li,canvas,svg,h1,h2,h3,h4,h5,h6,p,button,a,input,textarea,select,div') || node;\n"
|
|
5029
|
+
+ " }\n"
|
|
5030
|
+
+ " return node;\n"
|
|
5031
|
+
+ " }\n"
|
|
5032
|
+
+ " function getHtmlCommentSelectionText() {\n"
|
|
5033
|
+
+ " const selection = typeof window.getSelection === 'function' ? window.getSelection() : null;\n"
|
|
5034
|
+
+ " if (!selection || selection.rangeCount <= 0 || selection.isCollapsed) return '';\n"
|
|
5035
|
+
+ " return normalizeHtmlCommentText(selection.toString(), 1000);\n"
|
|
5036
|
+
+ " }\n"
|
|
5037
|
+
+ " function getHtmlCommentSelectionElement() {\n"
|
|
5038
|
+
+ " const selection = typeof window.getSelection === 'function' ? window.getSelection() : null;\n"
|
|
5039
|
+
+ " if (!selection || selection.rangeCount <= 0) return null;\n"
|
|
5040
|
+
+ " const range = selection.getRangeAt(0);\n"
|
|
5041
|
+
+ " let node = range.commonAncestorContainer;\n"
|
|
5042
|
+
+ " if (node && node.nodeType === 3) node = node.parentElement;\n"
|
|
5043
|
+
+ " return node && node.nodeType === 1 ? node : null;\n"
|
|
5044
|
+
+ " }\n"
|
|
5045
|
+
+ " function postHtmlCommentTarget(kind, element, event, selectedText) {\n"
|
|
5046
|
+
+ " const target = getHtmlCommentTarget(element || (event && event.target));\n"
|
|
5047
|
+
+ " if (!target) return false;\n"
|
|
5048
|
+
+ " htmlCommentLastPostAt = Date.now();\n"
|
|
5049
|
+
+ " try {\n"
|
|
5050
|
+
+ " parent.postMessage({\n"
|
|
5051
|
+
+ " type: 'pi-studio-html-artifact-comment-target',\n"
|
|
5052
|
+
+ " id: PREVIEW_ID,\n"
|
|
5053
|
+
+ " kind: kind === 'selection' ? 'selection' : 'element',\n"
|
|
5054
|
+
+ " selector: getHtmlCommentSelector(target),\n"
|
|
5055
|
+
+ " tag: (target.tagName || '').toLowerCase(),\n"
|
|
5056
|
+
+ " label: getHtmlCommentElementLabel(target),\n"
|
|
5057
|
+
+ " text: normalizeHtmlCommentText(selectedText || '', 1000),\n"
|
|
5058
|
+
+ " clientX: event && event.clientX || 0,\n"
|
|
5059
|
+
+ " clientY: event && event.clientY || 0\n"
|
|
5060
|
+
+ " }, '*');\n"
|
|
5061
|
+
+ " return true;\n"
|
|
5062
|
+
+ " } catch { return false; }\n"
|
|
5063
|
+
+ " }\n"
|
|
5064
|
+
+ " function clearHtmlCommentHover() {\n"
|
|
5065
|
+
+ " if (htmlCommentHoverEl && htmlCommentHoverEl.classList) htmlCommentHoverEl.classList.remove('pi-studio-html-comment-hover');\n"
|
|
5066
|
+
+ " htmlCommentHoverEl = null;\n"
|
|
5067
|
+
+ " }\n"
|
|
5068
|
+
+ " function setHtmlCommentMode(enabled) {\n"
|
|
5069
|
+
+ " htmlCommentMode = Boolean(enabled);\n"
|
|
5070
|
+
+ " if (document.documentElement && document.documentElement.classList) document.documentElement.classList.toggle('pi-studio-html-comment-mode', htmlCommentMode);\n"
|
|
5071
|
+
+ " if (!htmlCommentMode) clearHtmlCommentHover();\n"
|
|
5072
|
+
+ " }\n"
|
|
5073
|
+
+ " function handleHtmlCommentMouseMove(event) {\n"
|
|
5074
|
+
+ " if (!htmlCommentMode) return;\n"
|
|
5075
|
+
+ " const target = getHtmlCommentTarget(event && event.target);\n"
|
|
5076
|
+
+ " if (target === htmlCommentHoverEl) return;\n"
|
|
5077
|
+
+ " clearHtmlCommentHover();\n"
|
|
5078
|
+
+ " htmlCommentHoverEl = target;\n"
|
|
5079
|
+
+ " if (htmlCommentHoverEl && htmlCommentHoverEl.classList) htmlCommentHoverEl.classList.add('pi-studio-html-comment-hover');\n"
|
|
5080
|
+
+ " }\n"
|
|
5081
|
+
+ " function handleHtmlCommentMouseUp(event) {\n"
|
|
5082
|
+
+ " if (!htmlCommentMode) return;\n"
|
|
5083
|
+
+ " const selectedText = getHtmlCommentSelectionText();\n"
|
|
5084
|
+
+ " if (!selectedText) return;\n"
|
|
5085
|
+
+ " postHtmlCommentTarget('selection', getHtmlCommentSelectionElement() || (event && event.target), event, selectedText);\n"
|
|
5086
|
+
+ " if (event) { event.preventDefault(); event.stopPropagation(); }\n"
|
|
5087
|
+
+ " }\n"
|
|
5088
|
+
+ " function handleHtmlCommentClick(event) {\n"
|
|
5089
|
+
+ " if (!htmlCommentMode) return;\n"
|
|
5090
|
+
+ " if (Date.now() - htmlCommentLastPostAt < 450) { event.preventDefault(); event.stopPropagation(); return; }\n"
|
|
5091
|
+
+ " postHtmlCommentTarget('element', event && event.target, event, '');\n"
|
|
5092
|
+
+ " event.preventDefault();\n"
|
|
5093
|
+
+ " event.stopPropagation();\n"
|
|
5094
|
+
+ " }\n"
|
|
5095
|
+
+ " function highlightHtmlCommentTarget(selector, anchorKind) {\n"
|
|
5096
|
+
+ " if (htmlCommentHighlightTimer) { clearTimeout(htmlCommentHighlightTimer); htmlCommentHighlightTimer = null; }\n"
|
|
5097
|
+
+ " Array.prototype.slice.call(document.querySelectorAll('.pi-studio-html-comment-highlight')).forEach(function(el) { el.classList.remove('pi-studio-html-comment-highlight'); });\n"
|
|
5098
|
+
+ " if (anchorKind === 'html-page' || !selector) { try { window.scrollTo({ top: 0, behavior: 'smooth' }); } catch { window.scrollTo(0, 0); } return; }\n"
|
|
5099
|
+
+ " let target = null;\n"
|
|
5100
|
+
+ " try { target = document.querySelector(String(selector || '')); } catch {}\n"
|
|
5101
|
+
+ " if (!target) return;\n"
|
|
5102
|
+
+ " try { target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' }); } catch { try { target.scrollIntoView(true); } catch {} }\n"
|
|
5103
|
+
+ " if (target.classList) target.classList.add('pi-studio-html-comment-highlight');\n"
|
|
5104
|
+
+ " htmlCommentHighlightTimer = setTimeout(function() { if (target && target.classList) target.classList.remove('pi-studio-html-comment-highlight'); }, 2400);\n"
|
|
5105
|
+
+ " }\n"
|
|
4949
5106
|
+ " window.addEventListener('message', (event) => {\n"
|
|
4950
5107
|
+ " const data = event && event.data;\n"
|
|
4951
5108
|
+ " if (!data || typeof data !== 'object' || data.id !== PREVIEW_ID) return;\n"
|
|
@@ -4959,11 +5116,23 @@
|
|
|
4959
5116
|
+ " }\n"
|
|
4960
5117
|
+ " if (data.type === 'pi-studio-html-artifact-resources-resolved') {\n"
|
|
4961
5118
|
+ " applyResolvedHtmlPreviewResources(data.results);\n"
|
|
5119
|
+
+ " return;\n"
|
|
5120
|
+
+ " }\n"
|
|
5121
|
+
+ " if (data.type === 'pi-studio-html-artifact-comment-mode') {\n"
|
|
5122
|
+
+ " setHtmlCommentMode(data.enabled);\n"
|
|
5123
|
+
+ " return;\n"
|
|
5124
|
+
+ " }\n"
|
|
5125
|
+
+ " if (data.type === 'pi-studio-html-artifact-highlight-comment') {\n"
|
|
5126
|
+
+ " highlightHtmlCommentTarget(data.selector, data.anchorKind);\n"
|
|
4962
5127
|
+ " }\n"
|
|
4963
5128
|
+ " });\n"
|
|
4964
5129
|
+ " document.addEventListener('click', handleFragmentAnchorClick);\n"
|
|
4965
5130
|
+ " document.addEventListener('click', handleHtmlPreviewLocalLinkClick);\n"
|
|
4966
5131
|
+ " document.addEventListener('contextmenu', handleHtmlPreviewLocalLinkContextMenu);\n"
|
|
5132
|
+
+ " document.addEventListener('mousemove', handleHtmlCommentMouseMove, true);\n"
|
|
5133
|
+
+ " document.addEventListener('mouseleave', clearHtmlCommentHover, true);\n"
|
|
5134
|
+
+ " document.addEventListener('mouseup', handleHtmlCommentMouseUp, true);\n"
|
|
5135
|
+
+ " document.addEventListener('click', handleHtmlCommentClick, true);\n"
|
|
4967
5136
|
+ " document.addEventListener('DOMContentLoaded', () => { scheduleHtmlMathRenderScan(); scheduleHtmlPreviewResourceScan(); });\n"
|
|
4968
5137
|
+ " window.addEventListener('hashchange', () => {\n"
|
|
4969
5138
|
+ " const hash = String(window.location && window.location.hash || '');\n"
|
|
@@ -4995,6 +5164,9 @@
|
|
|
4995
5164
|
+ ".pi-studio-html-math-display{display:block;margin:0.75em 0;overflow-x:auto;text-align:center;}\n"
|
|
4996
5165
|
+ ".pi-studio-html-math-display>math{display:block;margin:0 auto;}\n"
|
|
4997
5166
|
+ ".pi-studio-html-math-inline>math{vertical-align:-0.15em;}\n"
|
|
5167
|
+
+ "html.pi-studio-html-comment-mode,html.pi-studio-html-comment-mode body{cursor:crosshair!important;}\n"
|
|
5168
|
+
+ ".pi-studio-html-comment-hover{outline:2px solid #0f8b8d!important;outline-offset:3px!important;}\n"
|
|
5169
|
+
+ ".pi-studio-html-comment-highlight{outline:3px solid #d97706!important;outline-offset:4px!important;box-shadow:0 0 0 6px rgba(217,119,6,.18)!important;}\n"
|
|
4998
5170
|
+ "</style>\n";
|
|
4999
5171
|
}
|
|
5000
5172
|
|
|
@@ -5029,6 +5201,11 @@
|
|
|
5029
5201
|
});
|
|
5030
5202
|
}
|
|
5031
5203
|
|
|
5204
|
+
function setHtmlArtifactDetailText(record, text) {
|
|
5205
|
+
if (!record || !record.detail) return;
|
|
5206
|
+
record.detail.textContent = record.commentMode ? "HTML preview · comment mode" : (text || "HTML preview");
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5032
5209
|
function handleHtmlArtifactFrameSizeMessage(event) {
|
|
5033
5210
|
const data = event && event.data;
|
|
5034
5211
|
if (!data || typeof data !== "object" || data.type !== "pi-studio-html-artifact-size") return;
|
|
@@ -5040,7 +5217,7 @@
|
|
|
5040
5217
|
}
|
|
5041
5218
|
if (event.source && record.iframe.contentWindow && event.source !== record.iframe.contentWindow) return;
|
|
5042
5219
|
if (record.shell && record.shell.classList && record.shell.classList.contains("is-focused")) {
|
|
5043
|
-
|
|
5220
|
+
setHtmlArtifactDetailText(record, "HTML preview");
|
|
5044
5221
|
return;
|
|
5045
5222
|
}
|
|
5046
5223
|
const rawHeight = Number(data.height);
|
|
@@ -5057,9 +5234,7 @@
|
|
|
5057
5234
|
record.shell.style.minHeight = "0";
|
|
5058
5235
|
record.shell.classList.toggle("is-height-capped", capped);
|
|
5059
5236
|
}
|
|
5060
|
-
|
|
5061
|
-
record.detail.textContent = "HTML preview";
|
|
5062
|
-
}
|
|
5237
|
+
setHtmlArtifactDetailText(record, "HTML preview");
|
|
5063
5238
|
}
|
|
5064
5239
|
|
|
5065
5240
|
function handleHtmlArtifactFrameFragmentMessage(event) {
|
|
@@ -5183,7 +5358,7 @@
|
|
|
5183
5358
|
error: error && error.message ? error.message : String(error || "HTML preview math render failed."),
|
|
5184
5359
|
})));
|
|
5185
5360
|
} finally {
|
|
5186
|
-
|
|
5361
|
+
setHtmlArtifactDetailText(record, "HTML preview");
|
|
5187
5362
|
}
|
|
5188
5363
|
}
|
|
5189
5364
|
|
|
@@ -5273,7 +5448,7 @@
|
|
|
5273
5448
|
if (record.detail) record.detail.textContent = "HTML preview · loading local images";
|
|
5274
5449
|
const results = await Promise.all(items.map((item) => fetchHtmlArtifactResource(record, item)));
|
|
5275
5450
|
postHtmlArtifactResourceResults(record, results);
|
|
5276
|
-
|
|
5451
|
+
setHtmlArtifactDetailText(record, "HTML preview");
|
|
5277
5452
|
}
|
|
5278
5453
|
|
|
5279
5454
|
function handleHtmlArtifactFrameResourceMessage(event) {
|
|
@@ -5356,11 +5531,36 @@
|
|
|
5356
5531
|
setStatus("Right-click this local HTML preview link for file actions.", "warning");
|
|
5357
5532
|
}
|
|
5358
5533
|
|
|
5534
|
+
function handleHtmlArtifactFrameCommentTargetMessage(event) {
|
|
5535
|
+
const data = event && event.data;
|
|
5536
|
+
if (!data || typeof data !== "object" || data.type !== "pi-studio-html-artifact-comment-target") return;
|
|
5537
|
+
const id = typeof data.id === "string" ? data.id : "";
|
|
5538
|
+
const record = id ? htmlArtifactFramesById.get(id) : null;
|
|
5539
|
+
if (!record || !record.iframe || !record.iframe.isConnected) {
|
|
5540
|
+
if (id) htmlArtifactFramesById.delete(id);
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5543
|
+
if (!record.commentable) return;
|
|
5544
|
+
if (event.source && record.iframe.contentWindow && event.source !== record.iframe.contentWindow) return;
|
|
5545
|
+
const note = addReviewNoteFromHtmlArtifactTarget(record, data);
|
|
5546
|
+
if (note && record.iframe && record.iframe.contentWindow) {
|
|
5547
|
+
try {
|
|
5548
|
+
record.iframe.contentWindow.postMessage({
|
|
5549
|
+
type: "pi-studio-html-artifact-highlight-comment",
|
|
5550
|
+
id: record.id || "",
|
|
5551
|
+
selector: note.htmlSelector || "",
|
|
5552
|
+
anchorKind: note.anchorKind || "html-element",
|
|
5553
|
+
}, "*");
|
|
5554
|
+
} catch {}
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5359
5558
|
window.addEventListener("message", handleHtmlArtifactFrameSizeMessage);
|
|
5360
5559
|
window.addEventListener("message", handleHtmlArtifactFrameFragmentMessage);
|
|
5361
5560
|
window.addEventListener("message", handleHtmlArtifactFrameMathRenderMessage);
|
|
5362
5561
|
window.addEventListener("message", handleHtmlArtifactFrameResourceMessage);
|
|
5363
5562
|
window.addEventListener("message", handleHtmlArtifactFrameLocalLinkMessage);
|
|
5563
|
+
window.addEventListener("message", handleHtmlArtifactFrameCommentTargetMessage);
|
|
5364
5564
|
|
|
5365
5565
|
function isStudioHtmlFocusOpen() {
|
|
5366
5566
|
return Boolean(studioHtmlFocusOverlayEl && studioHtmlFocusOverlayEl.hidden === false && studioHtmlFocusShellEl);
|
|
@@ -5588,6 +5788,36 @@
|
|
|
5588
5788
|
|
|
5589
5789
|
const tools = document.createElement("span");
|
|
5590
5790
|
tools.className = "studio-html-artifact-tools";
|
|
5791
|
+
const commentable = Boolean(options && options.commentable);
|
|
5792
|
+
|
|
5793
|
+
let commentBtn = null;
|
|
5794
|
+
let pageCommentBtn = null;
|
|
5795
|
+
const makeCommentButton = (text, title, onClick) => {
|
|
5796
|
+
const button = document.createElement("button");
|
|
5797
|
+
button.type = "button";
|
|
5798
|
+
button.className = "studio-html-artifact-comment-btn";
|
|
5799
|
+
button.textContent = text;
|
|
5800
|
+
button.title = title;
|
|
5801
|
+
button.addEventListener("pointerdown", (event) => { event.stopPropagation(); });
|
|
5802
|
+
button.addEventListener("mousedown", (event) => { event.stopPropagation(); });
|
|
5803
|
+
button.addEventListener("click", (event) => {
|
|
5804
|
+
event.preventDefault();
|
|
5805
|
+
event.stopPropagation();
|
|
5806
|
+
onClick();
|
|
5807
|
+
});
|
|
5808
|
+
return button;
|
|
5809
|
+
};
|
|
5810
|
+
if (commentable) {
|
|
5811
|
+
commentBtn = makeCommentButton("Comment mode", "Turn on HTML preview comment mode. Select text or click an element in the preview to add a local comment.", () => {
|
|
5812
|
+
const record = htmlArtifactFramesById.get(previewId);
|
|
5813
|
+
setHtmlArtifactRecordCommentMode(record, !(record && record.commentMode));
|
|
5814
|
+
});
|
|
5815
|
+
commentBtn.setAttribute("aria-pressed", "false");
|
|
5816
|
+
pageCommentBtn = makeCommentButton("Page", "Add a page-level local comment for this HTML preview.", () => {
|
|
5817
|
+
const record = htmlArtifactFramesById.get(previewId);
|
|
5818
|
+
addReviewNoteFromHtmlArtifactPage(record || null);
|
|
5819
|
+
});
|
|
5820
|
+
}
|
|
5591
5821
|
|
|
5592
5822
|
const zoomControls = document.createElement("span");
|
|
5593
5823
|
zoomControls.className = "studio-html-artifact-zoom-controls";
|
|
@@ -5651,6 +5881,8 @@
|
|
|
5651
5881
|
fullscreenBtn.appendChild(makeStudioUiRefreshIcon("fullscreen"));
|
|
5652
5882
|
updateZoomUi();
|
|
5653
5883
|
tools.appendChild(detail);
|
|
5884
|
+
if (commentBtn) tools.appendChild(commentBtn);
|
|
5885
|
+
if (pageCommentBtn) tools.appendChild(pageCommentBtn);
|
|
5654
5886
|
tools.appendChild(zoomControls);
|
|
5655
5887
|
tools.appendChild(fullscreenBtn);
|
|
5656
5888
|
|
|
@@ -5665,7 +5897,7 @@
|
|
|
5665
5897
|
iframe.referrerPolicy = "no-referrer";
|
|
5666
5898
|
iframe.setAttribute("sandbox", "allow-scripts allow-modals");
|
|
5667
5899
|
iframe.setAttribute("allow", "clipboard-write");
|
|
5668
|
-
iframe.addEventListener("load", () => { postArtifactZoom(); });
|
|
5900
|
+
iframe.addEventListener("load", () => { postArtifactZoom(); postHtmlArtifactCommentMode(htmlArtifactFramesById.get(previewId)); });
|
|
5669
5901
|
iframe.srcdoc = buildHtmlArtifactSrcdoc(html, previewId);
|
|
5670
5902
|
shell.appendChild(iframe);
|
|
5671
5903
|
htmlArtifactFramesById.set(previewId, {
|
|
@@ -5674,6 +5906,11 @@
|
|
|
5674
5906
|
shell,
|
|
5675
5907
|
detail,
|
|
5676
5908
|
zoomControls,
|
|
5909
|
+
commentBtn,
|
|
5910
|
+
pageCommentBtn,
|
|
5911
|
+
commentMode: false,
|
|
5912
|
+
commentable,
|
|
5913
|
+
title,
|
|
5677
5914
|
sourcePath: options && options.sourcePath ? String(options.sourcePath) : "",
|
|
5678
5915
|
resourceDir: options && options.resourceDir ? String(options.resourceDir) : "",
|
|
5679
5916
|
mathRenderBatchCount: 0,
|
|
@@ -5690,6 +5927,33 @@
|
|
|
5690
5927
|
}
|
|
5691
5928
|
}
|
|
5692
5929
|
|
|
5930
|
+
function postHtmlArtifactCommentMode(record) {
|
|
5931
|
+
if (!record || !record.iframe || !record.iframe.contentWindow) return;
|
|
5932
|
+
try {
|
|
5933
|
+
record.iframe.contentWindow.postMessage({
|
|
5934
|
+
type: "pi-studio-html-artifact-comment-mode",
|
|
5935
|
+
id: record.id || "",
|
|
5936
|
+
enabled: Boolean(record.commentMode),
|
|
5937
|
+
}, "*");
|
|
5938
|
+
} catch {}
|
|
5939
|
+
}
|
|
5940
|
+
|
|
5941
|
+
function setHtmlArtifactRecordCommentMode(record, enabled) {
|
|
5942
|
+
if (!record) return;
|
|
5943
|
+
record.commentMode = Boolean(enabled);
|
|
5944
|
+
if (record.shell && record.shell.classList) record.shell.classList.toggle("is-comment-mode", record.commentMode);
|
|
5945
|
+
if (record.commentBtn) {
|
|
5946
|
+
record.commentBtn.classList.toggle("is-active", record.commentMode);
|
|
5947
|
+
record.commentBtn.setAttribute("aria-pressed", record.commentMode ? "true" : "false");
|
|
5948
|
+
record.commentBtn.textContent = "Comment mode";
|
|
5949
|
+
record.commentBtn.title = record.commentMode
|
|
5950
|
+
? "HTML comment mode is on. Select text or click an element in the preview to add a local comment."
|
|
5951
|
+
: "Turn on HTML preview comment mode. Select text or click an element in the preview to add a local comment.";
|
|
5952
|
+
}
|
|
5953
|
+
if (record.detail) record.detail.textContent = record.commentMode ? "HTML preview · comment mode" : "HTML preview";
|
|
5954
|
+
postHtmlArtifactCommentMode(record);
|
|
5955
|
+
}
|
|
5956
|
+
|
|
5693
5957
|
function getRightPaneHtmlArtifactSource() {
|
|
5694
5958
|
if (rightView === "editor-preview") {
|
|
5695
5959
|
const editorText = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
@@ -5807,6 +6071,15 @@
|
|
|
5807
6071
|
openLink.textContent = "Open PDF";
|
|
5808
6072
|
actions.appendChild(openLink);
|
|
5809
6073
|
|
|
6074
|
+
const refreshBtn = document.createElement("button");
|
|
6075
|
+
refreshBtn.type = "button";
|
|
6076
|
+
refreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-refresh";
|
|
6077
|
+
refreshBtn.textContent = "Refresh";
|
|
6078
|
+
refreshBtn.title = "Reload this PDF preview from disk.";
|
|
6079
|
+
refreshBtn.setAttribute("aria-label", "Refresh PDF preview from disk");
|
|
6080
|
+
refreshBtn.addEventListener("click", () => refreshStudioPdfFocusViewer());
|
|
6081
|
+
actions.appendChild(refreshBtn);
|
|
6082
|
+
|
|
5810
6083
|
const fullscreenBtn = document.createElement("button");
|
|
5811
6084
|
fullscreenBtn.type = "button";
|
|
5812
6085
|
fullscreenBtn.className = "studio-pdf-focus-btn studio-pdf-focus-fullscreen";
|
|
@@ -5928,6 +6201,70 @@
|
|
|
5928
6201
|
return "/pdf-resource?" + params.toString();
|
|
5929
6202
|
}
|
|
5930
6203
|
|
|
6204
|
+
function buildRefreshedStudioPdfViewerUrl(value) {
|
|
6205
|
+
const raw = String(value || "").trim();
|
|
6206
|
+
if (!raw) return "";
|
|
6207
|
+
const hashIndex = raw.indexOf("#");
|
|
6208
|
+
const base = hashIndex >= 0 ? raw.slice(0, hashIndex) : raw;
|
|
6209
|
+
const hash = hashIndex >= 0 ? raw.slice(hashIndex) : "";
|
|
6210
|
+
const nonce = Date.now().toString(36);
|
|
6211
|
+
try {
|
|
6212
|
+
const url = new URL(base || window.location.href, window.location.href);
|
|
6213
|
+
url.searchParams.set("_studioPdfRefresh", nonce);
|
|
6214
|
+
return url.href + hash;
|
|
6215
|
+
} catch {
|
|
6216
|
+
const separator = base.indexOf("?") >= 0 ? "&" : "?";
|
|
6217
|
+
return base + separator + "_studioPdfRefresh=" + encodeURIComponent(nonce) + hash;
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
|
|
6221
|
+
function syncStudioPdfCardViewerUrl(card, viewerUrl) {
|
|
6222
|
+
if (!card) return;
|
|
6223
|
+
const nextUrl = String(viewerUrl || "").trim();
|
|
6224
|
+
if (!nextUrl) return;
|
|
6225
|
+
if (card.dataset) card.dataset.studioPdfViewerUrl = nextUrl;
|
|
6226
|
+
const frame = typeof card.querySelector === "function" ? card.querySelector("iframe.studio-pdf-frame") : null;
|
|
6227
|
+
if (frame) frame.src = nextUrl;
|
|
6228
|
+
const openLink = typeof card.querySelector === "function" ? card.querySelector("a.studio-pdf-card-link") : null;
|
|
6229
|
+
if (openLink) openLink.href = nextUrl;
|
|
6230
|
+
const focusBtn = typeof card.querySelector === "function" ? card.querySelector("button.studio-pdf-card-focus") : null;
|
|
6231
|
+
if (focusBtn && focusBtn.dataset) focusBtn.dataset.studioPdfViewerUrl = nextUrl;
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
function refreshStudioPdfCard(card) {
|
|
6235
|
+
if (!card) return false;
|
|
6236
|
+
const frame = typeof card.querySelector === "function" ? card.querySelector("iframe.studio-pdf-frame") : null;
|
|
6237
|
+
const currentUrl = String(card.dataset && card.dataset.studioPdfViewerUrl ? card.dataset.studioPdfViewerUrl : "").trim()
|
|
6238
|
+
|| String(frame && frame.src ? frame.src : "").trim();
|
|
6239
|
+
const nextUrl = buildRefreshedStudioPdfViewerUrl(currentUrl);
|
|
6240
|
+
if (!nextUrl) return false;
|
|
6241
|
+
syncStudioPdfCardViewerUrl(card, nextUrl);
|
|
6242
|
+
setStatus("Refreshed PDF preview from disk.", "success");
|
|
6243
|
+
return true;
|
|
6244
|
+
}
|
|
6245
|
+
|
|
6246
|
+
function getStudioPdfFocusActiveFrame() {
|
|
6247
|
+
if (studioPdfFocusMovedFrameState && studioPdfFocusMovedFrameState.frame && studioPdfFocusMovedFrameState.frame.isConnected) {
|
|
6248
|
+
return studioPdfFocusMovedFrameState.frame;
|
|
6249
|
+
}
|
|
6250
|
+
return studioPdfFocusFrameEl;
|
|
6251
|
+
}
|
|
6252
|
+
|
|
6253
|
+
function refreshStudioPdfFocusViewer() {
|
|
6254
|
+
const frame = getStudioPdfFocusActiveFrame();
|
|
6255
|
+
const currentUrl = String(frame && frame.src ? frame.src : "").trim()
|
|
6256
|
+
|| String(studioPdfFocusOpenLinkEl && studioPdfFocusOpenLinkEl.href ? studioPdfFocusOpenLinkEl.href : "").trim();
|
|
6257
|
+
const nextUrl = buildRefreshedStudioPdfViewerUrl(currentUrl);
|
|
6258
|
+
if (!nextUrl) {
|
|
6259
|
+
setStatus("Could not refresh this PDF preview.", "warning");
|
|
6260
|
+
return false;
|
|
6261
|
+
}
|
|
6262
|
+
if (frame) frame.src = nextUrl;
|
|
6263
|
+
if (studioPdfFocusOpenLinkEl) studioPdfFocusOpenLinkEl.href = nextUrl;
|
|
6264
|
+
setStatus("Refreshed PDF preview from disk.", "success");
|
|
6265
|
+
return true;
|
|
6266
|
+
}
|
|
6267
|
+
|
|
5931
6268
|
function syncStudioPdfFocusFullscreenButton() {
|
|
5932
6269
|
if (!studioPdfFocusFullscreenBtn) return;
|
|
5933
6270
|
const isFullscreen = Boolean(document.fullscreenElement && studioPdfFocusDialogEl && document.fullscreenElement === studioPdfFocusDialogEl);
|
|
@@ -6458,6 +6795,18 @@
|
|
|
6458
6795
|
openLink.textContent = "Open PDF";
|
|
6459
6796
|
actions.appendChild(openLink);
|
|
6460
6797
|
|
|
6798
|
+
const refreshBtn = document.createElement("button");
|
|
6799
|
+
refreshBtn.type = "button";
|
|
6800
|
+
refreshBtn.className = "studio-pdf-card-action studio-pdf-card-refresh";
|
|
6801
|
+
refreshBtn.textContent = "Refresh";
|
|
6802
|
+
refreshBtn.title = "Reload this PDF preview from disk.";
|
|
6803
|
+
refreshBtn.addEventListener("click", (event) => {
|
|
6804
|
+
event.preventDefault();
|
|
6805
|
+
event.stopPropagation();
|
|
6806
|
+
if (!refreshStudioPdfCard(card)) setStatus("Could not refresh this PDF preview.", "warning");
|
|
6807
|
+
});
|
|
6808
|
+
actions.appendChild(refreshBtn);
|
|
6809
|
+
|
|
6461
6810
|
header.appendChild(actions);
|
|
6462
6811
|
}
|
|
6463
6812
|
card.appendChild(header);
|
|
@@ -8236,15 +8585,16 @@
|
|
|
8236
8585
|
function renderSourcePreviewNow() {
|
|
8237
8586
|
if (editorView !== "preview") return;
|
|
8238
8587
|
const text = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
8239
|
-
|
|
8240
|
-
|
|
8588
|
+
const previewLanguage = getEditorLanguageForPreview();
|
|
8589
|
+
if (isHtmlArtifactPreviewText(text, previewLanguage)) {
|
|
8590
|
+
renderHtmlArtifactPreview(sourcePreviewEl, text, "source", { title: "Editor HTML preview", commentable: true, ...getHtmlPreviewResourceContextOptions() });
|
|
8241
8591
|
return;
|
|
8242
8592
|
}
|
|
8243
|
-
if (renderDelimitedTextPreview(sourcePreviewEl, text, "source")) {
|
|
8593
|
+
if (renderDelimitedTextPreview(sourcePreviewEl, text, "source", previewLanguage)) {
|
|
8244
8594
|
return;
|
|
8245
8595
|
}
|
|
8246
|
-
if (
|
|
8247
|
-
renderCodePreviewWithCommentBlocks(sourcePreviewEl, text, "source");
|
|
8596
|
+
if (supportsCodePreviewCommentsForLanguage(previewLanguage)) {
|
|
8597
|
+
renderCodePreviewWithCommentBlocks(sourcePreviewEl, text, "source", previewLanguage);
|
|
8248
8598
|
return;
|
|
8249
8599
|
}
|
|
8250
8600
|
const nonce = ++sourcePreviewRenderNonce;
|
|
@@ -8368,9 +8718,12 @@
|
|
|
8368
8718
|
return { text: preview, truncated: true, hiddenChars, hiddenLines };
|
|
8369
8719
|
}
|
|
8370
8720
|
|
|
8371
|
-
function renderTraceOutput(text, outputKey) {
|
|
8721
|
+
function renderTraceOutput(text, outputKey, options) {
|
|
8372
8722
|
const value = String(text || "");
|
|
8373
8723
|
const key = String(outputKey || "trace-output");
|
|
8724
|
+
const label = options && typeof options.label === "string" && options.label.trim()
|
|
8725
|
+
? options.label.trim()
|
|
8726
|
+
: "Output";
|
|
8374
8727
|
const isExpanded = traceExpandedOutputs.has(key);
|
|
8375
8728
|
const preview = getTraceOutputPreview(value);
|
|
8376
8729
|
const visibleText = isExpanded || !preview.truncated ? value : preview.text;
|
|
@@ -8380,10 +8733,11 @@
|
|
|
8380
8733
|
const hiddenParts = [];
|
|
8381
8734
|
if (preview.hiddenLines > 0) hiddenParts.push(preview.hiddenLines + " more line" + (preview.hiddenLines === 1 ? "" : "s"));
|
|
8382
8735
|
if (preview.hiddenChars > 0) hiddenParts.push(formatCompactNumber(preview.hiddenChars) + " chars hidden");
|
|
8736
|
+
const labelLower = label.toLowerCase();
|
|
8383
8737
|
const summary = isExpanded
|
|
8384
|
-
? "Showing full
|
|
8385
|
-
: "
|
|
8386
|
-
const buttonLabel = isExpanded ? "Collapse" : "Show full";
|
|
8738
|
+
? "Showing full " + labelLower + " (" + formatTraceOutputSize(value) + ")."
|
|
8739
|
+
: label + " truncated — " + (hiddenParts.join(", ") || "more hidden") + ".";
|
|
8740
|
+
const buttonLabel = isExpanded ? "Collapse " + labelLower : "Show full " + labelLower;
|
|
8387
8741
|
return "<div class='trace-output-wrap" + (isExpanded ? " is-expanded" : " is-truncated") + "'>"
|
|
8388
8742
|
+ body
|
|
8389
8743
|
+ "<div class='trace-output-truncation'>"
|
|
@@ -8757,15 +9111,16 @@
|
|
|
8757
9111
|
}
|
|
8758
9112
|
|
|
8759
9113
|
const title = entry.label || entry.toolName || "tool";
|
|
8760
|
-
const
|
|
8761
|
-
|
|
9114
|
+
const inputText = entry.args || entry.argsSummary || "";
|
|
9115
|
+
const argsSummary = inputText
|
|
9116
|
+
? "<div class='trace-section trace-section-input'><div class='trace-section-label'>Input</div>" + renderTraceOutput(inputText, entry.id + ":input", { label: "Input" }) + "</div>"
|
|
8762
9117
|
: "";
|
|
8763
9118
|
const imageOutput = renderTraceImages(entry.images);
|
|
8764
9119
|
const outputPieces = [];
|
|
8765
|
-
if (entry.output) outputPieces.push(renderTraceOutput(entry.output, entry.id + ":output"));
|
|
9120
|
+
if (entry.output) outputPieces.push(renderTraceOutput(entry.output, entry.id + ":output", { label: "Output" }));
|
|
8766
9121
|
if (imageOutput) outputPieces.push(imageOutput);
|
|
8767
9122
|
const output = outputPieces.length
|
|
8768
|
-
? "<div class='trace-section'><div class='trace-section-label'>Output</div>" + outputPieces.join("") + "</div>"
|
|
9123
|
+
? "<div class='trace-section trace-section-output'><div class='trace-section-label'>Output</div>" + outputPieces.join("") + "</div>"
|
|
8769
9124
|
: "<div class='trace-empty-inline'>No output yet.</div>";
|
|
8770
9125
|
const toolStatusLabel = entry.isError
|
|
8771
9126
|
? "Error"
|
|
@@ -8874,8 +9229,11 @@
|
|
|
8874
9229
|
const newTabAction = kind === "text" || kind === "office"
|
|
8875
9230
|
? "open-new"
|
|
8876
9231
|
: ((kind === "pdf" || kind === "image") ? "open-preview-new" : "");
|
|
9232
|
+
const newTabLabel = kind === "text"
|
|
9233
|
+
? "Open file tab"
|
|
9234
|
+
: (kind === "office" ? "Convert tab" : ((kind === "pdf" || kind === "image") ? "Preview tab" : "New tab"));
|
|
8877
9235
|
const textActions = newTabAction
|
|
8878
|
-
? "<button type='button' data-files-action='" + escapeHtml(newTabAction) + "' data-files-path='" + escapeHtml(entry.path) + "'>
|
|
9236
|
+
? "<button type='button' data-files-action='" + escapeHtml(newTabAction) + "' data-files-path='" + escapeHtml(entry.path) + "'>" + escapeHtml(newTabLabel) + "</button>"
|
|
8879
9237
|
: "";
|
|
8880
9238
|
const openTitle = type === "directory"
|
|
8881
9239
|
? "Open folder"
|
|
@@ -8998,10 +9356,38 @@
|
|
|
8998
9356
|
}
|
|
8999
9357
|
}
|
|
9000
9358
|
|
|
9359
|
+
function basenameForStudioPath(path) {
|
|
9360
|
+
const value = stripPreviewLocalLinkUrlSuffix(path || "").replace(/\\/g, "/");
|
|
9361
|
+
const parts = value.split("/");
|
|
9362
|
+
return parts.pop() || value || "file";
|
|
9363
|
+
}
|
|
9364
|
+
|
|
9365
|
+
function ensureCurrentEditorFileBackedFromFilesPath(path) {
|
|
9366
|
+
const cleanPath = stripPreviewLocalLinkUrlSuffix(path || "").trim();
|
|
9367
|
+
if (!isLikelyAbsoluteStudioPath(cleanPath)) return;
|
|
9368
|
+
if (sourceState && sourceState.path === cleanPath) return;
|
|
9369
|
+
const resourceDir = normalizeStudioResourceDirValue(fileBrowserState.rootDir || getCurrentResourceDirValue() || dirnameForDisplayPath(cleanPath));
|
|
9370
|
+
if (resourceDirInput && resourceDir) resourceDirInput.value = resourceDir;
|
|
9371
|
+
setSourceState({
|
|
9372
|
+
source: "file",
|
|
9373
|
+
label: sourceState && sourceState.label && sourceState.label !== "blank" ? sourceState.label : basenameForStudioPath(cleanPath),
|
|
9374
|
+
path: cleanPath,
|
|
9375
|
+
});
|
|
9376
|
+
markFileBackedBaseline(sourceTextEl.value);
|
|
9377
|
+
}
|
|
9378
|
+
|
|
9001
9379
|
async function openFileBrowserEntry(path, kind) {
|
|
9002
9380
|
const context = getFileBrowserLocalLinkContext();
|
|
9003
|
-
if (kind === "text"
|
|
9004
|
-
await openPreviewDocumentHere(path, context);
|
|
9381
|
+
if (kind === "text") {
|
|
9382
|
+
await openPreviewDocumentHere(path, context, { fallbackPath: path, fileBackedIntent: true });
|
|
9383
|
+
ensureCurrentEditorFileBackedFromFilesPath(path);
|
|
9384
|
+
if (sourceState && sourceState.path) {
|
|
9385
|
+
setStatus("Opened file-backed document in editor: " + (sourceState.label || sourceState.path), "success");
|
|
9386
|
+
}
|
|
9387
|
+
return;
|
|
9388
|
+
}
|
|
9389
|
+
if (kind === "office") {
|
|
9390
|
+
await openPreviewDocumentHere(path, context, { fallbackPath: path });
|
|
9005
9391
|
return;
|
|
9006
9392
|
}
|
|
9007
9393
|
if (kind === "pdf") {
|
|
@@ -9103,15 +9489,16 @@
|
|
|
9103
9489
|
scheduleResponsePaneRepaintNudge();
|
|
9104
9490
|
return;
|
|
9105
9491
|
}
|
|
9106
|
-
|
|
9107
|
-
|
|
9492
|
+
const previewLanguage = getEditorLanguageForPreview();
|
|
9493
|
+
if (isHtmlArtifactPreviewText(editorText, previewLanguage)) {
|
|
9494
|
+
renderHtmlArtifactPreview(critiqueViewEl, editorText, "response", { title: "Editor HTML preview", commentable: true, ...getHtmlPreviewResourceContextOptions() });
|
|
9108
9495
|
return;
|
|
9109
9496
|
}
|
|
9110
|
-
if (renderDelimitedTextPreview(critiqueViewEl, editorText, "response")) {
|
|
9497
|
+
if (renderDelimitedTextPreview(critiqueViewEl, editorText, "response", previewLanguage)) {
|
|
9111
9498
|
return;
|
|
9112
9499
|
}
|
|
9113
|
-
if (
|
|
9114
|
-
renderCodePreviewWithCommentBlocks(critiqueViewEl, editorText, "response");
|
|
9500
|
+
if (supportsCodePreviewCommentsForLanguage(previewLanguage)) {
|
|
9501
|
+
renderCodePreviewWithCommentBlocks(critiqueViewEl, editorText, "response", previewLanguage);
|
|
9115
9502
|
return;
|
|
9116
9503
|
}
|
|
9117
9504
|
const nonce = ++responsePreviewRenderNonce;
|
|
@@ -9296,13 +9683,17 @@
|
|
|
9296
9683
|
return resourceDirInput ? normalizeStudioResourceDirValue(resourceDirInput.value) : "";
|
|
9297
9684
|
}
|
|
9298
9685
|
|
|
9686
|
+
function stripImportedFileLabel(label) {
|
|
9687
|
+
return String(label || "").replace(/^(?:upload|imported copy):\s*/i, "");
|
|
9688
|
+
}
|
|
9689
|
+
|
|
9299
9690
|
function getEffectiveSavePath() {
|
|
9300
9691
|
// File-backed: use the original path
|
|
9301
9692
|
if (sourceState.path) return sourceState.path;
|
|
9302
|
-
//
|
|
9693
|
+
// Browser-imported copy with working dir + filename: derive path
|
|
9303
9694
|
const resourceDir = getCurrentResourceDirValue();
|
|
9304
9695
|
if (sourceState.source === "upload" && sourceState.label && resourceDir) {
|
|
9305
|
-
var name = sourceState.label
|
|
9696
|
+
var name = stripImportedFileLabel(sourceState.label);
|
|
9306
9697
|
if (name) return resourceDir.replace(/\/$/, "") + "/" + name;
|
|
9307
9698
|
}
|
|
9308
9699
|
return null;
|
|
@@ -9327,7 +9718,7 @@
|
|
|
9327
9718
|
return dir + stem + ".annotated.md";
|
|
9328
9719
|
}
|
|
9329
9720
|
|
|
9330
|
-
const rawLabel = sourceState.label ? sourceState.label
|
|
9721
|
+
const rawLabel = sourceState.label ? stripImportedFileLabel(sourceState.label) : "draft.md";
|
|
9331
9722
|
const stem = rawLabel.replace(/\.[^.]+$/, "") || "draft";
|
|
9332
9723
|
const suggestedDir = getCurrentResourceDirValue()
|
|
9333
9724
|
? getCurrentResourceDirValue().replace(/\/$/, "") + "/"
|
|
@@ -9355,7 +9746,7 @@
|
|
|
9355
9746
|
return;
|
|
9356
9747
|
}
|
|
9357
9748
|
|
|
9358
|
-
refreshFromDiskBtn.title = "Refresh from disk is
|
|
9749
|
+
refreshFromDiskBtn.title = "Refresh from disk is available after opening a file from disk. Use Files → Open here, Files → Open file tab, or /studio-editor-only <path> for a refreshable editor tab.";
|
|
9359
9750
|
}
|
|
9360
9751
|
|
|
9361
9752
|
function syncActionButtons() {
|
|
@@ -9390,7 +9781,8 @@
|
|
|
9390
9781
|
if (stripAnnotationsBtn) stripAnnotationsBtn.disabled = uiBusy || !hasAnnotationMarkers(sourceTextEl.value);
|
|
9391
9782
|
if (compactBtn) compactBtn.disabled = isEditorOnlyMode || uiBusy || compactInProgress || wsState === "Disconnected";
|
|
9392
9783
|
editorViewSelect.disabled = isEditorOnlyMode;
|
|
9393
|
-
|
|
9784
|
+
syncRightViewModeOptions();
|
|
9785
|
+
rightViewSelect.disabled = false;
|
|
9394
9786
|
followSelect.disabled = isEditorOnlyMode || uiBusy;
|
|
9395
9787
|
if (responseHighlightSelect) responseHighlightSelect.disabled = isEditorOnlyMode || rightView !== "markdown";
|
|
9396
9788
|
insertHeaderBtn.disabled = uiBusy;
|
|
@@ -9501,7 +9893,7 @@
|
|
|
9501
9893
|
sourceState: normalizeWorkspaceSourceState(sourceState),
|
|
9502
9894
|
resourceDir: getCurrentResourceDirValue(),
|
|
9503
9895
|
editorView,
|
|
9504
|
-
rightView:
|
|
9896
|
+
rightView: normalizeRightViewValue(rightView),
|
|
9505
9897
|
editorLanguage,
|
|
9506
9898
|
followLatest,
|
|
9507
9899
|
responseHistoryIndex,
|
|
@@ -9576,15 +9968,7 @@
|
|
|
9576
9968
|
setEditorLanguage(state.editorLanguage.trim());
|
|
9577
9969
|
}
|
|
9578
9970
|
editorView = state.editorView === "preview" ? "preview" : "markdown";
|
|
9579
|
-
rightView =
|
|
9580
|
-
? "editor-preview"
|
|
9581
|
-
: (state.rightView === "preview"
|
|
9582
|
-
? "preview"
|
|
9583
|
-
: (state.rightView === "editor-preview"
|
|
9584
|
-
? "editor-preview"
|
|
9585
|
-
: (state.rightView === "repl"
|
|
9586
|
-
? "repl"
|
|
9587
|
-
: (state.rightView === "files" ? "files" : ((state.rightView === "trace" || state.rightView === "thinking") ? "trace" : "markdown")))));
|
|
9971
|
+
rightView = normalizeRightViewValue(state.rightView);
|
|
9588
9972
|
if (typeof state.followLatest === "boolean") {
|
|
9589
9973
|
followLatest = state.followLatest;
|
|
9590
9974
|
}
|
|
@@ -10127,15 +10511,8 @@
|
|
|
10127
10511
|
|
|
10128
10512
|
function setRightView(nextView) {
|
|
10129
10513
|
const previousView = rightView;
|
|
10130
|
-
rightView = nextView
|
|
10131
|
-
|
|
10132
|
-
: (nextView === "editor-preview"
|
|
10133
|
-
? "editor-preview"
|
|
10134
|
-
: (nextView === "repl"
|
|
10135
|
-
? "repl"
|
|
10136
|
-
: (nextView === "files"
|
|
10137
|
-
? "files"
|
|
10138
|
-
: ((nextView === "trace" || nextView === "thinking") ? "trace" : "markdown"))));
|
|
10514
|
+
rightView = normalizeRightViewValue(nextView);
|
|
10515
|
+
syncRightViewModeOptions();
|
|
10139
10516
|
rightViewSelect.value = rightView;
|
|
10140
10517
|
if (rightView === "trace" && previousView !== "trace") {
|
|
10141
10518
|
traceAutoScroll = true;
|
|
@@ -10726,21 +11103,34 @@
|
|
|
10726
11103
|
return confirmed;
|
|
10727
11104
|
}
|
|
10728
11105
|
|
|
10729
|
-
|
|
11106
|
+
function isLikelyAbsoluteStudioPath(path) {
|
|
11107
|
+
const value = stripPreviewLocalLinkUrlSuffix(path || "").trim();
|
|
11108
|
+
return Boolean(value && (/^\//.test(value) || /^[A-Za-z]:[\\/]/.test(value)));
|
|
11109
|
+
}
|
|
11110
|
+
|
|
11111
|
+
async function openPreviewDocumentHere(href, contextOverride, options) {
|
|
10730
11112
|
if (!confirmPreviewOfficeConversion(href, "here")) return;
|
|
10731
11113
|
if (editorHasPotentialUnsavedContent()) {
|
|
10732
|
-
const
|
|
11114
|
+
const kind = getPreviewLocalLinkKind(href);
|
|
11115
|
+
const prompt = kind === "office"
|
|
11116
|
+
? "Replace the current editor contents with this converted Markdown copy? Unsaved editor changes may be lost."
|
|
11117
|
+
: "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.";
|
|
11118
|
+
const confirmed = window.confirm(prompt);
|
|
10733
11119
|
if (!confirmed) return;
|
|
10734
11120
|
}
|
|
10735
11121
|
const payload = await fetchPreviewLocalLink("document", href, contextOverride);
|
|
10736
11122
|
if (typeof payload.text !== "string") throw new Error("Studio did not return document text.");
|
|
10737
|
-
const
|
|
11123
|
+
const responsePath = typeof payload.path === "string" ? payload.path : "";
|
|
11124
|
+
const fallbackPath = options && typeof options.fallbackPath === "string" && isLikelyAbsoluteStudioPath(options.fallbackPath)
|
|
11125
|
+
? stripPreviewLocalLinkUrlSuffix(options.fallbackPath).trim()
|
|
11126
|
+
: "";
|
|
11127
|
+
const path = responsePath || fallbackPath;
|
|
10738
11128
|
const label = typeof payload.label === "string" && payload.label.trim() ? payload.label.trim() : (path || "linked file");
|
|
10739
11129
|
const nextResourceDir = typeof payload.resourceDir === "string" ? normalizeStudioResourceDirValue(payload.resourceDir) : "";
|
|
10740
11130
|
const converted = payload && payload.converted === true;
|
|
10741
11131
|
if (resourceDirInput && nextResourceDir) resourceDirInput.value = nextResourceDir;
|
|
10742
11132
|
setEditorText(payload.text, { preserveScroll: false, preserveSelection: false });
|
|
10743
|
-
if (converted) {
|
|
11133
|
+
if (converted || !path) {
|
|
10744
11134
|
setSourceState({ source: "blank", label, path: null });
|
|
10745
11135
|
} else {
|
|
10746
11136
|
setSourceState({ source: "file", label, path });
|
|
@@ -10750,7 +11140,9 @@
|
|
|
10750
11140
|
if (detected) setEditorLanguage(detected);
|
|
10751
11141
|
setEditorView("markdown");
|
|
10752
11142
|
setActivePane("left");
|
|
10753
|
-
setStatus(converted
|
|
11143
|
+
setStatus(converted
|
|
11144
|
+
? ("Converted document into editor: " + label)
|
|
11145
|
+
: (path ? ("Opened file-backed document in editor: " + label) : ("Opened linked file copy in editor: " + label)), "success");
|
|
10754
11146
|
}
|
|
10755
11147
|
|
|
10756
11148
|
async function openPreviewDocumentInNewEditor(href, pendingWindow, contextOverride) {
|
|
@@ -12145,8 +12537,21 @@
|
|
|
12145
12537
|
return out.join("<br>");
|
|
12146
12538
|
}
|
|
12147
12539
|
|
|
12540
|
+
function getEditorLanguageForPreview() {
|
|
12541
|
+
const detected = detectLanguageFromName((sourceState && (sourceState.path || sourceState.label)) || "");
|
|
12542
|
+
if (detected && (!editorLanguage || editorLanguage === "markdown" || editorLanguage === "text")) {
|
|
12543
|
+
return detected;
|
|
12544
|
+
}
|
|
12545
|
+
return editorLanguage || detected || "";
|
|
12546
|
+
}
|
|
12547
|
+
|
|
12548
|
+
function supportsCodePreviewCommentsForLanguage(language) {
|
|
12549
|
+
const lang = normalizeFenceLanguage(language || "");
|
|
12550
|
+
return Boolean(lang) && lang !== "markdown" && lang !== "latex" && !getDelimitedTextPreviewConfig(lang);
|
|
12551
|
+
}
|
|
12552
|
+
|
|
12148
12553
|
function supportsCodePreviewCommentsForCurrentEditor() {
|
|
12149
|
-
return
|
|
12554
|
+
return supportsCodePreviewCommentsForLanguage(getEditorLanguageForPreview());
|
|
12150
12555
|
}
|
|
12151
12556
|
|
|
12152
12557
|
function getCodePreviewCommentKind(language) {
|
|
@@ -12191,11 +12596,11 @@
|
|
|
12191
12596
|
return "<div class='response-markdown-highlight preview-code-lines'>" + html.join("") + "</div>";
|
|
12192
12597
|
}
|
|
12193
12598
|
|
|
12194
|
-
function renderCodePreviewWithCommentBlocks(targetEl, text, pane) {
|
|
12599
|
+
function renderCodePreviewWithCommentBlocks(targetEl, text, pane, language) {
|
|
12195
12600
|
if (!targetEl) return;
|
|
12196
12601
|
clearPreviewJumpHighlight(targetEl);
|
|
12197
12602
|
finishPreviewRender(targetEl);
|
|
12198
|
-
targetEl.innerHTML = buildCodePreviewHtmlWithCommentBlocks(text, editorLanguage || "");
|
|
12603
|
+
targetEl.innerHTML = buildCodePreviewHtmlWithCommentBlocks(text, language || editorLanguage || "");
|
|
12199
12604
|
ensurePreviewSelectionActions(targetEl);
|
|
12200
12605
|
updatePreviewCommentBlocksForElement(targetEl);
|
|
12201
12606
|
decorateCopyablePreviewBlocks(targetEl);
|
|
@@ -12512,10 +12917,21 @@
|
|
|
12512
12917
|
scheduleScratchpadPersistence(value, descriptor.key);
|
|
12513
12918
|
}
|
|
12514
12919
|
|
|
12920
|
+
function normalizeReviewNoteAnchorKind(value) {
|
|
12921
|
+
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
12922
|
+
if (raw === "html-selection" || raw === "html-element" || raw === "html-page") return raw;
|
|
12923
|
+
return "source";
|
|
12924
|
+
}
|
|
12925
|
+
|
|
12926
|
+
function isReviewNoteDomAnchor(note) {
|
|
12927
|
+
return Boolean(note && normalizeReviewNoteAnchorKind(note.anchorKind) !== "source");
|
|
12928
|
+
}
|
|
12929
|
+
|
|
12515
12930
|
function normalizeReviewNote(note) {
|
|
12516
12931
|
if (!note || typeof note !== "object") return null;
|
|
12517
12932
|
const id = typeof note.id === "string" && note.id.trim() ? note.id : makeRequestId();
|
|
12518
12933
|
const text = typeof note.text === "string" ? note.text : "";
|
|
12934
|
+
const anchorKind = normalizeReviewNoteAnchorKind(note.anchorKind);
|
|
12519
12935
|
const createdAt = typeof note.createdAt === "number" && Number.isFinite(note.createdAt)
|
|
12520
12936
|
? note.createdAt
|
|
12521
12937
|
: Date.now();
|
|
@@ -12545,6 +12961,11 @@
|
|
|
12545
12961
|
lineEnd,
|
|
12546
12962
|
selectedText: typeof note.selectedText === "string" ? note.selectedText : "",
|
|
12547
12963
|
selectedDisplayText: typeof note.selectedDisplayText === "string" ? note.selectedDisplayText : "",
|
|
12964
|
+
anchorKind,
|
|
12965
|
+
htmlSelector: typeof note.htmlSelector === "string" ? note.htmlSelector : "",
|
|
12966
|
+
htmlTag: typeof note.htmlTag === "string" ? note.htmlTag : "",
|
|
12967
|
+
htmlLabel: typeof note.htmlLabel === "string" ? note.htmlLabel : "",
|
|
12968
|
+
htmlPreviewTitle: typeof note.htmlPreviewTitle === "string" ? note.htmlPreviewTitle : "",
|
|
12548
12969
|
};
|
|
12549
12970
|
}
|
|
12550
12971
|
|
|
@@ -13043,17 +13464,26 @@
|
|
|
13043
13464
|
}
|
|
13044
13465
|
}
|
|
13045
13466
|
|
|
13467
|
+
function formatHtmlReviewNoteAnchorLabel(note) {
|
|
13468
|
+
const kind = normalizeReviewNoteAnchorKind(note && note.anchorKind);
|
|
13469
|
+
const tag = String(note && note.htmlTag ? note.htmlTag : "").trim().toLowerCase();
|
|
13470
|
+
if (kind === "html-selection") return "HTML selection";
|
|
13471
|
+
if (kind === "html-page") return "HTML page";
|
|
13472
|
+
return tag ? ("HTML <" + tag + ">") : "HTML element";
|
|
13473
|
+
}
|
|
13474
|
+
|
|
13046
13475
|
function summarizeReviewNoteAnchor(note) {
|
|
13476
|
+
if (isReviewNoteDomAnchor(note)) return formatHtmlReviewNoteAnchorLabel(note);
|
|
13047
13477
|
const start = Math.max(1, Number(note && note.lineStart) || 1);
|
|
13048
13478
|
const end = Math.max(start, Number(note && note.lineEnd) || start);
|
|
13049
13479
|
return start === end ? "Line " + start : ("Lines " + start + "–" + end);
|
|
13050
13480
|
}
|
|
13051
13481
|
|
|
13052
13482
|
function summarizeReviewNoteQuote(note) {
|
|
13053
|
-
const normalized = String(note && (note.selectedDisplayText || note.selectedText) ? (note.selectedDisplayText || note.selectedText) : "")
|
|
13483
|
+
const normalized = String(note && (note.selectedDisplayText || note.selectedText || note.htmlLabel || note.htmlSelector) ? (note.selectedDisplayText || note.selectedText || note.htmlLabel || note.htmlSelector) : "")
|
|
13054
13484
|
.replace(/\s+/g, " ")
|
|
13055
13485
|
.trim();
|
|
13056
|
-
if (!normalized) return "Anchor: current line / empty selection";
|
|
13486
|
+
if (!normalized) return isReviewNoteDomAnchor(note) ? "Anchor: HTML preview" : "Anchor: current line / empty selection";
|
|
13057
13487
|
return normalized.length > 140 ? normalized.slice(0, 137) + "…" : normalized;
|
|
13058
13488
|
}
|
|
13059
13489
|
|
|
@@ -13141,6 +13571,7 @@
|
|
|
13141
13571
|
}
|
|
13142
13572
|
|
|
13143
13573
|
function resolveReviewNoteRange(note, text) {
|
|
13574
|
+
if (isReviewNoteDomAnchor(note)) return null;
|
|
13144
13575
|
const source = String(text || "");
|
|
13145
13576
|
const safeStart = Math.max(0, Math.min(Number(note && note.selectionStart) || 0, source.length));
|
|
13146
13577
|
const safeEnd = Math.max(safeStart, Math.min(Number(note && note.selectionEnd) || safeStart, source.length));
|
|
@@ -13204,6 +13635,7 @@
|
|
|
13204
13635
|
}
|
|
13205
13636
|
|
|
13206
13637
|
function formatReviewNotePromptLineRange(bounds, note) {
|
|
13638
|
+
if (isReviewNoteDomAnchor(note)) return summarizeReviewNoteAnchor(note);
|
|
13207
13639
|
const start = bounds ? bounds.lineStart : Math.max(1, Number(note && note.lineStart) || 1);
|
|
13208
13640
|
const end = bounds ? bounds.lineEnd : Math.max(start, Number(note && note.lineEnd) || start);
|
|
13209
13641
|
return start === end ? "L" + start : ("L" + start + "-L" + end);
|
|
@@ -13217,7 +13649,7 @@
|
|
|
13217
13649
|
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
13218
13650
|
const documentLabel = descriptor && descriptor.label ? descriptor.label : (sourceState && sourceState.label ? sourceState.label : "Studio document");
|
|
13219
13651
|
const parts = [
|
|
13220
|
-
"Please address the following Studio comments. Use
|
|
13652
|
+
"Please address the following Studio comments. Use file names, line numbers, and preview anchors to locate each comment. The full document is not included here, only the comments and their anchors.",
|
|
13221
13653
|
"Document: " + documentLabel,
|
|
13222
13654
|
"",
|
|
13223
13655
|
"## Comments",
|
|
@@ -13239,6 +13671,9 @@
|
|
|
13239
13671
|
if (anchor) {
|
|
13240
13672
|
parts.push("", "> " + anchor.replace(/\n/g, "\n> "));
|
|
13241
13673
|
}
|
|
13674
|
+
if (isReviewNoteDomAnchor(note) && note.htmlSelector) {
|
|
13675
|
+
parts.push("", "Preview selector: `" + String(note.htmlSelector).replace(/`/g, "\\`") + "`");
|
|
13676
|
+
}
|
|
13242
13677
|
parts.push("");
|
|
13243
13678
|
});
|
|
13244
13679
|
|
|
@@ -13260,6 +13695,7 @@
|
|
|
13260
13695
|
const source = String(text || "");
|
|
13261
13696
|
const lineMap = new Map();
|
|
13262
13697
|
for (const note of reviewNotes) {
|
|
13698
|
+
if (isReviewNoteDomAnchor(note)) continue;
|
|
13263
13699
|
const bounds = getResolvedReviewNoteLineBounds(note, source);
|
|
13264
13700
|
if (!bounds) continue;
|
|
13265
13701
|
for (let line = bounds.lineStart; line <= bounds.lineEnd; line += 1) {
|
|
@@ -15798,8 +16234,8 @@
|
|
|
15798
16234
|
return reviewNotes.slice().sort((left, right) => {
|
|
15799
16235
|
const leftBounds = getResolvedReviewNoteLineBounds(left, source);
|
|
15800
16236
|
const rightBounds = getResolvedReviewNoteLineBounds(right, source);
|
|
15801
|
-
const leftLine = leftBounds ? leftBounds.lineStart : Math.max(1, Number(left && left.lineStart) || 1);
|
|
15802
|
-
const rightLine = rightBounds ? rightBounds.lineStart : Math.max(1, Number(right && right.lineStart) || 1);
|
|
16237
|
+
const leftLine = leftBounds ? leftBounds.lineStart : (isReviewNoteDomAnchor(left) ? Number.MAX_SAFE_INTEGER : Math.max(1, Number(left && left.lineStart) || 1));
|
|
16238
|
+
const rightLine = rightBounds ? rightBounds.lineStart : (isReviewNoteDomAnchor(right) ? Number.MAX_SAFE_INTEGER : Math.max(1, Number(right && right.lineStart) || 1));
|
|
15803
16239
|
if (leftLine !== rightLine) return leftLine - rightLine;
|
|
15804
16240
|
|
|
15805
16241
|
const leftStart = leftBounds ? leftBounds.start : Math.max(0, Number(left && left.selectionStart) || 0);
|
|
@@ -15831,6 +16267,15 @@
|
|
|
15831
16267
|
function getReviewNoteInlineState(note, text) {
|
|
15832
16268
|
const source = String(text || "");
|
|
15833
16269
|
const annotationBody = escapeReviewNoteAnnotationText(note && note.text);
|
|
16270
|
+
if (isReviewNoteDomAnchor(note)) {
|
|
16271
|
+
return {
|
|
16272
|
+
annotationBody,
|
|
16273
|
+
range: null,
|
|
16274
|
+
markerText: "",
|
|
16275
|
+
exists: false,
|
|
16276
|
+
canToggle: false,
|
|
16277
|
+
};
|
|
16278
|
+
}
|
|
15834
16279
|
if (!annotationBody) {
|
|
15835
16280
|
return {
|
|
15836
16281
|
annotationBody: "",
|
|
@@ -16156,7 +16601,9 @@
|
|
|
16156
16601
|
const jumpBtn = document.createElement("button");
|
|
16157
16602
|
jumpBtn.type = "button";
|
|
16158
16603
|
jumpBtn.textContent = "Jump";
|
|
16159
|
-
jumpBtn.title =
|
|
16604
|
+
jumpBtn.title = isReviewNoteDomAnchor(note)
|
|
16605
|
+
? "Jump to this comment's HTML preview anchor."
|
|
16606
|
+
: "Jump to this comment's anchored location in the editor.";
|
|
16160
16607
|
jumpBtn.addEventListener("click", () => {
|
|
16161
16608
|
jumpToReviewNote(note.id);
|
|
16162
16609
|
});
|
|
@@ -16169,9 +16616,11 @@
|
|
|
16169
16616
|
convertBtn.textContent = inlineState.exists ? "Inline: On" : "Inline: Off";
|
|
16170
16617
|
convertBtn.setAttribute("aria-pressed", inlineState.exists ? "true" : "false");
|
|
16171
16618
|
convertBtn.disabled = !inlineState.canToggle || uiBusy;
|
|
16172
|
-
convertBtn.title =
|
|
16173
|
-
? "
|
|
16174
|
-
:
|
|
16619
|
+
convertBtn.title = isReviewNoteDomAnchor(note)
|
|
16620
|
+
? "Inline annotations are only available for comments anchored to source text."
|
|
16621
|
+
: (inlineState.exists
|
|
16622
|
+
? "This comment currently has an inline [an: ...] annotation in the editor. Click to remove it."
|
|
16623
|
+
: "This comment is currently not inline in the editor. Click to add it as an inline [an: ...] annotation.");
|
|
16175
16624
|
convertBtn.addEventListener("click", () => {
|
|
16176
16625
|
convertReviewNoteToAnnotation(note.id);
|
|
16177
16626
|
});
|
|
@@ -16199,9 +16648,11 @@
|
|
|
16199
16648
|
convertBtn.disabled = !nextInlineState.canToggle || uiBusy;
|
|
16200
16649
|
convertBtn.textContent = nextInlineState.exists ? "Inline: On" : "Inline: Off";
|
|
16201
16650
|
convertBtn.setAttribute("aria-pressed", nextInlineState.exists ? "true" : "false");
|
|
16202
|
-
convertBtn.title =
|
|
16203
|
-
? "
|
|
16204
|
-
:
|
|
16651
|
+
convertBtn.title = isReviewNoteDomAnchor(note)
|
|
16652
|
+
? "Inline annotations are only available for comments anchored to source text."
|
|
16653
|
+
: (nextInlineState.exists
|
|
16654
|
+
? "This comment currently has an inline [an: ...] annotation in the editor. Click to remove it."
|
|
16655
|
+
: "This comment is currently not inline in the editor. Click to add it as an inline [an: ...] annotation.");
|
|
16205
16656
|
scheduleReviewNotesPersistence();
|
|
16206
16657
|
updateReviewNotesUi();
|
|
16207
16658
|
});
|
|
@@ -16273,6 +16724,55 @@
|
|
|
16273
16724
|
return note;
|
|
16274
16725
|
}
|
|
16275
16726
|
|
|
16727
|
+
function addReviewNoteFromHtmlArtifactTarget(record, data) {
|
|
16728
|
+
if (!record || !record.commentable) return null;
|
|
16729
|
+
const kind = data && data.kind === "selection" ? "html-selection" : "html-element";
|
|
16730
|
+
const selector = typeof data.selector === "string" ? data.selector : "";
|
|
16731
|
+
const tag = typeof data.tag === "string" ? data.tag : "";
|
|
16732
|
+
const text = typeof data.text === "string" ? data.text : "";
|
|
16733
|
+
const label = typeof data.label === "string" ? data.label : "";
|
|
16734
|
+
const display = text || label || selector || (tag ? ("<" + tag + ">") : "HTML element");
|
|
16735
|
+
return addReviewNoteFromAnchor({
|
|
16736
|
+
selectionStart: 0,
|
|
16737
|
+
selectionEnd: 0,
|
|
16738
|
+
lineStart: 1,
|
|
16739
|
+
lineEnd: 1,
|
|
16740
|
+
selectedText: "",
|
|
16741
|
+
selectedDisplayText: display,
|
|
16742
|
+
anchorKind: kind,
|
|
16743
|
+
htmlSelector: selector,
|
|
16744
|
+
htmlTag: tag,
|
|
16745
|
+
htmlLabel: label,
|
|
16746
|
+
htmlPreviewTitle: record.title || "HTML preview",
|
|
16747
|
+
}, {
|
|
16748
|
+
statusMessage: kind === "html-selection"
|
|
16749
|
+
? "Added local comment from HTML preview selection."
|
|
16750
|
+
: "Added local comment from HTML preview element.",
|
|
16751
|
+
});
|
|
16752
|
+
}
|
|
16753
|
+
|
|
16754
|
+
function addReviewNoteFromHtmlArtifactPage(record) {
|
|
16755
|
+
if (!record || !record.commentable) {
|
|
16756
|
+
setStatus("HTML preview comments are only available for editor previews.", "warning");
|
|
16757
|
+
return null;
|
|
16758
|
+
}
|
|
16759
|
+
return addReviewNoteFromAnchor({
|
|
16760
|
+
selectionStart: 0,
|
|
16761
|
+
selectionEnd: 0,
|
|
16762
|
+
lineStart: 1,
|
|
16763
|
+
lineEnd: 1,
|
|
16764
|
+
selectedText: "",
|
|
16765
|
+
selectedDisplayText: record.title || "HTML preview",
|
|
16766
|
+
anchorKind: "html-page",
|
|
16767
|
+
htmlSelector: "",
|
|
16768
|
+
htmlTag: "",
|
|
16769
|
+
htmlLabel: record.title || "HTML preview",
|
|
16770
|
+
htmlPreviewTitle: record.title || "HTML preview",
|
|
16771
|
+
}, {
|
|
16772
|
+
statusMessage: "Added page-level local comment for HTML preview.",
|
|
16773
|
+
});
|
|
16774
|
+
}
|
|
16775
|
+
|
|
16276
16776
|
function addReviewNoteFromAnchor(anchor, options) {
|
|
16277
16777
|
if (!anchor || typeof anchor !== "object") return null;
|
|
16278
16778
|
const note = normalizeReviewNote({
|
|
@@ -16286,6 +16786,11 @@
|
|
|
16286
16786
|
lineEnd: anchor.lineEnd,
|
|
16287
16787
|
selectedText: anchor.selectedText,
|
|
16288
16788
|
selectedDisplayText: typeof anchor.selectedDisplayText === "string" ? anchor.selectedDisplayText : (typeof anchor.selectedText === "string" ? anchor.selectedText : ""),
|
|
16789
|
+
anchorKind: anchor.anchorKind,
|
|
16790
|
+
htmlSelector: anchor.htmlSelector,
|
|
16791
|
+
htmlTag: anchor.htmlTag,
|
|
16792
|
+
htmlLabel: anchor.htmlLabel,
|
|
16793
|
+
htmlPreviewTitle: anchor.htmlPreviewTitle,
|
|
16289
16794
|
});
|
|
16290
16795
|
if (!note) return null;
|
|
16291
16796
|
if (editorSelectionCommentBtn) {
|
|
@@ -16433,9 +16938,55 @@
|
|
|
16433
16938
|
return jumped;
|
|
16434
16939
|
}
|
|
16435
16940
|
|
|
16941
|
+
function getConnectedHtmlArtifactRecords() {
|
|
16942
|
+
const records = [];
|
|
16943
|
+
htmlArtifactFramesById.forEach((record, id) => {
|
|
16944
|
+
if (!record || !record.iframe || !record.iframe.isConnected || !record.iframe.contentWindow) {
|
|
16945
|
+
if (id) htmlArtifactFramesById.delete(id);
|
|
16946
|
+
return;
|
|
16947
|
+
}
|
|
16948
|
+
records.push(record);
|
|
16949
|
+
});
|
|
16950
|
+
return records;
|
|
16951
|
+
}
|
|
16952
|
+
|
|
16953
|
+
function jumpToHtmlReviewNote(note) {
|
|
16954
|
+
if (!isReviewNoteDomAnchor(note)) return false;
|
|
16955
|
+
const records = getConnectedHtmlArtifactRecords().filter((record) => record && record.commentable);
|
|
16956
|
+
if (records.length === 0) {
|
|
16957
|
+
setStatus("Open the HTML preview before jumping to this comment.", "warning");
|
|
16958
|
+
return false;
|
|
16959
|
+
}
|
|
16960
|
+
const record = records[0];
|
|
16961
|
+
if (record.shell && typeof record.shell.scrollIntoView === "function") {
|
|
16962
|
+
try {
|
|
16963
|
+
record.shell.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth" });
|
|
16964
|
+
} catch {
|
|
16965
|
+
try { record.shell.scrollIntoView(false); } catch {}
|
|
16966
|
+
}
|
|
16967
|
+
}
|
|
16968
|
+
try {
|
|
16969
|
+
record.iframe.contentWindow.postMessage({
|
|
16970
|
+
type: "pi-studio-html-artifact-highlight-comment",
|
|
16971
|
+
id: record.id || "",
|
|
16972
|
+
selector: note.htmlSelector || "",
|
|
16973
|
+
anchorKind: normalizeReviewNoteAnchorKind(note.anchorKind),
|
|
16974
|
+
}, "*");
|
|
16975
|
+
setStatus("Jumped to HTML preview comment anchor.", "success");
|
|
16976
|
+
return true;
|
|
16977
|
+
} catch {
|
|
16978
|
+
setStatus("Could not jump to this HTML preview comment.", "warning");
|
|
16979
|
+
return false;
|
|
16980
|
+
}
|
|
16981
|
+
}
|
|
16982
|
+
|
|
16436
16983
|
function jumpToReviewNote(noteId) {
|
|
16437
16984
|
const note = reviewNotes.find((entry) => entry && entry.id === noteId);
|
|
16438
16985
|
if (!note) return;
|
|
16986
|
+
if (isReviewNoteDomAnchor(note)) {
|
|
16987
|
+
jumpToHtmlReviewNote(note);
|
|
16988
|
+
return;
|
|
16989
|
+
}
|
|
16439
16990
|
jumpToReviewAnchor(note, {
|
|
16440
16991
|
status: false,
|
|
16441
16992
|
notFoundStatusMessage: "Could not find the anchored location for this comment.",
|
|
@@ -16850,6 +17401,8 @@
|
|
|
16850
17401
|
const directIsStop = activeKind === "direct";
|
|
16851
17402
|
const critiqueIsStop = activeKind === "critique";
|
|
16852
17403
|
const canQueueSteering = studioRunChainActive && !critiqueIsStop;
|
|
17404
|
+
const hasReplSession = Boolean(getActiveReplSessionForCurrentRuntime());
|
|
17405
|
+
const showReplSend = rightView === "repl";
|
|
16853
17406
|
|
|
16854
17407
|
if (isEditorOnlyMode) {
|
|
16855
17408
|
if (sendRunBtn) {
|
|
@@ -16865,15 +17418,25 @@
|
|
|
16865
17418
|
queueSteerBtn.title = "Queue steering is unavailable in editor-only mode.";
|
|
16866
17419
|
}
|
|
16867
17420
|
if (sendReplBtn) {
|
|
16868
|
-
sendReplBtn.hidden =
|
|
16869
|
-
sendReplBtn.disabled =
|
|
16870
|
-
sendReplBtn.classList.
|
|
17421
|
+
sendReplBtn.hidden = !showReplSend;
|
|
17422
|
+
sendReplBtn.disabled = !showReplSend || wsState === "Disconnected" || uiBusy || replBusy || !hasReplSession;
|
|
17423
|
+
sendReplBtn.classList.toggle("repl-primary-action", showReplSend);
|
|
17424
|
+
sendReplBtn.textContent = showReplSend ? withStudioShortcutLabel(replSendMode === "literate" ? "Send selection/chunks" : "Send to REPL", "repl-send") : "Send to REPL";
|
|
17425
|
+
sendReplBtn.title = hasReplSession
|
|
17426
|
+
? (replSendMode === "literate"
|
|
17427
|
+
? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut: Cmd/Ctrl+Shift+Enter."
|
|
17428
|
+
: "Raw send: selection, or full editor if no selection. Shortcut: Cmd/Ctrl+Shift+Enter.")
|
|
17429
|
+
: "Start or select a REPL session in the right pane first.";
|
|
16871
17430
|
const replActionLine = sendReplBtn.closest(".repl-action-line");
|
|
16872
|
-
if (replActionLine instanceof HTMLElement) replActionLine.hidden =
|
|
17431
|
+
if (replActionLine instanceof HTMLElement) replActionLine.hidden = !showReplSend;
|
|
16873
17432
|
}
|
|
16874
17433
|
if (replSendModeSelect) {
|
|
16875
|
-
replSendModeSelect.hidden =
|
|
16876
|
-
replSendModeSelect.disabled =
|
|
17434
|
+
replSendModeSelect.hidden = !showReplSend;
|
|
17435
|
+
replSendModeSelect.disabled = !showReplSend || wsState === "Disconnected" || uiBusy || replBusy;
|
|
17436
|
+
replSendModeSelect.value = replSendMode;
|
|
17437
|
+
replSendModeSelect.title = replSendMode === "literate"
|
|
17438
|
+
? "Literate send: Send to REPL uses the selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk."
|
|
17439
|
+
: "Raw send: Send to REPL uses the selection, or full editor if no selection.";
|
|
16877
17440
|
}
|
|
16878
17441
|
if (critiqueBtn) {
|
|
16879
17442
|
critiqueBtn.textContent = "Critique text";
|
|
@@ -16915,9 +17478,7 @@
|
|
|
16915
17478
|
: "Queue steering is available while Run editor text is active.";
|
|
16916
17479
|
}
|
|
16917
17480
|
|
|
16918
|
-
const hasReplSession = Boolean(getActiveReplSessionForCurrentRuntime());
|
|
16919
17481
|
if (sendReplBtn) {
|
|
16920
|
-
const showReplSend = rightView === "repl";
|
|
16921
17482
|
sendReplBtn.hidden = !showReplSend;
|
|
16922
17483
|
sendReplBtn.disabled = !showReplSend || wsState === "Disconnected" || uiBusy || replBusy || !hasReplSession;
|
|
16923
17484
|
sendReplBtn.classList.toggle("repl-primary-action", showReplSend);
|
|
@@ -18562,7 +19123,7 @@
|
|
|
18562
19123
|
return;
|
|
18563
19124
|
}
|
|
18564
19125
|
|
|
18565
|
-
var suggestedName = sourceState.label ? sourceState.label
|
|
19126
|
+
var suggestedName = sourceState.label ? stripImportedFileLabel(sourceState.label) : "draft.md";
|
|
18566
19127
|
var suggestedDir = getCurrentResourceDirValue() ? getCurrentResourceDirValue().replace(/\/$/, "") + "/" : "./";
|
|
18567
19128
|
const suggested = sourceState.path || (suggestedDir + suggestedName);
|
|
18568
19129
|
const path = window.prompt("Save editor content as:", suggested);
|
|
@@ -18617,7 +19178,7 @@
|
|
|
18617
19178
|
if (refreshFromDiskBtn) {
|
|
18618
19179
|
refreshFromDiskBtn.addEventListener("click", () => {
|
|
18619
19180
|
if (!hasRefreshableFilePath()) {
|
|
18620
|
-
setStatus("Refresh from disk
|
|
19181
|
+
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");
|
|
18621
19182
|
return;
|
|
18622
19183
|
}
|
|
18623
19184
|
|
|
@@ -18632,6 +19193,7 @@
|
|
|
18632
19193
|
const sent = sendMessage({
|
|
18633
19194
|
type: "refresh_from_disk_request",
|
|
18634
19195
|
requestId,
|
|
19196
|
+
path: sourceState.path,
|
|
18635
19197
|
});
|
|
18636
19198
|
|
|
18637
19199
|
if (!sent) {
|
|
@@ -19252,7 +19814,7 @@
|
|
|
19252
19814
|
setEditorText(text, { preserveScroll: false, preserveSelection: false });
|
|
19253
19815
|
setSourceState({
|
|
19254
19816
|
source: "upload",
|
|
19255
|
-
label: "
|
|
19817
|
+
label: "imported copy: " + file.name,
|
|
19256
19818
|
path: null,
|
|
19257
19819
|
});
|
|
19258
19820
|
refreshResponseUi();
|
|
@@ -19260,7 +19822,7 @@
|
|
|
19260
19822
|
if (detectedLang) {
|
|
19261
19823
|
setEditorLanguage(detectedLang);
|
|
19262
19824
|
}
|
|
19263
|
-
setStatus("
|
|
19825
|
+
setStatus("Imported file copy: " + file.name + ".", "success");
|
|
19264
19826
|
};
|
|
19265
19827
|
reader.onerror = () => {
|
|
19266
19828
|
setStatus("Failed to read file.", "error");
|