pi-studio 0.5.53 → 0.5.54
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 +6 -0
- package/client/studio-annotation-helpers.js +67 -0
- package/client/studio-client.js +83 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.54] — 2026-04-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Markdown editor-preview comment mapping now keeps standalone markdown image blocks aligned with rendered preview figures, preventing later preview comment targets from drifting after figures.
|
|
11
|
+
- Rendered Markdown fenced code blocks now once again support preview-side text-selection comments.
|
|
12
|
+
|
|
7
13
|
## [0.5.53] — 2026-04-10
|
|
8
14
|
|
|
9
15
|
### Added
|
|
@@ -187,7 +187,9 @@
|
|
|
187
187
|
|
|
188
188
|
if (index >= text.length || text[index] !== "]" || text[index + 1] !== "(") return null;
|
|
189
189
|
|
|
190
|
+
const label = text.slice(startIndex + 1, index);
|
|
190
191
|
index += 2;
|
|
192
|
+
const destinationStart = index;
|
|
191
193
|
let parenDepth = 0;
|
|
192
194
|
while (index < text.length) {
|
|
193
195
|
const ch = text[index];
|
|
@@ -210,6 +212,8 @@
|
|
|
210
212
|
type: "literal",
|
|
211
213
|
raw: text.slice(startIndex, index + 1),
|
|
212
214
|
end: index + 1,
|
|
215
|
+
label: label,
|
|
216
|
+
destination: text.slice(destinationStart, index),
|
|
213
217
|
};
|
|
214
218
|
}
|
|
215
219
|
parenDepth -= 1;
|
|
@@ -223,6 +227,68 @@
|
|
|
223
227
|
return null;
|
|
224
228
|
}
|
|
225
229
|
|
|
230
|
+
function readMarkdownAttributeBlockAt(source, startIndex) {
|
|
231
|
+
const text = String(source || "");
|
|
232
|
+
if (text[startIndex] !== "{") return null;
|
|
233
|
+
|
|
234
|
+
let depth = 0;
|
|
235
|
+
for (let index = startIndex; index < text.length; index += 1) {
|
|
236
|
+
const ch = text[index];
|
|
237
|
+
if (ch === "\\") {
|
|
238
|
+
index += 1;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (ch === "\n") return null;
|
|
242
|
+
if (ch === "{") {
|
|
243
|
+
depth += 1;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (ch === "}") {
|
|
247
|
+
depth -= 1;
|
|
248
|
+
if (depth === 0) {
|
|
249
|
+
return {
|
|
250
|
+
raw: text.slice(startIndex, index + 1),
|
|
251
|
+
end: index + 1,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function extractStandaloneMarkdownImageCaptionText(text) {
|
|
261
|
+
const source = String(text || "").replace(/\r\n/g, "\n").trim();
|
|
262
|
+
if (!source) return null;
|
|
263
|
+
|
|
264
|
+
const captions = [];
|
|
265
|
+
let index = 0;
|
|
266
|
+
let sawImage = false;
|
|
267
|
+
|
|
268
|
+
while (index < source.length) {
|
|
269
|
+
while (index < source.length && /\s/.test(source[index])) index += 1;
|
|
270
|
+
if (index >= source.length) break;
|
|
271
|
+
if (source[index] !== "!") return null;
|
|
272
|
+
|
|
273
|
+
const imageToken = readInlineMarkdownLinkAt(source, index + 1);
|
|
274
|
+
if (!imageToken) return null;
|
|
275
|
+
|
|
276
|
+
sawImage = true;
|
|
277
|
+
captions.push(normalizePreviewAnnotationLabel(imageToken.label || ""));
|
|
278
|
+
index = imageToken.end;
|
|
279
|
+
|
|
280
|
+
while (index < source.length && /\s/.test(source[index])) index += 1;
|
|
281
|
+
if (index < source.length && source[index] === "{") {
|
|
282
|
+
const attributeBlock = readMarkdownAttributeBlockAt(source, index);
|
|
283
|
+
if (!attributeBlock) return null;
|
|
284
|
+
index = attributeBlock.end;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!sawImage) return null;
|
|
289
|
+
return captions.filter(Boolean).join(" ").trim();
|
|
290
|
+
}
|
|
291
|
+
|
|
226
292
|
function readDelimitedPreviewTokenAt(source, startIndex, open, close, allowNewlines) {
|
|
227
293
|
const text = String(source || "");
|
|
228
294
|
if (text.slice(startIndex, startIndex + open.length) !== open) return null;
|
|
@@ -596,6 +662,7 @@
|
|
|
596
662
|
collectInlineAnnotationMarkers: collectInlineAnnotationMarkers,
|
|
597
663
|
hasAnnotationMarkers: hasAnnotationMarkers,
|
|
598
664
|
normalizePreviewAnnotationLabel: normalizePreviewAnnotationLabel,
|
|
665
|
+
extractStandaloneMarkdownImageCaptionText: extractStandaloneMarkdownImageCaptionText,
|
|
599
666
|
prepareMarkdownForPandocPreview: prepareMarkdownForPandocPreview,
|
|
600
667
|
readInlineAnnotationMarkerAt: readInlineAnnotationMarkerAt,
|
|
601
668
|
renderPreviewAnnotationHtml: renderPreviewAnnotationHtml,
|
package/client/studio-client.js
CHANGED
|
@@ -5309,6 +5309,7 @@
|
|
|
5309
5309
|
|| kind === "blockquote"
|
|
5310
5310
|
|| kind === "list"
|
|
5311
5311
|
|| kind === "math"
|
|
5312
|
+
|| kind === "code"
|
|
5312
5313
|
|| kind === "code-line"
|
|
5313
5314
|
|| kind === "diff-line"
|
|
5314
5315
|
|| kind === "text-line";
|
|
@@ -6422,7 +6423,7 @@
|
|
|
6422
6423
|
|
|
6423
6424
|
function buildPreviewSelectionDisplayMap(blockText, kind) {
|
|
6424
6425
|
const body = buildPreviewSelectionSourceBody(blockText, kind);
|
|
6425
|
-
if (kind === "code-line" || kind === "diff-line" || kind === "text-line") {
|
|
6426
|
+
if (kind === "code" || kind === "code-line" || kind === "diff-line" || kind === "text-line") {
|
|
6426
6427
|
return buildLiteralPreviewDisplayMap(body.text, body.rawOffsets);
|
|
6427
6428
|
}
|
|
6428
6429
|
const inlineMap = buildPreviewInlineDisplayMap(body.text, body.rawOffsets);
|
|
@@ -6694,6 +6695,15 @@
|
|
|
6694
6695
|
};
|
|
6695
6696
|
}
|
|
6696
6697
|
|
|
6698
|
+
function getChunkText(startLineIndex, endLineIndex) {
|
|
6699
|
+
const safeStartLine = Math.max(0, Math.min(startLineIndex, Math.max(0, lines.length - 1)));
|
|
6700
|
+
const safeEndLine = Math.max(safeStartLine, Math.min(endLineIndex, Math.max(0, lines.length - 1)));
|
|
6701
|
+
return source.slice(
|
|
6702
|
+
lineOffsets[safeStartLine] || 0,
|
|
6703
|
+
(lineOffsets[safeEndLine] || 0) + getLine(safeEndLine).length,
|
|
6704
|
+
);
|
|
6705
|
+
}
|
|
6706
|
+
|
|
6697
6707
|
const blocks = [];
|
|
6698
6708
|
let index = 0;
|
|
6699
6709
|
|
|
@@ -6826,7 +6836,11 @@
|
|
|
6826
6836
|
}
|
|
6827
6837
|
endParagraph = i;
|
|
6828
6838
|
}
|
|
6829
|
-
|
|
6839
|
+
const paragraphText = getChunkText(index, endParagraph);
|
|
6840
|
+
const markdownFigureCaption = annotationHelpers && typeof annotationHelpers.extractStandaloneMarkdownImageCaptionText === "function"
|
|
6841
|
+
? annotationHelpers.extractStandaloneMarkdownImageCaptionText(paragraphText)
|
|
6842
|
+
: null;
|
|
6843
|
+
blocks.push(makeBlock(markdownFigureCaption != null ? "figure" : "paragraph", index, endParagraph));
|
|
6830
6844
|
index = endParagraph + 1;
|
|
6831
6845
|
}
|
|
6832
6846
|
|
|
@@ -7145,6 +7159,42 @@
|
|
|
7145
7159
|
});
|
|
7146
7160
|
}
|
|
7147
7161
|
|
|
7162
|
+
function isPreviewMediaOnlyParagraphElement(element) {
|
|
7163
|
+
if (!element || !(element instanceof Element)) return false;
|
|
7164
|
+
if ((element.tagName ? element.tagName.toUpperCase() : "") !== "P") return false;
|
|
7165
|
+
|
|
7166
|
+
let hasMedia = false;
|
|
7167
|
+
for (const childNode of Array.from(element.childNodes || [])) {
|
|
7168
|
+
if (!childNode) continue;
|
|
7169
|
+
if (childNode.nodeType === Node.TEXT_NODE) {
|
|
7170
|
+
if (normalizeVisiblePreviewText(childNode.nodeValue || "")) {
|
|
7171
|
+
return false;
|
|
7172
|
+
}
|
|
7173
|
+
continue;
|
|
7174
|
+
}
|
|
7175
|
+
if (!(childNode instanceof Element)) continue;
|
|
7176
|
+
|
|
7177
|
+
const childTag = childNode.tagName ? childNode.tagName.toUpperCase() : "";
|
|
7178
|
+
if (childTag === "BR") continue;
|
|
7179
|
+
if (childTag === "IMG" || childTag === "EMBED" || childTag === "OBJECT" || childTag === "IFRAME" || childTag === "CANVAS") {
|
|
7180
|
+
hasMedia = true;
|
|
7181
|
+
continue;
|
|
7182
|
+
}
|
|
7183
|
+
|
|
7184
|
+
const nestedMedia = typeof childNode.querySelector === "function"
|
|
7185
|
+
? childNode.querySelector("img, embed, object, iframe, canvas")
|
|
7186
|
+
: null;
|
|
7187
|
+
if (nestedMedia && !buildNormalizedPreviewSearchText(childNode)) {
|
|
7188
|
+
hasMedia = true;
|
|
7189
|
+
continue;
|
|
7190
|
+
}
|
|
7191
|
+
|
|
7192
|
+
return false;
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7195
|
+
return hasMedia;
|
|
7196
|
+
}
|
|
7197
|
+
|
|
7148
7198
|
function getPreviewCommentTargetKind(element) {
|
|
7149
7199
|
if (!element || !(element instanceof Element)) return "";
|
|
7150
7200
|
if (element.classList && element.classList.contains("studio-mathjax-fallback-display")) {
|
|
@@ -7155,12 +7205,12 @@
|
|
|
7155
7205
|
}
|
|
7156
7206
|
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
7157
7207
|
if (/^H[1-6]$/.test(tag)) return "heading";
|
|
7158
|
-
if (tag === "P") return "paragraph";
|
|
7208
|
+
if (tag === "P") return isPreviewMediaOnlyParagraphElement(element) ? "figure" : "paragraph";
|
|
7159
7209
|
if (tag === "FIGURE") {
|
|
7160
7210
|
if (element.classList && element.classList.contains("studio-algorithm-block")) {
|
|
7161
7211
|
return "algorithm";
|
|
7162
7212
|
}
|
|
7163
|
-
return
|
|
7213
|
+
return "figure";
|
|
7164
7214
|
}
|
|
7165
7215
|
if (tag === "DIV" && element.classList) {
|
|
7166
7216
|
if (element.classList.contains("studio-display-equation")) {
|
|
@@ -7267,6 +7317,14 @@
|
|
|
7267
7317
|
const match = blockText.trim().match(/^\\(newpage|pagebreak|clearpage)/i);
|
|
7268
7318
|
return match ? String(match[1] || "").toLowerCase() : "page-break";
|
|
7269
7319
|
}
|
|
7320
|
+
if (sourceBlock.kind === "figure") {
|
|
7321
|
+
const figureCaption = annotationHelpers && typeof annotationHelpers.extractStandaloneMarkdownImageCaptionText === "function"
|
|
7322
|
+
? annotationHelpers.extractStandaloneMarkdownImageCaptionText(blockText)
|
|
7323
|
+
: null;
|
|
7324
|
+
if (figureCaption != null) {
|
|
7325
|
+
return normalizeVisiblePreviewText(figureCaption);
|
|
7326
|
+
}
|
|
7327
|
+
}
|
|
7270
7328
|
if (supportsPreviewSelectionCommentsForBlockKind(sourceBlock.kind)) {
|
|
7271
7329
|
return normalizeVisiblePreviewText(buildPreviewSelectionDisplayMap(blockText, sourceBlock.kind).text);
|
|
7272
7330
|
}
|
|
@@ -7287,6 +7345,23 @@
|
|
|
7287
7345
|
return normalizeVisiblePreviewText(blockText);
|
|
7288
7346
|
}
|
|
7289
7347
|
|
|
7348
|
+
function getPreviewFigureSearchText(element) {
|
|
7349
|
+
if (!element || !(element instanceof Element)) return "";
|
|
7350
|
+
const visibleText = buildNormalizedPreviewSearchText(element);
|
|
7351
|
+
if (visibleText) return visibleText;
|
|
7352
|
+
|
|
7353
|
+
const imageNodes = (element.tagName ? element.tagName.toUpperCase() : "") === "IMG"
|
|
7354
|
+
? [element]
|
|
7355
|
+
: (typeof element.querySelectorAll === "function" ? Array.from(element.querySelectorAll("img[alt], img[title]")) : []);
|
|
7356
|
+
const altText = imageNodes
|
|
7357
|
+
.filter((imageEl) => imageEl instanceof Element)
|
|
7358
|
+
.map((imageEl) => imageEl.getAttribute("alt") || imageEl.getAttribute("title") || "")
|
|
7359
|
+
.map((text) => normalizeVisiblePreviewText(text))
|
|
7360
|
+
.filter(Boolean)
|
|
7361
|
+
.join(" ");
|
|
7362
|
+
return altText;
|
|
7363
|
+
}
|
|
7364
|
+
|
|
7290
7365
|
function getNormalizedPreviewCommentTargetText(targetEntry) {
|
|
7291
7366
|
if (!targetEntry) return "";
|
|
7292
7367
|
if (typeof targetEntry.normalizedText === "string") return targetEntry.normalizedText;
|
|
@@ -7295,6 +7370,10 @@
|
|
|
7295
7370
|
targetEntry.normalizedText = String(element && element.getAttribute ? (element.getAttribute("data-page-break-kind") || "page-break") : "page-break").toLowerCase();
|
|
7296
7371
|
return targetEntry.normalizedText;
|
|
7297
7372
|
}
|
|
7373
|
+
if (targetEntry.kind === "figure") {
|
|
7374
|
+
targetEntry.normalizedText = getPreviewFigureSearchText(targetEntry.element);
|
|
7375
|
+
return targetEntry.normalizedText;
|
|
7376
|
+
}
|
|
7298
7377
|
targetEntry.normalizedText = buildNormalizedPreviewSearchText(targetEntry.element);
|
|
7299
7378
|
return targetEntry.normalizedText;
|
|
7300
7379
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.54",
|
|
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",
|