pi-studio 0.5.49 → 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 +10 -0
- package/README.md +1 -1
- package/client/studio-client.js +854 -26
- package/client/studio.css +9 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.50] — 2026-04-09
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Editor-preview comments now also work for minimal structured LaTeX preview content, including headings, prose paragraphs, references sections, and whole display equations.
|
|
11
|
+
- Preview-side selection affordances now include a transient **Jump** action alongside **Comment**, so you can reveal the corresponding raw-editor span without creating a local comment first.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- LaTeX preview comment mapping now follows Studio's rendered structure more closely, including title-block abstracts, loose prose after decorated display equations, bibliography/reference sections, and paragraph alignment around figures and rendered citation/reference text.
|
|
15
|
+
- Preview comment controls for display equations now anchor to the outer rendered equation frame, improving visibility and making preview jump targeting more reliable.
|
|
16
|
+
|
|
7
17
|
## [0.5.49] — 2026-04-09
|
|
8
18
|
|
|
9
19
|
### Fixed
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
|
|
|
18
18
|
- Supports one canonical full Studio view per Pi session, plus additional editor-only companion views when you just want extra editing/preview surfaces
|
|
19
19
|
- Runs editor text directly, or asks for structured critique (auto/writing/code focus)
|
|
20
20
|
- Includes a local persistent scratchpad for quick notes you want to keep out of the main editor until you're ready to copy or insert them
|
|
21
|
-
- Includes local comments anchored to selections/lines, shown in a docked **Comments** rail, with **Comment** actions from raw-editor selections plus editor-preview selections for Markdown and code/text/diff previews, alongside optional inline `[an: ...]` toggles when you want comments reflected in the document text
|
|
21
|
+
- Includes local comments anchored to selections/lines, shown in a docked **Comments** rail, with **Comment** actions from raw-editor selections plus editor-preview selections for Markdown, LaTeX, and code/text/diff previews, alongside a transient preview **Jump** action and optional inline `[an: ...]` toggles when you want comments reflected in the document text
|
|
22
22
|
- Browses response history (`Prev/Next/Last`) and loads either:
|
|
23
23
|
- response text
|
|
24
24
|
- critique notes/full critique
|
package/client/studio-client.js
CHANGED
|
@@ -1904,6 +1904,7 @@
|
|
|
1904
1904
|
|
|
1905
1905
|
fallbackTargets.forEach((entry) => {
|
|
1906
1906
|
entry.renderTarget.classList.add("studio-mathjax-fallback");
|
|
1907
|
+
entry.renderTarget.setAttribute("data-tex-source", entry.tex);
|
|
1907
1908
|
if (entry.displayMode) {
|
|
1908
1909
|
entry.renderTarget.classList.add("studio-mathjax-fallback-display");
|
|
1909
1910
|
entry.renderTarget.textContent = "\\[\n" + entry.tex + "\n\\]";
|
|
@@ -3860,7 +3861,8 @@
|
|
|
3860
3861
|
+ ">"
|
|
3861
3862
|
+ "<div class='preview-comment-controls'>"
|
|
3862
3863
|
+ "<button type='button' class='preview-comment-summary' hidden></button>"
|
|
3863
|
-
+ "<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>"
|
|
3864
3866
|
+ "</div>"
|
|
3865
3867
|
+ "<div class='preview-comment-block-content preview-code-line-content'>" + lineHtml + "</div>"
|
|
3866
3868
|
+ "</div>",
|
|
@@ -4447,10 +4449,9 @@
|
|
|
4447
4449
|
}
|
|
4448
4450
|
|
|
4449
4451
|
function supportsPreviewCommentsForCurrentEditor() {
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
return editorLanguage === "markdown" || supportsCodePreviewCommentsForCurrentEditor();
|
|
4452
|
+
return editorLanguage === "markdown"
|
|
4453
|
+
|| editorLanguage === "latex"
|
|
4454
|
+
|| supportsCodePreviewCommentsForCurrentEditor();
|
|
4454
4455
|
}
|
|
4455
4456
|
|
|
4456
4457
|
function getPreviewCommentBlockKindLabel(kind) {
|
|
@@ -4458,6 +4459,8 @@
|
|
|
4458
4459
|
if (kind === "blockquote") return "quote block";
|
|
4459
4460
|
if (kind === "list") return "list";
|
|
4460
4461
|
if (kind === "math") return "equation";
|
|
4462
|
+
if (kind === "figure") return "figure";
|
|
4463
|
+
if (kind === "algorithm") return "algorithm block";
|
|
4461
4464
|
if (kind === "page-break") return "page break";
|
|
4462
4465
|
if (kind === "code") return "code block";
|
|
4463
4466
|
if (kind === "table") return "table";
|
|
@@ -4670,6 +4673,389 @@
|
|
|
4670
4673
|
bodyText: String(range.bodyText || ""),
|
|
4671
4674
|
};
|
|
4672
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
|
+
|
|
4673
5059
|
function normalizePreviewComparableCharacter(character) {
|
|
4674
5060
|
switch (String(character || "")) {
|
|
4675
5061
|
case "\u2018":
|
|
@@ -5102,12 +5488,12 @@
|
|
|
5102
5488
|
|
|
5103
5489
|
function getPreviewMathSearchText(element) {
|
|
5104
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
|
+
}
|
|
5105
5495
|
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
5106
5496
|
if (tag === "MATH") {
|
|
5107
|
-
const texSource = element.getAttribute("data-tex-source");
|
|
5108
|
-
if (texSource && texSource.trim()) {
|
|
5109
|
-
return texSource;
|
|
5110
|
-
}
|
|
5111
5497
|
return typeof element.textContent === "string" ? element.textContent : "";
|
|
5112
5498
|
}
|
|
5113
5499
|
if (element.classList && element.classList.contains("math") && (element.classList.contains("inline") || element.classList.contains("display"))) {
|
|
@@ -5116,6 +5502,16 @@
|
|
|
5116
5502
|
element.classList.contains("display"),
|
|
5117
5503
|
);
|
|
5118
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
|
+
}
|
|
5119
5515
|
return null;
|
|
5120
5516
|
}
|
|
5121
5517
|
|
|
@@ -5277,8 +5673,9 @@
|
|
|
5277
5673
|
}
|
|
5278
5674
|
|
|
5279
5675
|
function scanSourcePreviewCommentBlocks(markdown) {
|
|
5280
|
-
if (editorLanguage
|
|
5281
|
-
return
|
|
5676
|
+
if (editorLanguage === "markdown") return scanMarkdownPreviewCommentBlocks(markdown);
|
|
5677
|
+
if (editorLanguage === "latex") return scanLatexPreviewCommentBlocks(markdown);
|
|
5678
|
+
return [];
|
|
5282
5679
|
}
|
|
5283
5680
|
|
|
5284
5681
|
function scanMarkdownPreviewCommentBlocks(markdown) {
|
|
@@ -5497,8 +5894,199 @@
|
|
|
5497
5894
|
return expandSourcePreviewCommentBlocksByDisplayMath(source, blocks);
|
|
5498
5895
|
}
|
|
5499
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
|
+
|
|
6080
|
+
return blocks;
|
|
6081
|
+
}
|
|
6082
|
+
|
|
5500
6083
|
function isPreviewDisplayMathElement(element) {
|
|
5501
|
-
return Boolean(
|
|
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
|
+
);
|
|
5502
6090
|
}
|
|
5503
6091
|
|
|
5504
6092
|
function previewNodesHaveVisibleContent(nodes) {
|
|
@@ -5511,8 +6099,63 @@
|
|
|
5511
6099
|
});
|
|
5512
6100
|
}
|
|
5513
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
|
+
|
|
5514
6154
|
function splitMixedPreviewParagraphsAroundDisplayMath(targetEl) {
|
|
5515
6155
|
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
6156
|
+
if (editorLanguage === "latex") {
|
|
6157
|
+
wrapLoosePreviewInlineRunsAsParagraphs(targetEl);
|
|
6158
|
+
}
|
|
5516
6159
|
Array.from(targetEl.querySelectorAll("p")).forEach((paragraphEl) => {
|
|
5517
6160
|
if (!(paragraphEl instanceof Element) || !paragraphEl.parentNode) return;
|
|
5518
6161
|
if (paragraphEl.closest && paragraphEl.closest(".preview-comment-block")) return;
|
|
@@ -5574,6 +6217,20 @@
|
|
|
5574
6217
|
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
5575
6218
|
if (/^H[1-6]$/.test(tag)) return "heading";
|
|
5576
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
|
+
}
|
|
5577
6234
|
if (tag === "BLOCKQUOTE") return "blockquote";
|
|
5578
6235
|
if (tag === "UL" || tag === "OL") return "list";
|
|
5579
6236
|
if (tag === "TABLE") return "table";
|
|
@@ -5605,11 +6262,49 @@
|
|
|
5605
6262
|
return Boolean(getPreviewCommentTargetKind(element));
|
|
5606
6263
|
}
|
|
5607
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
|
+
|
|
5608
6300
|
function collectPreviewCommentTargetElements(targetEl) {
|
|
5609
6301
|
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return [];
|
|
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";
|
|
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";
|
|
5611
6303
|
return Array.from(targetEl.querySelectorAll(selector)).filter((element) => {
|
|
5612
6304
|
if (!isPreviewCommentTargetElement(element)) return false;
|
|
6305
|
+
if (editorLanguage === "latex" && !isLatexPreviewCommentTargetElement(element, targetEl)) {
|
|
6306
|
+
return false;
|
|
6307
|
+
}
|
|
5613
6308
|
let ancestor = element.parentElement;
|
|
5614
6309
|
while (ancestor && ancestor !== targetEl) {
|
|
5615
6310
|
if (ancestor.classList && ancestor.classList.contains("preview-comment-block")) return false;
|
|
@@ -5626,6 +6321,9 @@
|
|
|
5626
6321
|
function getNormalizedPreviewCommentSourceBlockText(sourceText, sourceBlock) {
|
|
5627
6322
|
if (!sourceBlock) return "";
|
|
5628
6323
|
const blockText = String(sourceText || "").slice(sourceBlock.start, sourceBlock.end);
|
|
6324
|
+
if (editorLanguage === "latex") {
|
|
6325
|
+
return normalizeLatexPreviewBlockText(blockText, sourceBlock.kind);
|
|
6326
|
+
}
|
|
5629
6327
|
if (sourceBlock.kind === "page-break") {
|
|
5630
6328
|
const match = blockText.trim().match(/^\\(newpage|pagebreak|clearpage)/i);
|
|
5631
6329
|
return match ? String(match[1] || "").toLowerCase() : "page-break";
|
|
@@ -5673,13 +6371,46 @@
|
|
|
5673
6371
|
return longer.includes(shorter);
|
|
5674
6372
|
}
|
|
5675
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
|
+
|
|
5676
6404
|
function findMatchingPreviewCommentTargetIndex(sourceText, sourceBlock, targetBlocks, startIndex) {
|
|
5677
6405
|
const desiredKind = sourceBlock ? sourceBlock.kind : "";
|
|
5678
6406
|
const desiredText = getNormalizedPreviewCommentSourceBlockText(sourceText, sourceBlock);
|
|
6407
|
+
const preferredStartIndex = Math.max(0, startIndex || 0);
|
|
5679
6408
|
let fallbackIndex = -1;
|
|
5680
6409
|
let containsIndex = -1;
|
|
6410
|
+
let orderedTokenIndex = -1;
|
|
6411
|
+
let orderedTokenScore = Number.NEGATIVE_INFINITY;
|
|
5681
6412
|
|
|
5682
|
-
for (let i =
|
|
6413
|
+
for (let i = preferredStartIndex; i < targetBlocks.length; i += 1) {
|
|
5683
6414
|
const targetEntry = targetBlocks[i];
|
|
5684
6415
|
if (!targetEntry || targetEntry.kind !== desiredKind) continue;
|
|
5685
6416
|
if (fallbackIndex < 0) fallbackIndex = i;
|
|
@@ -5691,10 +6422,19 @@
|
|
|
5691
6422
|
if (containsIndex < 0 && isHighConfidencePreviewTextContainmentMatch(targetText, desiredText)) {
|
|
5692
6423
|
containsIndex = i;
|
|
5693
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
|
+
}
|
|
5694
6433
|
}
|
|
5695
6434
|
}
|
|
5696
6435
|
|
|
5697
6436
|
if (containsIndex >= 0) return containsIndex;
|
|
6437
|
+
if (orderedTokenIndex >= 0) return orderedTokenIndex;
|
|
5698
6438
|
return fallbackIndex;
|
|
5699
6439
|
}
|
|
5700
6440
|
|
|
@@ -5713,6 +6453,7 @@
|
|
|
5713
6453
|
const lineEnd = Math.max(lineStart, Number(blockEl.dataset.reviewNoteLineEnd) || lineStart);
|
|
5714
6454
|
const summaryBtn = blockEl.querySelector(".preview-comment-summary");
|
|
5715
6455
|
const addBtn = blockEl.querySelector(".preview-comment-add");
|
|
6456
|
+
const jumpBtn = blockEl.querySelector(".preview-comment-jump");
|
|
5716
6457
|
const lineLabel = summarizeReviewNoteAnchor({ lineStart: lineStart, lineEnd: lineEnd }).toLowerCase();
|
|
5717
6458
|
const blockKindLabel = getPreviewCommentBlockKindLabel(blockEl.dataset.previewCommentKind || "paragraph");
|
|
5718
6459
|
const blockKey = getPreviewCommentBlockKey(blockEl);
|
|
@@ -5736,6 +6477,16 @@
|
|
|
5736
6477
|
: "";
|
|
5737
6478
|
addBtn.setAttribute("aria-label", addBtn.title || "Comment");
|
|
5738
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
|
+
}
|
|
5739
6490
|
}
|
|
5740
6491
|
|
|
5741
6492
|
function updatePreviewCommentBlocksForElement(targetEl) {
|
|
@@ -5783,9 +6534,17 @@
|
|
|
5783
6534
|
const addBtn = document.createElement("button");
|
|
5784
6535
|
addBtn.type = "button";
|
|
5785
6536
|
addBtn.className = "preview-comment-add";
|
|
6537
|
+
addBtn.dataset.previewCommentAction = "comment";
|
|
5786
6538
|
addBtn.textContent = "Comment";
|
|
5787
6539
|
controls.appendChild(addBtn);
|
|
5788
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
|
+
|
|
5789
6548
|
originalElement.replaceWith(wrapper);
|
|
5790
6549
|
wrapper.appendChild(controls);
|
|
5791
6550
|
originalElement.classList.add("preview-comment-block-content");
|
|
@@ -5845,6 +6604,19 @@
|
|
|
5845
6604
|
};
|
|
5846
6605
|
}
|
|
5847
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
|
+
|
|
5848
6620
|
const sourceBlockText = source.slice(blockStart, blockEnd);
|
|
5849
6621
|
const displayMap = buildPreviewSelectionDisplayMap(sourceBlockText, kind);
|
|
5850
6622
|
if (!displayMap.text || !displayMap.charStarts.length || !displayMap.charEnds.length) return null;
|
|
@@ -5992,7 +6764,16 @@
|
|
|
5992
6764
|
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5993
6765
|
const range = resolveReviewNoteRange(note, source);
|
|
5994
6766
|
if (!range) return false;
|
|
5995
|
-
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
|
+
}
|
|
5996
6777
|
if (!blockEl) return false;
|
|
5997
6778
|
const contentEl = blockEl.querySelector(".preview-comment-block-content") || blockEl;
|
|
5998
6779
|
if (String(blockEl.dataset && blockEl.dataset.previewCommentKind || "") === "math") {
|
|
@@ -6362,12 +7143,17 @@
|
|
|
6362
7143
|
});
|
|
6363
7144
|
}
|
|
6364
7145
|
|
|
6365
|
-
function
|
|
7146
|
+
function getActivePreviewSelectionAnchorForBlock(blockEl) {
|
|
6366
7147
|
if (!blockEl) return null;
|
|
6367
7148
|
const blockKey = getPreviewCommentBlockKey(blockEl);
|
|
6368
|
-
|
|
7149
|
+
return activePreviewCommentSelection && activePreviewCommentSelection.blockKey === blockKey
|
|
6369
7150
|
? activePreviewCommentSelection
|
|
6370
7151
|
: null;
|
|
7152
|
+
}
|
|
7153
|
+
|
|
7154
|
+
function addReviewNoteFromPreviewSelection(blockEl) {
|
|
7155
|
+
if (!blockEl) return null;
|
|
7156
|
+
const anchor = getActivePreviewSelectionAnchorForBlock(blockEl);
|
|
6371
7157
|
if (!anchor) {
|
|
6372
7158
|
setStatus("Select some preview text within a single block first.", "warning");
|
|
6373
7159
|
return null;
|
|
@@ -6442,14 +7228,13 @@
|
|
|
6442
7228
|
});
|
|
6443
7229
|
}
|
|
6444
7230
|
|
|
6445
|
-
function
|
|
6446
|
-
|
|
6447
|
-
if (!note) return;
|
|
7231
|
+
function jumpToReviewAnchor(anchor, options) {
|
|
7232
|
+
if (!anchor) return false;
|
|
6448
7233
|
const current = String(sourceTextEl.value || "");
|
|
6449
|
-
const range = resolveReviewNoteRange(
|
|
7234
|
+
const range = resolveReviewNoteRange(anchor, current);
|
|
6450
7235
|
if (!range) {
|
|
6451
|
-
setStatus("Could not find the anchored location
|
|
6452
|
-
return;
|
|
7236
|
+
setStatus((options && options.notFoundStatusMessage) || "Could not find the anchored location.", "warning");
|
|
7237
|
+
return false;
|
|
6453
7238
|
}
|
|
6454
7239
|
suppressEditorSelectionComment = true;
|
|
6455
7240
|
suppressedEditorSelectionStart = range.start;
|
|
@@ -6464,9 +7249,47 @@
|
|
|
6464
7249
|
: (cb) => window.setTimeout(cb, 16);
|
|
6465
7250
|
schedule(() => {
|
|
6466
7251
|
scrollEditorRangeIntoView(range);
|
|
6467
|
-
|
|
7252
|
+
if (options && typeof options.afterJump === "function") {
|
|
7253
|
+
options.afterJump(range);
|
|
7254
|
+
}
|
|
6468
7255
|
updateEditorSelectionCommentUi();
|
|
6469
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
|
+
});
|
|
6470
7293
|
}
|
|
6471
7294
|
|
|
6472
7295
|
function deleteReviewNote(noteId) {
|
|
@@ -8399,14 +9222,14 @@
|
|
|
8399
9222
|
|
|
8400
9223
|
function handlePreviewCommentActionMouseDown(event) {
|
|
8401
9224
|
const target = event.target;
|
|
8402
|
-
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;
|
|
8403
9226
|
if (!actionBtn) return;
|
|
8404
9227
|
event.preventDefault();
|
|
8405
9228
|
}
|
|
8406
9229
|
|
|
8407
9230
|
function handlePreviewCommentActionClick(event) {
|
|
8408
9231
|
const target = event.target;
|
|
8409
|
-
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;
|
|
8410
9233
|
if (!actionBtn) return;
|
|
8411
9234
|
const blockEl = actionBtn.closest(".preview-comment-block");
|
|
8412
9235
|
if (!blockEl) return;
|
|
@@ -8414,6 +9237,11 @@
|
|
|
8414
9237
|
event.stopPropagation();
|
|
8415
9238
|
const mode = String(actionBtn.dataset && actionBtn.dataset.previewCommentMode ? actionBtn.dataset.previewCommentMode : "");
|
|
8416
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
|
+
}
|
|
8417
9245
|
addReviewNoteFromPreviewSelection(blockEl);
|
|
8418
9246
|
}
|
|
8419
9247
|
|
package/client/studio.css
CHANGED
|
@@ -920,18 +920,19 @@
|
|
|
920
920
|
position: absolute;
|
|
921
921
|
top: 0;
|
|
922
922
|
right: 0;
|
|
923
|
-
z-index:
|
|
923
|
+
z-index: 6;
|
|
924
924
|
display: inline-flex;
|
|
925
925
|
align-items: center;
|
|
926
926
|
gap: 8px;
|
|
927
|
-
transform: translateY(-0.
|
|
927
|
+
transform: translateY(-0.46rem);
|
|
928
928
|
}
|
|
929
929
|
|
|
930
930
|
.preview-comment-summary {
|
|
931
931
|
display: none !important;
|
|
932
932
|
}
|
|
933
933
|
|
|
934
|
-
.preview-comment-add
|
|
934
|
+
.preview-comment-add,
|
|
935
|
+
.preview-comment-jump {
|
|
935
936
|
display: inline-flex;
|
|
936
937
|
align-items: center;
|
|
937
938
|
justify-content: center;
|
|
@@ -950,14 +951,17 @@
|
|
|
950
951
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
|
951
952
|
}
|
|
952
953
|
|
|
953
|
-
.preview-comment-block.has-selection .preview-comment-add
|
|
954
|
+
.preview-comment-block.has-selection .preview-comment-add,
|
|
955
|
+
.preview-comment-block.has-selection .preview-comment-jump {
|
|
954
956
|
opacity: 1;
|
|
955
957
|
pointer-events: auto;
|
|
956
958
|
transform: translateY(0);
|
|
957
959
|
}
|
|
958
960
|
|
|
959
961
|
.preview-comment-add:hover,
|
|
960
|
-
.preview-comment-add:focus-visible
|
|
962
|
+
.preview-comment-add:focus-visible,
|
|
963
|
+
.preview-comment-jump:hover,
|
|
964
|
+
.preview-comment-jump:focus-visible {
|
|
961
965
|
background: var(--accent-soft-strong);
|
|
962
966
|
color: var(--accent);
|
|
963
967
|
border-color: var(--accent);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.50",
|
|
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",
|