pi-studio 0.5.48 → 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 +7 -0
- package/client/studio-annotation-helpers.js +66 -0
- package/client/studio-client.js +630 -28
- package/index.ts +1 -108
- package/package.json +1 -1
- package/shared/studio-markdown-html-comments.js +111 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.49] — 2026-04-09
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Markdown editor-preview comments are now substantially more robust on real pandoc-rendered documents, including smart punctuation and ellipsis normalization, mixed prose plus display-math sections, standalone display equations treated as whole units, MathJax fallback display math, standalone page-break commands, and raw-TeX commands that disappear from preview output.
|
|
11
|
+
- Preview comment block matching now avoids unsafe tiny substring fallbacks, so extra blank-line splits that create short paragraphs like `or` no longer derail later Markdown comment targets.
|
|
12
|
+
- Markdown HTML comments like `<!-- ... -->` are now stripped more consistently across both the main pandoc preview path and plain-markdown fallback rendering, including edge cases after unmatched inline backticks at line breaks.
|
|
13
|
+
|
|
7
14
|
## [0.5.48] — 2026-04-07
|
|
8
15
|
|
|
9
16
|
### Added
|
|
@@ -510,6 +510,71 @@
|
|
|
510
510
|
});
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
+
function stripMarkdownHtmlCommentsInSegment(markdown) {
|
|
514
|
+
const source = String(markdown || "");
|
|
515
|
+
let out = "";
|
|
516
|
+
let index = 0;
|
|
517
|
+
let codeSpanFenceLength = 0;
|
|
518
|
+
let inHtmlComment = false;
|
|
519
|
+
|
|
520
|
+
while (index < source.length) {
|
|
521
|
+
if (inHtmlComment) {
|
|
522
|
+
if (source.startsWith("-->", index)) {
|
|
523
|
+
inHtmlComment = false;
|
|
524
|
+
index += 3;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const ch = source[index];
|
|
528
|
+
if (ch === "\n" || ch === "\r") out += ch;
|
|
529
|
+
index += 1;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (codeSpanFenceLength > 0) {
|
|
534
|
+
const fence = "`".repeat(codeSpanFenceLength);
|
|
535
|
+
if (source.startsWith(fence, index)) {
|
|
536
|
+
out += fence;
|
|
537
|
+
index += codeSpanFenceLength;
|
|
538
|
+
codeSpanFenceLength = 0;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
const ch = source[index];
|
|
542
|
+
out += ch;
|
|
543
|
+
index += 1;
|
|
544
|
+
if (ch === "\n" || ch === "\r") {
|
|
545
|
+
codeSpanFenceLength = 0;
|
|
546
|
+
}
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const backtickMatch = source.slice(index).match(/^`+/);
|
|
551
|
+
if (backtickMatch) {
|
|
552
|
+
const fence = backtickMatch[0] || "`";
|
|
553
|
+
codeSpanFenceLength = fence.length;
|
|
554
|
+
out += fence;
|
|
555
|
+
index += fence.length;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (source.startsWith("<!--", index)) {
|
|
560
|
+
inHtmlComment = true;
|
|
561
|
+
index += 4;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
out += source[index];
|
|
566
|
+
index += 1;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return out;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function stripMarkdownHtmlComments(text) {
|
|
573
|
+
return transformMarkdownOutsideFences(text, function(segment) {
|
|
574
|
+
return stripMarkdownHtmlCommentsInSegment(segment);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
513
578
|
function prepareMarkdownForPandocPreview(markdown, placeholderPrefix) {
|
|
514
579
|
const placeholders = [];
|
|
515
580
|
const prefix = typeof placeholderPrefix === "string" && placeholderPrefix
|
|
@@ -536,6 +601,7 @@
|
|
|
536
601
|
renderPreviewAnnotationHtml: renderPreviewAnnotationHtml,
|
|
537
602
|
replaceInlineAnnotationMarkers: replaceInlineAnnotationMarkers,
|
|
538
603
|
stripAnnotationMarkers: stripAnnotationMarkers,
|
|
604
|
+
stripMarkdownHtmlComments: stripMarkdownHtmlComments,
|
|
539
605
|
transformMarkdownOutsideFences: transformMarkdownOutsideFences,
|
|
540
606
|
});
|
|
541
607
|
|
package/client/studio-client.js
CHANGED
|
@@ -1461,6 +1461,13 @@
|
|
|
1461
1461
|
return annotationHelpers.stripAnnotationMarkers(text);
|
|
1462
1462
|
}
|
|
1463
1463
|
|
|
1464
|
+
function stripMarkdownHtmlComments(text) {
|
|
1465
|
+
if (annotationHelpers && typeof annotationHelpers.stripMarkdownHtmlComments === "function") {
|
|
1466
|
+
return annotationHelpers.stripMarkdownHtmlComments(text);
|
|
1467
|
+
}
|
|
1468
|
+
return String(text || "");
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1464
1471
|
function prepareEditorTextForSend(text) {
|
|
1465
1472
|
const raw = String(text || "");
|
|
1466
1473
|
return annotationsEnabled ? raw : stripAnnotationMarkers(raw);
|
|
@@ -1524,15 +1531,17 @@
|
|
|
1524
1531
|
syncBadgeEl.classList.remove("sync");
|
|
1525
1532
|
}
|
|
1526
1533
|
|
|
1527
|
-
function buildPlainMarkdownHtml(markdown) {
|
|
1528
|
-
|
|
1534
|
+
function buildPlainMarkdownHtml(markdown, options) {
|
|
1535
|
+
const shouldStripHtmlComments = Boolean(options && options.stripMarkdownHtmlComments);
|
|
1536
|
+
const source = shouldStripHtmlComments ? stripMarkdownHtmlComments(markdown) : String(markdown || "");
|
|
1537
|
+
return "<pre class='plain-markdown'>" + escapeHtml(source) + "</pre>";
|
|
1529
1538
|
}
|
|
1530
1539
|
|
|
1531
|
-
function buildPreviewErrorHtml(message, markdown) {
|
|
1532
|
-
return "<div class='preview-error'>" + escapeHtml(String(message || "Preview rendering failed.")) + "</div>" + buildPlainMarkdownHtml(markdown);
|
|
1540
|
+
function buildPreviewErrorHtml(message, markdown, options) {
|
|
1541
|
+
return "<div class='preview-error'>" + escapeHtml(String(message || "Preview rendering failed.")) + "</div>" + buildPlainMarkdownHtml(markdown, options);
|
|
1533
1542
|
}
|
|
1534
1543
|
|
|
1535
|
-
function sanitizeRenderedHtml(html, markdown) {
|
|
1544
|
+
function sanitizeRenderedHtml(html, markdown, options) {
|
|
1536
1545
|
const rawHtml = typeof html === "string" ? html : "";
|
|
1537
1546
|
const mathAnnotationPreserved = rawHtml.replace(/<math\b([^>]*)>([\s\S]*?)<\/math>/gi, (match, attrs, inner) => {
|
|
1538
1547
|
const texAnnotationMatch = String(inner || "").match(/<annotation\b[^>]*encoding="application\/x-tex"[^>]*>([\s\S]*?)<\/annotation>/i);
|
|
@@ -1556,7 +1565,7 @@
|
|
|
1556
1565
|
ADD_DATA_URI_TAGS: ["embed"],
|
|
1557
1566
|
});
|
|
1558
1567
|
}
|
|
1559
|
-
return buildPreviewErrorHtml("Preview sanitizer unavailable. Showing plain markdown.", markdown);
|
|
1568
|
+
return buildPreviewErrorHtml("Preview sanitizer unavailable. Showing plain markdown.", markdown, options);
|
|
1560
1569
|
}
|
|
1561
1570
|
|
|
1562
1571
|
function isPdfPreviewSource(src) {
|
|
@@ -2536,6 +2545,10 @@
|
|
|
2536
2545
|
const previewPrepared = annotationsEnabled
|
|
2537
2546
|
? prepareMarkdownForPandocPreview(markdown)
|
|
2538
2547
|
: { markdown: stripAnnotationMarkers(String(markdown || "")), placeholders: [] };
|
|
2548
|
+
const previewingEditorText = pane === "source" || rightView === "editor-preview";
|
|
2549
|
+
const previewFallbackOptions = {
|
|
2550
|
+
stripMarkdownHtmlComments: !previewingEditorText || editorLanguage !== "latex",
|
|
2551
|
+
};
|
|
2539
2552
|
|
|
2540
2553
|
try {
|
|
2541
2554
|
const renderedHtml = await renderMarkdownWithPandoc(previewPrepared.markdown, {
|
|
@@ -2550,7 +2563,7 @@
|
|
|
2550
2563
|
|
|
2551
2564
|
clearPreviewJumpHighlight(targetEl);
|
|
2552
2565
|
finishPreviewRender(targetEl);
|
|
2553
|
-
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown);
|
|
2566
|
+
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown, previewFallbackOptions);
|
|
2554
2567
|
applyPreviewAnnotationPlaceholdersToElement(targetEl, previewPrepared.placeholders);
|
|
2555
2568
|
await renderAnnotationMathInElement(targetEl);
|
|
2556
2569
|
decoratePdfEmbeds(targetEl);
|
|
@@ -2594,7 +2607,7 @@
|
|
|
2594
2607
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
2595
2608
|
clearPreviewJumpHighlight(targetEl);
|
|
2596
2609
|
finishPreviewRender(targetEl);
|
|
2597
|
-
targetEl.innerHTML = buildPreviewErrorHtml("Preview renderer unavailable (" + detail + "). Showing plain markdown.", markdown);
|
|
2610
|
+
targetEl.innerHTML = buildPreviewErrorHtml("Preview renderer unavailable (" + detail + "). Showing plain markdown.", markdown, previewFallbackOptions);
|
|
2598
2611
|
if (pane === "response") {
|
|
2599
2612
|
applyPendingResponseScrollReset();
|
|
2600
2613
|
scheduleResponsePaneRepaintNudge();
|
|
@@ -4444,6 +4457,8 @@
|
|
|
4444
4457
|
if (kind === "heading") return "heading";
|
|
4445
4458
|
if (kind === "blockquote") return "quote block";
|
|
4446
4459
|
if (kind === "list") return "list";
|
|
4460
|
+
if (kind === "math") return "equation";
|
|
4461
|
+
if (kind === "page-break") return "page break";
|
|
4447
4462
|
if (kind === "code") return "code block";
|
|
4448
4463
|
if (kind === "table") return "table";
|
|
4449
4464
|
if (kind === "code-line") return "code line";
|
|
@@ -4457,13 +4472,304 @@
|
|
|
4457
4472
|
|| kind === "heading"
|
|
4458
4473
|
|| kind === "blockquote"
|
|
4459
4474
|
|| kind === "list"
|
|
4475
|
+
|| kind === "math"
|
|
4460
4476
|
|| kind === "code-line"
|
|
4461
4477
|
|| kind === "diff-line"
|
|
4462
4478
|
|| kind === "text-line";
|
|
4463
4479
|
}
|
|
4464
4480
|
|
|
4481
|
+
const DISPLAY_MATH_ENV_NAMES = new Set([
|
|
4482
|
+
"displaymath",
|
|
4483
|
+
"equation",
|
|
4484
|
+
"equation*",
|
|
4485
|
+
"align",
|
|
4486
|
+
"align*",
|
|
4487
|
+
"aligned",
|
|
4488
|
+
"gather",
|
|
4489
|
+
"gather*",
|
|
4490
|
+
"multline",
|
|
4491
|
+
"multline*",
|
|
4492
|
+
"eqnarray",
|
|
4493
|
+
"eqnarray*",
|
|
4494
|
+
"split",
|
|
4495
|
+
]);
|
|
4496
|
+
|
|
4497
|
+
function isEscapedAt(text, index) {
|
|
4498
|
+
let slashCount = 0;
|
|
4499
|
+
for (let i = index - 1; i >= 0 && text[i] === "\\"; i -= 1) {
|
|
4500
|
+
slashCount += 1;
|
|
4501
|
+
}
|
|
4502
|
+
return (slashCount % 2) === 1;
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
function readBalancedLatexGroup(source, startIndex, openChar, closeChar) {
|
|
4506
|
+
if (!source || source[startIndex] !== openChar) return null;
|
|
4507
|
+
let depth = 0;
|
|
4508
|
+
for (let index = startIndex; index < source.length; index += 1) {
|
|
4509
|
+
const ch = source[index];
|
|
4510
|
+
if (ch === "\\") {
|
|
4511
|
+
index += 1;
|
|
4512
|
+
continue;
|
|
4513
|
+
}
|
|
4514
|
+
if (ch === openChar) {
|
|
4515
|
+
depth += 1;
|
|
4516
|
+
continue;
|
|
4517
|
+
}
|
|
4518
|
+
if (ch === closeChar) {
|
|
4519
|
+
depth -= 1;
|
|
4520
|
+
if (depth === 0) {
|
|
4521
|
+
return {
|
|
4522
|
+
start: startIndex,
|
|
4523
|
+
contentStart: startIndex + 1,
|
|
4524
|
+
contentEnd: index,
|
|
4525
|
+
end: index + 1,
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
return null;
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
const DROPPED_MARKDOWN_RAW_TEX_GROUP_COMMANDS = new Set([
|
|
4534
|
+
"textbf",
|
|
4535
|
+
"textit",
|
|
4536
|
+
"emph",
|
|
4537
|
+
"underline",
|
|
4538
|
+
"texttt",
|
|
4539
|
+
"textrm",
|
|
4540
|
+
"textsf",
|
|
4541
|
+
"textsc",
|
|
4542
|
+
"mbox",
|
|
4543
|
+
"makebox",
|
|
4544
|
+
"framebox",
|
|
4545
|
+
"fbox",
|
|
4546
|
+
"url",
|
|
4547
|
+
"path",
|
|
4548
|
+
"nolinkurl",
|
|
4549
|
+
]);
|
|
4550
|
+
const DROPPED_MARKDOWN_RAW_TEX_DOUBLE_GROUP_COMMANDS = new Set([
|
|
4551
|
+
"href",
|
|
4552
|
+
"hyperref",
|
|
4553
|
+
]);
|
|
4554
|
+
const DROPPED_MARKDOWN_RAW_TEX_STANDALONE_COMMANDS = new Set([
|
|
4555
|
+
"latex",
|
|
4556
|
+
"tex",
|
|
4557
|
+
"newpage",
|
|
4558
|
+
"pagebreak",
|
|
4559
|
+
"clearpage",
|
|
4560
|
+
]);
|
|
4561
|
+
|
|
4562
|
+
function skipLatexWhitespace(source, startIndex) {
|
|
4563
|
+
let index = startIndex;
|
|
4564
|
+
while (index < source.length && /\s/.test(source[index])) index += 1;
|
|
4565
|
+
return index;
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
function parseLatexCommandAt(source, startIndex) {
|
|
4569
|
+
if (!source || source[startIndex] !== "\\") return null;
|
|
4570
|
+
let index = startIndex + 1;
|
|
4571
|
+
if (index >= source.length) {
|
|
4572
|
+
return { name: "", end: index };
|
|
4573
|
+
}
|
|
4574
|
+
if (/[A-Za-z@]/.test(source[index])) {
|
|
4575
|
+
const nameStart = index;
|
|
4576
|
+
while (index < source.length && /[A-Za-z@]/.test(source[index])) index += 1;
|
|
4577
|
+
if (source[index] === "*") index += 1;
|
|
4578
|
+
return {
|
|
4579
|
+
name: source.slice(nameStart, index),
|
|
4580
|
+
end: index,
|
|
4581
|
+
};
|
|
4582
|
+
}
|
|
4583
|
+
return {
|
|
4584
|
+
name: source[index],
|
|
4585
|
+
end: index + 1,
|
|
4586
|
+
};
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
function collectDisplayMathRanges(text) {
|
|
4590
|
+
const source = String(text || "");
|
|
4591
|
+
const ranges = [];
|
|
4592
|
+
let index = 0;
|
|
4593
|
+
|
|
4594
|
+
while (index < source.length) {
|
|
4595
|
+
if (source[index] === "%" && !isEscapedAt(source, index)) {
|
|
4596
|
+
while (index < source.length && source[index] !== "\n") index += 1;
|
|
4597
|
+
continue;
|
|
4598
|
+
}
|
|
4599
|
+
if (source.startsWith("$$", index)) {
|
|
4600
|
+
const close = source.indexOf("$$", index + 2);
|
|
4601
|
+
if (close >= 0) {
|
|
4602
|
+
ranges.push({
|
|
4603
|
+
start: index,
|
|
4604
|
+
end: close + 2,
|
|
4605
|
+
bodyStart: index + 2,
|
|
4606
|
+
bodyEnd: close,
|
|
4607
|
+
bodyText: source.slice(index + 2, close),
|
|
4608
|
+
});
|
|
4609
|
+
index = close + 2;
|
|
4610
|
+
continue;
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
if (source.startsWith("\\[", index)) {
|
|
4614
|
+
const close = source.indexOf("\\]", index + 2);
|
|
4615
|
+
if (close >= 0) {
|
|
4616
|
+
ranges.push({
|
|
4617
|
+
start: index,
|
|
4618
|
+
end: close + 2,
|
|
4619
|
+
bodyStart: index + 2,
|
|
4620
|
+
bodyEnd: close,
|
|
4621
|
+
bodyText: source.slice(index + 2, close),
|
|
4622
|
+
});
|
|
4623
|
+
index = close + 2;
|
|
4624
|
+
continue;
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
if (source.startsWith("\\begin{", index)) {
|
|
4628
|
+
const envGroup = readBalancedLatexGroup(source, index + 6, "{", "}");
|
|
4629
|
+
const envName = envGroup ? source.slice(envGroup.contentStart, envGroup.contentEnd).trim() : "";
|
|
4630
|
+
if (envName && DISPLAY_MATH_ENV_NAMES.has(envName)) {
|
|
4631
|
+
const closeToken = "\\end{" + envName + "}";
|
|
4632
|
+
const close = source.indexOf(closeToken, envGroup.end);
|
|
4633
|
+
if (close >= 0) {
|
|
4634
|
+
ranges.push({
|
|
4635
|
+
start: index,
|
|
4636
|
+
end: close + closeToken.length,
|
|
4637
|
+
bodyStart: envGroup.end,
|
|
4638
|
+
bodyEnd: close,
|
|
4639
|
+
bodyText: source.slice(envGroup.end, close),
|
|
4640
|
+
});
|
|
4641
|
+
index = close + closeToken.length;
|
|
4642
|
+
continue;
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
index += 1;
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
return ranges;
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
function getStandaloneDisplayMathRange(text) {
|
|
4653
|
+
const source = String(text || "");
|
|
4654
|
+
const leadingMatch = source.match(/^\s*/);
|
|
4655
|
+
const trailingMatch = source.match(/\s*$/);
|
|
4656
|
+
const leadingLength = leadingMatch ? leadingMatch[0].length : 0;
|
|
4657
|
+
const trailingLength = trailingMatch ? trailingMatch[0].length : 0;
|
|
4658
|
+
const trimmedEnd = Math.max(leadingLength, source.length - trailingLength);
|
|
4659
|
+
const trimmed = source.slice(leadingLength, trimmedEnd);
|
|
4660
|
+
if (!trimmed) return null;
|
|
4661
|
+
const ranges = collectDisplayMathRanges(trimmed);
|
|
4662
|
+
if (ranges.length !== 1) return null;
|
|
4663
|
+
const range = ranges[0];
|
|
4664
|
+
if (!range || range.start !== 0 || range.end !== trimmed.length) return null;
|
|
4665
|
+
return {
|
|
4666
|
+
start: leadingLength + range.start,
|
|
4667
|
+
end: leadingLength + range.end,
|
|
4668
|
+
bodyStart: leadingLength + range.bodyStart,
|
|
4669
|
+
bodyEnd: leadingLength + range.bodyEnd,
|
|
4670
|
+
bodyText: String(range.bodyText || ""),
|
|
4671
|
+
};
|
|
4672
|
+
}
|
|
4673
|
+
function normalizePreviewComparableCharacter(character) {
|
|
4674
|
+
switch (String(character || "")) {
|
|
4675
|
+
case "\u2018":
|
|
4676
|
+
case "\u2019":
|
|
4677
|
+
case "\u201A":
|
|
4678
|
+
case "\u201B":
|
|
4679
|
+
return "'";
|
|
4680
|
+
case "\u201C":
|
|
4681
|
+
case "\u201D":
|
|
4682
|
+
case "\u201E":
|
|
4683
|
+
case "\u201F":
|
|
4684
|
+
return '"';
|
|
4685
|
+
case "\u2013":
|
|
4686
|
+
case "\u2014":
|
|
4687
|
+
case "\u2212":
|
|
4688
|
+
return "-";
|
|
4689
|
+
case "\u2026":
|
|
4690
|
+
return "…";
|
|
4691
|
+
default:
|
|
4692
|
+
return String(character || "");
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
|
|
4465
4696
|
function normalizeVisiblePreviewText(text) {
|
|
4466
|
-
|
|
4697
|
+
const source = String(text || "");
|
|
4698
|
+
let normalized = "";
|
|
4699
|
+
let pendingWhitespace = false;
|
|
4700
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
4701
|
+
let character = source[i] === "." && source.slice(i, i + 3) === "..."
|
|
4702
|
+
? "…"
|
|
4703
|
+
: normalizePreviewComparableCharacter(source[i]);
|
|
4704
|
+
if (character === "…" && source[i] === "." && source.slice(i, i + 3) === "...") {
|
|
4705
|
+
i += 2;
|
|
4706
|
+
}
|
|
4707
|
+
if (/\s/.test(character)) {
|
|
4708
|
+
if (normalized) {
|
|
4709
|
+
pendingWhitespace = true;
|
|
4710
|
+
}
|
|
4711
|
+
continue;
|
|
4712
|
+
}
|
|
4713
|
+
if (pendingWhitespace && normalized) {
|
|
4714
|
+
normalized += " ";
|
|
4715
|
+
pendingWhitespace = false;
|
|
4716
|
+
}
|
|
4717
|
+
normalized += character;
|
|
4718
|
+
}
|
|
4719
|
+
return normalized.trim();
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4722
|
+
function splitSourcePreviewCommentBlockByDisplayMath(sourceText, block) {
|
|
4723
|
+
if (!block || block.kind !== "paragraph") {
|
|
4724
|
+
return block ? [block] : [];
|
|
4725
|
+
}
|
|
4726
|
+
const source = String(sourceText || "");
|
|
4727
|
+
const blockStart = Math.max(0, Math.min(Number(block.start) || 0, source.length));
|
|
4728
|
+
const blockEnd = Math.max(blockStart, Math.min(Number(block.end) || blockStart, source.length));
|
|
4729
|
+
const blockText = source.slice(blockStart, blockEnd);
|
|
4730
|
+
const mathRanges = collectDisplayMathRanges(blockText);
|
|
4731
|
+
if (mathRanges.length === 0) {
|
|
4732
|
+
return [block];
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
const segments = [];
|
|
4736
|
+
function pushSegment(kind, relativeStart, relativeEnd) {
|
|
4737
|
+
const safeRelativeStart = Math.max(0, Math.min(relativeStart, blockText.length));
|
|
4738
|
+
const safeRelativeEnd = Math.max(safeRelativeStart, Math.min(relativeEnd, blockText.length));
|
|
4739
|
+
if (safeRelativeEnd <= safeRelativeStart) return;
|
|
4740
|
+
const absoluteStart = blockStart + safeRelativeStart;
|
|
4741
|
+
const absoluteEnd = blockStart + safeRelativeEnd;
|
|
4742
|
+
const segmentText = source.slice(absoluteStart, absoluteEnd);
|
|
4743
|
+
if (kind === "paragraph" && !normalizeVisiblePreviewText(segmentText)) {
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4746
|
+
segments.push({
|
|
4747
|
+
kind,
|
|
4748
|
+
start: absoluteStart,
|
|
4749
|
+
end: absoluteEnd,
|
|
4750
|
+
lineStart: getLineNumberAtOffset(source, absoluteStart),
|
|
4751
|
+
lineEnd: getLineNumberAtOffset(source, Math.max(absoluteStart, absoluteEnd - 1)),
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
4754
|
+
|
|
4755
|
+
let cursor = 0;
|
|
4756
|
+
mathRanges.forEach((mathRange) => {
|
|
4757
|
+
if (!mathRange) return;
|
|
4758
|
+
pushSegment("paragraph", cursor, mathRange.start);
|
|
4759
|
+
pushSegment("math", mathRange.start, mathRange.end);
|
|
4760
|
+
cursor = mathRange.end;
|
|
4761
|
+
});
|
|
4762
|
+
pushSegment("paragraph", cursor, blockText.length);
|
|
4763
|
+
|
|
4764
|
+
return segments.length > 0 ? segments : [block];
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
function expandSourcePreviewCommentBlocksByDisplayMath(sourceText, blocks) {
|
|
4768
|
+
const expanded = [];
|
|
4769
|
+
(Array.isArray(blocks) ? blocks : []).forEach((block) => {
|
|
4770
|
+
expanded.push(...splitSourcePreviewCommentBlockByDisplayMath(sourceText, block));
|
|
4771
|
+
});
|
|
4772
|
+
return expanded;
|
|
4467
4773
|
}
|
|
4468
4774
|
|
|
4469
4775
|
function appendMappedPreviewSlice(chars, rawOffsets, lineText, lineBaseOffset, start, end) {
|
|
@@ -4516,6 +4822,14 @@
|
|
|
4516
4822
|
}
|
|
4517
4823
|
}
|
|
4518
4824
|
|
|
4825
|
+
if (kind === "math") {
|
|
4826
|
+
const mathRange = getStandaloneDisplayMathRange(source);
|
|
4827
|
+
if (mathRange) {
|
|
4828
|
+
appendMappedPreviewSlice(chars, rawOffsets, source, 0, mathRange.bodyStart, mathRange.bodyEnd);
|
|
4829
|
+
return { text: chars.join(""), rawOffsets };
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4519
4833
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
4520
4834
|
const line = lines[lineIndex] || "";
|
|
4521
4835
|
if (kind === "blockquote") {
|
|
@@ -4543,6 +4857,26 @@
|
|
|
4543
4857
|
return { text: chars.join(""), rawOffsets };
|
|
4544
4858
|
}
|
|
4545
4859
|
|
|
4860
|
+
function findClosingUnescapedSequence(source, startIndex, sequence) {
|
|
4861
|
+
const text = String(source || "");
|
|
4862
|
+
const needle = String(sequence || "");
|
|
4863
|
+
if (!text || !needle) return -1;
|
|
4864
|
+
let searchIndex = Math.max(0, Number(startIndex) || 0);
|
|
4865
|
+
while (searchIndex <= text.length) {
|
|
4866
|
+
const matchIndex = text.indexOf(needle, searchIndex);
|
|
4867
|
+
if (matchIndex < 0) return -1;
|
|
4868
|
+
let backslashCount = 0;
|
|
4869
|
+
for (let i = matchIndex - 1; i >= 0 && text[i] === "\\"; i -= 1) {
|
|
4870
|
+
backslashCount += 1;
|
|
4871
|
+
}
|
|
4872
|
+
if (backslashCount % 2 === 0) {
|
|
4873
|
+
return matchIndex;
|
|
4874
|
+
}
|
|
4875
|
+
searchIndex = matchIndex + needle.length;
|
|
4876
|
+
}
|
|
4877
|
+
return -1;
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4546
4880
|
function buildPreviewInlineDisplayMap(text, rawOffsets) {
|
|
4547
4881
|
const source = String(text || "");
|
|
4548
4882
|
const rawMap = Array.isArray(rawOffsets) ? rawOffsets : [];
|
|
@@ -4556,6 +4890,12 @@
|
|
|
4556
4890
|
charEnds.push(rawEnd);
|
|
4557
4891
|
}
|
|
4558
4892
|
|
|
4893
|
+
function appendRawRange(startIndex, endIndex) {
|
|
4894
|
+
for (let i = startIndex; i < endIndex; i += 1) {
|
|
4895
|
+
appendChar(source[i], rawMap[i], rawMap[i] + 1);
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4559
4899
|
function appendNestedRange(startIndex, endIndex) {
|
|
4560
4900
|
const nested = buildPreviewInlineDisplayMap(
|
|
4561
4901
|
source.slice(startIndex, endIndex),
|
|
@@ -4584,15 +4924,86 @@
|
|
|
4584
4924
|
const fence = "`".repeat(tickCount);
|
|
4585
4925
|
const closeIndex = source.indexOf(fence, index + tickCount);
|
|
4586
4926
|
if (closeIndex >= 0) {
|
|
4587
|
-
|
|
4588
|
-
appendChar(source[i], rawMap[i], rawMap[i] + 1);
|
|
4589
|
-
}
|
|
4927
|
+
appendRawRange(index + tickCount, closeIndex);
|
|
4590
4928
|
index = closeIndex + tickCount;
|
|
4591
4929
|
continue;
|
|
4592
4930
|
}
|
|
4593
4931
|
}
|
|
4594
4932
|
|
|
4933
|
+
if (remaining.startsWith("\\(")) {
|
|
4934
|
+
const closeIndex = source.indexOf("\\)", index + 2);
|
|
4935
|
+
if (closeIndex >= 0) {
|
|
4936
|
+
appendRawRange(index + 2, closeIndex);
|
|
4937
|
+
index = closeIndex + 2;
|
|
4938
|
+
continue;
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
|
|
4942
|
+
if (remaining.startsWith("\\[")) {
|
|
4943
|
+
const closeIndex = source.indexOf("\\]", index + 2);
|
|
4944
|
+
if (closeIndex >= 0) {
|
|
4945
|
+
appendRawRange(index + 2, closeIndex);
|
|
4946
|
+
index = closeIndex + 2;
|
|
4947
|
+
continue;
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4951
|
+
if (remaining.startsWith("$$")) {
|
|
4952
|
+
const closeIndex = findClosingUnescapedSequence(source, index + 2, "$$");
|
|
4953
|
+
if (closeIndex >= 0) {
|
|
4954
|
+
appendRawRange(index + 2, closeIndex);
|
|
4955
|
+
index = closeIndex + 2;
|
|
4956
|
+
continue;
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
if (source[index] === "$") {
|
|
4961
|
+
const closeIndex = findClosingUnescapedSequence(source, index + 1, "$");
|
|
4962
|
+
if (closeIndex >= 0) {
|
|
4963
|
+
appendRawRange(index + 1, closeIndex);
|
|
4964
|
+
index = closeIndex + 1;
|
|
4965
|
+
continue;
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
|
|
4595
4969
|
if (source[index] === "\\" && index + 1 < source.length) {
|
|
4970
|
+
const latexCommand = parseLatexCommandAt(source, index);
|
|
4971
|
+
const normalizedCommandName = latexCommand && latexCommand.name
|
|
4972
|
+
? String(latexCommand.name || "").replace(/\*$/, "").toLowerCase()
|
|
4973
|
+
: "";
|
|
4974
|
+
const isDroppedLatexCommand = Boolean(
|
|
4975
|
+
normalizedCommandName
|
|
4976
|
+
&& (
|
|
4977
|
+
DROPPED_MARKDOWN_RAW_TEX_GROUP_COMMANDS.has(normalizedCommandName)
|
|
4978
|
+
|| DROPPED_MARKDOWN_RAW_TEX_DOUBLE_GROUP_COMMANDS.has(normalizedCommandName)
|
|
4979
|
+
|| DROPPED_MARKDOWN_RAW_TEX_STANDALONE_COMMANDS.has(normalizedCommandName)
|
|
4980
|
+
)
|
|
4981
|
+
);
|
|
4982
|
+
if (latexCommand && isDroppedLatexCommand) {
|
|
4983
|
+
let nextIndex = skipLatexWhitespace(source, latexCommand.end);
|
|
4984
|
+
if (source[nextIndex] === "[") {
|
|
4985
|
+
const optionalGroup = readBalancedLatexGroup(source, nextIndex, "[", "]");
|
|
4986
|
+
if (optionalGroup) {
|
|
4987
|
+
nextIndex = skipLatexWhitespace(source, optionalGroup.end);
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
if (DROPPED_MARKDOWN_RAW_TEX_GROUP_COMMANDS.has(normalizedCommandName) || DROPPED_MARKDOWN_RAW_TEX_DOUBLE_GROUP_COMMANDS.has(normalizedCommandName)) {
|
|
4991
|
+
if (source[nextIndex] === "{") {
|
|
4992
|
+
const firstGroup = readBalancedLatexGroup(source, nextIndex, "{", "}");
|
|
4993
|
+
if (firstGroup) {
|
|
4994
|
+
nextIndex = skipLatexWhitespace(source, firstGroup.end);
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
if (DROPPED_MARKDOWN_RAW_TEX_DOUBLE_GROUP_COMMANDS.has(normalizedCommandName) && source[nextIndex] === "{") {
|
|
4999
|
+
const secondGroup = readBalancedLatexGroup(source, nextIndex, "{", "}");
|
|
5000
|
+
if (secondGroup) {
|
|
5001
|
+
nextIndex = skipLatexWhitespace(source, secondGroup.end);
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
index = Math.max(index + 1, nextIndex);
|
|
5005
|
+
continue;
|
|
5006
|
+
}
|
|
4596
5007
|
appendChar(source[index + 1], rawMap[index], rawMap[index + 1] + 1);
|
|
4597
5008
|
index += 2;
|
|
4598
5009
|
continue;
|
|
@@ -4630,13 +5041,20 @@
|
|
|
4630
5041
|
let pendingWhitespaceEnd = null;
|
|
4631
5042
|
|
|
4632
5043
|
for (let i = 0; i < source.length; i += 1) {
|
|
4633
|
-
|
|
5044
|
+
let character = normalizePreviewComparableCharacter(source[i]);
|
|
5045
|
+
let startRef = charStarts[i];
|
|
5046
|
+
let endRef = charEnds[i];
|
|
5047
|
+
if (source[i] === "." && source.slice(i, i + 3) === "...") {
|
|
5048
|
+
character = "…";
|
|
5049
|
+
endRef = charEnds[Math.min(i + 2, charEnds.length - 1)];
|
|
5050
|
+
i += 2;
|
|
5051
|
+
}
|
|
4634
5052
|
if (/\s/.test(character)) {
|
|
4635
5053
|
if (outChars.length === 0) continue;
|
|
4636
5054
|
if (pendingWhitespaceStart == null) {
|
|
4637
|
-
pendingWhitespaceStart =
|
|
5055
|
+
pendingWhitespaceStart = startRef;
|
|
4638
5056
|
}
|
|
4639
|
-
pendingWhitespaceEnd =
|
|
5057
|
+
pendingWhitespaceEnd = endRef;
|
|
4640
5058
|
continue;
|
|
4641
5059
|
}
|
|
4642
5060
|
|
|
@@ -4649,8 +5067,8 @@
|
|
|
4649
5067
|
}
|
|
4650
5068
|
|
|
4651
5069
|
outChars.push(character);
|
|
4652
|
-
outStarts.push(
|
|
4653
|
-
outEnds.push(
|
|
5070
|
+
outStarts.push(startRef);
|
|
5071
|
+
outEnds.push(endRef);
|
|
4654
5072
|
}
|
|
4655
5073
|
|
|
4656
5074
|
return {
|
|
@@ -4682,6 +5100,68 @@
|
|
|
4682
5100
|
return buildNormalizedPreviewDisplayMap(chars.join(""), starts, ends);
|
|
4683
5101
|
}
|
|
4684
5102
|
|
|
5103
|
+
function getPreviewMathSearchText(element) {
|
|
5104
|
+
if (!element || !(element instanceof Element)) return null;
|
|
5105
|
+
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
5106
|
+
if (tag === "MATH") {
|
|
5107
|
+
const texSource = element.getAttribute("data-tex-source");
|
|
5108
|
+
if (texSource && texSource.trim()) {
|
|
5109
|
+
return texSource;
|
|
5110
|
+
}
|
|
5111
|
+
return typeof element.textContent === "string" ? element.textContent : "";
|
|
5112
|
+
}
|
|
5113
|
+
if (element.classList && element.classList.contains("math") && (element.classList.contains("inline") || element.classList.contains("display"))) {
|
|
5114
|
+
return extractMathFallbackTex(
|
|
5115
|
+
typeof element.textContent === "string" ? element.textContent : "",
|
|
5116
|
+
element.classList.contains("display"),
|
|
5117
|
+
);
|
|
5118
|
+
}
|
|
5119
|
+
return null;
|
|
5120
|
+
}
|
|
5121
|
+
|
|
5122
|
+
function buildNormalizedPreviewSearchText(rootNode) {
|
|
5123
|
+
if (!rootNode) return "";
|
|
5124
|
+
const parts = [];
|
|
5125
|
+
|
|
5126
|
+
function visit(node) {
|
|
5127
|
+
if (!node) return;
|
|
5128
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
5129
|
+
parts.push(typeof node.nodeValue === "string" ? node.nodeValue : "");
|
|
5130
|
+
return;
|
|
5131
|
+
}
|
|
5132
|
+
if (node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
|
5133
|
+
return;
|
|
5134
|
+
}
|
|
5135
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
5136
|
+
const element = node;
|
|
5137
|
+
const mathText = getPreviewMathSearchText(element);
|
|
5138
|
+
if (mathText != null) {
|
|
5139
|
+
parts.push(mathText);
|
|
5140
|
+
return;
|
|
5141
|
+
}
|
|
5142
|
+
if (element.tagName === "BR") {
|
|
5143
|
+
parts.push("\n");
|
|
5144
|
+
return;
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
Array.from(node.childNodes || []).forEach(visit);
|
|
5148
|
+
}
|
|
5149
|
+
|
|
5150
|
+
visit(rootNode);
|
|
5151
|
+
return normalizeVisiblePreviewText(parts.join(""));
|
|
5152
|
+
}
|
|
5153
|
+
|
|
5154
|
+
function buildNormalizedPreviewRangeText(range) {
|
|
5155
|
+
if (!range || typeof range.cloneContents !== "function") {
|
|
5156
|
+
return "";
|
|
5157
|
+
}
|
|
5158
|
+
try {
|
|
5159
|
+
return buildNormalizedPreviewSearchText(range.cloneContents());
|
|
5160
|
+
} catch {
|
|
5161
|
+
return normalizeVisiblePreviewText(range.toString());
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
|
|
4685
5165
|
function findPreferredNormalizedTextMatch(haystack, needle, preferredIndex) {
|
|
4686
5166
|
const source = String(haystack || "");
|
|
4687
5167
|
const query = String(needle || "");
|
|
@@ -4860,6 +5340,10 @@
|
|
|
4860
5340
|
return /^\s*<!--/.test(getLine(index));
|
|
4861
5341
|
}
|
|
4862
5342
|
|
|
5343
|
+
function isPageBreakLine(index) {
|
|
5344
|
+
return /^\\(?:newpage|pagebreak|clearpage)(?:\s*\[[^\]]*\])?\s*$/i.test(getLine(index));
|
|
5345
|
+
}
|
|
5346
|
+
|
|
4863
5347
|
function makeBlock(kind, startLineIndex, endLineIndex) {
|
|
4864
5348
|
const safeStartLine = Math.max(0, Math.min(startLineIndex, Math.max(0, lines.length - 1)));
|
|
4865
5349
|
const safeEndLine = Math.max(safeStartLine, Math.min(endLineIndex, Math.max(0, lines.length - 1)));
|
|
@@ -4906,6 +5390,12 @@
|
|
|
4906
5390
|
continue;
|
|
4907
5391
|
}
|
|
4908
5392
|
|
|
5393
|
+
if (isPageBreakLine(index)) {
|
|
5394
|
+
blocks.push(makeBlock("page-break", index, index));
|
|
5395
|
+
index += 1;
|
|
5396
|
+
continue;
|
|
5397
|
+
}
|
|
5398
|
+
|
|
4909
5399
|
const fenceMatch = lineStartsFence(index);
|
|
4910
5400
|
if (fenceMatch) {
|
|
4911
5401
|
const marker = fenceMatch[1] || "";
|
|
@@ -5004,11 +5494,83 @@
|
|
|
5004
5494
|
index = endParagraph + 1;
|
|
5005
5495
|
}
|
|
5006
5496
|
|
|
5007
|
-
return blocks;
|
|
5497
|
+
return expandSourcePreviewCommentBlocksByDisplayMath(source, blocks);
|
|
5498
|
+
}
|
|
5499
|
+
|
|
5500
|
+
function isPreviewDisplayMathElement(element) {
|
|
5501
|
+
return Boolean(element && element instanceof Element && element.matches && element.matches("math[display='block'], .studio-mathjax-fallback-display"));
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
function previewNodesHaveVisibleContent(nodes) {
|
|
5505
|
+
return (Array.isArray(nodes) ? nodes : []).some((node) => {
|
|
5506
|
+
if (!node) return false;
|
|
5507
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
5508
|
+
return Boolean(normalizeVisiblePreviewText(node.nodeValue || ""));
|
|
5509
|
+
}
|
|
5510
|
+
return node instanceof Element && Boolean(buildNormalizedPreviewSearchText(node));
|
|
5511
|
+
});
|
|
5512
|
+
}
|
|
5513
|
+
|
|
5514
|
+
function splitMixedPreviewParagraphsAroundDisplayMath(targetEl) {
|
|
5515
|
+
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
5516
|
+
Array.from(targetEl.querySelectorAll("p")).forEach((paragraphEl) => {
|
|
5517
|
+
if (!(paragraphEl instanceof Element) || !paragraphEl.parentNode) return;
|
|
5518
|
+
if (paragraphEl.closest && paragraphEl.closest(".preview-comment-block")) return;
|
|
5519
|
+
let ancestor = paragraphEl.parentElement;
|
|
5520
|
+
while (ancestor && ancestor !== targetEl) {
|
|
5521
|
+
if (getPreviewCommentTargetKind(ancestor)) return;
|
|
5522
|
+
ancestor = ancestor.parentElement;
|
|
5523
|
+
}
|
|
5524
|
+
const childNodes = Array.from(paragraphEl.childNodes || []);
|
|
5525
|
+
if (!childNodes.some((node) => isPreviewDisplayMathElement(node))) return;
|
|
5526
|
+
|
|
5527
|
+
const fragment = document.createDocumentFragment();
|
|
5528
|
+
let proseNodes = [];
|
|
5529
|
+
let segmentCount = 0;
|
|
5530
|
+
|
|
5531
|
+
function flushProse() {
|
|
5532
|
+
if (proseNodes.length === 0) return;
|
|
5533
|
+
if (!previewNodesHaveVisibleContent(proseNodes)) {
|
|
5534
|
+
proseNodes = [];
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5537
|
+
const proseEl = paragraphEl.cloneNode(false);
|
|
5538
|
+
if (proseEl instanceof Element) {
|
|
5539
|
+
proseEl.removeAttribute("id");
|
|
5540
|
+
}
|
|
5541
|
+
proseNodes.forEach((node) => {
|
|
5542
|
+
proseEl.appendChild(node);
|
|
5543
|
+
});
|
|
5544
|
+
fragment.appendChild(proseEl);
|
|
5545
|
+
proseNodes = [];
|
|
5546
|
+
segmentCount += 1;
|
|
5547
|
+
}
|
|
5548
|
+
|
|
5549
|
+
childNodes.forEach((node) => {
|
|
5550
|
+
if (isPreviewDisplayMathElement(node)) {
|
|
5551
|
+
flushProse();
|
|
5552
|
+
fragment.appendChild(node);
|
|
5553
|
+
segmentCount += 1;
|
|
5554
|
+
return;
|
|
5555
|
+
}
|
|
5556
|
+
proseNodes.push(node);
|
|
5557
|
+
});
|
|
5558
|
+
flushProse();
|
|
5559
|
+
|
|
5560
|
+
if (segmentCount > 0) {
|
|
5561
|
+
paragraphEl.replaceWith(fragment);
|
|
5562
|
+
}
|
|
5563
|
+
});
|
|
5008
5564
|
}
|
|
5009
5565
|
|
|
5010
5566
|
function getPreviewCommentTargetKind(element) {
|
|
5011
5567
|
if (!element || !(element instanceof Element)) return "";
|
|
5568
|
+
if (element.classList && element.classList.contains("studio-mathjax-fallback-display")) {
|
|
5569
|
+
return "math";
|
|
5570
|
+
}
|
|
5571
|
+
if (element.classList && element.classList.contains("studio-page-break")) {
|
|
5572
|
+
return "page-break";
|
|
5573
|
+
}
|
|
5012
5574
|
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
5013
5575
|
if (/^H[1-6]$/.test(tag)) return "heading";
|
|
5014
5576
|
if (tag === "P") return "paragraph";
|
|
@@ -5016,6 +5578,9 @@
|
|
|
5016
5578
|
if (tag === "UL" || tag === "OL") return "list";
|
|
5017
5579
|
if (tag === "TABLE") return "table";
|
|
5018
5580
|
if (tag === "PRE") return "code";
|
|
5581
|
+
if (tag === "MATH") {
|
|
5582
|
+
return String(element.getAttribute("display") || "").toLowerCase() === "block" ? "math" : "";
|
|
5583
|
+
}
|
|
5019
5584
|
if (element.classList) {
|
|
5020
5585
|
if (
|
|
5021
5586
|
element.classList.contains("sourceCode")
|
|
@@ -5042,7 +5607,7 @@
|
|
|
5042
5607
|
|
|
5043
5608
|
function collectPreviewCommentTargetElements(targetEl) {
|
|
5044
5609
|
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return [];
|
|
5045
|
-
const selector = "h1, h2, h3, h4, h5, h6, p, blockquote, ul, ol, table, div.sourceCode, pre, .callout-note, .callout-tip, .callout-warning, .callout-important, .callout-caution, .mermaid-container";
|
|
5610
|
+
const selector = "h1, h2, h3, h4, h5, h6, p, blockquote, ul, ol, table, div.sourceCode, pre, math[display='block'], .studio-mathjax-fallback-display, .studio-page-break, .callout-note, .callout-tip, .callout-warning, .callout-important, .callout-caution, .mermaid-container";
|
|
5046
5611
|
return Array.from(targetEl.querySelectorAll(selector)).filter((element) => {
|
|
5047
5612
|
if (!isPreviewCommentTargetElement(element)) return false;
|
|
5048
5613
|
let ancestor = element.parentElement;
|
|
@@ -5061,6 +5626,10 @@
|
|
|
5061
5626
|
function getNormalizedPreviewCommentSourceBlockText(sourceText, sourceBlock) {
|
|
5062
5627
|
if (!sourceBlock) return "";
|
|
5063
5628
|
const blockText = String(sourceText || "").slice(sourceBlock.start, sourceBlock.end);
|
|
5629
|
+
if (sourceBlock.kind === "page-break") {
|
|
5630
|
+
const match = blockText.trim().match(/^\\(newpage|pagebreak|clearpage)/i);
|
|
5631
|
+
return match ? String(match[1] || "").toLowerCase() : "page-break";
|
|
5632
|
+
}
|
|
5064
5633
|
if (supportsPreviewSelectionCommentsForBlockKind(sourceBlock.kind)) {
|
|
5065
5634
|
return normalizeVisiblePreviewText(buildPreviewSelectionDisplayMap(blockText, sourceBlock.kind).text);
|
|
5066
5635
|
}
|
|
@@ -5084,14 +5653,26 @@
|
|
|
5084
5653
|
function getNormalizedPreviewCommentTargetText(targetEntry) {
|
|
5085
5654
|
if (!targetEntry) return "";
|
|
5086
5655
|
if (typeof targetEntry.normalizedText === "string") return targetEntry.normalizedText;
|
|
5087
|
-
targetEntry.
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5656
|
+
if (targetEntry.kind === "page-break") {
|
|
5657
|
+
const element = targetEntry.element;
|
|
5658
|
+
targetEntry.normalizedText = String(element && element.getAttribute ? (element.getAttribute("data-page-break-kind") || "page-break") : "page-break").toLowerCase();
|
|
5659
|
+
return targetEntry.normalizedText;
|
|
5660
|
+
}
|
|
5661
|
+
targetEntry.normalizedText = buildNormalizedPreviewSearchText(targetEntry.element);
|
|
5092
5662
|
return targetEntry.normalizedText;
|
|
5093
5663
|
}
|
|
5094
5664
|
|
|
5665
|
+
function isHighConfidencePreviewTextContainmentMatch(leftText, rightText) {
|
|
5666
|
+
const left = String(leftText || "");
|
|
5667
|
+
const right = String(rightText || "");
|
|
5668
|
+
if (!left || !right || left === right) return false;
|
|
5669
|
+
const shorter = left.length <= right.length ? left : right;
|
|
5670
|
+
const longer = left.length <= right.length ? right : left;
|
|
5671
|
+
if (shorter.length < 12) return false;
|
|
5672
|
+
if (!/\s/.test(shorter)) return false;
|
|
5673
|
+
return longer.includes(shorter);
|
|
5674
|
+
}
|
|
5675
|
+
|
|
5095
5676
|
function findMatchingPreviewCommentTargetIndex(sourceText, sourceBlock, targetBlocks, startIndex) {
|
|
5096
5677
|
const desiredKind = sourceBlock ? sourceBlock.kind : "";
|
|
5097
5678
|
const desiredText = getNormalizedPreviewCommentSourceBlockText(sourceText, sourceBlock);
|
|
@@ -5107,7 +5688,7 @@
|
|
|
5107
5688
|
if (targetText === desiredText) {
|
|
5108
5689
|
return i;
|
|
5109
5690
|
}
|
|
5110
|
-
if (containsIndex < 0 && (targetText
|
|
5691
|
+
if (containsIndex < 0 && isHighConfidencePreviewTextContainmentMatch(targetText, desiredText)) {
|
|
5111
5692
|
containsIndex = i;
|
|
5112
5693
|
}
|
|
5113
5694
|
}
|
|
@@ -5167,6 +5748,7 @@
|
|
|
5167
5748
|
|
|
5168
5749
|
function decorateRenderedEditorPreviewComments(targetEl, sourceText) {
|
|
5169
5750
|
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
5751
|
+
splitMixedPreviewParagraphsAroundDisplayMath(targetEl);
|
|
5170
5752
|
const sourceBlocks = scanSourcePreviewCommentBlocks(sourceText);
|
|
5171
5753
|
const targetBlocks = collectPreviewCommentTargetElements(targetEl);
|
|
5172
5754
|
if (sourceBlocks.length === 0 || targetBlocks.length === 0) return;
|
|
@@ -5250,6 +5832,19 @@
|
|
|
5250
5832
|
const blockEnd = Math.max(blockStart, Math.min(Number(blockEl.dataset.reviewNoteEnd) || blockStart, source.length));
|
|
5251
5833
|
if (blockEnd <= blockStart) return null;
|
|
5252
5834
|
|
|
5835
|
+
if (kind === "math") {
|
|
5836
|
+
const selectedDisplayText = normalizeVisiblePreviewText(getPreviewMathSearchText(contentEl) || buildNormalizedPreviewSearchText(contentEl));
|
|
5837
|
+
if (!selectedDisplayText) return null;
|
|
5838
|
+
return {
|
|
5839
|
+
selectionStart: blockStart,
|
|
5840
|
+
selectionEnd: blockEnd,
|
|
5841
|
+
lineStart: getLineNumberAtOffset(source, blockStart),
|
|
5842
|
+
lineEnd: getLineNumberAtOffset(source, Math.max(blockStart, blockEnd - 1)),
|
|
5843
|
+
selectedText: source.slice(blockStart, blockEnd),
|
|
5844
|
+
selectedDisplayText,
|
|
5845
|
+
};
|
|
5846
|
+
}
|
|
5847
|
+
|
|
5253
5848
|
const sourceBlockText = source.slice(blockStart, blockEnd);
|
|
5254
5849
|
const displayMap = buildPreviewSelectionDisplayMap(sourceBlockText, kind);
|
|
5255
5850
|
if (!displayMap.text || !displayMap.charStarts.length || !displayMap.charEnds.length) return null;
|
|
@@ -5257,8 +5852,8 @@
|
|
|
5257
5852
|
const prefixRange = document.createRange();
|
|
5258
5853
|
prefixRange.selectNodeContents(contentEl);
|
|
5259
5854
|
prefixRange.setEnd(range.startContainer, range.startOffset);
|
|
5260
|
-
const prefixText =
|
|
5261
|
-
const selectedDisplayText =
|
|
5855
|
+
const prefixText = buildNormalizedPreviewRangeText(prefixRange);
|
|
5856
|
+
const selectedDisplayText = buildNormalizedPreviewRangeText(range);
|
|
5262
5857
|
if (!selectedDisplayText) return null;
|
|
5263
5858
|
|
|
5264
5859
|
const desiredStart = Math.max(0, Math.min(prefixText.length, displayMap.text.length));
|
|
@@ -5376,7 +5971,7 @@
|
|
|
5376
5971
|
let bestScore = Number.NEGATIVE_INFINITY;
|
|
5377
5972
|
Array.from(targetEl.querySelectorAll(".preview-comment-block")).forEach((blockEl) => {
|
|
5378
5973
|
const contentEl = blockEl.querySelector(".preview-comment-block-content") || blockEl;
|
|
5379
|
-
const blockText =
|
|
5974
|
+
const blockText = buildNormalizedPreviewSearchText(contentEl);
|
|
5380
5975
|
if (!blockText) return;
|
|
5381
5976
|
const matchIndex = blockText.indexOf(selectionText);
|
|
5382
5977
|
if (matchIndex < 0) return;
|
|
@@ -5400,6 +5995,13 @@
|
|
|
5400
5995
|
const blockEl = findPreviewCommentBlockForRange(targetEl, range) || findPreviewCommentBlockForNoteText(targetEl, note);
|
|
5401
5996
|
if (!blockEl) return false;
|
|
5402
5997
|
const contentEl = blockEl.querySelector(".preview-comment-block-content") || blockEl;
|
|
5998
|
+
if (String(blockEl.dataset && blockEl.dataset.previewCommentKind || "") === "math") {
|
|
5999
|
+
if (typeof contentEl.scrollIntoView === "function") {
|
|
6000
|
+
contentEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
6001
|
+
}
|
|
6002
|
+
setPreviewJumpHighlight(targetEl, contentEl, null);
|
|
6003
|
+
return true;
|
|
6004
|
+
}
|
|
5403
6005
|
const inlineHighlightEl = createPreviewJumpInlineHighlight(contentEl, blockEl, note, range);
|
|
5404
6006
|
if (typeof blockEl.scrollIntoView === "function") {
|
|
5405
6007
|
blockEl.scrollIntoView({ block: "center", inline: "nearest" });
|
package/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
replaceStudioInlineAnnotationMarkers,
|
|
20
20
|
transformStudioMarkdownOutsideFences,
|
|
21
21
|
} from "./shared/studio-annotation-scanner.js";
|
|
22
|
+
import { stripStudioMarkdownHtmlComments } from "./shared/studio-markdown-html-comments.js";
|
|
22
23
|
import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
|
|
23
24
|
|
|
24
25
|
type Lens = "writing" | "code";
|
|
@@ -2893,114 +2894,6 @@ function normalizeMathDelimiters(markdown: string): string {
|
|
|
2893
2894
|
return out.join("\n");
|
|
2894
2895
|
}
|
|
2895
2896
|
|
|
2896
|
-
function stripStudioMarkdownHtmlCommentsInSegment(markdown: string): string {
|
|
2897
|
-
const source = String(markdown ?? "");
|
|
2898
|
-
let out = "";
|
|
2899
|
-
let i = 0;
|
|
2900
|
-
let codeSpanFenceLength = 0;
|
|
2901
|
-
let inHtmlComment = false;
|
|
2902
|
-
|
|
2903
|
-
while (i < source.length) {
|
|
2904
|
-
if (inHtmlComment) {
|
|
2905
|
-
if (source.startsWith("-->", i)) {
|
|
2906
|
-
inHtmlComment = false;
|
|
2907
|
-
i += 3;
|
|
2908
|
-
continue;
|
|
2909
|
-
}
|
|
2910
|
-
const ch = source[i]!;
|
|
2911
|
-
if (ch === "\n" || ch === "\r") out += ch;
|
|
2912
|
-
i += 1;
|
|
2913
|
-
continue;
|
|
2914
|
-
}
|
|
2915
|
-
|
|
2916
|
-
if (codeSpanFenceLength > 0) {
|
|
2917
|
-
const fence = "`".repeat(codeSpanFenceLength);
|
|
2918
|
-
if (source.startsWith(fence, i)) {
|
|
2919
|
-
out += fence;
|
|
2920
|
-
i += codeSpanFenceLength;
|
|
2921
|
-
codeSpanFenceLength = 0;
|
|
2922
|
-
continue;
|
|
2923
|
-
}
|
|
2924
|
-
out += source[i]!;
|
|
2925
|
-
i += 1;
|
|
2926
|
-
continue;
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
const backtickMatch = source.slice(i).match(/^`+/);
|
|
2930
|
-
if (backtickMatch) {
|
|
2931
|
-
const fence = backtickMatch[0]!;
|
|
2932
|
-
codeSpanFenceLength = fence.length;
|
|
2933
|
-
out += fence;
|
|
2934
|
-
i += fence.length;
|
|
2935
|
-
continue;
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
|
-
if (source.startsWith("<!--", i)) {
|
|
2939
|
-
inHtmlComment = true;
|
|
2940
|
-
i += 4;
|
|
2941
|
-
continue;
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
out += source[i]!;
|
|
2945
|
-
i += 1;
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
return out;
|
|
2949
|
-
}
|
|
2950
|
-
|
|
2951
|
-
function stripStudioMarkdownHtmlComments(markdown: string): string {
|
|
2952
|
-
const lines = String(markdown ?? "").split("\n");
|
|
2953
|
-
const out: string[] = [];
|
|
2954
|
-
let plainBuffer: string[] = [];
|
|
2955
|
-
let inFence = false;
|
|
2956
|
-
let fenceChar: "`" | "~" | undefined;
|
|
2957
|
-
let fenceLength = 0;
|
|
2958
|
-
|
|
2959
|
-
const flushPlain = () => {
|
|
2960
|
-
if (plainBuffer.length === 0) return;
|
|
2961
|
-
out.push(stripStudioMarkdownHtmlCommentsInSegment(plainBuffer.join("\n")));
|
|
2962
|
-
plainBuffer = [];
|
|
2963
|
-
};
|
|
2964
|
-
|
|
2965
|
-
for (const line of lines) {
|
|
2966
|
-
const trimmed = line.trimStart();
|
|
2967
|
-
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
2968
|
-
|
|
2969
|
-
if (fenceMatch) {
|
|
2970
|
-
const marker = fenceMatch[1]!;
|
|
2971
|
-
const markerChar = marker[0] as "`" | "~";
|
|
2972
|
-
const markerLength = marker.length;
|
|
2973
|
-
|
|
2974
|
-
if (!inFence) {
|
|
2975
|
-
flushPlain();
|
|
2976
|
-
inFence = true;
|
|
2977
|
-
fenceChar = markerChar;
|
|
2978
|
-
fenceLength = markerLength;
|
|
2979
|
-
out.push(line);
|
|
2980
|
-
continue;
|
|
2981
|
-
}
|
|
2982
|
-
|
|
2983
|
-
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
2984
|
-
inFence = false;
|
|
2985
|
-
fenceChar = undefined;
|
|
2986
|
-
fenceLength = 0;
|
|
2987
|
-
}
|
|
2988
|
-
|
|
2989
|
-
out.push(line);
|
|
2990
|
-
continue;
|
|
2991
|
-
}
|
|
2992
|
-
|
|
2993
|
-
if (inFence) {
|
|
2994
|
-
out.push(line);
|
|
2995
|
-
} else {
|
|
2996
|
-
plainBuffer.push(line);
|
|
2997
|
-
}
|
|
2998
|
-
}
|
|
2999
|
-
|
|
3000
|
-
flushPlain();
|
|
3001
|
-
return out.join("\n");
|
|
3002
|
-
}
|
|
3003
|
-
|
|
3004
2897
|
const STUDIO_PREVIEW_PAGE_BREAK_SENTINEL_PREFIX = "PI_STUDIO_PAGE_BREAK__";
|
|
3005
2898
|
|
|
3006
2899
|
function replaceStudioPreviewPageBreakCommands(markdown: string): string {
|
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
|
+
}
|