pi-studio 0.9.47 → 0.9.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 +10 -0
- package/README.md +3 -1
- package/client/studio-client.js +512 -75
- package/client/studio.css +116 -17
- package/index.ts +207 -17
- package/package.json +1 -1
- package/shared/studio-latex-pandoc-compat.js +122 -0
package/client/studio.css
CHANGED
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
background: var(--panel-2);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
button, select
|
|
151
|
+
button, select {
|
|
152
152
|
border: 1px solid var(--control-border);
|
|
153
153
|
background: var(--panel);
|
|
154
154
|
color: var(--text);
|
|
@@ -163,14 +163,12 @@
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
button:not(:disabled):hover,
|
|
166
|
-
select:hover
|
|
167
|
-
.file-label:hover {
|
|
166
|
+
select:hover {
|
|
168
167
|
background: var(--panel-2);
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
button:focus-visible,
|
|
172
|
-
select:focus-visible
|
|
173
|
-
.file-label:focus-within {
|
|
171
|
+
select:focus-visible {
|
|
174
172
|
outline: 2px solid var(--accent-soft-strong);
|
|
175
173
|
outline-offset: 1px;
|
|
176
174
|
}
|
|
@@ -229,15 +227,8 @@
|
|
|
229
227
|
filter: none;
|
|
230
228
|
}
|
|
231
229
|
|
|
232
|
-
.file-
|
|
233
|
-
|
|
234
|
-
display: inline-flex;
|
|
235
|
-
align-items: center;
|
|
236
|
-
gap: 6px;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.file-label input {
|
|
240
|
-
display: none;
|
|
230
|
+
.file-input-hidden {
|
|
231
|
+
display: none !important;
|
|
241
232
|
}
|
|
242
233
|
|
|
243
234
|
main {
|
|
@@ -1749,6 +1740,7 @@
|
|
|
1749
1740
|
|
|
1750
1741
|
.rendered-markdown .studio-pdf-card-header {
|
|
1751
1742
|
display: flex;
|
|
1743
|
+
flex-wrap: wrap;
|
|
1752
1744
|
align-items: center;
|
|
1753
1745
|
justify-content: space-between;
|
|
1754
1746
|
gap: 10px;
|
|
@@ -1777,10 +1769,13 @@
|
|
|
1777
1769
|
}
|
|
1778
1770
|
|
|
1779
1771
|
.rendered-markdown .studio-pdf-card-actions {
|
|
1780
|
-
flex: 0
|
|
1772
|
+
flex: 0 1 auto;
|
|
1781
1773
|
display: inline-flex;
|
|
1774
|
+
flex-wrap: wrap;
|
|
1775
|
+
justify-content: flex-end;
|
|
1782
1776
|
align-items: center;
|
|
1783
1777
|
gap: 8px;
|
|
1778
|
+
margin-left: auto;
|
|
1784
1779
|
}
|
|
1785
1780
|
|
|
1786
1781
|
.rendered-markdown .studio-pdf-card-link,
|
|
@@ -1907,6 +1902,7 @@
|
|
|
1907
1902
|
.studio-pdf-focus-header {
|
|
1908
1903
|
flex: 0 0 auto;
|
|
1909
1904
|
display: flex;
|
|
1905
|
+
flex-wrap: wrap;
|
|
1910
1906
|
align-items: center;
|
|
1911
1907
|
justify-content: space-between;
|
|
1912
1908
|
gap: 12px;
|
|
@@ -1934,10 +1930,13 @@
|
|
|
1934
1930
|
}
|
|
1935
1931
|
|
|
1936
1932
|
.studio-pdf-focus-actions {
|
|
1937
|
-
flex: 0
|
|
1933
|
+
flex: 0 1 auto;
|
|
1938
1934
|
display: inline-flex;
|
|
1935
|
+
flex-wrap: wrap;
|
|
1936
|
+
justify-content: flex-end;
|
|
1939
1937
|
align-items: center;
|
|
1940
1938
|
gap: 8px;
|
|
1939
|
+
margin-left: auto;
|
|
1941
1940
|
}
|
|
1942
1941
|
|
|
1943
1942
|
.studio-pdf-focus-link,
|
|
@@ -2909,6 +2908,10 @@
|
|
|
2909
2908
|
font-style: italic;
|
|
2910
2909
|
}
|
|
2911
2910
|
|
|
2911
|
+
.studio-latex-compatibility-warning {
|
|
2912
|
+
margin: 0 0 0.85em;
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2912
2915
|
.marker {
|
|
2913
2916
|
display: inline-block;
|
|
2914
2917
|
padding: 0 4px;
|
|
@@ -5362,13 +5365,13 @@
|
|
|
5362
5365
|
}
|
|
5363
5366
|
|
|
5364
5367
|
body.studio-ui-refresh > header button,
|
|
5365
|
-
body.studio-ui-refresh > header .file-label,
|
|
5366
5368
|
body.studio-ui-refresh #responseActions button,
|
|
5367
5369
|
body.studio-ui-refresh #responseActions select {
|
|
5368
5370
|
padding: 5px 7px;
|
|
5369
5371
|
font-size: 12px;
|
|
5370
5372
|
}
|
|
5371
5373
|
|
|
5374
|
+
|
|
5372
5375
|
body.studio-ui-refresh main {
|
|
5373
5376
|
gap: 0;
|
|
5374
5377
|
padding: 9px;
|
|
@@ -6101,6 +6104,102 @@
|
|
|
6101
6104
|
font-weight: 700;
|
|
6102
6105
|
}
|
|
6103
6106
|
|
|
6107
|
+
.studio-decision-overlay {
|
|
6108
|
+
position: fixed;
|
|
6109
|
+
inset: 0;
|
|
6110
|
+
z-index: 13000;
|
|
6111
|
+
display: grid;
|
|
6112
|
+
place-items: center;
|
|
6113
|
+
padding: 20px;
|
|
6114
|
+
background: rgba(0, 0, 0, 0.58);
|
|
6115
|
+
backdrop-filter: blur(2px);
|
|
6116
|
+
}
|
|
6117
|
+
|
|
6118
|
+
.studio-decision-overlay[hidden] {
|
|
6119
|
+
display: none !important;
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
.studio-decision-dialog {
|
|
6123
|
+
width: min(560px, calc(100vw - 32px));
|
|
6124
|
+
max-height: min(80vh, 680px);
|
|
6125
|
+
overflow: auto;
|
|
6126
|
+
padding: 20px;
|
|
6127
|
+
border: 1px solid var(--panel-border);
|
|
6128
|
+
border-radius: 14px;
|
|
6129
|
+
background: var(--panel);
|
|
6130
|
+
color: var(--text);
|
|
6131
|
+
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.48);
|
|
6132
|
+
}
|
|
6133
|
+
|
|
6134
|
+
.studio-decision-title {
|
|
6135
|
+
margin: 0;
|
|
6136
|
+
color: var(--text);
|
|
6137
|
+
font-size: 17px;
|
|
6138
|
+
line-height: 1.3;
|
|
6139
|
+
}
|
|
6140
|
+
|
|
6141
|
+
.studio-decision-message {
|
|
6142
|
+
margin-top: 10px;
|
|
6143
|
+
color: var(--studio-info-text, var(--muted));
|
|
6144
|
+
font-size: 13px;
|
|
6145
|
+
line-height: 1.5;
|
|
6146
|
+
white-space: pre-wrap;
|
|
6147
|
+
overflow-wrap: anywhere;
|
|
6148
|
+
}
|
|
6149
|
+
|
|
6150
|
+
.studio-decision-input {
|
|
6151
|
+
width: 100%;
|
|
6152
|
+
margin-top: 16px;
|
|
6153
|
+
padding: 9px 10px;
|
|
6154
|
+
border: 1px solid var(--control-border);
|
|
6155
|
+
border-radius: 8px;
|
|
6156
|
+
background: var(--panel-2);
|
|
6157
|
+
color: var(--text);
|
|
6158
|
+
font-family: var(--font-mono);
|
|
6159
|
+
font-size: 13px;
|
|
6160
|
+
}
|
|
6161
|
+
|
|
6162
|
+
.studio-decision-input[hidden] {
|
|
6163
|
+
display: none !important;
|
|
6164
|
+
}
|
|
6165
|
+
|
|
6166
|
+
.studio-decision-input:focus {
|
|
6167
|
+
border-color: var(--accent);
|
|
6168
|
+
outline: 2px solid var(--accent-soft);
|
|
6169
|
+
outline-offset: 1px;
|
|
6170
|
+
}
|
|
6171
|
+
|
|
6172
|
+
.studio-decision-actions {
|
|
6173
|
+
display: flex;
|
|
6174
|
+
flex-wrap: wrap;
|
|
6175
|
+
justify-content: flex-end;
|
|
6176
|
+
gap: 8px;
|
|
6177
|
+
margin-top: 18px;
|
|
6178
|
+
}
|
|
6179
|
+
|
|
6180
|
+
.studio-decision-confirm:not(.is-destructive) {
|
|
6181
|
+
border-color: var(--accent);
|
|
6182
|
+
background: var(--accent);
|
|
6183
|
+
color: var(--accent-contrast);
|
|
6184
|
+
font-weight: 650;
|
|
6185
|
+
}
|
|
6186
|
+
|
|
6187
|
+
.studio-decision-confirm.is-destructive {
|
|
6188
|
+
border-color: var(--warn-border);
|
|
6189
|
+
background: color-mix(in srgb, var(--warn) 15%, var(--panel));
|
|
6190
|
+
color: var(--warn);
|
|
6191
|
+
font-weight: 650;
|
|
6192
|
+
}
|
|
6193
|
+
|
|
6194
|
+
.studio-decision-confirm.is-destructive:hover,
|
|
6195
|
+
.studio-decision-confirm.is-destructive:focus-visible {
|
|
6196
|
+
background: color-mix(in srgb, var(--warn) 24%, var(--panel));
|
|
6197
|
+
}
|
|
6198
|
+
|
|
6199
|
+
body.studio-decision-open {
|
|
6200
|
+
overflow: hidden;
|
|
6201
|
+
}
|
|
6202
|
+
|
|
6104
6203
|
.studio-preview-link-menu {
|
|
6105
6204
|
position: fixed;
|
|
6106
6205
|
z-index: 12000;
|
package/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
|
|
31
31
|
import { resolveStudioPdfResourceFile } from "./shared/studio-pdf-resource.js";
|
|
32
32
|
import { createStudioPandocHtmlResourceFlagResolver } from "./shared/studio-pandoc-resource-flag.js";
|
|
33
|
+
import { prepareStudioLatexForPandoc } from "./shared/studio-latex-pandoc-compat.js";
|
|
33
34
|
import { isStudioCmuxSession, openStudioUrlInBrowser } from "./shared/studio-browser-launcher.js";
|
|
34
35
|
import { buildStudioReplTmuxStartArgs } from "./shared/studio-repl-tmux.js";
|
|
35
36
|
import { buildStudioForwardingHint, buildStudioSshTunnelHint, isStudioSshSession as isSshSession } from "./shared/studio-ssh-hint.js";
|
|
@@ -648,6 +649,7 @@ const STUDIO_QUIZ_CONTEXT_MAX_FILES = 18;
|
|
|
648
649
|
const STUDIO_QUIZ_SNIPPET_MAX_CHARS = 8_000;
|
|
649
650
|
const STUDIO_QUIZ_DISCUSSION_MAX_CHARS = 6_000;
|
|
650
651
|
const REQUEST_BODY_MAX_BYTES = 1_000_000;
|
|
652
|
+
const STUDIO_IMPORT_FILE_MAX_BYTES = 10_000_000;
|
|
651
653
|
const STUDIO_WORKSPACE_STATE_REQUEST_MAX_BYTES = 4_000_000;
|
|
652
654
|
const RESPONSE_HISTORY_LIMIT = 30;
|
|
653
655
|
const CMUX_NOTIFY_TIMEOUT_MS = 1200;
|
|
@@ -6127,6 +6129,31 @@ function decorateStudioPandocSyntaxHtml(html: string): string {
|
|
|
6127
6129
|
);
|
|
6128
6130
|
}
|
|
6129
6131
|
|
|
6132
|
+
function buildStudioLatexPandocCompatibilityWarning(omittedPackages: Array<{ name: string }>): string | undefined {
|
|
6133
|
+
const packageNames = Array.from(new Set(
|
|
6134
|
+
omittedPackages
|
|
6135
|
+
.map((entry) => String(entry?.name ?? "").trim())
|
|
6136
|
+
.filter(Boolean),
|
|
6137
|
+
));
|
|
6138
|
+
if (packageNames.length === 0) return undefined;
|
|
6139
|
+
const packageLabel = packageNames
|
|
6140
|
+
.map((name) => name.toLowerCase().endsWith(".sty") ? name : `${name}.sty`)
|
|
6141
|
+
.join(", ");
|
|
6142
|
+
const noun = packageNames.length === 1 ? "package" : "packages";
|
|
6143
|
+
const subject = packageNames.length === 1 ? "it redefines" : "they redefine";
|
|
6144
|
+
return `Studio omitted local LaTeX ${noun} ${packageLabel} from this Pandoc rendering because ${subject} document startup. Package-specific layout may be absent; compile the source directly with LaTeX for authoritative output.`;
|
|
6145
|
+
}
|
|
6146
|
+
|
|
6147
|
+
function combineStudioWarnings(...warnings: Array<string | undefined>): string | undefined {
|
|
6148
|
+
const messages = Array.from(new Set(warnings.map((warning) => String(warning ?? "").trim()).filter(Boolean)));
|
|
6149
|
+
return messages.length > 0 ? messages.join(" ") : undefined;
|
|
6150
|
+
}
|
|
6151
|
+
|
|
6152
|
+
function renderStudioLatexPandocCompatibilityWarningHtml(warning: string | undefined): string {
|
|
6153
|
+
if (!warning) return "";
|
|
6154
|
+
return `<div class="preview-warning studio-latex-compatibility-warning" role="note">${escapeStudioHtmlText(warning)}</div>`;
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6130
6157
|
const resolveStudioPandocHtmlResourceFlag = createStudioPandocHtmlResourceFlagResolver(async (pandocCommand: string) => {
|
|
6131
6158
|
const result = await runStudioSubprocess(pandocCommand, ["--help"], {
|
|
6132
6159
|
timeoutMs: 5_000,
|
|
@@ -6150,8 +6177,15 @@ function preprocessStudioLatexFootnotemarksForPreview(latex: string): string {
|
|
|
6150
6177
|
|
|
6151
6178
|
async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string, sourcePath?: string): Promise<string> {
|
|
6152
6179
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
6153
|
-
const
|
|
6154
|
-
|
|
6180
|
+
const pandocWorkingDir = resolveStudioPandocWorkingDir(resourcePath)
|
|
6181
|
+
?? resolveStudioPandocWorkingDir(sourcePath ? dirname(sourcePath) : undefined);
|
|
6182
|
+
const latexPandocCompatibility = isLatex
|
|
6183
|
+
? prepareStudioLatexForPandoc(markdown, pandocWorkingDir)
|
|
6184
|
+
: { source: markdown, omittedPackages: [] as Array<{ name: string; path: string }> };
|
|
6185
|
+
const latexPandocCompatibilityWarning = buildStudioLatexPandocCompatibilityWarning(latexPandocCompatibility.omittedPackages);
|
|
6186
|
+
const pandocInputSource = latexPandocCompatibility.source;
|
|
6187
|
+
const latexPreviewSource = isLatex ? preprocessStudioLatexFootnotemarksForPreview(pandocInputSource) : pandocInputSource;
|
|
6188
|
+
const markdownWithNormalizedFences = isLatex ? latexPreviewSource : normalizeStudioMarkdownSmartFences(pandocInputSource);
|
|
6155
6189
|
const markdownWithoutHtmlComments = isLatex ? markdownWithNormalizedFences : stripStudioMarkdownHtmlCommentsPreservingYamlFrontMatter(markdownWithNormalizedFences);
|
|
6156
6190
|
const markdownWithPreviewPageBreaks = isLatex ? markdownWithoutHtmlComments : replaceStudioPreviewPageBreakCommands(markdownWithoutHtmlComments);
|
|
6157
6191
|
const latexSubfigurePreviewTransform = isLatex
|
|
@@ -6164,7 +6198,7 @@ async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolea
|
|
|
6164
6198
|
? preprocessStudioLatexReferences(latexAlgorithmPreviewTransform.markdown, sourcePath, resourcePath)
|
|
6165
6199
|
: markdownWithPreviewPageBreaks;
|
|
6166
6200
|
const inputFormat = isLatex ? "latex" : "markdown+lists_without_preceding_blankline-blank_before_blockquote-blank_before_header+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris-raw_html";
|
|
6167
|
-
const bibliographyArgs = buildStudioPandocBibliographyArgs(
|
|
6201
|
+
const bibliographyArgs = buildStudioPandocBibliographyArgs(pandocInputSource, isLatex, resourcePath);
|
|
6168
6202
|
const args = ["-f", inputFormat, "-t", "html5", "--mathml", "--wrap=none", ...bibliographyArgs];
|
|
6169
6203
|
let htmlTemplateDir: string | null = null;
|
|
6170
6204
|
const useStudioHtmlTemplate = Boolean(resourcePath || isLatex);
|
|
@@ -6185,7 +6219,6 @@ async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolea
|
|
|
6185
6219
|
const normalizedMarkdown = isLatex
|
|
6186
6220
|
? sourceWithResolvedRefs
|
|
6187
6221
|
: normalizeStudioMarkdownFencedBlocks(prepareStudioMarkdownForPandoc(sourceWithResolvedRefs));
|
|
6188
|
-
const pandocWorkingDir = resolveStudioPandocWorkingDir(resourcePath);
|
|
6189
6222
|
|
|
6190
6223
|
let pandocResult: StudioSubprocessResult;
|
|
6191
6224
|
try {
|
|
@@ -6230,6 +6263,7 @@ async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolea
|
|
|
6230
6263
|
renderedHtml = decorateStudioPreviewPageBreakHtml(renderedHtml);
|
|
6231
6264
|
}
|
|
6232
6265
|
renderedHtml = decorateStudioPandocSyntaxHtml(renderedHtml);
|
|
6266
|
+
renderedHtml = renderStudioLatexPandocCompatibilityWarningHtml(latexPandocCompatibilityWarning) + renderedHtml;
|
|
6233
6267
|
return stripMathMlAnnotationTags(renderedHtml);
|
|
6234
6268
|
}
|
|
6235
6269
|
|
|
@@ -6616,8 +6650,15 @@ async function renderStudioStandaloneHtmlWithPandoc(
|
|
|
6616
6650
|
sourcePath?: string,
|
|
6617
6651
|
options?: StudioHtmlRenderOptions,
|
|
6618
6652
|
): Promise<{ html: Buffer; warning?: string }> {
|
|
6619
|
-
const
|
|
6620
|
-
|
|
6653
|
+
const pandocWorkingDir = resolveStudioPandocWorkingDir(resourcePath)
|
|
6654
|
+
?? resolveStudioPandocWorkingDir(sourcePath ? dirname(sourcePath) : undefined);
|
|
6655
|
+
const latexPandocCompatibility = isLatex
|
|
6656
|
+
? prepareStudioLatexForPandoc(markdown, pandocWorkingDir)
|
|
6657
|
+
: { source: markdown, omittedPackages: [] as Array<{ name: string; path: string }> };
|
|
6658
|
+
const latexPandocCompatibilityWarning = buildStudioLatexPandocCompatibilityWarning(latexPandocCompatibility.omittedPackages);
|
|
6659
|
+
const pandocCompatibleMarkdown = latexPandocCompatibility.source;
|
|
6660
|
+
const delimitedMarkdown = isLatex ? null : formatStudioDelimitedTextAsMarkdown(pandocCompatibleMarkdown, editorLanguage);
|
|
6661
|
+
const input = delimitedMarkdown ?? pandocCompatibleMarkdown;
|
|
6621
6662
|
const effectiveEditorLanguage = delimitedMarkdown ? "markdown" : inferStudioPdfLanguage(input, editorLanguage);
|
|
6622
6663
|
if (!isLatex && isLikelyStandaloneStudioHtml(input, effectiveEditorLanguage)) {
|
|
6623
6664
|
return { html: Buffer.from(String(input ?? ""), "utf-8") };
|
|
@@ -6634,8 +6675,12 @@ async function renderStudioStandaloneHtmlWithPandoc(
|
|
|
6634
6675
|
let renderedHtml = await renderStudioMarkdownWithPandoc(pdfPrepared.markdown, isLatex, resourcePath, sourcePath);
|
|
6635
6676
|
renderedHtml = renderStudioPdfBlocksInHtml(renderedHtml, pdfPrepared.blocks, sourcePath, resourcePath);
|
|
6636
6677
|
renderedHtml = applyStudioAnnotationPlaceholdersToHtml(renderedHtml, annotationPrepared.placeholders);
|
|
6678
|
+
renderedHtml = renderStudioLatexPandocCompatibilityWarningHtml(latexPandocCompatibilityWarning) + renderedHtml;
|
|
6637
6679
|
const standaloneHtml = buildStudioStandaloneHtmlDocument(renderedHtml, resourcePath, options);
|
|
6638
|
-
return {
|
|
6680
|
+
return {
|
|
6681
|
+
html: Buffer.from(standaloneHtml, "utf-8"),
|
|
6682
|
+
warning: latexPandocCompatibilityWarning,
|
|
6683
|
+
};
|
|
6639
6684
|
}
|
|
6640
6685
|
|
|
6641
6686
|
async function renderStudioLiteralTextPdf(text: string, title = "Studio export", options?: StudioPdfRenderOptions): Promise<Buffer> {
|
|
@@ -7024,19 +7069,26 @@ async function renderStudioPdfWithPandoc(
|
|
|
7024
7069
|
): Promise<{ pdf: Buffer; warning?: string }> {
|
|
7025
7070
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
7026
7071
|
const pdfEngine = process.env.PANDOC_PDF_ENGINE?.trim() || "xelatex";
|
|
7072
|
+
const pandocWorkingDir = resolveStudioPandocWorkingDir(resourcePath)
|
|
7073
|
+
?? resolveStudioPandocWorkingDir(sourcePath ? dirname(sourcePath) : undefined);
|
|
7074
|
+
const latexPandocCompatibility = isLatex
|
|
7075
|
+
? prepareStudioLatexForPandoc(markdown, pandocWorkingDir)
|
|
7076
|
+
: { source: markdown, omittedPackages: [] as Array<{ name: string; path: string }> };
|
|
7077
|
+
const latexPandocCompatibilityWarning = buildStudioLatexPandocCompatibilityWarning(latexPandocCompatibility.omittedPackages);
|
|
7078
|
+
const pandocInputSource = latexPandocCompatibility.source;
|
|
7027
7079
|
const latexSubfigurePdfTransform = isLatex
|
|
7028
|
-
? preprocessStudioLatexSubfiguresForPdf(
|
|
7029
|
-
: { markdown, groups: [] };
|
|
7080
|
+
? preprocessStudioLatexSubfiguresForPdf(pandocInputSource)
|
|
7081
|
+
: { markdown: pandocInputSource, groups: [] };
|
|
7030
7082
|
const latexPdfSource = isLatex
|
|
7031
7083
|
? preprocessStudioLatexAlgorithmsForPdf(
|
|
7032
7084
|
latexSubfigurePdfTransform.markdown,
|
|
7033
7085
|
sourcePath,
|
|
7034
7086
|
resourcePath,
|
|
7035
7087
|
)
|
|
7036
|
-
:
|
|
7088
|
+
: pandocInputSource;
|
|
7037
7089
|
const sourceWithResolvedRefs = isLatex
|
|
7038
7090
|
? injectStudioLatexEquationTags(preprocessStudioLatexReferences(latexPdfSource, sourcePath, resourcePath), sourcePath, resourcePath)
|
|
7039
|
-
:
|
|
7091
|
+
: pandocInputSource;
|
|
7040
7092
|
const effectiveEditorLanguage = inferStudioPdfLanguage(sourceWithResolvedRefs, editorPdfLanguage);
|
|
7041
7093
|
const pdfCalloutTransform = !isLatex && (!effectiveEditorLanguage || effectiveEditorLanguage === "markdown")
|
|
7042
7094
|
? preprocessStudioMarkdownCalloutsForPdf(sourceWithResolvedRefs)
|
|
@@ -7044,8 +7096,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
7044
7096
|
const pdfAlignedImageTransform = !isLatex && (!effectiveEditorLanguage || effectiveEditorLanguage === "markdown")
|
|
7045
7097
|
? preprocessStudioMarkdownImageAlignmentForPdf(pdfCalloutTransform.markdown)
|
|
7046
7098
|
: { markdown: pdfCalloutTransform.markdown, blocks: [] as StudioPdfAlignedImageBlock[] };
|
|
7047
|
-
const
|
|
7048
|
-
const bibliographyArgs = buildStudioPandocBibliographyArgs(markdown, isLatex, resourcePath);
|
|
7099
|
+
const bibliographyArgs = buildStudioPandocBibliographyArgs(pandocInputSource, isLatex, resourcePath);
|
|
7049
7100
|
|
|
7050
7101
|
const runPandocPdfExport = async (
|
|
7051
7102
|
inputFormat: string,
|
|
@@ -7098,7 +7149,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
7098
7149
|
};
|
|
7099
7150
|
|
|
7100
7151
|
if (isLatex && (latexSubfigurePdfTransform.groups.length > 0 || collectStudioInlineAnnotationMarkers(sourceWithResolvedRefs).length > 0)) {
|
|
7101
|
-
|
|
7152
|
+
const rendered = await renderStudioPdfFromGeneratedLatex(
|
|
7102
7153
|
sourceWithResolvedRefs,
|
|
7103
7154
|
pandocCommand,
|
|
7104
7155
|
pdfEngine,
|
|
@@ -7114,6 +7165,10 @@ async function renderStudioPdfWithPandoc(
|
|
|
7114
7165
|
"",
|
|
7115
7166
|
themeStyle,
|
|
7116
7167
|
);
|
|
7168
|
+
return {
|
|
7169
|
+
pdf: rendered.pdf,
|
|
7170
|
+
warning: combineStudioWarnings(latexPandocCompatibilityWarning, rendered.warning),
|
|
7171
|
+
};
|
|
7117
7172
|
}
|
|
7118
7173
|
|
|
7119
7174
|
if (!isLatex && effectiveEditorLanguage === "diff") {
|
|
@@ -7187,7 +7242,10 @@ async function renderStudioPdfWithPandoc(
|
|
|
7187
7242
|
themeStyle,
|
|
7188
7243
|
);
|
|
7189
7244
|
await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
|
|
7190
|
-
return {
|
|
7245
|
+
return {
|
|
7246
|
+
pdf: rendered.pdf,
|
|
7247
|
+
warning: combineStudioWarnings(latexPandocCompatibilityWarning, mermaidPrepared.warning, rendered.warning),
|
|
7248
|
+
};
|
|
7191
7249
|
}
|
|
7192
7250
|
|
|
7193
7251
|
const hasYamlHeaderIncludesForPdf = !isLatex && hasStudioYamlHeaderIncludes(markdownForPdf);
|
|
@@ -7222,7 +7280,10 @@ async function renderStudioPdfWithPandoc(
|
|
|
7222
7280
|
throw new Error(`pandoc PDF export failed with exit code ${pandocResult.code}${stderr ? `: ${stderr}` : ""}${hint}`);
|
|
7223
7281
|
}
|
|
7224
7282
|
|
|
7225
|
-
return {
|
|
7283
|
+
return {
|
|
7284
|
+
pdf: await readFile(outputPath),
|
|
7285
|
+
warning: combineStudioWarnings(latexPandocCompatibilityWarning, mermaidPrepared.warning),
|
|
7286
|
+
};
|
|
7226
7287
|
} finally {
|
|
7227
7288
|
await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
|
|
7228
7289
|
}
|
|
@@ -7532,6 +7593,64 @@ function revealStudioLocalFile(filePath: string): { ok: true; message: string }
|
|
|
7532
7593
|
return { ok: true, message: process.platform === "linux" ? "Opened containing folder." : "Revealed resource in file manager." };
|
|
7533
7594
|
}
|
|
7534
7595
|
|
|
7596
|
+
async function handleImportStudioFileCopyRequest(req: IncomingMessage, res: ServerResponse, studioCwd: string): Promise<void> {
|
|
7597
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
7598
|
+
if (method !== "POST") {
|
|
7599
|
+
res.setHeader("Allow", "POST");
|
|
7600
|
+
respondJson(res, 405, { ok: false, error: "Method not allowed. Use POST." });
|
|
7601
|
+
return;
|
|
7602
|
+
}
|
|
7603
|
+
|
|
7604
|
+
const rawBody = await readRequestBody(req, REQUEST_BODY_MAX_BYTES);
|
|
7605
|
+
let payload: Record<string, unknown> = {};
|
|
7606
|
+
try {
|
|
7607
|
+
payload = rawBody ? JSON.parse(rawBody) : {};
|
|
7608
|
+
} catch {
|
|
7609
|
+
respondJson(res, 400, { ok: false, error: "Invalid JSON body." });
|
|
7610
|
+
return;
|
|
7611
|
+
}
|
|
7612
|
+
|
|
7613
|
+
const requestedPath = typeof payload.path === "string" ? payload.path : "";
|
|
7614
|
+
const resolved = resolveStudioPath(requestedPath, studioCwd);
|
|
7615
|
+
if (resolved.ok === false) {
|
|
7616
|
+
respondJson(res, 400, { ok: false, error: resolved.message });
|
|
7617
|
+
return;
|
|
7618
|
+
}
|
|
7619
|
+
|
|
7620
|
+
try {
|
|
7621
|
+
const stats = statSync(resolved.resolved);
|
|
7622
|
+
if (!stats.isFile()) {
|
|
7623
|
+
respondJson(res, 400, { ok: false, error: `Path is not a file: ${resolved.label}` });
|
|
7624
|
+
return;
|
|
7625
|
+
}
|
|
7626
|
+
if (stats.size > STUDIO_IMPORT_FILE_MAX_BYTES) {
|
|
7627
|
+
respondJson(res, 413, {
|
|
7628
|
+
ok: false,
|
|
7629
|
+
error: `File is too large to import into Studio (${stats.size} bytes; limit ${STUDIO_IMPORT_FILE_MAX_BYTES} bytes).`,
|
|
7630
|
+
});
|
|
7631
|
+
return;
|
|
7632
|
+
}
|
|
7633
|
+
} catch (error) {
|
|
7634
|
+
respondJson(res, 404, {
|
|
7635
|
+
ok: false,
|
|
7636
|
+
error: `Could not access file: ${resolved.label} (${error instanceof Error ? error.message : String(error)})`,
|
|
7637
|
+
});
|
|
7638
|
+
return;
|
|
7639
|
+
}
|
|
7640
|
+
|
|
7641
|
+
const file = readStudioFile(resolved.resolved, studioCwd);
|
|
7642
|
+
if (file.ok === false) {
|
|
7643
|
+
respondJson(res, 400, { ok: false, error: file.message });
|
|
7644
|
+
return;
|
|
7645
|
+
}
|
|
7646
|
+
respondJson(res, 200, {
|
|
7647
|
+
ok: true,
|
|
7648
|
+
text: file.text,
|
|
7649
|
+
filename: basename(file.resolvedPath),
|
|
7650
|
+
resolvedPath: file.resolvedPath,
|
|
7651
|
+
});
|
|
7652
|
+
}
|
|
7653
|
+
|
|
7535
7654
|
async function handleRevealLocalPreviewResourceRequest(req: IncomingMessage, res: ServerResponse, studioCwd: string): Promise<void> {
|
|
7536
7655
|
const method = (req.method ?? "GET").toUpperCase();
|
|
7537
7656
|
if (method !== "POST") {
|
|
@@ -7567,6 +7686,50 @@ async function handleRevealLocalPreviewResourceRequest(req: IncomingMessage, res
|
|
|
7567
7686
|
}
|
|
7568
7687
|
}
|
|
7569
7688
|
|
|
7689
|
+
async function handleOpenLocalPreviewResourceRequest(req: IncomingMessage, res: ServerResponse, studioCwd: string): Promise<void> {
|
|
7690
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
7691
|
+
if (method !== "POST") {
|
|
7692
|
+
res.setHeader("Allow", "POST");
|
|
7693
|
+
respondJson(res, 405, { ok: false, error: "Method not allowed. Use POST." });
|
|
7694
|
+
return;
|
|
7695
|
+
}
|
|
7696
|
+
if (isSshSession()) {
|
|
7697
|
+
respondJson(res, 409, { ok: false, error: "Cannot open a system PDF viewer from an SSH/headless Studio session. Copy the path instead." });
|
|
7698
|
+
return;
|
|
7699
|
+
}
|
|
7700
|
+
|
|
7701
|
+
const rawBody = await readRequestBody(req, REQUEST_BODY_MAX_BYTES);
|
|
7702
|
+
let payload: Record<string, unknown> = {};
|
|
7703
|
+
try {
|
|
7704
|
+
payload = rawBody ? JSON.parse(rawBody) : {};
|
|
7705
|
+
} catch {
|
|
7706
|
+
respondJson(res, 400, { ok: false, error: "Invalid JSON body." });
|
|
7707
|
+
return;
|
|
7708
|
+
}
|
|
7709
|
+
|
|
7710
|
+
try {
|
|
7711
|
+
const resource = resolveStudioLocalPreviewResourcePath(
|
|
7712
|
+
typeof payload.path === "string" ? payload.path : "",
|
|
7713
|
+
typeof payload.sourcePath === "string" ? payload.sourcePath : undefined,
|
|
7714
|
+
typeof payload.resourceDir === "string" ? payload.resourceDir : undefined,
|
|
7715
|
+
studioCwd,
|
|
7716
|
+
);
|
|
7717
|
+
if (resource.kind !== "pdf") {
|
|
7718
|
+
respondJson(res, 400, { ok: false, error: "Only local PDF previews can be opened in the system PDF viewer." });
|
|
7719
|
+
return;
|
|
7720
|
+
}
|
|
7721
|
+
await openPathInDefaultViewer(resource.filePath);
|
|
7722
|
+
respondJson(res, 200, {
|
|
7723
|
+
ok: true,
|
|
7724
|
+
message: "Opened PDF in the system viewer.",
|
|
7725
|
+
path: resource.filePath,
|
|
7726
|
+
label: resource.label,
|
|
7727
|
+
});
|
|
7728
|
+
} catch (error) {
|
|
7729
|
+
respondJson(res, 404, { ok: false, error: `Local PDF unavailable: ${error instanceof Error ? error.message : String(error)}` });
|
|
7730
|
+
}
|
|
7731
|
+
}
|
|
7732
|
+
|
|
7570
7733
|
function openPathInDefaultViewer(path: string): Promise<void> {
|
|
7571
7734
|
const openCommand =
|
|
7572
7735
|
process.platform === "darwin"
|
|
@@ -10589,7 +10752,8 @@ ${cssVarsBlock}
|
|
|
10589
10752
|
<button id="saveOverBtn" type="button" title="Overwrite current file with editor content. Shortcut: Cmd/Ctrl+S.">Save editor</button>
|
|
10590
10753
|
<button id="refreshFromDiskBtn" type="button" title="Reload the current file-backed document from disk.">Refresh from disk</button>
|
|
10591
10754
|
<button id="clearWorkspaceBtn" type="button" title="Clear editor text and reset this tab to a fresh blank draft. Saved files and responses are not changed.">Reset editor</button>
|
|
10592
|
-
<
|
|
10755
|
+
<button id="importFileBtn" type="button" title="Import a file as an editable copy.">Import file copy…</button>
|
|
10756
|
+
<input id="fileInput" class="file-input-hidden" type="file" tabindex="-1" aria-hidden="true" accept=".md,.markdown,.mdx,.qmd,.js,.mjs,.cjs,.jsx,.ts,.mts,.cts,.tsx,.py,.pyw,.sh,.bash,.zsh,.json,.jsonc,.json5,.rs,.c,.h,.cpp,.cxx,.cc,.hpp,.hxx,.jl,.f90,.f95,.f03,.f,.for,.r,.R,.m,.tex,.latex,.diff,.patch,.java,.go,.rb,.swift,.html,.htm,.css,.xml,.yaml,.yml,.toml,.lua,.txt,.rst,.adoc" />
|
|
10593
10757
|
<button id="getEditorBtn" type="button" title="Load the current terminal editor draft into Studio.">Load from pi editor</button>
|
|
10594
10758
|
<button id="zenModeBtn" class="zen-mode-btn" type="button" title="Hide secondary Studio controls. Shortcut: F9.">Zen</button>
|
|
10595
10759
|
</div>
|
|
@@ -14909,6 +15073,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
14909
15073
|
return;
|
|
14910
15074
|
}
|
|
14911
15075
|
|
|
15076
|
+
if (requestUrl.pathname === "/import-file-copy") {
|
|
15077
|
+
const token = requestUrl.searchParams.get("token") ?? "";
|
|
15078
|
+
if (token !== serverState.token) {
|
|
15079
|
+
respondJson(res, 403, { ok: false, error: "Invalid or expired studio token. Re-run /studio." });
|
|
15080
|
+
return;
|
|
15081
|
+
}
|
|
15082
|
+
|
|
15083
|
+
void handleImportStudioFileCopyRequest(req, res, studioCwd).catch((error) => {
|
|
15084
|
+
respondJson(res, 500, { ok: false, error: `File import failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
15085
|
+
});
|
|
15086
|
+
return;
|
|
15087
|
+
}
|
|
15088
|
+
|
|
14912
15089
|
if (requestUrl.pathname === "/local-preview-link") {
|
|
14913
15090
|
const token = requestUrl.searchParams.get("token") ?? "";
|
|
14914
15091
|
if (token !== serverState.token) {
|
|
@@ -14945,6 +15122,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
14945
15122
|
return;
|
|
14946
15123
|
}
|
|
14947
15124
|
|
|
15125
|
+
if (requestUrl.pathname === "/open-local-resource") {
|
|
15126
|
+
const token = requestUrl.searchParams.get("token") ?? "";
|
|
15127
|
+
if (token !== serverState.token) {
|
|
15128
|
+
respondJson(res, 403, { ok: false, error: "Invalid or expired studio token. Re-run /studio." });
|
|
15129
|
+
return;
|
|
15130
|
+
}
|
|
15131
|
+
|
|
15132
|
+
void handleOpenLocalPreviewResourceRequest(req, res, studioCwd).catch((error) => {
|
|
15133
|
+
respondJson(res, 500, { ok: false, error: `Open in system viewer failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
15134
|
+
});
|
|
15135
|
+
return;
|
|
15136
|
+
}
|
|
15137
|
+
|
|
14948
15138
|
if (requestUrl.pathname === "/pdf-resource") {
|
|
14949
15139
|
const token = requestUrl.searchParams.get("token") ?? "";
|
|
14950
15140
|
if (token !== serverState.token) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.48",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, active quiz, prompt/response history, live previews, and tmux-backed REPL/literate REPL workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|