pi-studio 0.5.47 → 0.5.49
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/README.md +1 -1
- package/client/studio-annotation-helpers.js +66 -0
- package/client/studio-client.js +775 -51
- package/client/studio.css +18 -0
- package/index.ts +41 -112
- package/package.json +1 -1
- package/shared/studio-markdown-html-comments.js +111 -0
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,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
replaceStudioInlineAnnotationMarkers,
|
|
19
20
|
transformStudioMarkdownOutsideFences,
|
|
20
21
|
} from "./shared/studio-annotation-scanner.js";
|
|
22
|
+
import { stripStudioMarkdownHtmlComments } from "./shared/studio-markdown-html-comments.js";
|
|
21
23
|
import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
|
|
22
24
|
|
|
23
25
|
type Lens = "writing" | "code";
|
|
@@ -2892,114 +2894,6 @@ function normalizeMathDelimiters(markdown: string): string {
|
|
|
2892
2894
|
return out.join("\n");
|
|
2893
2895
|
}
|
|
2894
2896
|
|
|
2895
|
-
function stripStudioMarkdownHtmlCommentsInSegment(markdown: string): string {
|
|
2896
|
-
const source = String(markdown ?? "");
|
|
2897
|
-
let out = "";
|
|
2898
|
-
let i = 0;
|
|
2899
|
-
let codeSpanFenceLength = 0;
|
|
2900
|
-
let inHtmlComment = false;
|
|
2901
|
-
|
|
2902
|
-
while (i < source.length) {
|
|
2903
|
-
if (inHtmlComment) {
|
|
2904
|
-
if (source.startsWith("-->", i)) {
|
|
2905
|
-
inHtmlComment = false;
|
|
2906
|
-
i += 3;
|
|
2907
|
-
continue;
|
|
2908
|
-
}
|
|
2909
|
-
const ch = source[i]!;
|
|
2910
|
-
if (ch === "\n" || ch === "\r") out += ch;
|
|
2911
|
-
i += 1;
|
|
2912
|
-
continue;
|
|
2913
|
-
}
|
|
2914
|
-
|
|
2915
|
-
if (codeSpanFenceLength > 0) {
|
|
2916
|
-
const fence = "`".repeat(codeSpanFenceLength);
|
|
2917
|
-
if (source.startsWith(fence, i)) {
|
|
2918
|
-
out += fence;
|
|
2919
|
-
i += codeSpanFenceLength;
|
|
2920
|
-
codeSpanFenceLength = 0;
|
|
2921
|
-
continue;
|
|
2922
|
-
}
|
|
2923
|
-
out += source[i]!;
|
|
2924
|
-
i += 1;
|
|
2925
|
-
continue;
|
|
2926
|
-
}
|
|
2927
|
-
|
|
2928
|
-
const backtickMatch = source.slice(i).match(/^`+/);
|
|
2929
|
-
if (backtickMatch) {
|
|
2930
|
-
const fence = backtickMatch[0]!;
|
|
2931
|
-
codeSpanFenceLength = fence.length;
|
|
2932
|
-
out += fence;
|
|
2933
|
-
i += fence.length;
|
|
2934
|
-
continue;
|
|
2935
|
-
}
|
|
2936
|
-
|
|
2937
|
-
if (source.startsWith("<!--", i)) {
|
|
2938
|
-
inHtmlComment = true;
|
|
2939
|
-
i += 4;
|
|
2940
|
-
continue;
|
|
2941
|
-
}
|
|
2942
|
-
|
|
2943
|
-
out += source[i]!;
|
|
2944
|
-
i += 1;
|
|
2945
|
-
}
|
|
2946
|
-
|
|
2947
|
-
return out;
|
|
2948
|
-
}
|
|
2949
|
-
|
|
2950
|
-
function stripStudioMarkdownHtmlComments(markdown: string): string {
|
|
2951
|
-
const lines = String(markdown ?? "").split("\n");
|
|
2952
|
-
const out: string[] = [];
|
|
2953
|
-
let plainBuffer: string[] = [];
|
|
2954
|
-
let inFence = false;
|
|
2955
|
-
let fenceChar: "`" | "~" | undefined;
|
|
2956
|
-
let fenceLength = 0;
|
|
2957
|
-
|
|
2958
|
-
const flushPlain = () => {
|
|
2959
|
-
if (plainBuffer.length === 0) return;
|
|
2960
|
-
out.push(stripStudioMarkdownHtmlCommentsInSegment(plainBuffer.join("\n")));
|
|
2961
|
-
plainBuffer = [];
|
|
2962
|
-
};
|
|
2963
|
-
|
|
2964
|
-
for (const line of lines) {
|
|
2965
|
-
const trimmed = line.trimStart();
|
|
2966
|
-
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
2967
|
-
|
|
2968
|
-
if (fenceMatch) {
|
|
2969
|
-
const marker = fenceMatch[1]!;
|
|
2970
|
-
const markerChar = marker[0] as "`" | "~";
|
|
2971
|
-
const markerLength = marker.length;
|
|
2972
|
-
|
|
2973
|
-
if (!inFence) {
|
|
2974
|
-
flushPlain();
|
|
2975
|
-
inFence = true;
|
|
2976
|
-
fenceChar = markerChar;
|
|
2977
|
-
fenceLength = markerLength;
|
|
2978
|
-
out.push(line);
|
|
2979
|
-
continue;
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
2983
|
-
inFence = false;
|
|
2984
|
-
fenceChar = undefined;
|
|
2985
|
-
fenceLength = 0;
|
|
2986
|
-
}
|
|
2987
|
-
|
|
2988
|
-
out.push(line);
|
|
2989
|
-
continue;
|
|
2990
|
-
}
|
|
2991
|
-
|
|
2992
|
-
if (inFence) {
|
|
2993
|
-
out.push(line);
|
|
2994
|
-
} else {
|
|
2995
|
-
plainBuffer.push(line);
|
|
2996
|
-
}
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
flushPlain();
|
|
3000
|
-
return out.join("\n");
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
2897
|
const STUDIO_PREVIEW_PAGE_BREAK_SENTINEL_PREFIX = "PI_STUDIO_PAGE_BREAK__";
|
|
3004
2898
|
|
|
3005
2899
|
function replaceStudioPreviewPageBreakCommands(markdown: string): string {
|
|
@@ -3263,6 +3157,26 @@ function inferStudioPdfLanguage(markdown: string, editorLanguage?: string): stri
|
|
|
3263
3157
|
return undefined;
|
|
3264
3158
|
}
|
|
3265
3159
|
|
|
3160
|
+
function stripStudioMarkdownInlineCodeSpans(markdown: string): string {
|
|
3161
|
+
const source = String(markdown ?? "");
|
|
3162
|
+
let out = "";
|
|
3163
|
+
let index = 0;
|
|
3164
|
+
while (index < source.length) {
|
|
3165
|
+
if (source[index] === "`") {
|
|
3166
|
+
index = advancePastStudioInlineBacktickSpan(source, index);
|
|
3167
|
+
continue;
|
|
3168
|
+
}
|
|
3169
|
+
out += source[index];
|
|
3170
|
+
index += 1;
|
|
3171
|
+
}
|
|
3172
|
+
return out;
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
function isLikelyStandaloneLatexPreview(markdown: string): boolean {
|
|
3176
|
+
const outsideFences = transformStudioMarkdownOutsideFences(markdown, (segment: string) => stripStudioMarkdownInlineCodeSpans(segment));
|
|
3177
|
+
return /\\documentclass\b|\\begin\{document\}/.test(outsideFences);
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3266
3180
|
function escapeStudioPdfLatexText(text: string): string {
|
|
3267
3181
|
const normalized = String(text ?? "")
|
|
3268
3182
|
.replace(/\r\n/g, "\n")
|
|
@@ -4043,9 +3957,15 @@ function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLan
|
|
|
4043
3957
|
}
|
|
4044
3958
|
|
|
4045
3959
|
function stripMathMlAnnotationTags(html: string): string {
|
|
4046
|
-
return html
|
|
4047
|
-
.
|
|
4048
|
-
|
|
3960
|
+
return String(html ?? "").replace(/<math\b([^>]*)>([\s\S]*?)<\/math>/gi, (_match, attrs, inner) => {
|
|
3961
|
+
const texAnnotationMatch = String(inner ?? "").match(/<annotation\b[^>]*encoding="application\/x-tex"[^>]*>([\s\S]*?)<\/annotation>/i);
|
|
3962
|
+
const texSource = texAnnotationMatch ? String(texAnnotationMatch[1] ?? "").trim() : "";
|
|
3963
|
+
const cleanedInner = String(inner ?? "")
|
|
3964
|
+
.replace(/<annotation-xml\b[\s\S]*?<\/annotation-xml>/gi, "")
|
|
3965
|
+
.replace(/<annotation\b[\s\S]*?<\/annotation>/gi, "");
|
|
3966
|
+
const texAttr = texSource ? ` data-tex-source="${escapeStudioHtmlText(texSource)}"` : "";
|
|
3967
|
+
return `<math${attrs}${texAttr}>${cleanedInner}</math>`;
|
|
3968
|
+
});
|
|
4049
3969
|
}
|
|
4050
3970
|
|
|
4051
3971
|
function normalizeObsidianImages(markdown: string): string {
|
|
@@ -7869,8 +7789,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
7869
7789
|
parsedBody && typeof parsedBody === "object" && typeof (parsedBody as { resourceDir?: unknown }).resourceDir === "string"
|
|
7870
7790
|
? (parsedBody as { resourceDir: string }).resourceDir
|
|
7871
7791
|
: "";
|
|
7792
|
+
const requestedEditorLanguage =
|
|
7793
|
+
parsedBody && typeof parsedBody === "object" && typeof (parsedBody as { editorLanguage?: unknown }).editorLanguage === "string"
|
|
7794
|
+
? (parsedBody as { editorLanguage: string }).editorLanguage
|
|
7795
|
+
: "";
|
|
7872
7796
|
const resourcePath = resolveStudioBaseDir(sourcePath || undefined, userResourceDir || undefined, studioCwd);
|
|
7873
|
-
const
|
|
7797
|
+
const editorPreviewLanguage = normalizeStudioEditorLanguage(requestedEditorLanguage);
|
|
7798
|
+
const isLatex = editorPreviewLanguage === "latex"
|
|
7799
|
+
|| (
|
|
7800
|
+
(editorPreviewLanguage === undefined || editorPreviewLanguage === "markdown")
|
|
7801
|
+
&& isLikelyStandaloneLatexPreview(markdown)
|
|
7802
|
+
);
|
|
7874
7803
|
const html = await renderStudioMarkdownWithPandoc(markdown, isLatex, resourcePath, sourcePath || undefined);
|
|
7875
7804
|
respondJson(res, 200, { ok: true, html, renderer: "pandoc" });
|
|
7876
7805
|
} 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.49",
|
|
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",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export function stripStudioMarkdownHtmlCommentsInSegment(markdown) {
|
|
2
|
+
const source = String(markdown ?? "");
|
|
3
|
+
let out = "";
|
|
4
|
+
let index = 0;
|
|
5
|
+
let codeSpanFenceLength = 0;
|
|
6
|
+
let inHtmlComment = false;
|
|
7
|
+
|
|
8
|
+
while (index < source.length) {
|
|
9
|
+
if (inHtmlComment) {
|
|
10
|
+
if (source.startsWith("-->", index)) {
|
|
11
|
+
inHtmlComment = false;
|
|
12
|
+
index += 3;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const ch = source[index];
|
|
16
|
+
if (ch === "\n" || ch === "\r") out += ch;
|
|
17
|
+
index += 1;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (codeSpanFenceLength > 0) {
|
|
22
|
+
const fence = "`".repeat(codeSpanFenceLength);
|
|
23
|
+
if (source.startsWith(fence, index)) {
|
|
24
|
+
out += fence;
|
|
25
|
+
index += codeSpanFenceLength;
|
|
26
|
+
codeSpanFenceLength = 0;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const ch = source[index];
|
|
30
|
+
out += ch;
|
|
31
|
+
index += 1;
|
|
32
|
+
if (ch === "\n" || ch === "\r") {
|
|
33
|
+
codeSpanFenceLength = 0;
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const backtickMatch = source.slice(index).match(/^`+/);
|
|
39
|
+
if (backtickMatch) {
|
|
40
|
+
const fence = backtickMatch[0];
|
|
41
|
+
codeSpanFenceLength = fence.length;
|
|
42
|
+
out += fence;
|
|
43
|
+
index += fence.length;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (source.startsWith("<!--", index)) {
|
|
48
|
+
inHtmlComment = true;
|
|
49
|
+
index += 4;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
out += source[index];
|
|
54
|
+
index += 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function stripStudioMarkdownHtmlComments(markdown) {
|
|
61
|
+
const lines = String(markdown ?? "").split("\n");
|
|
62
|
+
const out = [];
|
|
63
|
+
let plainBuffer = [];
|
|
64
|
+
let inFence = false;
|
|
65
|
+
let fenceChar;
|
|
66
|
+
let fenceLength = 0;
|
|
67
|
+
|
|
68
|
+
const flushPlain = () => {
|
|
69
|
+
if (plainBuffer.length === 0) return;
|
|
70
|
+
out.push(stripStudioMarkdownHtmlCommentsInSegment(plainBuffer.join("\n")));
|
|
71
|
+
plainBuffer = [];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
const trimmed = line.trimStart();
|
|
76
|
+
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
77
|
+
|
|
78
|
+
if (fenceMatch) {
|
|
79
|
+
const marker = fenceMatch[1];
|
|
80
|
+
const markerChar = marker[0];
|
|
81
|
+
const markerLength = marker.length;
|
|
82
|
+
|
|
83
|
+
if (!inFence) {
|
|
84
|
+
flushPlain();
|
|
85
|
+
inFence = true;
|
|
86
|
+
fenceChar = markerChar;
|
|
87
|
+
fenceLength = markerLength;
|
|
88
|
+
out.push(line);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
93
|
+
inFence = false;
|
|
94
|
+
fenceChar = undefined;
|
|
95
|
+
fenceLength = 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
out.push(line);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (inFence) {
|
|
103
|
+
out.push(line);
|
|
104
|
+
} else {
|
|
105
|
+
plainBuffer.push(line);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
flushPlain();
|
|
110
|
+
return out.join("\n");
|
|
111
|
+
}
|