pi-studio 0.5.32 → 0.5.34
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 +19 -0
- package/client/studio-client.js +185 -1
- package/client/studio.css +20 -0
- package/index.ts +315 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.34] — 2026-03-27
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Preview-side fenced `text`/`plaintext` blocks now soft-wrap long lines instead of forcing horizontal scrolling, while code/diff blocks keep their existing scrollable behavior.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Preview annotation pills once again render inline math within long `[an: ...]` notes instead of leaving `$...$` / `\(...\)` fragments as literal text.
|
|
14
|
+
|
|
15
|
+
## [0.5.33] — 2026-03-27
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Studio browser tabs now use `π Studio` branding plus a simple theme-reactive `π` favicon instead of the generic browser globe.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Markdown preview now preserves `[an: ...]` markers more reliably by replacing them with preview-safe placeholders before pandoc and restoring annotation pills afterwards, preventing long or markdown-like annotations from leaking through as raw text.
|
|
22
|
+
- Preview/PDF markdown preparation now normalizes fenced blocks whose contents contain competing backtick/tilde fence runs, avoiding broken rendering/export for diff-heavy content that itself contains code fences.
|
|
23
|
+
- Diff PDF exports now route highlighted diff content through the generated-LaTeX path more reliably, keeping add/delete/meta/hunk styling and line wrapping on exports that previously rendered poorly or fell back unnecessarily.
|
|
24
|
+
- PDF annotation badges now wrap within the page width instead of overflowing on long notes, preserve inline math inside annotation text, and also render correctly inside diff token lines such as `+[an: ...]`.
|
|
25
|
+
|
|
7
26
|
## [0.5.32] — 2026-03-25
|
|
8
27
|
|
|
9
28
|
### Added
|
package/client/studio-client.js
CHANGED
|
@@ -242,6 +242,7 @@
|
|
|
242
242
|
let editorHighlightRenderRaf = null;
|
|
243
243
|
let annotationsEnabled = true;
|
|
244
244
|
const ANNOTATION_MARKER_REGEX = /\[an:\s*([^\]]+?)\]/gi;
|
|
245
|
+
const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
|
|
245
246
|
const EMPTY_OVERLAY_LINE = "\u200b";
|
|
246
247
|
const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
247
248
|
const MATHJAX_CDN_URL = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js";
|
|
@@ -1183,6 +1184,80 @@
|
|
|
1183
1184
|
return annotationsEnabled ? raw : stripAnnotationMarkers(raw);
|
|
1184
1185
|
}
|
|
1185
1186
|
|
|
1187
|
+
function normalizePreviewAnnotationLabel(text) {
|
|
1188
|
+
return String(text || "")
|
|
1189
|
+
.replace(/\r\n/g, "\n")
|
|
1190
|
+
.replace(/\s*\n\s*/g, " ")
|
|
1191
|
+
.replace(/\s{2,}/g, " ")
|
|
1192
|
+
.trim();
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function prepareMarkdownForPandocPreview(markdown) {
|
|
1196
|
+
const source = String(markdown || "").replace(/\r\n/g, "\n");
|
|
1197
|
+
const placeholders = [];
|
|
1198
|
+
if (!source) {
|
|
1199
|
+
return { markdown: source, placeholders: placeholders };
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const lines = source.split("\n");
|
|
1203
|
+
const out = [];
|
|
1204
|
+
let plainBuffer = [];
|
|
1205
|
+
let inFence = false;
|
|
1206
|
+
let fenceChar = null;
|
|
1207
|
+
let fenceLength = 0;
|
|
1208
|
+
|
|
1209
|
+
function flushPlain() {
|
|
1210
|
+
if (plainBuffer.length === 0) return;
|
|
1211
|
+
const segment = plainBuffer.join("\n").replace(/\[an:\s*([^\]]+?)\]/gi, function(_match, markerText) {
|
|
1212
|
+
const label = normalizePreviewAnnotationLabel(markerText);
|
|
1213
|
+
if (!label) return "";
|
|
1214
|
+
const token = PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX + placeholders.length + "TOKEN";
|
|
1215
|
+
placeholders.push({ token: token, text: label, title: "[an: " + label + "]" });
|
|
1216
|
+
return token;
|
|
1217
|
+
});
|
|
1218
|
+
out.push(segment);
|
|
1219
|
+
plainBuffer = [];
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
for (const line of lines) {
|
|
1223
|
+
const trimmed = line.trimStart();
|
|
1224
|
+
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
1225
|
+
|
|
1226
|
+
if (fenceMatch) {
|
|
1227
|
+
const marker = fenceMatch[1] || "";
|
|
1228
|
+
const markerChar = marker.charAt(0);
|
|
1229
|
+
const markerLength = marker.length;
|
|
1230
|
+
|
|
1231
|
+
if (!inFence) {
|
|
1232
|
+
flushPlain();
|
|
1233
|
+
inFence = true;
|
|
1234
|
+
fenceChar = markerChar;
|
|
1235
|
+
fenceLength = markerLength;
|
|
1236
|
+
out.push(line);
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
1241
|
+
inFence = false;
|
|
1242
|
+
fenceChar = null;
|
|
1243
|
+
fenceLength = 0;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
out.push(line);
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (inFence) {
|
|
1251
|
+
out.push(line);
|
|
1252
|
+
} else {
|
|
1253
|
+
plainBuffer.push(line);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
flushPlain();
|
|
1258
|
+
return { markdown: out.join("\n"), placeholders: placeholders };
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1186
1261
|
function wrapAsFencedCodeBlock(text, language) {
|
|
1187
1262
|
const source = String(text || "").trimEnd();
|
|
1188
1263
|
const lang = String(language || "").trim();
|
|
@@ -1622,6 +1697,105 @@
|
|
|
1622
1697
|
}
|
|
1623
1698
|
}
|
|
1624
1699
|
|
|
1700
|
+
async function renderAnnotationMathInElement(targetEl) {
|
|
1701
|
+
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
1702
|
+
|
|
1703
|
+
const markers = Array.from(targetEl.querySelectorAll(".annotation-preview-marker")).filter((node) => {
|
|
1704
|
+
const text = typeof node.textContent === "string" ? node.textContent : "";
|
|
1705
|
+
return /\\\(|\\\[|\$\$?|\\[A-Za-z]+/.test(text);
|
|
1706
|
+
});
|
|
1707
|
+
if (markers.length === 0) return;
|
|
1708
|
+
|
|
1709
|
+
let mathJax;
|
|
1710
|
+
try {
|
|
1711
|
+
mathJax = await ensureMathJax();
|
|
1712
|
+
} catch (error) {
|
|
1713
|
+
console.error("Annotation MathJax load failed:", error);
|
|
1714
|
+
appendMathFallbackNotice(targetEl, MATHJAX_UNAVAILABLE_MESSAGE);
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
try {
|
|
1719
|
+
await mathJax.typesetPromise(markers);
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
console.error("Annotation math render failed:", error);
|
|
1722
|
+
appendMathFallbackNotice(targetEl, MATHJAX_RENDER_FAIL_MESSAGE);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
function applyPreviewAnnotationPlaceholdersToElement(targetEl, placeholders) {
|
|
1727
|
+
if (!targetEl || !Array.isArray(placeholders) || placeholders.length === 0) return;
|
|
1728
|
+
if (typeof document.createTreeWalker !== "function") return;
|
|
1729
|
+
|
|
1730
|
+
const placeholderMap = new Map();
|
|
1731
|
+
const placeholderTokens = [];
|
|
1732
|
+
placeholders.forEach(function(entry) {
|
|
1733
|
+
const token = entry && typeof entry.token === "string" ? entry.token : "";
|
|
1734
|
+
if (!token) return;
|
|
1735
|
+
placeholderMap.set(token, entry);
|
|
1736
|
+
placeholderTokens.push(token);
|
|
1737
|
+
});
|
|
1738
|
+
if (placeholderTokens.length === 0) return;
|
|
1739
|
+
|
|
1740
|
+
const placeholderPattern = new RegExp(placeholderTokens.map(escapeRegExp).join("|"), "g");
|
|
1741
|
+
const walker = document.createTreeWalker(targetEl, NodeFilter.SHOW_TEXT);
|
|
1742
|
+
const textNodes = [];
|
|
1743
|
+
let node = walker.nextNode();
|
|
1744
|
+
while (node) {
|
|
1745
|
+
const textNode = node;
|
|
1746
|
+
const value = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
|
|
1747
|
+
if (value && value.indexOf(PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX) !== -1) {
|
|
1748
|
+
const parent = textNode.parentElement;
|
|
1749
|
+
const tag = parent && parent.tagName ? parent.tagName.toUpperCase() : "";
|
|
1750
|
+
if (tag !== "CODE" && tag !== "PRE" && tag !== "SCRIPT" && tag !== "STYLE" && tag !== "TEXTAREA") {
|
|
1751
|
+
textNodes.push(textNode);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
node = walker.nextNode();
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
textNodes.forEach(function(textNode) {
|
|
1758
|
+
const text = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
|
|
1759
|
+
if (!text) return;
|
|
1760
|
+
placeholderPattern.lastIndex = 0;
|
|
1761
|
+
if (!placeholderPattern.test(text)) return;
|
|
1762
|
+
placeholderPattern.lastIndex = 0;
|
|
1763
|
+
|
|
1764
|
+
const fragment = document.createDocumentFragment();
|
|
1765
|
+
let lastIndex = 0;
|
|
1766
|
+
let match;
|
|
1767
|
+
while ((match = placeholderPattern.exec(text)) !== null) {
|
|
1768
|
+
const token = match[0] || "";
|
|
1769
|
+
const entry = placeholderMap.get(token);
|
|
1770
|
+
const start = typeof match.index === "number" ? match.index : 0;
|
|
1771
|
+
if (start > lastIndex) {
|
|
1772
|
+
fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)));
|
|
1773
|
+
}
|
|
1774
|
+
if (entry) {
|
|
1775
|
+
const markerEl = document.createElement("span");
|
|
1776
|
+
markerEl.className = "annotation-preview-marker";
|
|
1777
|
+
markerEl.textContent = typeof entry.text === "string" ? entry.text : token;
|
|
1778
|
+
markerEl.title = typeof entry.title === "string" ? entry.title : markerEl.textContent;
|
|
1779
|
+
fragment.appendChild(markerEl);
|
|
1780
|
+
} else {
|
|
1781
|
+
fragment.appendChild(document.createTextNode(token));
|
|
1782
|
+
}
|
|
1783
|
+
lastIndex = start + token.length;
|
|
1784
|
+
if (token.length === 0) {
|
|
1785
|
+
placeholderPattern.lastIndex += 1;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
if (lastIndex < text.length) {
|
|
1790
|
+
fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
if (textNode.parentNode) {
|
|
1794
|
+
textNode.parentNode.replaceChild(fragment, textNode);
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1625
1799
|
function applyAnnotationMarkersToElement(targetEl, mode) {
|
|
1626
1800
|
if (!targetEl || mode === "none") return;
|
|
1627
1801
|
if (typeof document.createTreeWalker !== "function") return;
|
|
@@ -2094,8 +2268,12 @@
|
|
|
2094
2268
|
}
|
|
2095
2269
|
|
|
2096
2270
|
async function applyRenderedMarkdown(targetEl, markdown, pane, nonce) {
|
|
2271
|
+
const previewPrepared = annotationsEnabled
|
|
2272
|
+
? prepareMarkdownForPandocPreview(markdown)
|
|
2273
|
+
: { markdown: stripAnnotationMarkers(String(markdown || "")), placeholders: [] };
|
|
2274
|
+
|
|
2097
2275
|
try {
|
|
2098
|
-
const renderedHtml = await renderMarkdownWithPandoc(markdown);
|
|
2276
|
+
const renderedHtml = await renderMarkdownWithPandoc(previewPrepared.markdown);
|
|
2099
2277
|
|
|
2100
2278
|
if (pane === "source") {
|
|
2101
2279
|
if (nonce !== sourcePreviewRenderNonce || editorView !== "preview") return;
|
|
@@ -2105,6 +2283,8 @@
|
|
|
2105
2283
|
|
|
2106
2284
|
finishPreviewRender(targetEl);
|
|
2107
2285
|
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown);
|
|
2286
|
+
applyPreviewAnnotationPlaceholdersToElement(targetEl, previewPrepared.placeholders);
|
|
2287
|
+
await renderAnnotationMathInElement(targetEl);
|
|
2108
2288
|
decoratePdfEmbeds(targetEl);
|
|
2109
2289
|
await renderPdfPreviewsInElement(targetEl);
|
|
2110
2290
|
const annotationMode = (pane === "source" || pane === "response")
|
|
@@ -2544,6 +2724,10 @@
|
|
|
2544
2724
|
.replace(/'/g, "'");
|
|
2545
2725
|
}
|
|
2546
2726
|
|
|
2727
|
+
function escapeRegExp(text) {
|
|
2728
|
+
return String(text || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2547
2731
|
function wrapHighlight(className, text) {
|
|
2548
2732
|
return "<span class='" + className + "'>" + escapeHtml(String(text || "")) + "</span>";
|
|
2549
2733
|
}
|
package/client/studio.css
CHANGED
|
@@ -516,7 +516,11 @@
|
|
|
516
516
|
background: var(--accent-soft);
|
|
517
517
|
border: 1px solid var(--marker-border);
|
|
518
518
|
border-radius: 4px;
|
|
519
|
+
display: inline-block;
|
|
520
|
+
max-width: 100%;
|
|
519
521
|
padding: 0 4px;
|
|
522
|
+
white-space: normal;
|
|
523
|
+
vertical-align: baseline;
|
|
520
524
|
}
|
|
521
525
|
|
|
522
526
|
#sourcePreview {
|
|
@@ -738,6 +742,22 @@
|
|
|
738
742
|
color: var(--md-codeblock);
|
|
739
743
|
}
|
|
740
744
|
|
|
745
|
+
.rendered-markdown pre.text,
|
|
746
|
+
.rendered-markdown pre.plaintext,
|
|
747
|
+
.rendered-markdown pre.sourceCode.txt {
|
|
748
|
+
white-space: pre-wrap;
|
|
749
|
+
overflow-x: hidden;
|
|
750
|
+
overflow-wrap: anywhere;
|
|
751
|
+
word-break: break-word;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.rendered-markdown pre.text code,
|
|
755
|
+
.rendered-markdown pre.plaintext code,
|
|
756
|
+
.rendered-markdown pre.sourceCode.txt code,
|
|
757
|
+
.rendered-markdown pre.sourceCode.txt code > span {
|
|
758
|
+
white-space: inherit;
|
|
759
|
+
}
|
|
760
|
+
|
|
741
761
|
.rendered-markdown :not(pre) > code {
|
|
742
762
|
background: rgba(127, 127, 127, 0.13);
|
|
743
763
|
border: 1px solid var(--md-codeblock-border);
|
package/index.ts
CHANGED
|
@@ -236,10 +236,20 @@ function buildStudioPdfPreamble(options?: StudioPdfRenderOptions): string {
|
|
|
236
236
|
\\titlespacing*{\\section}{0pt}{${sectionSpaceBefore}}{${sectionSpaceAfter}}
|
|
237
237
|
\\titlespacing*{\\subsection}{0pt}{${subsectionSpaceBefore}}{${subsectionSpaceAfter}}
|
|
238
238
|
\\usepackage{xcolor}
|
|
239
|
+
\\usepackage{varwidth}
|
|
239
240
|
\\definecolor{StudioAnnotationBg}{HTML}{EAF3FF}
|
|
240
241
|
\\definecolor{StudioAnnotationBorder}{HTML}{8CB8FF}
|
|
241
242
|
\\definecolor{StudioAnnotationText}{HTML}{1F5FBF}
|
|
242
|
-
\\
|
|
243
|
+
\\definecolor{StudioDiffAddText}{HTML}{1A7F37}
|
|
244
|
+
\\definecolor{StudioDiffDelText}{HTML}{CF222E}
|
|
245
|
+
\\definecolor{StudioDiffMetaText}{HTML}{57606A}
|
|
246
|
+
\\definecolor{StudioDiffHunkText}{HTML}{0969DA}
|
|
247
|
+
\\newcommand{\\studioannotation}[1]{\\begingroup\\setlength{\\fboxsep}{1.5pt}\\fcolorbox{StudioAnnotationBorder}{StudioAnnotationBg}{\\begin{varwidth}{\\dimexpr\\linewidth-2\\fboxsep-2\\fboxrule\\relax}\\raggedright\\textcolor{StudioAnnotationText}{\\sffamily\\footnotesize\\strut #1}\\end{varwidth}}\\endgroup}
|
|
248
|
+
\\newcommand{\\StudioDiffAddTok}[1]{\\textcolor{StudioDiffAddText}{#1}}
|
|
249
|
+
\\newcommand{\\StudioDiffDelTok}[1]{\\textcolor{StudioDiffDelText}{#1}}
|
|
250
|
+
\\newcommand{\\StudioDiffMetaTok}[1]{\\textcolor{StudioDiffMetaText}{#1}}
|
|
251
|
+
\\newcommand{\\StudioDiffHunkTok}[1]{\\textcolor{StudioDiffHunkText}{#1}}
|
|
252
|
+
\\newcommand{\\StudioDiffHeaderTok}[1]{\\textcolor{StudioDiffHunkText}{\\textbf{#1}}}
|
|
243
253
|
\\newenvironment{studiocallout}[1]{\\par\\vspace{0.22em}\\noindent\\begingroup\\color{StudioAnnotationBorder}\\hrule height 0.45pt\\color{black}\\vspace{0.08em}\\noindent{\\sffamily\\bfseries\\textcolor{StudioAnnotationText}{#1}}\\par\\vspace{0.02em}\\leftskip=0.7em\\rightskip=0pt\\parindent=0pt\\parskip=0.15em}{\\par\\vspace{0.02em}\\noindent\\color{StudioAnnotationBorder}\\hrule height 0.45pt\\par\\endgroup\\vspace{0.22em}}
|
|
244
254
|
\\usepackage{caption}
|
|
245
255
|
\\captionsetup[figure]{justification=raggedright,singlelinecheck=false}
|
|
@@ -2892,6 +2902,105 @@ function wrapStudioCodeAsMarkdown(code: string, language?: string): string {
|
|
|
2892
2902
|
return `${marker}${lang}\n${source}\n${marker}`;
|
|
2893
2903
|
}
|
|
2894
2904
|
|
|
2905
|
+
function extractStudioFenceInfoLanguage(info: string): string | undefined {
|
|
2906
|
+
const firstToken = String(info ?? "").trim().split(/\s+/)[0]?.replace(/^\./, "") ?? "";
|
|
2907
|
+
return normalizeStudioEditorLanguage(firstToken || undefined);
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
function normalizeStudioMarkdownFencedBlocks(markdown: string): string {
|
|
2911
|
+
const lines = String(markdown ?? "").replace(/\r\n/g, "\n").split("\n");
|
|
2912
|
+
const out: string[] = [];
|
|
2913
|
+
|
|
2914
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2915
|
+
const line = lines[index] ?? "";
|
|
2916
|
+
const openingMatch = line.match(/^(\s{0,3})(`{3,}|~{3,})([^\n]*)$/);
|
|
2917
|
+
if (!openingMatch) {
|
|
2918
|
+
out.push(line);
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
const indent = openingMatch[1] ?? "";
|
|
2923
|
+
const openingFence = openingMatch[2]!;
|
|
2924
|
+
const openingSuffix = openingMatch[3] ?? "";
|
|
2925
|
+
const fenceChar = openingFence[0] as "`" | "~";
|
|
2926
|
+
const fenceLength = openingFence.length;
|
|
2927
|
+
|
|
2928
|
+
let closingIndex = -1;
|
|
2929
|
+
for (let innerIndex = index + 1; innerIndex < lines.length; innerIndex += 1) {
|
|
2930
|
+
const innerLine = lines[innerIndex] ?? "";
|
|
2931
|
+
const closingMatch = innerLine.match(/^\s{0,3}(`{3,}|~{3,})\s*$/);
|
|
2932
|
+
if (!closingMatch) continue;
|
|
2933
|
+
const closingFence = closingMatch[1]!;
|
|
2934
|
+
if (closingFence[0] !== fenceChar || closingFence.length < fenceLength) continue;
|
|
2935
|
+
closingIndex = innerIndex;
|
|
2936
|
+
break;
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
if (closingIndex === -1) {
|
|
2940
|
+
out.push(line);
|
|
2941
|
+
continue;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
const contentLines = lines.slice(index + 1, closingIndex);
|
|
2945
|
+
const content = contentLines.join("\n");
|
|
2946
|
+
const maxBackticks = getLongestStudioFenceRun(content, "`");
|
|
2947
|
+
const maxTildes = getLongestStudioFenceRun(content, "~");
|
|
2948
|
+
const currentMaxRun = fenceChar === "`" ? maxBackticks : maxTildes;
|
|
2949
|
+
|
|
2950
|
+
if (currentMaxRun < fenceLength) {
|
|
2951
|
+
out.push(line, ...contentLines, lines[closingIndex] ?? "");
|
|
2952
|
+
index = closingIndex;
|
|
2953
|
+
continue;
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
const neededBackticks = Math.max(3, maxBackticks + 1);
|
|
2957
|
+
const neededTildes = Math.max(3, maxTildes + 1);
|
|
2958
|
+
let markerChar: "`" | "~" = fenceChar;
|
|
2959
|
+
|
|
2960
|
+
if (neededBackticks < neededTildes) {
|
|
2961
|
+
markerChar = "`";
|
|
2962
|
+
} else if (neededTildes < neededBackticks) {
|
|
2963
|
+
markerChar = "~";
|
|
2964
|
+
} else if (fenceChar === "`") {
|
|
2965
|
+
markerChar = "~";
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
const markerLength = markerChar === "`" ? neededBackticks : neededTildes;
|
|
2969
|
+
const marker = markerChar.repeat(markerLength);
|
|
2970
|
+
out.push(`${indent}${marker}${openingSuffix}`, ...contentLines, `${indent}${marker}`);
|
|
2971
|
+
index = closingIndex;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
return out.join("\n");
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
function hasStudioMarkdownDiffFence(markdown: string): boolean {
|
|
2978
|
+
const lines = String(markdown ?? "").replace(/\r\n/g, "\n").split("\n");
|
|
2979
|
+
|
|
2980
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2981
|
+
const line = lines[index] ?? "";
|
|
2982
|
+
const openingMatch = line.match(/^\s{0,3}(`{3,}|~{3,})([^\n]*)$/);
|
|
2983
|
+
if (!openingMatch) continue;
|
|
2984
|
+
|
|
2985
|
+
const openingFence = openingMatch[1]!;
|
|
2986
|
+
const infoLanguage = extractStudioFenceInfoLanguage(openingMatch[2] ?? "");
|
|
2987
|
+
if (infoLanguage !== "diff") continue;
|
|
2988
|
+
|
|
2989
|
+
const fenceChar = openingFence[0];
|
|
2990
|
+
const fenceLength = openingFence.length;
|
|
2991
|
+
for (let innerIndex = index + 1; innerIndex < lines.length; innerIndex += 1) {
|
|
2992
|
+
const innerLine = lines[innerIndex] ?? "";
|
|
2993
|
+
const closingMatch = innerLine.match(/^\s{0,3}(`{3,}|~{3,})\s*$/);
|
|
2994
|
+
if (!closingMatch) continue;
|
|
2995
|
+
const closingFence = closingMatch[1]!;
|
|
2996
|
+
if (closingFence[0] !== fenceChar || closingFence.length < fenceLength) continue;
|
|
2997
|
+
return true;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
return false;
|
|
3002
|
+
}
|
|
3003
|
+
|
|
2895
3004
|
function isLikelyRawStudioGitDiff(markdown: string): boolean {
|
|
2896
3005
|
const text = String(markdown ?? "");
|
|
2897
3006
|
if (!text.trim() || isStudioSingleFencedCodeBlock(text)) return false;
|
|
@@ -2914,16 +3023,66 @@ function inferStudioPdfLanguage(markdown: string, editorLanguage?: string): stri
|
|
|
2914
3023
|
return undefined;
|
|
2915
3024
|
}
|
|
2916
3025
|
|
|
2917
|
-
function
|
|
3026
|
+
function escapeStudioPdfLatexTextFragment(text: string): string {
|
|
2918
3027
|
return String(text ?? "")
|
|
2919
|
-
.replace(/\r\n/g, "\n")
|
|
2920
|
-
.replace(/\s*\n\s*/g, " ")
|
|
2921
|
-
.trim()
|
|
2922
3028
|
.replace(/\\/g, "\\textbackslash{}")
|
|
2923
3029
|
.replace(/([{}%#$&_])/g, "\\$1")
|
|
2924
3030
|
.replace(/~/g, "\\textasciitilde{}")
|
|
2925
|
-
.replace(/\^/g, "\\textasciicircum{}")
|
|
2926
|
-
|
|
3031
|
+
.replace(/\^/g, "\\textasciicircum{}");
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
function escapeStudioPdfLatexText(text: string): string {
|
|
3035
|
+
const normalized = String(text ?? "")
|
|
3036
|
+
.replace(/\r\n/g, "\n")
|
|
3037
|
+
.replace(/\s*\n\s*/g, " ")
|
|
3038
|
+
.replace(/\s{2,}/g, " ")
|
|
3039
|
+
.trim();
|
|
3040
|
+
if (!normalized) return "";
|
|
3041
|
+
|
|
3042
|
+
const mathPattern = /\\\(([\s\S]*?)\\\)|\\\[([\s\S]*?)\\\]|\$\$([\s\S]*?)\$\$|\$([^$\n]+?)\$/g;
|
|
3043
|
+
let out = "";
|
|
3044
|
+
let lastIndex = 0;
|
|
3045
|
+
let match: RegExpExecArray | null;
|
|
3046
|
+
|
|
3047
|
+
while ((match = mathPattern.exec(normalized)) !== null) {
|
|
3048
|
+
const token = match[0] ?? "";
|
|
3049
|
+
const start = match.index;
|
|
3050
|
+
if (start > lastIndex) {
|
|
3051
|
+
out += escapeStudioPdfLatexTextFragment(normalized.slice(lastIndex, start));
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
const inlineParenExpr = match[1];
|
|
3055
|
+
const displayBracketExpr = match[2];
|
|
3056
|
+
const displayDollarExpr = match[3];
|
|
3057
|
+
const inlineDollarExpr = match[4];
|
|
3058
|
+
let mathLatex = "";
|
|
3059
|
+
|
|
3060
|
+
if (typeof inlineParenExpr === "string" && isLikelyMathExpression(inlineParenExpr)) {
|
|
3061
|
+
const content = inlineParenExpr.trim();
|
|
3062
|
+
mathLatex = content ? `\\(${content}\\)` : "";
|
|
3063
|
+
} else if (typeof displayBracketExpr === "string" && isLikelyMathExpression(displayBracketExpr)) {
|
|
3064
|
+
const content = collapseDisplayMathContent(displayBracketExpr);
|
|
3065
|
+
mathLatex = content ? `\\(${content}\\)` : "";
|
|
3066
|
+
} else if (typeof displayDollarExpr === "string" && isLikelyMathExpression(displayDollarExpr)) {
|
|
3067
|
+
const content = collapseDisplayMathContent(displayDollarExpr);
|
|
3068
|
+
mathLatex = content ? `\\(${content}\\)` : "";
|
|
3069
|
+
} else if (typeof inlineDollarExpr === "string" && isLikelyMathExpression(inlineDollarExpr)) {
|
|
3070
|
+
const content = inlineDollarExpr.trim();
|
|
3071
|
+
mathLatex = content ? `\\(${content}\\)` : "";
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
out += mathLatex || escapeStudioPdfLatexTextFragment(token);
|
|
3075
|
+
lastIndex = start + token.length;
|
|
3076
|
+
if (token.length === 0) {
|
|
3077
|
+
mathPattern.lastIndex += 1;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
if (lastIndex < normalized.length) {
|
|
3082
|
+
out += escapeStudioPdfLatexTextFragment(normalized.slice(lastIndex));
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
return out.trim();
|
|
2927
3086
|
}
|
|
2928
3087
|
|
|
2929
3088
|
function replaceStudioAnnotationMarkersForPdfInSegment(text: string): string {
|
|
@@ -3697,7 +3856,9 @@ async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolea
|
|
|
3697
3856
|
// Embed images as data URIs so they render in the browser preview
|
|
3698
3857
|
args.push("--embed-resources", "--standalone");
|
|
3699
3858
|
}
|
|
3700
|
-
const normalizedMarkdown = isLatex
|
|
3859
|
+
const normalizedMarkdown = isLatex
|
|
3860
|
+
? sourceWithResolvedRefs
|
|
3861
|
+
: normalizeStudioMarkdownFencedBlocks(normalizeObsidianImages(normalizeMathDelimiters(sourceWithResolvedRefs)));
|
|
3701
3862
|
const pandocWorkingDir = resolveStudioPandocWorkingDir(resourcePath);
|
|
3702
3863
|
|
|
3703
3864
|
let renderedHtml = await new Promise<string>((resolve, reject) => {
|
|
@@ -3888,6 +4049,114 @@ function replaceStudioAnnotationMarkersInGeneratedLatex(latex: string): string {
|
|
|
3888
4049
|
return out.join("\n");
|
|
3889
4050
|
}
|
|
3890
4051
|
|
|
4052
|
+
function isStudioGeneratedDiffHighlightingBlock(lines: string[]): boolean {
|
|
4053
|
+
const body = lines.join("\n");
|
|
4054
|
+
const hasAdditionOrDeletion = /\\VariableTok\{\+|\\StringTok\{\{-\}/.test(body);
|
|
4055
|
+
const hasDiffStructure = /\\DataTypeTok\{@@|\\NormalTok\{diff \{-\}\{-\}git |\\KeywordTok\{\{-\}\{-\}\{-\}|\\DataTypeTok\{\+\+\+/.test(body);
|
|
4056
|
+
return hasAdditionOrDeletion && hasDiffStructure;
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
function replaceStudioAnnotationMarkersInDiffTokenLine(line: string, macroName: string): string {
|
|
4060
|
+
const tokenMatch = line.match(new RegExp(`^\\\\${macroName}\\{([\\s\\S]*)\\}$`));
|
|
4061
|
+
if (!tokenMatch) return line;
|
|
4062
|
+
|
|
4063
|
+
const body = tokenMatch[1] ?? "";
|
|
4064
|
+
const markerPattern = /\[an:\s*([^\]]+?)\]/gi;
|
|
4065
|
+
let lastIndex = 0;
|
|
4066
|
+
let rewritten = "";
|
|
4067
|
+
let match: RegExpExecArray | null;
|
|
4068
|
+
|
|
4069
|
+
const wrapText = (text: string): string => text ? `\\${macroName}{${text}}` : "";
|
|
4070
|
+
|
|
4071
|
+
while ((match = markerPattern.exec(body)) !== null) {
|
|
4072
|
+
const token = match[0] ?? "";
|
|
4073
|
+
const start = match.index;
|
|
4074
|
+
if (start > lastIndex) {
|
|
4075
|
+
rewritten += wrapText(body.slice(lastIndex, start));
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
const markerText = (match[1] ?? "").replace(/\s{2,}/g, " ").trim();
|
|
4079
|
+
if (markerText) {
|
|
4080
|
+
rewritten += `\\studioannotation{${markerText}}`;
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
lastIndex = start + token.length;
|
|
4084
|
+
if (token.length === 0) {
|
|
4085
|
+
markerPattern.lastIndex += 1;
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
if (lastIndex === 0) return line;
|
|
4090
|
+
if (lastIndex < body.length) {
|
|
4091
|
+
rewritten += wrapText(body.slice(lastIndex));
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
return rewritten || wrapText(body);
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
function rewriteStudioGeneratedDiffHighlighting(latex: string): string {
|
|
4098
|
+
const lines = String(latex ?? "").split("\n");
|
|
4099
|
+
const out: string[] = [];
|
|
4100
|
+
|
|
4101
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4102
|
+
const line = lines[index] ?? "";
|
|
4103
|
+
if (!/^\\begin\{Highlighting\}/.test(line)) {
|
|
4104
|
+
out.push(line);
|
|
4105
|
+
continue;
|
|
4106
|
+
}
|
|
4107
|
+
|
|
4108
|
+
let closingIndex = -1;
|
|
4109
|
+
for (let innerIndex = index + 1; innerIndex < lines.length; innerIndex += 1) {
|
|
4110
|
+
if (/^\\end\{Highlighting\}/.test(lines[innerIndex] ?? "")) {
|
|
4111
|
+
closingIndex = innerIndex;
|
|
4112
|
+
break;
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
if (closingIndex === -1) {
|
|
4117
|
+
out.push(line);
|
|
4118
|
+
continue;
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
const blockLines = lines.slice(index, closingIndex + 1);
|
|
4122
|
+
if (!isStudioGeneratedDiffHighlightingBlock(blockLines)) {
|
|
4123
|
+
out.push(...blockLines);
|
|
4124
|
+
index = closingIndex;
|
|
4125
|
+
continue;
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
const rewrittenBlock = blockLines.map((blockLine) => {
|
|
4129
|
+
if (/^\\VariableTok\{/.test(blockLine)) {
|
|
4130
|
+
return replaceStudioAnnotationMarkersInDiffTokenLine(
|
|
4131
|
+
blockLine.replace(/^\\VariableTok\{/, "\\StudioDiffAddTok{"),
|
|
4132
|
+
"StudioDiffAddTok",
|
|
4133
|
+
);
|
|
4134
|
+
}
|
|
4135
|
+
if (/^\\StringTok\{/.test(blockLine)) {
|
|
4136
|
+
return replaceStudioAnnotationMarkersInDiffTokenLine(
|
|
4137
|
+
blockLine.replace(/^\\StringTok\{/, "\\StudioDiffDelTok{"),
|
|
4138
|
+
"StudioDiffDelTok",
|
|
4139
|
+
);
|
|
4140
|
+
}
|
|
4141
|
+
if (/^\\DataTypeTok\{@@/.test(blockLine)) return blockLine.replace(/^\\DataTypeTok\{/, "\\StudioDiffHunkTok{");
|
|
4142
|
+
if (/^\\DataTypeTok\{\+\+\+/.test(blockLine)) return blockLine.replace(/^\\DataTypeTok\{/, "\\StudioDiffHeaderTok{");
|
|
4143
|
+
if (/^\\KeywordTok\{\{-\}\{-\}\{-\}/.test(blockLine)) return blockLine.replace(/^\\KeywordTok\{/, "\\StudioDiffHeaderTok{");
|
|
4144
|
+
if (/^\\NormalTok\{(?:diff \{-\}\{-\}git |index |new file mode |deleted file mode |similarity index |rename from |rename to |Binary files )/.test(blockLine)) {
|
|
4145
|
+
return replaceStudioAnnotationMarkersInDiffTokenLine(
|
|
4146
|
+
blockLine.replace(/^\\NormalTok\{/, "\\StudioDiffMetaTok{"),
|
|
4147
|
+
"StudioDiffMetaTok",
|
|
4148
|
+
);
|
|
4149
|
+
}
|
|
4150
|
+
return blockLine;
|
|
4151
|
+
});
|
|
4152
|
+
|
|
4153
|
+
out.push(...rewrittenBlock);
|
|
4154
|
+
index = closingIndex;
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
return out.join("\n");
|
|
4158
|
+
}
|
|
4159
|
+
|
|
3891
4160
|
async function renderStudioPdfFromGeneratedLatex(
|
|
3892
4161
|
markdown: string,
|
|
3893
4162
|
pandocCommand: string,
|
|
@@ -3923,6 +4192,8 @@ async function renderStudioPdfFromGeneratedLatex(
|
|
|
3923
4192
|
];
|
|
3924
4193
|
if (resourcePath) pandocArgs.push(`--resource-path=${resourcePath}`);
|
|
3925
4194
|
|
|
4195
|
+
const pandocSource = inputFormat === "latex" ? markdown : normalizeStudioMarkdownFencedBlocks(markdown);
|
|
4196
|
+
|
|
3926
4197
|
try {
|
|
3927
4198
|
await new Promise<void>((resolve, reject) => {
|
|
3928
4199
|
const child = spawn(pandocCommand, pandocArgs, { stdio: ["pipe", "pipe", "pipe"], cwd: pandocWorkingDir });
|
|
@@ -3962,13 +4233,14 @@ async function renderStudioPdfFromGeneratedLatex(
|
|
|
3962
4233
|
fail(new Error(`pandoc LaTeX generation failed with exit code ${code}${stderr ? `: ${stderr}` : ""}`));
|
|
3963
4234
|
});
|
|
3964
4235
|
|
|
3965
|
-
child.stdin.end(
|
|
4236
|
+
child.stdin.end(pandocSource);
|
|
3966
4237
|
});
|
|
3967
4238
|
|
|
3968
4239
|
const generatedLatex = await readFile(latexPath, "utf-8");
|
|
3969
4240
|
const injectedLatex = injectStudioLatexPdfSubfigureBlocks(generatedLatex, subfigureGroups, sourcePath, resourcePath);
|
|
3970
4241
|
const annotationReadyLatex = replaceStudioAnnotationMarkersInGeneratedLatex(injectedLatex);
|
|
3971
|
-
const
|
|
4242
|
+
const diffReadyLatex = rewriteStudioGeneratedDiffHighlighting(annotationReadyLatex);
|
|
4243
|
+
const calloutReadyLatex = replaceStudioPdfCalloutBlocksInGeneratedLatex(diffReadyLatex, calloutBlocks);
|
|
3972
4244
|
const alignedReadyLatex = replaceStudioPdfAlignedImageBlocksInGeneratedLatex(calloutReadyLatex, alignedImageBlocks);
|
|
3973
4245
|
const normalizedLatex = normalizeStudioGeneratedFigureCaptions(alignedReadyLatex);
|
|
3974
4246
|
await writeFile(latexPath, normalizedLatex, "utf-8");
|
|
@@ -4067,6 +4339,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
4067
4339
|
markdownForPdf: string,
|
|
4068
4340
|
warning?: string,
|
|
4069
4341
|
): Promise<{ pdf: Buffer; warning?: string }> => {
|
|
4342
|
+
const pandocSource = inputFormat === "latex" ? markdownForPdf : normalizeStudioMarkdownFencedBlocks(markdownForPdf);
|
|
4070
4343
|
const tempDir = join(tmpdir(), `pi-studio-pdf-${Date.now()}-${randomUUID()}`);
|
|
4071
4344
|
const preamblePath = join(tempDir, "_pdf_preamble.tex");
|
|
4072
4345
|
const outputPath = join(tempDir, "studio-export.pdf");
|
|
@@ -4128,7 +4401,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
4128
4401
|
fail(new Error(`pandoc PDF export failed with exit code ${code}${stderr ? `: ${stderr}` : ""}${hint}`));
|
|
4129
4402
|
});
|
|
4130
4403
|
|
|
4131
|
-
child.stdin.end(
|
|
4404
|
+
child.stdin.end(pandocSource);
|
|
4132
4405
|
});
|
|
4133
4406
|
|
|
4134
4407
|
return { pdf: await readFile(outputPath), warning };
|
|
@@ -4158,7 +4431,20 @@ async function renderStudioPdfWithPandoc(
|
|
|
4158
4431
|
const inputFormat = "markdown+lists_without_preceding_blankline-blank_before_blockquote-blank_before_header+tex_math_dollars+autolink_bare_uris+superscript+subscript-raw_html";
|
|
4159
4432
|
const diffMarkdown = prepareStudioPdfMarkdown(markdown, false, effectiveEditorLanguage);
|
|
4160
4433
|
try {
|
|
4161
|
-
return await
|
|
4434
|
+
return await renderStudioPdfFromGeneratedLatex(
|
|
4435
|
+
diffMarkdown,
|
|
4436
|
+
pandocCommand,
|
|
4437
|
+
pdfEngine,
|
|
4438
|
+
resourcePath,
|
|
4439
|
+
pandocWorkingDir,
|
|
4440
|
+
bibliographyArgs,
|
|
4441
|
+
sourcePath,
|
|
4442
|
+
[],
|
|
4443
|
+
inputFormat,
|
|
4444
|
+
[],
|
|
4445
|
+
[],
|
|
4446
|
+
pdfOptions,
|
|
4447
|
+
);
|
|
4162
4448
|
} catch {
|
|
4163
4449
|
const fenced = parseStudioSingleFencedCodeBlock(diffMarkdown);
|
|
4164
4450
|
const diffText = fenced ? fenced.content : markdown;
|
|
@@ -4185,8 +4471,9 @@ async function renderStudioPdfWithPandoc(
|
|
|
4185
4471
|
? { markdown: normalizedMarkdown, found: 0, replaced: 0, failed: 0, missingCli: false }
|
|
4186
4472
|
: await preprocessStudioMermaidForPdf(normalizedMarkdown, tempDir);
|
|
4187
4473
|
const markdownForPdf = mermaidPrepared.markdown;
|
|
4474
|
+
const hasDiffBlocks = !isLatex && hasStudioMarkdownDiffFence(markdownForPdf);
|
|
4188
4475
|
|
|
4189
|
-
if (!isLatex && (pdfCalloutTransform.blocks.length > 0 || pdfAlignedImageTransform.blocks.length > 0)) {
|
|
4476
|
+
if (!isLatex && (pdfCalloutTransform.blocks.length > 0 || pdfAlignedImageTransform.blocks.length > 0 || hasDiffBlocks)) {
|
|
4190
4477
|
const rendered = await renderStudioPdfFromGeneratedLatex(
|
|
4191
4478
|
markdownForPdf,
|
|
4192
4479
|
pandocCommand,
|
|
@@ -4216,6 +4503,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
4216
4503
|
...bibliographyArgs,
|
|
4217
4504
|
];
|
|
4218
4505
|
if (resourcePath) args.push(`--resource-path=${resourcePath}`);
|
|
4506
|
+
const pandocSource = isLatex ? markdownForPdf : normalizeStudioMarkdownFencedBlocks(markdownForPdf);
|
|
4219
4507
|
|
|
4220
4508
|
try {
|
|
4221
4509
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -4259,7 +4547,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
4259
4547
|
fail(new Error(`pandoc PDF export failed with exit code ${code}${stderr ? `: ${stderr}` : ""}${hint}`));
|
|
4260
4548
|
});
|
|
4261
4549
|
|
|
4262
|
-
child.stdin.end(
|
|
4550
|
+
child.stdin.end(pandocSource);
|
|
4263
4551
|
});
|
|
4264
4552
|
|
|
4265
4553
|
return { pdf: await readFile(outputPath), warning: mermaidPrepared.warning };
|
|
@@ -5185,6 +5473,16 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
5185
5473
|
};
|
|
5186
5474
|
}
|
|
5187
5475
|
|
|
5476
|
+
function buildStudioFaviconDataUri(style: StudioThemeStyle): string {
|
|
5477
|
+
const iconFg = style.palette.text;
|
|
5478
|
+
const svg = [
|
|
5479
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">',
|
|
5480
|
+
`<text x="32" y="35" text-anchor="middle" dominant-baseline="middle" font-size="50" font-weight="700" font-family="ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" fill="${iconFg}">π</text>`,
|
|
5481
|
+
"</svg>",
|
|
5482
|
+
].join("");
|
|
5483
|
+
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
5484
|
+
}
|
|
5485
|
+
|
|
5188
5486
|
function buildStudioHtml(
|
|
5189
5487
|
initialDocument: InitialStudioDocument | null,
|
|
5190
5488
|
studioToken?: string,
|
|
@@ -5244,6 +5542,7 @@ function buildStudioHtml(
|
|
|
5244
5542
|
const cssVarsBlock = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
5245
5543
|
const stylesheetHref = `/studio.css?token=${encodeURIComponent(studioToken ?? "")}`;
|
|
5246
5544
|
const clientScriptHref = `/studio-client.js?token=${encodeURIComponent(studioToken ?? "")}`;
|
|
5545
|
+
const faviconHref = buildStudioFaviconDataUri(style);
|
|
5247
5546
|
const bootConfigJson = JSON.stringify({ mermaidConfig }).replace(/</g, "\\u003c");
|
|
5248
5547
|
|
|
5249
5548
|
return `<!doctype html>
|
|
@@ -5251,7 +5550,8 @@ function buildStudioHtml(
|
|
|
5251
5550
|
<head>
|
|
5252
5551
|
<meta charset="utf-8" />
|
|
5253
5552
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
5254
|
-
<title
|
|
5553
|
+
<title>π Studio</title>
|
|
5554
|
+
<link rel="icon" href="${faviconHref}" type="image/svg+xml" />
|
|
5255
5555
|
<style>
|
|
5256
5556
|
:root {
|
|
5257
5557
|
${cssVarsBlock}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.34",
|
|
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",
|