pi-studio 0.5.48 → 0.5.50
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 +17 -0
- package/README.md +1 -1
- package/client/studio-annotation-helpers.js +66 -0
- package/client/studio-client.js +1477 -47
- package/client/studio.css +9 -5
- package/index.ts +1 -108
- package/package.json +1 -1
- package/shared/studio-markdown-html-comments.js +111 -0
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) {
|
|
@@ -1895,6 +1904,7 @@
|
|
|
1895
1904
|
|
|
1896
1905
|
fallbackTargets.forEach((entry) => {
|
|
1897
1906
|
entry.renderTarget.classList.add("studio-mathjax-fallback");
|
|
1907
|
+
entry.renderTarget.setAttribute("data-tex-source", entry.tex);
|
|
1898
1908
|
if (entry.displayMode) {
|
|
1899
1909
|
entry.renderTarget.classList.add("studio-mathjax-fallback-display");
|
|
1900
1910
|
entry.renderTarget.textContent = "\\[\n" + entry.tex + "\n\\]";
|
|
@@ -2536,6 +2546,10 @@
|
|
|
2536
2546
|
const previewPrepared = annotationsEnabled
|
|
2537
2547
|
? prepareMarkdownForPandocPreview(markdown)
|
|
2538
2548
|
: { markdown: stripAnnotationMarkers(String(markdown || "")), placeholders: [] };
|
|
2549
|
+
const previewingEditorText = pane === "source" || rightView === "editor-preview";
|
|
2550
|
+
const previewFallbackOptions = {
|
|
2551
|
+
stripMarkdownHtmlComments: !previewingEditorText || editorLanguage !== "latex",
|
|
2552
|
+
};
|
|
2539
2553
|
|
|
2540
2554
|
try {
|
|
2541
2555
|
const renderedHtml = await renderMarkdownWithPandoc(previewPrepared.markdown, {
|
|
@@ -2550,7 +2564,7 @@
|
|
|
2550
2564
|
|
|
2551
2565
|
clearPreviewJumpHighlight(targetEl);
|
|
2552
2566
|
finishPreviewRender(targetEl);
|
|
2553
|
-
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown);
|
|
2567
|
+
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown, previewFallbackOptions);
|
|
2554
2568
|
applyPreviewAnnotationPlaceholdersToElement(targetEl, previewPrepared.placeholders);
|
|
2555
2569
|
await renderAnnotationMathInElement(targetEl);
|
|
2556
2570
|
decoratePdfEmbeds(targetEl);
|
|
@@ -2594,7 +2608,7 @@
|
|
|
2594
2608
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
2595
2609
|
clearPreviewJumpHighlight(targetEl);
|
|
2596
2610
|
finishPreviewRender(targetEl);
|
|
2597
|
-
targetEl.innerHTML = buildPreviewErrorHtml("Preview renderer unavailable (" + detail + "). Showing plain markdown.", markdown);
|
|
2611
|
+
targetEl.innerHTML = buildPreviewErrorHtml("Preview renderer unavailable (" + detail + "). Showing plain markdown.", markdown, previewFallbackOptions);
|
|
2598
2612
|
if (pane === "response") {
|
|
2599
2613
|
applyPendingResponseScrollReset();
|
|
2600
2614
|
scheduleResponsePaneRepaintNudge();
|
|
@@ -3847,7 +3861,8 @@
|
|
|
3847
3861
|
+ ">"
|
|
3848
3862
|
+ "<div class='preview-comment-controls'>"
|
|
3849
3863
|
+ "<button type='button' class='preview-comment-summary' hidden></button>"
|
|
3850
|
-
+ "<button type='button' class='preview-comment-add'>Comment</button>"
|
|
3864
|
+
+ "<button type='button' class='preview-comment-add' data-preview-comment-action='comment'>Comment</button>"
|
|
3865
|
+
+ "<button type='button' class='preview-comment-jump' data-preview-comment-action='jump'>Jump</button>"
|
|
3851
3866
|
+ "</div>"
|
|
3852
3867
|
+ "<div class='preview-comment-block-content preview-code-line-content'>" + lineHtml + "</div>"
|
|
3853
3868
|
+ "</div>",
|
|
@@ -4434,16 +4449,19 @@
|
|
|
4434
4449
|
}
|
|
4435
4450
|
|
|
4436
4451
|
function supportsPreviewCommentsForCurrentEditor() {
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
return editorLanguage === "markdown" || supportsCodePreviewCommentsForCurrentEditor();
|
|
4452
|
+
return editorLanguage === "markdown"
|
|
4453
|
+
|| editorLanguage === "latex"
|
|
4454
|
+
|| supportsCodePreviewCommentsForCurrentEditor();
|
|
4441
4455
|
}
|
|
4442
4456
|
|
|
4443
4457
|
function getPreviewCommentBlockKindLabel(kind) {
|
|
4444
4458
|
if (kind === "heading") return "heading";
|
|
4445
4459
|
if (kind === "blockquote") return "quote block";
|
|
4446
4460
|
if (kind === "list") return "list";
|
|
4461
|
+
if (kind === "math") return "equation";
|
|
4462
|
+
if (kind === "figure") return "figure";
|
|
4463
|
+
if (kind === "algorithm") return "algorithm block";
|
|
4464
|
+
if (kind === "page-break") return "page break";
|
|
4447
4465
|
if (kind === "code") return "code block";
|
|
4448
4466
|
if (kind === "table") return "table";
|
|
4449
4467
|
if (kind === "code-line") return "code line";
|
|
@@ -4457,13 +4475,687 @@
|
|
|
4457
4475
|
|| kind === "heading"
|
|
4458
4476
|
|| kind === "blockquote"
|
|
4459
4477
|
|| kind === "list"
|
|
4478
|
+
|| kind === "math"
|
|
4460
4479
|
|| kind === "code-line"
|
|
4461
4480
|
|| kind === "diff-line"
|
|
4462
4481
|
|| kind === "text-line";
|
|
4463
4482
|
}
|
|
4464
4483
|
|
|
4484
|
+
const DISPLAY_MATH_ENV_NAMES = new Set([
|
|
4485
|
+
"displaymath",
|
|
4486
|
+
"equation",
|
|
4487
|
+
"equation*",
|
|
4488
|
+
"align",
|
|
4489
|
+
"align*",
|
|
4490
|
+
"aligned",
|
|
4491
|
+
"gather",
|
|
4492
|
+
"gather*",
|
|
4493
|
+
"multline",
|
|
4494
|
+
"multline*",
|
|
4495
|
+
"eqnarray",
|
|
4496
|
+
"eqnarray*",
|
|
4497
|
+
"split",
|
|
4498
|
+
]);
|
|
4499
|
+
|
|
4500
|
+
function isEscapedAt(text, index) {
|
|
4501
|
+
let slashCount = 0;
|
|
4502
|
+
for (let i = index - 1; i >= 0 && text[i] === "\\"; i -= 1) {
|
|
4503
|
+
slashCount += 1;
|
|
4504
|
+
}
|
|
4505
|
+
return (slashCount % 2) === 1;
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
function readBalancedLatexGroup(source, startIndex, openChar, closeChar) {
|
|
4509
|
+
if (!source || source[startIndex] !== openChar) return null;
|
|
4510
|
+
let depth = 0;
|
|
4511
|
+
for (let index = startIndex; index < source.length; index += 1) {
|
|
4512
|
+
const ch = source[index];
|
|
4513
|
+
if (ch === "\\") {
|
|
4514
|
+
index += 1;
|
|
4515
|
+
continue;
|
|
4516
|
+
}
|
|
4517
|
+
if (ch === openChar) {
|
|
4518
|
+
depth += 1;
|
|
4519
|
+
continue;
|
|
4520
|
+
}
|
|
4521
|
+
if (ch === closeChar) {
|
|
4522
|
+
depth -= 1;
|
|
4523
|
+
if (depth === 0) {
|
|
4524
|
+
return {
|
|
4525
|
+
start: startIndex,
|
|
4526
|
+
contentStart: startIndex + 1,
|
|
4527
|
+
contentEnd: index,
|
|
4528
|
+
end: index + 1,
|
|
4529
|
+
};
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
return null;
|
|
4534
|
+
}
|
|
4535
|
+
|
|
4536
|
+
const DROPPED_MARKDOWN_RAW_TEX_GROUP_COMMANDS = new Set([
|
|
4537
|
+
"textbf",
|
|
4538
|
+
"textit",
|
|
4539
|
+
"emph",
|
|
4540
|
+
"underline",
|
|
4541
|
+
"texttt",
|
|
4542
|
+
"textrm",
|
|
4543
|
+
"textsf",
|
|
4544
|
+
"textsc",
|
|
4545
|
+
"mbox",
|
|
4546
|
+
"makebox",
|
|
4547
|
+
"framebox",
|
|
4548
|
+
"fbox",
|
|
4549
|
+
"url",
|
|
4550
|
+
"path",
|
|
4551
|
+
"nolinkurl",
|
|
4552
|
+
]);
|
|
4553
|
+
const DROPPED_MARKDOWN_RAW_TEX_DOUBLE_GROUP_COMMANDS = new Set([
|
|
4554
|
+
"href",
|
|
4555
|
+
"hyperref",
|
|
4556
|
+
]);
|
|
4557
|
+
const DROPPED_MARKDOWN_RAW_TEX_STANDALONE_COMMANDS = new Set([
|
|
4558
|
+
"latex",
|
|
4559
|
+
"tex",
|
|
4560
|
+
"newpage",
|
|
4561
|
+
"pagebreak",
|
|
4562
|
+
"clearpage",
|
|
4563
|
+
]);
|
|
4564
|
+
|
|
4565
|
+
function skipLatexWhitespace(source, startIndex) {
|
|
4566
|
+
let index = startIndex;
|
|
4567
|
+
while (index < source.length && /\s/.test(source[index])) index += 1;
|
|
4568
|
+
return index;
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4571
|
+
function parseLatexCommandAt(source, startIndex) {
|
|
4572
|
+
if (!source || source[startIndex] !== "\\") return null;
|
|
4573
|
+
let index = startIndex + 1;
|
|
4574
|
+
if (index >= source.length) {
|
|
4575
|
+
return { name: "", end: index };
|
|
4576
|
+
}
|
|
4577
|
+
if (/[A-Za-z@]/.test(source[index])) {
|
|
4578
|
+
const nameStart = index;
|
|
4579
|
+
while (index < source.length && /[A-Za-z@]/.test(source[index])) index += 1;
|
|
4580
|
+
if (source[index] === "*") index += 1;
|
|
4581
|
+
return {
|
|
4582
|
+
name: source.slice(nameStart, index),
|
|
4583
|
+
end: index,
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
return {
|
|
4587
|
+
name: source[index],
|
|
4588
|
+
end: index + 1,
|
|
4589
|
+
};
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4592
|
+
function collectDisplayMathRanges(text) {
|
|
4593
|
+
const source = String(text || "");
|
|
4594
|
+
const ranges = [];
|
|
4595
|
+
let index = 0;
|
|
4596
|
+
|
|
4597
|
+
while (index < source.length) {
|
|
4598
|
+
if (source[index] === "%" && !isEscapedAt(source, index)) {
|
|
4599
|
+
while (index < source.length && source[index] !== "\n") index += 1;
|
|
4600
|
+
continue;
|
|
4601
|
+
}
|
|
4602
|
+
if (source.startsWith("$$", index)) {
|
|
4603
|
+
const close = source.indexOf("$$", index + 2);
|
|
4604
|
+
if (close >= 0) {
|
|
4605
|
+
ranges.push({
|
|
4606
|
+
start: index,
|
|
4607
|
+
end: close + 2,
|
|
4608
|
+
bodyStart: index + 2,
|
|
4609
|
+
bodyEnd: close,
|
|
4610
|
+
bodyText: source.slice(index + 2, close),
|
|
4611
|
+
});
|
|
4612
|
+
index = close + 2;
|
|
4613
|
+
continue;
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
if (source.startsWith("\\[", index)) {
|
|
4617
|
+
const close = source.indexOf("\\]", index + 2);
|
|
4618
|
+
if (close >= 0) {
|
|
4619
|
+
ranges.push({
|
|
4620
|
+
start: index,
|
|
4621
|
+
end: close + 2,
|
|
4622
|
+
bodyStart: index + 2,
|
|
4623
|
+
bodyEnd: close,
|
|
4624
|
+
bodyText: source.slice(index + 2, close),
|
|
4625
|
+
});
|
|
4626
|
+
index = close + 2;
|
|
4627
|
+
continue;
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
if (source.startsWith("\\begin{", index)) {
|
|
4631
|
+
const envGroup = readBalancedLatexGroup(source, index + 6, "{", "}");
|
|
4632
|
+
const envName = envGroup ? source.slice(envGroup.contentStart, envGroup.contentEnd).trim() : "";
|
|
4633
|
+
if (envName && DISPLAY_MATH_ENV_NAMES.has(envName)) {
|
|
4634
|
+
const closeToken = "\\end{" + envName + "}";
|
|
4635
|
+
const close = source.indexOf(closeToken, envGroup.end);
|
|
4636
|
+
if (close >= 0) {
|
|
4637
|
+
ranges.push({
|
|
4638
|
+
start: index,
|
|
4639
|
+
end: close + closeToken.length,
|
|
4640
|
+
bodyStart: envGroup.end,
|
|
4641
|
+
bodyEnd: close,
|
|
4642
|
+
bodyText: source.slice(envGroup.end, close),
|
|
4643
|
+
});
|
|
4644
|
+
index = close + closeToken.length;
|
|
4645
|
+
continue;
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
index += 1;
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
return ranges;
|
|
4653
|
+
}
|
|
4654
|
+
|
|
4655
|
+
function getStandaloneDisplayMathRange(text) {
|
|
4656
|
+
const source = String(text || "");
|
|
4657
|
+
const leadingMatch = source.match(/^\s*/);
|
|
4658
|
+
const trailingMatch = source.match(/\s*$/);
|
|
4659
|
+
const leadingLength = leadingMatch ? leadingMatch[0].length : 0;
|
|
4660
|
+
const trailingLength = trailingMatch ? trailingMatch[0].length : 0;
|
|
4661
|
+
const trimmedEnd = Math.max(leadingLength, source.length - trailingLength);
|
|
4662
|
+
const trimmed = source.slice(leadingLength, trimmedEnd);
|
|
4663
|
+
if (!trimmed) return null;
|
|
4664
|
+
const ranges = collectDisplayMathRanges(trimmed);
|
|
4665
|
+
if (ranges.length !== 1) return null;
|
|
4666
|
+
const range = ranges[0];
|
|
4667
|
+
if (!range || range.start !== 0 || range.end !== trimmed.length) return null;
|
|
4668
|
+
return {
|
|
4669
|
+
start: leadingLength + range.start,
|
|
4670
|
+
end: leadingLength + range.end,
|
|
4671
|
+
bodyStart: leadingLength + range.bodyStart,
|
|
4672
|
+
bodyEnd: leadingLength + range.bodyEnd,
|
|
4673
|
+
bodyText: String(range.bodyText || ""),
|
|
4674
|
+
};
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
const LATEX_PREVIEW_HEADING_COMMANDS = new Set([
|
|
4678
|
+
"part",
|
|
4679
|
+
"chapter",
|
|
4680
|
+
"section",
|
|
4681
|
+
"subsection",
|
|
4682
|
+
"subsubsection",
|
|
4683
|
+
"paragraph",
|
|
4684
|
+
"subparagraph",
|
|
4685
|
+
]);
|
|
4686
|
+
const LATEX_PREVIEW_VISIBLE_GROUP_COMMANDS = new Set([
|
|
4687
|
+
"part",
|
|
4688
|
+
"chapter",
|
|
4689
|
+
"section",
|
|
4690
|
+
"subsection",
|
|
4691
|
+
"subsubsection",
|
|
4692
|
+
"paragraph",
|
|
4693
|
+
"subparagraph",
|
|
4694
|
+
"title",
|
|
4695
|
+
"author",
|
|
4696
|
+
"caption",
|
|
4697
|
+
"text",
|
|
4698
|
+
"textbf",
|
|
4699
|
+
"textit",
|
|
4700
|
+
"emph",
|
|
4701
|
+
"underline",
|
|
4702
|
+
"texttt",
|
|
4703
|
+
"textrm",
|
|
4704
|
+
"textsf",
|
|
4705
|
+
"textsc",
|
|
4706
|
+
"mbox",
|
|
4707
|
+
"makebox",
|
|
4708
|
+
"framebox",
|
|
4709
|
+
"fbox",
|
|
4710
|
+
"url",
|
|
4711
|
+
"path",
|
|
4712
|
+
"nolinkurl",
|
|
4713
|
+
]);
|
|
4714
|
+
const LATEX_PREVIEW_SECOND_ARG_VISIBLE_COMMANDS = new Set([
|
|
4715
|
+
"href",
|
|
4716
|
+
"hyperref",
|
|
4717
|
+
]);
|
|
4718
|
+
const LATEX_PREVIEW_HIDDEN_COMMANDS = new Set([
|
|
4719
|
+
"label",
|
|
4720
|
+
"ref",
|
|
4721
|
+
"eqref",
|
|
4722
|
+
"autoref",
|
|
4723
|
+
"pageref",
|
|
4724
|
+
"cite",
|
|
4725
|
+
"citet",
|
|
4726
|
+
"citep",
|
|
4727
|
+
"citealt",
|
|
4728
|
+
"citeauthor",
|
|
4729
|
+
"nocite",
|
|
4730
|
+
"footnote",
|
|
4731
|
+
"marginpar",
|
|
4732
|
+
"index",
|
|
4733
|
+
"includegraphics",
|
|
4734
|
+
"addbibresource",
|
|
4735
|
+
]);
|
|
4736
|
+
const LATEX_PREVIEW_SKIPPED_ENV_NAMES = new Set([
|
|
4737
|
+
"document",
|
|
4738
|
+
"thebibliography",
|
|
4739
|
+
"itemize",
|
|
4740
|
+
"enumerate",
|
|
4741
|
+
"description",
|
|
4742
|
+
"figure",
|
|
4743
|
+
"figure*",
|
|
4744
|
+
"table",
|
|
4745
|
+
"table*",
|
|
4746
|
+
"tabular",
|
|
4747
|
+
"tabular*",
|
|
4748
|
+
"theorem",
|
|
4749
|
+
"lemma",
|
|
4750
|
+
"proposition",
|
|
4751
|
+
"corollary",
|
|
4752
|
+
"definition",
|
|
4753
|
+
"proof",
|
|
4754
|
+
"remark",
|
|
4755
|
+
"example",
|
|
4756
|
+
"verbatim",
|
|
4757
|
+
"lstlisting",
|
|
4758
|
+
"minted",
|
|
4759
|
+
"algorithm",
|
|
4760
|
+
"algorithm*",
|
|
4761
|
+
"algorithmic",
|
|
4762
|
+
]);
|
|
4763
|
+
const LATEX_PREVIEW_STRUCTURAL_ENV_KIND_BY_NAME = new Map([
|
|
4764
|
+
["figure", "figure"],
|
|
4765
|
+
["figure*", "figure"],
|
|
4766
|
+
["table", "table"],
|
|
4767
|
+
["table*", "table"],
|
|
4768
|
+
["algorithm", "algorithm"],
|
|
4769
|
+
["algorithm*", "algorithm"],
|
|
4770
|
+
]);
|
|
4771
|
+
|
|
4772
|
+
function stripLatexPreviewComments(text) {
|
|
4773
|
+
const source = String(text || "");
|
|
4774
|
+
let out = "";
|
|
4775
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
4776
|
+
const ch = source[index];
|
|
4777
|
+
if (ch === "%" && !isEscapedAt(source, index)) {
|
|
4778
|
+
while (index < source.length && source[index] !== "\n") index += 1;
|
|
4779
|
+
if (index < source.length && source[index] === "\n") {
|
|
4780
|
+
out += "\n";
|
|
4781
|
+
}
|
|
4782
|
+
continue;
|
|
4783
|
+
}
|
|
4784
|
+
out += ch;
|
|
4785
|
+
}
|
|
4786
|
+
return out;
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4789
|
+
function skipLatexPreviewCommentSpace(source, startIndex) {
|
|
4790
|
+
let index = Math.max(0, Number(startIndex) || 0);
|
|
4791
|
+
while (index < source.length) {
|
|
4792
|
+
const ch = source[index];
|
|
4793
|
+
if (/\s/.test(ch)) {
|
|
4794
|
+
index += 1;
|
|
4795
|
+
continue;
|
|
4796
|
+
}
|
|
4797
|
+
if (ch === "%" && !isEscapedAt(source, index)) {
|
|
4798
|
+
while (index < source.length && source[index] !== "\n") index += 1;
|
|
4799
|
+
continue;
|
|
4800
|
+
}
|
|
4801
|
+
break;
|
|
4802
|
+
}
|
|
4803
|
+
return index;
|
|
4804
|
+
}
|
|
4805
|
+
|
|
4806
|
+
function readLatexHeadingChunk(chunkText) {
|
|
4807
|
+
const source = String(chunkText || "");
|
|
4808
|
+
let index = skipLatexPreviewCommentSpace(source, 0);
|
|
4809
|
+
const command = parseLatexCommandAt(source, index);
|
|
4810
|
+
const commandName = command && command.name
|
|
4811
|
+
? String(command.name || "").replace(/\*$/, "").toLowerCase()
|
|
4812
|
+
: "";
|
|
4813
|
+
if (!command || !LATEX_PREVIEW_HEADING_COMMANDS.has(commandName)) return null;
|
|
4814
|
+
index = skipLatexPreviewCommentSpace(source, command.end);
|
|
4815
|
+
if (source[index] === "[") {
|
|
4816
|
+
const optionalGroup = readBalancedLatexGroup(source, index, "[", "]");
|
|
4817
|
+
if (optionalGroup) {
|
|
4818
|
+
index = skipLatexPreviewCommentSpace(source, optionalGroup.end);
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
if (source[index] !== "{") return null;
|
|
4822
|
+
const titleGroup = readBalancedLatexGroup(source, index, "{", "}");
|
|
4823
|
+
if (!titleGroup) return null;
|
|
4824
|
+
index = skipLatexPreviewCommentSpace(source, titleGroup.end);
|
|
4825
|
+
while (index < source.length) {
|
|
4826
|
+
const trailingCommand = parseLatexCommandAt(source, index);
|
|
4827
|
+
const trailingName = trailingCommand && trailingCommand.name
|
|
4828
|
+
? String(trailingCommand.name || "").replace(/\*$/, "").toLowerCase()
|
|
4829
|
+
: "";
|
|
4830
|
+
if (!trailingCommand || !LATEX_PREVIEW_HIDDEN_COMMANDS.has(trailingName)) {
|
|
4831
|
+
break;
|
|
4832
|
+
}
|
|
4833
|
+
let nextIndex = skipLatexPreviewCommentSpace(source, trailingCommand.end);
|
|
4834
|
+
if (source[nextIndex] === "[") {
|
|
4835
|
+
const optionalGroup = readBalancedLatexGroup(source, nextIndex, "[", "]");
|
|
4836
|
+
if (optionalGroup) {
|
|
4837
|
+
nextIndex = skipLatexPreviewCommentSpace(source, optionalGroup.end);
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
if (source[nextIndex] === "{") {
|
|
4841
|
+
const argGroup = readBalancedLatexGroup(source, nextIndex, "{", "}");
|
|
4842
|
+
if (argGroup) {
|
|
4843
|
+
nextIndex = skipLatexPreviewCommentSpace(source, argGroup.end);
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
index = nextIndex;
|
|
4847
|
+
}
|
|
4848
|
+
if (skipLatexPreviewCommentSpace(source, index) < source.length) return null;
|
|
4849
|
+
return {
|
|
4850
|
+
commandName,
|
|
4851
|
+
titleText: source.slice(titleGroup.contentStart, titleGroup.contentEnd),
|
|
4852
|
+
};
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4855
|
+
function extractLatexPreviewVisibleText(text) {
|
|
4856
|
+
const source = String(text || "");
|
|
4857
|
+
let out = "";
|
|
4858
|
+
let index = 0;
|
|
4859
|
+
|
|
4860
|
+
while (index < source.length) {
|
|
4861
|
+
const ch = source[index];
|
|
4862
|
+
if (ch === "%" && !isEscapedAt(source, index)) {
|
|
4863
|
+
while (index < source.length && source[index] !== "\n") index += 1;
|
|
4864
|
+
continue;
|
|
4865
|
+
}
|
|
4866
|
+
if (source.startsWith("$$", index)) {
|
|
4867
|
+
const close = source.indexOf("$$", index + 2);
|
|
4868
|
+
if (close >= 0) {
|
|
4869
|
+
out += " " + source.slice(index + 2, close) + " ";
|
|
4870
|
+
index = close + 2;
|
|
4871
|
+
continue;
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
if (ch === "$" && !isEscapedAt(source, index)) {
|
|
4875
|
+
const close = findClosingUnescapedSequence(source, index + 1, "$", true);
|
|
4876
|
+
if (close >= 0) {
|
|
4877
|
+
out += " " + source.slice(index + 1, close) + " ";
|
|
4878
|
+
index = close + 1;
|
|
4879
|
+
continue;
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
if (source.startsWith("\\(", index)) {
|
|
4883
|
+
const close = source.indexOf("\\)", index + 2);
|
|
4884
|
+
if (close >= 0) {
|
|
4885
|
+
out += " " + source.slice(index + 2, close) + " ";
|
|
4886
|
+
index = close + 2;
|
|
4887
|
+
continue;
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4890
|
+
if (source.startsWith("\\[", index)) {
|
|
4891
|
+
const close = source.indexOf("\\]", index + 2);
|
|
4892
|
+
if (close >= 0) {
|
|
4893
|
+
out += " " + source.slice(index + 2, close) + " ";
|
|
4894
|
+
index = close + 2;
|
|
4895
|
+
continue;
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
if (source.startsWith("\\begin{", index)) {
|
|
4899
|
+
const envGroup = readBalancedLatexGroup(source, index + 6, "{", "}");
|
|
4900
|
+
const envName = envGroup ? source.slice(envGroup.contentStart, envGroup.contentEnd).trim() : "";
|
|
4901
|
+
if (envName && DISPLAY_MATH_ENV_NAMES.has(envName)) {
|
|
4902
|
+
const closeToken = "\\end{" + envName + "}";
|
|
4903
|
+
const close = source.indexOf(closeToken, envGroup.end);
|
|
4904
|
+
if (close >= 0) {
|
|
4905
|
+
out += " " + source.slice(envGroup.end, close) + " ";
|
|
4906
|
+
index = close + closeToken.length;
|
|
4907
|
+
continue;
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
if (source.startsWith("\\end{", index)) {
|
|
4912
|
+
const envGroup = readBalancedLatexGroup(source, index + 4, "{", "}");
|
|
4913
|
+
if (envGroup) {
|
|
4914
|
+
index = envGroup.end;
|
|
4915
|
+
continue;
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
if (ch === "\\") {
|
|
4919
|
+
const command = parseLatexCommandAt(source, index);
|
|
4920
|
+
const commandName = command && command.name
|
|
4921
|
+
? String(command.name || "").replace(/\*$/, "").toLowerCase()
|
|
4922
|
+
: "";
|
|
4923
|
+
if (!command) {
|
|
4924
|
+
index += 1;
|
|
4925
|
+
continue;
|
|
4926
|
+
}
|
|
4927
|
+
if (commandName === "begin" || commandName === "end") {
|
|
4928
|
+
let nextIndex = skipLatexWhitespace(source, command.end);
|
|
4929
|
+
if (source[nextIndex] === "{") {
|
|
4930
|
+
const group = readBalancedLatexGroup(source, nextIndex, "{", "}");
|
|
4931
|
+
if (group) {
|
|
4932
|
+
index = group.end;
|
|
4933
|
+
continue;
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
if (commandName === "latex") {
|
|
4938
|
+
out += "LaTeX";
|
|
4939
|
+
index = command.end;
|
|
4940
|
+
continue;
|
|
4941
|
+
}
|
|
4942
|
+
if (commandName === "tex") {
|
|
4943
|
+
out += "TeX";
|
|
4944
|
+
index = command.end;
|
|
4945
|
+
continue;
|
|
4946
|
+
}
|
|
4947
|
+
if (commandName === "item") {
|
|
4948
|
+
out += " ";
|
|
4949
|
+
index = command.end;
|
|
4950
|
+
continue;
|
|
4951
|
+
}
|
|
4952
|
+
let nextIndex = skipLatexWhitespace(source, command.end);
|
|
4953
|
+
if (source[nextIndex] === "[") {
|
|
4954
|
+
const optionalGroup = readBalancedLatexGroup(source, nextIndex, "[", "]");
|
|
4955
|
+
if (optionalGroup) {
|
|
4956
|
+
nextIndex = skipLatexWhitespace(source, optionalGroup.end);
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
if (LATEX_PREVIEW_VISIBLE_GROUP_COMMANDS.has(commandName) && source[nextIndex] === "{") {
|
|
4960
|
+
const group = readBalancedLatexGroup(source, nextIndex, "{", "}");
|
|
4961
|
+
if (group) {
|
|
4962
|
+
out += " " + extractLatexPreviewVisibleText(source.slice(group.contentStart, group.contentEnd)) + " ";
|
|
4963
|
+
index = group.end;
|
|
4964
|
+
continue;
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
if (LATEX_PREVIEW_SECOND_ARG_VISIBLE_COMMANDS.has(commandName) && source[nextIndex] === "{") {
|
|
4968
|
+
const firstGroup = readBalancedLatexGroup(source, nextIndex, "{", "}");
|
|
4969
|
+
if (firstGroup) {
|
|
4970
|
+
let secondIndex = skipLatexWhitespace(source, firstGroup.end);
|
|
4971
|
+
if (source[secondIndex] === "{") {
|
|
4972
|
+
const secondGroup = readBalancedLatexGroup(source, secondIndex, "{", "}");
|
|
4973
|
+
if (secondGroup) {
|
|
4974
|
+
out += " " + extractLatexPreviewVisibleText(source.slice(secondGroup.contentStart, secondGroup.contentEnd)) + " ";
|
|
4975
|
+
index = secondGroup.end;
|
|
4976
|
+
continue;
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
if (LATEX_PREVIEW_HIDDEN_COMMANDS.has(commandName)) {
|
|
4982
|
+
index = nextIndex;
|
|
4983
|
+
if (source[index] === "{") {
|
|
4984
|
+
const group = readBalancedLatexGroup(source, index, "{", "}");
|
|
4985
|
+
if (group) {
|
|
4986
|
+
index = group.end;
|
|
4987
|
+
continue;
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
index = command.end;
|
|
4991
|
+
continue;
|
|
4992
|
+
}
|
|
4993
|
+
index = command.end;
|
|
4994
|
+
continue;
|
|
4995
|
+
}
|
|
4996
|
+
if (ch === "{" || ch === "}") {
|
|
4997
|
+
index += 1;
|
|
4998
|
+
continue;
|
|
4999
|
+
}
|
|
5000
|
+
if (ch === "~") {
|
|
5001
|
+
out += " ";
|
|
5002
|
+
index += 1;
|
|
5003
|
+
continue;
|
|
5004
|
+
}
|
|
5005
|
+
out += ch;
|
|
5006
|
+
index += 1;
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
return normalizeVisiblePreviewText(out);
|
|
5010
|
+
}
|
|
5011
|
+
|
|
5012
|
+
function findLatexDocumentBodyRange(text) {
|
|
5013
|
+
const source = String(text || "");
|
|
5014
|
+
const beginMatch = source.match(/\\begin\{document\}/);
|
|
5015
|
+
if (!beginMatch || beginMatch.index == null) {
|
|
5016
|
+
return { start: 0, end: source.length };
|
|
5017
|
+
}
|
|
5018
|
+
const start = beginMatch.index + beginMatch[0].length;
|
|
5019
|
+
const endMatch = source.slice(start).match(/\\end\{document\}/);
|
|
5020
|
+
return {
|
|
5021
|
+
start,
|
|
5022
|
+
end: endMatch && endMatch.index != null ? (start + endMatch.index) : source.length,
|
|
5023
|
+
};
|
|
5024
|
+
}
|
|
5025
|
+
|
|
5026
|
+
function normalizeLatexPreviewBlockText(blockText, kind) {
|
|
5027
|
+
const source = String(blockText || "");
|
|
5028
|
+
if (/\\(?:bibliography|printbibliography)\b/i.test(source)) {
|
|
5029
|
+
return kind === "heading" ? "References" : "references";
|
|
5030
|
+
}
|
|
5031
|
+
if (kind === "math") {
|
|
5032
|
+
const mathRange = getStandaloneDisplayMathRange(stripLatexPreviewComments(source));
|
|
5033
|
+
return mathRange ? normalizeVisiblePreviewText(mathRange.bodyText) : normalizeVisiblePreviewText(source);
|
|
5034
|
+
}
|
|
5035
|
+
if (kind === "heading") {
|
|
5036
|
+
const heading = readLatexHeadingChunk(stripLatexPreviewComments(source));
|
|
5037
|
+
return heading ? extractLatexPreviewVisibleText(heading.titleText) : extractLatexPreviewVisibleText(source);
|
|
5038
|
+
}
|
|
5039
|
+
return extractLatexPreviewVisibleText(source);
|
|
5040
|
+
}
|
|
5041
|
+
|
|
5042
|
+
function isLatexPreviewSkippableChunk(chunkText) {
|
|
5043
|
+
const source = stripLatexPreviewComments(chunkText).trim();
|
|
5044
|
+
if (!source) return true;
|
|
5045
|
+
const command = parseLatexCommandAt(source, 0);
|
|
5046
|
+
const commandName = command && command.name
|
|
5047
|
+
? String(command.name || "").replace(/\*$/, "").toLowerCase()
|
|
5048
|
+
: "";
|
|
5049
|
+
if (command && LATEX_PREVIEW_HIDDEN_COMMANDS.has(commandName)) return true;
|
|
5050
|
+
if (command && /^(?:documentclass|usepackage|newtheorem|title|author|date|maketitle|tableofcontents)$/i.test(commandName)) return true;
|
|
5051
|
+
if (source.startsWith("\\begin{")) {
|
|
5052
|
+
const envGroup = readBalancedLatexGroup(source, 6, "{", "}");
|
|
5053
|
+
const envName = envGroup ? source.slice(envGroup.contentStart, envGroup.contentEnd).trim().toLowerCase() : "";
|
|
5054
|
+
if (envName && LATEX_PREVIEW_SKIPPED_ENV_NAMES.has(envName)) return true;
|
|
5055
|
+
}
|
|
5056
|
+
return false;
|
|
5057
|
+
}
|
|
5058
|
+
|
|
5059
|
+
function normalizePreviewComparableCharacter(character) {
|
|
5060
|
+
switch (String(character || "")) {
|
|
5061
|
+
case "\u2018":
|
|
5062
|
+
case "\u2019":
|
|
5063
|
+
case "\u201A":
|
|
5064
|
+
case "\u201B":
|
|
5065
|
+
return "'";
|
|
5066
|
+
case "\u201C":
|
|
5067
|
+
case "\u201D":
|
|
5068
|
+
case "\u201E":
|
|
5069
|
+
case "\u201F":
|
|
5070
|
+
return '"';
|
|
5071
|
+
case "\u2013":
|
|
5072
|
+
case "\u2014":
|
|
5073
|
+
case "\u2212":
|
|
5074
|
+
return "-";
|
|
5075
|
+
case "\u2026":
|
|
5076
|
+
return "…";
|
|
5077
|
+
default:
|
|
5078
|
+
return String(character || "");
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
|
|
4465
5082
|
function normalizeVisiblePreviewText(text) {
|
|
4466
|
-
|
|
5083
|
+
const source = String(text || "");
|
|
5084
|
+
let normalized = "";
|
|
5085
|
+
let pendingWhitespace = false;
|
|
5086
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
5087
|
+
let character = source[i] === "." && source.slice(i, i + 3) === "..."
|
|
5088
|
+
? "…"
|
|
5089
|
+
: normalizePreviewComparableCharacter(source[i]);
|
|
5090
|
+
if (character === "…" && source[i] === "." && source.slice(i, i + 3) === "...") {
|
|
5091
|
+
i += 2;
|
|
5092
|
+
}
|
|
5093
|
+
if (/\s/.test(character)) {
|
|
5094
|
+
if (normalized) {
|
|
5095
|
+
pendingWhitespace = true;
|
|
5096
|
+
}
|
|
5097
|
+
continue;
|
|
5098
|
+
}
|
|
5099
|
+
if (pendingWhitespace && normalized) {
|
|
5100
|
+
normalized += " ";
|
|
5101
|
+
pendingWhitespace = false;
|
|
5102
|
+
}
|
|
5103
|
+
normalized += character;
|
|
5104
|
+
}
|
|
5105
|
+
return normalized.trim();
|
|
5106
|
+
}
|
|
5107
|
+
|
|
5108
|
+
function splitSourcePreviewCommentBlockByDisplayMath(sourceText, block) {
|
|
5109
|
+
if (!block || block.kind !== "paragraph") {
|
|
5110
|
+
return block ? [block] : [];
|
|
5111
|
+
}
|
|
5112
|
+
const source = String(sourceText || "");
|
|
5113
|
+
const blockStart = Math.max(0, Math.min(Number(block.start) || 0, source.length));
|
|
5114
|
+
const blockEnd = Math.max(blockStart, Math.min(Number(block.end) || blockStart, source.length));
|
|
5115
|
+
const blockText = source.slice(blockStart, blockEnd);
|
|
5116
|
+
const mathRanges = collectDisplayMathRanges(blockText);
|
|
5117
|
+
if (mathRanges.length === 0) {
|
|
5118
|
+
return [block];
|
|
5119
|
+
}
|
|
5120
|
+
|
|
5121
|
+
const segments = [];
|
|
5122
|
+
function pushSegment(kind, relativeStart, relativeEnd) {
|
|
5123
|
+
const safeRelativeStart = Math.max(0, Math.min(relativeStart, blockText.length));
|
|
5124
|
+
const safeRelativeEnd = Math.max(safeRelativeStart, Math.min(relativeEnd, blockText.length));
|
|
5125
|
+
if (safeRelativeEnd <= safeRelativeStart) return;
|
|
5126
|
+
const absoluteStart = blockStart + safeRelativeStart;
|
|
5127
|
+
const absoluteEnd = blockStart + safeRelativeEnd;
|
|
5128
|
+
const segmentText = source.slice(absoluteStart, absoluteEnd);
|
|
5129
|
+
if (kind === "paragraph" && !normalizeVisiblePreviewText(segmentText)) {
|
|
5130
|
+
return;
|
|
5131
|
+
}
|
|
5132
|
+
segments.push({
|
|
5133
|
+
kind,
|
|
5134
|
+
start: absoluteStart,
|
|
5135
|
+
end: absoluteEnd,
|
|
5136
|
+
lineStart: getLineNumberAtOffset(source, absoluteStart),
|
|
5137
|
+
lineEnd: getLineNumberAtOffset(source, Math.max(absoluteStart, absoluteEnd - 1)),
|
|
5138
|
+
});
|
|
5139
|
+
}
|
|
5140
|
+
|
|
5141
|
+
let cursor = 0;
|
|
5142
|
+
mathRanges.forEach((mathRange) => {
|
|
5143
|
+
if (!mathRange) return;
|
|
5144
|
+
pushSegment("paragraph", cursor, mathRange.start);
|
|
5145
|
+
pushSegment("math", mathRange.start, mathRange.end);
|
|
5146
|
+
cursor = mathRange.end;
|
|
5147
|
+
});
|
|
5148
|
+
pushSegment("paragraph", cursor, blockText.length);
|
|
5149
|
+
|
|
5150
|
+
return segments.length > 0 ? segments : [block];
|
|
5151
|
+
}
|
|
5152
|
+
|
|
5153
|
+
function expandSourcePreviewCommentBlocksByDisplayMath(sourceText, blocks) {
|
|
5154
|
+
const expanded = [];
|
|
5155
|
+
(Array.isArray(blocks) ? blocks : []).forEach((block) => {
|
|
5156
|
+
expanded.push(...splitSourcePreviewCommentBlockByDisplayMath(sourceText, block));
|
|
5157
|
+
});
|
|
5158
|
+
return expanded;
|
|
4467
5159
|
}
|
|
4468
5160
|
|
|
4469
5161
|
function appendMappedPreviewSlice(chars, rawOffsets, lineText, lineBaseOffset, start, end) {
|
|
@@ -4516,6 +5208,14 @@
|
|
|
4516
5208
|
}
|
|
4517
5209
|
}
|
|
4518
5210
|
|
|
5211
|
+
if (kind === "math") {
|
|
5212
|
+
const mathRange = getStandaloneDisplayMathRange(source);
|
|
5213
|
+
if (mathRange) {
|
|
5214
|
+
appendMappedPreviewSlice(chars, rawOffsets, source, 0, mathRange.bodyStart, mathRange.bodyEnd);
|
|
5215
|
+
return { text: chars.join(""), rawOffsets };
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5218
|
+
|
|
4519
5219
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
4520
5220
|
const line = lines[lineIndex] || "";
|
|
4521
5221
|
if (kind === "blockquote") {
|
|
@@ -4543,6 +5243,26 @@
|
|
|
4543
5243
|
return { text: chars.join(""), rawOffsets };
|
|
4544
5244
|
}
|
|
4545
5245
|
|
|
5246
|
+
function findClosingUnescapedSequence(source, startIndex, sequence) {
|
|
5247
|
+
const text = String(source || "");
|
|
5248
|
+
const needle = String(sequence || "");
|
|
5249
|
+
if (!text || !needle) return -1;
|
|
5250
|
+
let searchIndex = Math.max(0, Number(startIndex) || 0);
|
|
5251
|
+
while (searchIndex <= text.length) {
|
|
5252
|
+
const matchIndex = text.indexOf(needle, searchIndex);
|
|
5253
|
+
if (matchIndex < 0) return -1;
|
|
5254
|
+
let backslashCount = 0;
|
|
5255
|
+
for (let i = matchIndex - 1; i >= 0 && text[i] === "\\"; i -= 1) {
|
|
5256
|
+
backslashCount += 1;
|
|
5257
|
+
}
|
|
5258
|
+
if (backslashCount % 2 === 0) {
|
|
5259
|
+
return matchIndex;
|
|
5260
|
+
}
|
|
5261
|
+
searchIndex = matchIndex + needle.length;
|
|
5262
|
+
}
|
|
5263
|
+
return -1;
|
|
5264
|
+
}
|
|
5265
|
+
|
|
4546
5266
|
function buildPreviewInlineDisplayMap(text, rawOffsets) {
|
|
4547
5267
|
const source = String(text || "");
|
|
4548
5268
|
const rawMap = Array.isArray(rawOffsets) ? rawOffsets : [];
|
|
@@ -4556,6 +5276,12 @@
|
|
|
4556
5276
|
charEnds.push(rawEnd);
|
|
4557
5277
|
}
|
|
4558
5278
|
|
|
5279
|
+
function appendRawRange(startIndex, endIndex) {
|
|
5280
|
+
for (let i = startIndex; i < endIndex; i += 1) {
|
|
5281
|
+
appendChar(source[i], rawMap[i], rawMap[i] + 1);
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
|
|
4559
5285
|
function appendNestedRange(startIndex, endIndex) {
|
|
4560
5286
|
const nested = buildPreviewInlineDisplayMap(
|
|
4561
5287
|
source.slice(startIndex, endIndex),
|
|
@@ -4584,15 +5310,86 @@
|
|
|
4584
5310
|
const fence = "`".repeat(tickCount);
|
|
4585
5311
|
const closeIndex = source.indexOf(fence, index + tickCount);
|
|
4586
5312
|
if (closeIndex >= 0) {
|
|
4587
|
-
|
|
4588
|
-
appendChar(source[i], rawMap[i], rawMap[i] + 1);
|
|
4589
|
-
}
|
|
5313
|
+
appendRawRange(index + tickCount, closeIndex);
|
|
4590
5314
|
index = closeIndex + tickCount;
|
|
4591
5315
|
continue;
|
|
4592
5316
|
}
|
|
4593
5317
|
}
|
|
4594
5318
|
|
|
5319
|
+
if (remaining.startsWith("\\(")) {
|
|
5320
|
+
const closeIndex = source.indexOf("\\)", index + 2);
|
|
5321
|
+
if (closeIndex >= 0) {
|
|
5322
|
+
appendRawRange(index + 2, closeIndex);
|
|
5323
|
+
index = closeIndex + 2;
|
|
5324
|
+
continue;
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
|
|
5328
|
+
if (remaining.startsWith("\\[")) {
|
|
5329
|
+
const closeIndex = source.indexOf("\\]", index + 2);
|
|
5330
|
+
if (closeIndex >= 0) {
|
|
5331
|
+
appendRawRange(index + 2, closeIndex);
|
|
5332
|
+
index = closeIndex + 2;
|
|
5333
|
+
continue;
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
|
|
5337
|
+
if (remaining.startsWith("$$")) {
|
|
5338
|
+
const closeIndex = findClosingUnescapedSequence(source, index + 2, "$$");
|
|
5339
|
+
if (closeIndex >= 0) {
|
|
5340
|
+
appendRawRange(index + 2, closeIndex);
|
|
5341
|
+
index = closeIndex + 2;
|
|
5342
|
+
continue;
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
|
|
5346
|
+
if (source[index] === "$") {
|
|
5347
|
+
const closeIndex = findClosingUnescapedSequence(source, index + 1, "$");
|
|
5348
|
+
if (closeIndex >= 0) {
|
|
5349
|
+
appendRawRange(index + 1, closeIndex);
|
|
5350
|
+
index = closeIndex + 1;
|
|
5351
|
+
continue;
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
|
|
4595
5355
|
if (source[index] === "\\" && index + 1 < source.length) {
|
|
5356
|
+
const latexCommand = parseLatexCommandAt(source, index);
|
|
5357
|
+
const normalizedCommandName = latexCommand && latexCommand.name
|
|
5358
|
+
? String(latexCommand.name || "").replace(/\*$/, "").toLowerCase()
|
|
5359
|
+
: "";
|
|
5360
|
+
const isDroppedLatexCommand = Boolean(
|
|
5361
|
+
normalizedCommandName
|
|
5362
|
+
&& (
|
|
5363
|
+
DROPPED_MARKDOWN_RAW_TEX_GROUP_COMMANDS.has(normalizedCommandName)
|
|
5364
|
+
|| DROPPED_MARKDOWN_RAW_TEX_DOUBLE_GROUP_COMMANDS.has(normalizedCommandName)
|
|
5365
|
+
|| DROPPED_MARKDOWN_RAW_TEX_STANDALONE_COMMANDS.has(normalizedCommandName)
|
|
5366
|
+
)
|
|
5367
|
+
);
|
|
5368
|
+
if (latexCommand && isDroppedLatexCommand) {
|
|
5369
|
+
let nextIndex = skipLatexWhitespace(source, latexCommand.end);
|
|
5370
|
+
if (source[nextIndex] === "[") {
|
|
5371
|
+
const optionalGroup = readBalancedLatexGroup(source, nextIndex, "[", "]");
|
|
5372
|
+
if (optionalGroup) {
|
|
5373
|
+
nextIndex = skipLatexWhitespace(source, optionalGroup.end);
|
|
5374
|
+
}
|
|
5375
|
+
}
|
|
5376
|
+
if (DROPPED_MARKDOWN_RAW_TEX_GROUP_COMMANDS.has(normalizedCommandName) || DROPPED_MARKDOWN_RAW_TEX_DOUBLE_GROUP_COMMANDS.has(normalizedCommandName)) {
|
|
5377
|
+
if (source[nextIndex] === "{") {
|
|
5378
|
+
const firstGroup = readBalancedLatexGroup(source, nextIndex, "{", "}");
|
|
5379
|
+
if (firstGroup) {
|
|
5380
|
+
nextIndex = skipLatexWhitespace(source, firstGroup.end);
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
if (DROPPED_MARKDOWN_RAW_TEX_DOUBLE_GROUP_COMMANDS.has(normalizedCommandName) && source[nextIndex] === "{") {
|
|
5385
|
+
const secondGroup = readBalancedLatexGroup(source, nextIndex, "{", "}");
|
|
5386
|
+
if (secondGroup) {
|
|
5387
|
+
nextIndex = skipLatexWhitespace(source, secondGroup.end);
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
index = Math.max(index + 1, nextIndex);
|
|
5391
|
+
continue;
|
|
5392
|
+
}
|
|
4596
5393
|
appendChar(source[index + 1], rawMap[index], rawMap[index + 1] + 1);
|
|
4597
5394
|
index += 2;
|
|
4598
5395
|
continue;
|
|
@@ -4630,13 +5427,20 @@
|
|
|
4630
5427
|
let pendingWhitespaceEnd = null;
|
|
4631
5428
|
|
|
4632
5429
|
for (let i = 0; i < source.length; i += 1) {
|
|
4633
|
-
|
|
5430
|
+
let character = normalizePreviewComparableCharacter(source[i]);
|
|
5431
|
+
let startRef = charStarts[i];
|
|
5432
|
+
let endRef = charEnds[i];
|
|
5433
|
+
if (source[i] === "." && source.slice(i, i + 3) === "...") {
|
|
5434
|
+
character = "…";
|
|
5435
|
+
endRef = charEnds[Math.min(i + 2, charEnds.length - 1)];
|
|
5436
|
+
i += 2;
|
|
5437
|
+
}
|
|
4634
5438
|
if (/\s/.test(character)) {
|
|
4635
5439
|
if (outChars.length === 0) continue;
|
|
4636
5440
|
if (pendingWhitespaceStart == null) {
|
|
4637
|
-
pendingWhitespaceStart =
|
|
5441
|
+
pendingWhitespaceStart = startRef;
|
|
4638
5442
|
}
|
|
4639
|
-
pendingWhitespaceEnd =
|
|
5443
|
+
pendingWhitespaceEnd = endRef;
|
|
4640
5444
|
continue;
|
|
4641
5445
|
}
|
|
4642
5446
|
|
|
@@ -4649,8 +5453,8 @@
|
|
|
4649
5453
|
}
|
|
4650
5454
|
|
|
4651
5455
|
outChars.push(character);
|
|
4652
|
-
outStarts.push(
|
|
4653
|
-
outEnds.push(
|
|
5456
|
+
outStarts.push(startRef);
|
|
5457
|
+
outEnds.push(endRef);
|
|
4654
5458
|
}
|
|
4655
5459
|
|
|
4656
5460
|
return {
|
|
@@ -4682,6 +5486,78 @@
|
|
|
4682
5486
|
return buildNormalizedPreviewDisplayMap(chars.join(""), starts, ends);
|
|
4683
5487
|
}
|
|
4684
5488
|
|
|
5489
|
+
function getPreviewMathSearchText(element) {
|
|
5490
|
+
if (!element || !(element instanceof Element)) return null;
|
|
5491
|
+
const texSourceAttr = element.getAttribute("data-tex-source");
|
|
5492
|
+
if (texSourceAttr && texSourceAttr.trim()) {
|
|
5493
|
+
return texSourceAttr;
|
|
5494
|
+
}
|
|
5495
|
+
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
5496
|
+
if (tag === "MATH") {
|
|
5497
|
+
return typeof element.textContent === "string" ? element.textContent : "";
|
|
5498
|
+
}
|
|
5499
|
+
if (element.classList && element.classList.contains("math") && (element.classList.contains("inline") || element.classList.contains("display"))) {
|
|
5500
|
+
return extractMathFallbackTex(
|
|
5501
|
+
typeof element.textContent === "string" ? element.textContent : "",
|
|
5502
|
+
element.classList.contains("display"),
|
|
5503
|
+
);
|
|
5504
|
+
}
|
|
5505
|
+
if (
|
|
5506
|
+
element.classList
|
|
5507
|
+
&& (element.classList.contains("studio-display-equation") || element.classList.contains("studio-display-equation-body"))
|
|
5508
|
+
&& typeof element.querySelector === "function"
|
|
5509
|
+
) {
|
|
5510
|
+
const innerMathEl = element.querySelector("[data-tex-source], math[display='block'], .studio-mathjax-fallback-display");
|
|
5511
|
+
if (innerMathEl && innerMathEl !== element) {
|
|
5512
|
+
return getPreviewMathSearchText(innerMathEl);
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
return null;
|
|
5516
|
+
}
|
|
5517
|
+
|
|
5518
|
+
function buildNormalizedPreviewSearchText(rootNode) {
|
|
5519
|
+
if (!rootNode) return "";
|
|
5520
|
+
const parts = [];
|
|
5521
|
+
|
|
5522
|
+
function visit(node) {
|
|
5523
|
+
if (!node) return;
|
|
5524
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
5525
|
+
parts.push(typeof node.nodeValue === "string" ? node.nodeValue : "");
|
|
5526
|
+
return;
|
|
5527
|
+
}
|
|
5528
|
+
if (node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
|
5529
|
+
return;
|
|
5530
|
+
}
|
|
5531
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
5532
|
+
const element = node;
|
|
5533
|
+
const mathText = getPreviewMathSearchText(element);
|
|
5534
|
+
if (mathText != null) {
|
|
5535
|
+
parts.push(mathText);
|
|
5536
|
+
return;
|
|
5537
|
+
}
|
|
5538
|
+
if (element.tagName === "BR") {
|
|
5539
|
+
parts.push("\n");
|
|
5540
|
+
return;
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
Array.from(node.childNodes || []).forEach(visit);
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5546
|
+
visit(rootNode);
|
|
5547
|
+
return normalizeVisiblePreviewText(parts.join(""));
|
|
5548
|
+
}
|
|
5549
|
+
|
|
5550
|
+
function buildNormalizedPreviewRangeText(range) {
|
|
5551
|
+
if (!range || typeof range.cloneContents !== "function") {
|
|
5552
|
+
return "";
|
|
5553
|
+
}
|
|
5554
|
+
try {
|
|
5555
|
+
return buildNormalizedPreviewSearchText(range.cloneContents());
|
|
5556
|
+
} catch {
|
|
5557
|
+
return normalizeVisiblePreviewText(range.toString());
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
|
|
4685
5561
|
function findPreferredNormalizedTextMatch(haystack, needle, preferredIndex) {
|
|
4686
5562
|
const source = String(haystack || "");
|
|
4687
5563
|
const query = String(needle || "");
|
|
@@ -4797,8 +5673,9 @@
|
|
|
4797
5673
|
}
|
|
4798
5674
|
|
|
4799
5675
|
function scanSourcePreviewCommentBlocks(markdown) {
|
|
4800
|
-
if (editorLanguage
|
|
4801
|
-
return
|
|
5676
|
+
if (editorLanguage === "markdown") return scanMarkdownPreviewCommentBlocks(markdown);
|
|
5677
|
+
if (editorLanguage === "latex") return scanLatexPreviewCommentBlocks(markdown);
|
|
5678
|
+
return [];
|
|
4802
5679
|
}
|
|
4803
5680
|
|
|
4804
5681
|
function scanMarkdownPreviewCommentBlocks(markdown) {
|
|
@@ -4860,6 +5737,10 @@
|
|
|
4860
5737
|
return /^\s*<!--/.test(getLine(index));
|
|
4861
5738
|
}
|
|
4862
5739
|
|
|
5740
|
+
function isPageBreakLine(index) {
|
|
5741
|
+
return /^\\(?:newpage|pagebreak|clearpage)(?:\s*\[[^\]]*\])?\s*$/i.test(getLine(index));
|
|
5742
|
+
}
|
|
5743
|
+
|
|
4863
5744
|
function makeBlock(kind, startLineIndex, endLineIndex) {
|
|
4864
5745
|
const safeStartLine = Math.max(0, Math.min(startLineIndex, Math.max(0, lines.length - 1)));
|
|
4865
5746
|
const safeEndLine = Math.max(safeStartLine, Math.min(endLineIndex, Math.max(0, lines.length - 1)));
|
|
@@ -4906,6 +5787,12 @@
|
|
|
4906
5787
|
continue;
|
|
4907
5788
|
}
|
|
4908
5789
|
|
|
5790
|
+
if (isPageBreakLine(index)) {
|
|
5791
|
+
blocks.push(makeBlock("page-break", index, index));
|
|
5792
|
+
index += 1;
|
|
5793
|
+
continue;
|
|
5794
|
+
}
|
|
5795
|
+
|
|
4909
5796
|
const fenceMatch = lineStartsFence(index);
|
|
4910
5797
|
if (fenceMatch) {
|
|
4911
5798
|
const marker = fenceMatch[1] || "";
|
|
@@ -5004,18 +5891,353 @@
|
|
|
5004
5891
|
index = endParagraph + 1;
|
|
5005
5892
|
}
|
|
5006
5893
|
|
|
5894
|
+
return expandSourcePreviewCommentBlocksByDisplayMath(source, blocks);
|
|
5895
|
+
}
|
|
5896
|
+
|
|
5897
|
+
function scanLatexPreviewCommentBlocks(markdown) {
|
|
5898
|
+
const source = String(markdown || "").replace(/\r\n/g, "\n");
|
|
5899
|
+
if (!source) return [];
|
|
5900
|
+
const bodyRange = findLatexDocumentBodyRange(source);
|
|
5901
|
+
const bodyStart = Math.max(0, Math.min(bodyRange.start, source.length));
|
|
5902
|
+
const bodyEnd = Math.max(bodyStart, Math.min(bodyRange.end, source.length));
|
|
5903
|
+
const bodyText = source.slice(bodyStart, bodyEnd);
|
|
5904
|
+
const lines = bodyText.split("\n");
|
|
5905
|
+
const lineOffsets = [];
|
|
5906
|
+
let runningOffset = 0;
|
|
5907
|
+
for (const line of lines) {
|
|
5908
|
+
lineOffsets.push(runningOffset);
|
|
5909
|
+
runningOffset += line.length + 1;
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
function getLine(index) {
|
|
5913
|
+
return index >= 0 && index < lines.length ? String(lines[index] || "") : "";
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
function getStrippedLine(index) {
|
|
5917
|
+
return stripLatexPreviewComments(getLine(index)).trim();
|
|
5918
|
+
}
|
|
5919
|
+
|
|
5920
|
+
function isBlankLine(index) {
|
|
5921
|
+
return !getStrippedLine(index);
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5924
|
+
function isBibliographyCommandLine(index) {
|
|
5925
|
+
return /^\\(?:bibliographystyle|bibliography|printbibliography)\b/i.test(getStrippedLine(index));
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5928
|
+
function makeBlock(kind, startLineIndex, endLineIndex) {
|
|
5929
|
+
const safeStartLine = Math.max(0, Math.min(startLineIndex, Math.max(0, lines.length - 1)));
|
|
5930
|
+
const safeEndLine = Math.max(safeStartLine, Math.min(endLineIndex, Math.max(0, lines.length - 1)));
|
|
5931
|
+
const start = bodyStart + (lineOffsets[safeStartLine] || 0);
|
|
5932
|
+
const end = bodyStart + (lineOffsets[safeEndLine] || 0) + getLine(safeEndLine).length;
|
|
5933
|
+
return {
|
|
5934
|
+
kind,
|
|
5935
|
+
start,
|
|
5936
|
+
end,
|
|
5937
|
+
lineStart: getLineNumberAtOffset(source, start),
|
|
5938
|
+
lineEnd: getLineNumberAtOffset(source, Math.max(start, end - 1)),
|
|
5939
|
+
};
|
|
5940
|
+
}
|
|
5941
|
+
|
|
5942
|
+
function getChunkText(startLineIndex, endLineIndex) {
|
|
5943
|
+
return bodyText.slice(
|
|
5944
|
+
lineOffsets[startLineIndex] || 0,
|
|
5945
|
+
(lineOffsets[endLineIndex] || 0) + getLine(endLineIndex).length,
|
|
5946
|
+
);
|
|
5947
|
+
}
|
|
5948
|
+
|
|
5949
|
+
function getEnvironmentStartName(index) {
|
|
5950
|
+
const line = getStrippedLine(index);
|
|
5951
|
+
const match = line.match(/^\\begin\{([^}]+)\}/);
|
|
5952
|
+
return match ? String(match[1] || "").trim().toLowerCase() : "";
|
|
5953
|
+
}
|
|
5954
|
+
|
|
5955
|
+
function findEnvironmentEndLine(startLineIndex, envName) {
|
|
5956
|
+
const openToken = "\\begin{" + envName + "}";
|
|
5957
|
+
const closeToken = "\\end{" + envName + "}";
|
|
5958
|
+
let depth = 0;
|
|
5959
|
+
for (let lineIndex = startLineIndex; lineIndex < lines.length; lineIndex += 1) {
|
|
5960
|
+
const line = getStrippedLine(lineIndex);
|
|
5961
|
+
if (line.includes(openToken)) depth += 1;
|
|
5962
|
+
if (line.includes(closeToken)) {
|
|
5963
|
+
depth -= 1;
|
|
5964
|
+
if (depth <= 0) return lineIndex;
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
return startLineIndex;
|
|
5968
|
+
}
|
|
5969
|
+
|
|
5970
|
+
function isHeadingLine(index) {
|
|
5971
|
+
return Boolean(readLatexHeadingChunk(getLine(index)));
|
|
5972
|
+
}
|
|
5973
|
+
|
|
5974
|
+
function findBibliographyCommandEndLine(startLineIndex) {
|
|
5975
|
+
let endLineIndex = startLineIndex;
|
|
5976
|
+
for (let lineIndex = startLineIndex + 1; lineIndex < lines.length; lineIndex += 1) {
|
|
5977
|
+
if (!isBibliographyCommandLine(lineIndex)) break;
|
|
5978
|
+
endLineIndex = lineIndex;
|
|
5979
|
+
}
|
|
5980
|
+
return endLineIndex;
|
|
5981
|
+
}
|
|
5982
|
+
|
|
5983
|
+
function isMathStartLine(index) {
|
|
5984
|
+
const line = getStrippedLine(index);
|
|
5985
|
+
if (!line) return false;
|
|
5986
|
+
if (line.startsWith("$$") || line.startsWith("\\[")) return true;
|
|
5987
|
+
const envName = getEnvironmentStartName(index);
|
|
5988
|
+
return Boolean(envName && DISPLAY_MATH_ENV_NAMES.has(envName));
|
|
5989
|
+
}
|
|
5990
|
+
|
|
5991
|
+
function findMathEndLine(startLineIndex) {
|
|
5992
|
+
for (let endLineIndex = startLineIndex; endLineIndex < lines.length; endLineIndex += 1) {
|
|
5993
|
+
const chunkText = getChunkText(startLineIndex, endLineIndex);
|
|
5994
|
+
if (getStandaloneDisplayMathRange(stripLatexPreviewComments(chunkText))) {
|
|
5995
|
+
return endLineIndex;
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5998
|
+
return startLineIndex;
|
|
5999
|
+
}
|
|
6000
|
+
|
|
6001
|
+
const blocks = [];
|
|
6002
|
+
let lineIndex = 0;
|
|
6003
|
+
while (lineIndex < lines.length) {
|
|
6004
|
+
if (isBlankLine(lineIndex)) {
|
|
6005
|
+
lineIndex += 1;
|
|
6006
|
+
continue;
|
|
6007
|
+
}
|
|
6008
|
+
|
|
6009
|
+
const strippedLine = getStrippedLine(lineIndex);
|
|
6010
|
+
const envName = getEnvironmentStartName(lineIndex);
|
|
6011
|
+
|
|
6012
|
+
if (isHeadingLine(lineIndex)) {
|
|
6013
|
+
blocks.push(makeBlock("heading", lineIndex, lineIndex));
|
|
6014
|
+
lineIndex += 1;
|
|
6015
|
+
continue;
|
|
6016
|
+
}
|
|
6017
|
+
|
|
6018
|
+
if (envName === "abstract" || envName === "keywords") {
|
|
6019
|
+
const endLineIndex = findEnvironmentEndLine(lineIndex, envName);
|
|
6020
|
+
const chunkText = getChunkText(lineIndex, endLineIndex);
|
|
6021
|
+
if (normalizeLatexPreviewBlockText(chunkText, "paragraph")) {
|
|
6022
|
+
blocks.push(makeBlock("paragraph", lineIndex, endLineIndex));
|
|
6023
|
+
}
|
|
6024
|
+
lineIndex = endLineIndex + 1;
|
|
6025
|
+
continue;
|
|
6026
|
+
}
|
|
6027
|
+
|
|
6028
|
+
if (envName && LATEX_PREVIEW_STRUCTURAL_ENV_KIND_BY_NAME.has(envName)) {
|
|
6029
|
+
const endLineIndex = findEnvironmentEndLine(lineIndex, envName);
|
|
6030
|
+
blocks.push(makeBlock(LATEX_PREVIEW_STRUCTURAL_ENV_KIND_BY_NAME.get(envName) || "paragraph", lineIndex, endLineIndex));
|
|
6031
|
+
lineIndex = endLineIndex + 1;
|
|
6032
|
+
continue;
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
if (isBibliographyCommandLine(lineIndex)) {
|
|
6036
|
+
const endLineIndex = findBibliographyCommandEndLine(lineIndex);
|
|
6037
|
+
blocks.push(makeBlock("heading", lineIndex, endLineIndex));
|
|
6038
|
+
blocks.push(makeBlock("paragraph", lineIndex, endLineIndex));
|
|
6039
|
+
lineIndex = endLineIndex + 1;
|
|
6040
|
+
continue;
|
|
6041
|
+
}
|
|
6042
|
+
|
|
6043
|
+
if (envName && LATEX_PREVIEW_SKIPPED_ENV_NAMES.has(envName) && !DISPLAY_MATH_ENV_NAMES.has(envName)) {
|
|
6044
|
+
lineIndex = findEnvironmentEndLine(lineIndex, envName) + 1;
|
|
6045
|
+
continue;
|
|
6046
|
+
}
|
|
6047
|
+
|
|
6048
|
+
if (isMathStartLine(lineIndex)) {
|
|
6049
|
+
const endLineIndex = findMathEndLine(lineIndex);
|
|
6050
|
+
blocks.push(makeBlock("math", lineIndex, endLineIndex));
|
|
6051
|
+
lineIndex = endLineIndex + 1;
|
|
6052
|
+
continue;
|
|
6053
|
+
}
|
|
6054
|
+
|
|
6055
|
+
if (isLatexPreviewSkippableChunk(strippedLine)) {
|
|
6056
|
+
lineIndex += 1;
|
|
6057
|
+
continue;
|
|
6058
|
+
}
|
|
6059
|
+
|
|
6060
|
+
const paragraphStartLine = lineIndex;
|
|
6061
|
+
let paragraphEndLine = lineIndex;
|
|
6062
|
+
for (let nextLineIndex = lineIndex + 1; nextLineIndex < lines.length; nextLineIndex += 1) {
|
|
6063
|
+
if (isBlankLine(nextLineIndex) || isHeadingLine(nextLineIndex) || isMathStartLine(nextLineIndex)) {
|
|
6064
|
+
break;
|
|
6065
|
+
}
|
|
6066
|
+
const nextEnvName = getEnvironmentStartName(nextLineIndex);
|
|
6067
|
+
if (nextEnvName) {
|
|
6068
|
+
break;
|
|
6069
|
+
}
|
|
6070
|
+
paragraphEndLine = nextLineIndex;
|
|
6071
|
+
}
|
|
6072
|
+
|
|
6073
|
+
const chunkText = getChunkText(paragraphStartLine, paragraphEndLine);
|
|
6074
|
+
if (normalizeLatexPreviewBlockText(chunkText, "paragraph") && !isLatexPreviewSkippableChunk(chunkText)) {
|
|
6075
|
+
blocks.push(makeBlock("paragraph", paragraphStartLine, paragraphEndLine));
|
|
6076
|
+
}
|
|
6077
|
+
lineIndex = paragraphEndLine + 1;
|
|
6078
|
+
}
|
|
6079
|
+
|
|
5007
6080
|
return blocks;
|
|
5008
6081
|
}
|
|
5009
6082
|
|
|
6083
|
+
function isPreviewDisplayMathElement(element) {
|
|
6084
|
+
return Boolean(
|
|
6085
|
+
element
|
|
6086
|
+
&& element instanceof Element
|
|
6087
|
+
&& element.matches
|
|
6088
|
+
&& element.matches("math[display='block'], .studio-mathjax-fallback-display, .studio-display-equation, .studio-display-equation-body")
|
|
6089
|
+
);
|
|
6090
|
+
}
|
|
6091
|
+
|
|
6092
|
+
function previewNodesHaveVisibleContent(nodes) {
|
|
6093
|
+
return (Array.isArray(nodes) ? nodes : []).some((node) => {
|
|
6094
|
+
if (!node) return false;
|
|
6095
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
6096
|
+
return Boolean(normalizeVisiblePreviewText(node.nodeValue || ""));
|
|
6097
|
+
}
|
|
6098
|
+
return node instanceof Element && Boolean(buildNormalizedPreviewSearchText(node));
|
|
6099
|
+
});
|
|
6100
|
+
}
|
|
6101
|
+
|
|
6102
|
+
function wrapLoosePreviewInlineRunsAsParagraphs(targetEl) {
|
|
6103
|
+
if (!targetEl || !targetEl.childNodes || typeof document.createElement !== "function") return;
|
|
6104
|
+
const childNodes = Array.from(targetEl.childNodes || []);
|
|
6105
|
+
if (childNodes.length === 0) return;
|
|
6106
|
+
|
|
6107
|
+
function isDirectBlockChild(node) {
|
|
6108
|
+
if (!(node instanceof Element) || node.parentElement !== targetEl) return false;
|
|
6109
|
+
const tag = node.tagName ? node.tagName.toUpperCase() : "";
|
|
6110
|
+
if (/^H[1-6]$/.test(tag)) return true;
|
|
6111
|
+
if (tag === "P" || tag === "BLOCKQUOTE" || tag === "UL" || tag === "OL" || tag === "TABLE" || tag === "PRE" || tag === "HEADER" || tag === "FIGURE") {
|
|
6112
|
+
return true;
|
|
6113
|
+
}
|
|
6114
|
+
if (tag === "MATH") {
|
|
6115
|
+
return String(node.getAttribute("display") || "").toLowerCase() === "block";
|
|
6116
|
+
}
|
|
6117
|
+
if (tag === "DIV") return true;
|
|
6118
|
+
return false;
|
|
6119
|
+
}
|
|
6120
|
+
|
|
6121
|
+
let runNodes = [];
|
|
6122
|
+
|
|
6123
|
+
function flushRun(referenceNode) {
|
|
6124
|
+
if (runNodes.length === 0) return;
|
|
6125
|
+
if (!previewNodesHaveVisibleContent(runNodes)) {
|
|
6126
|
+
runNodes.forEach((node) => {
|
|
6127
|
+
if (node && node.parentNode === targetEl) {
|
|
6128
|
+
targetEl.removeChild(node);
|
|
6129
|
+
}
|
|
6130
|
+
});
|
|
6131
|
+
runNodes = [];
|
|
6132
|
+
return;
|
|
6133
|
+
}
|
|
6134
|
+
const paragraphEl = document.createElement("p");
|
|
6135
|
+
runNodes.forEach((node) => {
|
|
6136
|
+
paragraphEl.appendChild(node);
|
|
6137
|
+
});
|
|
6138
|
+
targetEl.insertBefore(paragraphEl, referenceNode || null);
|
|
6139
|
+
runNodes = [];
|
|
6140
|
+
}
|
|
6141
|
+
|
|
6142
|
+
childNodes.forEach((node) => {
|
|
6143
|
+
if (node instanceof Element && isDirectBlockChild(node)) {
|
|
6144
|
+
flushRun(node);
|
|
6145
|
+
return;
|
|
6146
|
+
}
|
|
6147
|
+
if (node.parentNode === targetEl) {
|
|
6148
|
+
runNodes.push(node);
|
|
6149
|
+
}
|
|
6150
|
+
});
|
|
6151
|
+
flushRun(null);
|
|
6152
|
+
}
|
|
6153
|
+
|
|
6154
|
+
function splitMixedPreviewParagraphsAroundDisplayMath(targetEl) {
|
|
6155
|
+
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
6156
|
+
if (editorLanguage === "latex") {
|
|
6157
|
+
wrapLoosePreviewInlineRunsAsParagraphs(targetEl);
|
|
6158
|
+
}
|
|
6159
|
+
Array.from(targetEl.querySelectorAll("p")).forEach((paragraphEl) => {
|
|
6160
|
+
if (!(paragraphEl instanceof Element) || !paragraphEl.parentNode) return;
|
|
6161
|
+
if (paragraphEl.closest && paragraphEl.closest(".preview-comment-block")) return;
|
|
6162
|
+
let ancestor = paragraphEl.parentElement;
|
|
6163
|
+
while (ancestor && ancestor !== targetEl) {
|
|
6164
|
+
if (getPreviewCommentTargetKind(ancestor)) return;
|
|
6165
|
+
ancestor = ancestor.parentElement;
|
|
6166
|
+
}
|
|
6167
|
+
const childNodes = Array.from(paragraphEl.childNodes || []);
|
|
6168
|
+
if (!childNodes.some((node) => isPreviewDisplayMathElement(node))) return;
|
|
6169
|
+
|
|
6170
|
+
const fragment = document.createDocumentFragment();
|
|
6171
|
+
let proseNodes = [];
|
|
6172
|
+
let segmentCount = 0;
|
|
6173
|
+
|
|
6174
|
+
function flushProse() {
|
|
6175
|
+
if (proseNodes.length === 0) return;
|
|
6176
|
+
if (!previewNodesHaveVisibleContent(proseNodes)) {
|
|
6177
|
+
proseNodes = [];
|
|
6178
|
+
return;
|
|
6179
|
+
}
|
|
6180
|
+
const proseEl = paragraphEl.cloneNode(false);
|
|
6181
|
+
if (proseEl instanceof Element) {
|
|
6182
|
+
proseEl.removeAttribute("id");
|
|
6183
|
+
}
|
|
6184
|
+
proseNodes.forEach((node) => {
|
|
6185
|
+
proseEl.appendChild(node);
|
|
6186
|
+
});
|
|
6187
|
+
fragment.appendChild(proseEl);
|
|
6188
|
+
proseNodes = [];
|
|
6189
|
+
segmentCount += 1;
|
|
6190
|
+
}
|
|
6191
|
+
|
|
6192
|
+
childNodes.forEach((node) => {
|
|
6193
|
+
if (isPreviewDisplayMathElement(node)) {
|
|
6194
|
+
flushProse();
|
|
6195
|
+
fragment.appendChild(node);
|
|
6196
|
+
segmentCount += 1;
|
|
6197
|
+
return;
|
|
6198
|
+
}
|
|
6199
|
+
proseNodes.push(node);
|
|
6200
|
+
});
|
|
6201
|
+
flushProse();
|
|
6202
|
+
|
|
6203
|
+
if (segmentCount > 0) {
|
|
6204
|
+
paragraphEl.replaceWith(fragment);
|
|
6205
|
+
}
|
|
6206
|
+
});
|
|
6207
|
+
}
|
|
6208
|
+
|
|
5010
6209
|
function getPreviewCommentTargetKind(element) {
|
|
5011
6210
|
if (!element || !(element instanceof Element)) return "";
|
|
6211
|
+
if (element.classList && element.classList.contains("studio-mathjax-fallback-display")) {
|
|
6212
|
+
return "math";
|
|
6213
|
+
}
|
|
6214
|
+
if (element.classList && element.classList.contains("studio-page-break")) {
|
|
6215
|
+
return "page-break";
|
|
6216
|
+
}
|
|
5012
6217
|
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
5013
6218
|
if (/^H[1-6]$/.test(tag)) return "heading";
|
|
5014
6219
|
if (tag === "P") return "paragraph";
|
|
6220
|
+
if (tag === "FIGURE") {
|
|
6221
|
+
if (element.classList && element.classList.contains("studio-algorithm-block")) {
|
|
6222
|
+
return "algorithm";
|
|
6223
|
+
}
|
|
6224
|
+
return editorLanguage === "latex" ? "figure" : "";
|
|
6225
|
+
}
|
|
6226
|
+
if (tag === "DIV" && element.classList) {
|
|
6227
|
+
if (element.classList.contains("studio-display-equation")) {
|
|
6228
|
+
return "math";
|
|
6229
|
+
}
|
|
6230
|
+
if (element.classList.contains("abstract") || element.classList.contains("keywords") || element.classList.contains("references")) {
|
|
6231
|
+
return "paragraph";
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
5015
6234
|
if (tag === "BLOCKQUOTE") return "blockquote";
|
|
5016
6235
|
if (tag === "UL" || tag === "OL") return "list";
|
|
5017
6236
|
if (tag === "TABLE") return "table";
|
|
5018
6237
|
if (tag === "PRE") return "code";
|
|
6238
|
+
if (tag === "MATH") {
|
|
6239
|
+
return String(element.getAttribute("display") || "").toLowerCase() === "block" ? "math" : "";
|
|
6240
|
+
}
|
|
5019
6241
|
if (element.classList) {
|
|
5020
6242
|
if (
|
|
5021
6243
|
element.classList.contains("sourceCode")
|
|
@@ -5040,11 +6262,49 @@
|
|
|
5040
6262
|
return Boolean(getPreviewCommentTargetKind(element));
|
|
5041
6263
|
}
|
|
5042
6264
|
|
|
6265
|
+
function isLatexPreviewCommentTargetElement(element, targetEl) {
|
|
6266
|
+
if (!element || !(element instanceof Element) || !targetEl) return false;
|
|
6267
|
+
const kind = getPreviewCommentTargetKind(element);
|
|
6268
|
+
if (kind === "heading" || kind === "paragraph" || kind === "figure" || kind === "algorithm" || kind === "table") {
|
|
6269
|
+
if (element.parentElement === targetEl) return true;
|
|
6270
|
+
if (
|
|
6271
|
+
kind === "paragraph"
|
|
6272
|
+
&& element.classList
|
|
6273
|
+
&& element.classList.contains("abstract")
|
|
6274
|
+
&& element.parentElement
|
|
6275
|
+
&& element.parentElement.tagName === "HEADER"
|
|
6276
|
+
&& element.parentElement.id === "title-block-header"
|
|
6277
|
+
&& element.parentElement.parentElement === targetEl
|
|
6278
|
+
) {
|
|
6279
|
+
return true;
|
|
6280
|
+
}
|
|
6281
|
+
return false;
|
|
6282
|
+
}
|
|
6283
|
+
if (kind === "math") {
|
|
6284
|
+
if (element.parentElement === targetEl) return true;
|
|
6285
|
+
const bodyEl = element.parentElement;
|
|
6286
|
+
const frameEl = bodyEl && bodyEl.parentElement;
|
|
6287
|
+
return Boolean(
|
|
6288
|
+
bodyEl
|
|
6289
|
+
&& bodyEl.classList
|
|
6290
|
+
&& bodyEl.classList.contains("studio-display-equation-body")
|
|
6291
|
+
&& frameEl
|
|
6292
|
+
&& frameEl.classList
|
|
6293
|
+
&& frameEl.classList.contains("studio-display-equation")
|
|
6294
|
+
&& frameEl.parentElement === targetEl
|
|
6295
|
+
);
|
|
6296
|
+
}
|
|
6297
|
+
return false;
|
|
6298
|
+
}
|
|
6299
|
+
|
|
5043
6300
|
function collectPreviewCommentTargetElements(targetEl) {
|
|
5044
6301
|
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";
|
|
6302
|
+
const selector = "h1, h2, h3, h4, h5, h6, p, figure, blockquote, ul, ol, table, div.sourceCode, pre, math[display='block'], .studio-display-equation, .studio-mathjax-fallback-display, .studio-page-break, .abstract, .keywords, .references, .callout-note, .callout-tip, .callout-warning, .callout-important, .callout-caution, .mermaid-container";
|
|
5046
6303
|
return Array.from(targetEl.querySelectorAll(selector)).filter((element) => {
|
|
5047
6304
|
if (!isPreviewCommentTargetElement(element)) return false;
|
|
6305
|
+
if (editorLanguage === "latex" && !isLatexPreviewCommentTargetElement(element, targetEl)) {
|
|
6306
|
+
return false;
|
|
6307
|
+
}
|
|
5048
6308
|
let ancestor = element.parentElement;
|
|
5049
6309
|
while (ancestor && ancestor !== targetEl) {
|
|
5050
6310
|
if (ancestor.classList && ancestor.classList.contains("preview-comment-block")) return false;
|
|
@@ -5061,6 +6321,13 @@
|
|
|
5061
6321
|
function getNormalizedPreviewCommentSourceBlockText(sourceText, sourceBlock) {
|
|
5062
6322
|
if (!sourceBlock) return "";
|
|
5063
6323
|
const blockText = String(sourceText || "").slice(sourceBlock.start, sourceBlock.end);
|
|
6324
|
+
if (editorLanguage === "latex") {
|
|
6325
|
+
return normalizeLatexPreviewBlockText(blockText, sourceBlock.kind);
|
|
6326
|
+
}
|
|
6327
|
+
if (sourceBlock.kind === "page-break") {
|
|
6328
|
+
const match = blockText.trim().match(/^\\(newpage|pagebreak|clearpage)/i);
|
|
6329
|
+
return match ? String(match[1] || "").toLowerCase() : "page-break";
|
|
6330
|
+
}
|
|
5064
6331
|
if (supportsPreviewSelectionCommentsForBlockKind(sourceBlock.kind)) {
|
|
5065
6332
|
return normalizeVisiblePreviewText(buildPreviewSelectionDisplayMap(blockText, sourceBlock.kind).text);
|
|
5066
6333
|
}
|
|
@@ -5084,21 +6351,66 @@
|
|
|
5084
6351
|
function getNormalizedPreviewCommentTargetText(targetEntry) {
|
|
5085
6352
|
if (!targetEntry) return "";
|
|
5086
6353
|
if (typeof targetEntry.normalizedText === "string") return targetEntry.normalizedText;
|
|
5087
|
-
targetEntry.
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
6354
|
+
if (targetEntry.kind === "page-break") {
|
|
6355
|
+
const element = targetEntry.element;
|
|
6356
|
+
targetEntry.normalizedText = String(element && element.getAttribute ? (element.getAttribute("data-page-break-kind") || "page-break") : "page-break").toLowerCase();
|
|
6357
|
+
return targetEntry.normalizedText;
|
|
6358
|
+
}
|
|
6359
|
+
targetEntry.normalizedText = buildNormalizedPreviewSearchText(targetEntry.element);
|
|
5092
6360
|
return targetEntry.normalizedText;
|
|
5093
6361
|
}
|
|
5094
6362
|
|
|
6363
|
+
function isHighConfidencePreviewTextContainmentMatch(leftText, rightText) {
|
|
6364
|
+
const left = String(leftText || "");
|
|
6365
|
+
const right = String(rightText || "");
|
|
6366
|
+
if (!left || !right || left === right) return false;
|
|
6367
|
+
const shorter = left.length <= right.length ? left : right;
|
|
6368
|
+
const longer = left.length <= right.length ? right : left;
|
|
6369
|
+
if (shorter.length < 12) return false;
|
|
6370
|
+
if (!/\s/.test(shorter)) return false;
|
|
6371
|
+
return longer.includes(shorter);
|
|
6372
|
+
}
|
|
6373
|
+
|
|
6374
|
+
function tokenizePreviewComparableText(text) {
|
|
6375
|
+
return normalizeVisiblePreviewText(text)
|
|
6376
|
+
.toLowerCase()
|
|
6377
|
+
.split(/\s+/)
|
|
6378
|
+
.map((token) => token.replace(/^[^0-9A-Za-z\u00C0-\uFFFF]+|[^0-9A-Za-z\u00C0-\uFFFF]+$/g, ""))
|
|
6379
|
+
.filter((token) => token && (token.length >= 4 || /[A-Za-z\u00C0-\uFFFF]/.test(token)));
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
function getHighConfidenceLatexOrderedTokenMatchScore(targetText, desiredText) {
|
|
6383
|
+
if (editorLanguage !== "latex") return -1;
|
|
6384
|
+
const targetTokens = tokenizePreviewComparableText(targetText);
|
|
6385
|
+
const desiredTokens = tokenizePreviewComparableText(desiredText);
|
|
6386
|
+
if (targetTokens.length === 0 || desiredTokens.length < 5) return -1;
|
|
6387
|
+
|
|
6388
|
+
let targetTokenIndex = 0;
|
|
6389
|
+
let matchedCount = 0;
|
|
6390
|
+
for (const token of desiredTokens) {
|
|
6391
|
+
while (targetTokenIndex < targetTokens.length && targetTokens[targetTokenIndex] !== token) {
|
|
6392
|
+
targetTokenIndex += 1;
|
|
6393
|
+
}
|
|
6394
|
+
if (targetTokenIndex >= targetTokens.length) break;
|
|
6395
|
+
matchedCount += 1;
|
|
6396
|
+
targetTokenIndex += 1;
|
|
6397
|
+
}
|
|
6398
|
+
|
|
6399
|
+
const matchRatio = matchedCount / desiredTokens.length;
|
|
6400
|
+
if (matchedCount < 5 || matchRatio < 0.6) return -1;
|
|
6401
|
+
return matchedCount * 1000 + Math.round(matchRatio * 100);
|
|
6402
|
+
}
|
|
6403
|
+
|
|
5095
6404
|
function findMatchingPreviewCommentTargetIndex(sourceText, sourceBlock, targetBlocks, startIndex) {
|
|
5096
6405
|
const desiredKind = sourceBlock ? sourceBlock.kind : "";
|
|
5097
6406
|
const desiredText = getNormalizedPreviewCommentSourceBlockText(sourceText, sourceBlock);
|
|
6407
|
+
const preferredStartIndex = Math.max(0, startIndex || 0);
|
|
5098
6408
|
let fallbackIndex = -1;
|
|
5099
6409
|
let containsIndex = -1;
|
|
6410
|
+
let orderedTokenIndex = -1;
|
|
6411
|
+
let orderedTokenScore = Number.NEGATIVE_INFINITY;
|
|
5100
6412
|
|
|
5101
|
-
for (let i =
|
|
6413
|
+
for (let i = preferredStartIndex; i < targetBlocks.length; i += 1) {
|
|
5102
6414
|
const targetEntry = targetBlocks[i];
|
|
5103
6415
|
if (!targetEntry || targetEntry.kind !== desiredKind) continue;
|
|
5104
6416
|
if (fallbackIndex < 0) fallbackIndex = i;
|
|
@@ -5107,13 +6419,22 @@
|
|
|
5107
6419
|
if (targetText === desiredText) {
|
|
5108
6420
|
return i;
|
|
5109
6421
|
}
|
|
5110
|
-
if (containsIndex < 0 && (targetText
|
|
6422
|
+
if (containsIndex < 0 && isHighConfidencePreviewTextContainmentMatch(targetText, desiredText)) {
|
|
5111
6423
|
containsIndex = i;
|
|
5112
6424
|
}
|
|
6425
|
+
const latexTokenScore = getHighConfidenceLatexOrderedTokenMatchScore(targetText, desiredText);
|
|
6426
|
+
if (latexTokenScore >= 0) {
|
|
6427
|
+
const score = latexTokenScore - (Math.abs(i - preferredStartIndex) * 4);
|
|
6428
|
+
if (score > orderedTokenScore) {
|
|
6429
|
+
orderedTokenScore = score;
|
|
6430
|
+
orderedTokenIndex = i;
|
|
6431
|
+
}
|
|
6432
|
+
}
|
|
5113
6433
|
}
|
|
5114
6434
|
}
|
|
5115
6435
|
|
|
5116
6436
|
if (containsIndex >= 0) return containsIndex;
|
|
6437
|
+
if (orderedTokenIndex >= 0) return orderedTokenIndex;
|
|
5117
6438
|
return fallbackIndex;
|
|
5118
6439
|
}
|
|
5119
6440
|
|
|
@@ -5132,6 +6453,7 @@
|
|
|
5132
6453
|
const lineEnd = Math.max(lineStart, Number(blockEl.dataset.reviewNoteLineEnd) || lineStart);
|
|
5133
6454
|
const summaryBtn = blockEl.querySelector(".preview-comment-summary");
|
|
5134
6455
|
const addBtn = blockEl.querySelector(".preview-comment-add");
|
|
6456
|
+
const jumpBtn = blockEl.querySelector(".preview-comment-jump");
|
|
5135
6457
|
const lineLabel = summarizeReviewNoteAnchor({ lineStart: lineStart, lineEnd: lineEnd }).toLowerCase();
|
|
5136
6458
|
const blockKindLabel = getPreviewCommentBlockKindLabel(blockEl.dataset.previewCommentKind || "paragraph");
|
|
5137
6459
|
const blockKey = getPreviewCommentBlockKey(blockEl);
|
|
@@ -5155,6 +6477,16 @@
|
|
|
5155
6477
|
: "";
|
|
5156
6478
|
addBtn.setAttribute("aria-label", addBtn.title || "Comment");
|
|
5157
6479
|
}
|
|
6480
|
+
|
|
6481
|
+
if (jumpBtn) {
|
|
6482
|
+
jumpBtn.hidden = !hasSelection;
|
|
6483
|
+
jumpBtn.textContent = "Jump";
|
|
6484
|
+
jumpBtn.dataset.previewCommentMode = hasSelection ? "selection" : "";
|
|
6485
|
+
jumpBtn.title = hasSelection
|
|
6486
|
+
? ("Jump to the current preview selection on this " + blockKindLabel + " in the raw editor (" + lineLabel + ").")
|
|
6487
|
+
: "";
|
|
6488
|
+
jumpBtn.setAttribute("aria-label", jumpBtn.title || "Jump");
|
|
6489
|
+
}
|
|
5158
6490
|
}
|
|
5159
6491
|
|
|
5160
6492
|
function updatePreviewCommentBlocksForElement(targetEl) {
|
|
@@ -5167,6 +6499,7 @@
|
|
|
5167
6499
|
|
|
5168
6500
|
function decorateRenderedEditorPreviewComments(targetEl, sourceText) {
|
|
5169
6501
|
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
6502
|
+
splitMixedPreviewParagraphsAroundDisplayMath(targetEl);
|
|
5170
6503
|
const sourceBlocks = scanSourcePreviewCommentBlocks(sourceText);
|
|
5171
6504
|
const targetBlocks = collectPreviewCommentTargetElements(targetEl);
|
|
5172
6505
|
if (sourceBlocks.length === 0 || targetBlocks.length === 0) return;
|
|
@@ -5201,9 +6534,17 @@
|
|
|
5201
6534
|
const addBtn = document.createElement("button");
|
|
5202
6535
|
addBtn.type = "button";
|
|
5203
6536
|
addBtn.className = "preview-comment-add";
|
|
6537
|
+
addBtn.dataset.previewCommentAction = "comment";
|
|
5204
6538
|
addBtn.textContent = "Comment";
|
|
5205
6539
|
controls.appendChild(addBtn);
|
|
5206
6540
|
|
|
6541
|
+
const jumpBtn = document.createElement("button");
|
|
6542
|
+
jumpBtn.type = "button";
|
|
6543
|
+
jumpBtn.className = "preview-comment-jump";
|
|
6544
|
+
jumpBtn.dataset.previewCommentAction = "jump";
|
|
6545
|
+
jumpBtn.textContent = "Jump";
|
|
6546
|
+
controls.appendChild(jumpBtn);
|
|
6547
|
+
|
|
5207
6548
|
originalElement.replaceWith(wrapper);
|
|
5208
6549
|
wrapper.appendChild(controls);
|
|
5209
6550
|
originalElement.classList.add("preview-comment-block-content");
|
|
@@ -5250,6 +6591,32 @@
|
|
|
5250
6591
|
const blockEnd = Math.max(blockStart, Math.min(Number(blockEl.dataset.reviewNoteEnd) || blockStart, source.length));
|
|
5251
6592
|
if (blockEnd <= blockStart) return null;
|
|
5252
6593
|
|
|
6594
|
+
if (kind === "math") {
|
|
6595
|
+
const selectedDisplayText = normalizeVisiblePreviewText(getPreviewMathSearchText(contentEl) || buildNormalizedPreviewSearchText(contentEl));
|
|
6596
|
+
if (!selectedDisplayText) return null;
|
|
6597
|
+
return {
|
|
6598
|
+
selectionStart: blockStart,
|
|
6599
|
+
selectionEnd: blockEnd,
|
|
6600
|
+
lineStart: getLineNumberAtOffset(source, blockStart),
|
|
6601
|
+
lineEnd: getLineNumberAtOffset(source, Math.max(blockStart, blockEnd - 1)),
|
|
6602
|
+
selectedText: source.slice(blockStart, blockEnd),
|
|
6603
|
+
selectedDisplayText,
|
|
6604
|
+
};
|
|
6605
|
+
}
|
|
6606
|
+
|
|
6607
|
+
if (editorLanguage === "latex") {
|
|
6608
|
+
const selectedDisplayText = buildNormalizedPreviewRangeText(range);
|
|
6609
|
+
if (!selectedDisplayText) return null;
|
|
6610
|
+
return {
|
|
6611
|
+
selectionStart: blockStart,
|
|
6612
|
+
selectionEnd: blockEnd,
|
|
6613
|
+
lineStart: getLineNumberAtOffset(source, blockStart),
|
|
6614
|
+
lineEnd: getLineNumberAtOffset(source, Math.max(blockStart, blockEnd - 1)),
|
|
6615
|
+
selectedText: source.slice(blockStart, blockEnd),
|
|
6616
|
+
selectedDisplayText,
|
|
6617
|
+
};
|
|
6618
|
+
}
|
|
6619
|
+
|
|
5253
6620
|
const sourceBlockText = source.slice(blockStart, blockEnd);
|
|
5254
6621
|
const displayMap = buildPreviewSelectionDisplayMap(sourceBlockText, kind);
|
|
5255
6622
|
if (!displayMap.text || !displayMap.charStarts.length || !displayMap.charEnds.length) return null;
|
|
@@ -5257,8 +6624,8 @@
|
|
|
5257
6624
|
const prefixRange = document.createRange();
|
|
5258
6625
|
prefixRange.selectNodeContents(contentEl);
|
|
5259
6626
|
prefixRange.setEnd(range.startContainer, range.startOffset);
|
|
5260
|
-
const prefixText =
|
|
5261
|
-
const selectedDisplayText =
|
|
6627
|
+
const prefixText = buildNormalizedPreviewRangeText(prefixRange);
|
|
6628
|
+
const selectedDisplayText = buildNormalizedPreviewRangeText(range);
|
|
5262
6629
|
if (!selectedDisplayText) return null;
|
|
5263
6630
|
|
|
5264
6631
|
const desiredStart = Math.max(0, Math.min(prefixText.length, displayMap.text.length));
|
|
@@ -5376,7 +6743,7 @@
|
|
|
5376
6743
|
let bestScore = Number.NEGATIVE_INFINITY;
|
|
5377
6744
|
Array.from(targetEl.querySelectorAll(".preview-comment-block")).forEach((blockEl) => {
|
|
5378
6745
|
const contentEl = blockEl.querySelector(".preview-comment-block-content") || blockEl;
|
|
5379
|
-
const blockText =
|
|
6746
|
+
const blockText = buildNormalizedPreviewSearchText(contentEl);
|
|
5380
6747
|
if (!blockText) return;
|
|
5381
6748
|
const matchIndex = blockText.indexOf(selectionText);
|
|
5382
6749
|
if (matchIndex < 0) return;
|
|
@@ -5397,9 +6764,25 @@
|
|
|
5397
6764
|
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5398
6765
|
const range = resolveReviewNoteRange(note, source);
|
|
5399
6766
|
if (!range) return false;
|
|
5400
|
-
const
|
|
6767
|
+
const rangeBlock = findPreviewCommentBlockForRange(targetEl, range);
|
|
6768
|
+
const selectionText = getPreviewNoteNormalizedSelectionText(note);
|
|
6769
|
+
let blockEl = rangeBlock;
|
|
6770
|
+
if (selectionText) {
|
|
6771
|
+
const rangeContentEl = rangeBlock ? (rangeBlock.querySelector(".preview-comment-block-content") || rangeBlock) : null;
|
|
6772
|
+
const rangeText = rangeContentEl ? buildNormalizedPreviewSearchText(rangeContentEl) : "";
|
|
6773
|
+
if (!rangeText || !rangeText.includes(selectionText)) {
|
|
6774
|
+
blockEl = findPreviewCommentBlockForNoteText(targetEl, note) || rangeBlock;
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
5401
6777
|
if (!blockEl) return false;
|
|
5402
6778
|
const contentEl = blockEl.querySelector(".preview-comment-block-content") || blockEl;
|
|
6779
|
+
if (String(blockEl.dataset && blockEl.dataset.previewCommentKind || "") === "math") {
|
|
6780
|
+
if (typeof contentEl.scrollIntoView === "function") {
|
|
6781
|
+
contentEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
6782
|
+
}
|
|
6783
|
+
setPreviewJumpHighlight(targetEl, contentEl, null);
|
|
6784
|
+
return true;
|
|
6785
|
+
}
|
|
5403
6786
|
const inlineHighlightEl = createPreviewJumpInlineHighlight(contentEl, blockEl, note, range);
|
|
5404
6787
|
if (typeof blockEl.scrollIntoView === "function") {
|
|
5405
6788
|
blockEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
@@ -5760,12 +7143,17 @@
|
|
|
5760
7143
|
});
|
|
5761
7144
|
}
|
|
5762
7145
|
|
|
5763
|
-
function
|
|
7146
|
+
function getActivePreviewSelectionAnchorForBlock(blockEl) {
|
|
5764
7147
|
if (!blockEl) return null;
|
|
5765
7148
|
const blockKey = getPreviewCommentBlockKey(blockEl);
|
|
5766
|
-
|
|
7149
|
+
return activePreviewCommentSelection && activePreviewCommentSelection.blockKey === blockKey
|
|
5767
7150
|
? activePreviewCommentSelection
|
|
5768
7151
|
: null;
|
|
7152
|
+
}
|
|
7153
|
+
|
|
7154
|
+
function addReviewNoteFromPreviewSelection(blockEl) {
|
|
7155
|
+
if (!blockEl) return null;
|
|
7156
|
+
const anchor = getActivePreviewSelectionAnchorForBlock(blockEl);
|
|
5769
7157
|
if (!anchor) {
|
|
5770
7158
|
setStatus("Select some preview text within a single block first.", "warning");
|
|
5771
7159
|
return null;
|
|
@@ -5840,14 +7228,13 @@
|
|
|
5840
7228
|
});
|
|
5841
7229
|
}
|
|
5842
7230
|
|
|
5843
|
-
function
|
|
5844
|
-
|
|
5845
|
-
if (!note) return;
|
|
7231
|
+
function jumpToReviewAnchor(anchor, options) {
|
|
7232
|
+
if (!anchor) return false;
|
|
5846
7233
|
const current = String(sourceTextEl.value || "");
|
|
5847
|
-
const range = resolveReviewNoteRange(
|
|
7234
|
+
const range = resolveReviewNoteRange(anchor, current);
|
|
5848
7235
|
if (!range) {
|
|
5849
|
-
setStatus("Could not find the anchored location
|
|
5850
|
-
return;
|
|
7236
|
+
setStatus((options && options.notFoundStatusMessage) || "Could not find the anchored location.", "warning");
|
|
7237
|
+
return false;
|
|
5851
7238
|
}
|
|
5852
7239
|
suppressEditorSelectionComment = true;
|
|
5853
7240
|
suppressedEditorSelectionStart = range.start;
|
|
@@ -5862,9 +7249,47 @@
|
|
|
5862
7249
|
: (cb) => window.setTimeout(cb, 16);
|
|
5863
7250
|
schedule(() => {
|
|
5864
7251
|
scrollEditorRangeIntoView(range);
|
|
5865
|
-
|
|
7252
|
+
if (options && typeof options.afterJump === "function") {
|
|
7253
|
+
options.afterJump(range);
|
|
7254
|
+
}
|
|
5866
7255
|
updateEditorSelectionCommentUi();
|
|
5867
7256
|
});
|
|
7257
|
+
if (!options || options.status !== false) {
|
|
7258
|
+
setStatus((options && options.statusMessage) || "Jumped to anchored location in the editor.", "success");
|
|
7259
|
+
}
|
|
7260
|
+
return true;
|
|
7261
|
+
}
|
|
7262
|
+
|
|
7263
|
+
function jumpToPreviewSelection(blockEl) {
|
|
7264
|
+
if (!blockEl) return false;
|
|
7265
|
+
const anchor = getActivePreviewSelectionAnchorForBlock(blockEl);
|
|
7266
|
+
if (!anchor) {
|
|
7267
|
+
setStatus("Select some preview text within a single block first.", "warning");
|
|
7268
|
+
return false;
|
|
7269
|
+
}
|
|
7270
|
+
const jumped = jumpToReviewAnchor(anchor, {
|
|
7271
|
+
statusMessage: "Jumped to preview selection in the raw editor.",
|
|
7272
|
+
});
|
|
7273
|
+
if (jumped) {
|
|
7274
|
+
const selection = typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
7275
|
+
if (selection && typeof selection.removeAllRanges === "function") {
|
|
7276
|
+
selection.removeAllRanges();
|
|
7277
|
+
}
|
|
7278
|
+
clearPreviewCommentSelection();
|
|
7279
|
+
}
|
|
7280
|
+
return jumped;
|
|
7281
|
+
}
|
|
7282
|
+
|
|
7283
|
+
function jumpToReviewNote(noteId) {
|
|
7284
|
+
const note = reviewNotes.find((entry) => entry && entry.id === noteId);
|
|
7285
|
+
if (!note) return;
|
|
7286
|
+
jumpToReviewAnchor(note, {
|
|
7287
|
+
status: false,
|
|
7288
|
+
notFoundStatusMessage: "Could not find the anchored location for this comment.",
|
|
7289
|
+
afterJump: () => {
|
|
7290
|
+
revealReviewNoteInPreview(note);
|
|
7291
|
+
},
|
|
7292
|
+
});
|
|
5868
7293
|
}
|
|
5869
7294
|
|
|
5870
7295
|
function deleteReviewNote(noteId) {
|
|
@@ -7797,14 +9222,14 @@
|
|
|
7797
9222
|
|
|
7798
9223
|
function handlePreviewCommentActionMouseDown(event) {
|
|
7799
9224
|
const target = event.target;
|
|
7800
|
-
const actionBtn = target instanceof Element ? target.closest(".preview-comment-add, .preview-comment-summary") : null;
|
|
9225
|
+
const actionBtn = target instanceof Element ? target.closest(".preview-comment-add, .preview-comment-jump, .preview-comment-summary") : null;
|
|
7801
9226
|
if (!actionBtn) return;
|
|
7802
9227
|
event.preventDefault();
|
|
7803
9228
|
}
|
|
7804
9229
|
|
|
7805
9230
|
function handlePreviewCommentActionClick(event) {
|
|
7806
9231
|
const target = event.target;
|
|
7807
|
-
const actionBtn = target instanceof Element ? target.closest(".preview-comment-add, .preview-comment-summary") : null;
|
|
9232
|
+
const actionBtn = target instanceof Element ? target.closest(".preview-comment-add, .preview-comment-jump, .preview-comment-summary") : null;
|
|
7808
9233
|
if (!actionBtn) return;
|
|
7809
9234
|
const blockEl = actionBtn.closest(".preview-comment-block");
|
|
7810
9235
|
if (!blockEl) return;
|
|
@@ -7812,6 +9237,11 @@
|
|
|
7812
9237
|
event.stopPropagation();
|
|
7813
9238
|
const mode = String(actionBtn.dataset && actionBtn.dataset.previewCommentMode ? actionBtn.dataset.previewCommentMode : "");
|
|
7814
9239
|
if (!mode || !mode.startsWith("selection")) return;
|
|
9240
|
+
const action = String(actionBtn.dataset && actionBtn.dataset.previewCommentAction ? actionBtn.dataset.previewCommentAction : "comment");
|
|
9241
|
+
if (action === "jump") {
|
|
9242
|
+
jumpToPreviewSelection(blockEl);
|
|
9243
|
+
return;
|
|
9244
|
+
}
|
|
7815
9245
|
addReviewNoteFromPreviewSelection(blockEl);
|
|
7816
9246
|
}
|
|
7817
9247
|
|