@researai/deepscientist 1.5.15 → 1.5.16
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/README.md +336 -98
- package/bin/ds.js +691 -91
- package/docs/en/00_QUICK_START.md +36 -15
- package/docs/en/01_SETTINGS_REFERENCE.md +33 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +11 -5
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +25 -8
- package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/README.md +18 -0
- package/docs/zh/00_QUICK_START.md +36 -15
- package/docs/zh/01_SETTINGS_REFERENCE.md +33 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +11 -5
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +25 -8
- package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/README.md +18 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +6 -0
- package/src/deepscientist/artifact/service.py +647 -22
- package/src/deepscientist/bash_exec/service.py +234 -9
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +232 -0
- package/src/deepscientist/config/models.py +2 -1
- package/src/deepscientist/config/service.py +31 -9
- package/src/deepscientist/daemon/api/handlers.py +125 -6
- package/src/deepscientist/daemon/api/router.py +4 -0
- package/src/deepscientist/daemon/app.py +715 -98
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +129 -0
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +39 -0
- package/src/deepscientist/prompts/builder.py +255 -32
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +295 -43
- package/src/deepscientist/quest/stage_views.py +6 -1
- package/src/deepscientist/runners/codex.py +86 -31
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +12 -1
- package/src/prompts/system.md +10 -5
- package/src/prompts/system_copilot.md +43 -0
- package/src/skills/analysis-campaign/SKILL.md +1 -0
- package/src/skills/baseline/SKILL.md +8 -0
- package/src/skills/decision/SKILL.md +8 -0
- package/src/skills/experiment/SKILL.md +8 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +1 -0
- package/src/skills/idea/SKILL.md +1 -0
- package/src/skills/intake-audit/SKILL.md +8 -0
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1 -0
- package/src/skills/rebuttal/SKILL.md +1 -0
- package/src/skills/review/SKILL.md +1 -0
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +1 -0
- package/src/tui/dist/app/AppContainer.js +19 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-COFACy7V.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-DnSm0GZn.js +1 -0
- package/src/ui/dist/assets/CliPlugin-CvwCmDQ5.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-cOqSa0xq.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-itb0tltR.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DqKkiCI6.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-DVgNHBCS.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-DxL2ezFG.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-B_RQm1YZ.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-tHqlXY3n.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-ClMbq5Yu.js +14 -0
- package/src/ui/dist/assets/LabPlugin-L_SuE8ow.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-B495DTXC.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DG28-61B.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BiOGT-Kj.js +13 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-C-4Kt1p9.js +81 -0
- package/src/ui/dist/assets/NotebookEditor-CVsj8h_T.js +361 -0
- package/src/ui/dist/assets/PdfLoader-CASDQmxJ.js +16 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-BFhwoKsY.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-DcOzU9vd.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CHj7M58O.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-CB4DYfWO.js +54 -0
- package/src/ui/dist/assets/VNCViewer-CjlbyCB3.js +11 -0
- package/src/ui/dist/assets/bot-CFkZY-JP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-Dq5ofbht.js +6 -0
- package/src/ui/dist/assets/code-DLC6G24T.js +6 -0
- package/src/ui/dist/assets/file-content-Dv4LoZec.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-Denq-lC3.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-Cu4Qln7Y.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-BUh6G52n.js +6 -0
- package/src/ui/dist/assets/image-B9HUUddG.js +6 -0
- package/src/ui/dist/assets/index-B2B1sg-M.js +1 -0
- package/src/ui/dist/assets/index-Cgla8biy.css +33 -0
- package/src/ui/dist/assets/index-DRyx7vAc.js +1 -0
- package/src/ui/dist/assets/index-Gbl53BNp.js +2496 -0
- package/src/ui/dist/assets/index-wQ7RIIRd.js +11 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-ZtnHFCAi.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-DL6h35vr.js +1 -0
- package/src/ui/dist/assets/project-sync-CsX08Qno.js +1 -0
- package/src/ui/dist/assets/select-DvmXt1yY.js +11 -0
- package/src/ui/dist/assets/sigma-7jpXazui.js +6 -0
- package/src/ui/dist/assets/trash-xA7kFt8i.js +11 -0
- package/src/ui/dist/assets/useCliAccess-DsMwDjOp.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-CwMn-iqb.js +11 -0
- package/src/ui/dist/assets/zoom-out-R-GWEhzS.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DDjbFnbt.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
- package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
- package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
- package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
- package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
- package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
- package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
- package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
- package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
- package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
- package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
- package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
- package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
- package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
- package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
- package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
- package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
|
@@ -1,1792 +0,0 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/monaco-LExaAN3Y.js","assets/index-D_E4281X.js","assets/index-G7AcWcMu.css","assets/yjs-DncrqiZ8.js","assets/function-B5QZkkHC.js","assets/awareness-C0NPR2Dj.js"])))=>i.map(i=>d[i]);
|
|
2
|
-
import { w as createLucideIcon, z as useAuthStore, u as useI18n, a as useWorkspaceSurfaceStore, r as reactExports, f as useFileTreeStore, x as supportsSocketIo, W as toFilesResourcePath, Y as checkProjectAccess, Z as listFiles, V as getFileContent, _ as __vitePreload, U as updateFileContent, $ as compileLatex, a0 as listLatexBuilds, j as jsxRuntimeExports, b as cn, k as FileText, a1 as Link2, L as LoaderCircle, S as Save, a2 as Play, T as TriangleAlert, a3 as Download, a4 as getLatexBuild, a5 as getLatexBuildPdfBlob, a6 as getLatexBuildLogText, h as dynamic } from './index-D_E4281X.js';
|
|
3
|
-
import { P as ProjectSyncClient } from './project-sync-C_ygLlVU.js';
|
|
4
|
-
import { c as configureMonacoLoader } from './monaco-LExaAN3Y.js';
|
|
5
|
-
import { P as PAGE_DIMENSIONS, a as PdfLoader, b as PDF_CMAP_URL, c as PDF_WORKER_SRC, Z as ZOOM_LEVELS, d as PdfHighlighter } from './PdfLoader-BfOHw8Zw.js';
|
|
6
|
-
import { Z as ZoomOut, a as ZoomIn } from './zoom-out-BgVMmOW4.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @license lucide-react v0.511.0 - ISC
|
|
10
|
-
*
|
|
11
|
-
* This source code is licensed under the ISC license.
|
|
12
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const __iconNode = [
|
|
17
|
-
["circle", { cx: "12", cy: "12", r: "4", key: "4exip2" }],
|
|
18
|
-
["path", { d: "M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8", key: "7n84p3" }]
|
|
19
|
-
];
|
|
20
|
-
const AtSign = createLucideIcon("at-sign", __iconNode);
|
|
21
|
-
|
|
22
|
-
const MonacoEditor = dynamic(() => __vitePreload(() => import('./monaco-LExaAN3Y.js').then(n => n.i),true?__vite__mapDeps([0,1,2]):void 0), { });
|
|
23
|
-
configureMonacoLoader();
|
|
24
|
-
function useIsDarkMode() {
|
|
25
|
-
const [isDark, setIsDark] = reactExports.useState(() => {
|
|
26
|
-
if (typeof document === "undefined") return false;
|
|
27
|
-
return document.documentElement.classList.contains("dark");
|
|
28
|
-
});
|
|
29
|
-
reactExports.useEffect(() => {
|
|
30
|
-
const el = document.documentElement;
|
|
31
|
-
const observer = new MutationObserver(() => {
|
|
32
|
-
setIsDark(el.classList.contains("dark"));
|
|
33
|
-
});
|
|
34
|
-
observer.observe(el, { attributes: true, attributeFilter: ["class"] });
|
|
35
|
-
return () => observer.disconnect();
|
|
36
|
-
}, []);
|
|
37
|
-
return isDark;
|
|
38
|
-
}
|
|
39
|
-
function getLatexIssueIdentity(issue) {
|
|
40
|
-
return [
|
|
41
|
-
issue.resourcePath || issue.resourceName || "",
|
|
42
|
-
issue.line || 0,
|
|
43
|
-
issue.severity,
|
|
44
|
-
issue.message
|
|
45
|
-
].join("::");
|
|
46
|
-
}
|
|
47
|
-
const normalizeBuildErrors = (errors, logItems) => {
|
|
48
|
-
if (Array.isArray(logItems) && logItems.length > 0) {
|
|
49
|
-
return logItems.map((item) => ({
|
|
50
|
-
path: item.file ?? null,
|
|
51
|
-
line: typeof item.line === "number" ? item.line : null,
|
|
52
|
-
message: item.message,
|
|
53
|
-
severity: item.severity === "warning" ? "warning" : "error"
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
return Array.isArray(errors) ? errors : [];
|
|
57
|
-
};
|
|
58
|
-
const LATEX_COMPILER_OPTIONS = ["pdflatex", "xelatex", "lualatex"];
|
|
59
|
-
const BIB_SNIPPETS = [
|
|
60
|
-
{
|
|
61
|
-
id: "article",
|
|
62
|
-
labelKey: "bib_snippet_article",
|
|
63
|
-
snippet: "@article{key,\n title = {},\n author = {},\n journal = {},\n year = {},\n}\n"
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
id: "inproceedings",
|
|
67
|
-
labelKey: "bib_snippet_inproceedings",
|
|
68
|
-
snippet: "@inproceedings{key,\n title = {},\n author = {},\n booktitle = {},\n year = {},\n}\n"
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
id: "misc",
|
|
72
|
-
labelKey: "bib_snippet_misc",
|
|
73
|
-
snippet: "@misc{key,\n title = {},\n author = {},\n year = {},\n note = {},\n}\n"
|
|
74
|
-
}
|
|
75
|
-
];
|
|
76
|
-
function normalizeCompiler(value) {
|
|
77
|
-
if (value === "xelatex" || value === "lualatex") return value;
|
|
78
|
-
return "pdflatex";
|
|
79
|
-
}
|
|
80
|
-
function normalizeLatexPath(value) {
|
|
81
|
-
return String(value || "").trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "").toLowerCase();
|
|
82
|
-
}
|
|
83
|
-
function parseBibEntries(text, sourceFile) {
|
|
84
|
-
const entries = [];
|
|
85
|
-
const source = String(text || "");
|
|
86
|
-
const entryRegex = /@([a-zA-Z]+)\s*\{\s*([^,\s]+)\s*,([\s\S]*?)\n\}/g;
|
|
87
|
-
let match = null;
|
|
88
|
-
while (match = entryRegex.exec(source)) {
|
|
89
|
-
const body = match[3] || "";
|
|
90
|
-
const title = body.match(/title\s*=\s*[\{"']([^}"']+)/i)?.[1]?.trim();
|
|
91
|
-
const author = body.match(/author\s*=\s*[\{"']([^}"']+)/i)?.[1]?.trim();
|
|
92
|
-
entries.push({
|
|
93
|
-
key: match[2].trim(),
|
|
94
|
-
title,
|
|
95
|
-
author,
|
|
96
|
-
sourceFile
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
return entries;
|
|
100
|
-
}
|
|
101
|
-
function parseLatexLabels(text, sourceFile) {
|
|
102
|
-
const entries = [];
|
|
103
|
-
const source = String(text || "");
|
|
104
|
-
const regex = /\\label\{([^}]+)\}/g;
|
|
105
|
-
let match = null;
|
|
106
|
-
while (match = regex.exec(source)) {
|
|
107
|
-
entries.push({
|
|
108
|
-
key: match[1].trim(),
|
|
109
|
-
sourceFile
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
return entries;
|
|
113
|
-
}
|
|
114
|
-
function resolveLatexFileId(files, rawPath) {
|
|
115
|
-
const normalized = normalizeLatexPath(rawPath);
|
|
116
|
-
if (!normalized) return null;
|
|
117
|
-
const exact = files.find((file) => normalizeLatexPath(file.name) === normalized);
|
|
118
|
-
if (exact) return exact.id;
|
|
119
|
-
const basename = normalized.split("/").filter(Boolean).pop();
|
|
120
|
-
if (!basename) return null;
|
|
121
|
-
const byBasename = files.find((file) => normalizeLatexPath(file.name).endsWith(`/${basename}`));
|
|
122
|
-
if (byBasename) return byBasename.id;
|
|
123
|
-
const simpleName = files.find((file) => file.name.toLowerCase() === basename);
|
|
124
|
-
return simpleName?.id ?? null;
|
|
125
|
-
}
|
|
126
|
-
function PdfSurface({ pdfDocument, zoomFactor, highlights, onPageWidth }) {
|
|
127
|
-
reactExports.useEffect(() => {
|
|
128
|
-
let cancelled = false;
|
|
129
|
-
pdfDocument.getPage(1).then((page) => {
|
|
130
|
-
if (cancelled) return;
|
|
131
|
-
const viewport = page.getViewport({ scale: 1 });
|
|
132
|
-
if (viewport?.width) {
|
|
133
|
-
onPageWidth(viewport.width);
|
|
134
|
-
}
|
|
135
|
-
}).catch(() => {
|
|
136
|
-
if (!cancelled) onPageWidth(PAGE_DIMENSIONS.A4_WIDTH);
|
|
137
|
-
});
|
|
138
|
-
return () => {
|
|
139
|
-
cancelled = true;
|
|
140
|
-
};
|
|
141
|
-
}, [onPageWidth, pdfDocument]);
|
|
142
|
-
const safeZoomFactor = Number.isFinite(zoomFactor) && zoomFactor > 0 ? zoomFactor : 1;
|
|
143
|
-
const pdfScaleValue = Math.abs(safeZoomFactor - 1) < 1e-3 ? "page-width" : `page-width:${safeZoomFactor}`;
|
|
144
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
145
|
-
PdfHighlighter,
|
|
146
|
-
{
|
|
147
|
-
pdfDocument,
|
|
148
|
-
pdfScaleValue,
|
|
149
|
-
highlights,
|
|
150
|
-
highlightTransform: () => /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, {}),
|
|
151
|
-
onScrollChange: () => {
|
|
152
|
-
},
|
|
153
|
-
scrollRef: () => {
|
|
154
|
-
},
|
|
155
|
-
onSelectionFinished: (_position, _content, _hideTipAndSelection, _transformSelection) => null,
|
|
156
|
-
enableAreaSelection: () => false
|
|
157
|
-
}
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
function LatexPlugin({ context, tabId, setDirty, setTitle }) {
|
|
161
|
-
const custom = context.customData ?? {};
|
|
162
|
-
const projectId = custom.projectId ?? void 0;
|
|
163
|
-
const latexFolderId = custom.latexFolderId ?? context.resourceId ?? void 0;
|
|
164
|
-
const viewReadOnly = Boolean(custom.readOnly);
|
|
165
|
-
const user = useAuthStore((s) => s.user);
|
|
166
|
-
const { t, language } = useI18n("latex");
|
|
167
|
-
const updateWorkspaceTabState = useWorkspaceSurfaceStore((state) => state.updateTabState);
|
|
168
|
-
const setWorkspaceActiveIssue = useWorkspaceSurfaceStore((state) => state.setActiveIssue);
|
|
169
|
-
const [roleWritable, setRoleWritable] = reactExports.useState(null);
|
|
170
|
-
const isDark = useIsDarkMode();
|
|
171
|
-
const updateFileMeta = useFileTreeStore((s) => s.updateFileMeta);
|
|
172
|
-
const [files, setFiles] = reactExports.useState([]);
|
|
173
|
-
const initialFileId = custom.openFileId ?? custom.mainFileId ?? null;
|
|
174
|
-
const [activeFileId, setActiveFileId] = reactExports.useState(initialFileId);
|
|
175
|
-
const [activeFileName, setActiveFileName] = reactExports.useState("main.tex");
|
|
176
|
-
const [initialText, setInitialText] = reactExports.useState("");
|
|
177
|
-
const [syncState, setSyncState] = reactExports.useState("idle");
|
|
178
|
-
const [saveState, setSaveState] = reactExports.useState("idle");
|
|
179
|
-
const [error, setError] = reactExports.useState(null);
|
|
180
|
-
const [isDirty, setIsDirty] = reactExports.useState(false);
|
|
181
|
-
const [buildId, setBuildId] = reactExports.useState(null);
|
|
182
|
-
const [buildStatus, setBuildStatus] = reactExports.useState("idle");
|
|
183
|
-
const [buildError, setBuildError] = reactExports.useState(null);
|
|
184
|
-
const [buildErrors, setBuildErrors] = reactExports.useState([]);
|
|
185
|
-
const [compiler, setCompiler] = reactExports.useState("pdflatex");
|
|
186
|
-
const [pdfObjectUrl, setPdfObjectUrl] = reactExports.useState(null);
|
|
187
|
-
const [logText, setLogText] = reactExports.useState(null);
|
|
188
|
-
const [zoomScale, setZoomScale] = reactExports.useState(1);
|
|
189
|
-
const [pdfPageWidth, setPdfPageWidth] = reactExports.useState(PAGE_DIMENSIONS.A4_WIDTH);
|
|
190
|
-
const [pdfPaneWidth, setPdfPaneWidth] = reactExports.useState(0);
|
|
191
|
-
const [splitRatio, setSplitRatio] = reactExports.useState(0.58);
|
|
192
|
-
const [isResizing, setIsResizing] = reactExports.useState(false);
|
|
193
|
-
const [isWideLayout, setIsWideLayout] = reactExports.useState(false);
|
|
194
|
-
const [referencePanelOpen, setReferencePanelOpen] = reactExports.useState(false);
|
|
195
|
-
const [bibPanelOpen, setBibPanelOpen] = reactExports.useState(false);
|
|
196
|
-
const [assistQuery, setAssistQuery] = reactExports.useState("");
|
|
197
|
-
const [citationIndex, setCitationIndex] = reactExports.useState([]);
|
|
198
|
-
const [labelIndex, setLabelIndex] = reactExports.useState([]);
|
|
199
|
-
const emptyHighlights = reactExports.useMemo(() => [], []);
|
|
200
|
-
const lastSavedRef = reactExports.useRef("");
|
|
201
|
-
const yDocRef = reactExports.useRef(null);
|
|
202
|
-
const yTextRef = reactExports.useRef(null);
|
|
203
|
-
const syncRef = reactExports.useRef(null);
|
|
204
|
-
const remoteOriginRef = reactExports.useRef("");
|
|
205
|
-
const pendingUpdatesRef = reactExports.useRef([]);
|
|
206
|
-
const flushTimerRef = reactExports.useRef(null);
|
|
207
|
-
const bindingCleanupRef = reactExports.useRef(null);
|
|
208
|
-
const applyingRemoteRef = reactExports.useRef(false);
|
|
209
|
-
const lastResetTimestampRef = reactExports.useRef(0);
|
|
210
|
-
const forceSeedRef = reactExports.useRef(false);
|
|
211
|
-
const [resetNonce, setResetNonce] = reactExports.useState(0);
|
|
212
|
-
const pdfUrlRef = reactExports.useRef(null);
|
|
213
|
-
const lastLoadedPdfBuildIdRef = reactExports.useRef(null);
|
|
214
|
-
const splitContainerRef = reactExports.useRef(null);
|
|
215
|
-
const pdfPaneRef = reactExports.useRef(null);
|
|
216
|
-
const editorRef = reactExports.useRef(null);
|
|
217
|
-
const pendingJumpRef = reactExports.useRef(null);
|
|
218
|
-
const citationIndexRef = reactExports.useRef([]);
|
|
219
|
-
const labelIndexRef = reactExports.useRef([]);
|
|
220
|
-
const latexCompletionDisposablesRef = reactExports.useRef([]);
|
|
221
|
-
const effectiveReadOnly = viewReadOnly || roleWritable === false;
|
|
222
|
-
const socketAuthMode = "user";
|
|
223
|
-
const canUseRealtimeSync = supportsSocketIo();
|
|
224
|
-
const isBibFile = activeFileName.toLowerCase().endsWith(".bib");
|
|
225
|
-
reactExports.useEffect(() => {
|
|
226
|
-
const activeFileMeta = files.find((file) => file.id === activeFileId) ?? files.find((file) => file.name === activeFileName) ?? null;
|
|
227
|
-
updateWorkspaceTabState(tabId, {
|
|
228
|
-
contentKind: "latex",
|
|
229
|
-
documentMode: "source",
|
|
230
|
-
resourceName: activeFileMeta?.name || activeFileName || context.resourceName || "main.tex",
|
|
231
|
-
resourcePath: activeFileMeta?.path ? toFilesResourcePath(activeFileMeta.path) : void 0,
|
|
232
|
-
isReadOnly: effectiveReadOnly,
|
|
233
|
-
compileState: buildStatus === "queued" || buildStatus === "running" ? "compiling" : saveState === "saving" ? "saving" : buildStatus === "error" ? "error" : "idle",
|
|
234
|
-
diagnostics: {
|
|
235
|
-
errors: buildErrors.filter((err) => err.severity !== "warning").length,
|
|
236
|
-
warnings: buildErrors.filter((err) => err.severity === "warning").length
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
}, [
|
|
240
|
-
activeFileId,
|
|
241
|
-
activeFileName,
|
|
242
|
-
buildErrors,
|
|
243
|
-
buildStatus,
|
|
244
|
-
context.resourceName,
|
|
245
|
-
effectiveReadOnly,
|
|
246
|
-
files,
|
|
247
|
-
saveState,
|
|
248
|
-
tabId,
|
|
249
|
-
updateWorkspaceTabState
|
|
250
|
-
]);
|
|
251
|
-
reactExports.useEffect(() => {
|
|
252
|
-
setTitle(context.resourceName || t("title"));
|
|
253
|
-
}, [context.resourceName, setTitle, t]);
|
|
254
|
-
reactExports.useEffect(() => {
|
|
255
|
-
if (!projectId) return;
|
|
256
|
-
if (viewReadOnly) {
|
|
257
|
-
setRoleWritable(null);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
let cancelled = false;
|
|
261
|
-
(async () => {
|
|
262
|
-
try {
|
|
263
|
-
const access = await checkProjectAccess(projectId);
|
|
264
|
-
if (cancelled) return;
|
|
265
|
-
const role = String(access?.role ?? "");
|
|
266
|
-
setRoleWritable(role === "owner" || role === "admin" || role === "editor");
|
|
267
|
-
} catch {
|
|
268
|
-
if (cancelled) return;
|
|
269
|
-
setRoleWritable(null);
|
|
270
|
-
}
|
|
271
|
-
})();
|
|
272
|
-
return () => {
|
|
273
|
-
cancelled = true;
|
|
274
|
-
};
|
|
275
|
-
}, [projectId, viewReadOnly]);
|
|
276
|
-
reactExports.useEffect(() => {
|
|
277
|
-
if (!projectId || !latexFolderId) return;
|
|
278
|
-
let cancelled = false;
|
|
279
|
-
(async () => {
|
|
280
|
-
try {
|
|
281
|
-
const items = await listFiles(projectId, latexFolderId);
|
|
282
|
-
if (cancelled) return;
|
|
283
|
-
const candidates = items.filter((x) => x.type === "file").map((x) => ({ id: x.id, name: x.name, path: x.path || void 0 })).sort((a, b) => a.name.localeCompare(b.name));
|
|
284
|
-
setFiles(candidates);
|
|
285
|
-
if (!activeFileId) {
|
|
286
|
-
const main = candidates.find((f) => f.name.toLowerCase() === "main.tex") ?? candidates.find((f) => f.name.toLowerCase().endsWith(".tex")) ?? candidates[0];
|
|
287
|
-
if (main) {
|
|
288
|
-
setActiveFileId(main.id);
|
|
289
|
-
setActiveFileName(main.name);
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
const meta = candidates.find((f) => f.id === activeFileId);
|
|
293
|
-
if (meta) setActiveFileName(meta.name);
|
|
294
|
-
}
|
|
295
|
-
} catch (e) {
|
|
296
|
-
console.error("[LatexPlugin] Failed to list files:", e);
|
|
297
|
-
setError(e instanceof Error ? e.message : t("load_files_failed"));
|
|
298
|
-
}
|
|
299
|
-
})();
|
|
300
|
-
return () => {
|
|
301
|
-
cancelled = true;
|
|
302
|
-
};
|
|
303
|
-
}, [projectId, latexFolderId]);
|
|
304
|
-
reactExports.useEffect(() => {
|
|
305
|
-
citationIndexRef.current = citationIndex;
|
|
306
|
-
}, [citationIndex]);
|
|
307
|
-
reactExports.useEffect(() => {
|
|
308
|
-
labelIndexRef.current = labelIndex;
|
|
309
|
-
}, [labelIndex]);
|
|
310
|
-
reactExports.useEffect(() => {
|
|
311
|
-
return () => {
|
|
312
|
-
latexCompletionDisposablesRef.current.forEach((disposable) => {
|
|
313
|
-
try {
|
|
314
|
-
disposable?.dispose?.();
|
|
315
|
-
} catch {
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
latexCompletionDisposablesRef.current = [];
|
|
319
|
-
};
|
|
320
|
-
}, []);
|
|
321
|
-
reactExports.useEffect(() => {
|
|
322
|
-
if (!projectId || files.length === 0) {
|
|
323
|
-
setCitationIndex([]);
|
|
324
|
-
setLabelIndex([]);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
let cancelled = false;
|
|
328
|
-
const candidateFiles = files.filter(
|
|
329
|
-
(file) => file.name.toLowerCase().endsWith(".bib") || file.name.toLowerCase().endsWith(".tex")
|
|
330
|
-
);
|
|
331
|
-
(async () => {
|
|
332
|
-
try {
|
|
333
|
-
const loaded = await Promise.all(
|
|
334
|
-
candidateFiles.map(async (file) => {
|
|
335
|
-
try {
|
|
336
|
-
const content = await getFileContent(file.id);
|
|
337
|
-
return { file, content };
|
|
338
|
-
} catch {
|
|
339
|
-
return { file, content: "" };
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
);
|
|
343
|
-
if (cancelled) return;
|
|
344
|
-
const nextCitationIndex = loaded.filter((item) => item.file.name.toLowerCase().endsWith(".bib")).flatMap((item) => parseBibEntries(item.content, item.file.name)).sort((a, b) => a.key.localeCompare(b.key));
|
|
345
|
-
const nextLabelIndex = loaded.filter((item) => item.file.name.toLowerCase().endsWith(".tex")).flatMap((item) => parseLatexLabels(item.content, item.file.name)).sort((a, b) => a.key.localeCompare(b.key));
|
|
346
|
-
setCitationIndex(nextCitationIndex);
|
|
347
|
-
setLabelIndex(nextLabelIndex);
|
|
348
|
-
} catch {
|
|
349
|
-
if (!cancelled) {
|
|
350
|
-
setCitationIndex([]);
|
|
351
|
-
setLabelIndex([]);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
})();
|
|
355
|
-
return () => {
|
|
356
|
-
cancelled = true;
|
|
357
|
-
};
|
|
358
|
-
}, [files, projectId]);
|
|
359
|
-
reactExports.useEffect(() => {
|
|
360
|
-
return () => {
|
|
361
|
-
try {
|
|
362
|
-
bindingCleanupRef.current?.();
|
|
363
|
-
} finally {
|
|
364
|
-
bindingCleanupRef.current = null;
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
}, [activeFileId]);
|
|
368
|
-
reactExports.useEffect(() => {
|
|
369
|
-
if (!projectId || !activeFileId) return;
|
|
370
|
-
let cancelled = false;
|
|
371
|
-
let cleanup = null;
|
|
372
|
-
setSyncState("loading");
|
|
373
|
-
setError(null);
|
|
374
|
-
(async () => {
|
|
375
|
-
const { Doc, applyUpdate, encodeStateVector, encodeStateAsUpdate, mergeUpdates } = await __vitePreload(async () => { const { Doc, applyUpdate, encodeStateVector, encodeStateAsUpdate, mergeUpdates } = await import('./yjs-DncrqiZ8.js').then(n => n.z);return { Doc, applyUpdate, encodeStateVector, encodeStateAsUpdate, mergeUpdates }},true?__vite__mapDeps([3,4]):void 0);
|
|
376
|
-
const ydoc = new Doc();
|
|
377
|
-
const ytext = ydoc.getText("content");
|
|
378
|
-
yDocRef.current = ydoc;
|
|
379
|
-
yTextRef.current = ytext;
|
|
380
|
-
if (!canUseRealtimeSync) {
|
|
381
|
-
const seed = await getFileContent(activeFileId);
|
|
382
|
-
ydoc.transact(() => {
|
|
383
|
-
const length = ytext.length || 0;
|
|
384
|
-
if (length) ytext.delete(0, length);
|
|
385
|
-
if (seed) ytext.insert(0, seed);
|
|
386
|
-
}, "ds-local-seed");
|
|
387
|
-
const textNow2 = ytext.toString();
|
|
388
|
-
setInitialText(textNow2);
|
|
389
|
-
lastSavedRef.current = textNow2;
|
|
390
|
-
setIsDirty(false);
|
|
391
|
-
setDirty(false);
|
|
392
|
-
setSyncState("ready");
|
|
393
|
-
cleanup = () => {
|
|
394
|
-
try {
|
|
395
|
-
bindingCleanupRef.current?.();
|
|
396
|
-
} finally {
|
|
397
|
-
bindingCleanupRef.current = null;
|
|
398
|
-
if (yDocRef.current === ydoc) yDocRef.current = null;
|
|
399
|
-
if (yTextRef.current === ytext) yTextRef.current = null;
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
const sync = new ProjectSyncClient(projectId, {
|
|
405
|
-
authMode: socketAuthMode,
|
|
406
|
-
docKind: "latex"
|
|
407
|
-
});
|
|
408
|
-
syncRef.current = sync;
|
|
409
|
-
await sync.connect();
|
|
410
|
-
const remoteOrigin = `ds-remote:${projectId}:${activeFileId}:${Date.now()}`;
|
|
411
|
-
remoteOriginRef.current = remoteOrigin;
|
|
412
|
-
const diff = await sync.loadDoc(activeFileId, encodeStateVector(ydoc));
|
|
413
|
-
if (diff?.missing) {
|
|
414
|
-
applyUpdate(ydoc, diff.missing, remoteOrigin);
|
|
415
|
-
}
|
|
416
|
-
if (forceSeedRef.current) {
|
|
417
|
-
const seed = await getFileContent(activeFileId);
|
|
418
|
-
ydoc.transact(() => {
|
|
419
|
-
const length = ytext.length || 0;
|
|
420
|
-
if (length) ytext.delete(0, length);
|
|
421
|
-
if (seed) ytext.insert(0, seed);
|
|
422
|
-
}, "ds-reset");
|
|
423
|
-
if (!effectiveReadOnly) {
|
|
424
|
-
const resetUpdate = encodeStateAsUpdate(ydoc);
|
|
425
|
-
await sync.pushDocUpdate(activeFileId, resetUpdate);
|
|
426
|
-
}
|
|
427
|
-
forceSeedRef.current = false;
|
|
428
|
-
}
|
|
429
|
-
if (!diff) {
|
|
430
|
-
const seed = await getFileContent(activeFileId);
|
|
431
|
-
ydoc.transact(() => {
|
|
432
|
-
ytext.insert(0, seed);
|
|
433
|
-
}, "ds-seed");
|
|
434
|
-
if (!effectiveReadOnly) {
|
|
435
|
-
const initUpdate = encodeStateAsUpdate(ydoc);
|
|
436
|
-
await sync.pushDocUpdate(activeFileId, initUpdate);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
const unsubscribeRemote = sync.onDocUpdate((msg) => {
|
|
440
|
-
if (msg.docId !== activeFileId) return;
|
|
441
|
-
applyUpdate(ydoc, msg.update, remoteOrigin);
|
|
442
|
-
});
|
|
443
|
-
const unsubscribeReset = sync.onDocReset((msg) => {
|
|
444
|
-
if (msg.docId !== activeFileId) return;
|
|
445
|
-
const ts = Number(msg.timestamp || 0);
|
|
446
|
-
if (ts && ts === lastResetTimestampRef.current) return;
|
|
447
|
-
lastResetTimestampRef.current = ts || Date.now();
|
|
448
|
-
if (cancelled) return;
|
|
449
|
-
forceSeedRef.current = true;
|
|
450
|
-
setResetNonce((v) => v + 1);
|
|
451
|
-
});
|
|
452
|
-
const scheduleFlush = () => {
|
|
453
|
-
if (flushTimerRef.current != null) {
|
|
454
|
-
window.clearTimeout(flushTimerRef.current);
|
|
455
|
-
}
|
|
456
|
-
flushTimerRef.current = window.setTimeout(async () => {
|
|
457
|
-
flushTimerRef.current = null;
|
|
458
|
-
const pending = pendingUpdatesRef.current;
|
|
459
|
-
if (!pending.length) return;
|
|
460
|
-
pendingUpdatesRef.current = [];
|
|
461
|
-
try {
|
|
462
|
-
const merged = pending.length === 1 ? pending[0] : mergeUpdates(pending);
|
|
463
|
-
await sync.pushDocUpdate(activeFileId, merged);
|
|
464
|
-
} catch (e) {
|
|
465
|
-
console.error("[LatexPlugin] Failed to push update:", e);
|
|
466
|
-
}
|
|
467
|
-
}, 300);
|
|
468
|
-
};
|
|
469
|
-
const handleLocalUpdate = (update, origin) => {
|
|
470
|
-
if (origin === remoteOrigin) return;
|
|
471
|
-
if (effectiveReadOnly) return;
|
|
472
|
-
pendingUpdatesRef.current.push(update);
|
|
473
|
-
scheduleFlush();
|
|
474
|
-
};
|
|
475
|
-
ydoc.on("update", handleLocalUpdate);
|
|
476
|
-
let awareness = null;
|
|
477
|
-
let unsubscribeAwarenessUpdate = null;
|
|
478
|
-
let unsubscribeAwarenessCollect = null;
|
|
479
|
-
let handleAwarenessChange = null;
|
|
480
|
-
if (!effectiveReadOnly) {
|
|
481
|
-
try {
|
|
482
|
-
const { Awareness, encodeAwarenessUpdate, applyAwarenessUpdate } = await __vitePreload(async () => { const { Awareness, encodeAwarenessUpdate, applyAwarenessUpdate } = await import('./awareness-C0NPR2Dj.js');return { Awareness, encodeAwarenessUpdate, applyAwarenessUpdate }},true?__vite__mapDeps([5,4]):void 0);
|
|
483
|
-
awareness = new Awareness(ydoc);
|
|
484
|
-
awareness.setLocalStateField("user", {
|
|
485
|
-
id: user?.id ?? null,
|
|
486
|
-
name: user?.username ?? "User"
|
|
487
|
-
});
|
|
488
|
-
await sync.joinAwareness(activeFileId);
|
|
489
|
-
const localAwarenessOrigin = `ds-awareness:${projectId}:${activeFileId}:${Date.now()}`;
|
|
490
|
-
unsubscribeAwarenessUpdate = sync.onAwarenessUpdate(activeFileId, (update) => {
|
|
491
|
-
applyAwarenessUpdate(awareness, update, localAwarenessOrigin);
|
|
492
|
-
});
|
|
493
|
-
unsubscribeAwarenessCollect = sync.onAwarenessCollect(activeFileId, () => {
|
|
494
|
-
const update = encodeAwarenessUpdate(awareness, [awareness.clientID]);
|
|
495
|
-
void sync.broadcastAwareness(activeFileId, update);
|
|
496
|
-
});
|
|
497
|
-
handleAwarenessChange = (changes, origin) => {
|
|
498
|
-
if (origin === localAwarenessOrigin) return;
|
|
499
|
-
const changedClients = [
|
|
500
|
-
...changes?.added ?? [],
|
|
501
|
-
...changes?.updated ?? [],
|
|
502
|
-
...changes?.removed ?? []
|
|
503
|
-
];
|
|
504
|
-
const update = encodeAwarenessUpdate(awareness, changedClients);
|
|
505
|
-
void sync.broadcastAwareness(activeFileId, update);
|
|
506
|
-
};
|
|
507
|
-
awareness.on("change", handleAwarenessChange);
|
|
508
|
-
sync.requestAwarenesses(activeFileId);
|
|
509
|
-
} catch (e) {
|
|
510
|
-
console.warn("[LatexPlugin] Awareness init failed:", e);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
const textNow = ytext.toString();
|
|
514
|
-
setInitialText(textNow);
|
|
515
|
-
lastSavedRef.current = textNow;
|
|
516
|
-
setIsDirty(false);
|
|
517
|
-
setDirty(false);
|
|
518
|
-
setSyncState("ready");
|
|
519
|
-
cleanup = () => {
|
|
520
|
-
try {
|
|
521
|
-
try {
|
|
522
|
-
bindingCleanupRef.current?.();
|
|
523
|
-
} finally {
|
|
524
|
-
bindingCleanupRef.current = null;
|
|
525
|
-
}
|
|
526
|
-
unsubscribeRemote?.();
|
|
527
|
-
unsubscribeReset?.();
|
|
528
|
-
try {
|
|
529
|
-
ydoc.off("update", handleLocalUpdate);
|
|
530
|
-
} catch {
|
|
531
|
-
}
|
|
532
|
-
if (flushTimerRef.current != null) {
|
|
533
|
-
window.clearTimeout(flushTimerRef.current);
|
|
534
|
-
flushTimerRef.current = null;
|
|
535
|
-
}
|
|
536
|
-
pendingUpdatesRef.current = [];
|
|
537
|
-
if (unsubscribeAwarenessUpdate) unsubscribeAwarenessUpdate();
|
|
538
|
-
if (unsubscribeAwarenessCollect) unsubscribeAwarenessCollect();
|
|
539
|
-
if (awareness && handleAwarenessChange) {
|
|
540
|
-
try {
|
|
541
|
-
awareness.off("change", handleAwarenessChange);
|
|
542
|
-
} catch {
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
if (!effectiveReadOnly) {
|
|
546
|
-
try {
|
|
547
|
-
sync.leaveAwareness(activeFileId);
|
|
548
|
-
} catch {
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
} finally {
|
|
552
|
-
try {
|
|
553
|
-
sync.disconnect();
|
|
554
|
-
} catch {
|
|
555
|
-
}
|
|
556
|
-
if (syncRef.current === sync) syncRef.current = null;
|
|
557
|
-
if (yDocRef.current === ydoc) yDocRef.current = null;
|
|
558
|
-
if (yTextRef.current === ytext) yTextRef.current = null;
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
})().catch((e) => {
|
|
562
|
-
console.error("[LatexPlugin] Sync init failed:", e);
|
|
563
|
-
if (cancelled) return;
|
|
564
|
-
setSyncState("error");
|
|
565
|
-
setError(e instanceof Error ? e.message : t("collaboration_failed"));
|
|
566
|
-
});
|
|
567
|
-
return () => {
|
|
568
|
-
cancelled = true;
|
|
569
|
-
cleanup?.();
|
|
570
|
-
};
|
|
571
|
-
}, [activeFileId, canUseRealtimeSync, effectiveReadOnly, projectId, resetNonce, setDirty, socketAuthMode, t, user?.id, user?.username]);
|
|
572
|
-
const jumpEditorToLine = reactExports.useCallback((line) => {
|
|
573
|
-
const editor = editorRef.current;
|
|
574
|
-
if (!editor) return false;
|
|
575
|
-
const model = editor.getModel?.();
|
|
576
|
-
if (!model) return false;
|
|
577
|
-
const maxLine = Math.max(1, Number(model.getLineCount?.() ?? 1));
|
|
578
|
-
const safeLine = Math.min(Math.max(1, Math.round(line || 1)), maxLine);
|
|
579
|
-
editor.revealLineInCenter?.(safeLine);
|
|
580
|
-
editor.setPosition?.({ lineNumber: safeLine, column: 1 });
|
|
581
|
-
editor.setSelection?.({
|
|
582
|
-
startLineNumber: safeLine,
|
|
583
|
-
startColumn: 1,
|
|
584
|
-
endLineNumber: safeLine,
|
|
585
|
-
endColumn: Number(model.getLineMaxColumn?.(safeLine) ?? 1)
|
|
586
|
-
});
|
|
587
|
-
editor.focus?.();
|
|
588
|
-
return true;
|
|
589
|
-
}, []);
|
|
590
|
-
const flushPendingJump = reactExports.useCallback(() => {
|
|
591
|
-
const pending = pendingJumpRef.current;
|
|
592
|
-
if (!pending) return;
|
|
593
|
-
if (pending.fileId && pending.fileId !== activeFileId) return;
|
|
594
|
-
if (jumpEditorToLine(pending.line)) {
|
|
595
|
-
pendingJumpRef.current = null;
|
|
596
|
-
}
|
|
597
|
-
}, [activeFileId, jumpEditorToLine]);
|
|
598
|
-
const insertAtCursor = reactExports.useCallback((text) => {
|
|
599
|
-
const editor = editorRef.current;
|
|
600
|
-
if (!editor || effectiveReadOnly) return;
|
|
601
|
-
const selection = editor.getSelection?.();
|
|
602
|
-
if (!selection) return;
|
|
603
|
-
editor.executeEdits?.("ds-latex-assist", [
|
|
604
|
-
{
|
|
605
|
-
range: selection,
|
|
606
|
-
text,
|
|
607
|
-
forceMoveMarkers: true
|
|
608
|
-
}
|
|
609
|
-
]);
|
|
610
|
-
editor.focus?.();
|
|
611
|
-
setIsDirty(true);
|
|
612
|
-
setDirty(true);
|
|
613
|
-
}, [effectiveReadOnly, setDirty]);
|
|
614
|
-
const insertCitation = reactExports.useCallback(
|
|
615
|
-
(entry, command = "\\cite") => {
|
|
616
|
-
const editor = editorRef.current;
|
|
617
|
-
const model = editor?.getModel?.();
|
|
618
|
-
const position = editor?.getPosition?.();
|
|
619
|
-
if (!editor || !model || !position) {
|
|
620
|
-
insertAtCursor(`${command}{${entry.key}}`);
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
const linePrefix = model.getValueInRange({
|
|
624
|
-
startLineNumber: position.lineNumber,
|
|
625
|
-
startColumn: 1,
|
|
626
|
-
endLineNumber: position.lineNumber,
|
|
627
|
-
endColumn: position.column
|
|
628
|
-
});
|
|
629
|
-
const insideCitation = /\\(?:cite|citet|citep|autocite|parencite)\{[^}]*$/i.test(linePrefix);
|
|
630
|
-
insertAtCursor(insideCitation ? entry.key : `${command}{${entry.key}}`);
|
|
631
|
-
setReferencePanelOpen(false);
|
|
632
|
-
setAssistQuery("");
|
|
633
|
-
},
|
|
634
|
-
[insertAtCursor]
|
|
635
|
-
);
|
|
636
|
-
const insertLabelReference = reactExports.useCallback(
|
|
637
|
-
(entry, command = "\\ref") => {
|
|
638
|
-
const editor = editorRef.current;
|
|
639
|
-
const model = editor?.getModel?.();
|
|
640
|
-
const position = editor?.getPosition?.();
|
|
641
|
-
if (!editor || !model || !position) {
|
|
642
|
-
insertAtCursor(`${command}{${entry.key}}`);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const linePrefix = model.getValueInRange({
|
|
646
|
-
startLineNumber: position.lineNumber,
|
|
647
|
-
startColumn: 1,
|
|
648
|
-
endLineNumber: position.lineNumber,
|
|
649
|
-
endColumn: position.column
|
|
650
|
-
});
|
|
651
|
-
const insideRef = /\\(?:ref|eqref)\{[^}]*$/i.test(linePrefix);
|
|
652
|
-
insertAtCursor(insideRef ? entry.key : `${command}{${entry.key}}`);
|
|
653
|
-
setReferencePanelOpen(false);
|
|
654
|
-
setAssistQuery("");
|
|
655
|
-
},
|
|
656
|
-
[insertAtCursor]
|
|
657
|
-
);
|
|
658
|
-
const insertBibSnippet = reactExports.useCallback(
|
|
659
|
-
(snippet) => {
|
|
660
|
-
insertAtCursor(snippet);
|
|
661
|
-
setBibPanelOpen(false);
|
|
662
|
-
setAssistQuery("");
|
|
663
|
-
},
|
|
664
|
-
[insertAtCursor]
|
|
665
|
-
);
|
|
666
|
-
const filteredCitationIndex = reactExports.useMemo(() => {
|
|
667
|
-
const query = assistQuery.trim().toLowerCase();
|
|
668
|
-
if (!query) return citationIndex.slice(0, 12);
|
|
669
|
-
return citationIndex.filter(
|
|
670
|
-
(entry) => [entry.key, entry.title, entry.author, entry.sourceFile].some(
|
|
671
|
-
(value) => String(value || "").toLowerCase().includes(query)
|
|
672
|
-
)
|
|
673
|
-
).slice(0, 12);
|
|
674
|
-
}, [assistQuery, citationIndex]);
|
|
675
|
-
const filteredLabelIndex = reactExports.useMemo(() => {
|
|
676
|
-
const query = assistQuery.trim().toLowerCase();
|
|
677
|
-
if (!query) return labelIndex.slice(0, 10);
|
|
678
|
-
return labelIndex.filter(
|
|
679
|
-
(entry) => [entry.key, entry.sourceFile].some(
|
|
680
|
-
(value) => String(value || "").toLowerCase().includes(query)
|
|
681
|
-
)
|
|
682
|
-
).slice(0, 10);
|
|
683
|
-
}, [assistQuery, labelIndex]);
|
|
684
|
-
const showAssistPanel = referencePanelOpen || bibPanelOpen;
|
|
685
|
-
const bindEditor = reactExports.useCallback(
|
|
686
|
-
(editor, monaco) => {
|
|
687
|
-
editorRef.current = editor;
|
|
688
|
-
const ytext = yTextRef.current;
|
|
689
|
-
const ydoc = yDocRef.current;
|
|
690
|
-
const remoteOrigin = remoteOriginRef.current;
|
|
691
|
-
if (!ytext || !ydoc) return;
|
|
692
|
-
const model = editor.getModel?.();
|
|
693
|
-
if (!model) return;
|
|
694
|
-
const ensureLanguage = (id) => {
|
|
695
|
-
const languages = monaco.languages.getLanguages?.() || [];
|
|
696
|
-
if (!languages.some((item) => item.id === id)) {
|
|
697
|
-
monaco.languages.register({ id });
|
|
698
|
-
}
|
|
699
|
-
};
|
|
700
|
-
ensureLanguage("latex-ds");
|
|
701
|
-
ensureLanguage("bibtex-ds");
|
|
702
|
-
monaco.editor.setModelLanguage(model, isBibFile ? "bibtex-ds" : "latex-ds");
|
|
703
|
-
latexCompletionDisposablesRef.current.forEach((disposable2) => {
|
|
704
|
-
try {
|
|
705
|
-
disposable2?.dispose?.();
|
|
706
|
-
} catch {
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
latexCompletionDisposablesRef.current = [
|
|
710
|
-
monaco.languages.registerCompletionItemProvider("latex-ds", {
|
|
711
|
-
triggerCharacters: ["\\", "{"],
|
|
712
|
-
provideCompletionItems: (targetModel, position) => {
|
|
713
|
-
const linePrefix = targetModel.getValueInRange({
|
|
714
|
-
startLineNumber: position.lineNumber,
|
|
715
|
-
startColumn: 1,
|
|
716
|
-
endLineNumber: position.lineNumber,
|
|
717
|
-
endColumn: position.column
|
|
718
|
-
});
|
|
719
|
-
const word = targetModel.getWordUntilPosition(position);
|
|
720
|
-
const range = new monaco.Range(
|
|
721
|
-
position.lineNumber,
|
|
722
|
-
word.startColumn,
|
|
723
|
-
position.lineNumber,
|
|
724
|
-
word.endColumn
|
|
725
|
-
);
|
|
726
|
-
if (/\\(?:cite|citet|citep|autocite|parencite)\{[^}]*$/i.test(linePrefix)) {
|
|
727
|
-
return {
|
|
728
|
-
suggestions: citationIndexRef.current.slice(0, 40).map((entry) => ({
|
|
729
|
-
label: entry.key,
|
|
730
|
-
kind: monaco.languages.CompletionItemKind.Reference,
|
|
731
|
-
insertText: entry.key,
|
|
732
|
-
detail: entry.title || entry.author || entry.sourceFile,
|
|
733
|
-
documentation: [entry.author, entry.title].filter(Boolean).join(" · "),
|
|
734
|
-
range
|
|
735
|
-
}))
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
if (/\\(?:ref|eqref)\{[^}]*$/i.test(linePrefix)) {
|
|
739
|
-
return {
|
|
740
|
-
suggestions: labelIndexRef.current.slice(0, 40).map((entry) => ({
|
|
741
|
-
label: entry.key,
|
|
742
|
-
kind: monaco.languages.CompletionItemKind.Reference,
|
|
743
|
-
insertText: entry.key,
|
|
744
|
-
detail: entry.sourceFile,
|
|
745
|
-
range
|
|
746
|
-
}))
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
return { suggestions: [] };
|
|
750
|
-
}
|
|
751
|
-
}),
|
|
752
|
-
monaco.languages.registerCompletionItemProvider("bibtex-ds", {
|
|
753
|
-
triggerCharacters: ["@"],
|
|
754
|
-
provideCompletionItems: (_targetModel, position) => {
|
|
755
|
-
const range = new monaco.Range(
|
|
756
|
-
position.lineNumber,
|
|
757
|
-
position.column,
|
|
758
|
-
position.lineNumber,
|
|
759
|
-
position.column
|
|
760
|
-
);
|
|
761
|
-
return {
|
|
762
|
-
suggestions: BIB_SNIPPETS.map((item) => ({
|
|
763
|
-
label: item.id,
|
|
764
|
-
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
765
|
-
insertText: item.snippet,
|
|
766
|
-
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
767
|
-
documentation: t(item.labelKey),
|
|
768
|
-
range
|
|
769
|
-
}))
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
})
|
|
773
|
-
];
|
|
774
|
-
bindingCleanupRef.current?.();
|
|
775
|
-
applyingRemoteRef.current = true;
|
|
776
|
-
try {
|
|
777
|
-
model.setValue(ytext.toString());
|
|
778
|
-
} finally {
|
|
779
|
-
applyingRemoteRef.current = false;
|
|
780
|
-
}
|
|
781
|
-
const applyDelta = (delta) => {
|
|
782
|
-
if (!Array.isArray(delta) || delta.length === 0) return;
|
|
783
|
-
const edits = [];
|
|
784
|
-
let index = 0;
|
|
785
|
-
for (const op of delta) {
|
|
786
|
-
const retain = typeof op?.retain === "number" ? op.retain : 0;
|
|
787
|
-
if (retain) index += retain;
|
|
788
|
-
const ins = typeof op?.insert === "string" ? op.insert : null;
|
|
789
|
-
if (ins != null) {
|
|
790
|
-
const pos = model.getPositionAt(index);
|
|
791
|
-
edits.push({
|
|
792
|
-
range: new monaco.Range(pos.lineNumber, pos.column, pos.lineNumber, pos.column),
|
|
793
|
-
text: ins,
|
|
794
|
-
forceMoveMarkers: true
|
|
795
|
-
});
|
|
796
|
-
index += ins.length;
|
|
797
|
-
}
|
|
798
|
-
const del = typeof op?.delete === "number" ? op.delete : 0;
|
|
799
|
-
if (del) {
|
|
800
|
-
const start = model.getPositionAt(index);
|
|
801
|
-
const end = model.getPositionAt(index + del);
|
|
802
|
-
edits.push({
|
|
803
|
-
range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column),
|
|
804
|
-
text: "",
|
|
805
|
-
forceMoveMarkers: true
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
if (!edits.length) return;
|
|
810
|
-
applyingRemoteRef.current = true;
|
|
811
|
-
try {
|
|
812
|
-
model.applyEdits(edits);
|
|
813
|
-
} finally {
|
|
814
|
-
applyingRemoteRef.current = false;
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
const yObserver = (event) => {
|
|
818
|
-
const origin = event?.transaction?.origin;
|
|
819
|
-
if (origin !== remoteOrigin) return;
|
|
820
|
-
applyDelta(event.delta ?? []);
|
|
821
|
-
setIsDirty(true);
|
|
822
|
-
};
|
|
823
|
-
ytext.observe(yObserver);
|
|
824
|
-
const disposable = model.onDidChangeContent((e) => {
|
|
825
|
-
if (applyingRemoteRef.current) return;
|
|
826
|
-
if (effectiveReadOnly) return;
|
|
827
|
-
const changes = Array.isArray(e?.changes) ? e.changes : [];
|
|
828
|
-
if (!changes.length) return;
|
|
829
|
-
ydoc.transact(() => {
|
|
830
|
-
const sorted = [...changes].sort((a, b) => (b.rangeOffset ?? 0) - (a.rangeOffset ?? 0));
|
|
831
|
-
for (const ch of sorted) {
|
|
832
|
-
const offset = Number(ch.rangeOffset ?? 0);
|
|
833
|
-
const length = Number(ch.rangeLength ?? 0);
|
|
834
|
-
const text = String(ch.text ?? "");
|
|
835
|
-
if (length) ytext.delete(offset, length);
|
|
836
|
-
if (text) ytext.insert(offset, text);
|
|
837
|
-
}
|
|
838
|
-
}, "ds-monaco");
|
|
839
|
-
setIsDirty(true);
|
|
840
|
-
});
|
|
841
|
-
bindingCleanupRef.current = () => {
|
|
842
|
-
try {
|
|
843
|
-
disposable?.dispose?.();
|
|
844
|
-
} catch {
|
|
845
|
-
}
|
|
846
|
-
try {
|
|
847
|
-
ytext.unobserve(yObserver);
|
|
848
|
-
} catch {
|
|
849
|
-
}
|
|
850
|
-
};
|
|
851
|
-
window.requestAnimationFrame(() => {
|
|
852
|
-
flushPendingJump();
|
|
853
|
-
});
|
|
854
|
-
},
|
|
855
|
-
[effectiveReadOnly, flushPendingJump, isBibFile, setDirty, t]
|
|
856
|
-
);
|
|
857
|
-
reactExports.useEffect(() => {
|
|
858
|
-
if (syncState !== "ready") return;
|
|
859
|
-
flushPendingJump();
|
|
860
|
-
}, [activeFileId, flushPendingJump, resetNonce, syncState]);
|
|
861
|
-
const save = reactExports.useCallback(async () => {
|
|
862
|
-
if (!activeFileId) return false;
|
|
863
|
-
if (effectiveReadOnly) return false;
|
|
864
|
-
const ytext = yTextRef.current;
|
|
865
|
-
if (!ytext) return false;
|
|
866
|
-
try {
|
|
867
|
-
setSaveState("saving");
|
|
868
|
-
const text = String(ytext.toString?.() ?? "");
|
|
869
|
-
const res = await updateFileContent(activeFileId, text);
|
|
870
|
-
lastSavedRef.current = text;
|
|
871
|
-
setSaveState("idle");
|
|
872
|
-
setIsDirty(false);
|
|
873
|
-
setDirty(false);
|
|
874
|
-
if (res?.updated_at) {
|
|
875
|
-
updateFileMeta(activeFileId, {
|
|
876
|
-
updatedAt: res.updated_at,
|
|
877
|
-
size: typeof res.size === "number" ? res.size : void 0,
|
|
878
|
-
mimeType: res.mime_type
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
return true;
|
|
882
|
-
} catch (e) {
|
|
883
|
-
console.error("[LatexPlugin] Save failed:", e);
|
|
884
|
-
setSaveState("error");
|
|
885
|
-
window.setTimeout(() => setSaveState("idle"), 1400);
|
|
886
|
-
return false;
|
|
887
|
-
}
|
|
888
|
-
}, [activeFileId, effectiveReadOnly, setDirty, updateFileMeta]);
|
|
889
|
-
const compile = reactExports.useCallback(
|
|
890
|
-
async (opts) => {
|
|
891
|
-
if (!projectId || !latexFolderId) return;
|
|
892
|
-
if (viewReadOnly) return;
|
|
893
|
-
if (isDirty && !effectiveReadOnly) {
|
|
894
|
-
const saved = await save();
|
|
895
|
-
if (!saved) return;
|
|
896
|
-
}
|
|
897
|
-
try {
|
|
898
|
-
setBuildError(null);
|
|
899
|
-
setBuildErrors([]);
|
|
900
|
-
setLogText(null);
|
|
901
|
-
setBuildStatus("queued");
|
|
902
|
-
const res = await compileLatex(projectId, latexFolderId, {
|
|
903
|
-
compiler,
|
|
904
|
-
auto: Boolean(opts?.auto),
|
|
905
|
-
stop_on_first_error: false
|
|
906
|
-
});
|
|
907
|
-
setBuildId(res.build_id);
|
|
908
|
-
setCompiler(normalizeCompiler(res.compiler));
|
|
909
|
-
setBuildStatus(res.status ?? "queued");
|
|
910
|
-
} catch (e) {
|
|
911
|
-
console.error("[LatexPlugin] Compile failed:", e);
|
|
912
|
-
setBuildError(e instanceof Error ? e.message : t("compile_request_failed"));
|
|
913
|
-
setBuildStatus("error");
|
|
914
|
-
}
|
|
915
|
-
},
|
|
916
|
-
[compiler, effectiveReadOnly, isDirty, latexFolderId, projectId, save, t, viewReadOnly]
|
|
917
|
-
);
|
|
918
|
-
reactExports.useEffect(() => {
|
|
919
|
-
if (!projectId || !latexFolderId) return;
|
|
920
|
-
const handler = (event) => {
|
|
921
|
-
const detail = event.detail;
|
|
922
|
-
if (!detail) return;
|
|
923
|
-
if (detail.projectId !== projectId || detail.folderId !== latexFolderId) return;
|
|
924
|
-
if (detail.buildId) {
|
|
925
|
-
setBuildId(detail.buildId);
|
|
926
|
-
}
|
|
927
|
-
setBuildStatus(detail.status ?? "queued");
|
|
928
|
-
setBuildError(detail.errorMessage ?? null);
|
|
929
|
-
setBuildErrors([]);
|
|
930
|
-
setLogText(null);
|
|
931
|
-
};
|
|
932
|
-
window.addEventListener("ds:latex-build", handler);
|
|
933
|
-
return () => {
|
|
934
|
-
window.removeEventListener("ds:latex-build", handler);
|
|
935
|
-
};
|
|
936
|
-
}, [latexFolderId, projectId]);
|
|
937
|
-
reactExports.useEffect(() => {
|
|
938
|
-
if (!projectId || !latexFolderId) return;
|
|
939
|
-
if (buildId) return;
|
|
940
|
-
let cancelled = false;
|
|
941
|
-
(async () => {
|
|
942
|
-
try {
|
|
943
|
-
const builds = await listLatexBuilds(projectId, latexFolderId, 1);
|
|
944
|
-
if (cancelled) return;
|
|
945
|
-
const latest = builds?.[0];
|
|
946
|
-
if (latest?.build_id) {
|
|
947
|
-
setBuildId(latest.build_id);
|
|
948
|
-
setCompiler(normalizeCompiler(latest.compiler));
|
|
949
|
-
setBuildStatus(latest.status ?? "idle");
|
|
950
|
-
setBuildError(latest.error_message ?? null);
|
|
951
|
-
setBuildErrors(normalizeBuildErrors(latest.errors, latest.log_items));
|
|
952
|
-
}
|
|
953
|
-
} catch {
|
|
954
|
-
}
|
|
955
|
-
})();
|
|
956
|
-
return () => {
|
|
957
|
-
cancelled = true;
|
|
958
|
-
};
|
|
959
|
-
}, [buildId, latexFolderId, projectId]);
|
|
960
|
-
reactExports.useEffect(() => {
|
|
961
|
-
if (!projectId || !latexFolderId || !buildId) return;
|
|
962
|
-
let cancelled = false;
|
|
963
|
-
let timer = null;
|
|
964
|
-
const poll = async () => {
|
|
965
|
-
try {
|
|
966
|
-
const res = await getLatexBuild(projectId, latexFolderId, buildId);
|
|
967
|
-
if (cancelled) return;
|
|
968
|
-
setCompiler(normalizeCompiler(res.compiler));
|
|
969
|
-
setBuildStatus(res.status);
|
|
970
|
-
setBuildError(res.error_message ?? null);
|
|
971
|
-
setBuildErrors(normalizeBuildErrors(res.errors, res.log_items));
|
|
972
|
-
if (res.status === "success" && res.pdf_ready) {
|
|
973
|
-
if (lastLoadedPdfBuildIdRef.current !== buildId) {
|
|
974
|
-
try {
|
|
975
|
-
const blob = await getLatexBuildPdfBlob(projectId, latexFolderId, buildId);
|
|
976
|
-
if (cancelled) return;
|
|
977
|
-
const nextUrl = URL.createObjectURL(blob);
|
|
978
|
-
if (pdfUrlRef.current) {
|
|
979
|
-
try {
|
|
980
|
-
URL.revokeObjectURL(pdfUrlRef.current);
|
|
981
|
-
} catch {
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
pdfUrlRef.current = nextUrl;
|
|
985
|
-
setPdfObjectUrl(nextUrl);
|
|
986
|
-
lastLoadedPdfBuildIdRef.current = buildId;
|
|
987
|
-
} catch (e) {
|
|
988
|
-
console.warn("[LatexPlugin] Failed to fetch PDF:", e);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
if (res.status === "error" && res.log_ready && !logText) {
|
|
993
|
-
try {
|
|
994
|
-
const txt = await getLatexBuildLogText(projectId, latexFolderId, buildId);
|
|
995
|
-
if (cancelled) return;
|
|
996
|
-
setLogText(txt);
|
|
997
|
-
} catch (e) {
|
|
998
|
-
console.warn("[LatexPlugin] Failed to fetch log:", e);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
if (res.status === "queued" || res.status === "running") {
|
|
1002
|
-
timer = window.setTimeout(poll, 1e3);
|
|
1003
|
-
}
|
|
1004
|
-
} catch (e) {
|
|
1005
|
-
if (cancelled) return;
|
|
1006
|
-
timer = window.setTimeout(poll, 1500);
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1009
|
-
poll();
|
|
1010
|
-
return () => {
|
|
1011
|
-
cancelled = true;
|
|
1012
|
-
if (timer != null) window.clearTimeout(timer);
|
|
1013
|
-
};
|
|
1014
|
-
}, [buildId, latexFolderId, logText, projectId]);
|
|
1015
|
-
reactExports.useEffect(() => {
|
|
1016
|
-
return () => {
|
|
1017
|
-
if (pdfUrlRef.current) {
|
|
1018
|
-
try {
|
|
1019
|
-
URL.revokeObjectURL(pdfUrlRef.current);
|
|
1020
|
-
} catch {
|
|
1021
|
-
}
|
|
1022
|
-
pdfUrlRef.current = null;
|
|
1023
|
-
}
|
|
1024
|
-
};
|
|
1025
|
-
}, []);
|
|
1026
|
-
reactExports.useEffect(() => {
|
|
1027
|
-
setDirty(isDirty);
|
|
1028
|
-
}, [isDirty, setDirty]);
|
|
1029
|
-
reactExports.useEffect(() => {
|
|
1030
|
-
if (typeof window === "undefined") return;
|
|
1031
|
-
const media = window.matchMedia("(min-width: 1024px)");
|
|
1032
|
-
const legacyMedia = media;
|
|
1033
|
-
const handleChange = () => setIsWideLayout(media.matches);
|
|
1034
|
-
handleChange();
|
|
1035
|
-
if (typeof media.addEventListener === "function") {
|
|
1036
|
-
media.addEventListener("change", handleChange);
|
|
1037
|
-
return () => media.removeEventListener("change", handleChange);
|
|
1038
|
-
}
|
|
1039
|
-
legacyMedia.addListener?.(handleChange);
|
|
1040
|
-
return () => legacyMedia.removeListener?.(handleChange);
|
|
1041
|
-
}, []);
|
|
1042
|
-
reactExports.useEffect(() => {
|
|
1043
|
-
const el = pdfPaneRef.current;
|
|
1044
|
-
if (!el) return;
|
|
1045
|
-
let rafId = null;
|
|
1046
|
-
const observer = new ResizeObserver((entries) => {
|
|
1047
|
-
const entry = entries[0];
|
|
1048
|
-
if (!entry) return;
|
|
1049
|
-
if (rafId != null) window.cancelAnimationFrame(rafId);
|
|
1050
|
-
rafId = window.requestAnimationFrame(() => {
|
|
1051
|
-
setPdfPaneWidth(entry.contentRect.width);
|
|
1052
|
-
});
|
|
1053
|
-
});
|
|
1054
|
-
observer.observe(el);
|
|
1055
|
-
return () => {
|
|
1056
|
-
if (rafId != null) window.cancelAnimationFrame(rafId);
|
|
1057
|
-
observer.disconnect();
|
|
1058
|
-
};
|
|
1059
|
-
}, []);
|
|
1060
|
-
const pdfFileName = reactExports.useMemo(() => {
|
|
1061
|
-
const base = activeFileName ? activeFileName.replace(/\.tex$/i, "") : "document";
|
|
1062
|
-
return `${base}.pdf`;
|
|
1063
|
-
}, [activeFileName]);
|
|
1064
|
-
const warningItems = reactExports.useMemo(
|
|
1065
|
-
() => buildErrors.filter((err) => err.severity === "warning"),
|
|
1066
|
-
[buildErrors]
|
|
1067
|
-
);
|
|
1068
|
-
const compilerLabel = reactExports.useMemo(() => {
|
|
1069
|
-
return t(`compiler_${compiler}`);
|
|
1070
|
-
}, [compiler, t]);
|
|
1071
|
-
const statusBadge = reactExports.useMemo(() => {
|
|
1072
|
-
if (effectiveReadOnly) {
|
|
1073
|
-
return {
|
|
1074
|
-
label: t("status_read_only"),
|
|
1075
|
-
className: "border-black/10 bg-white/60 text-muted-foreground dark:bg-white/[0.04] dark:border-white/10"
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
if (buildStatus === "queued" || buildStatus === "running") {
|
|
1079
|
-
return {
|
|
1080
|
-
label: t("status_compiling"),
|
|
1081
|
-
className: "border-[#8FA3B8]/30 bg-[#8FA3B8]/10 text-[#52667a] dark:bg-[#8FA3B8]/12 dark:text-[#c8d4df]"
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
if (saveState === "saving") {
|
|
1085
|
-
return {
|
|
1086
|
-
label: t("status_saving"),
|
|
1087
|
-
className: "border-[#A6B0B6]/30 bg-[#A6B0B6]/12 text-[#5c666b] dark:bg-[#A6B0B6]/12 dark:text-[#d8dde0]"
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
if (isDirty) {
|
|
1091
|
-
return {
|
|
1092
|
-
label: t("status_unsaved"),
|
|
1093
|
-
className: "border-[#B7A59A]/30 bg-[#B7A59A]/12 text-[#7e695d] dark:bg-[#B7A59A]/12 dark:text-[#eadfd8]"
|
|
1094
|
-
};
|
|
1095
|
-
}
|
|
1096
|
-
if (buildStatus === "error") {
|
|
1097
|
-
return {
|
|
1098
|
-
label: t("status_compile_failed"),
|
|
1099
|
-
className: "border-red-400/30 bg-red-50/80 text-red-600 dark:bg-red-500/10 dark:text-red-200"
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
return {
|
|
1103
|
-
label: t("status_saved"),
|
|
1104
|
-
className: "border-[#9AA79A]/30 bg-[#9AA79A]/12 text-[#5f6b5f] dark:bg-[#9AA79A]/12 dark:text-[#dbe4db]"
|
|
1105
|
-
};
|
|
1106
|
-
}, [buildStatus, effectiveReadOnly, isDirty, saveState, t]);
|
|
1107
|
-
const buildFocusedIssue = reactExports.useCallback(
|
|
1108
|
-
(issue) => {
|
|
1109
|
-
const targetFileId = resolveLatexFileId(files, issue.path) ?? activeFileId ?? files.find((file) => file.name.toLowerCase() === "main.tex")?.id ?? files[0]?.id ?? null;
|
|
1110
|
-
const targetMeta = targetFileId ? files.find((file) => file.id === targetFileId) ?? null : null;
|
|
1111
|
-
const resourceName = targetMeta?.name || issue.path || activeFileName || "main.tex";
|
|
1112
|
-
const normalizedPath = targetMeta?.path ? toFilesResourcePath(targetMeta.path) : issue.path ? toFilesResourcePath(issue.path) : "";
|
|
1113
|
-
const severity = issue.severity === "warning" ? "warning" : "error";
|
|
1114
|
-
return {
|
|
1115
|
-
kind: "latex_error",
|
|
1116
|
-
tabId,
|
|
1117
|
-
fileId: targetFileId || void 0,
|
|
1118
|
-
resourceId: targetFileId || void 0,
|
|
1119
|
-
resourcePath: normalizedPath && normalizedPath !== "/FILES" ? normalizedPath : void 0,
|
|
1120
|
-
resourceName,
|
|
1121
|
-
line: typeof issue.line === "number" ? Math.max(1, Number(issue.line || 1)) : void 0,
|
|
1122
|
-
message: issue.message,
|
|
1123
|
-
severity,
|
|
1124
|
-
excerpt: issue.path || issue.line ? `${issue.path || resourceName}${issue.line ? `:${issue.line}` : ""}` : void 0,
|
|
1125
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1126
|
-
};
|
|
1127
|
-
},
|
|
1128
|
-
[activeFileId, activeFileName, files, tabId]
|
|
1129
|
-
);
|
|
1130
|
-
const focusBuildIssue = reactExports.useCallback(
|
|
1131
|
-
(issue) => {
|
|
1132
|
-
const focusedIssue = buildFocusedIssue(issue);
|
|
1133
|
-
setWorkspaceActiveIssue(tabId, focusedIssue);
|
|
1134
|
-
return focusedIssue;
|
|
1135
|
-
},
|
|
1136
|
-
[buildFocusedIssue, setWorkspaceActiveIssue, tabId]
|
|
1137
|
-
);
|
|
1138
|
-
reactExports.useEffect(() => {
|
|
1139
|
-
if (buildStatus === "success") {
|
|
1140
|
-
setWorkspaceActiveIssue(tabId, null);
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
if (buildErrors.length === 0) {
|
|
1144
|
-
if (buildStatus === "error") {
|
|
1145
|
-
setWorkspaceActiveIssue(tabId, null);
|
|
1146
|
-
}
|
|
1147
|
-
return;
|
|
1148
|
-
}
|
|
1149
|
-
const preferredIssue = buildErrors.find((issue) => issue.severity !== "warning") ?? buildErrors[0] ?? null;
|
|
1150
|
-
if (!preferredIssue) return;
|
|
1151
|
-
const currentFocusedIssue = useWorkspaceSurfaceStore.getState().activeIssueByTabId[tabId];
|
|
1152
|
-
const matchingIssue = currentFocusedIssue?.kind === "latex_error" ? buildErrors.find(
|
|
1153
|
-
(issue) => getLatexIssueIdentity(buildFocusedIssue(issue)) === getLatexIssueIdentity(currentFocusedIssue)
|
|
1154
|
-
) ?? null : null;
|
|
1155
|
-
const nextFocusedIssue = buildFocusedIssue(matchingIssue ?? preferredIssue);
|
|
1156
|
-
if (currentFocusedIssue && getLatexIssueIdentity(currentFocusedIssue) === getLatexIssueIdentity(nextFocusedIssue)) {
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
setWorkspaceActiveIssue(tabId, nextFocusedIssue);
|
|
1160
|
-
}, [buildErrors, buildFocusedIssue, buildStatus, setWorkspaceActiveIssue, tabId]);
|
|
1161
|
-
const handleBuildIssueClick = reactExports.useCallback(
|
|
1162
|
-
(issue) => {
|
|
1163
|
-
const focusedIssue = focusBuildIssue(issue);
|
|
1164
|
-
const targetFileId = focusedIssue?.fileId ?? resolveLatexFileId(files, issue.path) ?? activeFileId ?? files.find((file) => file.name.toLowerCase() === "main.tex")?.id ?? files[0]?.id ?? null;
|
|
1165
|
-
if (!targetFileId) return;
|
|
1166
|
-
const targetMeta = files.find((file) => file.id === targetFileId);
|
|
1167
|
-
pendingJumpRef.current = {
|
|
1168
|
-
fileId: targetFileId,
|
|
1169
|
-
line: Math.max(1, Number(issue.line || 1))
|
|
1170
|
-
};
|
|
1171
|
-
if (targetMeta?.name && targetMeta.name !== activeFileName) {
|
|
1172
|
-
setActiveFileName(targetMeta.name);
|
|
1173
|
-
}
|
|
1174
|
-
if (targetFileId !== activeFileId) {
|
|
1175
|
-
setActiveFileId(targetFileId);
|
|
1176
|
-
return;
|
|
1177
|
-
}
|
|
1178
|
-
flushPendingJump();
|
|
1179
|
-
},
|
|
1180
|
-
[activeFileId, activeFileName, files, flushPendingJump, focusBuildIssue]
|
|
1181
|
-
);
|
|
1182
|
-
const handleAskCopilotForIssue = reactExports.useCallback(
|
|
1183
|
-
(issue) => {
|
|
1184
|
-
const focusedIssue = focusBuildIssue(issue);
|
|
1185
|
-
const severityLabel = issue.severity === "warning" ? t("warning_badge") : t("error_badge");
|
|
1186
|
-
const issueLocation = focusedIssue?.line && focusedIssue.resourceName ? `${focusedIssue.resourceName}:${focusedIssue.line}` : focusedIssue?.resourceName || issue.path || activeFileName || "main.tex";
|
|
1187
|
-
const prompt = t("issue_action_prompt", {
|
|
1188
|
-
severity: severityLabel,
|
|
1189
|
-
location: issueLocation,
|
|
1190
|
-
message: issue.message
|
|
1191
|
-
});
|
|
1192
|
-
window.dispatchEvent(
|
|
1193
|
-
new CustomEvent("ds:copilot:run", {
|
|
1194
|
-
detail: {
|
|
1195
|
-
text: prompt,
|
|
1196
|
-
focus: true,
|
|
1197
|
-
submit: true
|
|
1198
|
-
}
|
|
1199
|
-
})
|
|
1200
|
-
);
|
|
1201
|
-
},
|
|
1202
|
-
[activeFileName, focusBuildIssue, t]
|
|
1203
|
-
);
|
|
1204
|
-
const handleFixIssueWithAi = reactExports.useCallback(
|
|
1205
|
-
(issue) => {
|
|
1206
|
-
if (!latexFolderId || effectiveReadOnly) return;
|
|
1207
|
-
const focusedIssue = focusBuildIssue(issue);
|
|
1208
|
-
const severityLabel = issue.severity === "warning" ? t("warning_badge") : t("error_badge");
|
|
1209
|
-
const issueLocation = focusedIssue?.line && focusedIssue.resourceName ? `${focusedIssue.resourceName}:${focusedIssue.line}` : focusedIssue?.resourceName || issue.path || activeFileName || "main.tex";
|
|
1210
|
-
const promptText = t("issue_fix_prompt", {
|
|
1211
|
-
severity: severityLabel,
|
|
1212
|
-
location: issueLocation,
|
|
1213
|
-
message: issue.message
|
|
1214
|
-
});
|
|
1215
|
-
window.dispatchEvent(
|
|
1216
|
-
new CustomEvent("ds:copilot:fix-with-ai", {
|
|
1217
|
-
detail: {
|
|
1218
|
-
folderId: latexFolderId,
|
|
1219
|
-
buildId,
|
|
1220
|
-
focusedError: focusedIssue,
|
|
1221
|
-
promptText
|
|
1222
|
-
}
|
|
1223
|
-
})
|
|
1224
|
-
);
|
|
1225
|
-
},
|
|
1226
|
-
[activeFileName, buildId, effectiveReadOnly, focusBuildIssue, latexFolderId, t]
|
|
1227
|
-
);
|
|
1228
|
-
const renderBuildIssueRow = reactExports.useCallback(
|
|
1229
|
-
(issue, idx, scope) => {
|
|
1230
|
-
const canJump = Boolean(issue.path || issue.line) && Boolean(activeFileId || files.length);
|
|
1231
|
-
const key = `${issue.path ?? scope}-${issue.line ?? "0"}-${idx}`;
|
|
1232
|
-
const issueContent = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
1233
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1234
|
-
"span",
|
|
1235
|
-
{
|
|
1236
|
-
className: cn(
|
|
1237
|
-
"px-1.5 py-0.5 rounded-full text-[10px] uppercase border",
|
|
1238
|
-
issue.severity === "warning" ? "text-amber-700 border-amber-400/40 bg-amber-50/70 dark:bg-amber-500/10" : "text-red-600 border-red-400/40 bg-red-50/70 dark:bg-red-500/10"
|
|
1239
|
-
),
|
|
1240
|
-
children: issue.severity === "warning" ? t("warning_badge") : t("error_badge")
|
|
1241
|
-
}
|
|
1242
|
-
),
|
|
1243
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-mono", children: [
|
|
1244
|
-
issue.path || "main.tex",
|
|
1245
|
-
issue.line ? `:${issue.line}` : ""
|
|
1246
|
-
] }),
|
|
1247
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground break-words", children: issue.message }),
|
|
1248
|
-
canJump ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/80", children: t("issue_hint_clickable") }) : null
|
|
1249
|
-
] });
|
|
1250
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [
|
|
1251
|
-
canJump ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1252
|
-
"button",
|
|
1253
|
-
{
|
|
1254
|
-
type: "button",
|
|
1255
|
-
onClick: () => handleBuildIssueClick(issue),
|
|
1256
|
-
className: "min-w-0 flex flex-1 items-start gap-2 rounded-lg px-2 py-1.5 text-left hover:bg-black/5 dark:hover:bg-white/[0.04]",
|
|
1257
|
-
title: issue.line ? t("issue_jump_to_line", { line: issue.line }) : t("issue_jump_to_file", { file: issue.path || "main.tex" }),
|
|
1258
|
-
children: issueContent
|
|
1259
|
-
}
|
|
1260
|
-
) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-w-0 flex flex-1 items-start gap-2 px-2 py-1.5", children: issueContent }),
|
|
1261
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex shrink-0 items-center gap-1 pt-1", children: [
|
|
1262
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1263
|
-
"button",
|
|
1264
|
-
{
|
|
1265
|
-
type: "button",
|
|
1266
|
-
onClick: () => handleAskCopilotForIssue(issue),
|
|
1267
|
-
className: "rounded-md border border-black/10 dark:border-white/10 bg-white/70 dark:bg-white/[0.05] px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-black/5 dark:hover:bg-white/[0.08]",
|
|
1268
|
-
title: t("issue_action_ask_copilot"),
|
|
1269
|
-
children: t("issue_action_ask_copilot")
|
|
1270
|
-
}
|
|
1271
|
-
),
|
|
1272
|
-
!effectiveReadOnly && latexFolderId ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1273
|
-
"button",
|
|
1274
|
-
{
|
|
1275
|
-
type: "button",
|
|
1276
|
-
onClick: () => handleFixIssueWithAi(issue),
|
|
1277
|
-
className: "rounded-md border border-black/10 dark:border-white/10 bg-white/70 dark:bg-white/[0.05] px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-black/5 dark:hover:bg-white/[0.08]",
|
|
1278
|
-
title: t("issue_action_fix_with_ai"),
|
|
1279
|
-
children: t("issue_action_fix_with_ai")
|
|
1280
|
-
}
|
|
1281
|
-
) : null
|
|
1282
|
-
] })
|
|
1283
|
-
] }, key);
|
|
1284
|
-
},
|
|
1285
|
-
[
|
|
1286
|
-
activeFileId,
|
|
1287
|
-
effectiveReadOnly,
|
|
1288
|
-
files,
|
|
1289
|
-
handleAskCopilotForIssue,
|
|
1290
|
-
handleBuildIssueClick,
|
|
1291
|
-
handleFixIssueWithAi,
|
|
1292
|
-
latexFolderId,
|
|
1293
|
-
t
|
|
1294
|
-
]
|
|
1295
|
-
);
|
|
1296
|
-
const zoomOutDisabled = !pdfObjectUrl || zoomScale <= ZOOM_LEVELS[0];
|
|
1297
|
-
const zoomInDisabled = !pdfObjectUrl || zoomScale >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1];
|
|
1298
|
-
const handleZoomOut = () => {
|
|
1299
|
-
const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= zoomScale);
|
|
1300
|
-
const safeIndex = currentIndex === -1 ? ZOOM_LEVELS.length - 1 : currentIndex;
|
|
1301
|
-
if (safeIndex <= 0) return;
|
|
1302
|
-
setZoomScale(ZOOM_LEVELS[safeIndex - 1]);
|
|
1303
|
-
};
|
|
1304
|
-
const handleZoomIn = () => {
|
|
1305
|
-
const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= zoomScale);
|
|
1306
|
-
const safeIndex = currentIndex === -1 ? ZOOM_LEVELS.length - 1 : currentIndex;
|
|
1307
|
-
if (safeIndex >= ZOOM_LEVELS.length - 1) return;
|
|
1308
|
-
setZoomScale(ZOOM_LEVELS[safeIndex + 1]);
|
|
1309
|
-
};
|
|
1310
|
-
const fitScale = reactExports.useMemo(() => {
|
|
1311
|
-
if (!pdfPaneWidth || !pdfPageWidth) return 1;
|
|
1312
|
-
const paddedWidth = Math.max(pdfPaneWidth - 32, 120);
|
|
1313
|
-
return Math.max(paddedWidth / pdfPageWidth, 0.2);
|
|
1314
|
-
}, [pdfPaneWidth, pdfPageWidth]);
|
|
1315
|
-
const renderScale = fitScale * zoomScale;
|
|
1316
|
-
const handlePageWidth = reactExports.useCallback((width) => {
|
|
1317
|
-
setPdfPageWidth(width || PAGE_DIMENSIONS.A4_WIDTH);
|
|
1318
|
-
}, []);
|
|
1319
|
-
const handleResizeStart = reactExports.useCallback(
|
|
1320
|
-
(event) => {
|
|
1321
|
-
if (!isWideLayout || !splitContainerRef.current) return;
|
|
1322
|
-
if (event.button !== 0) return;
|
|
1323
|
-
event.preventDefault();
|
|
1324
|
-
const container = splitContainerRef.current;
|
|
1325
|
-
const rect = container.getBoundingClientRect();
|
|
1326
|
-
const startX = event.clientX;
|
|
1327
|
-
const startLeft = rect.width * splitRatio;
|
|
1328
|
-
const minLeft = 360;
|
|
1329
|
-
const minRight = 320;
|
|
1330
|
-
const maxLeft = Math.max(rect.width - minRight, minLeft);
|
|
1331
|
-
const prevCursor = document.body.style.cursor;
|
|
1332
|
-
const prevSelect = document.body.style.userSelect;
|
|
1333
|
-
document.body.style.cursor = "col-resize";
|
|
1334
|
-
document.body.style.userSelect = "none";
|
|
1335
|
-
setIsResizing(true);
|
|
1336
|
-
let rafId = null;
|
|
1337
|
-
const onMove = (moveEvent) => {
|
|
1338
|
-
const nextLeft = startLeft + (moveEvent.clientX - startX);
|
|
1339
|
-
const clamped = Math.min(Math.max(nextLeft, minLeft), maxLeft);
|
|
1340
|
-
const nextRatio = clamped / rect.width;
|
|
1341
|
-
if (rafId != null) window.cancelAnimationFrame(rafId);
|
|
1342
|
-
rafId = window.requestAnimationFrame(() => {
|
|
1343
|
-
setSplitRatio(nextRatio);
|
|
1344
|
-
});
|
|
1345
|
-
};
|
|
1346
|
-
const onUp = () => {
|
|
1347
|
-
if (rafId != null) window.cancelAnimationFrame(rafId);
|
|
1348
|
-
setIsResizing(false);
|
|
1349
|
-
document.body.style.cursor = prevCursor;
|
|
1350
|
-
document.body.style.userSelect = prevSelect;
|
|
1351
|
-
window.removeEventListener("pointermove", onMove);
|
|
1352
|
-
window.removeEventListener("pointerup", onUp);
|
|
1353
|
-
};
|
|
1354
|
-
window.addEventListener("pointermove", onMove);
|
|
1355
|
-
window.addEventListener("pointerup", onUp);
|
|
1356
|
-
},
|
|
1357
|
-
[isWideLayout, splitRatio]
|
|
1358
|
-
);
|
|
1359
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex flex-col bg-white/70 dark:bg-black/30", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1360
|
-
"div",
|
|
1361
|
-
{
|
|
1362
|
-
ref: splitContainerRef,
|
|
1363
|
-
className: "flex-1 min-h-0 flex flex-col lg:flex-row",
|
|
1364
|
-
children: [
|
|
1365
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1366
|
-
"div",
|
|
1367
|
-
{
|
|
1368
|
-
className: cn(
|
|
1369
|
-
"min-h-0 flex flex-col min-w-0",
|
|
1370
|
-
isWideLayout ? "lg:border-r-0" : "border-b border-black/5 dark:border-white/10",
|
|
1371
|
-
isResizing ? "transition-none" : "transition-[flex-basis] duration-200 ease-out"
|
|
1372
|
-
),
|
|
1373
|
-
style: isWideLayout ? {
|
|
1374
|
-
flexBasis: `${splitRatio * 100}%`,
|
|
1375
|
-
flexGrow: 0,
|
|
1376
|
-
flexShrink: 0
|
|
1377
|
-
} : void 0,
|
|
1378
|
-
children: [
|
|
1379
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2 px-3 py-2 border-b border-black/5 dark:border-white/10", children: [
|
|
1380
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
1381
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1382
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1383
|
-
"select",
|
|
1384
|
-
{
|
|
1385
|
-
className: cn(
|
|
1386
|
-
"h-8 rounded-lg px-2 text-sm bg-white/70 border border-black/10",
|
|
1387
|
-
"dark:bg-white/[0.04] dark:border-white/10",
|
|
1388
|
-
"min-w-[160px] max-w-[260px] truncate",
|
|
1389
|
-
effectiveReadOnly && "opacity-70"
|
|
1390
|
-
),
|
|
1391
|
-
value: activeFileId ?? "",
|
|
1392
|
-
onChange: (e) => {
|
|
1393
|
-
const next = e.target.value || null;
|
|
1394
|
-
const meta = files.find((f) => f.id === next);
|
|
1395
|
-
if (meta) setActiveFileName(meta.name);
|
|
1396
|
-
setActiveFileId(next);
|
|
1397
|
-
},
|
|
1398
|
-
disabled: files.length === 0,
|
|
1399
|
-
"aria-label": t("file_label"),
|
|
1400
|
-
title: activeFileName,
|
|
1401
|
-
children: files.map((f) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: f.id, children: f.name }, f.id))
|
|
1402
|
-
}
|
|
1403
|
-
)
|
|
1404
|
-
] }),
|
|
1405
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1406
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground", children: t("compiler_label") }),
|
|
1407
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1408
|
-
"select",
|
|
1409
|
-
{
|
|
1410
|
-
className: cn(
|
|
1411
|
-
"h-8 rounded-lg px-2 text-sm bg-white/70 border border-black/10",
|
|
1412
|
-
"dark:bg-white/[0.04] dark:border-white/10",
|
|
1413
|
-
"min-w-[122px]",
|
|
1414
|
-
buildStatus === "queued" || buildStatus === "running" ? "opacity-70" : ""
|
|
1415
|
-
),
|
|
1416
|
-
value: compiler,
|
|
1417
|
-
onChange: (event) => setCompiler(normalizeCompiler(event.target.value)),
|
|
1418
|
-
disabled: buildStatus === "queued" || buildStatus === "running",
|
|
1419
|
-
"aria-label": t("compiler_label"),
|
|
1420
|
-
title: compilerLabel,
|
|
1421
|
-
children: LATEX_COMPILER_OPTIONS.map((option) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: option, children: t(`compiler_${option}`) }, option))
|
|
1422
|
-
}
|
|
1423
|
-
)
|
|
1424
|
-
] }),
|
|
1425
|
-
!isBibFile ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1426
|
-
"button",
|
|
1427
|
-
{
|
|
1428
|
-
type: "button",
|
|
1429
|
-
onClick: () => {
|
|
1430
|
-
setReferencePanelOpen((current) => {
|
|
1431
|
-
const next = !current;
|
|
1432
|
-
if (next) setBibPanelOpen(false);
|
|
1433
|
-
if (!next) setAssistQuery("");
|
|
1434
|
-
return next;
|
|
1435
|
-
});
|
|
1436
|
-
},
|
|
1437
|
-
className: cn(
|
|
1438
|
-
"h-8 px-3 rounded-lg text-sm border inline-flex items-center gap-2",
|
|
1439
|
-
"bg-white/70 border-black/10 hover:bg-white/90",
|
|
1440
|
-
"dark:bg-white/[0.04] dark:border-white/10 dark:hover:bg-white/[0.08]",
|
|
1441
|
-
referencePanelOpen && "border-[#8FA3B8]/28 bg-[#8FA3B8]/12 text-[#405267]"
|
|
1442
|
-
),
|
|
1443
|
-
"aria-label": t("assist_references"),
|
|
1444
|
-
title: t("assist_references"),
|
|
1445
|
-
children: [
|
|
1446
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Link2, { className: "h-4 w-4" }),
|
|
1447
|
-
t("assist_references")
|
|
1448
|
-
]
|
|
1449
|
-
}
|
|
1450
|
-
) : null,
|
|
1451
|
-
isBibFile ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1452
|
-
"button",
|
|
1453
|
-
{
|
|
1454
|
-
type: "button",
|
|
1455
|
-
onClick: () => {
|
|
1456
|
-
setBibPanelOpen((current) => {
|
|
1457
|
-
const next = !current;
|
|
1458
|
-
if (next) setReferencePanelOpen(false);
|
|
1459
|
-
if (!next) setAssistQuery("");
|
|
1460
|
-
return next;
|
|
1461
|
-
});
|
|
1462
|
-
},
|
|
1463
|
-
className: cn(
|
|
1464
|
-
"h-8 px-3 rounded-lg text-sm border inline-flex items-center gap-2",
|
|
1465
|
-
"bg-white/70 border-black/10 hover:bg-white/90",
|
|
1466
|
-
"dark:bg-white/[0.04] dark:border-white/10 dark:hover:bg-white/[0.08]",
|
|
1467
|
-
bibPanelOpen && "border-[#A99EBE]/28 bg-[#A99EBE]/12 text-[#564f6a]"
|
|
1468
|
-
),
|
|
1469
|
-
"aria-label": t("assist_bibtex"),
|
|
1470
|
-
title: t("assist_bibtex"),
|
|
1471
|
-
children: [
|
|
1472
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(AtSign, { className: "h-4 w-4" }),
|
|
1473
|
-
t("assist_bibtex")
|
|
1474
|
-
]
|
|
1475
|
-
}
|
|
1476
|
-
) : null,
|
|
1477
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
|
|
1478
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1479
|
-
"span",
|
|
1480
|
-
{
|
|
1481
|
-
className: cn(
|
|
1482
|
-
"text-xs px-2 py-0.5 rounded-full border",
|
|
1483
|
-
statusBadge.className
|
|
1484
|
-
),
|
|
1485
|
-
children: statusBadge.label
|
|
1486
|
-
}
|
|
1487
|
-
),
|
|
1488
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1489
|
-
"button",
|
|
1490
|
-
{
|
|
1491
|
-
type: "button",
|
|
1492
|
-
onClick: save,
|
|
1493
|
-
disabled: effectiveReadOnly || saveState === "saving" || !isDirty,
|
|
1494
|
-
className: cn(
|
|
1495
|
-
"h-8 px-3 rounded-lg text-sm font-medium border",
|
|
1496
|
-
"bg-white/70 border-black/10 hover:bg-white/90",
|
|
1497
|
-
"dark:bg-white/[0.04] dark:border-white/10 dark:hover:bg-white/[0.08]",
|
|
1498
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1499
|
-
isDirty && !effectiveReadOnly && "border-black/20"
|
|
1500
|
-
),
|
|
1501
|
-
children: saveState === "saving" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
1502
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }),
|
|
1503
|
-
t("button_saving")
|
|
1504
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
1505
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "h-4 w-4" }),
|
|
1506
|
-
t("button_save")
|
|
1507
|
-
] })
|
|
1508
|
-
}
|
|
1509
|
-
),
|
|
1510
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1511
|
-
"button",
|
|
1512
|
-
{
|
|
1513
|
-
type: "button",
|
|
1514
|
-
onClick: () => void compile({ auto: false }),
|
|
1515
|
-
disabled: viewReadOnly || saveState === "saving" || buildStatus === "queued" || buildStatus === "running",
|
|
1516
|
-
className: cn(
|
|
1517
|
-
"h-8 px-3 rounded-lg text-sm font-medium border",
|
|
1518
|
-
"bg-[#8FA3B8]/14 border-[#8FA3B8]/28 text-[#405267] hover:bg-[#8FA3B8]/20",
|
|
1519
|
-
"dark:bg-[#8FA3B8]/14 dark:border-[#8FA3B8]/22 dark:text-[#dbe6ef] dark:hover:bg-[#8FA3B8]/20",
|
|
1520
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
1521
|
-
),
|
|
1522
|
-
title: viewReadOnly ? t("compile_disabled_read_only") : isDirty && !effectiveReadOnly ? t("button_save_and_compile") : t("button_compile"),
|
|
1523
|
-
children: buildStatus === "queued" || buildStatus === "running" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
1524
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }),
|
|
1525
|
-
t("button_compiling")
|
|
1526
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
1527
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "h-4 w-4" }),
|
|
1528
|
-
isDirty && !effectiveReadOnly ? t("button_save_and_compile") : t("button_compile")
|
|
1529
|
-
] })
|
|
1530
|
-
}
|
|
1531
|
-
)
|
|
1532
|
-
] })
|
|
1533
|
-
] }),
|
|
1534
|
-
showAssistPanel ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-b border-black/5 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.02] px-3 py-3", children: [
|
|
1535
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
1536
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
|
|
1537
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium text-foreground", children: referencePanelOpen ? t("assist_references_title") : t("assist_bibtex_title") }),
|
|
1538
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground", children: referencePanelOpen ? t("assist_reference_hint") : t("assist_bibtex_hint") })
|
|
1539
|
-
] }),
|
|
1540
|
-
referencePanelOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1541
|
-
"input",
|
|
1542
|
-
{
|
|
1543
|
-
value: assistQuery,
|
|
1544
|
-
onChange: (event) => setAssistQuery(event.target.value),
|
|
1545
|
-
placeholder: t("assist_search_placeholder"),
|
|
1546
|
-
className: cn(
|
|
1547
|
-
"h-8 w-full rounded-lg border border-black/10 bg-white/80 px-3 text-sm",
|
|
1548
|
-
"sm:w-[280px] dark:border-white/10 dark:bg-white/[0.05]"
|
|
1549
|
-
)
|
|
1550
|
-
}
|
|
1551
|
-
) : null
|
|
1552
|
-
] }),
|
|
1553
|
-
referencePanelOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 grid gap-3 lg:grid-cols-2", children: [
|
|
1554
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-black/5 bg-white/70 p-3 dark:border-white/10 dark:bg-white/[0.03]", children: [
|
|
1555
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("assist_citations") }),
|
|
1556
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: filteredCitationIndex.length > 0 ? filteredCitationIndex.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1557
|
-
"button",
|
|
1558
|
-
{
|
|
1559
|
-
type: "button",
|
|
1560
|
-
onClick: () => insertCitation(entry),
|
|
1561
|
-
className: "w-full rounded-lg border border-black/5 bg-black/[0.02] px-3 py-2 text-left hover:bg-black/[0.04] dark:border-white/10 dark:bg-white/[0.03] dark:hover:bg-white/[0.05]",
|
|
1562
|
-
title: entry.title || entry.key,
|
|
1563
|
-
children: [
|
|
1564
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1565
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs text-foreground", children: entry.key }),
|
|
1566
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: t("assist_insert_citation") })
|
|
1567
|
-
] }),
|
|
1568
|
-
entry.title ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-xs text-muted-foreground", children: entry.title }) : null,
|
|
1569
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-[10px] text-muted-foreground/80", children: entry.author || entry.sourceFile })
|
|
1570
|
-
]
|
|
1571
|
-
},
|
|
1572
|
-
`${entry.sourceFile}:${entry.key}`
|
|
1573
|
-
)) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg border border-dashed border-black/10 px-3 py-4 text-xs text-muted-foreground dark:border-white/10", children: t("assist_empty_citations") }) })
|
|
1574
|
-
] }),
|
|
1575
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-black/5 bg-white/70 p-3 dark:border-white/10 dark:bg-white/[0.03]", children: [
|
|
1576
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("assist_labels") }),
|
|
1577
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: filteredLabelIndex.length > 0 ? filteredLabelIndex.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1578
|
-
"button",
|
|
1579
|
-
{
|
|
1580
|
-
type: "button",
|
|
1581
|
-
onClick: () => insertLabelReference(entry),
|
|
1582
|
-
className: "w-full rounded-lg border border-black/5 bg-black/[0.02] px-3 py-2 text-left hover:bg-black/[0.04] dark:border-white/10 dark:bg-white/[0.03] dark:hover:bg-white/[0.05]",
|
|
1583
|
-
title: entry.key,
|
|
1584
|
-
children: [
|
|
1585
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1586
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs text-foreground", children: entry.key }),
|
|
1587
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: t("assist_insert_reference") })
|
|
1588
|
-
] }),
|
|
1589
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-[10px] text-muted-foreground/80", children: entry.sourceFile })
|
|
1590
|
-
]
|
|
1591
|
-
},
|
|
1592
|
-
`${entry.sourceFile}:${entry.key}`
|
|
1593
|
-
)) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg border border-dashed border-black/10 px-3 py-4 text-xs text-muted-foreground dark:border-white/10", children: t("assist_empty_labels") }) })
|
|
1594
|
-
] })
|
|
1595
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 grid gap-2 md:grid-cols-3", children: BIB_SNIPPETS.length > 0 ? BIB_SNIPPETS.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1596
|
-
"button",
|
|
1597
|
-
{
|
|
1598
|
-
type: "button",
|
|
1599
|
-
onClick: () => insertBibSnippet(item.snippet),
|
|
1600
|
-
className: "rounded-xl border border-black/5 bg-white/70 px-3 py-3 text-left hover:bg-white/90 dark:border-white/10 dark:bg-white/[0.03] dark:hover:bg-white/[0.05]",
|
|
1601
|
-
children: [
|
|
1602
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1603
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-sm text-foreground", children: [
|
|
1604
|
-
"@",
|
|
1605
|
-
item.id
|
|
1606
|
-
] }),
|
|
1607
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: t("assist_insert_snippet") })
|
|
1608
|
-
] }),
|
|
1609
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-xs text-muted-foreground", children: t(item.labelKey) })
|
|
1610
|
-
]
|
|
1611
|
-
},
|
|
1612
|
-
item.id
|
|
1613
|
-
)) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg border border-dashed border-black/10 px-3 py-4 text-xs text-muted-foreground dark:border-white/10", children: t("assist_empty_bib") }) })
|
|
1614
|
-
] }) : null,
|
|
1615
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: syncState === "loading" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex items-center justify-center text-muted-foreground", children: [
|
|
1616
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-5 w-5 animate-spin mr-2" }),
|
|
1617
|
-
t("connecting")
|
|
1618
|
-
] }) : syncState === "error" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center text-sm text-red-600", children: error ?? t("load_files_failed") }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1619
|
-
MonacoEditor,
|
|
1620
|
-
{
|
|
1621
|
-
defaultValue: initialText,
|
|
1622
|
-
language: "plaintext",
|
|
1623
|
-
theme: isDark ? "vs-dark" : "light",
|
|
1624
|
-
onMount: (editor, monaco) => {
|
|
1625
|
-
try {
|
|
1626
|
-
bindEditor(editor, monaco);
|
|
1627
|
-
} catch (e) {
|
|
1628
|
-
console.error("[LatexPlugin] Failed to bind editor:", e);
|
|
1629
|
-
}
|
|
1630
|
-
},
|
|
1631
|
-
options: {
|
|
1632
|
-
readOnly: effectiveReadOnly,
|
|
1633
|
-
minimap: { enabled: false },
|
|
1634
|
-
wordWrap: "on",
|
|
1635
|
-
fontSize: 13,
|
|
1636
|
-
scrollBeyondLastLine: false
|
|
1637
|
-
}
|
|
1638
|
-
},
|
|
1639
|
-
`${activeFileId ?? "latex"}:${resetNonce}`
|
|
1640
|
-
) }),
|
|
1641
|
-
buildStatus === "error" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-black/5 dark:border-white/10 p-4 text-sm max-h-[35vh] overflow-auto", children: [
|
|
1642
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 text-red-600", children: [
|
|
1643
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "h-4 w-4 mt-0.5" }),
|
|
1644
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
|
|
1645
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium", children: t("compile_failed_title") }),
|
|
1646
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs opacity-90 break-words", children: buildError ?? t("compile_failed_fallback") })
|
|
1647
|
-
] })
|
|
1648
|
-
] }),
|
|
1649
|
-
buildErrors.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 space-y-2 text-xs", children: [
|
|
1650
|
-
buildErrors.slice(0, 12).map((err, idx) => renderBuildIssueRow(err, idx, "error")),
|
|
1651
|
-
buildErrors.length > 12 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground", children: t("more_items", { count: buildErrors.length - 12 }) }) : null
|
|
1652
|
-
] }) : null,
|
|
1653
|
-
logText ? /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "mt-3 text-xs whitespace-pre-wrap break-words text-muted-foreground max-h-[40vh] overflow-auto rounded-lg border border-black/10 dark:border-white/10 bg-white/60 dark:bg-white/[0.03] p-3", children: logText.slice(0, 8e3) }) : null
|
|
1654
|
-
] }) : null,
|
|
1655
|
-
buildStatus !== "error" && warningItems.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-black/5 dark:border-white/10 p-4 text-sm max-h-[35vh] overflow-auto", children: [
|
|
1656
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 text-amber-700", children: [
|
|
1657
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "h-4 w-4 mt-0.5" }),
|
|
1658
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
|
|
1659
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium", children: t("warnings_title") }),
|
|
1660
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs opacity-90 break-words", children: t("warnings_reported", {
|
|
1661
|
-
count: warningItems.length,
|
|
1662
|
-
suffix: language === "zh-CN" || warningItems.length === 1 ? "" : "s"
|
|
1663
|
-
}) })
|
|
1664
|
-
] })
|
|
1665
|
-
] }),
|
|
1666
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 space-y-2 text-xs", children: [
|
|
1667
|
-
warningItems.slice(0, 12).map((err, idx) => renderBuildIssueRow(err, idx, "warning")),
|
|
1668
|
-
warningItems.length > 12 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground", children: t("more_items", { count: warningItems.length - 12 }) }) : null
|
|
1669
|
-
] })
|
|
1670
|
-
] }) : null
|
|
1671
|
-
]
|
|
1672
|
-
}
|
|
1673
|
-
),
|
|
1674
|
-
isWideLayout ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1675
|
-
"div",
|
|
1676
|
-
{
|
|
1677
|
-
className: cn(
|
|
1678
|
-
"hidden lg:flex w-3 relative items-stretch cursor-col-resize",
|
|
1679
|
-
isResizing ? "bg-primary/10" : "hover:bg-black/5 dark:hover:bg-white/[0.06]"
|
|
1680
|
-
),
|
|
1681
|
-
onPointerDown: handleResizeStart,
|
|
1682
|
-
role: "separator",
|
|
1683
|
-
"aria-orientation": "vertical",
|
|
1684
|
-
"aria-label": t("resize_panels_aria"),
|
|
1685
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1686
|
-
"div",
|
|
1687
|
-
{
|
|
1688
|
-
className: cn(
|
|
1689
|
-
"absolute inset-y-0 left-1/2 w-px",
|
|
1690
|
-
isResizing ? "bg-primary/60" : "bg-black/10 dark:bg-white/10"
|
|
1691
|
-
)
|
|
1692
|
-
}
|
|
1693
|
-
)
|
|
1694
|
-
}
|
|
1695
|
-
) : null,
|
|
1696
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-h-0 flex flex-1 flex-col min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: pdfPaneRef, className: "relative flex-1 min-h-0 overflow-hidden", children: [
|
|
1697
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1698
|
-
"div",
|
|
1699
|
-
{
|
|
1700
|
-
className: cn(
|
|
1701
|
-
"absolute z-10 flex flex-col gap-2",
|
|
1702
|
-
isWideLayout ? "left-3 top-1/2 -translate-y-1/2" : "right-3 top-3",
|
|
1703
|
-
"rounded-full border border-black/10 bg-white/80 p-1 shadow-sm",
|
|
1704
|
-
"backdrop-blur dark:border-white/10 dark:bg-black/40"
|
|
1705
|
-
),
|
|
1706
|
-
onPointerDown: (event) => event.stopPropagation(),
|
|
1707
|
-
children: [
|
|
1708
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1709
|
-
"button",
|
|
1710
|
-
{
|
|
1711
|
-
type: "button",
|
|
1712
|
-
onClick: handleZoomOut,
|
|
1713
|
-
disabled: zoomOutDisabled,
|
|
1714
|
-
className: cn(
|
|
1715
|
-
"h-7 w-7 rounded-full flex items-center justify-center",
|
|
1716
|
-
"text-muted-foreground hover:text-foreground hover:bg-black/5",
|
|
1717
|
-
"dark:hover:bg-white/[0.08]",
|
|
1718
|
-
"disabled:opacity-40 disabled:cursor-not-allowed"
|
|
1719
|
-
),
|
|
1720
|
-
"aria-label": t("zoom_out"),
|
|
1721
|
-
title: t("zoom_out"),
|
|
1722
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { className: "h-4 w-4" })
|
|
1723
|
-
}
|
|
1724
|
-
),
|
|
1725
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] font-medium text-muted-foreground text-center", children: pdfObjectUrl ? `${Math.round(renderScale * 100)}%` : "--" }),
|
|
1726
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1727
|
-
"button",
|
|
1728
|
-
{
|
|
1729
|
-
type: "button",
|
|
1730
|
-
onClick: handleZoomIn,
|
|
1731
|
-
disabled: zoomInDisabled,
|
|
1732
|
-
className: cn(
|
|
1733
|
-
"h-7 w-7 rounded-full flex items-center justify-center",
|
|
1734
|
-
"text-muted-foreground hover:text-foreground hover:bg-black/5",
|
|
1735
|
-
"dark:hover:bg-white/[0.08]",
|
|
1736
|
-
"disabled:opacity-40 disabled:cursor-not-allowed"
|
|
1737
|
-
),
|
|
1738
|
-
"aria-label": t("zoom_in"),
|
|
1739
|
-
title: t("zoom_in"),
|
|
1740
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { className: "h-4 w-4" })
|
|
1741
|
-
}
|
|
1742
|
-
),
|
|
1743
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1744
|
-
"a",
|
|
1745
|
-
{
|
|
1746
|
-
href: pdfObjectUrl ?? "#",
|
|
1747
|
-
download: pdfFileName,
|
|
1748
|
-
className: cn(
|
|
1749
|
-
"h-7 w-7 rounded-full text-xs font-medium border flex items-center justify-center",
|
|
1750
|
-
"bg-white/70 border-black/10 hover:bg-white/90",
|
|
1751
|
-
"dark:bg-white/[0.04] dark:border-white/10 dark:hover:bg-white/[0.08]",
|
|
1752
|
-
!pdfObjectUrl && "opacity-50 pointer-events-none"
|
|
1753
|
-
),
|
|
1754
|
-
title: t("download_pdf"),
|
|
1755
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "h-3.5 w-3.5" })
|
|
1756
|
-
}
|
|
1757
|
-
)
|
|
1758
|
-
]
|
|
1759
|
-
}
|
|
1760
|
-
),
|
|
1761
|
-
pdfObjectUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1762
|
-
PdfLoader,
|
|
1763
|
-
{
|
|
1764
|
-
url: pdfObjectUrl,
|
|
1765
|
-
workerSrc: PDF_WORKER_SRC,
|
|
1766
|
-
cMapUrl: PDF_CMAP_URL,
|
|
1767
|
-
cMapPacked: true,
|
|
1768
|
-
beforeLoad: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground", children: [
|
|
1769
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-5 w-5 animate-spin mr-2" }),
|
|
1770
|
-
t("loading_pdf")
|
|
1771
|
-
] }),
|
|
1772
|
-
children: (pdfDocument) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1773
|
-
PdfSurface,
|
|
1774
|
-
{
|
|
1775
|
-
pdfDocument,
|
|
1776
|
-
zoomFactor: zoomScale,
|
|
1777
|
-
highlights: emptyHighlights,
|
|
1778
|
-
onPageWidth: handlePageWidth
|
|
1779
|
-
}
|
|
1780
|
-
)
|
|
1781
|
-
}
|
|
1782
|
-
) : buildStatus === "queued" || buildStatus === "running" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground", children: [
|
|
1783
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-5 w-5 animate-spin mr-2" }),
|
|
1784
|
-
t("preview_compiling")
|
|
1785
|
-
] }) : buildStatus === "error" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground", children: t("preview_no_output") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground", children: t("preview_empty") })
|
|
1786
|
-
] }) })
|
|
1787
|
-
]
|
|
1788
|
-
}
|
|
1789
|
-
) });
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
export { LatexPlugin as default };
|