pi-studio 0.5.34 → 0.5.36

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.
@@ -241,8 +241,11 @@
241
241
  let responseHighlightEnabled = false;
242
242
  let editorHighlightRenderRaf = null;
243
243
  let annotationsEnabled = true;
244
- const ANNOTATION_MARKER_REGEX = /\[an:\s*([^\]]+?)\]/gi;
245
244
  const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
245
+ const annotationHelpers = globalThis.PiStudioAnnotationHelpers;
246
+ if (!annotationHelpers || typeof annotationHelpers.collectInlineAnnotationMarkers !== "function") {
247
+ throw new Error("Studio annotation helpers failed to load.");
248
+ }
246
249
  const EMPTY_OVERLAY_LINE = "\u200b";
247
250
  const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
248
251
  const MATHJAX_CDN_URL = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js";
@@ -1163,15 +1166,11 @@
1163
1166
  }
1164
1167
 
1165
1168
  function hasAnnotationMarkers(text) {
1166
- const source = String(text || "");
1167
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
1168
- const hasMarker = ANNOTATION_MARKER_REGEX.test(source);
1169
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
1170
- return hasMarker;
1169
+ return annotationHelpers.hasAnnotationMarkers(text);
1171
1170
  }
1172
1171
 
1173
1172
  function stripAnnotationMarkers(text) {
1174
- return String(text || "").replace(ANNOTATION_MARKER_REGEX, "");
1173
+ return annotationHelpers.stripAnnotationMarkers(text);
1175
1174
  }
1176
1175
 
1177
1176
  function prepareEditorTextForSend(text) {
@@ -1184,78 +1183,8 @@
1184
1183
  return annotationsEnabled ? raw : stripAnnotationMarkers(raw);
1185
1184
  }
1186
1185
 
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
1186
  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 };
1187
+ return annotationHelpers.prepareMarkdownForPandocPreview(markdown, PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX);
1259
1188
  }
1260
1189
 
1261
1190
  function wrapAsFencedCodeBlock(text, language) {
@@ -1774,8 +1703,9 @@
1774
1703
  if (entry) {
1775
1704
  const markerEl = document.createElement("span");
1776
1705
  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;
1706
+ const markerText = typeof entry.text === "string" ? entry.text : token;
1707
+ markerEl.title = typeof entry.title === "string" ? entry.title : markerText;
1708
+ setAnnotationPreviewMarkerContent(markerEl, markerText);
1779
1709
  fragment.appendChild(markerEl);
1780
1710
  } else {
1781
1711
  fragment.appendChild(document.createTextNode(token));
@@ -1819,33 +1749,28 @@
1819
1749
  for (const textNode of textNodes) {
1820
1750
  const text = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
1821
1751
  if (!text) continue;
1822
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
1823
- if (!ANNOTATION_MARKER_REGEX.test(text)) continue;
1824
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
1752
+ const markers = annotationHelpers.collectInlineAnnotationMarkers(text);
1753
+ if (markers.length === 0) continue;
1825
1754
 
1826
1755
  const fragment = document.createDocumentFragment();
1827
1756
  let lastIndex = 0;
1828
- let match;
1829
- while ((match = ANNOTATION_MARKER_REGEX.exec(text)) !== null) {
1830
- const token = match[0] || "";
1831
- const start = typeof match.index === "number" ? match.index : 0;
1832
- if (start > lastIndex) {
1833
- fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)));
1757
+ markers.forEach(function(marker) {
1758
+ const token = marker.raw || "";
1759
+ if (marker.start > lastIndex) {
1760
+ fragment.appendChild(document.createTextNode(text.slice(lastIndex, marker.start)));
1834
1761
  }
1835
1762
 
1836
1763
  if (mode === "highlight") {
1837
1764
  const markerEl = document.createElement("span");
1838
1765
  markerEl.className = "annotation-preview-marker";
1839
- markerEl.textContent = typeof match[1] === "string" ? match[1].trim() : token;
1766
+ const markerText = annotationHelpers.normalizePreviewAnnotationLabel(marker.body) || token;
1840
1767
  markerEl.title = token;
1768
+ setAnnotationPreviewMarkerContent(markerEl, markerText);
1841
1769
  fragment.appendChild(markerEl);
1842
1770
  }
1843
1771
 
1844
- lastIndex = start + token.length;
1845
- if (token.length === 0) {
1846
- ANNOTATION_MARKER_REGEX.lastIndex += 1;
1847
- }
1848
- }
1772
+ lastIndex = marker.end;
1773
+ });
1849
1774
 
1850
1775
  if (lastIndex < text.length) {
1851
1776
  fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
@@ -2732,50 +2657,44 @@
2732
2657
  return "<span class='" + className + "'>" + escapeHtml(String(text || "")) + "</span>";
2733
2658
  }
2734
2659
 
2735
- function wrapHighlightWithTitle(className, text, title) {
2660
+ function buildAnnotationPreviewMarkerHtml(text, title) {
2736
2661
  const titleAttr = title ? " title='" + escapeHtml(String(title)) + "'" : "";
2737
- return "<span class='" + className + "'" + titleAttr + ">" + escapeHtml(String(text || "")) + "</span>";
2662
+ const rendered = typeof annotationHelpers.renderPreviewAnnotationHtml === "function"
2663
+ ? annotationHelpers.renderPreviewAnnotationHtml(text)
2664
+ : escapeHtml(String(text || ""));
2665
+ return "<span class='annotation-preview-marker'" + titleAttr + ">" + rendered + "</span>";
2666
+ }
2667
+
2668
+ function setAnnotationPreviewMarkerContent(markerEl, text) {
2669
+ if (!markerEl) return;
2670
+ const rendered = typeof annotationHelpers.renderPreviewAnnotationHtml === "function"
2671
+ ? annotationHelpers.renderPreviewAnnotationHtml(text)
2672
+ : escapeHtml(String(text || ""));
2673
+ markerEl.innerHTML = rendered;
2738
2674
  }
2739
2675
 
2740
2676
  function highlightInlineAnnotations(text, mode) {
2741
2677
  const source = String(text || "");
2742
2678
  const renderMode = mode === "preview" ? "preview" : "overlay";
2743
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
2744
- let lastIndex = 0;
2745
- let out = "";
2746
-
2747
- let match;
2748
- while ((match = ANNOTATION_MARKER_REGEX.exec(source)) !== null) {
2749
- const token = match[0] || "";
2750
- const start = typeof match.index === "number" ? match.index : 0;
2751
- const markerText = typeof match[1] === "string" ? match[1].trim() : token;
2752
-
2753
- if (start > lastIndex) {
2754
- out += escapeHtml(source.slice(lastIndex, start));
2755
- }
2756
-
2757
- if (renderMode === "preview") {
2758
- out += wrapHighlightWithTitle("annotation-preview-marker", markerText || token, token);
2759
- } else {
2760
- out += wrapHighlight(annotationsEnabled ? "hl-annotation" : "hl-annotation-muted", token);
2761
- }
2762
- lastIndex = start + token.length;
2763
- if (token.length === 0) {
2764
- ANNOTATION_MARKER_REGEX.lastIndex += 1;
2765
- }
2766
- }
2767
-
2768
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
2769
- if (lastIndex < source.length) {
2770
- out += escapeHtml(source.slice(lastIndex));
2771
- }
2772
-
2773
- return out;
2679
+ return annotationHelpers.replaceInlineAnnotationMarkers(
2680
+ source,
2681
+ function(marker) {
2682
+ const token = marker.raw || "";
2683
+ const markerText = annotationHelpers.normalizePreviewAnnotationLabel(marker.body) || token;
2684
+ if (renderMode === "preview") {
2685
+ return buildAnnotationPreviewMarkerHtml(markerText, token);
2686
+ }
2687
+ return wrapHighlight(annotationsEnabled ? "hl-annotation" : "hl-annotation-muted", token);
2688
+ },
2689
+ function(segment) {
2690
+ return escapeHtml(segment);
2691
+ },
2692
+ );
2774
2693
  }
2775
2694
 
2776
- function highlightInlineMarkdown(text) {
2695
+ function highlightInlineMarkdownWithoutAnnotations(text) {
2777
2696
  const source = String(text || "");
2778
- const pattern = /(\x60[^\x60]*\x60)|(\[[^\]]+\]\([^)]+\))|(\[an:\s*[^\]]+\])/gi;
2697
+ const pattern = /(\x60[^\x60]*\x60)|(\[[^\]]+\]\([^)]+\))/g;
2779
2698
  let lastIndex = 0;
2780
2699
  let out = "";
2781
2700
 
@@ -2798,8 +2717,6 @@
2798
2717
  } else {
2799
2718
  out += escapeHtml(token);
2800
2719
  }
2801
- } else if (match[3]) {
2802
- out += highlightInlineAnnotations(token);
2803
2720
  } else {
2804
2721
  out += escapeHtml(token);
2805
2722
  }
@@ -2814,6 +2731,18 @@
2814
2731
  return out;
2815
2732
  }
2816
2733
 
2734
+ function highlightInlineMarkdown(text) {
2735
+ return annotationHelpers.replaceInlineAnnotationMarkers(
2736
+ String(text || ""),
2737
+ function(marker) {
2738
+ return highlightInlineAnnotations(marker.raw || "");
2739
+ },
2740
+ function(segment) {
2741
+ return highlightInlineMarkdownWithoutAnnotations(segment);
2742
+ },
2743
+ );
2744
+ }
2745
+
2817
2746
  function normalizeFenceLanguage(info) {
2818
2747
  const raw = String(info || "").trim();
2819
2748
  if (!raw) return "";
package/client/studio.css CHANGED
@@ -523,6 +523,14 @@
523
523
  vertical-align: baseline;
524
524
  }
525
525
 
526
+ .annotation-preview-marker code {
527
+ font-family: var(--mono-font, ui-monospace, SFMono-Regular, Menlo, monospace);
528
+ font-size: 0.95em;
529
+ background: rgba(0, 0, 0, 0.08);
530
+ border-radius: 3px;
531
+ padding: 0 0.2em;
532
+ }
533
+
526
534
  #sourcePreview {
527
535
  flex: 1 1 auto;
528
536
  min-height: 0;