pi-studio 0.5.47 → 0.5.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/client/studio-client.js +146 -24
- package/client/studio.css +18 -0
- package/index.ts +40 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.48] — 2026-04-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Editor-preview comments now also work for code, plain-text, and diff files using line-based preview selection anchors, so code/text review can use the same local comments workflow as Markdown preview.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- LaTeX editor preview now correctly treats `.tex`/LaTeX editor content as LaTeX in preview even when the document is only a fragment rather than a full `\documentclass` wrapper.
|
|
14
|
+
- Ordinary response preview no longer flips into LaTeX mode just because quoted LaTeX commands like `` `\\documentclass` `` or `` `\\begin{document}` `` appear in a normal response.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- LaTeX preview comments remain disabled for now; the earlier heuristic preview-side mapping attempts were removed in favor of keeping the rendering fixes and waiting for a more pipeline-grounded LaTeX comment design.
|
|
18
|
+
|
|
7
19
|
## [0.5.47] — 2026-04-07
|
|
8
20
|
|
|
9
21
|
### Changed
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
|
|
|
18
18
|
- Supports one canonical full Studio view per Pi session, plus additional editor-only companion views when you just want extra editing/preview surfaces
|
|
19
19
|
- Runs editor text directly, or asks for structured critique (auto/writing/code focus)
|
|
20
20
|
- Includes a local persistent scratchpad for quick notes you want to keep out of the main editor until you're ready to copy or insert them
|
|
21
|
-
- Includes local comments anchored to selections/lines, shown in a docked **Comments** rail, with **Comment** actions from raw-editor
|
|
21
|
+
- Includes local comments anchored to selections/lines, shown in a docked **Comments** rail, with **Comment** actions from raw-editor selections plus editor-preview selections for Markdown and code/text/diff previews, alongside optional inline `[an: ...]` toggles when you want comments reflected in the document text
|
|
22
22
|
- Browses response history (`Prev/Next/Last`) and loads either:
|
|
23
23
|
- response text
|
|
24
24
|
- critique notes/full critique
|
package/client/studio-client.js
CHANGED
|
@@ -1534,19 +1534,25 @@
|
|
|
1534
1534
|
|
|
1535
1535
|
function sanitizeRenderedHtml(html, markdown) {
|
|
1536
1536
|
const rawHtml = typeof html === "string" ? html : "";
|
|
1537
|
-
const
|
|
1538
|
-
.
|
|
1539
|
-
|
|
1537
|
+
const mathAnnotationPreserved = rawHtml.replace(/<math\b([^>]*)>([\s\S]*?)<\/math>/gi, (match, attrs, inner) => {
|
|
1538
|
+
const texAnnotationMatch = String(inner || "").match(/<annotation\b[^>]*encoding="application\/x-tex"[^>]*>([\s\S]*?)<\/annotation>/i);
|
|
1539
|
+
const texSource = texAnnotationMatch ? String(texAnnotationMatch[1] || "").trim() : "";
|
|
1540
|
+
const cleanedInner = String(inner || "")
|
|
1541
|
+
.replace(/<annotation-xml\b[\s\S]*?<\/annotation-xml>/gi, "")
|
|
1542
|
+
.replace(/<annotation\b[\s\S]*?<\/annotation>/gi, "");
|
|
1543
|
+
const texAttr = texSource ? (" data-tex-source=\"" + escapeHtml(texSource) + "\"") : "";
|
|
1544
|
+
return "<math" + attrs + texAttr + ">" + cleanedInner + "</math>";
|
|
1545
|
+
});
|
|
1540
1546
|
|
|
1541
1547
|
if (window.DOMPurify && typeof window.DOMPurify.sanitize === "function") {
|
|
1542
|
-
return window.DOMPurify.sanitize(
|
|
1548
|
+
return window.DOMPurify.sanitize(mathAnnotationPreserved, {
|
|
1543
1549
|
USE_PROFILES: {
|
|
1544
1550
|
html: true,
|
|
1545
1551
|
mathMl: true,
|
|
1546
1552
|
svg: true,
|
|
1547
1553
|
},
|
|
1548
1554
|
ADD_TAGS: ["embed"],
|
|
1549
|
-
ADD_ATTR: ["src", "type", "title", "width", "height", "style", "data-fig-align"],
|
|
1555
|
+
ADD_ATTR: ["src", "type", "title", "width", "height", "style", "data-fig-align", "data-tex-source"],
|
|
1550
1556
|
ADD_DATA_URI_TAGS: ["embed"],
|
|
1551
1557
|
});
|
|
1552
1558
|
}
|
|
@@ -2275,7 +2281,7 @@
|
|
|
2275
2281
|
}
|
|
2276
2282
|
}
|
|
2277
2283
|
|
|
2278
|
-
async function renderMarkdownWithPandoc(markdown) {
|
|
2284
|
+
async function renderMarkdownWithPandoc(markdown, options) {
|
|
2279
2285
|
const token = getToken();
|
|
2280
2286
|
if (!token) {
|
|
2281
2287
|
throw new Error("Missing Studio token in URL.");
|
|
@@ -2288,20 +2294,26 @@
|
|
|
2288
2294
|
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
2289
2295
|
const timeoutId = controller ? window.setTimeout(() => controller.abort(), 8000) : null;
|
|
2290
2296
|
|
|
2297
|
+
const previewOptions = options && typeof options === "object" ? options : {};
|
|
2298
|
+
|
|
2291
2299
|
let response;
|
|
2292
2300
|
try {
|
|
2293
2301
|
const effectivePath = getEffectiveSavePath();
|
|
2294
2302
|
const sourcePath = effectivePath || sourceState.path || "";
|
|
2303
|
+
const payload = {
|
|
2304
|
+
markdown: String(markdown || ""),
|
|
2305
|
+
sourcePath: sourcePath,
|
|
2306
|
+
resourceDir: (!sourcePath && resourceDirInput) ? resourceDirInput.value.trim() : "",
|
|
2307
|
+
};
|
|
2308
|
+
if (previewOptions.includeEditorLanguage) {
|
|
2309
|
+
payload.editorLanguage = String(editorLanguage || "");
|
|
2310
|
+
}
|
|
2295
2311
|
response = await fetch("/render-preview?token=" + encodeURIComponent(token), {
|
|
2296
2312
|
method: "POST",
|
|
2297
2313
|
headers: {
|
|
2298
2314
|
"Content-Type": "application/json",
|
|
2299
2315
|
},
|
|
2300
|
-
body: JSON.stringify(
|
|
2301
|
-
markdown: String(markdown || ""),
|
|
2302
|
-
sourcePath: sourcePath,
|
|
2303
|
-
resourceDir: (!sourcePath && resourceDirInput) ? resourceDirInput.value.trim() : "",
|
|
2304
|
-
}),
|
|
2316
|
+
body: JSON.stringify(payload),
|
|
2305
2317
|
signal: controller ? controller.signal : undefined,
|
|
2306
2318
|
});
|
|
2307
2319
|
} catch (error) {
|
|
@@ -2526,7 +2538,9 @@
|
|
|
2526
2538
|
: { markdown: stripAnnotationMarkers(String(markdown || "")), placeholders: [] };
|
|
2527
2539
|
|
|
2528
2540
|
try {
|
|
2529
|
-
const renderedHtml = await renderMarkdownWithPandoc(previewPrepared.markdown
|
|
2541
|
+
const renderedHtml = await renderMarkdownWithPandoc(previewPrepared.markdown, {
|
|
2542
|
+
includeEditorLanguage: pane === "source" || rightView === "editor-preview",
|
|
2543
|
+
});
|
|
2530
2544
|
|
|
2531
2545
|
if (pane === "source") {
|
|
2532
2546
|
if (nonce !== sourcePreviewRenderNonce || editorView !== "preview") return;
|
|
@@ -2591,9 +2605,8 @@
|
|
|
2591
2605
|
function renderSourcePreviewNow() {
|
|
2592
2606
|
if (editorView !== "preview") return;
|
|
2593
2607
|
const text = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
2594
|
-
if (
|
|
2595
|
-
|
|
2596
|
-
sourcePreviewEl.innerHTML = "<div class='response-markdown-highlight'>" + highlightCode(text, editorLanguage, "preview") + "</div>";
|
|
2608
|
+
if (supportsCodePreviewCommentsForCurrentEditor()) {
|
|
2609
|
+
renderCodePreviewWithCommentBlocks(sourcePreviewEl, text, "source");
|
|
2597
2610
|
return;
|
|
2598
2611
|
}
|
|
2599
2612
|
const nonce = ++sourcePreviewRenderNonce;
|
|
@@ -2660,10 +2673,8 @@
|
|
|
2660
2673
|
scheduleResponsePaneRepaintNudge();
|
|
2661
2674
|
return;
|
|
2662
2675
|
}
|
|
2663
|
-
if (
|
|
2664
|
-
|
|
2665
|
-
critiqueViewEl.innerHTML = "<div class='response-markdown-highlight'>" + highlightCode(editorText, editorLanguage, "preview") + "</div>";
|
|
2666
|
-
scheduleResponsePaneRepaintNudge();
|
|
2676
|
+
if (supportsCodePreviewCommentsForCurrentEditor()) {
|
|
2677
|
+
renderCodePreviewWithCommentBlocks(critiqueViewEl, editorText, "response");
|
|
2667
2678
|
return;
|
|
2668
2679
|
}
|
|
2669
2680
|
const nonce = ++responsePreviewRenderNonce;
|
|
@@ -3798,6 +3809,68 @@
|
|
|
3798
3809
|
return out.join("<br>");
|
|
3799
3810
|
}
|
|
3800
3811
|
|
|
3812
|
+
function supportsCodePreviewCommentsForCurrentEditor() {
|
|
3813
|
+
return Boolean(editorLanguage) && editorLanguage !== "markdown" && editorLanguage !== "latex";
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
function getCodePreviewCommentKind(language) {
|
|
3817
|
+
const lang = normalizeFenceLanguage(language || "");
|
|
3818
|
+
if (lang === "diff") return "diff-line";
|
|
3819
|
+
if (lang === "text") return "text-line";
|
|
3820
|
+
return "code-line";
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
function buildCodePreviewHtmlWithCommentBlocks(text, language) {
|
|
3824
|
+
const source = String(text || "").replace(/\r\n/g, "\n");
|
|
3825
|
+
const lines = source.split("\n");
|
|
3826
|
+
const lang = normalizeFenceLanguage(language || "");
|
|
3827
|
+
const kind = getCodePreviewCommentKind(lang);
|
|
3828
|
+
const html = [];
|
|
3829
|
+
let offset = 0;
|
|
3830
|
+
|
|
3831
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
3832
|
+
const line = String(lines[lineIndex] || "");
|
|
3833
|
+
const start = offset;
|
|
3834
|
+
const end = start + line.length;
|
|
3835
|
+
const lineNumber = lineIndex + 1;
|
|
3836
|
+
const lineHtml = line.length === 0
|
|
3837
|
+
? "<span class='hl-code'>" + EMPTY_OVERLAY_LINE + "</span>"
|
|
3838
|
+
: (lang ? highlightCodeLine(line, lang, "preview") : escapeHtml(line));
|
|
3839
|
+
|
|
3840
|
+
html.push(
|
|
3841
|
+
"<div class='preview-comment-block preview-comment-line-block'"
|
|
3842
|
+
+ " data-review-note-start='" + String(start) + "'"
|
|
3843
|
+
+ " data-review-note-end='" + String(end) + "'"
|
|
3844
|
+
+ " data-review-note-line-start='" + String(lineNumber) + "'"
|
|
3845
|
+
+ " data-review-note-line-end='" + String(lineNumber) + "'"
|
|
3846
|
+
+ " data-preview-comment-kind='" + escapeHtml(kind) + "'"
|
|
3847
|
+
+ ">"
|
|
3848
|
+
+ "<div class='preview-comment-controls'>"
|
|
3849
|
+
+ "<button type='button' class='preview-comment-summary' hidden></button>"
|
|
3850
|
+
+ "<button type='button' class='preview-comment-add'>Comment</button>"
|
|
3851
|
+
+ "</div>"
|
|
3852
|
+
+ "<div class='preview-comment-block-content preview-code-line-content'>" + lineHtml + "</div>"
|
|
3853
|
+
+ "</div>",
|
|
3854
|
+
);
|
|
3855
|
+
|
|
3856
|
+
offset = end + 1;
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
return "<div class='response-markdown-highlight preview-code-lines'>" + html.join("") + "</div>";
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
function renderCodePreviewWithCommentBlocks(targetEl, text, pane) {
|
|
3863
|
+
if (!targetEl) return;
|
|
3864
|
+
clearPreviewJumpHighlight(targetEl);
|
|
3865
|
+
finishPreviewRender(targetEl);
|
|
3866
|
+
targetEl.innerHTML = buildCodePreviewHtmlWithCommentBlocks(text, editorLanguage || "");
|
|
3867
|
+
updatePreviewCommentBlocksForElement(targetEl);
|
|
3868
|
+
if (pane === "response") {
|
|
3869
|
+
applyPendingResponseScrollReset();
|
|
3870
|
+
scheduleResponsePaneRepaintNudge();
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3801
3874
|
function detectLanguageFromName(name) {
|
|
3802
3875
|
if (!name) return "";
|
|
3803
3876
|
var dot = name.lastIndexOf(".");
|
|
@@ -4361,7 +4434,10 @@
|
|
|
4361
4434
|
}
|
|
4362
4435
|
|
|
4363
4436
|
function supportsPreviewCommentsForCurrentEditor() {
|
|
4364
|
-
|
|
4437
|
+
// LaTeX preview comments are intentionally disabled for now.
|
|
4438
|
+
// The initial client-side source/block heuristics were too unreliable on complex real documents.
|
|
4439
|
+
// Keep the independent LaTeX rendering fixes, but only enable preview comments where mapping is robust.
|
|
4440
|
+
return editorLanguage === "markdown" || supportsCodePreviewCommentsForCurrentEditor();
|
|
4365
4441
|
}
|
|
4366
4442
|
|
|
4367
4443
|
function getPreviewCommentBlockKindLabel(kind) {
|
|
@@ -4370,11 +4446,20 @@
|
|
|
4370
4446
|
if (kind === "list") return "list";
|
|
4371
4447
|
if (kind === "code") return "code block";
|
|
4372
4448
|
if (kind === "table") return "table";
|
|
4449
|
+
if (kind === "code-line") return "code line";
|
|
4450
|
+
if (kind === "diff-line") return "diff line";
|
|
4451
|
+
if (kind === "text-line") return "text line";
|
|
4373
4452
|
return "paragraph";
|
|
4374
4453
|
}
|
|
4375
4454
|
|
|
4376
4455
|
function supportsPreviewSelectionCommentsForBlockKind(kind) {
|
|
4377
|
-
return kind === "paragraph"
|
|
4456
|
+
return kind === "paragraph"
|
|
4457
|
+
|| kind === "heading"
|
|
4458
|
+
|| kind === "blockquote"
|
|
4459
|
+
|| kind === "list"
|
|
4460
|
+
|| kind === "code-line"
|
|
4461
|
+
|| kind === "diff-line"
|
|
4462
|
+
|| kind === "text-line";
|
|
4378
4463
|
}
|
|
4379
4464
|
|
|
4380
4465
|
function normalizeVisiblePreviewText(text) {
|
|
@@ -4711,6 +4796,11 @@
|
|
|
4711
4796
|
return safeStartA < safeEndB && safeStartB < safeEndA;
|
|
4712
4797
|
}
|
|
4713
4798
|
|
|
4799
|
+
function scanSourcePreviewCommentBlocks(markdown) {
|
|
4800
|
+
if (editorLanguage !== "markdown") return [];
|
|
4801
|
+
return scanMarkdownPreviewCommentBlocks(markdown);
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4714
4804
|
function scanMarkdownPreviewCommentBlocks(markdown) {
|
|
4715
4805
|
const source = String(markdown || "").replace(/\r\n/g, "\n");
|
|
4716
4806
|
const lines = source.split("\n");
|
|
@@ -5077,7 +5167,7 @@
|
|
|
5077
5167
|
|
|
5078
5168
|
function decorateRenderedEditorPreviewComments(targetEl, sourceText) {
|
|
5079
5169
|
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
5080
|
-
const sourceBlocks =
|
|
5170
|
+
const sourceBlocks = scanSourcePreviewCommentBlocks(sourceText);
|
|
5081
5171
|
const targetBlocks = collectPreviewCommentTargetElements(targetEl);
|
|
5082
5172
|
if (sourceBlocks.length === 0 || targetBlocks.length === 0) return;
|
|
5083
5173
|
|
|
@@ -5271,12 +5361,43 @@
|
|
|
5271
5361
|
return bestBlock;
|
|
5272
5362
|
}
|
|
5273
5363
|
|
|
5364
|
+
function getPreviewNoteNormalizedSelectionText(note) {
|
|
5365
|
+
const direct = normalizeVisiblePreviewText(note && (note.selectedDisplayText || note.selectedText) ? (note.selectedDisplayText || note.selectedText) : "");
|
|
5366
|
+
if (direct) return direct;
|
|
5367
|
+
return "";
|
|
5368
|
+
}
|
|
5369
|
+
|
|
5370
|
+
function findPreviewCommentBlockForNoteText(targetEl, note) {
|
|
5371
|
+
if (!targetEl || !note || typeof targetEl.querySelectorAll !== "function") return null;
|
|
5372
|
+
const selectionText = getPreviewNoteNormalizedSelectionText(note);
|
|
5373
|
+
if (!selectionText) return null;
|
|
5374
|
+
|
|
5375
|
+
let bestBlock = null;
|
|
5376
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
5377
|
+
Array.from(targetEl.querySelectorAll(".preview-comment-block")).forEach((blockEl) => {
|
|
5378
|
+
const contentEl = blockEl.querySelector(".preview-comment-block-content") || blockEl;
|
|
5379
|
+
const blockText = normalizeVisiblePreviewText(buildNormalizedDomTextMap(contentEl).text);
|
|
5380
|
+
if (!blockText) return;
|
|
5381
|
+
const matchIndex = blockText.indexOf(selectionText);
|
|
5382
|
+
if (matchIndex < 0) return;
|
|
5383
|
+
const lineStart = Math.max(1, Number(blockEl.dataset && blockEl.dataset.reviewNoteLineStart) || 1);
|
|
5384
|
+
const desiredLine = Math.max(1, Number(note && note.lineStart) || 1);
|
|
5385
|
+
const proximityPenalty = Math.abs(lineStart - desiredLine);
|
|
5386
|
+
const score = 1000000 - (matchIndex * 4) - proximityPenalty - Math.max(0, blockText.length - selectionText.length);
|
|
5387
|
+
if (score > bestScore) {
|
|
5388
|
+
bestScore = score;
|
|
5389
|
+
bestBlock = blockEl;
|
|
5390
|
+
}
|
|
5391
|
+
});
|
|
5392
|
+
return bestBlock;
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5274
5395
|
function revealReviewNoteInPreviewElement(targetEl, note) {
|
|
5275
5396
|
if (!targetEl || !note) return false;
|
|
5276
5397
|
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5277
5398
|
const range = resolveReviewNoteRange(note, source);
|
|
5278
5399
|
if (!range) return false;
|
|
5279
|
-
const blockEl = findPreviewCommentBlockForRange(targetEl, range);
|
|
5400
|
+
const blockEl = findPreviewCommentBlockForRange(targetEl, range) || findPreviewCommentBlockForNoteText(targetEl, note);
|
|
5280
5401
|
if (!blockEl) return false;
|
|
5281
5402
|
const contentEl = blockEl.querySelector(".preview-comment-block-content") || blockEl;
|
|
5282
5403
|
const inlineHighlightEl = createPreviewJumpInlineHighlight(contentEl, blockEl, note, range);
|
|
@@ -5288,6 +5409,7 @@
|
|
|
5288
5409
|
}
|
|
5289
5410
|
|
|
5290
5411
|
function revealReviewNoteInPreview(note) {
|
|
5412
|
+
if (!supportsPreviewCommentsForCurrentEditor()) return;
|
|
5291
5413
|
if (rightView === "editor-preview" && critiqueViewEl && critiqueViewEl.isConnected) {
|
|
5292
5414
|
revealReviewNoteInPreviewElement(critiqueViewEl, note);
|
|
5293
5415
|
}
|
|
@@ -7689,7 +7811,7 @@
|
|
|
7689
7811
|
event.preventDefault();
|
|
7690
7812
|
event.stopPropagation();
|
|
7691
7813
|
const mode = String(actionBtn.dataset && actionBtn.dataset.previewCommentMode ? actionBtn.dataset.previewCommentMode : "");
|
|
7692
|
-
if (mode
|
|
7814
|
+
if (!mode || !mode.startsWith("selection")) return;
|
|
7693
7815
|
addReviewNoteFromPreviewSelection(blockEl);
|
|
7694
7816
|
}
|
|
7695
7817
|
|
package/client/studio.css
CHANGED
|
@@ -898,6 +898,24 @@
|
|
|
898
898
|
scroll-margin-top: 24px;
|
|
899
899
|
}
|
|
900
900
|
|
|
901
|
+
.preview-comment-line-block {
|
|
902
|
+
min-height: 1.5em;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.preview-code-lines {
|
|
906
|
+
white-space: pre-wrap;
|
|
907
|
+
word-break: break-word;
|
|
908
|
+
overflow-wrap: anywhere;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.preview-code-line-content {
|
|
912
|
+
display: block;
|
|
913
|
+
min-height: 1.5em;
|
|
914
|
+
white-space: inherit;
|
|
915
|
+
word-break: inherit;
|
|
916
|
+
overflow-wrap: inherit;
|
|
917
|
+
}
|
|
918
|
+
|
|
901
919
|
.preview-comment-controls {
|
|
902
920
|
position: absolute;
|
|
903
921
|
top: 0;
|
package/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path
|
|
|
10
10
|
import { URL, pathToFileURL } from "node:url";
|
|
11
11
|
import { WebSocketServer, WebSocket, type RawData } from "ws";
|
|
12
12
|
import {
|
|
13
|
+
advancePastStudioInlineBacktickSpan,
|
|
13
14
|
collectStudioInlineAnnotationMarkers,
|
|
14
15
|
hasStudioMarkdownAnnotationMarkers,
|
|
15
16
|
isStudioAnnotationWordChar,
|
|
@@ -3263,6 +3264,26 @@ function inferStudioPdfLanguage(markdown: string, editorLanguage?: string): stri
|
|
|
3263
3264
|
return undefined;
|
|
3264
3265
|
}
|
|
3265
3266
|
|
|
3267
|
+
function stripStudioMarkdownInlineCodeSpans(markdown: string): string {
|
|
3268
|
+
const source = String(markdown ?? "");
|
|
3269
|
+
let out = "";
|
|
3270
|
+
let index = 0;
|
|
3271
|
+
while (index < source.length) {
|
|
3272
|
+
if (source[index] === "`") {
|
|
3273
|
+
index = advancePastStudioInlineBacktickSpan(source, index);
|
|
3274
|
+
continue;
|
|
3275
|
+
}
|
|
3276
|
+
out += source[index];
|
|
3277
|
+
index += 1;
|
|
3278
|
+
}
|
|
3279
|
+
return out;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
function isLikelyStandaloneLatexPreview(markdown: string): boolean {
|
|
3283
|
+
const outsideFences = transformStudioMarkdownOutsideFences(markdown, (segment: string) => stripStudioMarkdownInlineCodeSpans(segment));
|
|
3284
|
+
return /\\documentclass\b|\\begin\{document\}/.test(outsideFences);
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3266
3287
|
function escapeStudioPdfLatexText(text: string): string {
|
|
3267
3288
|
const normalized = String(text ?? "")
|
|
3268
3289
|
.replace(/\r\n/g, "\n")
|
|
@@ -4043,9 +4064,15 @@ function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLan
|
|
|
4043
4064
|
}
|
|
4044
4065
|
|
|
4045
4066
|
function stripMathMlAnnotationTags(html: string): string {
|
|
4046
|
-
return html
|
|
4047
|
-
.
|
|
4048
|
-
|
|
4067
|
+
return String(html ?? "").replace(/<math\b([^>]*)>([\s\S]*?)<\/math>/gi, (_match, attrs, inner) => {
|
|
4068
|
+
const texAnnotationMatch = String(inner ?? "").match(/<annotation\b[^>]*encoding="application\/x-tex"[^>]*>([\s\S]*?)<\/annotation>/i);
|
|
4069
|
+
const texSource = texAnnotationMatch ? String(texAnnotationMatch[1] ?? "").trim() : "";
|
|
4070
|
+
const cleanedInner = String(inner ?? "")
|
|
4071
|
+
.replace(/<annotation-xml\b[\s\S]*?<\/annotation-xml>/gi, "")
|
|
4072
|
+
.replace(/<annotation\b[\s\S]*?<\/annotation>/gi, "");
|
|
4073
|
+
const texAttr = texSource ? ` data-tex-source="${escapeStudioHtmlText(texSource)}"` : "";
|
|
4074
|
+
return `<math${attrs}${texAttr}>${cleanedInner}</math>`;
|
|
4075
|
+
});
|
|
4049
4076
|
}
|
|
4050
4077
|
|
|
4051
4078
|
function normalizeObsidianImages(markdown: string): string {
|
|
@@ -7869,8 +7896,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
7869
7896
|
parsedBody && typeof parsedBody === "object" && typeof (parsedBody as { resourceDir?: unknown }).resourceDir === "string"
|
|
7870
7897
|
? (parsedBody as { resourceDir: string }).resourceDir
|
|
7871
7898
|
: "";
|
|
7899
|
+
const requestedEditorLanguage =
|
|
7900
|
+
parsedBody && typeof parsedBody === "object" && typeof (parsedBody as { editorLanguage?: unknown }).editorLanguage === "string"
|
|
7901
|
+
? (parsedBody as { editorLanguage: string }).editorLanguage
|
|
7902
|
+
: "";
|
|
7872
7903
|
const resourcePath = resolveStudioBaseDir(sourcePath || undefined, userResourceDir || undefined, studioCwd);
|
|
7873
|
-
const
|
|
7904
|
+
const editorPreviewLanguage = normalizeStudioEditorLanguage(requestedEditorLanguage);
|
|
7905
|
+
const isLatex = editorPreviewLanguage === "latex"
|
|
7906
|
+
|| (
|
|
7907
|
+
(editorPreviewLanguage === undefined || editorPreviewLanguage === "markdown")
|
|
7908
|
+
&& isLikelyStandaloneLatexPreview(markdown)
|
|
7909
|
+
);
|
|
7874
7910
|
const html = await renderStudioMarkdownWithPandoc(markdown, isLatex, resourcePath, sourcePath || undefined);
|
|
7875
7911
|
respondJson(res, 200, { ok: true, html, renderer: "pandoc" });
|
|
7876
7912
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.48",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code preview",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|