pi-studio 0.9.9 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/client/studio-client.js +666 -10
- package/client/studio.css +128 -13
- package/index.ts +228 -4
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -119,6 +119,10 @@
|
|
|
119
119
|
const editorFontSizeSelect = document.getElementById("editorFontSizeSelect");
|
|
120
120
|
const annotationModeSelect = document.getElementById("annotationModeSelect");
|
|
121
121
|
const compactBtn = document.getElementById("compactBtn");
|
|
122
|
+
const shortcutsBtn = document.getElementById("shortcutsBtn");
|
|
123
|
+
const shortcutsOverlayEl = document.getElementById("shortcutsOverlay");
|
|
124
|
+
const shortcutsDialogEl = document.getElementById("shortcutsDialog");
|
|
125
|
+
const shortcutsCloseBtn = document.getElementById("shortcutsCloseBtn");
|
|
122
126
|
const leftFocusBtn = document.getElementById("leftFocusBtn");
|
|
123
127
|
const rightFocusBtn = document.getElementById("rightFocusBtn");
|
|
124
128
|
const reviewNotesBtn = document.getElementById("reviewNotesBtn");
|
|
@@ -241,6 +245,7 @@
|
|
|
241
245
|
const REPL_JOURNAL_MAX_ENTRIES = 80;
|
|
242
246
|
const PDF_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
243
247
|
const HTML_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
248
|
+
const HTML_ARTIFACT_MATH_RENDER_FETCH_TIMEOUT_MS = 30_000;
|
|
244
249
|
const EDITOR_TAB_TEXT = " ";
|
|
245
250
|
const QUIZ_DEFAULT_COUNT = 5;
|
|
246
251
|
const QUIZ_SCOPES = ["editor", "selection", "file", "folder", "repo"];
|
|
@@ -1905,7 +1910,7 @@
|
|
|
1905
1910
|
if (document.body) document.body.classList.toggle("studio-zen-mode", studioZenModeEnabled);
|
|
1906
1911
|
if (!zenModeBtn) return;
|
|
1907
1912
|
zenModeBtn.textContent = studioZenModeEnabled ? "Exit Zen" : "Zen";
|
|
1908
|
-
zenModeBtn.title = studioZenModeEnabled ? "Show full Studio controls." : "Hide secondary Studio controls.";
|
|
1913
|
+
zenModeBtn.title = studioZenModeEnabled ? "Show full Studio controls. Shortcut: F9." : "Hide secondary Studio controls. Shortcut: F9.";
|
|
1909
1914
|
zenModeBtn.setAttribute("aria-pressed", studioZenModeEnabled ? "true" : "false");
|
|
1910
1915
|
}
|
|
1911
1916
|
|
|
@@ -3084,6 +3089,110 @@
|
|
|
3084
3089
|
}
|
|
3085
3090
|
}
|
|
3086
3091
|
|
|
3092
|
+
function focusPaneViewControl(pane) {
|
|
3093
|
+
const control = pane === "right" ? rightViewSelect : editorViewSelect;
|
|
3094
|
+
if (!control || control.disabled || control.hidden) return false;
|
|
3095
|
+
try {
|
|
3096
|
+
control.focus({ preventScroll: true });
|
|
3097
|
+
} catch {
|
|
3098
|
+
try { control.focus(); } catch { return false; }
|
|
3099
|
+
}
|
|
3100
|
+
return true;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
function activatePaneFromShortcut(nextPane) {
|
|
3104
|
+
const pane = nextPane === "right" ? "right" : "left";
|
|
3105
|
+
if (isEditorOnlyMode && pane === "right") {
|
|
3106
|
+
setStatus("Only the editor pane is available in editor-only Studio.", "warning");
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
const snapshot = snapshotStudioScrollablePositions();
|
|
3110
|
+
setActivePane(pane);
|
|
3111
|
+
scheduleStudioScrollablePositionRestore(snapshot);
|
|
3112
|
+
focusPaneViewControl(pane);
|
|
3113
|
+
setStatus("Active pane: " + paneLabel(pane) + ". F7 cycles this pane's view.");
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
function getSelectEnabledValues(selectEl) {
|
|
3117
|
+
if (!selectEl || !selectEl.options) return [];
|
|
3118
|
+
return Array.from(selectEl.options)
|
|
3119
|
+
.filter((option) => option && !option.disabled)
|
|
3120
|
+
.map((option) => option.value)
|
|
3121
|
+
.filter((value) => typeof value === "string" && value);
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
function getCycledSelectValue(selectEl, currentValue, direction) {
|
|
3125
|
+
const values = getSelectEnabledValues(selectEl);
|
|
3126
|
+
if (!values.length) return null;
|
|
3127
|
+
const currentIndex = values.indexOf(currentValue);
|
|
3128
|
+
const startIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
3129
|
+
const step = direction < 0 ? -1 : 1;
|
|
3130
|
+
return values[(startIndex + step + values.length) % values.length];
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
function focusEditorTextFromShortcut() {
|
|
3134
|
+
const snapshot = snapshotStudioScrollablePositions();
|
|
3135
|
+
setActivePane("left");
|
|
3136
|
+
if (editorView !== "markdown") setEditorView("markdown");
|
|
3137
|
+
scheduleStudioScrollablePositionRestore(snapshot);
|
|
3138
|
+
window.setTimeout(() => {
|
|
3139
|
+
if (sourceTextEl && typeof sourceTextEl.focus === "function") {
|
|
3140
|
+
try {
|
|
3141
|
+
sourceTextEl.focus({ preventScroll: true });
|
|
3142
|
+
} catch {
|
|
3143
|
+
try { sourceTextEl.focus(); } catch {}
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}, 0);
|
|
3147
|
+
setStatus("Editor text focused.");
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
function focusRightContentFromShortcut() {
|
|
3151
|
+
if (isEditorOnlyMode) {
|
|
3152
|
+
setStatus("Only the editor pane is available in editor-only Studio.", "warning");
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
const snapshot = snapshotStudioScrollablePositions();
|
|
3156
|
+
setActivePane("right");
|
|
3157
|
+
scheduleStudioScrollablePositionRestore(snapshot);
|
|
3158
|
+
window.setTimeout(() => {
|
|
3159
|
+
if (critiqueViewEl && typeof critiqueViewEl.focus === "function") {
|
|
3160
|
+
if (!critiqueViewEl.hasAttribute("tabindex")) critiqueViewEl.setAttribute("tabindex", "-1");
|
|
3161
|
+
try {
|
|
3162
|
+
critiqueViewEl.focus({ preventScroll: true });
|
|
3163
|
+
} catch {
|
|
3164
|
+
try { critiqueViewEl.focus(); } catch {}
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
}, 0);
|
|
3168
|
+
setStatus("Right pane content focused.");
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
function cycleActivePaneView(direction) {
|
|
3172
|
+
if (activePane === "right") {
|
|
3173
|
+
if (isEditorOnlyMode || !rightViewSelect || rightViewSelect.disabled) {
|
|
3174
|
+
setStatus("The right-pane view selector is unavailable.", "warning");
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
const nextView = getCycledSelectValue(rightViewSelect, rightView, direction);
|
|
3178
|
+
if (!nextView) return;
|
|
3179
|
+
setRightView(nextView);
|
|
3180
|
+
focusPaneViewControl("right");
|
|
3181
|
+
setStatus("Right pane view: " + (rightViewSelect.selectedOptions && rightViewSelect.selectedOptions[0] ? rightViewSelect.selectedOptions[0].textContent : nextView) + ".");
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
if (!editorViewSelect || editorViewSelect.disabled) {
|
|
3186
|
+
setStatus("The editor view selector is unavailable.", "warning");
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
const nextView = getCycledSelectValue(editorViewSelect, editorView, direction);
|
|
3190
|
+
if (!nextView) return;
|
|
3191
|
+
setEditorView(nextView);
|
|
3192
|
+
focusPaneViewControl("left");
|
|
3193
|
+
setStatus("Editor view: " + (editorViewSelect.selectedOptions && editorViewSelect.selectedOptions[0] ? editorViewSelect.selectedOptions[0].textContent : nextView) + ".");
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3087
3196
|
function paneLabel(pane) {
|
|
3088
3197
|
if (pane === "right") {
|
|
3089
3198
|
return "Response";
|
|
@@ -3131,6 +3240,17 @@
|
|
|
3131
3240
|
return false;
|
|
3132
3241
|
}
|
|
3133
3242
|
|
|
3243
|
+
function isTextEntryShortcutTarget(target) {
|
|
3244
|
+
if (!(target instanceof Element)) return false;
|
|
3245
|
+
const editable = target.closest("input, textarea, select, [contenteditable]");
|
|
3246
|
+
if (!editable) return false;
|
|
3247
|
+
if (editable.hasAttribute && editable.hasAttribute("contenteditable")) {
|
|
3248
|
+
const value = String(editable.getAttribute("contenteditable") || "").toLowerCase();
|
|
3249
|
+
return value !== "false";
|
|
3250
|
+
}
|
|
3251
|
+
return true;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3134
3254
|
function handlePaneShortcut(event) {
|
|
3135
3255
|
if (!event || event.defaultPrevented) return;
|
|
3136
3256
|
|
|
@@ -3158,6 +3278,12 @@
|
|
|
3158
3278
|
&& typeof outlineDialogEl.contains === "function"
|
|
3159
3279
|
&& outlineDialogEl.contains(event.target)
|
|
3160
3280
|
);
|
|
3281
|
+
const shortcutsOwnsEvent = Boolean(
|
|
3282
|
+
shortcutsDialogEl
|
|
3283
|
+
&& event.target
|
|
3284
|
+
&& typeof shortcutsDialogEl.contains === "function"
|
|
3285
|
+
&& shortcutsDialogEl.contains(event.target)
|
|
3286
|
+
);
|
|
3161
3287
|
const pdfFocusOwnsEvent = Boolean(
|
|
3162
3288
|
studioPdfFocusDialogEl
|
|
3163
3289
|
&& event.target
|
|
@@ -3201,6 +3327,12 @@
|
|
|
3201
3327
|
return;
|
|
3202
3328
|
}
|
|
3203
3329
|
|
|
3330
|
+
if (isShortcutsOpen() && plainEscape) {
|
|
3331
|
+
event.preventDefault();
|
|
3332
|
+
closeShortcuts();
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3204
3336
|
if (isReviewNotesOpen() && plainEscape) {
|
|
3205
3337
|
event.preventDefault();
|
|
3206
3338
|
closeReviewNotes();
|
|
@@ -3213,7 +3345,46 @@
|
|
|
3213
3345
|
return;
|
|
3214
3346
|
}
|
|
3215
3347
|
|
|
3216
|
-
if (scratchpadOwnsEvent || reviewNotesOwnsEvent || outlineOwnsEvent || pdfFocusOwnsEvent || htmlFocusOwnsEvent || quizOwnsEvent) {
|
|
3348
|
+
if (scratchpadOwnsEvent || reviewNotesOwnsEvent || outlineOwnsEvent || shortcutsOwnsEvent || pdfFocusOwnsEvent || htmlFocusOwnsEvent || quizOwnsEvent) {
|
|
3349
|
+
return;
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
if ((key === "?" || (key === "/" && event.shiftKey)) && !event.metaKey && !event.ctrlKey && !event.altKey && !isTextEntryShortcutTarget(event.target)) {
|
|
3353
|
+
event.preventDefault();
|
|
3354
|
+
toggleShortcuts();
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
const isPaneSwitchShortcut = key === "F6" && !event.metaKey && !event.ctrlKey && !event.altKey;
|
|
3359
|
+
if (isPaneSwitchShortcut) {
|
|
3360
|
+
event.preventDefault();
|
|
3361
|
+
activatePaneFromShortcut(activePane === "right" ? "left" : "right");
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
const isViewCycleShortcut = key === "F7" && !event.metaKey && !event.ctrlKey && !event.altKey;
|
|
3366
|
+
if (isViewCycleShortcut) {
|
|
3367
|
+
event.preventDefault();
|
|
3368
|
+
cycleActivePaneView(event.shiftKey ? -1 : 1);
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
const isContentFocusShortcut = key === "F8" && !event.metaKey && !event.ctrlKey && !event.altKey;
|
|
3373
|
+
if (isContentFocusShortcut) {
|
|
3374
|
+
event.preventDefault();
|
|
3375
|
+
if (event.shiftKey) {
|
|
3376
|
+
focusRightContentFromShortcut();
|
|
3377
|
+
} else {
|
|
3378
|
+
focusEditorTextFromShortcut();
|
|
3379
|
+
}
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
const isZenModeShortcut = key === "F9" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
|
|
3384
|
+
if (isZenModeShortcut) {
|
|
3385
|
+
event.preventDefault();
|
|
3386
|
+
setStudioZenMode(!studioZenModeEnabled);
|
|
3387
|
+
setStatus(studioZenModeEnabled ? "Zen mode on." : "Zen mode off.");
|
|
3217
3388
|
return;
|
|
3218
3389
|
}
|
|
3219
3390
|
|
|
@@ -3798,6 +3969,11 @@
|
|
|
3798
3969
|
+ " root ? root.offsetHeight : 0\n"
|
|
3799
3970
|
+ " ));\n"
|
|
3800
3971
|
+ " }\n"
|
|
3972
|
+
+ " function getScrollTop() {\n"
|
|
3973
|
+
+ " const body = document.body;\n"
|
|
3974
|
+
+ " const root = document.documentElement;\n"
|
|
3975
|
+
+ " return window.scrollY || (root ? root.scrollTop : 0) || (body ? body.scrollTop : 0) || 0;\n"
|
|
3976
|
+
+ " }\n"
|
|
3801
3977
|
+ " function sendHeight() {\n"
|
|
3802
3978
|
+ " scheduled = false;\n"
|
|
3803
3979
|
+ " const height = measureHeight();\n"
|
|
@@ -3810,13 +3986,243 @@
|
|
|
3810
3986
|
+ " scheduled = true;\n"
|
|
3811
3987
|
+ " requestAnimationFrame(sendHeight);\n"
|
|
3812
3988
|
+ " }\n"
|
|
3989
|
+
+ " function decodeFragment(value) {\n"
|
|
3990
|
+
+ " const text = String(value || '').replace(/^#/, '');\n"
|
|
3991
|
+
+ " try { return decodeURIComponent(text); } catch { return text; }\n"
|
|
3992
|
+
+ " }\n"
|
|
3993
|
+
+ " function findNamedFragmentTarget(fragment) {\n"
|
|
3994
|
+
+ " const decoded = decodeFragment(fragment);\n"
|
|
3995
|
+
+ " if (!decoded) return document.documentElement || document.body;\n"
|
|
3996
|
+
+ " return document.getElementById(decoded) || document.getElementsByName(decoded)[0] || null;\n"
|
|
3997
|
+
+ " }\n"
|
|
3998
|
+
+ " function postFragmentScroll(target) {\n"
|
|
3999
|
+
+ " if (!target || typeof target.getBoundingClientRect !== 'function') return;\n"
|
|
4000
|
+
+ " const rect = target.getBoundingClientRect();\n"
|
|
4001
|
+
+ " const scrollTop = getScrollTop();\n"
|
|
4002
|
+
+ " try {\n"
|
|
4003
|
+
+ " parent.postMessage({ type: 'pi-studio-html-artifact-fragment', id: PREVIEW_ID, targetTop: Math.max(0, rect.top + scrollTop), scrollTop, viewportHeight: window.innerHeight || 0, documentHeight: measureHeight() }, '*');\n"
|
|
4004
|
+
+ " } catch {}\n"
|
|
4005
|
+
+ " }\n"
|
|
4006
|
+
+ " function scrollFragmentIntoView(fragment, options) {\n"
|
|
4007
|
+
+ " const target = findNamedFragmentTarget(fragment);\n"
|
|
4008
|
+
+ " if (!target) return false;\n"
|
|
4009
|
+
+ " const behavior = options && options.smooth === false ? 'auto' : 'smooth';\n"
|
|
4010
|
+
+ " try { target.scrollIntoView({ block: 'start', inline: 'nearest', behavior }); } catch { try { target.scrollIntoView(true); } catch {} }\n"
|
|
4011
|
+
+ " postFragmentScroll(target);\n"
|
|
4012
|
+
+ " setTimeout(() => postFragmentScroll(target), 80);\n"
|
|
4013
|
+
+ " setTimeout(() => postFragmentScroll(target), 300);\n"
|
|
4014
|
+
+ " return true;\n"
|
|
4015
|
+
+ " }\n"
|
|
4016
|
+
+ " function getAnchorFromClickTarget(target) {\n"
|
|
4017
|
+
+ " let node = target;\n"
|
|
4018
|
+
+ " if (node && node.nodeType === 3) node = node.parentElement;\n"
|
|
4019
|
+
+ " return node && typeof node.closest === 'function' ? node.closest('a[href]') : null;\n"
|
|
4020
|
+
+ " }\n"
|
|
4021
|
+
+ " function getSameDocumentFragment(anchor) {\n"
|
|
4022
|
+
+ " if (!anchor || typeof anchor.getAttribute !== 'function') return null;\n"
|
|
4023
|
+
+ " if (anchor.hasAttribute('download')) return null;\n"
|
|
4024
|
+
+ " const target = String(anchor.getAttribute('target') || '').trim().toLowerCase();\n"
|
|
4025
|
+
+ " if (target && target !== '_self') return null;\n"
|
|
4026
|
+
+ " const rawHref = String(anchor.getAttribute('href') || '').trim();\n"
|
|
4027
|
+
+ " if (!rawHref) return null;\n"
|
|
4028
|
+
+ " if (rawHref.charAt(0) === '#') return rawHref.slice(1);\n"
|
|
4029
|
+
+ " const hashIndex = rawHref.indexOf('#');\n"
|
|
4030
|
+
+ " if (hashIndex < 0) return null;\n"
|
|
4031
|
+
+ " const beforeHash = rawHref.slice(0, hashIndex);\n"
|
|
4032
|
+
+ " const currentWithoutHash = String(window.location && window.location.href || '').split('#')[0];\n"
|
|
4033
|
+
+ " if (!beforeHash || beforeHash === currentWithoutHash || beforeHash === 'about:srcdoc') return rawHref.slice(hashIndex + 1);\n"
|
|
4034
|
+
+ " return null;\n"
|
|
4035
|
+
+ " }\n"
|
|
4036
|
+
+ " function writeFragmentHistory(fragment) {\n"
|
|
4037
|
+
+ " try {\n"
|
|
4038
|
+
+ " if (history && typeof history.pushState === 'function') {\n"
|
|
4039
|
+
+ " history.pushState(null, '', fragment ? '#' + encodeURIComponent(decodeFragment(fragment)) : '#');\n"
|
|
4040
|
+
+ " }\n"
|
|
4041
|
+
+ " } catch {}\n"
|
|
4042
|
+
+ " }\n"
|
|
4043
|
+
+ " function handleFragmentAnchorClick(event) {\n"
|
|
4044
|
+
+ " if (!event || event.defaultPrevented) return;\n"
|
|
4045
|
+
+ " if (typeof event.button === 'number' && event.button !== 0) return;\n"
|
|
4046
|
+
+ " if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n"
|
|
4047
|
+
+ " const anchor = getAnchorFromClickTarget(event.target);\n"
|
|
4048
|
+
+ " const fragment = getSameDocumentFragment(anchor);\n"
|
|
4049
|
+
+ " if (fragment == null) return;\n"
|
|
4050
|
+
+ " if (!scrollFragmentIntoView(fragment)) return;\n"
|
|
4051
|
+
+ " event.preventDefault();\n"
|
|
4052
|
+
+ " writeFragmentHistory(fragment);\n"
|
|
4053
|
+
+ " }\n"
|
|
4054
|
+
+ " const htmlMathPlaceholders = new Map();\n"
|
|
4055
|
+
+ " let htmlMathSerial = 0;\n"
|
|
4056
|
+
+ " let htmlMathScanScheduled = false;\n"
|
|
4057
|
+
+ " function delimiterListIncludes(list, start, end) {\n"
|
|
4058
|
+
+ " if (!Array.isArray(list)) return false;\n"
|
|
4059
|
+
+ " return list.some((entry) => Array.isArray(entry) && entry[0] === start && entry[1] === end);\n"
|
|
4060
|
+
+ " }\n"
|
|
4061
|
+
+ " function getHtmlMathDelimiterConfig() {\n"
|
|
4062
|
+
+ " const mathJax = window.MathJax && typeof window.MathJax === 'object' ? window.MathJax : null;\n"
|
|
4063
|
+
+ " const tex = mathJax && mathJax.tex && typeof mathJax.tex === 'object' ? mathJax.tex : null;\n"
|
|
4064
|
+
+ " return {\n"
|
|
4065
|
+
+ " inlineDollar: Boolean(tex && delimiterListIncludes(tex.inlineMath, '$', '$')),\n"
|
|
4066
|
+
+ " displayDollar: Boolean(tex && delimiterListIncludes(tex.displayMath, '$$', '$$')),\n"
|
|
4067
|
+
+ " };\n"
|
|
4068
|
+
+ " }\n"
|
|
4069
|
+
+ " function isEscapedAt(text, index) {\n"
|
|
4070
|
+
+ " let count = 0;\n"
|
|
4071
|
+
+ " let pos = index - 1;\n"
|
|
4072
|
+
+ " while (pos >= 0 && text.charAt(pos) === '\\\\') { count += 1; pos -= 1; }\n"
|
|
4073
|
+
+ " return count % 2 === 1;\n"
|
|
4074
|
+
+ " }\n"
|
|
4075
|
+
+ " function findUnescapedDelimiter(text, delimiter, fromIndex) {\n"
|
|
4076
|
+
+ " let index = Math.max(0, Number(fromIndex) || 0);\n"
|
|
4077
|
+
+ " while (index < text.length) {\n"
|
|
4078
|
+
+ " index = text.indexOf(delimiter, index);\n"
|
|
4079
|
+
+ " if (index < 0) return -1;\n"
|
|
4080
|
+
+ " if (!isEscapedAt(text, index)) return index;\n"
|
|
4081
|
+
+ " index += Math.max(1, delimiter.length);\n"
|
|
4082
|
+
+ " }\n"
|
|
4083
|
+
+ " return -1;\n"
|
|
4084
|
+
+ " }\n"
|
|
4085
|
+
+ " function textMightContainMath(text, config) {\n"
|
|
4086
|
+
+ " if (!text) return false;\n"
|
|
4087
|
+
+ " if (text.indexOf('\\\\(') !== -1 || text.indexOf('\\\\[') !== -1) return true;\n"
|
|
4088
|
+
+ " return Boolean((config.inlineDollar || config.displayDollar) && text.indexOf('$') !== -1);\n"
|
|
4089
|
+
+ " }\n"
|
|
4090
|
+
+ " function findNextMathSegment(text, startIndex, config) {\n"
|
|
4091
|
+
+ " for (let index = startIndex; index < text.length; index += 1) {\n"
|
|
4092
|
+
+ " if (text.startsWith('\\\\(', index)) {\n"
|
|
4093
|
+
+ " const end = findUnescapedDelimiter(text, '\\\\)', index + 2);\n"
|
|
4094
|
+
+ " if (end > index + 2) return { start: index, end: end + 2, tex: text.slice(index + 2, end).trim(), display: false };\n"
|
|
4095
|
+
+ " }\n"
|
|
4096
|
+
+ " if (text.startsWith('\\\\[', index)) {\n"
|
|
4097
|
+
+ " const end = findUnescapedDelimiter(text, '\\\\]', index + 2);\n"
|
|
4098
|
+
+ " if (end > index + 2) return { start: index, end: end + 2, tex: text.slice(index + 2, end).trim(), display: true };\n"
|
|
4099
|
+
+ " }\n"
|
|
4100
|
+
+ " if (config.displayDollar && text.startsWith('$$', index) && !isEscapedAt(text, index)) {\n"
|
|
4101
|
+
+ " const end = findUnescapedDelimiter(text, '$$', index + 2);\n"
|
|
4102
|
+
+ " if (end > index + 2) return { start: index, end: end + 2, tex: text.slice(index + 2, end).trim(), display: true };\n"
|
|
4103
|
+
+ " }\n"
|
|
4104
|
+
+ " if (config.inlineDollar && text.charAt(index) === '$' && text.charAt(index + 1) !== '$' && !isEscapedAt(text, index)) {\n"
|
|
4105
|
+
+ " const end = findUnescapedDelimiter(text, '$', index + 1);\n"
|
|
4106
|
+
+ " if (end > index + 1) return { start: index, end: end + 1, tex: text.slice(index + 1, end).trim(), display: false };\n"
|
|
4107
|
+
+ " }\n"
|
|
4108
|
+
+ " }\n"
|
|
4109
|
+
+ " return null;\n"
|
|
4110
|
+
+ " }\n"
|
|
4111
|
+
+ " function parseHtmlMathSegments(text, config, maxCount) {\n"
|
|
4112
|
+
+ " const segments = [];\n"
|
|
4113
|
+
+ " let index = 0;\n"
|
|
4114
|
+
+ " const maxSegments = Math.max(1, Number(maxCount) || 1);\n"
|
|
4115
|
+
+ " while (index < text.length && segments.length < maxSegments) {\n"
|
|
4116
|
+
+ " const segment = findNextMathSegment(text, index, config);\n"
|
|
4117
|
+
+ " if (!segment) break;\n"
|
|
4118
|
+
+ " if (segment.tex) segments.push(segment);\n"
|
|
4119
|
+
+ " index = Math.max(segment.end, segment.start + 1);\n"
|
|
4120
|
+
+ " }\n"
|
|
4121
|
+
+ " return segments;\n"
|
|
4122
|
+
+ " }\n"
|
|
4123
|
+
+ " function shouldSkipHtmlMathTextNode(node) {\n"
|
|
4124
|
+
+ " let el = node && node.parentElement;\n"
|
|
4125
|
+
+ " while (el) {\n"
|
|
4126
|
+
+ " const tag = el.tagName ? el.tagName.toLowerCase() : '';\n"
|
|
4127
|
+
+ " if (['script', 'style', 'textarea', 'pre', 'code', 'math', 'svg', 'mjx-container'].indexOf(tag) !== -1) return true;\n"
|
|
4128
|
+
+ " if (el.classList && (el.classList.contains('pi-studio-html-math') || el.classList.contains('MathJax'))) return true;\n"
|
|
4129
|
+
+ " el = el.parentElement;\n"
|
|
4130
|
+
+ " }\n"
|
|
4131
|
+
+ " return false;\n"
|
|
4132
|
+
+ " }\n"
|
|
4133
|
+
+ " function replaceTextNodeWithHtmlMathPlaceholders(node, segments) {\n"
|
|
4134
|
+
+ " if (!node || !node.parentNode || !segments || segments.length === 0) return [];\n"
|
|
4135
|
+
+ " const text = String(node.nodeValue || '');\n"
|
|
4136
|
+
+ " const fragment = document.createDocumentFragment();\n"
|
|
4137
|
+
+ " const items = [];\n"
|
|
4138
|
+
+ " let index = 0;\n"
|
|
4139
|
+
+ " segments.forEach((segment) => {\n"
|
|
4140
|
+
+ " if (segment.start > index) fragment.appendChild(document.createTextNode(text.slice(index, segment.start)));\n"
|
|
4141
|
+
+ " const mathId = PREVIEW_ID + '_math_' + (++htmlMathSerial).toString(36);\n"
|
|
4142
|
+
+ " const span = document.createElement('span');\n"
|
|
4143
|
+
+ " span.className = 'pi-studio-html-math pi-studio-html-math-' + (segment.display ? 'display' : 'inline');\n"
|
|
4144
|
+
+ " span.setAttribute('data-pi-studio-html-math-id', mathId);\n"
|
|
4145
|
+
+ " span.setAttribute('aria-busy', 'true');\n"
|
|
4146
|
+
+ " span.textContent = text.slice(segment.start, segment.end);\n"
|
|
4147
|
+
+ " htmlMathPlaceholders.set(mathId, span);\n"
|
|
4148
|
+
+ " items.push({ mathId, tex: segment.tex, display: Boolean(segment.display) });\n"
|
|
4149
|
+
+ " fragment.appendChild(span);\n"
|
|
4150
|
+
+ " index = segment.end;\n"
|
|
4151
|
+
+ " });\n"
|
|
4152
|
+
+ " if (index < text.length) fragment.appendChild(document.createTextNode(text.slice(index)));\n"
|
|
4153
|
+
+ " node.parentNode.replaceChild(fragment, node);\n"
|
|
4154
|
+
+ " return items;\n"
|
|
4155
|
+
+ " }\n"
|
|
4156
|
+
+ " function applyRenderedHtmlMath(results) {\n"
|
|
4157
|
+
+ " if (!Array.isArray(results)) return;\n"
|
|
4158
|
+
+ " results.forEach((result) => {\n"
|
|
4159
|
+
+ " if (!result || typeof result !== 'object') return;\n"
|
|
4160
|
+
+ " const mathId = typeof result.mathId === 'string' ? result.mathId : '';\n"
|
|
4161
|
+
+ " const placeholder = mathId ? htmlMathPlaceholders.get(mathId) : null;\n"
|
|
4162
|
+
+ " if (!placeholder || !placeholder.isConnected) return;\n"
|
|
4163
|
+
+ " placeholder.removeAttribute('aria-busy');\n"
|
|
4164
|
+
+ " if (result.ok === true && typeof result.html === 'string' && result.html.trim()) {\n"
|
|
4165
|
+
+ " placeholder.innerHTML = result.html;\n"
|
|
4166
|
+
+ " placeholder.classList.add('pi-studio-html-math-rendered');\n"
|
|
4167
|
+
+ " } else {\n"
|
|
4168
|
+
+ " placeholder.classList.add('pi-studio-html-math-failed');\n"
|
|
4169
|
+
+ " if (typeof result.error === 'string' && result.error) placeholder.title = result.error;\n"
|
|
4170
|
+
+ " }\n"
|
|
4171
|
+
+ " htmlMathPlaceholders.delete(mathId);\n"
|
|
4172
|
+
+ " });\n"
|
|
4173
|
+
+ " scheduleHeight();\n"
|
|
4174
|
+
+ " }\n"
|
|
4175
|
+
+ " function runHtmlMathRenderScan() {\n"
|
|
4176
|
+
+ " htmlMathScanScheduled = false;\n"
|
|
4177
|
+
+ " if (!document.body || typeof document.createTreeWalker !== 'function') return;\n"
|
|
4178
|
+
+ " const nodeFilterApi = typeof NodeFilter !== 'undefined' ? NodeFilter : { SHOW_TEXT: 4, FILTER_ACCEPT: 1, FILTER_REJECT: 2 };\n"
|
|
4179
|
+
+ " const config = getHtmlMathDelimiterConfig();\n"
|
|
4180
|
+
+ " const nodes = [];\n"
|
|
4181
|
+
+ " const walker = document.createTreeWalker(document.body, nodeFilterApi.SHOW_TEXT, {\n"
|
|
4182
|
+
+ " acceptNode(node) {\n"
|
|
4183
|
+
+ " const text = String(node && node.nodeValue || '');\n"
|
|
4184
|
+
+ " if (!textMightContainMath(text, config)) return nodeFilterApi.FILTER_REJECT;\n"
|
|
4185
|
+
+ " if (shouldSkipHtmlMathTextNode(node)) return nodeFilterApi.FILTER_REJECT;\n"
|
|
4186
|
+
+ " return nodeFilterApi.FILTER_ACCEPT;\n"
|
|
4187
|
+
+ " }\n"
|
|
4188
|
+
+ " });\n"
|
|
4189
|
+
+ " while (walker.nextNode()) nodes.push(walker.currentNode);\n"
|
|
4190
|
+
+ " const items = [];\n"
|
|
4191
|
+
+ " for (const node of nodes) {\n"
|
|
4192
|
+
+ " const remaining = 250 - items.length;\n"
|
|
4193
|
+
+ " if (remaining <= 0) break;\n"
|
|
4194
|
+
+ " const text = String(node && node.nodeValue || '');\n"
|
|
4195
|
+
+ " const segments = parseHtmlMathSegments(text, config, remaining);\n"
|
|
4196
|
+
+ " if (segments.length === 0) continue;\n"
|
|
4197
|
+
+ " items.push(...replaceTextNodeWithHtmlMathPlaceholders(node, segments));\n"
|
|
4198
|
+
+ " }\n"
|
|
4199
|
+
+ " if (items.length > 0) {\n"
|
|
4200
|
+
+ " try { parent.postMessage({ type: 'pi-studio-html-artifact-render-math', id: PREVIEW_ID, items }, '*'); } catch {}\n"
|
|
4201
|
+
+ " }\n"
|
|
4202
|
+
+ " }\n"
|
|
4203
|
+
+ " function scheduleHtmlMathRenderScan() {\n"
|
|
4204
|
+
+ " if (htmlMathScanScheduled) return;\n"
|
|
4205
|
+
+ " htmlMathScanScheduled = true;\n"
|
|
4206
|
+
+ " requestAnimationFrame(runHtmlMathRenderScan);\n"
|
|
4207
|
+
+ " }\n"
|
|
3813
4208
|
+ " window.addEventListener('message', (event) => {\n"
|
|
3814
4209
|
+ " const data = event && event.data;\n"
|
|
3815
|
-
+ " if (!data || typeof data !== 'object') return;\n"
|
|
3816
|
-
+ " if (data.type
|
|
3817
|
-
+ "
|
|
4210
|
+
+ " if (!data || typeof data !== 'object' || data.id !== PREVIEW_ID) return;\n"
|
|
4211
|
+
+ " if (data.type === 'pi-studio-html-artifact-zoom') {\n"
|
|
4212
|
+
+ " applyZoom(data.zoom);\n"
|
|
4213
|
+
+ " return;\n"
|
|
4214
|
+
+ " }\n"
|
|
4215
|
+
+ " if (data.type === 'pi-studio-html-artifact-math-rendered') {\n"
|
|
4216
|
+
+ " applyRenderedHtmlMath(data.results);\n"
|
|
4217
|
+
+ " }\n"
|
|
3818
4218
|
+ " });\n"
|
|
3819
|
-
+ "
|
|
4219
|
+
+ " document.addEventListener('click', handleFragmentAnchorClick);\n"
|
|
4220
|
+
+ " document.addEventListener('DOMContentLoaded', scheduleHtmlMathRenderScan);\n"
|
|
4221
|
+
+ " window.addEventListener('hashchange', () => {\n"
|
|
4222
|
+
+ " const hash = String(window.location && window.location.hash || '');\n"
|
|
4223
|
+
+ " if (hash) scrollFragmentIntoView(hash.slice(1), { smooth: false });\n"
|
|
4224
|
+
+ " });\n"
|
|
4225
|
+
+ " window.addEventListener('load', () => { scheduleHeight(); scheduleHtmlMathRenderScan(); });\n"
|
|
3820
4226
|
+ " window.addEventListener('resize', scheduleHeight);\n"
|
|
3821
4227
|
+ " if (typeof ResizeObserver === 'function') {\n"
|
|
3822
4228
|
+ " const observer = new ResizeObserver(scheduleHeight);\n"
|
|
@@ -3824,20 +4230,32 @@
|
|
|
3824
4230
|
+ " if (document.body) observer.observe(document.body);\n"
|
|
3825
4231
|
+ " }\n"
|
|
3826
4232
|
+ " if (typeof MutationObserver === 'function') {\n"
|
|
3827
|
-
+ " const observer = new MutationObserver(scheduleHeight);\n"
|
|
4233
|
+
+ " const observer = new MutationObserver(() => { scheduleHeight(); scheduleHtmlMathRenderScan(); });\n"
|
|
3828
4234
|
+ " observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true });\n"
|
|
3829
4235
|
+ " }\n"
|
|
3830
4236
|
+ " scheduleHeight();\n"
|
|
3831
4237
|
+ " setTimeout(scheduleHeight, 80);\n"
|
|
3832
4238
|
+ " setTimeout(scheduleHeight, 350);\n"
|
|
4239
|
+
+ " setTimeout(scheduleHtmlMathRenderScan, 0);\n"
|
|
4240
|
+
+ " setTimeout(scheduleHtmlMathRenderScan, 120);\n"
|
|
4241
|
+
+ " setTimeout(scheduleHtmlMathRenderScan, 500);\n"
|
|
3833
4242
|
+ "})();\n"
|
|
3834
4243
|
+ "<\/script>";
|
|
3835
4244
|
}
|
|
3836
4245
|
|
|
4246
|
+
function buildHtmlArtifactPreviewMathStyle() {
|
|
4247
|
+
return "<style data-pi-studio-html-preview-math>\n"
|
|
4248
|
+
+ ".pi-studio-html-math-display{display:block;margin:0.75em 0;overflow-x:auto;text-align:center;}\n"
|
|
4249
|
+
+ ".pi-studio-html-math-display>math{display:block;margin:0 auto;}\n"
|
|
4250
|
+
+ ".pi-studio-html-math-inline>math{vertical-align:-0.15em;}\n"
|
|
4251
|
+
+ "</style>\n";
|
|
4252
|
+
}
|
|
4253
|
+
|
|
3837
4254
|
function buildHtmlArtifactPreviewHeadMarkup(previewId) {
|
|
3838
4255
|
return "<meta charset=\"utf-8\">\n"
|
|
3839
4256
|
+ "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n"
|
|
3840
4257
|
+ "<meta http-equiv=\"Content-Security-Policy\" content=\"" + escapeHtml(HTML_ARTIFACT_PREVIEW_CSP) + "\">\n"
|
|
4258
|
+
+ buildHtmlArtifactPreviewMathStyle()
|
|
3841
4259
|
+ buildHtmlArtifactPreviewResizeScript(previewId);
|
|
3842
4260
|
}
|
|
3843
4261
|
|
|
@@ -3897,7 +4315,161 @@
|
|
|
3897
4315
|
}
|
|
3898
4316
|
}
|
|
3899
4317
|
|
|
4318
|
+
function handleHtmlArtifactFrameFragmentMessage(event) {
|
|
4319
|
+
const data = event && event.data;
|
|
4320
|
+
if (!data || typeof data !== "object" || data.type !== "pi-studio-html-artifact-fragment") return;
|
|
4321
|
+
const id = typeof data.id === "string" ? data.id : "";
|
|
4322
|
+
const record = id ? htmlArtifactFramesById.get(id) : null;
|
|
4323
|
+
if (!record || !record.iframe || !record.iframe.isConnected) {
|
|
4324
|
+
if (id) htmlArtifactFramesById.delete(id);
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
if (event.source && record.iframe.contentWindow && event.source !== record.iframe.contentWindow) return;
|
|
4328
|
+
if (record.shell && record.shell.classList && record.shell.classList.contains("is-focused")) return;
|
|
4329
|
+
|
|
4330
|
+
const scrollContainer = record.shell && typeof record.shell.closest === "function"
|
|
4331
|
+
? record.shell.closest(".panel-scroll")
|
|
4332
|
+
: null;
|
|
4333
|
+
const isCapped = Boolean(record.iframe.classList && record.iframe.classList.contains("is-height-capped"));
|
|
4334
|
+
const documentHeight = Number(data.documentHeight);
|
|
4335
|
+
const viewportHeight = Number(data.viewportHeight);
|
|
4336
|
+
const isInternallyScrollable = isCapped
|
|
4337
|
+
|| (Number.isFinite(documentHeight) && Number.isFinite(viewportHeight) && documentHeight > viewportHeight + 2);
|
|
4338
|
+
if (!scrollContainer || isInternallyScrollable) {
|
|
4339
|
+
if (typeof record.iframe.scrollIntoView === "function") {
|
|
4340
|
+
try {
|
|
4341
|
+
record.iframe.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth" });
|
|
4342
|
+
} catch {
|
|
4343
|
+
record.iframe.scrollIntoView(false);
|
|
4344
|
+
}
|
|
4345
|
+
}
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
const rawTargetTop = Number(data.targetTop);
|
|
4350
|
+
const offsetInFrame = Number.isFinite(rawTargetTop) && rawTargetTop > 0 ? rawTargetTop : 0;
|
|
4351
|
+
const iframeRect = record.iframe.getBoundingClientRect();
|
|
4352
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
4353
|
+
const topPadding = 12;
|
|
4354
|
+
const nextTop = Math.max(
|
|
4355
|
+
0,
|
|
4356
|
+
scrollContainer.scrollTop + iframeRect.top - containerRect.top + offsetInFrame - topPadding,
|
|
4357
|
+
);
|
|
4358
|
+
try {
|
|
4359
|
+
scrollContainer.scrollTo({ top: nextTop, behavior: "smooth" });
|
|
4360
|
+
} catch {
|
|
4361
|
+
scrollContainer.scrollTop = nextTop;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
function normalizeHtmlArtifactMathRenderItems(rawItems) {
|
|
4366
|
+
if (!Array.isArray(rawItems)) return [];
|
|
4367
|
+
return rawItems.slice(0, 250).map((item) => {
|
|
4368
|
+
const raw = item && typeof item === "object" ? item : null;
|
|
4369
|
+
const mathId = raw && typeof raw.mathId === "string" ? raw.mathId : "";
|
|
4370
|
+
const tex = raw && typeof raw.tex === "string" ? raw.tex : "";
|
|
4371
|
+
if (!mathId || !tex.trim()) return null;
|
|
4372
|
+
return {
|
|
4373
|
+
mathId,
|
|
4374
|
+
tex,
|
|
4375
|
+
display: Boolean(raw.display),
|
|
4376
|
+
};
|
|
4377
|
+
}).filter(Boolean);
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
async function fetchRenderedHtmlArtifactMath(items) {
|
|
4381
|
+
const token = getToken();
|
|
4382
|
+
if (!token) {
|
|
4383
|
+
throw new Error("Missing Studio token in URL.");
|
|
4384
|
+
}
|
|
4385
|
+
const response = await fetchWithTimeout("/render-math?token=" + encodeURIComponent(token), {
|
|
4386
|
+
method: "POST",
|
|
4387
|
+
headers: {
|
|
4388
|
+
"Content-Type": "application/json",
|
|
4389
|
+
},
|
|
4390
|
+
body: JSON.stringify({ items }),
|
|
4391
|
+
}, HTML_ARTIFACT_MATH_RENDER_FETCH_TIMEOUT_MS, "HTML preview math render");
|
|
4392
|
+
|
|
4393
|
+
const rawBody = await response.text();
|
|
4394
|
+
let payload = null;
|
|
4395
|
+
try {
|
|
4396
|
+
payload = rawBody ? JSON.parse(rawBody) : null;
|
|
4397
|
+
} catch {
|
|
4398
|
+
payload = null;
|
|
4399
|
+
}
|
|
4400
|
+
if (!response.ok) {
|
|
4401
|
+
const message = payload && typeof payload.error === "string"
|
|
4402
|
+
? payload.error
|
|
4403
|
+
: "HTML preview math render failed with HTTP " + response.status + ".";
|
|
4404
|
+
throw new Error(message);
|
|
4405
|
+
}
|
|
4406
|
+
if (!payload || payload.ok !== true || !Array.isArray(payload.results)) {
|
|
4407
|
+
throw new Error("HTML preview math renderer returned an invalid payload.");
|
|
4408
|
+
}
|
|
4409
|
+
return payload.results;
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
function postHtmlArtifactMathResults(record, results) {
|
|
4413
|
+
if (!record || !record.iframe || !record.iframe.isConnected || !record.iframe.contentWindow) return;
|
|
4414
|
+
try {
|
|
4415
|
+
record.iframe.contentWindow.postMessage({
|
|
4416
|
+
type: "pi-studio-html-artifact-math-rendered",
|
|
4417
|
+
id: record.id || "",
|
|
4418
|
+
results: Array.isArray(results) ? results : [],
|
|
4419
|
+
}, "*");
|
|
4420
|
+
} catch {
|
|
4421
|
+
// Ignore iframe postMessage failures.
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
async function renderHtmlArtifactMathItems(record, items) {
|
|
4426
|
+
if (!record || !Array.isArray(items) || items.length === 0) return;
|
|
4427
|
+
if (record.detail) record.detail.textContent = "HTML preview · rendering math";
|
|
4428
|
+
try {
|
|
4429
|
+
const results = await fetchRenderedHtmlArtifactMath(items);
|
|
4430
|
+
postHtmlArtifactMathResults(record, results);
|
|
4431
|
+
} catch (error) {
|
|
4432
|
+
console.error("HTML preview math render failed:", error);
|
|
4433
|
+
postHtmlArtifactMathResults(record, items.map((item) => ({
|
|
4434
|
+
mathId: item.mathId,
|
|
4435
|
+
ok: false,
|
|
4436
|
+
error: error && error.message ? error.message : String(error || "HTML preview math render failed."),
|
|
4437
|
+
})));
|
|
4438
|
+
} finally {
|
|
4439
|
+
if (record.detail) record.detail.textContent = "HTML preview";
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
function handleHtmlArtifactFrameMathRenderMessage(event) {
|
|
4444
|
+
const data = event && event.data;
|
|
4445
|
+
if (!data || typeof data !== "object" || data.type !== "pi-studio-html-artifact-render-math") return;
|
|
4446
|
+
const id = typeof data.id === "string" ? data.id : "";
|
|
4447
|
+
const record = id ? htmlArtifactFramesById.get(id) : null;
|
|
4448
|
+
if (!record || !record.iframe || !record.iframe.isConnected) {
|
|
4449
|
+
if (id) htmlArtifactFramesById.delete(id);
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4452
|
+
if (event.source && record.iframe.contentWindow && event.source !== record.iframe.contentWindow) return;
|
|
4453
|
+
const items = normalizeHtmlArtifactMathRenderItems(data.items);
|
|
4454
|
+
if (items.length === 0) return;
|
|
4455
|
+
|
|
4456
|
+
record.mathRenderBatchCount = Math.max(0, Number(record.mathRenderBatchCount) || 0) + 1;
|
|
4457
|
+
record.mathRenderItemCount = Math.max(0, Number(record.mathRenderItemCount) || 0) + items.length;
|
|
4458
|
+
if (record.mathRenderBatchCount > 24 || record.mathRenderItemCount > 1000) {
|
|
4459
|
+
postHtmlArtifactMathResults(record, items.map((item) => ({
|
|
4460
|
+
mathId: item.mathId,
|
|
4461
|
+
ok: false,
|
|
4462
|
+
error: "HTML preview math render limit reached.",
|
|
4463
|
+
})));
|
|
4464
|
+
return;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
void renderHtmlArtifactMathItems(record, items);
|
|
4468
|
+
}
|
|
4469
|
+
|
|
3900
4470
|
window.addEventListener("message", handleHtmlArtifactFrameSizeMessage);
|
|
4471
|
+
window.addEventListener("message", handleHtmlArtifactFrameFragmentMessage);
|
|
4472
|
+
window.addEventListener("message", handleHtmlArtifactFrameMathRenderMessage);
|
|
3901
4473
|
|
|
3902
4474
|
function isStudioHtmlFocusOpen() {
|
|
3903
4475
|
return Boolean(studioHtmlFocusOverlayEl && studioHtmlFocusOverlayEl.hidden === false && studioHtmlFocusShellEl);
|
|
@@ -4205,7 +4777,7 @@
|
|
|
4205
4777
|
iframe.addEventListener("load", () => { postArtifactZoom(); });
|
|
4206
4778
|
iframe.srcdoc = buildHtmlArtifactSrcdoc(html, previewId);
|
|
4207
4779
|
shell.appendChild(iframe);
|
|
4208
|
-
htmlArtifactFramesById.set(previewId, { iframe, shell, detail, zoomControls });
|
|
4780
|
+
htmlArtifactFramesById.set(previewId, { id: previewId, iframe, shell, detail, zoomControls, mathRenderBatchCount: 0, mathRenderItemCount: 0 });
|
|
4209
4781
|
|
|
4210
4782
|
targetEl.appendChild(shell);
|
|
4211
4783
|
|
|
@@ -5549,12 +6121,34 @@
|
|
|
5549
6121
|
const source = codeEl ? codeEl.textContent : preEl.textContent;
|
|
5550
6122
|
|
|
5551
6123
|
const wrapper = document.createElement("div");
|
|
5552
|
-
wrapper.className = "mermaid-container";
|
|
6124
|
+
wrapper.className = "mermaid-container studio-copyable-block";
|
|
6125
|
+
if (wrapper.dataset) {
|
|
6126
|
+
wrapper.dataset.mermaidSource = source || "";
|
|
6127
|
+
wrapper.dataset.studioCopyDecorated = "1";
|
|
6128
|
+
}
|
|
5553
6129
|
|
|
5554
6130
|
const diagramEl = document.createElement("div");
|
|
5555
6131
|
diagramEl.className = "mermaid";
|
|
5556
6132
|
diagramEl.textContent = source || "";
|
|
5557
6133
|
|
|
6134
|
+
const copyBtn = document.createElement("button");
|
|
6135
|
+
copyBtn.type = "button";
|
|
6136
|
+
copyBtn.className = "studio-copy-block-btn studio-copy-mermaid-source-btn";
|
|
6137
|
+
copyBtn.textContent = "Copy source";
|
|
6138
|
+
copyBtn.title = "Copy this Mermaid source to the clipboard.";
|
|
6139
|
+
copyBtn.setAttribute("aria-label", "Copy Mermaid source to the clipboard");
|
|
6140
|
+
copyBtn.addEventListener("pointerdown", (event) => {
|
|
6141
|
+
event.stopPropagation();
|
|
6142
|
+
});
|
|
6143
|
+
copyBtn.addEventListener("mousedown", (event) => {
|
|
6144
|
+
event.stopPropagation();
|
|
6145
|
+
});
|
|
6146
|
+
|
|
6147
|
+
const toolbarEl = document.createElement("div");
|
|
6148
|
+
toolbarEl.className = "mermaid-source-toolbar";
|
|
6149
|
+
toolbarEl.appendChild(copyBtn);
|
|
6150
|
+
|
|
6151
|
+
wrapper.appendChild(toolbarEl);
|
|
5558
6152
|
wrapper.appendChild(diagramEl);
|
|
5559
6153
|
preEl.replaceWith(wrapper);
|
|
5560
6154
|
});
|
|
@@ -6130,6 +6724,9 @@
|
|
|
6130
6724
|
|
|
6131
6725
|
function getCopyablePreviewBlockText(blockEl) {
|
|
6132
6726
|
if (!blockEl || typeof blockEl.querySelectorAll !== "function") return "";
|
|
6727
|
+
if (blockEl.classList && blockEl.classList.contains("mermaid-container") && blockEl.dataset && typeof blockEl.dataset.mermaidSource === "string") {
|
|
6728
|
+
return normalizeCopyableBlockText(blockEl.dataset.mermaidSource);
|
|
6729
|
+
}
|
|
6133
6730
|
if (blockEl.classList && blockEl.classList.contains("preview-code-lines")) {
|
|
6134
6731
|
return normalizeCopyableBlockText(
|
|
6135
6732
|
Array.from(blockEl.querySelectorAll(".preview-code-line-content"))
|
|
@@ -9139,6 +9736,10 @@
|
|
|
9139
9736
|
persistStoredToggle(ANNOTATION_MODE_STORAGE_KEY, enabled);
|
|
9140
9737
|
}
|
|
9141
9738
|
|
|
9739
|
+
function isShortcutsOpen() {
|
|
9740
|
+
return Boolean(shortcutsOverlayEl && !shortcutsOverlayEl.hidden);
|
|
9741
|
+
}
|
|
9742
|
+
|
|
9142
9743
|
function isScratchpadOpen() {
|
|
9143
9744
|
return Boolean(scratchpadOverlayEl && !scratchpadOverlayEl.hidden);
|
|
9144
9745
|
}
|
|
@@ -9152,7 +9753,7 @@
|
|
|
9152
9753
|
}
|
|
9153
9754
|
|
|
9154
9755
|
function syncModalOpenState() {
|
|
9155
|
-
document.body.classList.toggle("scratchpad-open", isScratchpadOpen());
|
|
9756
|
+
document.body.classList.toggle("scratchpad-open", isScratchpadOpen() || isShortcutsOpen());
|
|
9156
9757
|
}
|
|
9157
9758
|
|
|
9158
9759
|
function describeStudioDocument(state) {
|
|
@@ -13348,6 +13949,41 @@
|
|
|
13348
13949
|
updateScratchpadUi();
|
|
13349
13950
|
}
|
|
13350
13951
|
|
|
13952
|
+
function closeShortcuts(options) {
|
|
13953
|
+
if (!shortcutsOverlayEl || shortcutsOverlayEl.hidden) return;
|
|
13954
|
+
shortcutsOverlayEl.hidden = true;
|
|
13955
|
+
syncModalOpenState();
|
|
13956
|
+
const focusTarget = options && Object.prototype.hasOwnProperty.call(options, "focusTarget")
|
|
13957
|
+
? options.focusTarget
|
|
13958
|
+
: (shortcutsBtn || sourceTextEl);
|
|
13959
|
+
if (focusTarget && typeof focusTarget.focus === "function") {
|
|
13960
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
13961
|
+
? window.requestAnimationFrame.bind(window)
|
|
13962
|
+
: (cb) => window.setTimeout(cb, 16);
|
|
13963
|
+
schedule(() => focusTarget.focus());
|
|
13964
|
+
}
|
|
13965
|
+
}
|
|
13966
|
+
|
|
13967
|
+
function openShortcuts() {
|
|
13968
|
+
if (!shortcutsOverlayEl) return;
|
|
13969
|
+
if (isScratchpadOpen()) closeScratchpad({ focusTarget: null });
|
|
13970
|
+
if (isReviewNotesOpen()) closeReviewNotes({ focusTarget: null });
|
|
13971
|
+
if (isOutlineOpen()) closeOutline({ focusTarget: null });
|
|
13972
|
+
shortcutsOverlayEl.hidden = false;
|
|
13973
|
+
syncModalOpenState();
|
|
13974
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
13975
|
+
? window.requestAnimationFrame.bind(window)
|
|
13976
|
+
: (cb) => window.setTimeout(cb, 16);
|
|
13977
|
+
schedule(() => {
|
|
13978
|
+
if (shortcutsCloseBtn && typeof shortcutsCloseBtn.focus === "function") shortcutsCloseBtn.focus();
|
|
13979
|
+
});
|
|
13980
|
+
}
|
|
13981
|
+
|
|
13982
|
+
function toggleShortcuts() {
|
|
13983
|
+
if (isShortcutsOpen()) closeShortcuts({ focusTarget: shortcutsBtn || sourceTextEl });
|
|
13984
|
+
else openShortcuts();
|
|
13985
|
+
}
|
|
13986
|
+
|
|
13351
13987
|
function closeScratchpad(options) {
|
|
13352
13988
|
if (!scratchpadOverlayEl || scratchpadOverlayEl.hidden) return;
|
|
13353
13989
|
scratchpadOverlayEl.hidden = true;
|
|
@@ -15699,6 +16335,26 @@
|
|
|
15699
16335
|
});
|
|
15700
16336
|
}
|
|
15701
16337
|
|
|
16338
|
+
if (shortcutsBtn) {
|
|
16339
|
+
shortcutsBtn.addEventListener("click", () => {
|
|
16340
|
+
toggleShortcuts();
|
|
16341
|
+
});
|
|
16342
|
+
}
|
|
16343
|
+
|
|
16344
|
+
if (shortcutsCloseBtn) {
|
|
16345
|
+
shortcutsCloseBtn.addEventListener("click", () => {
|
|
16346
|
+
closeShortcuts();
|
|
16347
|
+
});
|
|
16348
|
+
}
|
|
16349
|
+
|
|
16350
|
+
if (shortcutsOverlayEl) {
|
|
16351
|
+
shortcutsOverlayEl.addEventListener("click", (event) => {
|
|
16352
|
+
if (event.target === shortcutsOverlayEl) {
|
|
16353
|
+
closeShortcuts();
|
|
16354
|
+
}
|
|
16355
|
+
});
|
|
16356
|
+
}
|
|
16357
|
+
|
|
15702
16358
|
if (scratchpadBtn) {
|
|
15703
16359
|
scratchpadBtn.addEventListener("click", () => {
|
|
15704
16360
|
openScratchpad();
|