@researai/deepscientist 1.5.15 → 1.5.17
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 +385 -104
- package/bin/ds.js +1241 -110
- package/docs/en/00_QUICK_START.md +100 -19
- package/docs/en/01_SETTINGS_REFERENCE.md +34 -1
- 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 +25 -8
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +37 -11
- 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/21_LOCAL_MODEL_BACKENDS_GUIDE.md +283 -0
- package/docs/en/91_DEVELOPMENT.md +237 -0
- package/docs/en/README.md +24 -2
- package/docs/zh/00_QUICK_START.md +89 -19
- package/docs/zh/01_SETTINGS_REFERENCE.md +34 -1
- 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 +26 -9
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +37 -11
- 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/21_LOCAL_MODEL_BACKENDS_GUIDE.md +281 -0
- package/docs/zh/README.md +24 -2
- package/install.sh +46 -4
- package/package.json +2 -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/bridges/connectors.py +8 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +367 -22
- package/src/deepscientist/config/models.py +2 -1
- package/src/deepscientist/config/service.py +183 -13
- package/src/deepscientist/daemon/api/handlers.py +255 -31
- package/src/deepscientist/daemon/api/router.py +9 -0
- package/src/deepscientist/daemon/app.py +1146 -105
- package/src/deepscientist/diagnostics/__init__.py +6 -0
- package/src/deepscientist/diagnostics/runner_failures.py +130 -0
- package/src/deepscientist/doctor.py +207 -3
- 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 +275 -34
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +707 -55
- package/src/deepscientist/quest/stage_views.py +6 -1
- package/src/deepscientist/runners/codex.py +143 -43
- package/src/deepscientist/shared.py +19 -0
- 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 +14 -2
- package/src/prompts/system.md +23 -5
- package/src/prompts/system_copilot.md +56 -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-Bv-Z8YpU.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-BCKAfjba.js +1 -0
- package/src/ui/dist/assets/CliPlugin-BCKcpc35.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-DbOfSJ8K.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-CbaFRrUU.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DAjLVeQD.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-CIUqbUDO.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-CQACjoAA.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-0r4nLPke.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-nBOmI2v_.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-BHxOxF4z.js +14 -0
- package/src/ui/dist/assets/LabPlugin-BKoZGs95.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-ZwtV8pIp.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DKqVfKyW.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BwxStZ9D.js +13 -0
- package/src/ui/dist/assets/NotebookEditor-BEQhaQbt.js +81 -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-DB9N_T9q.js +361 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfLoader-eWBONbQP.js +16 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-D22YOZL3.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-c-RK9DLM.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CxF9ytAx.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-C5xqeeUH.js +54 -0
- package/src/ui/dist/assets/VNCViewer-BoLGLnHz.js +11 -0
- package/src/ui/dist/assets/bot-DREQOxzP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-C9Qpx4DE.js +6 -0
- package/src/ui/dist/assets/code-WlFHE7z_.js +6 -0
- package/src/ui/dist/assets/file-content-BZMz3RYp.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-CQhw0jS2.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-CfQPKQKj.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-DxZ8DCZh.js +6 -0
- package/src/ui/dist/assets/image-Bgl4VIyx.js +6 -0
- package/src/ui/dist/assets/index-BpV6lusQ.css +33 -0
- package/src/ui/dist/assets/index-CBNVuWcP.js +2496 -0
- package/src/ui/dist/assets/index-CwNu1aH4.js +11 -0
- package/src/ui/dist/assets/index-DrUnlf6K.js +1 -0
- package/src/ui/dist/assets/index-NW-h8VzN.js +1 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-J8OnM0jE.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-CLc0pPP8.js +1 -0
- package/src/ui/dist/assets/project-sync-C9IdzdZW.js +1 -0
- package/src/ui/dist/assets/select-Cs2PmzwL.js +11 -0
- package/src/ui/dist/assets/sigma-ClKcHAXm.js +6 -0
- package/src/ui/dist/assets/trash-DwpbFr3w.js +11 -0
- package/src/ui/dist/assets/useCliAccess-NQ8m0Let.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-BC-Hltpd.js +11 -0
- package/src/ui/dist/assets/zoom-out-E_gaeAxL.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,3095 +0,0 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-D_E4281X.js","assets/index-G7AcWcMu.css"])))=>i.map(i=>d[i]);
|
|
2
|
-
import { w as createLucideIcon, r as reactExports, u as useI18n, j as jsxRuntimeExports, a3 as Download, I as InfoTriangleIcon, b as cn, k as FileText, l as Search, X, m as ChevronDown, Q as create, t as apiClient, aT as useQueryClient, aN as useQuery, bR as useMutation, bS as Card, aO as Button, bT as DropdownMenu, bU as DropdownMenuTrigger, bV as DropdownMenuContent, bW as DropdownMenuLabel, bX as DropdownMenuItem, bY as SegmentedControl, br as CircleHelp, bZ as Textarea, i as isCliFileId, b_ as isQuestNodeId, b$ as getQuestNodeProjectId, n as useTabsStore, f as useFileTreeStore, a as useWorkspaceSurfaceStore, o as useToast, p as useArxivStore, z as useAuthStore, s as generateBibTeX, c as copyToClipboard, W as toFilesResourcePath, v as BUILTIN_PLUGINS, c0 as getFile, c1 as listProjectMembers, _ as __vitePreload, A as ArxivInfoModal, c2 as createFileObjectUrl, N as Sparkles, T as TriangleAlert, c3 as DropdownMenuRadioGroup, c4 as DropdownMenuRadioItem, c5 as MarkdownPreview } from './index-D_E4281X.js';
|
|
3
|
-
import { a as acquireFileSocket } from './file-socket-B4T2o4nR.js';
|
|
4
|
-
import { M as MessageSquare, i as isPdfEffectHandled, m as markPdfEffectHandled, c as consumePdfEffects } from './pdf-effect-queue-BJk5okWJ.js';
|
|
5
|
-
import { Z as ZOOM_LEVELS, R as Rnd, g as getPageFromElement, a as PdfLoader, e as PDF_CMAP_PACKED, b as PDF_CMAP_URL, c as PDF_WORKER_SRC, d as PdfHighlighter } from './PdfLoader-BfOHw8Zw.js';
|
|
6
|
-
import { Z as ZoomOut, a as ZoomIn } from './zoom-out-BgVMmOW4.js';
|
|
7
|
-
import { C as ChevronUp } from './index-BwRJaoTl.js';
|
|
8
|
-
import { S as SquareCheckBig } from './square-check-big-uUfyVsbD.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @license lucide-react v0.511.0 - ISC
|
|
12
|
-
*
|
|
13
|
-
* This source code is licensed under the ISC license.
|
|
14
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const __iconNode$1 = [
|
|
19
|
-
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
|
|
20
|
-
["path", { d: "M15 3v18", key: "14nvp0" }]
|
|
21
|
-
];
|
|
22
|
-
const PanelRight = createLucideIcon("panel-right", __iconNode$1);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @license lucide-react v0.511.0 - ISC
|
|
26
|
-
*
|
|
27
|
-
* This source code is licensed under the ISC license.
|
|
28
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const __iconNode = [
|
|
33
|
-
["path", { d: "M16 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8Z", key: "qazsjp" }],
|
|
34
|
-
["path", { d: "M15 3v4a2 2 0 0 0 2 2h4", key: "40519r" }]
|
|
35
|
-
];
|
|
36
|
-
const StickyNote = createLucideIcon("sticky-note", __iconNode);
|
|
37
|
-
|
|
38
|
-
const ZoomControls = reactExports.memo(function ZoomControls2({
|
|
39
|
-
scale,
|
|
40
|
-
onScaleChange
|
|
41
|
-
}) {
|
|
42
|
-
const { t } = useI18n("pdf_viewer");
|
|
43
|
-
const handleZoomOut = () => {
|
|
44
|
-
const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= scale);
|
|
45
|
-
if (currentIndex > 0) {
|
|
46
|
-
onScaleChange(ZOOM_LEVELS[currentIndex - 1]);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
const handleZoomIn = () => {
|
|
50
|
-
const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= scale);
|
|
51
|
-
if (currentIndex < ZOOM_LEVELS.length - 1) {
|
|
52
|
-
onScaleChange(ZOOM_LEVELS[currentIndex + 1]);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
const handleSelectChange = (e) => {
|
|
56
|
-
onScaleChange(parseFloat(e.target.value));
|
|
57
|
-
};
|
|
58
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
59
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
60
|
-
"button",
|
|
61
|
-
{
|
|
62
|
-
onClick: handleZoomOut,
|
|
63
|
-
disabled: scale <= ZOOM_LEVELS[0],
|
|
64
|
-
className: cn(
|
|
65
|
-
"p-1.5 rounded hover:bg-muted transition-colors",
|
|
66
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
67
|
-
),
|
|
68
|
-
title: t("zoom_out"),
|
|
69
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { className: "w-4 h-4" })
|
|
70
|
-
}
|
|
71
|
-
),
|
|
72
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
73
|
-
"select",
|
|
74
|
-
{
|
|
75
|
-
value: scale,
|
|
76
|
-
onChange: handleSelectChange,
|
|
77
|
-
className: cn(
|
|
78
|
-
"w-20 px-2 py-1 text-sm rounded",
|
|
79
|
-
"bg-muted/50 border border-border",
|
|
80
|
-
"focus:outline-none focus:ring-2 focus:ring-primary/50"
|
|
81
|
-
),
|
|
82
|
-
children: ZOOM_LEVELS.map((z) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: z, children: [
|
|
83
|
-
Math.round(z * 100),
|
|
84
|
-
"%"
|
|
85
|
-
] }, z))
|
|
86
|
-
}
|
|
87
|
-
),
|
|
88
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
89
|
-
"button",
|
|
90
|
-
{
|
|
91
|
-
onClick: handleZoomIn,
|
|
92
|
-
disabled: scale >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1],
|
|
93
|
-
className: cn(
|
|
94
|
-
"p-1.5 rounded hover:bg-muted transition-colors",
|
|
95
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
96
|
-
),
|
|
97
|
-
title: t("zoom_in"),
|
|
98
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { className: "w-4 h-4" })
|
|
99
|
-
}
|
|
100
|
-
)
|
|
101
|
-
] });
|
|
102
|
-
});
|
|
103
|
-
const PageNavigation = reactExports.memo(function PageNavigation2({
|
|
104
|
-
currentPage,
|
|
105
|
-
totalPages,
|
|
106
|
-
onPageChange
|
|
107
|
-
}) {
|
|
108
|
-
const { t } = useI18n("pdf_viewer");
|
|
109
|
-
const [inputValue, setInputValue] = reactExports.useState(String(currentPage));
|
|
110
|
-
const inputRef = reactExports.useRef(null);
|
|
111
|
-
reactExports.useEffect(() => {
|
|
112
|
-
setInputValue(String(currentPage));
|
|
113
|
-
}, [currentPage]);
|
|
114
|
-
const handlePrevPage = () => {
|
|
115
|
-
if (currentPage > 1) {
|
|
116
|
-
onPageChange(currentPage - 1);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
const handleNextPage = () => {
|
|
120
|
-
if (currentPage < totalPages) {
|
|
121
|
-
onPageChange(currentPage + 1);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
const handleInputChange = (e) => {
|
|
125
|
-
setInputValue(e.target.value);
|
|
126
|
-
};
|
|
127
|
-
const handleInputBlur = () => {
|
|
128
|
-
const page = parseInt(inputValue, 10);
|
|
129
|
-
if (!isNaN(page) && page >= 1 && page <= totalPages) {
|
|
130
|
-
onPageChange(page);
|
|
131
|
-
} else {
|
|
132
|
-
setInputValue(String(currentPage));
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
const handleInputKeyDown = (e) => {
|
|
136
|
-
if (e.key === "Enter") {
|
|
137
|
-
handleInputBlur();
|
|
138
|
-
inputRef.current?.blur();
|
|
139
|
-
} else if (e.key === "Escape") {
|
|
140
|
-
setInputValue(String(currentPage));
|
|
141
|
-
inputRef.current?.blur();
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
145
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
146
|
-
"button",
|
|
147
|
-
{
|
|
148
|
-
onClick: handlePrevPage,
|
|
149
|
-
disabled: currentPage <= 1,
|
|
150
|
-
className: cn(
|
|
151
|
-
"p-1.5 rounded hover:bg-muted transition-colors",
|
|
152
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
153
|
-
),
|
|
154
|
-
title: t("previous_page"),
|
|
155
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { className: "w-4 h-4" })
|
|
156
|
-
}
|
|
157
|
-
),
|
|
158
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-sm whitespace-nowrap", children: [
|
|
159
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
160
|
-
"input",
|
|
161
|
-
{
|
|
162
|
-
ref: inputRef,
|
|
163
|
-
type: "text",
|
|
164
|
-
value: inputValue,
|
|
165
|
-
onChange: handleInputChange,
|
|
166
|
-
onBlur: handleInputBlur,
|
|
167
|
-
onKeyDown: handleInputKeyDown,
|
|
168
|
-
className: cn(
|
|
169
|
-
"w-12 px-2 py-1 text-center rounded",
|
|
170
|
-
"bg-muted/50 border border-border",
|
|
171
|
-
"focus:outline-none focus:ring-2 focus:ring-primary/50"
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
),
|
|
175
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 whitespace-nowrap text-muted-foreground", children: [
|
|
176
|
-
"/ ",
|
|
177
|
-
totalPages
|
|
178
|
-
] })
|
|
179
|
-
] }),
|
|
180
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
181
|
-
"button",
|
|
182
|
-
{
|
|
183
|
-
onClick: handleNextPage,
|
|
184
|
-
disabled: currentPage >= totalPages,
|
|
185
|
-
className: cn(
|
|
186
|
-
"p-1.5 rounded hover:bg-muted transition-colors",
|
|
187
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
188
|
-
),
|
|
189
|
-
title: t("next_page"),
|
|
190
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "w-4 h-4" })
|
|
191
|
-
}
|
|
192
|
-
)
|
|
193
|
-
] });
|
|
194
|
-
});
|
|
195
|
-
const SearchBox = reactExports.memo(function SearchBox2({
|
|
196
|
-
value,
|
|
197
|
-
onChange,
|
|
198
|
-
placeholder
|
|
199
|
-
}) {
|
|
200
|
-
const { t } = useI18n("pdf_viewer");
|
|
201
|
-
const inputRef = reactExports.useRef(null);
|
|
202
|
-
const handleClear = () => {
|
|
203
|
-
onChange("");
|
|
204
|
-
inputRef.current?.focus();
|
|
205
|
-
};
|
|
206
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex items-center", children: [
|
|
207
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "absolute left-2 w-4 h-4 text-muted-foreground pointer-events-none" }),
|
|
208
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
209
|
-
"input",
|
|
210
|
-
{
|
|
211
|
-
ref: inputRef,
|
|
212
|
-
type: "text",
|
|
213
|
-
value,
|
|
214
|
-
onChange: (e) => onChange(e.target.value),
|
|
215
|
-
placeholder: placeholder || t("search_placeholder"),
|
|
216
|
-
className: cn(
|
|
217
|
-
"w-48 pl-8 pr-8 py-1.5 text-sm rounded",
|
|
218
|
-
"bg-muted/50 border border-border",
|
|
219
|
-
"placeholder:text-muted-foreground/60",
|
|
220
|
-
"focus:outline-none focus:ring-2 focus:ring-primary/50"
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
),
|
|
224
|
-
value && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
225
|
-
"button",
|
|
226
|
-
{
|
|
227
|
-
onClick: handleClear,
|
|
228
|
-
className: "absolute right-2 p-0.5 rounded hover:bg-muted/80 transition-colors",
|
|
229
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "w-3 h-3 text-muted-foreground" })
|
|
230
|
-
}
|
|
231
|
-
)
|
|
232
|
-
] });
|
|
233
|
-
});
|
|
234
|
-
function Separator() {
|
|
235
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-6 bg-border mx-2" });
|
|
236
|
-
}
|
|
237
|
-
const Toolbar = reactExports.memo(function Toolbar2({
|
|
238
|
-
scale,
|
|
239
|
-
onScaleChange,
|
|
240
|
-
currentPage,
|
|
241
|
-
totalPages,
|
|
242
|
-
onPageChange,
|
|
243
|
-
sidebarVisible,
|
|
244
|
-
onSidebarToggle,
|
|
245
|
-
searchQuery = "",
|
|
246
|
-
onSearchChange,
|
|
247
|
-
onDownload,
|
|
248
|
-
onInfo,
|
|
249
|
-
infoDisabled = false,
|
|
250
|
-
markdownActive,
|
|
251
|
-
onMarkdownToggle,
|
|
252
|
-
markdownLabel,
|
|
253
|
-
markdownTitle,
|
|
254
|
-
reviewOpinionActive,
|
|
255
|
-
onReviewOpinionToggle,
|
|
256
|
-
reviewOpinionLabel,
|
|
257
|
-
reviewOpinionTitle
|
|
258
|
-
}) {
|
|
259
|
-
const { t } = useI18n("pdf_viewer");
|
|
260
|
-
const isMarkdownActive = Boolean(markdownActive);
|
|
261
|
-
const canToggleMarkdown = typeof onMarkdownToggle === "function";
|
|
262
|
-
const markdownButtonLabel = markdownLabel?.trim() || t("markdown");
|
|
263
|
-
const markdownButtonTitle = markdownTitle?.trim() || t("open_markdown");
|
|
264
|
-
const isReviewOpinionActive = Boolean(reviewOpinionActive);
|
|
265
|
-
const canToggleReviewOpinion = typeof onReviewOpinionToggle === "function";
|
|
266
|
-
const reviewOpinionButtonLabel = reviewOpinionLabel?.trim() || t("review_opinion");
|
|
267
|
-
const reviewOpinionButtonTitle = reviewOpinionTitle?.trim() || t("open_review_opinion");
|
|
268
|
-
const canShowInfo = typeof onInfo === "function";
|
|
269
|
-
const hasLeftActions = Boolean(onSearchChange || onDownload || canShowInfo);
|
|
270
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
271
|
-
"div",
|
|
272
|
-
{
|
|
273
|
-
className: cn(
|
|
274
|
-
"flex flex-wrap items-center gap-3 px-4 py-2",
|
|
275
|
-
"bg-muted/30 border-b border-border"
|
|
276
|
-
),
|
|
277
|
-
children: [
|
|
278
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-2", children: [
|
|
279
|
-
onSearchChange ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
280
|
-
SearchBox,
|
|
281
|
-
{
|
|
282
|
-
value: searchQuery,
|
|
283
|
-
onChange: onSearchChange,
|
|
284
|
-
placeholder: t("search_pdf_placeholder")
|
|
285
|
-
}
|
|
286
|
-
) : null,
|
|
287
|
-
onDownload ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
288
|
-
"button",
|
|
289
|
-
{
|
|
290
|
-
onClick: onDownload,
|
|
291
|
-
className: "p-1.5 rounded hover:bg-muted transition-colors",
|
|
292
|
-
title: t("download_pdf"),
|
|
293
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "w-4 h-4" })
|
|
294
|
-
}
|
|
295
|
-
) : null,
|
|
296
|
-
canShowInfo ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
297
|
-
"button",
|
|
298
|
-
{
|
|
299
|
-
onClick: onInfo,
|
|
300
|
-
disabled: infoDisabled,
|
|
301
|
-
className: cn(
|
|
302
|
-
"p-1.5 rounded transition-colors",
|
|
303
|
-
"hover:bg-muted",
|
|
304
|
-
infoDisabled && "opacity-50 cursor-not-allowed"
|
|
305
|
-
),
|
|
306
|
-
title: t("paper_info"),
|
|
307
|
-
"aria-label": t("paper_info"),
|
|
308
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(InfoTriangleIcon, { className: "h-4 w-4" })
|
|
309
|
-
}
|
|
310
|
-
) : null,
|
|
311
|
-
hasLeftActions ? /* @__PURE__ */ jsxRuntimeExports.jsx(Separator, {}) : null,
|
|
312
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(ZoomControls, { scale, onScaleChange }),
|
|
313
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Separator, {}),
|
|
314
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
315
|
-
PageNavigation,
|
|
316
|
-
{
|
|
317
|
-
currentPage,
|
|
318
|
-
totalPages,
|
|
319
|
-
onPageChange
|
|
320
|
-
}
|
|
321
|
-
)
|
|
322
|
-
] }),
|
|
323
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ml-auto flex items-center justify-end gap-2", children: [
|
|
324
|
-
canToggleMarkdown ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
325
|
-
"button",
|
|
326
|
-
{
|
|
327
|
-
type: "button",
|
|
328
|
-
onClick: onMarkdownToggle,
|
|
329
|
-
className: cn(
|
|
330
|
-
"flex items-center gap-2 px-3 py-1.5 rounded-full border transition-colors text-sm",
|
|
331
|
-
"bg-background hover:bg-muted/60 border-border",
|
|
332
|
-
isMarkdownActive && "bg-primary/10 text-primary border-primary/20"
|
|
333
|
-
),
|
|
334
|
-
title: markdownButtonTitle,
|
|
335
|
-
children: [
|
|
336
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "w-4 h-4" }),
|
|
337
|
-
markdownButtonLabel
|
|
338
|
-
]
|
|
339
|
-
}
|
|
340
|
-
) : null,
|
|
341
|
-
canToggleReviewOpinion ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
342
|
-
"button",
|
|
343
|
-
{
|
|
344
|
-
type: "button",
|
|
345
|
-
onClick: onReviewOpinionToggle,
|
|
346
|
-
className: cn(
|
|
347
|
-
"flex items-center gap-2 px-3 py-1.5 rounded-full border transition-colors text-sm",
|
|
348
|
-
"bg-background hover:bg-muted/60 border-border",
|
|
349
|
-
isReviewOpinionActive && "bg-primary/10 text-primary border-primary/20"
|
|
350
|
-
),
|
|
351
|
-
title: reviewOpinionButtonTitle,
|
|
352
|
-
children: [
|
|
353
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "w-4 h-4" }),
|
|
354
|
-
reviewOpinionButtonLabel
|
|
355
|
-
]
|
|
356
|
-
}
|
|
357
|
-
) : null,
|
|
358
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
359
|
-
"button",
|
|
360
|
-
{
|
|
361
|
-
type: "button",
|
|
362
|
-
onClick: onSidebarToggle,
|
|
363
|
-
className: cn(
|
|
364
|
-
"flex items-center gap-2 px-3 py-1.5 rounded-full border transition-colors text-sm",
|
|
365
|
-
"bg-background hover:bg-muted/60 border-border",
|
|
366
|
-
sidebarVisible && "bg-primary/10 text-primary border-primary/20"
|
|
367
|
-
),
|
|
368
|
-
title: sidebarVisible ? t("hide_annotations") : t("show_annotations"),
|
|
369
|
-
children: [
|
|
370
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(PanelRight, { className: "w-4 h-4" }),
|
|
371
|
-
sidebarVisible ? t("hide") : t("show")
|
|
372
|
-
]
|
|
373
|
-
}
|
|
374
|
-
)
|
|
375
|
-
] })
|
|
376
|
-
]
|
|
377
|
-
}
|
|
378
|
-
);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
const initialState = {
|
|
382
|
-
scale: 1,
|
|
383
|
-
fitMode: "custom",
|
|
384
|
-
currentPage: 1,
|
|
385
|
-
totalPages: 0,
|
|
386
|
-
scrollTop: 0,
|
|
387
|
-
searchQuery: "",
|
|
388
|
-
searchResults: [],
|
|
389
|
-
currentSearchIndex: -1,
|
|
390
|
-
// Default: keep annotations sidebar hidden until the user creates one.
|
|
391
|
-
sidebarVisible: false,
|
|
392
|
-
selectedAnnotationId: null
|
|
393
|
-
};
|
|
394
|
-
const useViewerState = create((set, get) => ({
|
|
395
|
-
// Initial state
|
|
396
|
-
...initialState,
|
|
397
|
-
// ============================================================
|
|
398
|
-
// Scale Actions
|
|
399
|
-
// ============================================================
|
|
400
|
-
setScale: (scale) => {
|
|
401
|
-
const clampedScale = Math.max(
|
|
402
|
-
ZOOM_LEVELS[0],
|
|
403
|
-
Math.min(ZOOM_LEVELS[ZOOM_LEVELS.length - 1], scale)
|
|
404
|
-
);
|
|
405
|
-
set({ scale: clampedScale, fitMode: "custom" });
|
|
406
|
-
},
|
|
407
|
-
setFitMode: (mode) => {
|
|
408
|
-
set({ fitMode: mode });
|
|
409
|
-
},
|
|
410
|
-
// ============================================================
|
|
411
|
-
// Page Navigation Actions
|
|
412
|
-
// ============================================================
|
|
413
|
-
setCurrentPage: (page) => {
|
|
414
|
-
const { totalPages } = get();
|
|
415
|
-
const clampedPage = Math.max(1, Math.min(page, totalPages || 1));
|
|
416
|
-
set({ currentPage: clampedPage });
|
|
417
|
-
},
|
|
418
|
-
setTotalPages: (total) => {
|
|
419
|
-
set({ totalPages: total });
|
|
420
|
-
},
|
|
421
|
-
setScrollTop: (scrollTop) => {
|
|
422
|
-
set({ scrollTop });
|
|
423
|
-
},
|
|
424
|
-
scrollToPage: (page) => {
|
|
425
|
-
const { totalPages, setCurrentPage } = get();
|
|
426
|
-
const clampedPage = Math.max(1, Math.min(page, totalPages || 1));
|
|
427
|
-
setCurrentPage(clampedPage);
|
|
428
|
-
},
|
|
429
|
-
// ============================================================
|
|
430
|
-
// Search Actions
|
|
431
|
-
// ============================================================
|
|
432
|
-
setSearchQuery: (query) => {
|
|
433
|
-
set({ searchQuery: query, currentSearchIndex: -1 });
|
|
434
|
-
},
|
|
435
|
-
setSearchResults: (results) => {
|
|
436
|
-
set({
|
|
437
|
-
searchResults: results,
|
|
438
|
-
currentSearchIndex: results.length > 0 ? 0 : -1
|
|
439
|
-
});
|
|
440
|
-
},
|
|
441
|
-
nextSearchResult: () => {
|
|
442
|
-
const { searchResults, currentSearchIndex } = get();
|
|
443
|
-
if (searchResults.length === 0) return;
|
|
444
|
-
const nextIndex = (currentSearchIndex + 1) % searchResults.length;
|
|
445
|
-
set({ currentSearchIndex: nextIndex });
|
|
446
|
-
},
|
|
447
|
-
prevSearchResult: () => {
|
|
448
|
-
const { searchResults, currentSearchIndex } = get();
|
|
449
|
-
if (searchResults.length === 0) return;
|
|
450
|
-
const prevIndex = currentSearchIndex <= 0 ? searchResults.length - 1 : currentSearchIndex - 1;
|
|
451
|
-
set({ currentSearchIndex: prevIndex });
|
|
452
|
-
},
|
|
453
|
-
// ============================================================
|
|
454
|
-
// Sidebar Actions
|
|
455
|
-
// ============================================================
|
|
456
|
-
toggleSidebar: () => {
|
|
457
|
-
set((state) => ({ sidebarVisible: !state.sidebarVisible }));
|
|
458
|
-
},
|
|
459
|
-
// ============================================================
|
|
460
|
-
// Annotation Actions
|
|
461
|
-
// ============================================================
|
|
462
|
-
selectAnnotation: (id) => {
|
|
463
|
-
set({ selectedAnnotationId: id });
|
|
464
|
-
},
|
|
465
|
-
scrollToAnnotation: (id) => {
|
|
466
|
-
set({ selectedAnnotationId: id });
|
|
467
|
-
},
|
|
468
|
-
// ============================================================
|
|
469
|
-
// Reset
|
|
470
|
-
// ============================================================
|
|
471
|
-
reset: () => {
|
|
472
|
-
set(initialState);
|
|
473
|
-
}
|
|
474
|
-
}));
|
|
475
|
-
|
|
476
|
-
function toAnnotation(raw) {
|
|
477
|
-
const position = raw.position ? {
|
|
478
|
-
...raw.position,
|
|
479
|
-
boundingRect: {
|
|
480
|
-
...raw.position.boundingRect,
|
|
481
|
-
width: raw.position.boundingRect?.width ?? 100,
|
|
482
|
-
height: raw.position.boundingRect?.height ?? 100
|
|
483
|
-
},
|
|
484
|
-
rects: Array.isArray(raw.position.rects) ? raw.position.rects.map((r) => ({
|
|
485
|
-
...r,
|
|
486
|
-
width: r?.width ?? 100,
|
|
487
|
-
height: r?.height ?? 100
|
|
488
|
-
})) : []
|
|
489
|
-
} : void 0;
|
|
490
|
-
const content = raw.content;
|
|
491
|
-
return {
|
|
492
|
-
id: String(raw.id),
|
|
493
|
-
position,
|
|
494
|
-
content,
|
|
495
|
-
comment: String(raw.comment ?? ""),
|
|
496
|
-
kind: raw.kind === "question" || raw.kind === "task" ? raw.kind : "note",
|
|
497
|
-
color: String(raw.color ?? "#F1E9D0"),
|
|
498
|
-
tags: Array.isArray(raw.tags) ? raw.tags.map(String) : [],
|
|
499
|
-
createdBy: String(raw.created_by ?? raw.createdBy ?? ""),
|
|
500
|
-
author: raw.author ? {
|
|
501
|
-
id: String(raw.author.id),
|
|
502
|
-
handle: String(raw.author.handle ?? "user"),
|
|
503
|
-
color: String(raw.author.color ?? "#F1E9D0")
|
|
504
|
-
} : null,
|
|
505
|
-
createdAt: String(raw.created_at ?? raw.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
506
|
-
updatedAt: String(raw.updated_at ?? raw.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
507
|
-
fileId: String(raw.file_id ?? raw.fileId ?? ""),
|
|
508
|
-
projectId: String(raw.project_id ?? raw.projectId ?? "")
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
function toCreatePayload(data) {
|
|
512
|
-
return {
|
|
513
|
-
file_id: data.fileId,
|
|
514
|
-
position: data.position,
|
|
515
|
-
content: data.content,
|
|
516
|
-
comment: data.comment ?? "",
|
|
517
|
-
kind: data.kind ?? "note",
|
|
518
|
-
// backwards-compatible; server enforces author color
|
|
519
|
-
color: data.color,
|
|
520
|
-
tags: data.tags ?? []
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
function toUpdatePayload(data) {
|
|
524
|
-
return {
|
|
525
|
-
comment: data.comment,
|
|
526
|
-
kind: data.kind,
|
|
527
|
-
position: data.position,
|
|
528
|
-
content: data.content,
|
|
529
|
-
// ignored by server
|
|
530
|
-
color: data.color,
|
|
531
|
-
tags: data.tags
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
const ENDPOINTS = {
|
|
535
|
-
list: (fileId) => `/api/v1/annotations/file/${fileId}`,
|
|
536
|
-
create: () => `/api/v1/annotations/`,
|
|
537
|
-
get: (id) => `/api/v1/annotations/${id}`,
|
|
538
|
-
update: (id) => `/api/v1/annotations/${id}`,
|
|
539
|
-
delete: (id) => `/api/v1/annotations/${id}`,
|
|
540
|
-
search: (projectId) => `/api/v1/annotations/project/${projectId}`
|
|
541
|
-
};
|
|
542
|
-
const annotationsApi = {
|
|
543
|
-
/**
|
|
544
|
-
* List annotations for a file
|
|
545
|
-
*
|
|
546
|
-
* @param fileId - File ID
|
|
547
|
-
* @returns List of annotations
|
|
548
|
-
*/
|
|
549
|
-
async listAnnotations(fileId) {
|
|
550
|
-
const response = await apiClient.get(
|
|
551
|
-
ENDPOINTS.list(fileId)
|
|
552
|
-
);
|
|
553
|
-
if (Array.isArray(response.data)) {
|
|
554
|
-
return response.data.map(toAnnotation);
|
|
555
|
-
}
|
|
556
|
-
return (response.data.items || []).map(toAnnotation);
|
|
557
|
-
},
|
|
558
|
-
/**
|
|
559
|
-
* Create a new annotation
|
|
560
|
-
*
|
|
561
|
-
* @param data - Annotation data
|
|
562
|
-
* @returns Created annotation
|
|
563
|
-
*/
|
|
564
|
-
async createAnnotation(data) {
|
|
565
|
-
const response = await apiClient.post(
|
|
566
|
-
ENDPOINTS.create(),
|
|
567
|
-
toCreatePayload(data)
|
|
568
|
-
);
|
|
569
|
-
return toAnnotation(response.data);
|
|
570
|
-
},
|
|
571
|
-
/**
|
|
572
|
-
* Get a single annotation by ID
|
|
573
|
-
*
|
|
574
|
-
* @param id - Annotation ID
|
|
575
|
-
* @returns Annotation data
|
|
576
|
-
*/
|
|
577
|
-
async getAnnotation(id) {
|
|
578
|
-
const response = await apiClient.get(ENDPOINTS.get(id));
|
|
579
|
-
return toAnnotation(response.data);
|
|
580
|
-
},
|
|
581
|
-
/**
|
|
582
|
-
* Update an annotation
|
|
583
|
-
*
|
|
584
|
-
* @param id - Annotation ID
|
|
585
|
-
* @param data - Update data
|
|
586
|
-
* @returns Updated annotation
|
|
587
|
-
*/
|
|
588
|
-
async updateAnnotation(id, data) {
|
|
589
|
-
const response = await apiClient.patch(
|
|
590
|
-
ENDPOINTS.update(id),
|
|
591
|
-
toUpdatePayload(data)
|
|
592
|
-
);
|
|
593
|
-
return toAnnotation(response.data);
|
|
594
|
-
},
|
|
595
|
-
/**
|
|
596
|
-
* Delete an annotation
|
|
597
|
-
*
|
|
598
|
-
* @param id - Annotation ID
|
|
599
|
-
*/
|
|
600
|
-
async deleteAnnotation(id) {
|
|
601
|
-
await apiClient.delete(ENDPOINTS.delete(id));
|
|
602
|
-
},
|
|
603
|
-
/**
|
|
604
|
-
* Search annotations in a project
|
|
605
|
-
*
|
|
606
|
-
* @param projectId - Project ID
|
|
607
|
-
* @param query - Search query
|
|
608
|
-
* @param options - Search options
|
|
609
|
-
* @returns Matching annotations
|
|
610
|
-
*/
|
|
611
|
-
async searchAnnotations(projectId, query, options) {
|
|
612
|
-
const params = new URLSearchParams();
|
|
613
|
-
if (query) params.set("q", query);
|
|
614
|
-
if (options?.color) params.set("color", options.color);
|
|
615
|
-
if (options?.tag) params.set("tag", options.tag);
|
|
616
|
-
if (options?.page) params.set("page", String(options.page));
|
|
617
|
-
if (options?.limit) params.set("limit", String(options.limit));
|
|
618
|
-
const url = `${ENDPOINTS.search(projectId)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
619
|
-
const response = await apiClient.get(url);
|
|
620
|
-
if (Array.isArray(response.data)) {
|
|
621
|
-
return response.data.map(toAnnotation);
|
|
622
|
-
}
|
|
623
|
-
return (response.data.items || []).map(toAnnotation);
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
const annotationKeys = {
|
|
628
|
-
all: ["annotations"],
|
|
629
|
-
lists: () => [...annotationKeys.all, "list"],
|
|
630
|
-
list: (fileId) => [...annotationKeys.lists(), fileId],
|
|
631
|
-
details: () => [...annotationKeys.all, "detail"],
|
|
632
|
-
detail: (id) => [...annotationKeys.details(), id]
|
|
633
|
-
};
|
|
634
|
-
function useAnnotations(fileId) {
|
|
635
|
-
const queryClient = useQueryClient();
|
|
636
|
-
const queryKey = annotationKeys.list(fileId);
|
|
637
|
-
const {
|
|
638
|
-
data: annotations = [],
|
|
639
|
-
isLoading,
|
|
640
|
-
error,
|
|
641
|
-
refetch,
|
|
642
|
-
isFetching
|
|
643
|
-
} = useQuery({
|
|
644
|
-
queryKey,
|
|
645
|
-
queryFn: () => annotationsApi.listAnnotations(fileId),
|
|
646
|
-
staleTime: 30 * 1e3,
|
|
647
|
-
// 30 seconds cache
|
|
648
|
-
enabled: !!fileId
|
|
649
|
-
});
|
|
650
|
-
const createMutation = useMutation({
|
|
651
|
-
mutationFn: (data) => annotationsApi.createAnnotation(data),
|
|
652
|
-
onSuccess: (created) => {
|
|
653
|
-
queryClient.setQueryData(queryKey, (old = []) => [
|
|
654
|
-
created,
|
|
655
|
-
...old.filter((a) => a.id !== created.id)
|
|
656
|
-
]);
|
|
657
|
-
},
|
|
658
|
-
onSettled: () => queryClient.invalidateQueries({ queryKey })
|
|
659
|
-
});
|
|
660
|
-
const updateMutation = useMutation({
|
|
661
|
-
mutationFn: ({ id, updates }) => annotationsApi.updateAnnotation(id, updates),
|
|
662
|
-
// Optimistic update
|
|
663
|
-
onMutate: async ({ id, updates }) => {
|
|
664
|
-
await queryClient.cancelQueries({ queryKey });
|
|
665
|
-
const previousAnnotations = queryClient.getQueryData(queryKey);
|
|
666
|
-
queryClient.setQueryData(
|
|
667
|
-
queryKey,
|
|
668
|
-
(old = []) => old.map(
|
|
669
|
-
(annotation) => annotation.id === id ? {
|
|
670
|
-
...annotation,
|
|
671
|
-
...updates,
|
|
672
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
673
|
-
} : annotation
|
|
674
|
-
)
|
|
675
|
-
);
|
|
676
|
-
return { previousAnnotations };
|
|
677
|
-
},
|
|
678
|
-
onError: (_err, _variables, context) => {
|
|
679
|
-
if (context?.previousAnnotations) {
|
|
680
|
-
queryClient.setQueryData(queryKey, context.previousAnnotations);
|
|
681
|
-
}
|
|
682
|
-
},
|
|
683
|
-
onSuccess: (updated) => {
|
|
684
|
-
queryClient.setQueryData(
|
|
685
|
-
queryKey,
|
|
686
|
-
(old = []) => old.map((a) => a.id === updated.id ? updated : a)
|
|
687
|
-
);
|
|
688
|
-
},
|
|
689
|
-
onSettled: () => {
|
|
690
|
-
queryClient.invalidateQueries({ queryKey });
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
const deleteMutation = useMutation({
|
|
694
|
-
mutationFn: (id) => annotationsApi.deleteAnnotation(id),
|
|
695
|
-
// Optimistic update
|
|
696
|
-
onMutate: async (id) => {
|
|
697
|
-
await queryClient.cancelQueries({ queryKey });
|
|
698
|
-
const previousAnnotations = queryClient.getQueryData(queryKey);
|
|
699
|
-
queryClient.setQueryData(
|
|
700
|
-
queryKey,
|
|
701
|
-
(old = []) => old.filter((annotation) => annotation.id !== id)
|
|
702
|
-
);
|
|
703
|
-
return { previousAnnotations };
|
|
704
|
-
},
|
|
705
|
-
onError: (_err, _id, context) => {
|
|
706
|
-
if (context?.previousAnnotations) {
|
|
707
|
-
queryClient.setQueryData(queryKey, context.previousAnnotations);
|
|
708
|
-
}
|
|
709
|
-
},
|
|
710
|
-
onSettled: () => {
|
|
711
|
-
queryClient.invalidateQueries({ queryKey });
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
const createAnnotation = async (data) => {
|
|
715
|
-
return createMutation.mutateAsync(data);
|
|
716
|
-
};
|
|
717
|
-
const updateAnnotation = async (id, updates) => {
|
|
718
|
-
return updateMutation.mutateAsync({ id, updates });
|
|
719
|
-
};
|
|
720
|
-
const deleteAnnotation = async (id) => {
|
|
721
|
-
return deleteMutation.mutateAsync(id);
|
|
722
|
-
};
|
|
723
|
-
const refreshAnnotations = () => {
|
|
724
|
-
return refetch();
|
|
725
|
-
};
|
|
726
|
-
const getAnnotationById = (id) => {
|
|
727
|
-
return annotations.find((a) => a.id === id);
|
|
728
|
-
};
|
|
729
|
-
const getAnnotationsByPage = (pageNumber) => {
|
|
730
|
-
return annotations.filter((a) => a.position.pageNumber === pageNumber);
|
|
731
|
-
};
|
|
732
|
-
const getAnnotationsByTag = (tag) => {
|
|
733
|
-
return annotations.filter((a) => a.tags.includes(tag));
|
|
734
|
-
};
|
|
735
|
-
const getAnnotationsByColor = (color) => {
|
|
736
|
-
return annotations.filter((a) => a.color === color);
|
|
737
|
-
};
|
|
738
|
-
const getAllTags = () => {
|
|
739
|
-
const tags = /* @__PURE__ */ new Set();
|
|
740
|
-
annotations.forEach((a) => a.tags.forEach((t) => tags.add(t)));
|
|
741
|
-
return Array.from(tags).sort();
|
|
742
|
-
};
|
|
743
|
-
return {
|
|
744
|
-
// Data
|
|
745
|
-
annotations,
|
|
746
|
-
isLoading,
|
|
747
|
-
isFetching,
|
|
748
|
-
error,
|
|
749
|
-
// CRUD operations
|
|
750
|
-
createAnnotation,
|
|
751
|
-
updateAnnotation,
|
|
752
|
-
deleteAnnotation,
|
|
753
|
-
// Refresh
|
|
754
|
-
refreshAnnotations,
|
|
755
|
-
// Query helpers
|
|
756
|
-
getAnnotationById,
|
|
757
|
-
getAnnotationsByPage,
|
|
758
|
-
getAnnotationsByTag,
|
|
759
|
-
getAnnotationsByColor,
|
|
760
|
-
getAllTags,
|
|
761
|
-
// Mutation states
|
|
762
|
-
isCreating: createMutation.isPending,
|
|
763
|
-
isUpdating: updateMutation.isPending,
|
|
764
|
-
isDeleting: deleteMutation.isPending,
|
|
765
|
-
createError: createMutation.error,
|
|
766
|
-
updateError: updateMutation.error,
|
|
767
|
-
deleteError: deleteMutation.error
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
const tip = "_tip_14ms1_1";
|
|
772
|
-
const tipNote = "_tipNote_14ms1_38";
|
|
773
|
-
const tipQuestion = "_tipQuestion_14ms1_42";
|
|
774
|
-
const tipTask = "_tipTask_14ms1_46";
|
|
775
|
-
const styles$2 = {
|
|
776
|
-
tip: tip,
|
|
777
|
-
tipNote: tipNote,
|
|
778
|
-
tipQuestion: tipQuestion,
|
|
779
|
-
tipTask: tipTask};
|
|
780
|
-
|
|
781
|
-
function Tip({
|
|
782
|
-
onConfirm,
|
|
783
|
-
onCancel,
|
|
784
|
-
onOpen,
|
|
785
|
-
onUpdate,
|
|
786
|
-
onAskCopilot,
|
|
787
|
-
authorColor,
|
|
788
|
-
authorHandle,
|
|
789
|
-
showAuthorHandle,
|
|
790
|
-
onToggleAuthorHandle
|
|
791
|
-
}) {
|
|
792
|
-
const { t } = useI18n("pdf_viewer");
|
|
793
|
-
const [kind, setKind] = reactExports.useState("note");
|
|
794
|
-
const [text, setText] = reactExports.useState("");
|
|
795
|
-
reactExports.useEffect(() => {
|
|
796
|
-
onOpen();
|
|
797
|
-
onUpdate?.();
|
|
798
|
-
}, []);
|
|
799
|
-
reactExports.useEffect(() => {
|
|
800
|
-
onUpdate?.();
|
|
801
|
-
}, [kind, onUpdate]);
|
|
802
|
-
const placeholder = reactExports.useMemo(() => {
|
|
803
|
-
switch (kind) {
|
|
804
|
-
case "question":
|
|
805
|
-
return t("type_question");
|
|
806
|
-
case "task":
|
|
807
|
-
return t("type_task");
|
|
808
|
-
default:
|
|
809
|
-
return t("add_note_optional");
|
|
810
|
-
}
|
|
811
|
-
}, [kind, t]);
|
|
812
|
-
const handleSave = () => {
|
|
813
|
-
onConfirm({ text: text.trim(), emoji: kind });
|
|
814
|
-
};
|
|
815
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
816
|
-
Card,
|
|
817
|
-
{
|
|
818
|
-
className: cn(
|
|
819
|
-
"w-80 border border-border bg-popover text-popover-foreground shadow-soft-lg",
|
|
820
|
-
"backdrop-blur supports-[backdrop-filter]:bg-popover/95",
|
|
821
|
-
styles$2.tip,
|
|
822
|
-
kind === "note" && styles$2.tipNote,
|
|
823
|
-
kind === "question" && styles$2.tipQuestion,
|
|
824
|
-
kind === "task" && styles$2.tipTask
|
|
825
|
-
),
|
|
826
|
-
children: [
|
|
827
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-3 py-2 border-b", children: [
|
|
828
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 text-sm font-medium", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground", children: t("add_annotation") }) }),
|
|
829
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
830
|
-
Button,
|
|
831
|
-
{
|
|
832
|
-
type: "button",
|
|
833
|
-
variant: "ghost",
|
|
834
|
-
size: "icon",
|
|
835
|
-
className: "h-7 w-7",
|
|
836
|
-
onClick: onCancel,
|
|
837
|
-
"aria-label": t("close"),
|
|
838
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "h-4 w-4" })
|
|
839
|
-
}
|
|
840
|
-
)
|
|
841
|
-
] }),
|
|
842
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-3 space-y-3", children: [
|
|
843
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenu, { children: [
|
|
844
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
845
|
-
"button",
|
|
846
|
-
{
|
|
847
|
-
type: "button",
|
|
848
|
-
className: "flex items-center gap-2 rounded-md px-2 py-1 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/40 transition-colors",
|
|
849
|
-
title: authorHandle || t("author"),
|
|
850
|
-
children: [
|
|
851
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
852
|
-
"span",
|
|
853
|
-
{
|
|
854
|
-
className: "h-2 w-2 rounded-full border border-border",
|
|
855
|
-
style: { backgroundColor: authorColor || "#F1E9D0" }
|
|
856
|
-
}
|
|
857
|
-
),
|
|
858
|
-
showAuthorHandle ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px]", children: authorHandle || "user" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px]", children: t("author") })
|
|
859
|
-
]
|
|
860
|
-
}
|
|
861
|
-
) }),
|
|
862
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuContent, { align: "start", children: [
|
|
863
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuLabel, { children: t("author") }),
|
|
864
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuItem, { disabled: true, children: authorHandle || "user" }),
|
|
865
|
-
onToggleAuthorHandle ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
866
|
-
DropdownMenuItem,
|
|
867
|
-
{
|
|
868
|
-
onSelect: (e) => {
|
|
869
|
-
e.preventDefault();
|
|
870
|
-
onToggleAuthorHandle();
|
|
871
|
-
},
|
|
872
|
-
children: showAuthorHandle ? t("hide_name") : t("show_name")
|
|
873
|
-
}
|
|
874
|
-
) : null
|
|
875
|
-
] })
|
|
876
|
-
] }) }),
|
|
877
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
878
|
-
SegmentedControl,
|
|
879
|
-
{
|
|
880
|
-
value: kind,
|
|
881
|
-
onValueChange: setKind,
|
|
882
|
-
size: "sm",
|
|
883
|
-
ariaLabel: t("type_label"),
|
|
884
|
-
items: [
|
|
885
|
-
{
|
|
886
|
-
value: "note",
|
|
887
|
-
label: t("type_note"),
|
|
888
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(StickyNote, { className: "h-4 w-4" })
|
|
889
|
-
},
|
|
890
|
-
{
|
|
891
|
-
value: "question",
|
|
892
|
-
label: "Q",
|
|
893
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleHelp, { className: "h-4 w-4" })
|
|
894
|
-
},
|
|
895
|
-
{
|
|
896
|
-
value: "task",
|
|
897
|
-
label: t("type_task"),
|
|
898
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SquareCheckBig, { className: "h-4 w-4" })
|
|
899
|
-
}
|
|
900
|
-
],
|
|
901
|
-
className: "w-full justify-between"
|
|
902
|
-
}
|
|
903
|
-
),
|
|
904
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
905
|
-
Textarea,
|
|
906
|
-
{
|
|
907
|
-
autoFocus: true,
|
|
908
|
-
value: text,
|
|
909
|
-
onChange: (e) => setText(e.target.value),
|
|
910
|
-
placeholder,
|
|
911
|
-
className: "min-h-[92px] resize-none",
|
|
912
|
-
onKeyDown: (e) => {
|
|
913
|
-
if (e.key === "Escape") {
|
|
914
|
-
e.preventDefault();
|
|
915
|
-
onCancel?.();
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
919
|
-
e.preventDefault();
|
|
920
|
-
handleSave();
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
),
|
|
925
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
926
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("save_shortcut") }),
|
|
927
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
928
|
-
onAskCopilot ? /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "button", variant: "outline", onClick: onAskCopilot, className: "h-8", children: t("ask_copilot") }) : null,
|
|
929
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "button", onClick: handleSave, className: "h-8", children: t("save") })
|
|
930
|
-
] })
|
|
931
|
-
] })
|
|
932
|
-
] })
|
|
933
|
-
]
|
|
934
|
-
}
|
|
935
|
-
);
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
const highlight = "_highlight_dilkk_1";
|
|
939
|
-
const parts = "_parts_dilkk_12";
|
|
940
|
-
const part$1 = "_part_dilkk_12";
|
|
941
|
-
const partFocused = "_partFocused_dilkk_30";
|
|
942
|
-
const scrolledTo$1 = "_scrolledTo_dilkk_38";
|
|
943
|
-
const partNote = "_partNote_dilkk_77";
|
|
944
|
-
const partQuestion = "_partQuestion_dilkk_82";
|
|
945
|
-
const partTask = "_partTask_dilkk_88";
|
|
946
|
-
const hintChip = "_hintChip_dilkk_94";
|
|
947
|
-
const hintChipFocused = "_hintChipFocused_dilkk_139";
|
|
948
|
-
const hintPrimaryLine = "_hintPrimaryLine_dilkk_146";
|
|
949
|
-
const hintSecondaryLine = "_hintSecondaryLine_dilkk_159";
|
|
950
|
-
const styles$1 = {
|
|
951
|
-
highlight: highlight,
|
|
952
|
-
parts: parts,
|
|
953
|
-
part: part$1,
|
|
954
|
-
partFocused: partFocused,
|
|
955
|
-
scrolledTo: scrolledTo$1,
|
|
956
|
-
partNote: partNote,
|
|
957
|
-
partQuestion: partQuestion,
|
|
958
|
-
partTask: partTask,
|
|
959
|
-
hintChip: hintChip,
|
|
960
|
-
hintChipFocused: hintChipFocused,
|
|
961
|
-
hintPrimaryLine: hintPrimaryLine,
|
|
962
|
-
hintSecondaryLine: hintSecondaryLine};
|
|
963
|
-
|
|
964
|
-
function clamp01(value) {
|
|
965
|
-
return Math.max(0, Math.min(1, value));
|
|
966
|
-
}
|
|
967
|
-
function hexToRgb(hex) {
|
|
968
|
-
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
|
969
|
-
if (!m) return null;
|
|
970
|
-
const int = Number.parseInt(m[1], 16);
|
|
971
|
-
return { r: int >> 16 & 255, g: int >> 8 & 255, b: int & 255 };
|
|
972
|
-
}
|
|
973
|
-
function rgba(hex, alpha) {
|
|
974
|
-
const rgb = hexToRgb(hex);
|
|
975
|
-
const a = clamp01(alpha);
|
|
976
|
-
if (!rgb) return `rgba(241, 233, 208, ${a})`;
|
|
977
|
-
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a})`;
|
|
978
|
-
}
|
|
979
|
-
function Highlight({
|
|
980
|
-
position,
|
|
981
|
-
onClick,
|
|
982
|
-
onMouseOver,
|
|
983
|
-
onMouseOut,
|
|
984
|
-
comment,
|
|
985
|
-
color,
|
|
986
|
-
isScrolledTo,
|
|
987
|
-
hidden = false,
|
|
988
|
-
emphasized = false,
|
|
989
|
-
onHoverStart,
|
|
990
|
-
onHoverEnd,
|
|
991
|
-
showOverlayHint = false,
|
|
992
|
-
overlayHintText = "View details",
|
|
993
|
-
overlayHintTitle,
|
|
994
|
-
overlayHintBorderColor,
|
|
995
|
-
overlayHintTextColor,
|
|
996
|
-
overlayHintBackgroundColor,
|
|
997
|
-
overlayHintOffsetY = 0,
|
|
998
|
-
overlayHintTopPx,
|
|
999
|
-
overlayHintLeftPx,
|
|
1000
|
-
overlayHintWidthPx,
|
|
1001
|
-
overlayHintPageHeightPx,
|
|
1002
|
-
onOverlayHintClick
|
|
1003
|
-
}) {
|
|
1004
|
-
if (hidden) return null;
|
|
1005
|
-
const { rects } = position;
|
|
1006
|
-
const normalizedHintText = String(overlayHintText || "").trim() || "Annotation";
|
|
1007
|
-
const [primaryLine, secondaryLine] = normalizedHintText.split("\n").filter(Boolean);
|
|
1008
|
-
const kind = comment?.emoji === "question" || comment?.emoji === "task" ? comment.emoji : "note";
|
|
1009
|
-
const kindClass = kind === "question" ? styles$1.partQuestion : kind === "task" ? styles$1.partTask : styles$1.partNote;
|
|
1010
|
-
const alpha = kind === "question" ? 0.1 : kind === "task" ? 0.08 : 0.14;
|
|
1011
|
-
const firstRect = rects[0];
|
|
1012
|
-
const hintTop = firstRect ? Math.max(0, Number(firstRect.top || 0) + Number(firstRect.height || 0) * 0.5 - 9) : 0;
|
|
1013
|
-
const HINT_HEIGHT_ESTIMATE = 88;
|
|
1014
|
-
const hasLayoutTop = typeof overlayHintTopPx === "number" && Number.isFinite(overlayHintTopPx);
|
|
1015
|
-
const rawHintTop = hasLayoutTop ? Math.max(0, overlayHintTopPx) : Math.max(0, hintTop + overlayHintOffsetY);
|
|
1016
|
-
const boundedHintTop = hasLayoutTop || !(typeof overlayHintPageHeightPx === "number" && Number.isFinite(overlayHintPageHeightPx)) ? rawHintTop : Math.max(0, Math.min(rawHintTop, overlayHintPageHeightPx - HINT_HEIGHT_ESTIMATE));
|
|
1017
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1018
|
-
"div",
|
|
1019
|
-
{
|
|
1020
|
-
className: `Highlight ${styles$1.highlight} ${isScrolledTo ? styles$1.scrolledTo : ""}`,
|
|
1021
|
-
children: [
|
|
1022
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `Highlight__parts ${styles$1.parts}`, children: rects.map((rect, index) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1023
|
-
"div",
|
|
1024
|
-
{
|
|
1025
|
-
onMouseOver: () => {
|
|
1026
|
-
onMouseOver?.();
|
|
1027
|
-
onHoverStart?.();
|
|
1028
|
-
},
|
|
1029
|
-
onMouseOut: () => {
|
|
1030
|
-
onMouseOut?.();
|
|
1031
|
-
onHoverEnd?.();
|
|
1032
|
-
},
|
|
1033
|
-
onClick,
|
|
1034
|
-
"data-ds-highlight-target": "true",
|
|
1035
|
-
style: {
|
|
1036
|
-
...rect,
|
|
1037
|
-
backgroundColor: rgba(color || "#F1E9D0", alpha)
|
|
1038
|
-
},
|
|
1039
|
-
className: cn(
|
|
1040
|
-
"Highlight__part",
|
|
1041
|
-
styles$1.part,
|
|
1042
|
-
kindClass,
|
|
1043
|
-
emphasized ? styles$1.partFocused : null
|
|
1044
|
-
)
|
|
1045
|
-
},
|
|
1046
|
-
index
|
|
1047
|
-
)) }),
|
|
1048
|
-
showOverlayHint && firstRect ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1049
|
-
"button",
|
|
1050
|
-
{
|
|
1051
|
-
type: "button",
|
|
1052
|
-
"data-ds-overlay-hint": "true",
|
|
1053
|
-
onClick: (event) => {
|
|
1054
|
-
event.preventDefault();
|
|
1055
|
-
event.stopPropagation();
|
|
1056
|
-
if (onOverlayHintClick) {
|
|
1057
|
-
onOverlayHintClick();
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
onClick?.();
|
|
1061
|
-
},
|
|
1062
|
-
onMouseEnter: () => onHoverStart?.(),
|
|
1063
|
-
onMouseLeave: () => onHoverEnd?.(),
|
|
1064
|
-
className: cn(styles$1.hintChip, emphasized ? styles$1.hintChipFocused : null),
|
|
1065
|
-
style: {
|
|
1066
|
-
top: boundedHintTop,
|
|
1067
|
-
...typeof overlayHintLeftPx === "number" && Number.isFinite(overlayHintLeftPx) ? { left: overlayHintLeftPx } : {},
|
|
1068
|
-
...typeof overlayHintWidthPx === "number" && Number.isFinite(overlayHintWidthPx) ? { width: overlayHintWidthPx, maxWidth: overlayHintWidthPx } : {},
|
|
1069
|
-
...overlayHintBorderColor ? { borderColor: overlayHintBorderColor } : {},
|
|
1070
|
-
...overlayHintTextColor ? { color: overlayHintTextColor } : {},
|
|
1071
|
-
...overlayHintBackgroundColor ? { background: overlayHintBackgroundColor } : {}
|
|
1072
|
-
},
|
|
1073
|
-
"aria-label": overlayHintTitle || normalizedHintText,
|
|
1074
|
-
children: [
|
|
1075
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: styles$1.hintPrimaryLine, children: primaryLine || normalizedHintText }),
|
|
1076
|
-
secondaryLine ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: styles$1.hintSecondaryLine, children: secondaryLine }) : null
|
|
1077
|
-
]
|
|
1078
|
-
}
|
|
1079
|
-
) : null
|
|
1080
|
-
]
|
|
1081
|
-
}
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
const areaHighlight = "_areaHighlight_7ldrr_1";
|
|
1086
|
-
const part = "_part_7ldrr_8";
|
|
1087
|
-
const scrolledTo = "_scrolledTo_7ldrr_20";
|
|
1088
|
-
const focused = "_focused_7ldrr_28";
|
|
1089
|
-
const styles = {
|
|
1090
|
-
areaHighlight: areaHighlight,
|
|
1091
|
-
part: part,
|
|
1092
|
-
scrolledTo: scrolledTo,
|
|
1093
|
-
focused: focused
|
|
1094
|
-
};
|
|
1095
|
-
|
|
1096
|
-
function AreaHighlight({
|
|
1097
|
-
highlight,
|
|
1098
|
-
onChange,
|
|
1099
|
-
isScrolledTo,
|
|
1100
|
-
onSelect,
|
|
1101
|
-
hidden = false,
|
|
1102
|
-
emphasized = false,
|
|
1103
|
-
onHoverStart,
|
|
1104
|
-
onHoverEnd,
|
|
1105
|
-
...otherProps
|
|
1106
|
-
}) {
|
|
1107
|
-
if (hidden) return null;
|
|
1108
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1109
|
-
"div",
|
|
1110
|
-
{
|
|
1111
|
-
className: `${styles.areaHighlight} ${isScrolledTo ? styles.scrolledTo : ""} ${emphasized ? styles.focused : ""}`,
|
|
1112
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1113
|
-
Rnd,
|
|
1114
|
-
{
|
|
1115
|
-
className: styles.part,
|
|
1116
|
-
"data-ds-highlight-target": "true",
|
|
1117
|
-
onDragStop: (_, data) => {
|
|
1118
|
-
const boundingRect = {
|
|
1119
|
-
...highlight.position.boundingRect,
|
|
1120
|
-
top: data.y,
|
|
1121
|
-
left: data.x
|
|
1122
|
-
};
|
|
1123
|
-
onChange(boundingRect);
|
|
1124
|
-
},
|
|
1125
|
-
onResizeStop: (_mouseEvent, _direction, ref, _delta, position) => {
|
|
1126
|
-
const boundingRect = {
|
|
1127
|
-
top: position.y,
|
|
1128
|
-
left: position.x,
|
|
1129
|
-
width: ref.offsetWidth,
|
|
1130
|
-
height: ref.offsetHeight,
|
|
1131
|
-
pageNumber: getPageFromElement(ref)?.number || -1
|
|
1132
|
-
};
|
|
1133
|
-
onChange(boundingRect);
|
|
1134
|
-
},
|
|
1135
|
-
position: {
|
|
1136
|
-
x: highlight.position.boundingRect.left,
|
|
1137
|
-
y: highlight.position.boundingRect.top
|
|
1138
|
-
},
|
|
1139
|
-
size: {
|
|
1140
|
-
width: highlight.position.boundingRect.width,
|
|
1141
|
-
height: highlight.position.boundingRect.height
|
|
1142
|
-
},
|
|
1143
|
-
onClick: (event) => {
|
|
1144
|
-
event.stopPropagation();
|
|
1145
|
-
event.preventDefault();
|
|
1146
|
-
onSelect?.();
|
|
1147
|
-
},
|
|
1148
|
-
onMouseEnter: () => onHoverStart?.(),
|
|
1149
|
-
onMouseLeave: () => onHoverEnd?.(),
|
|
1150
|
-
...otherProps
|
|
1151
|
-
}
|
|
1152
|
-
)
|
|
1153
|
-
}
|
|
1154
|
-
);
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
function asNumber(value) {
|
|
1158
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1159
|
-
if (typeof value === "string") {
|
|
1160
|
-
const parsed = Number(value);
|
|
1161
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
1162
|
-
}
|
|
1163
|
-
return null;
|
|
1164
|
-
}
|
|
1165
|
-
function asNonEmptyString(value) {
|
|
1166
|
-
if (typeof value !== "string") return null;
|
|
1167
|
-
const trimmed = value.trim();
|
|
1168
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
1169
|
-
}
|
|
1170
|
-
function normalizeReviewObjectTypeToken(value) {
|
|
1171
|
-
if (!value) return null;
|
|
1172
|
-
const token = value.trim().toLowerCase();
|
|
1173
|
-
if (!token) return null;
|
|
1174
|
-
if (token === "evidence") return "suggestion";
|
|
1175
|
-
if (token === "issue" || token === "suggestion" || token === "verification") return token;
|
|
1176
|
-
if (token === "needs_verification" || token === "needs verification" || token === "verify" || token === "uncertain") {
|
|
1177
|
-
return "verification";
|
|
1178
|
-
}
|
|
1179
|
-
return null;
|
|
1180
|
-
}
|
|
1181
|
-
function parseRectLike(raw, fallbackPageNumber) {
|
|
1182
|
-
if (!raw || typeof raw !== "object") return null;
|
|
1183
|
-
const row = raw;
|
|
1184
|
-
const x1 = asNumber(row.x1);
|
|
1185
|
-
const y1 = asNumber(row.y1);
|
|
1186
|
-
const x2 = asNumber(row.x2);
|
|
1187
|
-
const y2 = asNumber(row.y2);
|
|
1188
|
-
if (x1 === null || y1 === null || x2 === null || y2 === null) return null;
|
|
1189
|
-
const widthRaw = asNumber(row.width);
|
|
1190
|
-
const heightRaw = asNumber(row.height);
|
|
1191
|
-
const width = widthRaw && widthRaw > 0 ? widthRaw : 100;
|
|
1192
|
-
const height = heightRaw && heightRaw > 0 ? heightRaw : 100;
|
|
1193
|
-
const pageNumberRaw = asNumber(row.pageNumber);
|
|
1194
|
-
const pageNumber = pageNumberRaw && pageNumberRaw > 0 ? Math.trunc(pageNumberRaw) : fallbackPageNumber;
|
|
1195
|
-
return {
|
|
1196
|
-
x1: Math.min(x1, x2),
|
|
1197
|
-
y1: Math.min(y1, y2),
|
|
1198
|
-
x2: Math.max(x1, x2),
|
|
1199
|
-
y2: Math.max(y1, y2),
|
|
1200
|
-
width,
|
|
1201
|
-
height,
|
|
1202
|
-
...pageNumber ? { pageNumber } : {}
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
function mapReadOnlyAnnotationToHighlight(row) {
|
|
1206
|
-
const id = asNonEmptyString(row.annotation_id) || asNonEmptyString(row.annotationId) || asNonEmptyString(row.id);
|
|
1207
|
-
if (!id) return null;
|
|
1208
|
-
const pageNumberRaw = asNumber(row.page_number) || asNumber(row.pageNumber) || asNumber(row.position?.pageNumber);
|
|
1209
|
-
const pageNumber = pageNumberRaw && pageNumberRaw > 0 ? Math.trunc(pageNumberRaw) : null;
|
|
1210
|
-
if (!pageNumber) return null;
|
|
1211
|
-
const rawRects = Array.isArray(row.rects) ? row.rects : [];
|
|
1212
|
-
const rects = rawRects.map((item) => parseRectLike(item, pageNumber)).filter((item) => Boolean(item));
|
|
1213
|
-
const boundingRect = parseRectLike(row.bounding_rect, pageNumber) || parseRectLike(row.boundingRect, pageNumber) || parseRectLike(row.position?.boundingRect, pageNumber) || null;
|
|
1214
|
-
const fallbackBoundingRect = rects.length ? rects.reduce(
|
|
1215
|
-
(acc, rect) => ({
|
|
1216
|
-
x1: Math.min(acc.x1, rect.x1),
|
|
1217
|
-
y1: Math.min(acc.y1, rect.y1),
|
|
1218
|
-
x2: Math.max(acc.x2, rect.x2),
|
|
1219
|
-
y2: Math.max(acc.y2, rect.y2),
|
|
1220
|
-
width: acc.width || rect.width || 100,
|
|
1221
|
-
height: acc.height || rect.height || 100,
|
|
1222
|
-
pageNumber
|
|
1223
|
-
}),
|
|
1224
|
-
{ ...rects[0], pageNumber }
|
|
1225
|
-
) : null;
|
|
1226
|
-
const resolvedBoundingRect = boundingRect || fallbackBoundingRect;
|
|
1227
|
-
if (!resolvedBoundingRect) return null;
|
|
1228
|
-
const color = asNonEmptyString(row.color) || "#F1E9D0";
|
|
1229
|
-
const objectType = normalizeReviewObjectTypeToken(asNonEmptyString(row.object_type));
|
|
1230
|
-
const severity = asNonEmptyString(row.severity);
|
|
1231
|
-
const reviewItemId = asNonEmptyString(row.review_item_id);
|
|
1232
|
-
const displayText = asNonEmptyString(row.display_text) || asNonEmptyString(row.content_text) || asNonEmptyString(row.content?.text) || "";
|
|
1233
|
-
const commentText = asNonEmptyString(row.comment) || asNonEmptyString(row.comment_data?.text) || displayText;
|
|
1234
|
-
const previewSummary = asNonEmptyString(row.summary) || asNonEmptyString(row.content?.summary) || asNonEmptyString(row.comment_data?.summary) || null;
|
|
1235
|
-
const incomingTags = Array.isArray(row.tags) ? row.tags.map((tag) => asNonEmptyString(tag)).filter((tag) => Boolean(tag)) : [];
|
|
1236
|
-
const mergedTagSet = new Set(incomingTags.map((tag) => tag.toLowerCase()));
|
|
1237
|
-
mergedTagSet.add("review_annotation");
|
|
1238
|
-
if (objectType && (objectType === "issue" || objectType === "suggestion" || objectType === "verification")) {
|
|
1239
|
-
mergedTagSet.add(`review_object:${objectType}`);
|
|
1240
|
-
}
|
|
1241
|
-
if (severity) {
|
|
1242
|
-
mergedTagSet.add(`severity:${severity.toLowerCase()}`);
|
|
1243
|
-
}
|
|
1244
|
-
if (reviewItemId) {
|
|
1245
|
-
mergedTagSet.add(`review_item:${reviewItemId}`);
|
|
1246
|
-
}
|
|
1247
|
-
return {
|
|
1248
|
-
id,
|
|
1249
|
-
position: {
|
|
1250
|
-
pageNumber,
|
|
1251
|
-
rects: rects.length ? rects : [resolvedBoundingRect],
|
|
1252
|
-
boundingRect: resolvedBoundingRect
|
|
1253
|
-
},
|
|
1254
|
-
content: { text: displayText },
|
|
1255
|
-
comment: { text: commentText, emoji: "note" },
|
|
1256
|
-
color,
|
|
1257
|
-
tags: Array.from(mergedTagSet),
|
|
1258
|
-
previewSummary,
|
|
1259
|
-
author: null
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
function normalizeKind(value) {
|
|
1263
|
-
if (value === "question" || value === "task") return value;
|
|
1264
|
-
return "note";
|
|
1265
|
-
}
|
|
1266
|
-
const KIND_META = {
|
|
1267
|
-
note: {
|
|
1268
|
-
label: "Note",
|
|
1269
|
-
dotClass: "bg-amber-400",
|
|
1270
|
-
iconClass: "text-amber-600",
|
|
1271
|
-
Icon: StickyNote
|
|
1272
|
-
},
|
|
1273
|
-
question: {
|
|
1274
|
-
label: "Q",
|
|
1275
|
-
dotClass: "bg-indigo-400",
|
|
1276
|
-
iconClass: "text-indigo-600",
|
|
1277
|
-
Icon: CircleHelp
|
|
1278
|
-
},
|
|
1279
|
-
task: {
|
|
1280
|
-
label: "Task",
|
|
1281
|
-
dotClass: "bg-emerald-400",
|
|
1282
|
-
iconClass: "text-emerald-600",
|
|
1283
|
-
Icon: SquareCheckBig
|
|
1284
|
-
}
|
|
1285
|
-
};
|
|
1286
|
-
const REVIEW_OBJECT_META = {
|
|
1287
|
-
issue: {
|
|
1288
|
-
label: "Issue",
|
|
1289
|
-
dotClass: "bg-rose-500",
|
|
1290
|
-
iconClass: "text-rose-600",
|
|
1291
|
-
Icon: TriangleAlert
|
|
1292
|
-
},
|
|
1293
|
-
suggestion: {
|
|
1294
|
-
label: "Suggestion",
|
|
1295
|
-
dotClass: "bg-emerald-500",
|
|
1296
|
-
iconClass: "text-emerald-600",
|
|
1297
|
-
Icon: Sparkles
|
|
1298
|
-
},
|
|
1299
|
-
verification: {
|
|
1300
|
-
label: "Needs Verification",
|
|
1301
|
-
dotClass: "bg-amber-500",
|
|
1302
|
-
iconClass: "text-amber-600",
|
|
1303
|
-
Icon: CircleHelp
|
|
1304
|
-
}
|
|
1305
|
-
};
|
|
1306
|
-
function extractReviewObjectType(tags) {
|
|
1307
|
-
let hasReviewAnnotationTag = false;
|
|
1308
|
-
for (const tag of tags || []) {
|
|
1309
|
-
const normalized = String(tag || "").trim().toLowerCase();
|
|
1310
|
-
if (normalized === "review_annotation") {
|
|
1311
|
-
hasReviewAnnotationTag = true;
|
|
1312
|
-
continue;
|
|
1313
|
-
}
|
|
1314
|
-
if (normalized === "review_object:issue") return "issue";
|
|
1315
|
-
if (normalized === "review_object:suggestion") return "suggestion";
|
|
1316
|
-
if (normalized === "review_object:verification" || normalized === "review_object:needs_verification") {
|
|
1317
|
-
return "verification";
|
|
1318
|
-
}
|
|
1319
|
-
if (normalized === "review_object:evidence") return "suggestion";
|
|
1320
|
-
}
|
|
1321
|
-
if (!hasReviewAnnotationTag) return null;
|
|
1322
|
-
for (const tag of tags || []) {
|
|
1323
|
-
const normalized = String(tag || "").trim().toLowerCase();
|
|
1324
|
-
if (normalized === "ds_kind:question") return "issue";
|
|
1325
|
-
if (normalized === "ds_kind:note" || normalized === "ds_kind:task") return "suggestion";
|
|
1326
|
-
}
|
|
1327
|
-
return null;
|
|
1328
|
-
}
|
|
1329
|
-
function extractAnnotationKindFromTags(tags) {
|
|
1330
|
-
for (const tag of tags || []) {
|
|
1331
|
-
const normalized = String(tag || "").trim().toLowerCase();
|
|
1332
|
-
if (normalized === "ds_kind:question") return "question";
|
|
1333
|
-
if (normalized === "ds_kind:task") return "task";
|
|
1334
|
-
if (normalized === "ds_kind:note") return "note";
|
|
1335
|
-
}
|
|
1336
|
-
return null;
|
|
1337
|
-
}
|
|
1338
|
-
function extractReviewItemId(tags) {
|
|
1339
|
-
for (const tag of tags || []) {
|
|
1340
|
-
const token = String(tag || "").trim();
|
|
1341
|
-
if (!token) continue;
|
|
1342
|
-
if (!token.toLowerCase().startsWith("review_item:")) continue;
|
|
1343
|
-
const value = token.slice("review_item:".length).trim();
|
|
1344
|
-
if (value) return value;
|
|
1345
|
-
}
|
|
1346
|
-
return null;
|
|
1347
|
-
}
|
|
1348
|
-
function resolveHighlightMeta(highlight) {
|
|
1349
|
-
const kind = normalizeKind(highlight.comment?.emoji || extractAnnotationKindFromTags(highlight.tags) || void 0);
|
|
1350
|
-
const reviewObjectType = extractReviewObjectType(highlight.tags);
|
|
1351
|
-
if (reviewObjectType) {
|
|
1352
|
-
return {
|
|
1353
|
-
kind,
|
|
1354
|
-
reviewObjectType,
|
|
1355
|
-
...REVIEW_OBJECT_META[reviewObjectType]
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
return {
|
|
1359
|
-
kind,
|
|
1360
|
-
reviewObjectType: null,
|
|
1361
|
-
...KIND_META[kind]
|
|
1362
|
-
};
|
|
1363
|
-
}
|
|
1364
|
-
function getAnnotationKindLabel(kind, t) {
|
|
1365
|
-
if (kind === "question") return t("type_question");
|
|
1366
|
-
if (kind === "task") return t("type_task");
|
|
1367
|
-
return t("type_note");
|
|
1368
|
-
}
|
|
1369
|
-
function getReviewObjectTypeLabel(value, t) {
|
|
1370
|
-
if (value === "issue") return t("review_type_issue");
|
|
1371
|
-
if (value === "verification") return t("review_type_verification");
|
|
1372
|
-
return t("review_type_suggestion");
|
|
1373
|
-
}
|
|
1374
|
-
function getHighlightMetaLabel(meta, t) {
|
|
1375
|
-
if (meta.reviewObjectType) {
|
|
1376
|
-
return getReviewObjectTypeLabel(meta.reviewObjectType, t);
|
|
1377
|
-
}
|
|
1378
|
-
return getAnnotationKindLabel(meta.kind, t);
|
|
1379
|
-
}
|
|
1380
|
-
function overlayHintTheme(meta) {
|
|
1381
|
-
return {
|
|
1382
|
-
borderColor: "rgba(140, 118, 72, 0.95)",
|
|
1383
|
-
textColor: "rgba(15, 15, 15, 0.96)",
|
|
1384
|
-
backgroundColor: "rgba(255, 255, 255, 0.98)"
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1387
|
-
function compactWords(text, maxWords) {
|
|
1388
|
-
const normalized = String(text || "").normalize("NFKC").replace(/\uFFFD/g, " ").replace(/\s+/g, " ").trim();
|
|
1389
|
-
if (!normalized) return "";
|
|
1390
|
-
if (maxWords <= 0) return "";
|
|
1391
|
-
const words = normalized.split(" ");
|
|
1392
|
-
if (words.length <= maxWords) return normalized;
|
|
1393
|
-
return `${words.slice(0, maxWords).join(" ")}…`;
|
|
1394
|
-
}
|
|
1395
|
-
const MARKDOWN_CODE_BLOCK_TOKEN = "__DS_ANNOTATION_MD_CODE_BLOCK__";
|
|
1396
|
-
const MARKDOWN_INLINE_CODE_TOKEN = "__DS_ANNOTATION_MD_INLINE_CODE__";
|
|
1397
|
-
const FENCED_CODE_REGEX = /(^|\n)(```|~~~)[\s\S]*?\n\2[^\n]*($|\n)/g;
|
|
1398
|
-
const INLINE_CODE_REGEX = /`[^`\n]*`/g;
|
|
1399
|
-
const BLOCK_MATH_BRACKET_REGEX = /\\\[([\s\S]+?)\\\]/g;
|
|
1400
|
-
const INLINE_MATH_PAREN_REGEX = /\\\((.+?)\\\)/g;
|
|
1401
|
-
function protectSegments(input, regex, token) {
|
|
1402
|
-
const segments = [];
|
|
1403
|
-
const text = input.replace(regex, (match) => {
|
|
1404
|
-
const index = segments.length;
|
|
1405
|
-
segments.push(match);
|
|
1406
|
-
return `${token}${index}__`;
|
|
1407
|
-
});
|
|
1408
|
-
return { text, segments };
|
|
1409
|
-
}
|
|
1410
|
-
function restoreSegments(input, segments, token) {
|
|
1411
|
-
let text = input;
|
|
1412
|
-
segments.forEach((segment, index) => {
|
|
1413
|
-
text = text.replace(`${token}${index}__`, segment);
|
|
1414
|
-
});
|
|
1415
|
-
return text;
|
|
1416
|
-
}
|
|
1417
|
-
function normalizeAnnotationCommentMarkdown(markdown) {
|
|
1418
|
-
if (!markdown) return markdown;
|
|
1419
|
-
const { text: withoutFenced, segments: fencedBlocks } = protectSegments(
|
|
1420
|
-
markdown,
|
|
1421
|
-
FENCED_CODE_REGEX,
|
|
1422
|
-
MARKDOWN_CODE_BLOCK_TOKEN
|
|
1423
|
-
);
|
|
1424
|
-
const { text: withoutInline, segments: inlineBlocks } = protectSegments(
|
|
1425
|
-
withoutFenced,
|
|
1426
|
-
INLINE_CODE_REGEX,
|
|
1427
|
-
MARKDOWN_INLINE_CODE_TOKEN
|
|
1428
|
-
);
|
|
1429
|
-
let transformed = withoutInline;
|
|
1430
|
-
transformed = transformed.replace(BLOCK_MATH_BRACKET_REGEX, (_match, latex) => {
|
|
1431
|
-
const value = String(latex || "").trim();
|
|
1432
|
-
return value ? `$$
|
|
1433
|
-
${value}
|
|
1434
|
-
$$` : "";
|
|
1435
|
-
});
|
|
1436
|
-
transformed = transformed.replace(INLINE_MATH_PAREN_REGEX, (_match, latex) => {
|
|
1437
|
-
const value = String(latex || "").trim();
|
|
1438
|
-
return value ? `$${value}$` : "";
|
|
1439
|
-
});
|
|
1440
|
-
const restoredInline = restoreSegments(
|
|
1441
|
-
transformed,
|
|
1442
|
-
inlineBlocks,
|
|
1443
|
-
MARKDOWN_INLINE_CODE_TOKEN
|
|
1444
|
-
);
|
|
1445
|
-
return restoreSegments(restoredInline, fencedBlocks, MARKDOWN_CODE_BLOCK_TOKEN);
|
|
1446
|
-
}
|
|
1447
|
-
const MARKDOWN_EXTENSIONS = [".md", ".markdown", ".mdx"];
|
|
1448
|
-
function isMarkdownFileName(name) {
|
|
1449
|
-
const lower = name.toLowerCase();
|
|
1450
|
-
return MARKDOWN_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
1451
|
-
}
|
|
1452
|
-
function normalizeExcerptText(value) {
|
|
1453
|
-
return String(value || "").replace(/[\u2010-\u2015]/g, "-").replace(/\s+/g, " ").trim();
|
|
1454
|
-
}
|
|
1455
|
-
function locateMarkdownExcerpt(markdown, selectedText) {
|
|
1456
|
-
const normalizedMarkdown = normalizeExcerptText(markdown);
|
|
1457
|
-
const normalizedSelection = normalizeExcerptText(selectedText);
|
|
1458
|
-
if (!normalizedMarkdown || !normalizedSelection || normalizedSelection.length < 12) {
|
|
1459
|
-
return null;
|
|
1460
|
-
}
|
|
1461
|
-
const exactIndex = normalizedMarkdown.indexOf(normalizedSelection);
|
|
1462
|
-
if (exactIndex >= 0) {
|
|
1463
|
-
const start2 = Math.max(0, exactIndex - 160);
|
|
1464
|
-
const end2 = Math.min(normalizedMarkdown.length, exactIndex + normalizedSelection.length + 160);
|
|
1465
|
-
return normalizedMarkdown.slice(start2, end2).trim();
|
|
1466
|
-
}
|
|
1467
|
-
const words = normalizedSelection.split(" ").filter(Boolean).slice(0, 8);
|
|
1468
|
-
if (words.length < 3) return null;
|
|
1469
|
-
const pattern = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*?");
|
|
1470
|
-
const match = normalizedMarkdown.match(new RegExp(pattern, "i"));
|
|
1471
|
-
if (!match || typeof match.index !== "number") return null;
|
|
1472
|
-
const start = Math.max(0, match.index - 160);
|
|
1473
|
-
const end = Math.min(normalizedMarkdown.length, match.index + match[0].length + 160);
|
|
1474
|
-
return normalizedMarkdown.slice(start, end).trim();
|
|
1475
|
-
}
|
|
1476
|
-
function PdfSpinner() {
|
|
1477
|
-
const { t } = useI18n("pdf_viewer");
|
|
1478
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-full items-center justify-center bg-muted/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-3 text-muted-foreground", children: [
|
|
1479
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-8 w-8 rounded-full border-2 border-primary/60 border-t-transparent animate-spin" }),
|
|
1480
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: t("loading_pdf") })
|
|
1481
|
-
] }) });
|
|
1482
|
-
}
|
|
1483
|
-
function AnnotationSidebarItem({
|
|
1484
|
-
highlight,
|
|
1485
|
-
selected,
|
|
1486
|
-
followed,
|
|
1487
|
-
flash,
|
|
1488
|
-
onSelect,
|
|
1489
|
-
onDelete,
|
|
1490
|
-
onUpdateKind,
|
|
1491
|
-
onUpdateComment,
|
|
1492
|
-
readOnly,
|
|
1493
|
-
registerItemRef
|
|
1494
|
-
}) {
|
|
1495
|
-
const { t } = useI18n("pdf_viewer");
|
|
1496
|
-
const meta = resolveHighlightMeta(highlight);
|
|
1497
|
-
const kind = meta.kind;
|
|
1498
|
-
const metaLabel = getHighlightMetaLabel(meta, t);
|
|
1499
|
-
const reviewItemId = extractReviewItemId(highlight.tags);
|
|
1500
|
-
const displayIdRaw = reviewItemId || String(highlight.id || "").slice(0, 8);
|
|
1501
|
-
const displayId = displayIdRaw.length > 10 ? `${displayIdRaw.slice(0, 10)}…` : displayIdRaw;
|
|
1502
|
-
const authorColor = highlight.author?.color || highlight.color || "#F1E9D0";
|
|
1503
|
-
const authorHandle = highlight.author?.handle || "user";
|
|
1504
|
-
const isAI = highlight.tags?.some((tag) => tag.toLowerCase() === "ai");
|
|
1505
|
-
const rawContentPreview = highlight.content?.text ? String(highlight.content.text).trim() : highlight.content?.image ? t("area_highlight") : "";
|
|
1506
|
-
const compactContentPreview = highlight.content?.text ? compactWords(rawContentPreview, 20) : rawContentPreview;
|
|
1507
|
-
const itemRef = reactExports.useRef(null);
|
|
1508
|
-
const [isEditing, setIsEditing] = reactExports.useState(false);
|
|
1509
|
-
const [draft, setDraft] = reactExports.useState(highlight.comment?.text || "");
|
|
1510
|
-
reactExports.useEffect(() => {
|
|
1511
|
-
if (!isEditing) setDraft(highlight.comment?.text || "");
|
|
1512
|
-
}, [highlight.comment?.text, isEditing]);
|
|
1513
|
-
reactExports.useEffect(() => {
|
|
1514
|
-
if (!flash) return;
|
|
1515
|
-
itemRef.current?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
1516
|
-
}, [flash]);
|
|
1517
|
-
reactExports.useEffect(() => {
|
|
1518
|
-
if (!selected) return;
|
|
1519
|
-
itemRef.current?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
1520
|
-
}, [selected]);
|
|
1521
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1522
|
-
"div",
|
|
1523
|
-
{
|
|
1524
|
-
ref: (node) => {
|
|
1525
|
-
itemRef.current = node;
|
|
1526
|
-
registerItemRef?.(highlight.id, node);
|
|
1527
|
-
},
|
|
1528
|
-
className: cn(
|
|
1529
|
-
"group relative p-3 rounded-lg border transition-all cursor-pointer",
|
|
1530
|
-
selected ? "border-primary bg-primary/5" : followed ? "border-border bg-muted/30 hover:bg-muted/45" : "border-border/60 hover:bg-muted/40",
|
|
1531
|
-
flash ? "ring-2 ring-primary/30 bg-primary/10 shadow-soft-card" : null
|
|
1532
|
-
),
|
|
1533
|
-
onClick: onSelect,
|
|
1534
|
-
children: [
|
|
1535
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1536
|
-
"span",
|
|
1537
|
-
{
|
|
1538
|
-
className: cn("absolute left-2 top-2 h-2 w-2 rounded-full", meta.dotClass),
|
|
1539
|
-
"aria-hidden": true
|
|
1540
|
-
}
|
|
1541
|
-
),
|
|
1542
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenu, { children: [
|
|
1543
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1544
|
-
"button",
|
|
1545
|
-
{
|
|
1546
|
-
type: "button",
|
|
1547
|
-
className: "absolute right-2 top-2 h-5 w-5 rounded-md border border-border bg-background/70 backdrop-blur flex items-center justify-center shadow-soft-sm",
|
|
1548
|
-
onClick: (e) => e.stopPropagation(),
|
|
1549
|
-
title: authorHandle,
|
|
1550
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1551
|
-
"span",
|
|
1552
|
-
{
|
|
1553
|
-
className: "h-2.5 w-2.5 rounded-full border border-border",
|
|
1554
|
-
style: { backgroundColor: authorColor }
|
|
1555
|
-
}
|
|
1556
|
-
)
|
|
1557
|
-
}
|
|
1558
|
-
) }),
|
|
1559
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuContent, { align: "end", children: [
|
|
1560
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuLabel, { children: t("author") }),
|
|
1561
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuItem, { disabled: true, children: authorHandle })
|
|
1562
|
-
] })
|
|
1563
|
-
] }),
|
|
1564
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3", children: [
|
|
1565
|
-
readOnly ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1566
|
-
"div",
|
|
1567
|
-
{
|
|
1568
|
-
className: cn(
|
|
1569
|
-
"mt-0.5 h-8 w-8 rounded-lg border border-border bg-background",
|
|
1570
|
-
"flex items-center justify-center flex-shrink-0 shadow-soft-sm"
|
|
1571
|
-
),
|
|
1572
|
-
title: t("type_label"),
|
|
1573
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1574
|
-
meta.Icon,
|
|
1575
|
-
{
|
|
1576
|
-
className: cn(
|
|
1577
|
-
"h-4 w-4",
|
|
1578
|
-
meta.iconClass
|
|
1579
|
-
)
|
|
1580
|
-
}
|
|
1581
|
-
)
|
|
1582
|
-
}
|
|
1583
|
-
) : /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenu, { children: [
|
|
1584
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1585
|
-
"button",
|
|
1586
|
-
{
|
|
1587
|
-
type: "button",
|
|
1588
|
-
className: cn(
|
|
1589
|
-
"mt-0.5 h-8 w-8 rounded-lg border border-border bg-background",
|
|
1590
|
-
"flex items-center justify-center flex-shrink-0 shadow-soft-sm"
|
|
1591
|
-
),
|
|
1592
|
-
title: t("change_type"),
|
|
1593
|
-
onClick: (e) => e.stopPropagation(),
|
|
1594
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1595
|
-
meta.Icon,
|
|
1596
|
-
{
|
|
1597
|
-
className: cn(
|
|
1598
|
-
"h-4 w-4",
|
|
1599
|
-
meta.iconClass
|
|
1600
|
-
)
|
|
1601
|
-
}
|
|
1602
|
-
)
|
|
1603
|
-
}
|
|
1604
|
-
) }),
|
|
1605
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuContent, { align: "start", children: [
|
|
1606
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuLabel, { children: t("type_label") }),
|
|
1607
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1608
|
-
DropdownMenuRadioGroup,
|
|
1609
|
-
{
|
|
1610
|
-
value: kind,
|
|
1611
|
-
onValueChange: (v) => onUpdateKind(normalizeKind(v)),
|
|
1612
|
-
children: [
|
|
1613
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuRadioItem, { value: "note", children: t("type_note") }),
|
|
1614
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuRadioItem, { value: "question", children: t("type_question") }),
|
|
1615
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuRadioItem, { value: "task", children: t("type_task") })
|
|
1616
|
-
]
|
|
1617
|
-
}
|
|
1618
|
-
)
|
|
1619
|
-
] })
|
|
1620
|
-
] }),
|
|
1621
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
1622
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1623
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-medium text-foreground/90", children: metaLabel }),
|
|
1624
|
-
isAI ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border border-border bg-muted/40 px-2 py-0.5 text-[9px] uppercase tracking-wide text-foreground/70", children: [
|
|
1625
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-3 w-3" }),
|
|
1626
|
-
"AI"
|
|
1627
|
-
] }) : null,
|
|
1628
|
-
typeof highlight.position?.pageNumber === "number" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-muted-foreground/70", children: t("page", { page: highlight.position.pageNumber }) }) : null,
|
|
1629
|
-
displayId ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1630
|
-
"span",
|
|
1631
|
-
{
|
|
1632
|
-
className: "text-[9px] text-muted-foreground/60",
|
|
1633
|
-
title: `ID ${displayIdRaw}`,
|
|
1634
|
-
children: [
|
|
1635
|
-
"ID ",
|
|
1636
|
-
displayId
|
|
1637
|
-
]
|
|
1638
|
-
}
|
|
1639
|
-
) : null
|
|
1640
|
-
] }),
|
|
1641
|
-
!isEditing ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 rounded-md border border-border/60 bg-background/70 px-2.5 py-2", children: highlight.comment?.text?.trim() ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1642
|
-
MarkdownPreview,
|
|
1643
|
-
{
|
|
1644
|
-
content: normalizeAnnotationCommentMarkdown(highlight.comment.text),
|
|
1645
|
-
rewriteDocsImages: false,
|
|
1646
|
-
rewriteDocsLinks: false,
|
|
1647
|
-
className: cn(
|
|
1648
|
-
"review-annotation-markdown text-[11px] leading-5 text-foreground/85 break-words",
|
|
1649
|
-
"[&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1 [&_li]:my-0.5",
|
|
1650
|
-
"[&_pre]:my-1 [&_pre]:max-w-full [&_pre]:overflow-x-auto",
|
|
1651
|
-
"[&_pre]:whitespace-pre-wrap [&_code]:break-words",
|
|
1652
|
-
"[&_.katex]:[word-break:normal] [&_.katex]:[overflow-wrap:normal]",
|
|
1653
|
-
"[&_.katex]:whitespace-nowrap [&_.katex-display]:whitespace-normal",
|
|
1654
|
-
"[&_.katex-display]:my-1 [&_.katex-display]:overflow-x-auto",
|
|
1655
|
-
"[&_.katex-display]:overflow-y-hidden"
|
|
1656
|
-
)
|
|
1657
|
-
}
|
|
1658
|
-
) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "review-annotation-text-fallback text-[11px] text-foreground/80 whitespace-pre-wrap break-words", children: t("no_comment_yet") }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1659
|
-
Textarea,
|
|
1660
|
-
{
|
|
1661
|
-
value: draft,
|
|
1662
|
-
onChange: (e) => setDraft(e.target.value),
|
|
1663
|
-
onClick: (e) => e.stopPropagation(),
|
|
1664
|
-
className: "mt-2 min-h-[84px] text-[11px] leading-5",
|
|
1665
|
-
placeholder: t("add_comment_placeholder")
|
|
1666
|
-
}
|
|
1667
|
-
),
|
|
1668
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex items-start justify-between gap-2", children: [
|
|
1669
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1670
|
-
"p",
|
|
1671
|
-
{
|
|
1672
|
-
className: "review-annotation-text-fallback text-[9px] text-muted-foreground/60 whitespace-pre-wrap break-words",
|
|
1673
|
-
title: rawContentPreview || void 0,
|
|
1674
|
-
children: compactContentPreview
|
|
1675
|
-
}
|
|
1676
|
-
),
|
|
1677
|
-
readOnly ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[9px] text-muted-foreground", children: t("read_only") }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1678
|
-
!isEditing ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1679
|
-
Button,
|
|
1680
|
-
{
|
|
1681
|
-
type: "button",
|
|
1682
|
-
variant: "ghost",
|
|
1683
|
-
size: "sm",
|
|
1684
|
-
className: "h-7 px-2 text-[10px]",
|
|
1685
|
-
onClick: (e) => {
|
|
1686
|
-
e.stopPropagation();
|
|
1687
|
-
setIsEditing(true);
|
|
1688
|
-
},
|
|
1689
|
-
children: "Edit"
|
|
1690
|
-
}
|
|
1691
|
-
) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
1692
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1693
|
-
Button,
|
|
1694
|
-
{
|
|
1695
|
-
type: "button",
|
|
1696
|
-
variant: "secondary",
|
|
1697
|
-
size: "sm",
|
|
1698
|
-
className: "h-7 px-2 text-[10px]",
|
|
1699
|
-
onClick: (e) => {
|
|
1700
|
-
e.stopPropagation();
|
|
1701
|
-
setIsEditing(false);
|
|
1702
|
-
setDraft(highlight.comment?.text || "");
|
|
1703
|
-
},
|
|
1704
|
-
children: "Cancel"
|
|
1705
|
-
}
|
|
1706
|
-
),
|
|
1707
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1708
|
-
Button,
|
|
1709
|
-
{
|
|
1710
|
-
type: "button",
|
|
1711
|
-
size: "sm",
|
|
1712
|
-
className: "h-7 px-2 text-[10px]",
|
|
1713
|
-
onClick: (e) => {
|
|
1714
|
-
e.stopPropagation();
|
|
1715
|
-
onUpdateComment(draft.trim());
|
|
1716
|
-
setIsEditing(false);
|
|
1717
|
-
},
|
|
1718
|
-
children: "Save"
|
|
1719
|
-
}
|
|
1720
|
-
)
|
|
1721
|
-
] }),
|
|
1722
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1723
|
-
Button,
|
|
1724
|
-
{
|
|
1725
|
-
type: "button",
|
|
1726
|
-
variant: "ghost",
|
|
1727
|
-
size: "sm",
|
|
1728
|
-
className: "h-7 px-2 text-[10px] text-muted-foreground hover:text-foreground",
|
|
1729
|
-
onClick: (e) => {
|
|
1730
|
-
e.stopPropagation();
|
|
1731
|
-
onDelete();
|
|
1732
|
-
},
|
|
1733
|
-
children: t("delete")
|
|
1734
|
-
}
|
|
1735
|
-
)
|
|
1736
|
-
] })
|
|
1737
|
-
] })
|
|
1738
|
-
] })
|
|
1739
|
-
] })
|
|
1740
|
-
]
|
|
1741
|
-
}
|
|
1742
|
-
);
|
|
1743
|
-
}
|
|
1744
|
-
function HighlightsSidebar({
|
|
1745
|
-
highlights,
|
|
1746
|
-
selectedId,
|
|
1747
|
-
currentPage,
|
|
1748
|
-
flashId,
|
|
1749
|
-
onSelect,
|
|
1750
|
-
onDelete,
|
|
1751
|
-
onUpdateKind,
|
|
1752
|
-
onUpdateComment,
|
|
1753
|
-
showOverlayHints,
|
|
1754
|
-
readOnly
|
|
1755
|
-
}) {
|
|
1756
|
-
const { t } = useI18n("pdf_viewer");
|
|
1757
|
-
const [objectFilter, setObjectFilter] = reactExports.useState("all");
|
|
1758
|
-
const itemRefs = reactExports.useRef(/* @__PURE__ */ new Map());
|
|
1759
|
-
const previousSelectedIdRef = reactExports.useRef(selectedId);
|
|
1760
|
-
const suppressFollowScrollOnceRef = reactExports.useRef(false);
|
|
1761
|
-
const sortHighlightsByPageOrder = reactExports.useCallback((left, right) => {
|
|
1762
|
-
const leftPage = Number(left.position?.pageNumber || 0);
|
|
1763
|
-
const rightPage = Number(right.position?.pageNumber || 0);
|
|
1764
|
-
const normalizedLeftPage = Number.isFinite(leftPage) && leftPage > 0 ? leftPage : Number.MAX_SAFE_INTEGER;
|
|
1765
|
-
const normalizedRightPage = Number.isFinite(rightPage) && rightPage > 0 ? rightPage : Number.MAX_SAFE_INTEGER;
|
|
1766
|
-
if (normalizedLeftPage !== normalizedRightPage) {
|
|
1767
|
-
return normalizedLeftPage - normalizedRightPage;
|
|
1768
|
-
}
|
|
1769
|
-
const leftTop = Number(
|
|
1770
|
-
left.position?.boundingRect?.y1 ?? left.position?.rects?.[0]?.y1 ?? Number.MAX_SAFE_INTEGER
|
|
1771
|
-
);
|
|
1772
|
-
const rightTop = Number(
|
|
1773
|
-
right.position?.boundingRect?.y1 ?? right.position?.rects?.[0]?.y1 ?? Number.MAX_SAFE_INTEGER
|
|
1774
|
-
);
|
|
1775
|
-
const normalizedLeftTop = Number.isFinite(leftTop) ? leftTop : Number.MAX_SAFE_INTEGER;
|
|
1776
|
-
const normalizedRightTop = Number.isFinite(rightTop) ? rightTop : Number.MAX_SAFE_INTEGER;
|
|
1777
|
-
if (normalizedLeftTop !== normalizedRightTop) {
|
|
1778
|
-
return normalizedLeftTop - normalizedRightTop;
|
|
1779
|
-
}
|
|
1780
|
-
const leftX = Number(
|
|
1781
|
-
left.position?.boundingRect?.x1 ?? left.position?.rects?.[0]?.x1 ?? Number.MAX_SAFE_INTEGER
|
|
1782
|
-
);
|
|
1783
|
-
const rightX = Number(
|
|
1784
|
-
right.position?.boundingRect?.x1 ?? right.position?.rects?.[0]?.x1 ?? Number.MAX_SAFE_INTEGER
|
|
1785
|
-
);
|
|
1786
|
-
const normalizedLeftX = Number.isFinite(leftX) ? leftX : Number.MAX_SAFE_INTEGER;
|
|
1787
|
-
const normalizedRightX = Number.isFinite(rightX) ? rightX : Number.MAX_SAFE_INTEGER;
|
|
1788
|
-
if (normalizedLeftX !== normalizedRightX) {
|
|
1789
|
-
return normalizedLeftX - normalizedRightX;
|
|
1790
|
-
}
|
|
1791
|
-
return left.id.localeCompare(right.id);
|
|
1792
|
-
}, []);
|
|
1793
|
-
const counts = reactExports.useMemo(() => {
|
|
1794
|
-
const base = {
|
|
1795
|
-
all: highlights.length,
|
|
1796
|
-
issue: 0,
|
|
1797
|
-
suggestion: 0,
|
|
1798
|
-
verification: 0,
|
|
1799
|
-
note: 0,
|
|
1800
|
-
question: 0,
|
|
1801
|
-
task: 0
|
|
1802
|
-
};
|
|
1803
|
-
for (const highlight of highlights) {
|
|
1804
|
-
const objectType = extractReviewObjectType(highlight.tags);
|
|
1805
|
-
if (objectType) {
|
|
1806
|
-
base[objectType] += 1;
|
|
1807
|
-
continue;
|
|
1808
|
-
}
|
|
1809
|
-
const kind = normalizeKind(
|
|
1810
|
-
highlight.comment?.emoji || extractAnnotationKindFromTags(highlight.tags) || void 0
|
|
1811
|
-
);
|
|
1812
|
-
base[kind] += 1;
|
|
1813
|
-
}
|
|
1814
|
-
return base;
|
|
1815
|
-
}, [highlights]);
|
|
1816
|
-
const filterItems = reactExports.useMemo(() => {
|
|
1817
|
-
const rows = [
|
|
1818
|
-
{ key: "all", label: t("filter_all"), count: counts.all }
|
|
1819
|
-
];
|
|
1820
|
-
const reviewFilterRows = [
|
|
1821
|
-
{ key: "issue", label: t("review_type_issue"), count: counts.issue },
|
|
1822
|
-
{ key: "suggestion", label: t("review_type_suggestion"), count: counts.suggestion },
|
|
1823
|
-
{ key: "verification", label: t("review_type_verification"), count: counts.verification }
|
|
1824
|
-
];
|
|
1825
|
-
const kindFilterRows = [
|
|
1826
|
-
{ key: "note", label: t("type_note"), count: counts.note },
|
|
1827
|
-
{ key: "question", label: t("type_question"), count: counts.question },
|
|
1828
|
-
{ key: "task", label: t("type_task"), count: counts.task }
|
|
1829
|
-
];
|
|
1830
|
-
const hasReviewRows = reviewFilterRows.some((row) => row.count > 0);
|
|
1831
|
-
const hasKindRows = kindFilterRows.some((row) => row.count > 0);
|
|
1832
|
-
if (hasReviewRows) {
|
|
1833
|
-
rows.push(...reviewFilterRows.filter((row) => row.count > 0));
|
|
1834
|
-
}
|
|
1835
|
-
if (hasKindRows) {
|
|
1836
|
-
rows.push(...kindFilterRows.filter((row) => row.count > 0));
|
|
1837
|
-
}
|
|
1838
|
-
return rows;
|
|
1839
|
-
}, [counts, t]);
|
|
1840
|
-
reactExports.useEffect(() => {
|
|
1841
|
-
if (objectFilter === "all") return;
|
|
1842
|
-
if (filterItems.some((item) => item.key === objectFilter)) return;
|
|
1843
|
-
setObjectFilter("all");
|
|
1844
|
-
}, [filterItems, objectFilter]);
|
|
1845
|
-
reactExports.useEffect(() => {
|
|
1846
|
-
if (previousSelectedIdRef.current && !selectedId) {
|
|
1847
|
-
suppressFollowScrollOnceRef.current = true;
|
|
1848
|
-
}
|
|
1849
|
-
previousSelectedIdRef.current = selectedId;
|
|
1850
|
-
}, [selectedId]);
|
|
1851
|
-
const filteredHighlights = reactExports.useMemo(() => {
|
|
1852
|
-
const base = highlights.filter((highlight) => {
|
|
1853
|
-
if (objectFilter === "all") return true;
|
|
1854
|
-
const reviewObjectType = extractReviewObjectType(highlight.tags);
|
|
1855
|
-
if (objectFilter === "issue" || objectFilter === "suggestion" || objectFilter === "verification") {
|
|
1856
|
-
return reviewObjectType === objectFilter;
|
|
1857
|
-
}
|
|
1858
|
-
if (reviewObjectType) return false;
|
|
1859
|
-
const kind = normalizeKind(
|
|
1860
|
-
highlight.comment?.emoji || extractAnnotationKindFromTags(highlight.tags) || void 0
|
|
1861
|
-
);
|
|
1862
|
-
return kind === objectFilter;
|
|
1863
|
-
});
|
|
1864
|
-
return [...base].sort(sortHighlightsByPageOrder);
|
|
1865
|
-
}, [highlights, objectFilter, sortHighlightsByPageOrder]);
|
|
1866
|
-
const followTargetId = reactExports.useMemo(() => {
|
|
1867
|
-
if (selectedId) return null;
|
|
1868
|
-
if (filteredHighlights.length === 0) return null;
|
|
1869
|
-
const rows = filteredHighlights.map((item) => ({
|
|
1870
|
-
id: item.id,
|
|
1871
|
-
page: Number(item.position?.pageNumber || 0)
|
|
1872
|
-
})).filter((item) => Number.isFinite(item.page) && item.page > 0);
|
|
1873
|
-
if (rows.length === 0) return null;
|
|
1874
|
-
const exact = rows.find((item) => item.page === currentPage);
|
|
1875
|
-
if (exact) return exact.id;
|
|
1876
|
-
let best = rows[0];
|
|
1877
|
-
let bestDistance = Math.abs(rows[0].page - currentPage);
|
|
1878
|
-
for (let index = 1; index < rows.length; index += 1) {
|
|
1879
|
-
const candidate = rows[index];
|
|
1880
|
-
const distance = Math.abs(candidate.page - currentPage);
|
|
1881
|
-
if (distance < bestDistance) {
|
|
1882
|
-
best = candidate;
|
|
1883
|
-
bestDistance = distance;
|
|
1884
|
-
continue;
|
|
1885
|
-
}
|
|
1886
|
-
if (distance === bestDistance && candidate.page < best.page) {
|
|
1887
|
-
best = candidate;
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
return best.id;
|
|
1891
|
-
}, [currentPage, filteredHighlights, selectedId]);
|
|
1892
|
-
reactExports.useEffect(() => {
|
|
1893
|
-
if (!followTargetId) return;
|
|
1894
|
-
if (suppressFollowScrollOnceRef.current) {
|
|
1895
|
-
suppressFollowScrollOnceRef.current = false;
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
|
-
const node = itemRefs.current.get(followTargetId);
|
|
1899
|
-
if (!node) return;
|
|
1900
|
-
node.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
1901
|
-
}, [followTargetId]);
|
|
1902
|
-
const registerItemRef = reactExports.useCallback((id, node) => {
|
|
1903
|
-
if (!id) return;
|
|
1904
|
-
if (!node) {
|
|
1905
|
-
itemRefs.current.delete(id);
|
|
1906
|
-
return;
|
|
1907
|
-
}
|
|
1908
|
-
itemRefs.current.set(id, node);
|
|
1909
|
-
}, []);
|
|
1910
|
-
if (highlights.length === 0) {
|
|
1911
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full p-4 text-center", children: [
|
|
1912
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-4xl mb-2 opacity-30", children: "📝" }),
|
|
1913
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("no_annotations") }),
|
|
1914
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-muted-foreground/60 mt-1", children: readOnly ? t("read_only_annotation_desc") : t("select_text_to_highlight") })
|
|
1915
|
-
] });
|
|
1916
|
-
}
|
|
1917
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full", children: [
|
|
1918
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-b space-y-2", children: [
|
|
1919
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1920
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-medium text-xs", children: [
|
|
1921
|
-
t("annotations"),
|
|
1922
|
-
" (",
|
|
1923
|
-
filteredHighlights.length,
|
|
1924
|
-
"/",
|
|
1925
|
-
highlights.length,
|
|
1926
|
-
")"
|
|
1927
|
-
] }),
|
|
1928
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1929
|
-
"span",
|
|
1930
|
-
{
|
|
1931
|
-
className: cn(
|
|
1932
|
-
"inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium tracking-wide",
|
|
1933
|
-
showOverlayHints ? "border-black/10 bg-white text-foreground/70" : "border-border bg-background text-muted-foreground"
|
|
1934
|
-
),
|
|
1935
|
-
title: t("all_markers_shown"),
|
|
1936
|
-
children: t("markers_on")
|
|
1937
|
-
}
|
|
1938
|
-
)
|
|
1939
|
-
] }),
|
|
1940
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1", children: filterItems.map((item) => {
|
|
1941
|
-
const active = objectFilter === item.key;
|
|
1942
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1943
|
-
"button",
|
|
1944
|
-
{
|
|
1945
|
-
type: "button",
|
|
1946
|
-
onClick: () => setObjectFilter(item.key),
|
|
1947
|
-
className: cn(
|
|
1948
|
-
"inline-flex min-w-0 flex-1 items-center justify-center rounded-md border px-2 py-1 text-[10px] transition-colors",
|
|
1949
|
-
active ? "border-primary/40 bg-primary/10 text-foreground" : "border-border/70 bg-background text-muted-foreground hover:text-foreground"
|
|
1950
|
-
),
|
|
1951
|
-
title: t("filter_annotations_title", { label: item.label }),
|
|
1952
|
-
children: [
|
|
1953
|
-
item.label,
|
|
1954
|
-
" (",
|
|
1955
|
-
item.count,
|
|
1956
|
-
")"
|
|
1957
|
-
]
|
|
1958
|
-
},
|
|
1959
|
-
item.key
|
|
1960
|
-
);
|
|
1961
|
-
}) })
|
|
1962
|
-
] }),
|
|
1963
|
-
filteredHighlights.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-1 items-center justify-center px-4 text-center text-xs text-muted-foreground", children: "No annotations for current filter." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-auto p-2 space-y-2", children: filteredHighlights.map((h) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1964
|
-
AnnotationSidebarItem,
|
|
1965
|
-
{
|
|
1966
|
-
highlight: h,
|
|
1967
|
-
selected: selectedId === h.id,
|
|
1968
|
-
followed: Boolean(followTargetId && followTargetId === h.id),
|
|
1969
|
-
flash: Boolean(flashId && flashId === h.id),
|
|
1970
|
-
onSelect: () => onSelect(h.id),
|
|
1971
|
-
onDelete: () => onDelete(h.id),
|
|
1972
|
-
onUpdateKind: (k) => onUpdateKind(h.id, k),
|
|
1973
|
-
onUpdateComment: (c) => onUpdateComment(h.id, c),
|
|
1974
|
-
readOnly,
|
|
1975
|
-
registerItemRef
|
|
1976
|
-
},
|
|
1977
|
-
h.id
|
|
1978
|
-
)) })
|
|
1979
|
-
] });
|
|
1980
|
-
}
|
|
1981
|
-
function PdfHighlighterSurface({
|
|
1982
|
-
pdfDocument,
|
|
1983
|
-
scale,
|
|
1984
|
-
highlights,
|
|
1985
|
-
selectedId,
|
|
1986
|
-
onSelect,
|
|
1987
|
-
onAdd,
|
|
1988
|
-
onUpdate,
|
|
1989
|
-
onScrollRef,
|
|
1990
|
-
onPageChange,
|
|
1991
|
-
pdfHighlighterRef,
|
|
1992
|
-
authorColor,
|
|
1993
|
-
authorHandle,
|
|
1994
|
-
showOverlayHints,
|
|
1995
|
-
tipPlacementMode,
|
|
1996
|
-
readOnly,
|
|
1997
|
-
onAskCopilot
|
|
1998
|
-
}) {
|
|
1999
|
-
const { t } = useI18n("pdf_viewer");
|
|
2000
|
-
const { setTotalPages } = useViewerState();
|
|
2001
|
-
const [hoveredHighlightId, setHoveredHighlightId] = reactExports.useState(null);
|
|
2002
|
-
const pdfScaleValue = scale === 1 ? "page-width" : `page-width:${scale}`;
|
|
2003
|
-
const activeFocusHighlightId = hoveredHighlightId || selectedId || null;
|
|
2004
|
-
reactExports.useEffect(() => {
|
|
2005
|
-
setTotalPages(pdfDocument.numPages);
|
|
2006
|
-
}, [pdfDocument.numPages, setTotalPages]);
|
|
2007
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2008
|
-
PdfHighlighter,
|
|
2009
|
-
{
|
|
2010
|
-
ref: pdfHighlighterRef,
|
|
2011
|
-
pdfDocument,
|
|
2012
|
-
pdfScaleValue,
|
|
2013
|
-
tipPlacementMode,
|
|
2014
|
-
onPageChange,
|
|
2015
|
-
enableAreaSelection: (event) => readOnly ? false : event.altKey,
|
|
2016
|
-
onScrollChange: () => {
|
|
2017
|
-
},
|
|
2018
|
-
scrollRef: onScrollRef,
|
|
2019
|
-
highlights,
|
|
2020
|
-
onSelectionFinished: (position, content, hideTipAndSelection, transformSelection) => readOnly ? null : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2021
|
-
Tip,
|
|
2022
|
-
{
|
|
2023
|
-
onOpen: transformSelection,
|
|
2024
|
-
onCancel: hideTipAndSelection,
|
|
2025
|
-
onConfirm: (comment) => {
|
|
2026
|
-
onAdd({ content, position, comment });
|
|
2027
|
-
hideTipAndSelection();
|
|
2028
|
-
},
|
|
2029
|
-
onAskCopilot: () => {
|
|
2030
|
-
onAskCopilot?.(content, position);
|
|
2031
|
-
hideTipAndSelection();
|
|
2032
|
-
},
|
|
2033
|
-
authorColor,
|
|
2034
|
-
authorHandle,
|
|
2035
|
-
showAuthorHandle: false
|
|
2036
|
-
}
|
|
2037
|
-
),
|
|
2038
|
-
highlightTransform: (highlight, _index, _setTip, _hideTip, viewportToScaled, screenshot, isScrolledTo, pageMetrics) => {
|
|
2039
|
-
const isGhost = Boolean(highlight.__dsGhost);
|
|
2040
|
-
const ghostOpacity = highlight.__dsGhostOpacity ?? 1;
|
|
2041
|
-
const ghostMode = highlight.__dsGhostMode;
|
|
2042
|
-
const isTextHighlight = !highlight.content?.image;
|
|
2043
|
-
const highlightMeta = resolveHighlightMeta({
|
|
2044
|
-
comment: highlight.comment,
|
|
2045
|
-
tags: highlight.tags
|
|
2046
|
-
});
|
|
2047
|
-
const safeMetaLabel = String(getHighlightMetaLabel(highlightMeta, t) || t("annotation_label"));
|
|
2048
|
-
const reviewItemId = extractReviewItemId(highlight.tags);
|
|
2049
|
-
const previewSummary = asNonEmptyString(
|
|
2050
|
-
highlight.previewSummary
|
|
2051
|
-
);
|
|
2052
|
-
const firstLine = reviewItemId ? `#${reviewItemId} · ${safeMetaLabel}` : `P${highlight.position.pageNumber} · ${safeMetaLabel}`;
|
|
2053
|
-
const secondLine = previewSummary || compactWords(highlight.comment?.text || "", 8);
|
|
2054
|
-
const compactHintText = reviewItemId ? secondLine ? `${firstLine}
|
|
2055
|
-
${secondLine}` : firstLine : secondLine ? `${firstLine}
|
|
2056
|
-
${secondLine}` : firstLine;
|
|
2057
|
-
const compactHintTitle = `${safeMetaLabel} · ${t("page", {
|
|
2058
|
-
page: highlight.position.pageNumber
|
|
2059
|
-
})} · ${t("open_details_hint")}`;
|
|
2060
|
-
const highlightTheme = overlayHintTheme();
|
|
2061
|
-
const isFocused = activeFocusHighlightId === highlight.id;
|
|
2062
|
-
const hideThisHighlight = Boolean(activeFocusHighlightId && !isFocused);
|
|
2063
|
-
const component = isTextHighlight ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2064
|
-
Highlight,
|
|
2065
|
-
{
|
|
2066
|
-
isScrolledTo: isScrolledTo || highlight.id === selectedId,
|
|
2067
|
-
position: highlight.position,
|
|
2068
|
-
comment: highlight.comment,
|
|
2069
|
-
color: highlight.color,
|
|
2070
|
-
hidden: hideThisHighlight,
|
|
2071
|
-
emphasized: isFocused,
|
|
2072
|
-
onHoverStart: isGhost ? void 0 : () => setHoveredHighlightId(highlight.id),
|
|
2073
|
-
onHoverEnd: isGhost ? void 0 : () => setHoveredHighlightId(
|
|
2074
|
-
(current) => current === highlight.id ? null : current
|
|
2075
|
-
),
|
|
2076
|
-
onClick: isGhost ? void 0 : () => onSelect(highlight.id, {
|
|
2077
|
-
allowToggleClose: false,
|
|
2078
|
-
skipAutoScroll: true
|
|
2079
|
-
}),
|
|
2080
|
-
showOverlayHint: !isGhost && showOverlayHints && !hideThisHighlight,
|
|
2081
|
-
overlayHintText: compactHintText,
|
|
2082
|
-
overlayHintTitle: compactHintTitle,
|
|
2083
|
-
overlayHintBorderColor: highlightTheme.borderColor,
|
|
2084
|
-
overlayHintTextColor: highlightTheme.textColor,
|
|
2085
|
-
overlayHintBackgroundColor: highlightTheme.backgroundColor,
|
|
2086
|
-
overlayHintTopPx: pageMetrics.overlayHintTopById?.[highlight.id],
|
|
2087
|
-
overlayHintLeftPx: pageMetrics.overlayHintLeftPx,
|
|
2088
|
-
overlayHintWidthPx: pageMetrics.overlayHintWidthPx,
|
|
2089
|
-
overlayHintPageHeightPx: pageMetrics.height,
|
|
2090
|
-
onOverlayHintClick: isGhost ? void 0 : () => {
|
|
2091
|
-
onSelect(highlight.id, {
|
|
2092
|
-
skipAutoScroll: true
|
|
2093
|
-
});
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2097
|
-
AreaHighlight,
|
|
2098
|
-
{
|
|
2099
|
-
isScrolledTo: isScrolledTo || highlight.id === selectedId,
|
|
2100
|
-
highlight,
|
|
2101
|
-
hidden: hideThisHighlight,
|
|
2102
|
-
emphasized: isFocused,
|
|
2103
|
-
onHoverStart: isGhost ? void 0 : () => setHoveredHighlightId(highlight.id),
|
|
2104
|
-
onHoverEnd: isGhost ? void 0 : () => setHoveredHighlightId(
|
|
2105
|
-
(current) => current === highlight.id ? null : current
|
|
2106
|
-
),
|
|
2107
|
-
onSelect: isGhost ? void 0 : () => onSelect(highlight.id, {
|
|
2108
|
-
allowToggleClose: false,
|
|
2109
|
-
skipAutoScroll: true
|
|
2110
|
-
}),
|
|
2111
|
-
onChange: (boundingRect) => {
|
|
2112
|
-
if (readOnly) return;
|
|
2113
|
-
onUpdate(
|
|
2114
|
-
highlight.id,
|
|
2115
|
-
{ boundingRect: viewportToScaled(boundingRect) },
|
|
2116
|
-
{ image: screenshot(boundingRect) }
|
|
2117
|
-
);
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
);
|
|
2121
|
-
if (isGhost) {
|
|
2122
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2123
|
-
"div",
|
|
2124
|
-
{
|
|
2125
|
-
className: cn(
|
|
2126
|
-
"pointer-events-none",
|
|
2127
|
-
ghostMode === "annotate" ? "ds-pdf-ghost-annotate" : "ds-pdf-ghost-guide"
|
|
2128
|
-
),
|
|
2129
|
-
style: { opacity: ghostOpacity, transition: "opacity 0.8s ease" },
|
|
2130
|
-
children: component
|
|
2131
|
-
},
|
|
2132
|
-
`ghost-${_index}`
|
|
2133
|
-
);
|
|
2134
|
-
}
|
|
2135
|
-
return component;
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
);
|
|
2139
|
-
}
|
|
2140
|
-
function PdfViewerPlugin({
|
|
2141
|
-
context,
|
|
2142
|
-
tabId,
|
|
2143
|
-
setDirty,
|
|
2144
|
-
setTitle
|
|
2145
|
-
}) {
|
|
2146
|
-
const { t } = useI18n("pdf_viewer");
|
|
2147
|
-
const rawFileId = context.resourceId ?? (typeof context.customData?.fileId === "string" ? context.customData.fileId : null);
|
|
2148
|
-
const fileId = rawFileId ? String(rawFileId) : "";
|
|
2149
|
-
const isCliFile = isCliFileId(fileId);
|
|
2150
|
-
const reviewRouteId = typeof context.customData?.reviewRouteId === "string" ? context.customData.reviewRouteId.trim() : "";
|
|
2151
|
-
const rebuttalRouteId = typeof context.customData?.rebuttalRouteId === "string" ? context.customData.rebuttalRouteId.trim() : "";
|
|
2152
|
-
const isReviewWorkspace = reviewRouteId.length > 0;
|
|
2153
|
-
const isRebuttalWorkspace = rebuttalRouteId.length > 0;
|
|
2154
|
-
const isResizableAnnotationWorkspace = isReviewWorkspace || isRebuttalWorkspace;
|
|
2155
|
-
const annotationRouteId = isReviewWorkspace ? reviewRouteId : rebuttalRouteId;
|
|
2156
|
-
const isSharedAnnotationWorkspace = annotationRouteId.startsWith("shared-");
|
|
2157
|
-
const REVIEW_SIDEBAR_WIDTH_SCALE = isSharedAnnotationWorkspace ? 0.8 : 1;
|
|
2158
|
-
const REVIEW_SIDEBAR_WIDTH_STORAGE_KEY = isRebuttalWorkspace ? isSharedAnnotationWorkspace ? "ds:rebuttal:pdf:annotation-sidebar-width:shared:v1" : "ds:rebuttal:pdf:annotation-sidebar-width:v1" : isSharedAnnotationWorkspace ? "ds:review:pdf:annotation-sidebar-width:shared:v1" : "ds:review:pdf:annotation-sidebar-width:v4";
|
|
2159
|
-
const REVIEW_SIDEBAR_MIN_WIDTH = Math.round(336 * REVIEW_SIDEBAR_WIDTH_SCALE);
|
|
2160
|
-
const REVIEW_SIDEBAR_MAX_WIDTH = Math.round(816 * REVIEW_SIDEBAR_WIDTH_SCALE);
|
|
2161
|
-
const REVIEW_SIDEBAR_DEFAULT_WIDTH = Math.round(673 * REVIEW_SIDEBAR_WIDTH_SCALE);
|
|
2162
|
-
const isReadOnlyMode = Boolean(
|
|
2163
|
-
typeof context.customData?.readOnlyMode === "boolean" ? context.customData.readOnlyMode : false
|
|
2164
|
-
);
|
|
2165
|
-
const readOnlyHighlights = reactExports.useMemo(() => {
|
|
2166
|
-
if (!isReadOnlyMode) return [];
|
|
2167
|
-
const raw = context.customData?.readOnlyAnnotations;
|
|
2168
|
-
if (!Array.isArray(raw)) return [];
|
|
2169
|
-
return raw.map((item) => mapReadOnlyAnnotationToHighlight(item)).filter((item) => Boolean(item));
|
|
2170
|
-
}, [context.customData?.readOnlyAnnotations, isReadOnlyMode]);
|
|
2171
|
-
const readOnlySidebarInitRef = reactExports.useRef(false);
|
|
2172
|
-
const fileName = context.resourceName || "Untitled.pdf";
|
|
2173
|
-
const externalPdfUrl = typeof context.customData?.externalPdfUrl === "string" ? context.customData.externalPdfUrl.trim() : "";
|
|
2174
|
-
const hasExternalPdfUrl = externalPdfUrl.length > 0;
|
|
2175
|
-
const apiBaseUrl = (apiClient.defaults.baseURL || "").replace(/\/$/, "");
|
|
2176
|
-
const isQuestFile = isQuestNodeId(fileId);
|
|
2177
|
-
const questProjectIdFromFileId = reactExports.useMemo(
|
|
2178
|
-
() => fileId && isQuestFile ? getQuestNodeProjectId(fileId) : null,
|
|
2179
|
-
[fileId, isQuestFile]
|
|
2180
|
-
);
|
|
2181
|
-
const projectIdFromContext = reactExports.useMemo(() => {
|
|
2182
|
-
const candidate = typeof context.customData?.projectId === "string" ? context.customData.projectId.trim() : "";
|
|
2183
|
-
return candidate || null;
|
|
2184
|
-
}, [context.customData]);
|
|
2185
|
-
const [cliPdfUrl, setCliPdfUrl] = reactExports.useState(null);
|
|
2186
|
-
const [cliPdfLoading, setCliPdfLoading] = reactExports.useState(false);
|
|
2187
|
-
const [cliPdfError, setCliPdfError] = reactExports.useState(null);
|
|
2188
|
-
const [questPdfUrl, setQuestPdfUrl] = reactExports.useState(null);
|
|
2189
|
-
const [questPdfLoading, setQuestPdfLoading] = reactExports.useState(false);
|
|
2190
|
-
const [questPdfError, setQuestPdfError] = reactExports.useState(null);
|
|
2191
|
-
const pdfUrl = hasExternalPdfUrl ? externalPdfUrl : isCliFile ? cliPdfUrl || "" : isQuestFile ? questPdfUrl || "" : fileId ? `${apiBaseUrl}/api/v1/files/${fileId}/content` : (
|
|
2192
|
-
// Demo: Use a sample PDF for testing
|
|
2193
|
-
"https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"
|
|
2194
|
-
);
|
|
2195
|
-
const token = typeof window !== "undefined" ? localStorage.getItem("ds_access_token") : null;
|
|
2196
|
-
const httpHeaders = token && (hasExternalPdfUrl || !isCliFile && !isQuestFile && fileId) ? { Authorization: `Bearer ${token}` } : void 0;
|
|
2197
|
-
const {
|
|
2198
|
-
scale,
|
|
2199
|
-
setScale,
|
|
2200
|
-
currentPage,
|
|
2201
|
-
setCurrentPage,
|
|
2202
|
-
totalPages,
|
|
2203
|
-
sidebarVisible,
|
|
2204
|
-
toggleSidebar,
|
|
2205
|
-
selectedAnnotationId,
|
|
2206
|
-
selectAnnotation
|
|
2207
|
-
} = useViewerState();
|
|
2208
|
-
const updateTabPlugin = useTabsStore((state) => state.updateTabPlugin);
|
|
2209
|
-
const findNode = useFileTreeStore((state) => state.findNode);
|
|
2210
|
-
const updateWorkspaceTabState = useWorkspaceSurfaceStore((state) => state.updateTabState);
|
|
2211
|
-
const addWorkspaceReference = useWorkspaceSurfaceStore((state) => state.addReference);
|
|
2212
|
-
const updateWorkspaceReference = useWorkspaceSurfaceStore((state) => state.updateReference);
|
|
2213
|
-
const openMarkdownViewFromHost = typeof context.customData?.onOpenMarkdownView === "function" ? context.customData.onOpenMarkdownView : null;
|
|
2214
|
-
const openReviewOpinionViewFromHost = typeof context.customData?.onOpenReviewOpinionView === "function" ? context.customData.onOpenReviewOpinionView : null;
|
|
2215
|
-
const reviewOpinionAvailableFromHost = Boolean(
|
|
2216
|
-
typeof context.customData?.reviewOpinionAvailable === "boolean" ? context.customData.reviewOpinionAvailable : false
|
|
2217
|
-
);
|
|
2218
|
-
const reviewOpinionModeFromHost = typeof context.customData?.reviewViewMode === "string" ? context.customData.reviewViewMode : "";
|
|
2219
|
-
const reviewOpinionActiveFromHost = reviewOpinionModeFromHost === "ai_review";
|
|
2220
|
-
const reviewOpinionLabelFromHost = typeof context.customData?.reviewOpinionLabel === "string" ? context.customData.reviewOpinionLabel : void 0;
|
|
2221
|
-
const reviewOpinionTitleFromHost = typeof context.customData?.reviewOpinionTitle === "string" ? context.customData.reviewOpinionTitle : void 0;
|
|
2222
|
-
const queryClient = useQueryClient();
|
|
2223
|
-
const { addToast } = useToast();
|
|
2224
|
-
const arxivItems = useArxivStore((s) => s.items);
|
|
2225
|
-
const arxivErrors = useArxivStore((s) => s.errors);
|
|
2226
|
-
const arxivFromContext = reactExports.useMemo(() => {
|
|
2227
|
-
const customData = context.customData;
|
|
2228
|
-
return customData?.arxiv ?? null;
|
|
2229
|
-
}, [context.customData]);
|
|
2230
|
-
const isArxivPdf = Boolean(arxivFromContext) || Boolean(fileId && arxivItems.some((item) => item.fileId === fileId));
|
|
2231
|
-
const annotationsFileId = isCliFile || !fileId || isReadOnlyMode ? "" : fileId;
|
|
2232
|
-
const { annotations, createAnnotation, updateAnnotation, deleteAnnotation } = useAnnotations(annotationsFileId);
|
|
2233
|
-
const currentUser = useAuthStore((s) => s.user);
|
|
2234
|
-
const [projectId, setProjectId] = reactExports.useState(null);
|
|
2235
|
-
const [authorColor, setAuthorColor] = reactExports.useState("#F1E9D0");
|
|
2236
|
-
const [infoOpen, setInfoOpen] = reactExports.useState(false);
|
|
2237
|
-
const markdownCacheRef = reactExports.useRef({});
|
|
2238
|
-
const markdownLoadPromiseRef = reactExports.useRef({});
|
|
2239
|
-
const showOverlayHints = true;
|
|
2240
|
-
const [aiHighlights, setAiHighlights] = reactExports.useState([]);
|
|
2241
|
-
const aiHighlightTimers = reactExports.useRef(/* @__PURE__ */ new Map());
|
|
2242
|
-
const pendingAnnotationIdRef = reactExports.useRef(null);
|
|
2243
|
-
const [flashAnnotationId, setFlashAnnotationId] = reactExports.useState(null);
|
|
2244
|
-
const [reviewSidebarWidth, setReviewSidebarWidth] = reactExports.useState(() => {
|
|
2245
|
-
if (typeof window === "undefined") return REVIEW_SIDEBAR_DEFAULT_WIDTH;
|
|
2246
|
-
const raw = window.localStorage.getItem(REVIEW_SIDEBAR_WIDTH_STORAGE_KEY);
|
|
2247
|
-
const parsed = raw ? Number(raw) : NaN;
|
|
2248
|
-
if (!Number.isFinite(parsed)) return REVIEW_SIDEBAR_DEFAULT_WIDTH;
|
|
2249
|
-
return Math.max(REVIEW_SIDEBAR_MIN_WIDTH, Math.min(REVIEW_SIDEBAR_MAX_WIDTH, parsed));
|
|
2250
|
-
});
|
|
2251
|
-
const reviewSidebarWidthRef = reactExports.useRef(reviewSidebarWidth);
|
|
2252
|
-
const flashTimerRef = reactExports.useRef(null);
|
|
2253
|
-
const sidebarVisibleRef = reactExports.useRef(sidebarVisible);
|
|
2254
|
-
const toggleSidebarRef = reactExports.useRef(toggleSidebar);
|
|
2255
|
-
const scrollViewerTo = reactExports.useRef(() => {
|
|
2256
|
-
});
|
|
2257
|
-
const pdfHighlighterRef = reactExports.useRef(null);
|
|
2258
|
-
const socketRef = reactExports.useRef(null);
|
|
2259
|
-
const joinedRef = reactExports.useRef(false);
|
|
2260
|
-
const shouldRedirectToNotebook = isMarkdownFileName(fileName);
|
|
2261
|
-
reactExports.useEffect(() => {
|
|
2262
|
-
reviewSidebarWidthRef.current = reviewSidebarWidth;
|
|
2263
|
-
}, [reviewSidebarWidth]);
|
|
2264
|
-
reactExports.useEffect(() => {
|
|
2265
|
-
if (!isResizableAnnotationWorkspace || typeof window === "undefined") return;
|
|
2266
|
-
window.localStorage.setItem(REVIEW_SIDEBAR_WIDTH_STORAGE_KEY, String(reviewSidebarWidth));
|
|
2267
|
-
}, [REVIEW_SIDEBAR_WIDTH_STORAGE_KEY, isResizableAnnotationWorkspace, reviewSidebarWidth]);
|
|
2268
|
-
const clampReviewSidebarWidth = reactExports.useCallback((rawWidth) => {
|
|
2269
|
-
const viewportMax = typeof window !== "undefined" ? Math.max(REVIEW_SIDEBAR_MIN_WIDTH, Math.min(REVIEW_SIDEBAR_MAX_WIDTH, Math.floor(window.innerWidth * 0.75))) : REVIEW_SIDEBAR_MAX_WIDTH;
|
|
2270
|
-
return Math.max(REVIEW_SIDEBAR_MIN_WIDTH, Math.min(viewportMax, rawWidth));
|
|
2271
|
-
}, [REVIEW_SIDEBAR_MAX_WIDTH, REVIEW_SIDEBAR_MIN_WIDTH]);
|
|
2272
|
-
const handleReviewSidebarResizePointerDown = reactExports.useCallback(
|
|
2273
|
-
(event) => {
|
|
2274
|
-
if (!isResizableAnnotationWorkspace) return;
|
|
2275
|
-
if (event.button !== 0) return;
|
|
2276
|
-
event.preventDefault();
|
|
2277
|
-
event.stopPropagation();
|
|
2278
|
-
const startX = event.clientX;
|
|
2279
|
-
const startWidth = reviewSidebarWidthRef.current;
|
|
2280
|
-
document.body.style.cursor = "col-resize";
|
|
2281
|
-
document.body.style.userSelect = "none";
|
|
2282
|
-
const handleMouseMove = (moveEvent) => {
|
|
2283
|
-
const delta = startX - moveEvent.clientX;
|
|
2284
|
-
setReviewSidebarWidth(clampReviewSidebarWidth(startWidth + delta));
|
|
2285
|
-
};
|
|
2286
|
-
const handleMouseUp = () => {
|
|
2287
|
-
document.body.style.cursor = "";
|
|
2288
|
-
document.body.style.userSelect = "";
|
|
2289
|
-
document.removeEventListener("mousemove", handleMouseMove);
|
|
2290
|
-
document.removeEventListener("mouseup", handleMouseUp);
|
|
2291
|
-
};
|
|
2292
|
-
document.addEventListener("mousemove", handleMouseMove);
|
|
2293
|
-
document.addEventListener("mouseup", handleMouseUp);
|
|
2294
|
-
},
|
|
2295
|
-
[clampReviewSidebarWidth, isResizableAnnotationWorkspace]
|
|
2296
|
-
);
|
|
2297
|
-
const arxivPaper = reactExports.useMemo(() => {
|
|
2298
|
-
if (arxivFromContext) return arxivFromContext;
|
|
2299
|
-
if (!fileId) return null;
|
|
2300
|
-
return arxivItems.find((item) => item.fileId === fileId) || null;
|
|
2301
|
-
}, [arxivFromContext, arxivItems, fileId]);
|
|
2302
|
-
const arxivError = arxivPaper ? arxivErrors[arxivPaper.arxivId] : void 0;
|
|
2303
|
-
const supportsMarkdownView = Boolean(
|
|
2304
|
-
fileId && !isCliFile && (!isArxivPdf || arxivPaper?.overview?.trim() || arxivPaper?.abstract?.trim())
|
|
2305
|
-
);
|
|
2306
|
-
const markdownButtonLabel = isArxivPdf ? t("summary") : t("markdown");
|
|
2307
|
-
const markdownButtonTitle = isArxivPdf ? t("open_summary") : t("open_markdown");
|
|
2308
|
-
reactExports.useEffect(() => {
|
|
2309
|
-
sidebarVisibleRef.current = sidebarVisible;
|
|
2310
|
-
}, [sidebarVisible]);
|
|
2311
|
-
reactExports.useEffect(() => {
|
|
2312
|
-
toggleSidebarRef.current = toggleSidebar;
|
|
2313
|
-
}, [toggleSidebar]);
|
|
2314
|
-
const handleCopyBibtex = reactExports.useCallback(async () => {
|
|
2315
|
-
if (!arxivPaper) return;
|
|
2316
|
-
const bibtex = generateBibTeX(arxivPaper);
|
|
2317
|
-
const success = await copyToClipboard(bibtex);
|
|
2318
|
-
addToast({
|
|
2319
|
-
type: success ? "success" : "error",
|
|
2320
|
-
title: success ? t("toast_bibtex_copied_title") : t("toast_copy_failed_title"),
|
|
2321
|
-
description: success ? arxivPaper.title || arxivPaper.arxivId : t("try_again"),
|
|
2322
|
-
duration: 1800
|
|
2323
|
-
});
|
|
2324
|
-
}, [addToast, arxivPaper, t]);
|
|
2325
|
-
const handleOpenArxiv = reactExports.useCallback(() => {
|
|
2326
|
-
if (!arxivPaper?.arxivId) return;
|
|
2327
|
-
window.open(`https://arxiv.org/abs/${arxivPaper.arxivId}`, "_blank", "noopener,noreferrer");
|
|
2328
|
-
}, [arxivPaper]);
|
|
2329
|
-
const resolveWorkspaceResourcePath = reactExports.useCallback(() => {
|
|
2330
|
-
const pathHint = String(context.resourcePath || "").trim();
|
|
2331
|
-
if (pathHint.startsWith("/FILES")) return pathHint;
|
|
2332
|
-
if (fileId) {
|
|
2333
|
-
const node = findNode(fileId);
|
|
2334
|
-
if (node?.path) return toFilesResourcePath(node.path);
|
|
2335
|
-
}
|
|
2336
|
-
if (pathHint) return toFilesResourcePath(pathHint);
|
|
2337
|
-
return void 0;
|
|
2338
|
-
}, [context.resourcePath, fileId, findNode]);
|
|
2339
|
-
const loadPdfMarkdown = reactExports.useCallback(async () => {
|
|
2340
|
-
if (!fileId || isCliFile || isArxivPdf) return null;
|
|
2341
|
-
const cached = markdownCacheRef.current[fileId];
|
|
2342
|
-
if (cached) return cached;
|
|
2343
|
-
const inflight = markdownLoadPromiseRef.current[fileId];
|
|
2344
|
-
if (inflight) return inflight;
|
|
2345
|
-
const request = apiClient.get(`/api/v1/pdf/markdown/${fileId}`, {
|
|
2346
|
-
responseType: "text",
|
|
2347
|
-
transformResponse: [(value) => value]
|
|
2348
|
-
}).then((response) => {
|
|
2349
|
-
const markdown = typeof response.data === "string" ? response.data : String(response.data || "");
|
|
2350
|
-
if (markdown) {
|
|
2351
|
-
markdownCacheRef.current[fileId] = markdown;
|
|
2352
|
-
return markdown;
|
|
2353
|
-
}
|
|
2354
|
-
return null;
|
|
2355
|
-
}).finally(() => {
|
|
2356
|
-
delete markdownLoadPromiseRef.current[fileId];
|
|
2357
|
-
});
|
|
2358
|
-
markdownLoadPromiseRef.current[fileId] = request;
|
|
2359
|
-
return request;
|
|
2360
|
-
}, [fileId, isArxivPdf, isCliFile]);
|
|
2361
|
-
const enrichReferenceWithMarkdown = reactExports.useCallback(
|
|
2362
|
-
async (referenceId, selectedText) => {
|
|
2363
|
-
if (!fileId || isCliFile || isArxivPdf) return;
|
|
2364
|
-
try {
|
|
2365
|
-
const markdown = await loadPdfMarkdown();
|
|
2366
|
-
const excerpt = locateMarkdownExcerpt(markdown || "", selectedText);
|
|
2367
|
-
updateWorkspaceReference(referenceId, {
|
|
2368
|
-
markdownExcerpt: excerpt || void 0,
|
|
2369
|
-
excerptStatus: excerpt ? "ready" : "error"
|
|
2370
|
-
});
|
|
2371
|
-
} catch {
|
|
2372
|
-
updateWorkspaceReference(referenceId, {
|
|
2373
|
-
excerptStatus: "error"
|
|
2374
|
-
});
|
|
2375
|
-
}
|
|
2376
|
-
},
|
|
2377
|
-
[fileId, isArxivPdf, isCliFile, loadPdfMarkdown, updateWorkspaceReference]
|
|
2378
|
-
);
|
|
2379
|
-
reactExports.useEffect(() => {
|
|
2380
|
-
if (!fileId || isCliFile || isArxivPdf) return;
|
|
2381
|
-
void loadPdfMarkdown().catch(() => void 0);
|
|
2382
|
-
}, [fileId, isArxivPdf, isCliFile, loadPdfMarkdown]);
|
|
2383
|
-
const handleAskCopilotFromSelection = reactExports.useCallback(
|
|
2384
|
-
(content, position) => {
|
|
2385
|
-
const selectedText = String(content?.text || "").trim();
|
|
2386
|
-
if (!selectedText) return;
|
|
2387
|
-
const referenceId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `pdf-ref-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2388
|
-
const fallbackRect = position.boundingRect ? [
|
|
2389
|
-
{
|
|
2390
|
-
left: position.boundingRect.x1,
|
|
2391
|
-
top: position.boundingRect.y1,
|
|
2392
|
-
width: position.boundingRect.width,
|
|
2393
|
-
height: position.boundingRect.height,
|
|
2394
|
-
pageNumber: position.boundingRect.pageNumber
|
|
2395
|
-
}
|
|
2396
|
-
] : [];
|
|
2397
|
-
addWorkspaceReference({
|
|
2398
|
-
id: referenceId,
|
|
2399
|
-
kind: "pdf_text",
|
|
2400
|
-
tabId,
|
|
2401
|
-
fileId: fileId || void 0,
|
|
2402
|
-
resourceId: fileId || void 0,
|
|
2403
|
-
resourcePath: resolveWorkspaceResourcePath(),
|
|
2404
|
-
resourceName: fileName,
|
|
2405
|
-
pageNumber: position.pageNumber,
|
|
2406
|
-
selectedText,
|
|
2407
|
-
markdownExcerpt: void 0,
|
|
2408
|
-
excerptStatus: fileId && !isCliFile && !isArxivPdf ? "loading" : "idle",
|
|
2409
|
-
rects: position.rects?.map((rect) => ({
|
|
2410
|
-
left: rect.x1,
|
|
2411
|
-
top: rect.y1,
|
|
2412
|
-
width: rect.width,
|
|
2413
|
-
height: rect.height,
|
|
2414
|
-
pageNumber: rect.pageNumber
|
|
2415
|
-
})) || fallbackRect,
|
|
2416
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2417
|
-
});
|
|
2418
|
-
window.dispatchEvent(new CustomEvent("ds:copilot:focus", { detail: { focus: true } }));
|
|
2419
|
-
if (fileId && !isCliFile && !isArxivPdf) {
|
|
2420
|
-
void enrichReferenceWithMarkdown(referenceId, selectedText);
|
|
2421
|
-
}
|
|
2422
|
-
},
|
|
2423
|
-
[
|
|
2424
|
-
addWorkspaceReference,
|
|
2425
|
-
enrichReferenceWithMarkdown,
|
|
2426
|
-
fileId,
|
|
2427
|
-
fileName,
|
|
2428
|
-
isArxivPdf,
|
|
2429
|
-
isCliFile,
|
|
2430
|
-
resolveWorkspaceResourcePath,
|
|
2431
|
-
tabId
|
|
2432
|
-
]
|
|
2433
|
-
);
|
|
2434
|
-
reactExports.useEffect(() => {
|
|
2435
|
-
if (!shouldRedirectToNotebook) return;
|
|
2436
|
-
updateTabPlugin(tabId, BUILTIN_PLUGINS.NOTEBOOK, {
|
|
2437
|
-
...context,
|
|
2438
|
-
resourceId: fileId,
|
|
2439
|
-
resourceName: fileName,
|
|
2440
|
-
mimeType: context.mimeType ?? "text/markdown",
|
|
2441
|
-
customData: {
|
|
2442
|
-
...context.customData || {},
|
|
2443
|
-
docKind: "markdown"
|
|
2444
|
-
}
|
|
2445
|
-
});
|
|
2446
|
-
}, [context, fileId, fileName, shouldRedirectToNotebook, tabId, updateTabPlugin]);
|
|
2447
|
-
reactExports.useEffect(() => {
|
|
2448
|
-
setTitle(fileName);
|
|
2449
|
-
}, [fileName, setTitle]);
|
|
2450
|
-
reactExports.useEffect(() => {
|
|
2451
|
-
setDirty(false);
|
|
2452
|
-
}, [setDirty]);
|
|
2453
|
-
reactExports.useEffect(() => {
|
|
2454
|
-
updateWorkspaceTabState(tabId, {
|
|
2455
|
-
contentKind: "pdf",
|
|
2456
|
-
documentMode: "pdf",
|
|
2457
|
-
pageNumber: currentPage,
|
|
2458
|
-
isReadOnly: isReadOnlyMode
|
|
2459
|
-
});
|
|
2460
|
-
}, [currentPage, isReadOnlyMode, tabId, updateWorkspaceTabState]);
|
|
2461
|
-
reactExports.useEffect(() => {
|
|
2462
|
-
const resolvedProjectId = projectIdFromContext || questProjectIdFromFileId;
|
|
2463
|
-
if (resolvedProjectId) {
|
|
2464
|
-
setProjectId(resolvedProjectId);
|
|
2465
|
-
return;
|
|
2466
|
-
}
|
|
2467
|
-
if (!fileId || isCliFile) return;
|
|
2468
|
-
getFile(fileId).then((f) => {
|
|
2469
|
-
if (f?.project_id) setProjectId(String(f.project_id));
|
|
2470
|
-
}).catch(() => {
|
|
2471
|
-
});
|
|
2472
|
-
}, [fileId, hasExternalPdfUrl, isCliFile, projectIdFromContext, questProjectIdFromFileId]);
|
|
2473
|
-
reactExports.useEffect(() => {
|
|
2474
|
-
if (!fileId || !isCliFile) {
|
|
2475
|
-
setCliPdfUrl(null);
|
|
2476
|
-
setCliPdfLoading(false);
|
|
2477
|
-
setCliPdfError(null);
|
|
2478
|
-
return;
|
|
2479
|
-
}
|
|
2480
|
-
let cancelled = false;
|
|
2481
|
-
let objectUrl = null;
|
|
2482
|
-
setCliPdfLoading(true);
|
|
2483
|
-
setCliPdfError(null);
|
|
2484
|
-
const loadCliPdf = async () => {
|
|
2485
|
-
try {
|
|
2486
|
-
const { createFileObjectUrl: createFileObjectUrl2 } = await __vitePreload(async () => { const { createFileObjectUrl: createFileObjectUrl2 } = await import('./index-D_E4281X.js').then(n => n.ep);return { createFileObjectUrl: createFileObjectUrl2 }},true?__vite__mapDeps([0,1]):void 0);
|
|
2487
|
-
objectUrl = await createFileObjectUrl2(fileId);
|
|
2488
|
-
if (cancelled) {
|
|
2489
|
-
URL.revokeObjectURL(objectUrl);
|
|
2490
|
-
return;
|
|
2491
|
-
}
|
|
2492
|
-
setCliPdfUrl(objectUrl);
|
|
2493
|
-
} catch (err) {
|
|
2494
|
-
if (!cancelled) {
|
|
2495
|
-
setCliPdfError("Failed to load PDF from CLI server.");
|
|
2496
|
-
}
|
|
2497
|
-
} finally {
|
|
2498
|
-
if (!cancelled) {
|
|
2499
|
-
setCliPdfLoading(false);
|
|
2500
|
-
}
|
|
2501
|
-
}
|
|
2502
|
-
};
|
|
2503
|
-
loadCliPdf();
|
|
2504
|
-
return () => {
|
|
2505
|
-
cancelled = true;
|
|
2506
|
-
if (objectUrl) {
|
|
2507
|
-
URL.revokeObjectURL(objectUrl);
|
|
2508
|
-
}
|
|
2509
|
-
};
|
|
2510
|
-
}, [fileId, isCliFile]);
|
|
2511
|
-
reactExports.useEffect(() => {
|
|
2512
|
-
if (!fileId || !isQuestFile || isCliFile) {
|
|
2513
|
-
setQuestPdfUrl(null);
|
|
2514
|
-
setQuestPdfLoading(false);
|
|
2515
|
-
setQuestPdfError(null);
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
let cancelled = false;
|
|
2519
|
-
let objectUrl = null;
|
|
2520
|
-
setQuestPdfLoading(true);
|
|
2521
|
-
setQuestPdfError(null);
|
|
2522
|
-
const loadQuestPdf = async () => {
|
|
2523
|
-
try {
|
|
2524
|
-
objectUrl = await createFileObjectUrl(fileId);
|
|
2525
|
-
if (cancelled) {
|
|
2526
|
-
URL.revokeObjectURL(objectUrl);
|
|
2527
|
-
return;
|
|
2528
|
-
}
|
|
2529
|
-
setQuestPdfUrl(objectUrl);
|
|
2530
|
-
} catch (err) {
|
|
2531
|
-
if (!cancelled) {
|
|
2532
|
-
setQuestPdfError("Failed to load PDF document.");
|
|
2533
|
-
}
|
|
2534
|
-
} finally {
|
|
2535
|
-
if (!cancelled) {
|
|
2536
|
-
setQuestPdfLoading(false);
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
};
|
|
2540
|
-
void loadQuestPdf();
|
|
2541
|
-
return () => {
|
|
2542
|
-
cancelled = true;
|
|
2543
|
-
if (objectUrl) {
|
|
2544
|
-
URL.revokeObjectURL(objectUrl);
|
|
2545
|
-
}
|
|
2546
|
-
};
|
|
2547
|
-
}, [fileId, isCliFile, isQuestFile]);
|
|
2548
|
-
reactExports.useEffect(() => {
|
|
2549
|
-
if (!projectId) return;
|
|
2550
|
-
const userId = currentUser?.id;
|
|
2551
|
-
if (!userId) return;
|
|
2552
|
-
listProjectMembers(projectId).then((members) => {
|
|
2553
|
-
const me = members.find((m) => m.user_id === userId);
|
|
2554
|
-
if (me?.annotation_color) setAuthorColor(String(me.annotation_color));
|
|
2555
|
-
}).catch(() => {
|
|
2556
|
-
});
|
|
2557
|
-
}, [projectId, currentUser?.id]);
|
|
2558
|
-
reactExports.useEffect(() => {
|
|
2559
|
-
if (!projectId || !fileId || isCliFile || isReadOnlyMode) return;
|
|
2560
|
-
const { socket, release } = acquireFileSocket();
|
|
2561
|
-
socketRef.current = { socket, release };
|
|
2562
|
-
const joinIfReady = async () => {
|
|
2563
|
-
if (joinedRef.current) return;
|
|
2564
|
-
if (!socket.connected) return;
|
|
2565
|
-
try {
|
|
2566
|
-
joinedRef.current = true;
|
|
2567
|
-
await socket.emitWithAck("file:join", { projectId, fileId, clientVersion: "1.0.0" });
|
|
2568
|
-
} catch {
|
|
2569
|
-
joinedRef.current = false;
|
|
2570
|
-
}
|
|
2571
|
-
};
|
|
2572
|
-
const onConnect = () => void joinIfReady();
|
|
2573
|
-
const onDisconnect = () => {
|
|
2574
|
-
joinedRef.current = false;
|
|
2575
|
-
};
|
|
2576
|
-
const onAnnotationEvent = (payload) => {
|
|
2577
|
-
if (payload?.fileId && String(payload.fileId) !== String(fileId)) return;
|
|
2578
|
-
queryClient.invalidateQueries({ queryKey: annotationKeys.list(fileId) });
|
|
2579
|
-
const incomingAnnotationId = typeof payload?.annotationId === "string" && payload.annotationId.trim() ? payload.annotationId.trim() : null;
|
|
2580
|
-
if (incomingAnnotationId) {
|
|
2581
|
-
pendingAnnotationIdRef.current = incomingAnnotationId;
|
|
2582
|
-
if (!sidebarVisibleRef.current) {
|
|
2583
|
-
toggleSidebarRef.current?.();
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
};
|
|
2587
|
-
socket.on("connect", onConnect);
|
|
2588
|
-
socket.on("disconnect", onDisconnect);
|
|
2589
|
-
socket.on("annotation:created", onAnnotationEvent);
|
|
2590
|
-
socket.on("annotation:updated", onAnnotationEvent);
|
|
2591
|
-
socket.on("annotation:deleted", onAnnotationEvent);
|
|
2592
|
-
if (socket.connected) void joinIfReady();
|
|
2593
|
-
return () => {
|
|
2594
|
-
try {
|
|
2595
|
-
socket.off("connect", onConnect);
|
|
2596
|
-
socket.off("disconnect", onDisconnect);
|
|
2597
|
-
socket.off("annotation:created", onAnnotationEvent);
|
|
2598
|
-
socket.off("annotation:updated", onAnnotationEvent);
|
|
2599
|
-
socket.off("annotation:deleted", onAnnotationEvent);
|
|
2600
|
-
if (joinedRef.current) {
|
|
2601
|
-
void socket.emit("file:leave", { projectId, fileId });
|
|
2602
|
-
}
|
|
2603
|
-
} finally {
|
|
2604
|
-
joinedRef.current = false;
|
|
2605
|
-
socketRef.current = null;
|
|
2606
|
-
release();
|
|
2607
|
-
}
|
|
2608
|
-
};
|
|
2609
|
-
}, [projectId, fileId, isCliFile, isReadOnlyMode]);
|
|
2610
|
-
const highlights = reactExports.useMemo(() => {
|
|
2611
|
-
if (isReadOnlyMode) {
|
|
2612
|
-
return readOnlyHighlights;
|
|
2613
|
-
}
|
|
2614
|
-
return annotations.map((a) => ({
|
|
2615
|
-
id: a.id,
|
|
2616
|
-
position: a.position,
|
|
2617
|
-
content: a.content,
|
|
2618
|
-
comment: { text: a.comment || "", emoji: a.kind },
|
|
2619
|
-
color: a.color,
|
|
2620
|
-
author: a.author ?? null,
|
|
2621
|
-
tags: a.tags,
|
|
2622
|
-
previewSummary: typeof a.content?.summary === "string" ? String(a.content.summary || "").trim() || null : null
|
|
2623
|
-
}));
|
|
2624
|
-
}, [annotations, isReadOnlyMode, readOnlyHighlights]);
|
|
2625
|
-
const mergedHighlights = reactExports.useMemo(
|
|
2626
|
-
() => [...highlights, ...aiHighlights],
|
|
2627
|
-
[highlights, aiHighlights]
|
|
2628
|
-
);
|
|
2629
|
-
reactExports.useEffect(() => {
|
|
2630
|
-
const pendingId = pendingAnnotationIdRef.current;
|
|
2631
|
-
if (!pendingId) return;
|
|
2632
|
-
const highlight = highlights.find((h) => h.id === pendingId);
|
|
2633
|
-
if (!highlight) return;
|
|
2634
|
-
selectAnnotation(highlight.id);
|
|
2635
|
-
setCurrentPage(highlight.position.pageNumber);
|
|
2636
|
-
scrollViewerTo.current(highlight);
|
|
2637
|
-
pendingAnnotationIdRef.current = null;
|
|
2638
|
-
setFlashAnnotationId(highlight.id);
|
|
2639
|
-
if (flashTimerRef.current) clearTimeout(flashTimerRef.current);
|
|
2640
|
-
flashTimerRef.current = setTimeout(() => setFlashAnnotationId(null), 2200);
|
|
2641
|
-
}, [highlights, selectAnnotation, setCurrentPage]);
|
|
2642
|
-
reactExports.useEffect(() => {
|
|
2643
|
-
const highlighter = pdfHighlighterRef.current;
|
|
2644
|
-
return () => {
|
|
2645
|
-
highlighter?.setPinnedTipForHighlight(null);
|
|
2646
|
-
if (flashTimerRef.current) clearTimeout(flashTimerRef.current);
|
|
2647
|
-
};
|
|
2648
|
-
}, []);
|
|
2649
|
-
reactExports.useEffect(() => {
|
|
2650
|
-
if (!isReadOnlyMode) {
|
|
2651
|
-
readOnlySidebarInitRef.current = false;
|
|
2652
|
-
return;
|
|
2653
|
-
}
|
|
2654
|
-
if (readOnlySidebarInitRef.current) return;
|
|
2655
|
-
if (highlights.length === 0) return;
|
|
2656
|
-
readOnlySidebarInitRef.current = true;
|
|
2657
|
-
if (!sidebarVisible) {
|
|
2658
|
-
toggleSidebar();
|
|
2659
|
-
}
|
|
2660
|
-
}, [highlights.length, isReadOnlyMode, sidebarVisible, toggleSidebar]);
|
|
2661
|
-
const scrollToPage = reactExports.useCallback(
|
|
2662
|
-
(page) => {
|
|
2663
|
-
setCurrentPage(page);
|
|
2664
|
-
pdfHighlighterRef.current?.scrollToPage(page);
|
|
2665
|
-
},
|
|
2666
|
-
[setCurrentPage]
|
|
2667
|
-
);
|
|
2668
|
-
const addGhostHighlight = reactExports.useCallback(
|
|
2669
|
-
(detail) => {
|
|
2670
|
-
const id = `ai-ghost-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2671
|
-
const rects = detail.rects.map((rect) => ({
|
|
2672
|
-
...rect,
|
|
2673
|
-
width: rect.width ?? 100,
|
|
2674
|
-
height: rect.height ?? 100,
|
|
2675
|
-
pageNumber: rect.pageNumber ?? detail.page
|
|
2676
|
-
}));
|
|
2677
|
-
const boundingRect = {
|
|
2678
|
-
...detail.boundingRect,
|
|
2679
|
-
width: detail.boundingRect.width ?? 100,
|
|
2680
|
-
height: detail.boundingRect.height ?? 100,
|
|
2681
|
-
pageNumber: detail.boundingRect.pageNumber ?? detail.page
|
|
2682
|
-
};
|
|
2683
|
-
const ghostHighlight = {
|
|
2684
|
-
id,
|
|
2685
|
-
position: {
|
|
2686
|
-
boundingRect,
|
|
2687
|
-
rects,
|
|
2688
|
-
pageNumber: detail.page
|
|
2689
|
-
},
|
|
2690
|
-
content: { text: detail.text || "" },
|
|
2691
|
-
comment: { text: "", emoji: "note" },
|
|
2692
|
-
color: detail.color || "#3F5A6B",
|
|
2693
|
-
__dsGhost: true,
|
|
2694
|
-
__dsGhostOpacity: 1,
|
|
2695
|
-
__dsGhostMode: detail.mode || "guide"
|
|
2696
|
-
};
|
|
2697
|
-
setAiHighlights((prev) => [...prev, ghostHighlight]);
|
|
2698
|
-
const duration = detail.durationMs ?? 3e3;
|
|
2699
|
-
const fadeTimer = setTimeout(() => {
|
|
2700
|
-
setAiHighlights(
|
|
2701
|
-
(prev) => prev.map((h) => h.id === id ? { ...h, __dsGhostOpacity: 0 } : h)
|
|
2702
|
-
);
|
|
2703
|
-
}, duration);
|
|
2704
|
-
const removeTimer = setTimeout(() => {
|
|
2705
|
-
setAiHighlights((prev) => prev.filter((h) => h.id !== id));
|
|
2706
|
-
aiHighlightTimers.current.delete(id);
|
|
2707
|
-
aiHighlightTimers.current.delete(`${id}:fade`);
|
|
2708
|
-
}, duration + 800);
|
|
2709
|
-
aiHighlightTimers.current.set(id, removeTimer);
|
|
2710
|
-
aiHighlightTimers.current.set(`${id}:fade`, fadeTimer);
|
|
2711
|
-
},
|
|
2712
|
-
[]
|
|
2713
|
-
);
|
|
2714
|
-
reactExports.useEffect(() => {
|
|
2715
|
-
const timers = aiHighlightTimers.current;
|
|
2716
|
-
return () => {
|
|
2717
|
-
timers.forEach((timer) => clearTimeout(timer));
|
|
2718
|
-
timers.clear();
|
|
2719
|
-
};
|
|
2720
|
-
}, []);
|
|
2721
|
-
const shouldSkipEffect = reactExports.useCallback((detail) => {
|
|
2722
|
-
const effectId = typeof detail?.__dsEffectId === "string" ? detail.__dsEffectId : null;
|
|
2723
|
-
if (!effectId) return false;
|
|
2724
|
-
if (isPdfEffectHandled(effectId)) return true;
|
|
2725
|
-
markPdfEffectHandled(effectId);
|
|
2726
|
-
return false;
|
|
2727
|
-
}, []);
|
|
2728
|
-
const applyNavigate = reactExports.useCallback(
|
|
2729
|
-
(detail) => {
|
|
2730
|
-
if (!detail || detail.fileId !== fileId) return;
|
|
2731
|
-
if (shouldSkipEffect(detail)) return;
|
|
2732
|
-
queryClient.invalidateQueries({ queryKey: annotationKeys.list(fileId) });
|
|
2733
|
-
if (detail.page) {
|
|
2734
|
-
scrollToPage(detail.page);
|
|
2735
|
-
}
|
|
2736
|
-
if (detail.rects && detail.boundingRect) {
|
|
2737
|
-
addGhostHighlight({
|
|
2738
|
-
page: detail.page || 1,
|
|
2739
|
-
rects: detail.rects,
|
|
2740
|
-
boundingRect: detail.boundingRect,
|
|
2741
|
-
color: detail.color,
|
|
2742
|
-
mode: detail.mode || "guide",
|
|
2743
|
-
durationMs: detail.durationMs,
|
|
2744
|
-
text: detail.text
|
|
2745
|
-
});
|
|
2746
|
-
}
|
|
2747
|
-
if (detail.annotationId) {
|
|
2748
|
-
pendingAnnotationIdRef.current = detail.annotationId;
|
|
2749
|
-
}
|
|
2750
|
-
if ((detail.annotationId || detail.__dsEffectId) && !sidebarVisible) {
|
|
2751
|
-
toggleSidebar();
|
|
2752
|
-
}
|
|
2753
|
-
},
|
|
2754
|
-
[addGhostHighlight, fileId, queryClient, scrollToPage, sidebarVisible, toggleSidebar, shouldSkipEffect]
|
|
2755
|
-
);
|
|
2756
|
-
const applyAnnotationCreated = reactExports.useCallback(
|
|
2757
|
-
(detail) => {
|
|
2758
|
-
if (!detail || detail.fileId !== fileId) return;
|
|
2759
|
-
if (shouldSkipEffect(detail)) return;
|
|
2760
|
-
queryClient.invalidateQueries({ queryKey: annotationKeys.list(fileId) });
|
|
2761
|
-
if (detail.page) {
|
|
2762
|
-
scrollToPage(detail.page);
|
|
2763
|
-
}
|
|
2764
|
-
if (detail.annotationId) {
|
|
2765
|
-
pendingAnnotationIdRef.current = detail.annotationId;
|
|
2766
|
-
if (!sidebarVisible) {
|
|
2767
|
-
toggleSidebar();
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
},
|
|
2771
|
-
[fileId, queryClient, scrollToPage, sidebarVisible, toggleSidebar, shouldSkipEffect]
|
|
2772
|
-
);
|
|
2773
|
-
const processQueuedEffects = reactExports.useCallback(() => {
|
|
2774
|
-
if (!fileId) return;
|
|
2775
|
-
const queued = consumePdfEffects(fileId);
|
|
2776
|
-
if (queued.length === 0) return;
|
|
2777
|
-
queued.forEach((effect) => {
|
|
2778
|
-
if (effect.name === "pdf:jump") {
|
|
2779
|
-
applyNavigate(effect.data);
|
|
2780
|
-
} else {
|
|
2781
|
-
applyAnnotationCreated(effect.data);
|
|
2782
|
-
}
|
|
2783
|
-
});
|
|
2784
|
-
}, [applyAnnotationCreated, applyNavigate, fileId]);
|
|
2785
|
-
reactExports.useEffect(() => {
|
|
2786
|
-
if (!fileId) return;
|
|
2787
|
-
processQueuedEffects();
|
|
2788
|
-
}, [fileId, processQueuedEffects]);
|
|
2789
|
-
reactExports.useEffect(() => {
|
|
2790
|
-
if (typeof window === "undefined") return;
|
|
2791
|
-
if (!fileId) return;
|
|
2792
|
-
const handleNavigate = (event) => {
|
|
2793
|
-
const detail = event.detail;
|
|
2794
|
-
applyNavigate(detail);
|
|
2795
|
-
};
|
|
2796
|
-
const handleAnnotationCreated = (event) => {
|
|
2797
|
-
const detail = event.detail;
|
|
2798
|
-
applyAnnotationCreated(detail);
|
|
2799
|
-
};
|
|
2800
|
-
const handleQueue = (event) => {
|
|
2801
|
-
const detail = event.detail;
|
|
2802
|
-
if (!detail || detail.fileId !== fileId) return;
|
|
2803
|
-
processQueuedEffects();
|
|
2804
|
-
};
|
|
2805
|
-
window.addEventListener("pdf:navigate", handleNavigate);
|
|
2806
|
-
window.addEventListener("pdf:annotation_created", handleAnnotationCreated);
|
|
2807
|
-
window.addEventListener("ds:pdf:queue", handleQueue);
|
|
2808
|
-
return () => {
|
|
2809
|
-
window.removeEventListener("pdf:navigate", handleNavigate);
|
|
2810
|
-
window.removeEventListener("pdf:annotation_created", handleAnnotationCreated);
|
|
2811
|
-
window.removeEventListener("ds:pdf:queue", handleQueue);
|
|
2812
|
-
};
|
|
2813
|
-
}, [applyAnnotationCreated, applyNavigate, fileId, processQueuedEffects]);
|
|
2814
|
-
const addHighlight = reactExports.useCallback(
|
|
2815
|
-
async (highlight) => {
|
|
2816
|
-
if (isReadOnlyMode || isCliFile || !fileId) return;
|
|
2817
|
-
try {
|
|
2818
|
-
const kind = normalizeKind(highlight.comment?.emoji);
|
|
2819
|
-
const created = await createAnnotation({
|
|
2820
|
-
fileId,
|
|
2821
|
-
position: highlight.position,
|
|
2822
|
-
content: highlight.content,
|
|
2823
|
-
comment: highlight.comment?.text || "",
|
|
2824
|
-
kind,
|
|
2825
|
-
tags: []
|
|
2826
|
-
});
|
|
2827
|
-
if (created?.id && !sidebarVisible) {
|
|
2828
|
-
toggleSidebar();
|
|
2829
|
-
}
|
|
2830
|
-
} catch (e) {
|
|
2831
|
-
console.warn("[PdfViewerPlugin] Failed to create annotation:", e);
|
|
2832
|
-
}
|
|
2833
|
-
},
|
|
2834
|
-
[createAnnotation, fileId, isCliFile, isReadOnlyMode, sidebarVisible, toggleSidebar]
|
|
2835
|
-
);
|
|
2836
|
-
const updateHighlight = reactExports.useCallback(
|
|
2837
|
-
async (highlightId, position, content) => {
|
|
2838
|
-
if (isReadOnlyMode || isCliFile || !fileId) return;
|
|
2839
|
-
try {
|
|
2840
|
-
const existing = annotations.find((a) => a.id === highlightId);
|
|
2841
|
-
if (!existing) return;
|
|
2842
|
-
const nextPosition = {
|
|
2843
|
-
...existing.position,
|
|
2844
|
-
...position,
|
|
2845
|
-
boundingRect: {
|
|
2846
|
-
...existing.position.boundingRect,
|
|
2847
|
-
...position.boundingRect
|
|
2848
|
-
}
|
|
2849
|
-
};
|
|
2850
|
-
const nextContent = { ...existing.content, ...content };
|
|
2851
|
-
await updateAnnotation(highlightId, { position: nextPosition, content: nextContent });
|
|
2852
|
-
} catch (e) {
|
|
2853
|
-
console.warn("[PdfViewerPlugin] Failed to update annotation position/content:", e);
|
|
2854
|
-
}
|
|
2855
|
-
},
|
|
2856
|
-
[annotations, fileId, isCliFile, isReadOnlyMode, updateAnnotation]
|
|
2857
|
-
);
|
|
2858
|
-
const handleSelectHighlight = reactExports.useCallback(
|
|
2859
|
-
(id, options) => {
|
|
2860
|
-
const highlight = highlights.find((h) => h.id === id);
|
|
2861
|
-
if (!highlight) return;
|
|
2862
|
-
const allowToggleClose = options?.allowToggleClose !== false;
|
|
2863
|
-
const skipAutoScroll = options?.skipAutoScroll === true;
|
|
2864
|
-
const isToggleClose = allowToggleClose && selectedAnnotationId === id;
|
|
2865
|
-
if (isToggleClose) {
|
|
2866
|
-
selectAnnotation(null);
|
|
2867
|
-
pdfHighlighterRef.current?.setPinnedTipForHighlight(null);
|
|
2868
|
-
return;
|
|
2869
|
-
}
|
|
2870
|
-
if (!sidebarVisible) {
|
|
2871
|
-
toggleSidebar();
|
|
2872
|
-
}
|
|
2873
|
-
selectAnnotation(id);
|
|
2874
|
-
pdfHighlighterRef.current?.setPinnedTipForHighlight(null);
|
|
2875
|
-
if (!skipAutoScroll) {
|
|
2876
|
-
setCurrentPage(highlight.position.pageNumber);
|
|
2877
|
-
scrollViewerTo.current(highlight);
|
|
2878
|
-
}
|
|
2879
|
-
},
|
|
2880
|
-
[
|
|
2881
|
-
highlights,
|
|
2882
|
-
selectedAnnotationId,
|
|
2883
|
-
sidebarVisible,
|
|
2884
|
-
selectAnnotation,
|
|
2885
|
-
setCurrentPage,
|
|
2886
|
-
toggleSidebar
|
|
2887
|
-
]
|
|
2888
|
-
);
|
|
2889
|
-
const handlePdfBackgroundPointerDown = reactExports.useCallback(
|
|
2890
|
-
(event) => {
|
|
2891
|
-
const target = event.target;
|
|
2892
|
-
if (!target) return;
|
|
2893
|
-
if (target.closest("[data-ds-highlight-target='true']") || target.closest("[data-ds-overlay-hint='true']")) {
|
|
2894
|
-
return;
|
|
2895
|
-
}
|
|
2896
|
-
if (!selectedAnnotationId) return;
|
|
2897
|
-
selectAnnotation(null);
|
|
2898
|
-
pdfHighlighterRef.current?.setPinnedTipForHighlight(null);
|
|
2899
|
-
},
|
|
2900
|
-
[selectedAnnotationId, selectAnnotation]
|
|
2901
|
-
);
|
|
2902
|
-
const handleDeleteHighlight = reactExports.useCallback(
|
|
2903
|
-
async (id) => {
|
|
2904
|
-
if (isReadOnlyMode || isCliFile || !fileId) return;
|
|
2905
|
-
try {
|
|
2906
|
-
await deleteAnnotation(id);
|
|
2907
|
-
if (selectedAnnotationId === id) {
|
|
2908
|
-
selectAnnotation(null);
|
|
2909
|
-
}
|
|
2910
|
-
} catch (e) {
|
|
2911
|
-
console.warn("[PdfViewerPlugin] Failed to delete annotation:", e);
|
|
2912
|
-
}
|
|
2913
|
-
},
|
|
2914
|
-
[deleteAnnotation, fileId, isCliFile, isReadOnlyMode, selectedAnnotationId, selectAnnotation]
|
|
2915
|
-
);
|
|
2916
|
-
const handleUpdateKind = reactExports.useCallback(
|
|
2917
|
-
async (id, kind) => {
|
|
2918
|
-
if (isReadOnlyMode || isCliFile || !fileId) return;
|
|
2919
|
-
try {
|
|
2920
|
-
await updateAnnotation(id, { kind });
|
|
2921
|
-
} catch (e) {
|
|
2922
|
-
console.warn("[PdfViewerPlugin] Failed to update annotation kind:", e);
|
|
2923
|
-
}
|
|
2924
|
-
},
|
|
2925
|
-
[fileId, isCliFile, isReadOnlyMode, updateAnnotation]
|
|
2926
|
-
);
|
|
2927
|
-
const handleUpdateComment = reactExports.useCallback(
|
|
2928
|
-
async (id, comment) => {
|
|
2929
|
-
if (isReadOnlyMode || isCliFile || !fileId) return;
|
|
2930
|
-
try {
|
|
2931
|
-
await updateAnnotation(id, { comment });
|
|
2932
|
-
} catch (e) {
|
|
2933
|
-
console.warn("[PdfViewerPlugin] Failed to update annotation comment:", e);
|
|
2934
|
-
}
|
|
2935
|
-
},
|
|
2936
|
-
[fileId, isCliFile, isReadOnlyMode, updateAnnotation]
|
|
2937
|
-
);
|
|
2938
|
-
const handleDownload = reactExports.useCallback(() => {
|
|
2939
|
-
if (!fileId) {
|
|
2940
|
-
window.open(pdfUrl, "_blank");
|
|
2941
|
-
return;
|
|
2942
|
-
}
|
|
2943
|
-
(async () => {
|
|
2944
|
-
const { downloadFileById } = await __vitePreload(async () => { const { downloadFileById } = await import('./index-D_E4281X.js').then(n => n.ep);return { downloadFileById }},true?__vite__mapDeps([0,1]):void 0);
|
|
2945
|
-
await downloadFileById(fileId, fileName);
|
|
2946
|
-
})();
|
|
2947
|
-
}, [fileId, fileName, pdfUrl]);
|
|
2948
|
-
const handleOpenMarkdown = reactExports.useCallback(() => {
|
|
2949
|
-
if (openMarkdownViewFromHost) {
|
|
2950
|
-
openMarkdownViewFromHost();
|
|
2951
|
-
return;
|
|
2952
|
-
}
|
|
2953
|
-
if (!fileId || isCliFile) return;
|
|
2954
|
-
updateTabPlugin(tabId, BUILTIN_PLUGINS.PDF_MARKDOWN, {
|
|
2955
|
-
...context,
|
|
2956
|
-
customData: {
|
|
2957
|
-
...context.customData || {},
|
|
2958
|
-
pdfView: isArxivPdf ? "summary" : "markdown"
|
|
2959
|
-
}
|
|
2960
|
-
});
|
|
2961
|
-
}, [context, fileId, isArxivPdf, isCliFile, openMarkdownViewFromHost, tabId, updateTabPlugin]);
|
|
2962
|
-
if (shouldRedirectToNotebook) {
|
|
2963
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-full items-center justify-center bg-muted/20", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-muted-foreground", children: "Switching to Markdown editor..." }) });
|
|
2964
|
-
}
|
|
2965
|
-
if (isCliFile && (cliPdfLoading || cliPdfError) && !cliPdfUrl) {
|
|
2966
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center bg-background", children: cliPdfError ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-muted-foreground", children: cliPdfError }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PdfSpinner, {}) });
|
|
2967
|
-
}
|
|
2968
|
-
if (isQuestFile && (questPdfLoading || questPdfError) && !questPdfUrl) {
|
|
2969
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center bg-background", children: questPdfError ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-muted-foreground", children: questPdfError }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PdfSpinner, {}) });
|
|
2970
|
-
}
|
|
2971
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex flex-col bg-background", children: [
|
|
2972
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2973
|
-
Toolbar,
|
|
2974
|
-
{
|
|
2975
|
-
scale,
|
|
2976
|
-
onScaleChange: setScale,
|
|
2977
|
-
currentPage,
|
|
2978
|
-
totalPages,
|
|
2979
|
-
onPageChange: scrollToPage,
|
|
2980
|
-
sidebarVisible,
|
|
2981
|
-
onSidebarToggle: toggleSidebar,
|
|
2982
|
-
onDownload: handleDownload,
|
|
2983
|
-
onInfo: arxivPaper ? () => setInfoOpen(true) : void 0,
|
|
2984
|
-
onMarkdownToggle: openMarkdownViewFromHost || supportsMarkdownView ? handleOpenMarkdown : void 0,
|
|
2985
|
-
markdownLabel: markdownButtonLabel,
|
|
2986
|
-
markdownTitle: markdownButtonTitle,
|
|
2987
|
-
reviewOpinionActive: reviewOpinionActiveFromHost,
|
|
2988
|
-
onReviewOpinionToggle: reviewOpinionAvailableFromHost && openReviewOpinionViewFromHost ? openReviewOpinionViewFromHost : void 0,
|
|
2989
|
-
reviewOpinionLabel: reviewOpinionLabelFromHost,
|
|
2990
|
-
reviewOpinionTitle: reviewOpinionTitleFromHost
|
|
2991
|
-
}
|
|
2992
|
-
),
|
|
2993
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex overflow-hidden bg-muted/10", children: [
|
|
2994
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden flex", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative h-full w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2995
|
-
"div",
|
|
2996
|
-
{
|
|
2997
|
-
className: "relative h-full overflow-hidden border border-border bg-background shadow-soft-card",
|
|
2998
|
-
onPointerDown: handlePdfBackgroundPointerDown,
|
|
2999
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3000
|
-
PdfLoader,
|
|
3001
|
-
{
|
|
3002
|
-
url: pdfUrl,
|
|
3003
|
-
httpHeaders,
|
|
3004
|
-
workerSrc: PDF_WORKER_SRC,
|
|
3005
|
-
cMapUrl: PDF_CMAP_URL,
|
|
3006
|
-
cMapPacked: PDF_CMAP_PACKED,
|
|
3007
|
-
beforeLoad: /* @__PURE__ */ jsxRuntimeExports.jsx(PdfSpinner, {}),
|
|
3008
|
-
children: (pdfDocument) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3009
|
-
PdfHighlighterSurface,
|
|
3010
|
-
{
|
|
3011
|
-
pdfDocument,
|
|
3012
|
-
scale,
|
|
3013
|
-
highlights: mergedHighlights,
|
|
3014
|
-
selectedId: selectedAnnotationId,
|
|
3015
|
-
onSelect: handleSelectHighlight,
|
|
3016
|
-
onAdd: addHighlight,
|
|
3017
|
-
onUpdate: updateHighlight,
|
|
3018
|
-
onScrollRef: (scrollTo) => {
|
|
3019
|
-
scrollViewerTo.current = scrollTo;
|
|
3020
|
-
},
|
|
3021
|
-
onPageChange: (pageNumber) => setCurrentPage(pageNumber),
|
|
3022
|
-
pdfHighlighterRef,
|
|
3023
|
-
authorColor,
|
|
3024
|
-
authorHandle: (currentUser?.email ? currentUser.email.split("@", 1)[0] : "") || "user",
|
|
3025
|
-
showOverlayHints,
|
|
3026
|
-
tipPlacementMode: isResizableAnnotationWorkspace ? "right" : "auto",
|
|
3027
|
-
readOnly: isReadOnlyMode,
|
|
3028
|
-
onAskCopilot: handleAskCopilotFromSelection
|
|
3029
|
-
}
|
|
3030
|
-
)
|
|
3031
|
-
}
|
|
3032
|
-
)
|
|
3033
|
-
}
|
|
3034
|
-
) }) }),
|
|
3035
|
-
sidebarVisible && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3036
|
-
"div",
|
|
3037
|
-
{
|
|
3038
|
-
className: cn(
|
|
3039
|
-
"relative border-l bg-background/60 backdrop-blur flex-shrink-0",
|
|
3040
|
-
isResizableAnnotationWorkspace ? "" : "w-72"
|
|
3041
|
-
),
|
|
3042
|
-
style: isResizableAnnotationWorkspace ? { width: reviewSidebarWidth } : void 0,
|
|
3043
|
-
children: [
|
|
3044
|
-
isResizableAnnotationWorkspace ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3045
|
-
"div",
|
|
3046
|
-
{
|
|
3047
|
-
role: "separator",
|
|
3048
|
-
"aria-orientation": "vertical",
|
|
3049
|
-
"aria-label": t("resize_annotation_sidebar"),
|
|
3050
|
-
tabIndex: 0,
|
|
3051
|
-
className: "group absolute inset-y-0 left-0 z-20 w-3 -translate-x-1/2 cursor-ew-resize touch-none",
|
|
3052
|
-
onPointerDown: handleReviewSidebarResizePointerDown,
|
|
3053
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "pointer-events-none absolute top-1/2 left-1/2 h-16 w-[2px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-black/20 opacity-0 transition-opacity group-hover:opacity-100" })
|
|
3054
|
-
}
|
|
3055
|
-
) : null,
|
|
3056
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3057
|
-
HighlightsSidebar,
|
|
3058
|
-
{
|
|
3059
|
-
highlights,
|
|
3060
|
-
selectedId: selectedAnnotationId,
|
|
3061
|
-
currentPage,
|
|
3062
|
-
flashId: flashAnnotationId,
|
|
3063
|
-
onSelect: handleSelectHighlight,
|
|
3064
|
-
onDelete: handleDeleteHighlight,
|
|
3065
|
-
onUpdateKind: handleUpdateKind,
|
|
3066
|
-
onUpdateComment: handleUpdateComment,
|
|
3067
|
-
showOverlayHints,
|
|
3068
|
-
readOnly: isReadOnlyMode
|
|
3069
|
-
}
|
|
3070
|
-
)
|
|
3071
|
-
]
|
|
3072
|
-
}
|
|
3073
|
-
)
|
|
3074
|
-
] }),
|
|
3075
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3076
|
-
ArxivInfoModal,
|
|
3077
|
-
{
|
|
3078
|
-
open: infoOpen,
|
|
3079
|
-
onClose: () => setInfoOpen(false),
|
|
3080
|
-
paper: arxivPaper,
|
|
3081
|
-
errorCode: arxivError,
|
|
3082
|
-
onCopyBibtex: arxivPaper ? handleCopyBibtex : void 0,
|
|
3083
|
-
onOpenArxiv: arxivPaper ? handleOpenArxiv : void 0
|
|
3084
|
-
}
|
|
3085
|
-
)
|
|
3086
|
-
] });
|
|
3087
|
-
}
|
|
3088
|
-
function onPdfViewerActivate() {
|
|
3089
|
-
console.debug("[PdfViewerPlugin] Activated");
|
|
3090
|
-
}
|
|
3091
|
-
function onPdfViewerDeactivate() {
|
|
3092
|
-
console.debug("[PdfViewerPlugin] Deactivated");
|
|
3093
|
-
}
|
|
3094
|
-
|
|
3095
|
-
export { PdfViewerPlugin as default, onPdfViewerActivate, onPdfViewerDeactivate };
|