@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,4214 +0,0 @@
|
|
|
1
|
-
import { w as createLucideIcon, j as jsxRuntimeExports, aO as Button, m as ChevronDown, d as Check, b as cn, r as reactExports, bx as ArrowLeft, c6 as ArrowRight, c7 as Separator, c8 as ArrowUp, c9 as Trash2, u as useI18n, o as useToast, V as getFileContent, L as LoaderCircle, e as Copy, C as CircleAlert, f as useFileTreeStore, a7 as getQuestMarkdownContextFromFileId, ca as rewriteQuestMarkdownForDisplay, cb as rewriteQuestMarkdownForSave, cc as uploadQuestDocumentAsset, U as updateFileContent } from './index-D_E4281X.js';
|
|
2
|
-
import { P as PluginKey, a as Plugin, S as Slice, F as Fragment, T as TextSelection, A as AllSelection, N as NodeSelection, E as Extension, m as me, l as le, u as useCurrentEditor, w, b as P, f as findParentNodeClosestToPos, g as getEditorMarkdown, s as setEditorMarkdown, q, c as uploadNotebookAsset, r as resolveNotebookAssetUrl, d as defaultExtensions, J, U, I, e as T, B, O, h as S, K as K$1, G, i as ce, j as isSupportedNotebookAsset } from './NotebookEditor-C1kWaxKi.js';
|
|
3
|
-
import { a4 as BIT8, ai as BIT30, ab as min, $ as max, y as unexpectedCase, n as methodUnimplemented, aj as keys, u as every, H as setIfUndefined, D as create } from './function-B5QZkkHC.js';
|
|
4
|
-
import { t as toBase64, e as encodeAny, i as isBrowser, d as doc, s as snapshot, D as Doc, a as applyUpdateV2, f as findIndexSS, b as iterateDeletedStructs, c as typeListToArraySnapshot, S as Snapshot, U as UndoManager, g as createSnapshot, h as isDeleted, Y as YXmlElement, j as YXmlText, k as YText, o as oneOf, C as ContentString, l as ContentFormat, m as createDeleteSet, I as Item, n as createRelativePositionFromTypeIndex, R as RelativePosition, p as findRootTypeKey, q as createID, r as createAbsolutePositionFromRelativePosition, u as isParentOf, v as ContentType, w as encodeStateVector, x as applyUpdate, y as mergeUpdates } from './yjs-DncrqiZ8.js';
|
|
5
|
-
import { P as ProjectSyncClient } from './project-sync-C_ygLlVU.js';
|
|
6
|
-
import { S as SquareCheckBig } from './square-check-big-uUfyVsbD.js';
|
|
7
|
-
import { C as Code } from './code-B20Slj_w.js';
|
|
8
|
-
import { I as Image } from './image-DSeR_sDS.js';
|
|
9
|
-
import { P as Popover, a as PopoverTrigger, b as PopoverContent } from './popover-D3Gg_FoV.js';
|
|
10
|
-
import { T as Trash, A as ArrowDown } from './trash-CXvwwSe8.js';
|
|
11
|
-
import { S as Sigma } from './sigma-DEccaSgk.js';
|
|
12
|
-
import { u as useFileDiffOverlay } from './useFileDiffOverlay-B8eUAX0I.js';
|
|
13
|
-
import { F as FileDiffPanel } from './file-diff-panel-DK13YPql.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @license lucide-react v0.511.0 - ISC
|
|
17
|
-
*
|
|
18
|
-
* This source code is licensed under the ISC license.
|
|
19
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const __iconNode$f = [
|
|
24
|
-
[
|
|
25
|
-
"path",
|
|
26
|
-
{ d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8", key: "mg9rjx" }
|
|
27
|
-
]
|
|
28
|
-
];
|
|
29
|
-
const Bold = createLucideIcon("bold", __iconNode$f);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @license lucide-react v0.511.0 - ISC
|
|
33
|
-
*
|
|
34
|
-
* This source code is licensed under the ISC license.
|
|
35
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const __iconNode$e = [
|
|
40
|
-
["path", { d: "M4 12h8", key: "17cfdx" }],
|
|
41
|
-
["path", { d: "M4 18V6", key: "1rz3zl" }],
|
|
42
|
-
["path", { d: "M12 18V6", key: "zqpxq5" }],
|
|
43
|
-
["path", { d: "m17 12 3-2v8", key: "1hhhft" }]
|
|
44
|
-
];
|
|
45
|
-
const Heading1 = createLucideIcon("heading-1", __iconNode$e);
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @license lucide-react v0.511.0 - ISC
|
|
49
|
-
*
|
|
50
|
-
* This source code is licensed under the ISC license.
|
|
51
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const __iconNode$d = [
|
|
56
|
-
["path", { d: "M4 12h8", key: "17cfdx" }],
|
|
57
|
-
["path", { d: "M4 18V6", key: "1rz3zl" }],
|
|
58
|
-
["path", { d: "M12 18V6", key: "zqpxq5" }],
|
|
59
|
-
["path", { d: "M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1", key: "9jr5yi" }]
|
|
60
|
-
];
|
|
61
|
-
const Heading2 = createLucideIcon("heading-2", __iconNode$d);
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @license lucide-react v0.511.0 - ISC
|
|
65
|
-
*
|
|
66
|
-
* This source code is licensed under the ISC license.
|
|
67
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
68
|
-
*/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const __iconNode$c = [
|
|
72
|
-
["path", { d: "M4 12h8", key: "17cfdx" }],
|
|
73
|
-
["path", { d: "M4 18V6", key: "1rz3zl" }],
|
|
74
|
-
["path", { d: "M12 18V6", key: "zqpxq5" }],
|
|
75
|
-
["path", { d: "M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2", key: "68ncm8" }],
|
|
76
|
-
["path", { d: "M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2", key: "1ejuhz" }]
|
|
77
|
-
];
|
|
78
|
-
const Heading3 = createLucideIcon("heading-3", __iconNode$c);
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* @license lucide-react v0.511.0 - ISC
|
|
82
|
-
*
|
|
83
|
-
* This source code is licensed under the ISC license.
|
|
84
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
85
|
-
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const __iconNode$b = [
|
|
89
|
-
["line", { x1: "19", x2: "10", y1: "4", y2: "4", key: "15jd3p" }],
|
|
90
|
-
["line", { x1: "14", x2: "5", y1: "20", y2: "20", key: "bu0au3" }],
|
|
91
|
-
["line", { x1: "15", x2: "9", y1: "4", y2: "20", key: "uljnxc" }]
|
|
92
|
-
];
|
|
93
|
-
const Italic = createLucideIcon("italic", __iconNode$b);
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* @license lucide-react v0.511.0 - ISC
|
|
97
|
-
*
|
|
98
|
-
* This source code is licensed under the ISC license.
|
|
99
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
100
|
-
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const __iconNode$a = [
|
|
104
|
-
["path", { d: "M10 12h11", key: "6m4ad9" }],
|
|
105
|
-
["path", { d: "M10 18h11", key: "11hvi2" }],
|
|
106
|
-
["path", { d: "M10 6h11", key: "c7qv1k" }],
|
|
107
|
-
["path", { d: "M4 10h2", key: "16xx2s" }],
|
|
108
|
-
["path", { d: "M4 6h1v4", key: "cnovpq" }],
|
|
109
|
-
["path", { d: "M6 18H4c0-1 2-2 2-3s-1-1.5-2-1", key: "m9a95d" }]
|
|
110
|
-
];
|
|
111
|
-
const ListOrdered = createLucideIcon("list-ordered", __iconNode$a);
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* @license lucide-react v0.511.0 - ISC
|
|
115
|
-
*
|
|
116
|
-
* This source code is licensed under the ISC license.
|
|
117
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
118
|
-
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const __iconNode$9 = [
|
|
122
|
-
["path", { d: "M3 12h.01", key: "nlz23k" }],
|
|
123
|
-
["path", { d: "M3 18h.01", key: "1tta3j" }],
|
|
124
|
-
["path", { d: "M3 6h.01", key: "1rqtza" }],
|
|
125
|
-
["path", { d: "M8 12h13", key: "1za7za" }],
|
|
126
|
-
["path", { d: "M8 18h13", key: "1lx6n3" }],
|
|
127
|
-
["path", { d: "M8 6h13", key: "ik3vkj" }]
|
|
128
|
-
];
|
|
129
|
-
const List = createLucideIcon("list", __iconNode$9);
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* @license lucide-react v0.511.0 - ISC
|
|
133
|
-
*
|
|
134
|
-
* This source code is licensed under the ISC license.
|
|
135
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
136
|
-
*/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const __iconNode$8 = [
|
|
140
|
-
["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }],
|
|
141
|
-
["path", { d: "M12 7v6", key: "lw1j43" }],
|
|
142
|
-
["path", { d: "M9 10h6", key: "9gxzsh" }]
|
|
143
|
-
];
|
|
144
|
-
const MessageSquarePlus = createLucideIcon("message-square-plus", __iconNode$8);
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* @license lucide-react v0.511.0 - ISC
|
|
148
|
-
*
|
|
149
|
-
* This source code is licensed under the ISC license.
|
|
150
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
151
|
-
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const __iconNode$7 = [["path", { d: "M5 12h14", key: "1ays0h" }]];
|
|
155
|
-
const Minus = createLucideIcon("minus", __iconNode$7);
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* @license lucide-react v0.511.0 - ISC
|
|
159
|
-
*
|
|
160
|
-
* This source code is licensed under the ISC license.
|
|
161
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
162
|
-
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const __iconNode$6 = [
|
|
166
|
-
["path", { d: "M16 4H9a3 3 0 0 0-2.83 4", key: "43sutm" }],
|
|
167
|
-
["path", { d: "M14 12a4 4 0 0 1 0 8H6", key: "nlfj13" }],
|
|
168
|
-
["line", { x1: "4", x2: "20", y1: "12", y2: "12", key: "1e0a9i" }]
|
|
169
|
-
];
|
|
170
|
-
const Strikethrough = createLucideIcon("strikethrough", __iconNode$6);
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* @license lucide-react v0.511.0 - ISC
|
|
174
|
-
*
|
|
175
|
-
* This source code is licensed under the ISC license.
|
|
176
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
177
|
-
*/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const __iconNode$5 = [
|
|
181
|
-
[
|
|
182
|
-
"path",
|
|
183
|
-
{
|
|
184
|
-
d: "M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18",
|
|
185
|
-
key: "gugj83"
|
|
186
|
-
}
|
|
187
|
-
]
|
|
188
|
-
];
|
|
189
|
-
const Table2 = createLucideIcon("table-2", __iconNode$5);
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @license lucide-react v0.511.0 - ISC
|
|
193
|
-
*
|
|
194
|
-
* This source code is licensed under the ISC license.
|
|
195
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
196
|
-
*/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const __iconNode$4 = [
|
|
200
|
-
["path", { d: "M17 6H3", key: "16j9eg" }],
|
|
201
|
-
["path", { d: "M21 12H8", key: "scolzb" }],
|
|
202
|
-
["path", { d: "M21 18H8", key: "1wfozv" }],
|
|
203
|
-
["path", { d: "M3 12v6", key: "fv4c87" }]
|
|
204
|
-
];
|
|
205
|
-
const TextQuote = createLucideIcon("text-quote", __iconNode$4);
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* @license lucide-react v0.511.0 - ISC
|
|
209
|
-
*
|
|
210
|
-
* This source code is licensed under the ISC license.
|
|
211
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
212
|
-
*/
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const __iconNode$3 = [
|
|
216
|
-
["path", { d: "M15 18H3", key: "olowqp" }],
|
|
217
|
-
["path", { d: "M17 6H3", key: "16j9eg" }],
|
|
218
|
-
["path", { d: "M21 12H3", key: "2avoz0" }]
|
|
219
|
-
];
|
|
220
|
-
const Text = createLucideIcon("text", __iconNode$3);
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* @license lucide-react v0.511.0 - ISC
|
|
224
|
-
*
|
|
225
|
-
* This source code is licensed under the ISC license.
|
|
226
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
227
|
-
*/
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const __iconNode$2 = [
|
|
231
|
-
[
|
|
232
|
-
"path",
|
|
233
|
-
{
|
|
234
|
-
d: "M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z",
|
|
235
|
-
key: "pff0z6"
|
|
236
|
-
}
|
|
237
|
-
]
|
|
238
|
-
];
|
|
239
|
-
const Twitter = createLucideIcon("twitter", __iconNode$2);
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* @license lucide-react v0.511.0 - ISC
|
|
243
|
-
*
|
|
244
|
-
* This source code is licensed under the ISC license.
|
|
245
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
246
|
-
*/
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const __iconNode$1 = [
|
|
250
|
-
["path", { d: "M6 4v6a6 6 0 0 0 12 0V4", key: "9kb039" }],
|
|
251
|
-
["line", { x1: "4", x2: "20", y1: "20", y2: "20", key: "nun2al" }]
|
|
252
|
-
];
|
|
253
|
-
const Underline = createLucideIcon("underline", __iconNode$1);
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* @license lucide-react v0.511.0 - ISC
|
|
257
|
-
*
|
|
258
|
-
* This source code is licensed under the ISC license.
|
|
259
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
260
|
-
*/
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const __iconNode = [
|
|
264
|
-
[
|
|
265
|
-
"path",
|
|
266
|
-
{
|
|
267
|
-
d: "M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17",
|
|
268
|
-
key: "1q2vi4"
|
|
269
|
-
}
|
|
270
|
-
],
|
|
271
|
-
["path", { d: "m10 15 5-3-5-3z", key: "1jp15x" }]
|
|
272
|
-
];
|
|
273
|
-
const Youtube = createLucideIcon("youtube", __iconNode);
|
|
274
|
-
|
|
275
|
-
/* global requestIdleCallback, requestAnimationFrame, cancelIdleCallback, cancelAnimationFrame */
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* @typedef {Object} TimeoutObject
|
|
280
|
-
* @property {function} TimeoutObject.destroy
|
|
281
|
-
*/
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* @param {function(number):void} clearFunction
|
|
285
|
-
*/
|
|
286
|
-
const createTimeoutClass = clearFunction => class TT {
|
|
287
|
-
/**
|
|
288
|
-
* @param {number} timeoutId
|
|
289
|
-
*/
|
|
290
|
-
constructor (timeoutId) {
|
|
291
|
-
this._ = timeoutId;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
destroy () {
|
|
295
|
-
clearFunction(this._);
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const Timeout = createTimeoutClass(clearTimeout);
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* @param {number} timeout
|
|
303
|
-
* @param {function} callback
|
|
304
|
-
* @return {TimeoutObject}
|
|
305
|
-
*/
|
|
306
|
-
const timeout = (timeout, callback) => new Timeout(setTimeout(callback, timeout));
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Mutual exclude for JavaScript.
|
|
310
|
-
*
|
|
311
|
-
* @module mutex
|
|
312
|
-
*/
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* @callback mutex
|
|
316
|
-
* @param {function():void} cb Only executed when this mutex is not in the current stack
|
|
317
|
-
* @param {function():void} [elseCb] Executed when this mutex is in the current stack
|
|
318
|
-
*/
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Creates a mutual exclude function with the following property:
|
|
322
|
-
*
|
|
323
|
-
* ```js
|
|
324
|
-
* const mutex = createMutex()
|
|
325
|
-
* mutex(() => {
|
|
326
|
-
* // This function is immediately executed
|
|
327
|
-
* mutex(() => {
|
|
328
|
-
* // This function is not executed, as the mutex is already active.
|
|
329
|
-
* })
|
|
330
|
-
* })
|
|
331
|
-
* ```
|
|
332
|
-
*
|
|
333
|
-
* @return {mutex} A mutual exclude function
|
|
334
|
-
* @public
|
|
335
|
-
*/
|
|
336
|
-
const createMutex = () => {
|
|
337
|
-
let token = true;
|
|
338
|
-
return (f, g) => {
|
|
339
|
-
if (token) {
|
|
340
|
-
token = false;
|
|
341
|
-
try {
|
|
342
|
-
f();
|
|
343
|
-
} finally {
|
|
344
|
-
token = true;
|
|
345
|
-
}
|
|
346
|
-
} else if (g !== undefined) {
|
|
347
|
-
g();
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Efficient diffs.
|
|
354
|
-
*
|
|
355
|
-
* @module diff
|
|
356
|
-
*/
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* A SimpleDiff describes a change on a String.
|
|
361
|
-
*
|
|
362
|
-
* ```js
|
|
363
|
-
* console.log(a) // the old value
|
|
364
|
-
* console.log(b) // the updated value
|
|
365
|
-
* // Apply changes of diff (pseudocode)
|
|
366
|
-
* a.remove(diff.index, diff.remove) // Remove `diff.remove` characters
|
|
367
|
-
* a.insert(diff.index, diff.insert) // Insert `diff.insert`
|
|
368
|
-
* a === b // values match
|
|
369
|
-
* ```
|
|
370
|
-
*
|
|
371
|
-
* @template {string|Array<any>} T
|
|
372
|
-
* @typedef {Object} SimpleDiff
|
|
373
|
-
* @property {Number} index The index where changes were applied
|
|
374
|
-
* @property {Number} remove The number of characters to delete starting
|
|
375
|
-
* at `index`.
|
|
376
|
-
* @property {T} insert The new text to insert at `index` after applying
|
|
377
|
-
*/
|
|
378
|
-
|
|
379
|
-
const highSurrogateRegex = /[\uD800-\uDBFF]/;
|
|
380
|
-
const lowSurrogateRegex = /[\uDC00-\uDFFF]/;
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Create a diff between two strings. This diff implementation is highly
|
|
384
|
-
* efficient, but not very sophisticated.
|
|
385
|
-
*
|
|
386
|
-
* @function
|
|
387
|
-
*
|
|
388
|
-
* @param {string} a The old version of the string
|
|
389
|
-
* @param {string} b The updated version of the string
|
|
390
|
-
* @return {SimpleDiff<string>} The diff description.
|
|
391
|
-
*/
|
|
392
|
-
const simpleDiffString = (a, b) => {
|
|
393
|
-
let left = 0; // number of same characters counting from left
|
|
394
|
-
let right = 0; // number of same characters counting from right
|
|
395
|
-
while (left < a.length && left < b.length && a[left] === b[left]) {
|
|
396
|
-
left++;
|
|
397
|
-
}
|
|
398
|
-
// If the last same character is a high surrogate, we need to rollback to the previous character
|
|
399
|
-
if (left > 0 && highSurrogateRegex.test(a[left - 1])) left--;
|
|
400
|
-
while (right + left < a.length && right + left < b.length && a[a.length - right - 1] === b[b.length - right - 1]) {
|
|
401
|
-
right++;
|
|
402
|
-
}
|
|
403
|
-
// If the last same character is a low surrogate, we need to rollback to the previous character
|
|
404
|
-
if (right > 0 && lowSurrogateRegex.test(a[a.length - right])) right--;
|
|
405
|
-
return {
|
|
406
|
-
index: left,
|
|
407
|
-
remove: a.length - left - right,
|
|
408
|
-
insert: b.slice(left, b.length - right)
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* @todo Remove in favor of simpleDiffString
|
|
414
|
-
* @deprecated
|
|
415
|
-
*/
|
|
416
|
-
const simpleDiff = simpleDiffString;
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* The unique prosemirror plugin key for syncPlugin
|
|
420
|
-
*
|
|
421
|
-
* @public
|
|
422
|
-
*/
|
|
423
|
-
const ySyncPluginKey = new PluginKey('y-sync');
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* The unique prosemirror plugin key for undoPlugin
|
|
427
|
-
*
|
|
428
|
-
* @public
|
|
429
|
-
* @type {PluginKey<import('./undo-plugin').UndoPluginState>}
|
|
430
|
-
*/
|
|
431
|
-
const yUndoPluginKey = new PluginKey('y-undo');
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* The unique prosemirror plugin key for cursorPlugin
|
|
435
|
-
*
|
|
436
|
-
* @public
|
|
437
|
-
*/
|
|
438
|
-
new PluginKey('yjs-cursor');
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* @module sha256
|
|
442
|
-
* Spec: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
|
|
443
|
-
* Resources:
|
|
444
|
-
* - https://web.archive.org/web/20150315061807/http://csrc.nist.gov/groups/STM/cavp/documents/shs/sha256-384-512.pdf
|
|
445
|
-
*/
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* @param {number} w - a 32bit uint
|
|
450
|
-
* @param {number} shift
|
|
451
|
-
*/
|
|
452
|
-
const rotr = (w, shift) => (w >>> shift) | (w << (32 - shift));
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Helper for SHA-224 & SHA-256. See 4.1.2.
|
|
456
|
-
* @param {number} x
|
|
457
|
-
*/
|
|
458
|
-
const sum0to256 = x => rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Helper for SHA-224 & SHA-256. See 4.1.2.
|
|
462
|
-
* @param {number} x
|
|
463
|
-
*/
|
|
464
|
-
const sum1to256 = x => rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Helper for SHA-224 & SHA-256. See 4.1.2.
|
|
468
|
-
* @param {number} x
|
|
469
|
-
*/
|
|
470
|
-
const sigma0to256 = x => rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3;
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Helper for SHA-224 & SHA-256. See 4.1.2.
|
|
474
|
-
* @param {number} x
|
|
475
|
-
*/
|
|
476
|
-
const sigma1to256 = x => rotr(x, 17) ^ rotr(x, 19) ^ x >>> 10;
|
|
477
|
-
|
|
478
|
-
// @todo don't init these variables globally
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* See 4.2.2: Constant for sha256 & sha224
|
|
482
|
-
* These words represent the first thirty-two bits of the fractional parts of
|
|
483
|
-
* the cube roots of the first sixty-four prime numbers. In hex, these constant words are (from left to
|
|
484
|
-
* right)
|
|
485
|
-
*/
|
|
486
|
-
const K = new Uint32Array([
|
|
487
|
-
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
488
|
-
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
489
|
-
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
490
|
-
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
491
|
-
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
492
|
-
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
493
|
-
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
494
|
-
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
495
|
-
]);
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* See 5.3.3. Initial hash value.
|
|
499
|
-
*
|
|
500
|
-
* These words were obtained by taking the first thirty-two bits of the fractional parts of the
|
|
501
|
-
* square roots of the first eight prime numbers.
|
|
502
|
-
*
|
|
503
|
-
* @todo shouldn't be a global variable
|
|
504
|
-
*/
|
|
505
|
-
const HINIT = new Uint32Array([
|
|
506
|
-
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
|
507
|
-
]);
|
|
508
|
-
|
|
509
|
-
// time to beat: (large value < 4.35s)
|
|
510
|
-
|
|
511
|
-
class Hasher {
|
|
512
|
-
constructor () {
|
|
513
|
-
const buf = new ArrayBuffer(64 + 64 * 4);
|
|
514
|
-
// Init working variables using a single arraybuffer
|
|
515
|
-
this._H = new Uint32Array(buf, 0, 8);
|
|
516
|
-
this._H.set(HINIT);
|
|
517
|
-
// "Message schedule" - a working variable
|
|
518
|
-
this._W = new Uint32Array(buf, 64, 64);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
_updateHash () {
|
|
522
|
-
const H = this._H;
|
|
523
|
-
const W = this._W;
|
|
524
|
-
for (let t = 16; t < 64; t++) {
|
|
525
|
-
W[t] = sigma1to256(W[t - 2]) + W[t - 7] + sigma0to256(W[t - 15]) + W[t - 16];
|
|
526
|
-
}
|
|
527
|
-
let a = H[0];
|
|
528
|
-
let b = H[1];
|
|
529
|
-
let c = H[2];
|
|
530
|
-
let d = H[3];
|
|
531
|
-
let e = H[4];
|
|
532
|
-
let f = H[5];
|
|
533
|
-
let g = H[6];
|
|
534
|
-
let h = H[7];
|
|
535
|
-
for (let tt = 0, T1, T2; tt < 64; tt++) {
|
|
536
|
-
T1 = (h + sum1to256(e) + ((e & f) ^ (~e & g)) + K[tt] + W[tt]) >>> 0;
|
|
537
|
-
T2 = (sum0to256(a) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
|
|
538
|
-
h = g;
|
|
539
|
-
g = f;
|
|
540
|
-
f = e;
|
|
541
|
-
e = (d + T1) >>> 0;
|
|
542
|
-
d = c;
|
|
543
|
-
c = b;
|
|
544
|
-
b = a;
|
|
545
|
-
a = (T1 + T2) >>> 0;
|
|
546
|
-
}
|
|
547
|
-
H[0] += a;
|
|
548
|
-
H[1] += b;
|
|
549
|
-
H[2] += c;
|
|
550
|
-
H[3] += d;
|
|
551
|
-
H[4] += e;
|
|
552
|
-
H[5] += f;
|
|
553
|
-
H[6] += g;
|
|
554
|
-
H[7] += h;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* Returns a 32-byte hash.
|
|
559
|
-
*
|
|
560
|
-
* @param {Uint8Array} data
|
|
561
|
-
*/
|
|
562
|
-
digest (data) {
|
|
563
|
-
let i = 0;
|
|
564
|
-
for (; i + 56 <= data.length;) {
|
|
565
|
-
// write data in big endianess
|
|
566
|
-
let j = 0;
|
|
567
|
-
for (; j < 16 && i + 3 < data.length; j++) {
|
|
568
|
-
this._W[j] = data[i++] << 24 | data[i++] << 16 | data[i++] << 8 | data[i++];
|
|
569
|
-
}
|
|
570
|
-
if (i % 64 !== 0) { // there is still room to write partial content and the ending bit.
|
|
571
|
-
this._W.fill(0, j, 16);
|
|
572
|
-
while (i < data.length) {
|
|
573
|
-
this._W[j] |= data[i] << ((3 - (i % 4)) * 8);
|
|
574
|
-
i++;
|
|
575
|
-
}
|
|
576
|
-
this._W[j] |= BIT8 << ((3 - (i % 4)) * 8);
|
|
577
|
-
}
|
|
578
|
-
this._updateHash();
|
|
579
|
-
}
|
|
580
|
-
// same check as earlier - the ending bit has been written
|
|
581
|
-
const isPaddedWith1 = i % 64 !== 0;
|
|
582
|
-
this._W.fill(0, 0, 16);
|
|
583
|
-
let j = 0;
|
|
584
|
-
for (; i < data.length; j++) {
|
|
585
|
-
for (let ci = 3; ci >= 0 && i < data.length; ci--) {
|
|
586
|
-
this._W[j] |= data[i++] << (ci * 8);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
// Write padding of the message. See 5.1.2.
|
|
590
|
-
if (!isPaddedWith1) {
|
|
591
|
-
this._W[j - (i % 4 === 0 ? 0 : 1)] |= BIT8 << ((3 - (i % 4)) * 8);
|
|
592
|
-
}
|
|
593
|
-
// write length of message (size in bits) as 64 bit uint
|
|
594
|
-
// @todo test that this works correctly
|
|
595
|
-
this._W[14] = data.byteLength / BIT30; // same as data.byteLength >>> 30 - but works on floats
|
|
596
|
-
this._W[15] = data.byteLength * 8;
|
|
597
|
-
this._updateHash();
|
|
598
|
-
// correct H endianness to use big endiannes and return a Uint8Array
|
|
599
|
-
const dv = new Uint8Array(32);
|
|
600
|
-
for (let i = 0; i < this._H.length; i++) {
|
|
601
|
-
for (let ci = 0; ci < 4; ci++) {
|
|
602
|
-
dv[i * 4 + ci] = this._H[i] >>> (3 - ci) * 8;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
return dv
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* Returns a 32-byte hash.
|
|
611
|
-
*
|
|
612
|
-
* @param {Uint8Array} data
|
|
613
|
-
*/
|
|
614
|
-
const digest = data => new Hasher().digest(data);
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Custom function to transform sha256 hash to N byte
|
|
618
|
-
*
|
|
619
|
-
* @param {Uint8Array} digest
|
|
620
|
-
*/
|
|
621
|
-
const _convolute = digest => {
|
|
622
|
-
const N = 6;
|
|
623
|
-
for (let i = N; i < digest.length; i++) {
|
|
624
|
-
digest[i % N] = digest[i % N] ^ digest[i];
|
|
625
|
-
}
|
|
626
|
-
return digest.slice(0, N)
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* @param {any} json
|
|
631
|
-
*/
|
|
632
|
-
const hashOfJSON = (json) => toBase64(_convolute(digest(encodeAny(json))));
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* @module bindings/prosemirror
|
|
636
|
-
*/
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* @param {Y.Item} item
|
|
641
|
-
* @param {Y.Snapshot} [snapshot]
|
|
642
|
-
*/
|
|
643
|
-
const isVisible = (item, snapshot) =>
|
|
644
|
-
snapshot === undefined
|
|
645
|
-
? !item.deleted
|
|
646
|
-
: (snapshot.sv.has(item.id.client) && /** @type {number} */
|
|
647
|
-
(snapshot.sv.get(item.id.client)) > item.id.clock &&
|
|
648
|
-
!isDeleted(snapshot.ds, item.id));
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Either a node if type is YXmlElement or an Array of text nodes if YXmlText
|
|
652
|
-
* @typedef {Map<Y.AbstractType<any>, PModel.Node | Array<PModel.Node>>} ProsemirrorMapping
|
|
653
|
-
*/
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* @typedef {Object} ColorDef
|
|
657
|
-
* @property {string} ColorDef.light
|
|
658
|
-
* @property {string} ColorDef.dark
|
|
659
|
-
*/
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* @typedef {Object} YSyncOpts
|
|
663
|
-
* @property {Array<ColorDef>} [YSyncOpts.colors]
|
|
664
|
-
* @property {Map<string,ColorDef>} [YSyncOpts.colorMapping]
|
|
665
|
-
* @property {Y.PermanentUserData|null} [YSyncOpts.permanentUserData]
|
|
666
|
-
* @property {ProsemirrorMapping} [YSyncOpts.mapping]
|
|
667
|
-
* @property {function} [YSyncOpts.onFirstRender] Fired when the content from Yjs is initially rendered to ProseMirror
|
|
668
|
-
*/
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* @type {Array<ColorDef>}
|
|
672
|
-
*/
|
|
673
|
-
const defaultColors = [{ light: '#ecd44433', dark: '#ecd444' }];
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* @param {Map<string,ColorDef>} colorMapping
|
|
677
|
-
* @param {Array<ColorDef>} colors
|
|
678
|
-
* @param {string} user
|
|
679
|
-
* @return {ColorDef}
|
|
680
|
-
*/
|
|
681
|
-
const getUserColor = (colorMapping, colors, user) => {
|
|
682
|
-
// @todo do not hit the same color twice if possible
|
|
683
|
-
if (!colorMapping.has(user)) {
|
|
684
|
-
if (colorMapping.size < colors.length) {
|
|
685
|
-
const usedColors = create();
|
|
686
|
-
colorMapping.forEach((color) => usedColors.add(color));
|
|
687
|
-
colors = colors.filter((color) => !usedColors.has(color));
|
|
688
|
-
}
|
|
689
|
-
colorMapping.set(user, oneOf(colors));
|
|
690
|
-
}
|
|
691
|
-
return /** @type {ColorDef} */ (colorMapping.get(user))
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* This plugin listens to changes in prosemirror view and keeps yXmlState and view in sync.
|
|
696
|
-
*
|
|
697
|
-
* This plugin also keeps references to the type and the shared document so other plugins can access it.
|
|
698
|
-
* @param {Y.XmlFragment} yXmlFragment
|
|
699
|
-
* @param {YSyncOpts} opts
|
|
700
|
-
* @return {any} Returns a prosemirror plugin that binds to this type
|
|
701
|
-
*/
|
|
702
|
-
const ySyncPlugin = (yXmlFragment, {
|
|
703
|
-
colors = defaultColors,
|
|
704
|
-
colorMapping = new Map(),
|
|
705
|
-
permanentUserData = null,
|
|
706
|
-
onFirstRender = () => {},
|
|
707
|
-
mapping
|
|
708
|
-
} = {}) => {
|
|
709
|
-
let initialContentChanged = false;
|
|
710
|
-
const binding = new ProsemirrorBinding(yXmlFragment, mapping);
|
|
711
|
-
const plugin = new Plugin({
|
|
712
|
-
props: {
|
|
713
|
-
editable: (state) => {
|
|
714
|
-
const syncState = ySyncPluginKey.getState(state);
|
|
715
|
-
return syncState.snapshot == null && syncState.prevSnapshot == null
|
|
716
|
-
}
|
|
717
|
-
},
|
|
718
|
-
key: ySyncPluginKey,
|
|
719
|
-
state: {
|
|
720
|
-
/**
|
|
721
|
-
* @returns {any}
|
|
722
|
-
*/
|
|
723
|
-
init: (_initargs, _state) => {
|
|
724
|
-
return {
|
|
725
|
-
type: yXmlFragment,
|
|
726
|
-
doc: yXmlFragment.doc,
|
|
727
|
-
binding,
|
|
728
|
-
snapshot: null,
|
|
729
|
-
prevSnapshot: null,
|
|
730
|
-
isChangeOrigin: false,
|
|
731
|
-
isUndoRedoOperation: false,
|
|
732
|
-
addToHistory: true,
|
|
733
|
-
colors,
|
|
734
|
-
colorMapping,
|
|
735
|
-
permanentUserData
|
|
736
|
-
}
|
|
737
|
-
},
|
|
738
|
-
apply: (tr, pluginState) => {
|
|
739
|
-
const change = tr.getMeta(ySyncPluginKey);
|
|
740
|
-
if (change !== undefined) {
|
|
741
|
-
pluginState = Object.assign({}, pluginState);
|
|
742
|
-
for (const key in change) {
|
|
743
|
-
pluginState[key] = change[key];
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
pluginState.addToHistory = tr.getMeta('addToHistory') !== false;
|
|
747
|
-
// always set isChangeOrigin. If undefined, this is not change origin.
|
|
748
|
-
pluginState.isChangeOrigin = change !== undefined &&
|
|
749
|
-
!!change.isChangeOrigin;
|
|
750
|
-
pluginState.isUndoRedoOperation = change !== undefined && !!change.isChangeOrigin && !!change.isUndoRedoOperation;
|
|
751
|
-
if (binding.prosemirrorView !== null) {
|
|
752
|
-
if (
|
|
753
|
-
change !== undefined &&
|
|
754
|
-
(change.snapshot != null || change.prevSnapshot != null)
|
|
755
|
-
) {
|
|
756
|
-
// snapshot changed, rerender next
|
|
757
|
-
timeout(0, () => {
|
|
758
|
-
if (binding.prosemirrorView == null) {
|
|
759
|
-
return
|
|
760
|
-
}
|
|
761
|
-
if (change.restore == null) {
|
|
762
|
-
binding._renderSnapshot(
|
|
763
|
-
change.snapshot,
|
|
764
|
-
change.prevSnapshot,
|
|
765
|
-
pluginState
|
|
766
|
-
);
|
|
767
|
-
} else {
|
|
768
|
-
binding._renderSnapshot(
|
|
769
|
-
change.snapshot,
|
|
770
|
-
change.snapshot,
|
|
771
|
-
pluginState
|
|
772
|
-
);
|
|
773
|
-
// reset to current prosemirror state
|
|
774
|
-
delete pluginState.restore;
|
|
775
|
-
delete pluginState.snapshot;
|
|
776
|
-
delete pluginState.prevSnapshot;
|
|
777
|
-
binding.mux(() => {
|
|
778
|
-
binding._prosemirrorChanged(
|
|
779
|
-
binding.prosemirrorView.state.doc
|
|
780
|
-
);
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
return pluginState
|
|
787
|
-
}
|
|
788
|
-
},
|
|
789
|
-
view: (view) => {
|
|
790
|
-
binding.initView(view);
|
|
791
|
-
if (mapping == null) {
|
|
792
|
-
// force rerender to update the bindings mapping
|
|
793
|
-
binding._forceRerender();
|
|
794
|
-
}
|
|
795
|
-
onFirstRender();
|
|
796
|
-
return {
|
|
797
|
-
update: () => {
|
|
798
|
-
const pluginState = plugin.getState(view.state);
|
|
799
|
-
if (
|
|
800
|
-
pluginState.snapshot == null && pluginState.prevSnapshot == null
|
|
801
|
-
) {
|
|
802
|
-
if (
|
|
803
|
-
// If the content doesn't change initially, we don't render anything to Yjs
|
|
804
|
-
// If the content was cleared by a user action, we want to catch the change and
|
|
805
|
-
// represent it in Yjs
|
|
806
|
-
initialContentChanged ||
|
|
807
|
-
view.state.doc.content.findDiffStart(
|
|
808
|
-
view.state.doc.type.createAndFill().content
|
|
809
|
-
) !== null
|
|
810
|
-
) {
|
|
811
|
-
initialContentChanged = true;
|
|
812
|
-
if (
|
|
813
|
-
pluginState.addToHistory === false &&
|
|
814
|
-
!pluginState.isChangeOrigin
|
|
815
|
-
) {
|
|
816
|
-
const yUndoPluginState = yUndoPluginKey.getState(view.state);
|
|
817
|
-
/**
|
|
818
|
-
* @type {Y.UndoManager}
|
|
819
|
-
*/
|
|
820
|
-
const um = yUndoPluginState && yUndoPluginState.undoManager;
|
|
821
|
-
if (um) {
|
|
822
|
-
um.stopCapturing();
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
binding.mux(() => {
|
|
826
|
-
/** @type {Y.Doc} */ (pluginState.doc).transact((tr) => {
|
|
827
|
-
tr.meta.set('addToHistory', pluginState.addToHistory);
|
|
828
|
-
binding._prosemirrorChanged(view.state.doc);
|
|
829
|
-
}, ySyncPluginKey);
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
},
|
|
834
|
-
destroy: () => {
|
|
835
|
-
binding.destroy();
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
});
|
|
840
|
-
return plugin
|
|
841
|
-
};
|
|
842
|
-
|
|
843
|
-
/**
|
|
844
|
-
* @param {import('prosemirror-state').Transaction} tr
|
|
845
|
-
* @param {ReturnType<typeof getRelativeSelection>} relSel
|
|
846
|
-
* @param {ProsemirrorBinding} binding
|
|
847
|
-
*/
|
|
848
|
-
const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
849
|
-
if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
|
|
850
|
-
if (relSel.type === 'all') {
|
|
851
|
-
tr.setSelection(new AllSelection(tr.doc));
|
|
852
|
-
} else if (relSel.type === 'node') {
|
|
853
|
-
const anchor = relativePositionToAbsolutePosition(
|
|
854
|
-
binding.doc,
|
|
855
|
-
binding.type,
|
|
856
|
-
relSel.anchor,
|
|
857
|
-
binding.mapping
|
|
858
|
-
);
|
|
859
|
-
tr.setSelection(NodeSelection.create(tr.doc, anchor));
|
|
860
|
-
} else {
|
|
861
|
-
const anchor = relativePositionToAbsolutePosition(
|
|
862
|
-
binding.doc,
|
|
863
|
-
binding.type,
|
|
864
|
-
relSel.anchor,
|
|
865
|
-
binding.mapping
|
|
866
|
-
);
|
|
867
|
-
const head = relativePositionToAbsolutePosition(
|
|
868
|
-
binding.doc,
|
|
869
|
-
binding.type,
|
|
870
|
-
relSel.head,
|
|
871
|
-
binding.mapping
|
|
872
|
-
);
|
|
873
|
-
if (anchor !== null && head !== null) {
|
|
874
|
-
const sel = TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head));
|
|
875
|
-
tr.setSelection(sel);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* @param {ProsemirrorBinding} pmbinding
|
|
883
|
-
* @param {import('prosemirror-state').EditorState} state
|
|
884
|
-
*/
|
|
885
|
-
const getRelativeSelection = (pmbinding, state) => ({
|
|
886
|
-
type: /** @type {any} */ (state.selection).jsonID,
|
|
887
|
-
anchor: absolutePositionToRelativePosition(
|
|
888
|
-
state.selection.anchor,
|
|
889
|
-
pmbinding.type,
|
|
890
|
-
pmbinding.mapping
|
|
891
|
-
),
|
|
892
|
-
head: absolutePositionToRelativePosition(
|
|
893
|
-
state.selection.head,
|
|
894
|
-
pmbinding.type,
|
|
895
|
-
pmbinding.mapping
|
|
896
|
-
)
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
/**
|
|
900
|
-
* Binding for prosemirror.
|
|
901
|
-
*
|
|
902
|
-
* @protected
|
|
903
|
-
*/
|
|
904
|
-
class ProsemirrorBinding {
|
|
905
|
-
/**
|
|
906
|
-
* @param {Y.XmlFragment} yXmlFragment The bind source
|
|
907
|
-
* @param {ProsemirrorMapping} mapping
|
|
908
|
-
*/
|
|
909
|
-
constructor (yXmlFragment, mapping = new Map()) {
|
|
910
|
-
this.type = yXmlFragment;
|
|
911
|
-
/**
|
|
912
|
-
* this will be set once the view is created
|
|
913
|
-
* @type {any}
|
|
914
|
-
*/
|
|
915
|
-
this.prosemirrorView = null;
|
|
916
|
-
this.mux = createMutex();
|
|
917
|
-
this.mapping = mapping;
|
|
918
|
-
/**
|
|
919
|
-
* Is overlapping mark - i.e. mark does not exclude itself.
|
|
920
|
-
*
|
|
921
|
-
* @type {Map<import('prosemirror-model').MarkType, boolean>}
|
|
922
|
-
*/
|
|
923
|
-
this.isOMark = new Map();
|
|
924
|
-
this._observeFunction = this._typeChanged.bind(this);
|
|
925
|
-
/**
|
|
926
|
-
* @type {Y.Doc}
|
|
927
|
-
*/
|
|
928
|
-
// @ts-ignore
|
|
929
|
-
this.doc = yXmlFragment.doc;
|
|
930
|
-
/**
|
|
931
|
-
* current selection as relative positions in the Yjs model
|
|
932
|
-
*/
|
|
933
|
-
this.beforeTransactionSelection = null;
|
|
934
|
-
this.beforeAllTransactions = () => {
|
|
935
|
-
if (this.beforeTransactionSelection === null && this.prosemirrorView != null) {
|
|
936
|
-
this.beforeTransactionSelection = getRelativeSelection(
|
|
937
|
-
this,
|
|
938
|
-
this.prosemirrorView.state
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
};
|
|
942
|
-
this.afterAllTransactions = () => {
|
|
943
|
-
this.beforeTransactionSelection = null;
|
|
944
|
-
};
|
|
945
|
-
this._domSelectionInView = null;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
* Create a transaction for changing the prosemirror state.
|
|
950
|
-
*
|
|
951
|
-
* @returns
|
|
952
|
-
*/
|
|
953
|
-
get _tr () {
|
|
954
|
-
return this.prosemirrorView.state.tr.setMeta('addToHistory', false)
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
_isLocalCursorInView () {
|
|
958
|
-
if (!this.prosemirrorView.hasFocus()) return false
|
|
959
|
-
if (isBrowser && this._domSelectionInView === null) {
|
|
960
|
-
// Calculate the domSelectionInView and clear by next tick after all events are finished
|
|
961
|
-
timeout(0, () => {
|
|
962
|
-
this._domSelectionInView = null;
|
|
963
|
-
});
|
|
964
|
-
this._domSelectionInView = this._isDomSelectionInView();
|
|
965
|
-
}
|
|
966
|
-
return this._domSelectionInView
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
_isDomSelectionInView () {
|
|
970
|
-
const selection = this.prosemirrorView._root.getSelection();
|
|
971
|
-
|
|
972
|
-
if (selection == null || selection.anchorNode == null) return false
|
|
973
|
-
|
|
974
|
-
const range = this.prosemirrorView._root.createRange();
|
|
975
|
-
range.setStart(selection.anchorNode, selection.anchorOffset);
|
|
976
|
-
range.setEnd(selection.focusNode, selection.focusOffset);
|
|
977
|
-
|
|
978
|
-
// This is a workaround for an edgecase where getBoundingClientRect will
|
|
979
|
-
// return zero values if the selection is collapsed at the start of a newline
|
|
980
|
-
// see reference here: https://stackoverflow.com/a/59780954
|
|
981
|
-
const rects = range.getClientRects();
|
|
982
|
-
if (rects.length === 0) {
|
|
983
|
-
// probably buggy newline behavior, explicitly select the node contents
|
|
984
|
-
if (range.startContainer && range.collapsed) {
|
|
985
|
-
range.selectNodeContents(range.startContainer);
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
const bounding = range.getBoundingClientRect();
|
|
990
|
-
const documentElement = doc.documentElement;
|
|
991
|
-
|
|
992
|
-
return bounding.bottom >= 0 && bounding.right >= 0 &&
|
|
993
|
-
bounding.left <=
|
|
994
|
-
(window.innerWidth || documentElement.clientWidth || 0) &&
|
|
995
|
-
bounding.top <= (window.innerHeight || documentElement.clientHeight || 0)
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
/**
|
|
999
|
-
* @param {Y.Snapshot} snapshot
|
|
1000
|
-
* @param {Y.Snapshot} prevSnapshot
|
|
1001
|
-
*/
|
|
1002
|
-
renderSnapshot (snapshot, prevSnapshot) {
|
|
1003
|
-
if (!prevSnapshot) {
|
|
1004
|
-
prevSnapshot = createSnapshot(createDeleteSet(), new Map());
|
|
1005
|
-
}
|
|
1006
|
-
this.prosemirrorView.dispatch(
|
|
1007
|
-
this._tr.setMeta(ySyncPluginKey, { snapshot, prevSnapshot })
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
unrenderSnapshot () {
|
|
1012
|
-
this.mapping.clear();
|
|
1013
|
-
this.mux(() => {
|
|
1014
|
-
const fragmentContent = this.type.toArray().map((t) =>
|
|
1015
|
-
createNodeFromYElement(
|
|
1016
|
-
/** @type {Y.XmlElement} */ (t),
|
|
1017
|
-
this.prosemirrorView.state.schema,
|
|
1018
|
-
this
|
|
1019
|
-
)
|
|
1020
|
-
).filter((n) => n !== null);
|
|
1021
|
-
// @ts-ignore
|
|
1022
|
-
const tr = this._tr.replace(
|
|
1023
|
-
0,
|
|
1024
|
-
this.prosemirrorView.state.doc.content.size,
|
|
1025
|
-
new Slice(Fragment.from(fragmentContent), 0, 0)
|
|
1026
|
-
);
|
|
1027
|
-
tr.setMeta(ySyncPluginKey, { snapshot: null, prevSnapshot: null });
|
|
1028
|
-
this.prosemirrorView.dispatch(tr);
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
_forceRerender () {
|
|
1033
|
-
this.mapping.clear();
|
|
1034
|
-
this.mux(() => {
|
|
1035
|
-
// If this is a forced rerender, this might neither happen as a pm change nor within a Yjs
|
|
1036
|
-
// transaction. Then the "before selection" doesn't exist. In this case, we need to create a
|
|
1037
|
-
// relative position before replacing content. Fixes #126
|
|
1038
|
-
const sel = this.beforeTransactionSelection !== null ? null : this.prosemirrorView.state.selection;
|
|
1039
|
-
const fragmentContent = this.type.toArray().map((t) =>
|
|
1040
|
-
createNodeFromYElement(
|
|
1041
|
-
/** @type {Y.XmlElement} */ (t),
|
|
1042
|
-
this.prosemirrorView.state.schema,
|
|
1043
|
-
this
|
|
1044
|
-
)
|
|
1045
|
-
).filter((n) => n !== null);
|
|
1046
|
-
// @ts-ignore
|
|
1047
|
-
const tr = this._tr.replace(
|
|
1048
|
-
0,
|
|
1049
|
-
this.prosemirrorView.state.doc.content.size,
|
|
1050
|
-
new Slice(Fragment.from(fragmentContent), 0, 0)
|
|
1051
|
-
);
|
|
1052
|
-
if (sel) {
|
|
1053
|
-
/**
|
|
1054
|
-
* If the Prosemirror document we just created from this.type is
|
|
1055
|
-
* smaller than the previous document, the selection might be
|
|
1056
|
-
* out of bound, which would make Prosemirror throw an error.
|
|
1057
|
-
*/
|
|
1058
|
-
const clampedAnchor = min(max(sel.anchor, 0), tr.doc.content.size);
|
|
1059
|
-
const clampedHead = min(max(sel.head, 0), tr.doc.content.size);
|
|
1060
|
-
|
|
1061
|
-
tr.setSelection(TextSelection.create(tr.doc, clampedAnchor, clampedHead));
|
|
1062
|
-
}
|
|
1063
|
-
this.prosemirrorView.dispatch(
|
|
1064
|
-
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, binding: this })
|
|
1065
|
-
);
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
/**
|
|
1070
|
-
* @param {Y.Snapshot|Uint8Array} snapshot
|
|
1071
|
-
* @param {Y.Snapshot|Uint8Array} prevSnapshot
|
|
1072
|
-
* @param {Object} pluginState
|
|
1073
|
-
*/
|
|
1074
|
-
_renderSnapshot (snapshot$1, prevSnapshot, pluginState) {
|
|
1075
|
-
/**
|
|
1076
|
-
* The document that contains the full history of this document.
|
|
1077
|
-
* @type {Y.Doc}
|
|
1078
|
-
*/
|
|
1079
|
-
let historyDoc = this.doc;
|
|
1080
|
-
let historyType = this.type;
|
|
1081
|
-
if (!snapshot$1) {
|
|
1082
|
-
snapshot$1 = snapshot(this.doc);
|
|
1083
|
-
}
|
|
1084
|
-
if (snapshot$1 instanceof Uint8Array || prevSnapshot instanceof Uint8Array) {
|
|
1085
|
-
if (!(snapshot$1 instanceof Uint8Array) || !(prevSnapshot instanceof Uint8Array)) {
|
|
1086
|
-
// expected both snapshots to be v2 updates
|
|
1087
|
-
unexpectedCase();
|
|
1088
|
-
}
|
|
1089
|
-
historyDoc = new Doc({ gc: false });
|
|
1090
|
-
applyUpdateV2(historyDoc, prevSnapshot);
|
|
1091
|
-
prevSnapshot = snapshot(historyDoc);
|
|
1092
|
-
applyUpdateV2(historyDoc, snapshot$1);
|
|
1093
|
-
snapshot$1 = snapshot(historyDoc);
|
|
1094
|
-
if (historyType._item === null) {
|
|
1095
|
-
/**
|
|
1096
|
-
* If is a root type, we need to find the root key in the initial document
|
|
1097
|
-
* and use it to get the history type.
|
|
1098
|
-
*/
|
|
1099
|
-
const rootKey = Array.from(this.doc.share.keys()).find(
|
|
1100
|
-
(key) => this.doc.share.get(key) === this.type
|
|
1101
|
-
);
|
|
1102
|
-
historyType = historyDoc.getXmlFragment(rootKey);
|
|
1103
|
-
} else {
|
|
1104
|
-
/**
|
|
1105
|
-
* If it is a sub type, we use the item id to find the history type.
|
|
1106
|
-
*/
|
|
1107
|
-
const historyStructs =
|
|
1108
|
-
historyDoc.store.clients.get(historyType._item.id.client) ?? [];
|
|
1109
|
-
const itemIndex = findIndexSS(
|
|
1110
|
-
historyStructs,
|
|
1111
|
-
historyType._item.id.clock
|
|
1112
|
-
);
|
|
1113
|
-
const item = /** @type {Y.Item} */ (historyStructs[itemIndex]);
|
|
1114
|
-
const content = /** @type {Y.ContentType} */ (item.content);
|
|
1115
|
-
historyType = /** @type {Y.XmlFragment} */ (content.type);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
// clear mapping because we are going to rerender
|
|
1119
|
-
this.mapping.clear();
|
|
1120
|
-
this.mux(() => {
|
|
1121
|
-
historyDoc.transact((transaction) => {
|
|
1122
|
-
// before rendering, we are going to sanitize ops and split deleted ops
|
|
1123
|
-
// if they were deleted by seperate users.
|
|
1124
|
-
/**
|
|
1125
|
-
* @type {Y.PermanentUserData}
|
|
1126
|
-
*/
|
|
1127
|
-
const pud = pluginState.permanentUserData;
|
|
1128
|
-
if (pud) {
|
|
1129
|
-
pud.dss.forEach((ds) => {
|
|
1130
|
-
iterateDeletedStructs(transaction, ds, (_item) => {});
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* @param {'removed'|'added'} type
|
|
1135
|
-
* @param {Y.ID} id
|
|
1136
|
-
*/
|
|
1137
|
-
const computeYChange = (type, id) => {
|
|
1138
|
-
const user = type === 'added'
|
|
1139
|
-
? pud.getUserByClientId(id.client)
|
|
1140
|
-
: pud.getUserByDeletedId(id);
|
|
1141
|
-
return {
|
|
1142
|
-
user,
|
|
1143
|
-
type,
|
|
1144
|
-
color: getUserColor(
|
|
1145
|
-
pluginState.colorMapping,
|
|
1146
|
-
pluginState.colors,
|
|
1147
|
-
user
|
|
1148
|
-
)
|
|
1149
|
-
}
|
|
1150
|
-
};
|
|
1151
|
-
// Create document fragment and render
|
|
1152
|
-
const fragmentContent = typeListToArraySnapshot(
|
|
1153
|
-
historyType,
|
|
1154
|
-
new Snapshot(prevSnapshot.ds, snapshot$1.sv)
|
|
1155
|
-
).map((t) => {
|
|
1156
|
-
if (
|
|
1157
|
-
!t._item.deleted || isVisible(t._item, snapshot$1) ||
|
|
1158
|
-
isVisible(t._item, prevSnapshot)
|
|
1159
|
-
) {
|
|
1160
|
-
return createNodeFromYElement(
|
|
1161
|
-
t,
|
|
1162
|
-
this.prosemirrorView.state.schema,
|
|
1163
|
-
{ mapping: new Map(), isOMark: new Map() },
|
|
1164
|
-
snapshot$1,
|
|
1165
|
-
prevSnapshot,
|
|
1166
|
-
computeYChange
|
|
1167
|
-
)
|
|
1168
|
-
} else {
|
|
1169
|
-
// No need to render elements that are not visible by either snapshot.
|
|
1170
|
-
// If a client adds and deletes content in the same snapshot the element is not visible by either snapshot.
|
|
1171
|
-
return null
|
|
1172
|
-
}
|
|
1173
|
-
}).filter((n) => n !== null);
|
|
1174
|
-
// @ts-ignore
|
|
1175
|
-
const tr = this._tr.replace(
|
|
1176
|
-
0,
|
|
1177
|
-
this.prosemirrorView.state.doc.content.size,
|
|
1178
|
-
new Slice(Fragment.from(fragmentContent), 0, 0)
|
|
1179
|
-
);
|
|
1180
|
-
this.prosemirrorView.dispatch(
|
|
1181
|
-
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true })
|
|
1182
|
-
);
|
|
1183
|
-
}, ySyncPluginKey);
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
/**
|
|
1188
|
-
* @param {Array<Y.YEvent<any>>} events
|
|
1189
|
-
* @param {Y.Transaction} transaction
|
|
1190
|
-
*/
|
|
1191
|
-
_typeChanged (events, transaction) {
|
|
1192
|
-
if (this.prosemirrorView == null) return
|
|
1193
|
-
const syncState = ySyncPluginKey.getState(this.prosemirrorView.state);
|
|
1194
|
-
if (
|
|
1195
|
-
events.length === 0 || syncState.snapshot != null ||
|
|
1196
|
-
syncState.prevSnapshot != null
|
|
1197
|
-
) {
|
|
1198
|
-
// drop out if snapshot is active
|
|
1199
|
-
this.renderSnapshot(syncState.snapshot, syncState.prevSnapshot);
|
|
1200
|
-
return
|
|
1201
|
-
}
|
|
1202
|
-
this.mux(() => {
|
|
1203
|
-
/**
|
|
1204
|
-
* @param {any} _
|
|
1205
|
-
* @param {Y.AbstractType<any>} type
|
|
1206
|
-
*/
|
|
1207
|
-
const delType = (_, type) => this.mapping.delete(type);
|
|
1208
|
-
iterateDeletedStructs(
|
|
1209
|
-
transaction,
|
|
1210
|
-
transaction.deleteSet,
|
|
1211
|
-
(struct) => {
|
|
1212
|
-
if (struct.constructor === Item) {
|
|
1213
|
-
const type = /** @type {Y.ContentType} */ (/** @type {Y.Item} */ (struct).content).type;
|
|
1214
|
-
type && this.mapping.delete(type);
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
);
|
|
1218
|
-
transaction.changed.forEach(delType);
|
|
1219
|
-
transaction.changedParentTypes.forEach(delType);
|
|
1220
|
-
const fragmentContent = this.type.toArray().map((t) =>
|
|
1221
|
-
createNodeIfNotExists(
|
|
1222
|
-
/** @type {Y.XmlElement | Y.XmlHook} */ (t),
|
|
1223
|
-
this.prosemirrorView.state.schema,
|
|
1224
|
-
this
|
|
1225
|
-
)
|
|
1226
|
-
).filter((n) => n !== null);
|
|
1227
|
-
// @ts-ignore
|
|
1228
|
-
let tr = this._tr.replace(
|
|
1229
|
-
0,
|
|
1230
|
-
this.prosemirrorView.state.doc.content.size,
|
|
1231
|
-
new Slice(Fragment.from(fragmentContent), 0, 0)
|
|
1232
|
-
);
|
|
1233
|
-
restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
|
|
1234
|
-
tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, isUndoRedoOperation: transaction.origin instanceof UndoManager });
|
|
1235
|
-
if (
|
|
1236
|
-
this.beforeTransactionSelection !== null && this._isLocalCursorInView()
|
|
1237
|
-
) {
|
|
1238
|
-
tr.scrollIntoView();
|
|
1239
|
-
}
|
|
1240
|
-
this.prosemirrorView.dispatch(tr);
|
|
1241
|
-
});
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
/**
|
|
1245
|
-
* @param {import('prosemirror-model').Node} doc
|
|
1246
|
-
*/
|
|
1247
|
-
_prosemirrorChanged (doc) {
|
|
1248
|
-
this.doc.transact(() => {
|
|
1249
|
-
updateYFragment(this.doc, this.type, doc, this);
|
|
1250
|
-
this.beforeTransactionSelection = getRelativeSelection(
|
|
1251
|
-
this,
|
|
1252
|
-
this.prosemirrorView.state
|
|
1253
|
-
);
|
|
1254
|
-
}, ySyncPluginKey);
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
/**
|
|
1258
|
-
* View is ready to listen to changes. Register observers.
|
|
1259
|
-
* @param {any} prosemirrorView
|
|
1260
|
-
*/
|
|
1261
|
-
initView (prosemirrorView) {
|
|
1262
|
-
if (this.prosemirrorView != null) this.destroy();
|
|
1263
|
-
this.prosemirrorView = prosemirrorView;
|
|
1264
|
-
this.doc.on('beforeAllTransactions', this.beforeAllTransactions);
|
|
1265
|
-
this.doc.on('afterAllTransactions', this.afterAllTransactions);
|
|
1266
|
-
this.type.observeDeep(this._observeFunction);
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
destroy () {
|
|
1270
|
-
if (this.prosemirrorView == null) return
|
|
1271
|
-
this.prosemirrorView = null;
|
|
1272
|
-
this.type.unobserveDeep(this._observeFunction);
|
|
1273
|
-
this.doc.off('beforeAllTransactions', this.beforeAllTransactions);
|
|
1274
|
-
this.doc.off('afterAllTransactions', this.afterAllTransactions);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
/**
|
|
1279
|
-
* @private
|
|
1280
|
-
* @param {Y.XmlElement | Y.XmlHook} el
|
|
1281
|
-
* @param {PModel.Schema} schema
|
|
1282
|
-
* @param {BindingMetadata} meta
|
|
1283
|
-
* @param {Y.Snapshot} [snapshot]
|
|
1284
|
-
* @param {Y.Snapshot} [prevSnapshot]
|
|
1285
|
-
* @param {function('removed' | 'added', Y.ID):any} [computeYChange]
|
|
1286
|
-
* @return {PModel.Node | null}
|
|
1287
|
-
*/
|
|
1288
|
-
const createNodeIfNotExists = (
|
|
1289
|
-
el,
|
|
1290
|
-
schema,
|
|
1291
|
-
meta,
|
|
1292
|
-
snapshot,
|
|
1293
|
-
prevSnapshot,
|
|
1294
|
-
computeYChange
|
|
1295
|
-
) => {
|
|
1296
|
-
const node = /** @type {PModel.Node} */ (meta.mapping.get(el));
|
|
1297
|
-
if (node === undefined) {
|
|
1298
|
-
if (el instanceof YXmlElement) {
|
|
1299
|
-
return createNodeFromYElement(
|
|
1300
|
-
el,
|
|
1301
|
-
schema,
|
|
1302
|
-
meta,
|
|
1303
|
-
snapshot,
|
|
1304
|
-
prevSnapshot,
|
|
1305
|
-
computeYChange
|
|
1306
|
-
)
|
|
1307
|
-
} else {
|
|
1308
|
-
throw methodUnimplemented() // we are currently not handling hooks
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
return node
|
|
1312
|
-
};
|
|
1313
|
-
|
|
1314
|
-
/**
|
|
1315
|
-
* @private
|
|
1316
|
-
* @param {Y.XmlElement} el
|
|
1317
|
-
* @param {any} schema
|
|
1318
|
-
* @param {BindingMetadata} meta
|
|
1319
|
-
* @param {Y.Snapshot} [snapshot]
|
|
1320
|
-
* @param {Y.Snapshot} [prevSnapshot]
|
|
1321
|
-
* @param {function('removed' | 'added', Y.ID):any} [computeYChange]
|
|
1322
|
-
* @return {PModel.Node | null} Returns node if node could be created. Otherwise it deletes the yjs type and returns null
|
|
1323
|
-
*/
|
|
1324
|
-
const createNodeFromYElement = (
|
|
1325
|
-
el,
|
|
1326
|
-
schema,
|
|
1327
|
-
meta,
|
|
1328
|
-
snapshot,
|
|
1329
|
-
prevSnapshot,
|
|
1330
|
-
computeYChange
|
|
1331
|
-
) => {
|
|
1332
|
-
const children = [];
|
|
1333
|
-
/**
|
|
1334
|
-
* @param {Y.XmlElement | Y.XmlText} type
|
|
1335
|
-
*/
|
|
1336
|
-
const createChildren = (type) => {
|
|
1337
|
-
if (type instanceof YXmlElement) {
|
|
1338
|
-
const n = createNodeIfNotExists(
|
|
1339
|
-
type,
|
|
1340
|
-
schema,
|
|
1341
|
-
meta,
|
|
1342
|
-
snapshot,
|
|
1343
|
-
prevSnapshot,
|
|
1344
|
-
computeYChange
|
|
1345
|
-
);
|
|
1346
|
-
if (n !== null) {
|
|
1347
|
-
children.push(n);
|
|
1348
|
-
}
|
|
1349
|
-
} else {
|
|
1350
|
-
// If the next ytext exists and was created by us, move the content to the current ytext.
|
|
1351
|
-
// This is a fix for #160 -- duplication of characters when two Y.Text exist next to each
|
|
1352
|
-
// other.
|
|
1353
|
-
const nextytext = /** @type {Y.ContentType} */ (type._item.right?.content)?.type;
|
|
1354
|
-
if (nextytext instanceof YText && !nextytext._item.deleted && nextytext._item.id.client === nextytext.doc.clientID) {
|
|
1355
|
-
type.applyDelta([
|
|
1356
|
-
{ retain: type.length },
|
|
1357
|
-
...nextytext.toDelta()
|
|
1358
|
-
]);
|
|
1359
|
-
nextytext.doc.transact(tr => {
|
|
1360
|
-
nextytext._item.delete(tr);
|
|
1361
|
-
});
|
|
1362
|
-
}
|
|
1363
|
-
// now create the prosemirror text nodes
|
|
1364
|
-
const ns = createTextNodesFromYText(
|
|
1365
|
-
type,
|
|
1366
|
-
schema,
|
|
1367
|
-
meta,
|
|
1368
|
-
snapshot,
|
|
1369
|
-
prevSnapshot,
|
|
1370
|
-
computeYChange
|
|
1371
|
-
);
|
|
1372
|
-
if (ns !== null) {
|
|
1373
|
-
ns.forEach((textchild) => {
|
|
1374
|
-
if (textchild !== null) {
|
|
1375
|
-
children.push(textchild);
|
|
1376
|
-
}
|
|
1377
|
-
});
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
};
|
|
1381
|
-
if (snapshot === undefined || prevSnapshot === undefined) {
|
|
1382
|
-
el.toArray().forEach(createChildren);
|
|
1383
|
-
} else {
|
|
1384
|
-
typeListToArraySnapshot(el, new Snapshot(prevSnapshot.ds, snapshot.sv))
|
|
1385
|
-
.forEach(createChildren);
|
|
1386
|
-
}
|
|
1387
|
-
try {
|
|
1388
|
-
const attrs = el.getAttributes(snapshot);
|
|
1389
|
-
if (snapshot !== undefined) {
|
|
1390
|
-
if (!isVisible(/** @type {Y.Item} */ (el._item), snapshot)) {
|
|
1391
|
-
attrs.ychange = computeYChange
|
|
1392
|
-
? computeYChange('removed', /** @type {Y.Item} */ (el._item).id)
|
|
1393
|
-
: { type: 'removed' };
|
|
1394
|
-
} else if (!isVisible(/** @type {Y.Item} */ (el._item), prevSnapshot)) {
|
|
1395
|
-
attrs.ychange = computeYChange
|
|
1396
|
-
? computeYChange('added', /** @type {Y.Item} */ (el._item).id)
|
|
1397
|
-
: { type: 'added' };
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
const node = schema.node(el.nodeName, attrs, children);
|
|
1401
|
-
meta.mapping.set(el, node);
|
|
1402
|
-
return node
|
|
1403
|
-
} catch (e) {
|
|
1404
|
-
// an error occured while creating the node. This is probably a result of a concurrent action.
|
|
1405
|
-
/** @type {Y.Doc} */ (el.doc).transact((transaction) => {
|
|
1406
|
-
/** @type {Y.Item} */ (el._item).delete(transaction);
|
|
1407
|
-
}, ySyncPluginKey);
|
|
1408
|
-
meta.mapping.delete(el);
|
|
1409
|
-
return null
|
|
1410
|
-
}
|
|
1411
|
-
};
|
|
1412
|
-
|
|
1413
|
-
/**
|
|
1414
|
-
* @private
|
|
1415
|
-
* @param {Y.XmlText} text
|
|
1416
|
-
* @param {import('prosemirror-model').Schema} schema
|
|
1417
|
-
* @param {BindingMetadata} _meta
|
|
1418
|
-
* @param {Y.Snapshot} [snapshot]
|
|
1419
|
-
* @param {Y.Snapshot} [prevSnapshot]
|
|
1420
|
-
* @param {function('removed' | 'added', Y.ID):any} [computeYChange]
|
|
1421
|
-
* @return {Array<PModel.Node>|null}
|
|
1422
|
-
*/
|
|
1423
|
-
const createTextNodesFromYText = (
|
|
1424
|
-
text,
|
|
1425
|
-
schema,
|
|
1426
|
-
_meta,
|
|
1427
|
-
snapshot,
|
|
1428
|
-
prevSnapshot,
|
|
1429
|
-
computeYChange
|
|
1430
|
-
) => {
|
|
1431
|
-
const nodes = [];
|
|
1432
|
-
const deltas = text.toDelta(snapshot, prevSnapshot, computeYChange);
|
|
1433
|
-
try {
|
|
1434
|
-
for (let i = 0; i < deltas.length; i++) {
|
|
1435
|
-
const delta = deltas[i];
|
|
1436
|
-
nodes.push(schema.text(delta.insert, attributesToMarks(delta.attributes, schema)));
|
|
1437
|
-
}
|
|
1438
|
-
} catch (e) {
|
|
1439
|
-
// an error occured while creating the node. This is probably a result of a concurrent action.
|
|
1440
|
-
/** @type {Y.Doc} */ (text.doc).transact((transaction) => {
|
|
1441
|
-
/** @type {Y.Item} */ (text._item).delete(transaction);
|
|
1442
|
-
}, ySyncPluginKey);
|
|
1443
|
-
return null
|
|
1444
|
-
}
|
|
1445
|
-
// @ts-ignore
|
|
1446
|
-
return nodes
|
|
1447
|
-
};
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* @private
|
|
1451
|
-
* @param {Array<any>} nodes prosemirror node
|
|
1452
|
-
* @param {BindingMetadata} meta
|
|
1453
|
-
* @return {Y.XmlText}
|
|
1454
|
-
*/
|
|
1455
|
-
const createTypeFromTextNodes = (nodes, meta) => {
|
|
1456
|
-
const type = new YXmlText();
|
|
1457
|
-
const delta = nodes.map((node) => ({
|
|
1458
|
-
// @ts-ignore
|
|
1459
|
-
insert: node.text,
|
|
1460
|
-
attributes: marksToAttributes(node.marks, meta)
|
|
1461
|
-
}));
|
|
1462
|
-
type.applyDelta(delta);
|
|
1463
|
-
meta.mapping.set(type, nodes);
|
|
1464
|
-
return type
|
|
1465
|
-
};
|
|
1466
|
-
|
|
1467
|
-
/**
|
|
1468
|
-
* @private
|
|
1469
|
-
* @param {any} node prosemirror node
|
|
1470
|
-
* @param {BindingMetadata} meta
|
|
1471
|
-
* @return {Y.XmlElement}
|
|
1472
|
-
*/
|
|
1473
|
-
const createTypeFromElementNode = (node, meta) => {
|
|
1474
|
-
const type = new YXmlElement(node.type.name);
|
|
1475
|
-
for (const key in node.attrs) {
|
|
1476
|
-
const val = node.attrs[key];
|
|
1477
|
-
if (val !== null && key !== 'ychange') {
|
|
1478
|
-
type.setAttribute(key, val);
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
type.insert(
|
|
1482
|
-
0,
|
|
1483
|
-
normalizePNodeContent(node).map((n) =>
|
|
1484
|
-
createTypeFromTextOrElementNode(n, meta)
|
|
1485
|
-
)
|
|
1486
|
-
);
|
|
1487
|
-
meta.mapping.set(type, node);
|
|
1488
|
-
return type
|
|
1489
|
-
};
|
|
1490
|
-
|
|
1491
|
-
/**
|
|
1492
|
-
* @private
|
|
1493
|
-
* @param {PModel.Node|Array<PModel.Node>} node prosemirror text node
|
|
1494
|
-
* @param {BindingMetadata} meta
|
|
1495
|
-
* @return {Y.XmlElement|Y.XmlText}
|
|
1496
|
-
*/
|
|
1497
|
-
const createTypeFromTextOrElementNode = (node, meta) =>
|
|
1498
|
-
node instanceof Array
|
|
1499
|
-
? createTypeFromTextNodes(node, meta)
|
|
1500
|
-
: createTypeFromElementNode(node, meta);
|
|
1501
|
-
|
|
1502
|
-
/**
|
|
1503
|
-
* @param {any} val
|
|
1504
|
-
*/
|
|
1505
|
-
const isObject = (val) => typeof val === 'object' && val !== null;
|
|
1506
|
-
|
|
1507
|
-
/**
|
|
1508
|
-
* @param {any} pattrs
|
|
1509
|
-
* @param {any} yattrs
|
|
1510
|
-
*/
|
|
1511
|
-
const equalAttrs = (pattrs, yattrs) => {
|
|
1512
|
-
const keys = Object.keys(pattrs).filter((key) => pattrs[key] !== null);
|
|
1513
|
-
let eq =
|
|
1514
|
-
keys.length ===
|
|
1515
|
-
(yattrs == null ? 0 : Object.keys(yattrs).filter((key) => yattrs[key] !== null).length);
|
|
1516
|
-
for (let i = 0; i < keys.length && eq; i++) {
|
|
1517
|
-
const key = keys[i];
|
|
1518
|
-
const l = pattrs[key];
|
|
1519
|
-
const r = yattrs[key];
|
|
1520
|
-
eq = key === 'ychange' || l === r ||
|
|
1521
|
-
(isObject(l) && isObject(r) && equalAttrs(l, r));
|
|
1522
|
-
}
|
|
1523
|
-
return eq
|
|
1524
|
-
};
|
|
1525
|
-
|
|
1526
|
-
/**
|
|
1527
|
-
* @typedef {Array<Array<PModel.Node>|PModel.Node>} NormalizedPNodeContent
|
|
1528
|
-
*/
|
|
1529
|
-
|
|
1530
|
-
/**
|
|
1531
|
-
* @param {any} pnode
|
|
1532
|
-
* @return {NormalizedPNodeContent}
|
|
1533
|
-
*/
|
|
1534
|
-
const normalizePNodeContent = (pnode) => {
|
|
1535
|
-
const c = pnode.content.content;
|
|
1536
|
-
const res = [];
|
|
1537
|
-
for (let i = 0; i < c.length; i++) {
|
|
1538
|
-
const n = c[i];
|
|
1539
|
-
if (n.isText) {
|
|
1540
|
-
const textNodes = [];
|
|
1541
|
-
for (let tnode = c[i]; i < c.length && tnode.isText; tnode = c[++i]) {
|
|
1542
|
-
textNodes.push(tnode);
|
|
1543
|
-
}
|
|
1544
|
-
i--;
|
|
1545
|
-
res.push(textNodes);
|
|
1546
|
-
} else {
|
|
1547
|
-
res.push(n);
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
return res
|
|
1551
|
-
};
|
|
1552
|
-
|
|
1553
|
-
/**
|
|
1554
|
-
* @param {Y.XmlText} ytext
|
|
1555
|
-
* @param {Array<any>} ptexts
|
|
1556
|
-
*/
|
|
1557
|
-
const equalYTextPText = (ytext, ptexts) => {
|
|
1558
|
-
const delta = ytext.toDelta();
|
|
1559
|
-
return delta.length === ptexts.length &&
|
|
1560
|
-
delta.every(/** @type {(d:any,i:number) => boolean} */ (d, i) =>
|
|
1561
|
-
d.insert === /** @type {any} */ (ptexts[i]).text &&
|
|
1562
|
-
keys(d.attributes || {}).length === ptexts[i].marks.length &&
|
|
1563
|
-
every(d.attributes, (attr, yattrname) => {
|
|
1564
|
-
const markname = yattr2markname(yattrname);
|
|
1565
|
-
const pmarks = ptexts[i].marks;
|
|
1566
|
-
return equalAttrs(attr, pmarks.find(/** @param {any} mark */ mark => mark.type.name === markname)?.attrs)
|
|
1567
|
-
})
|
|
1568
|
-
)
|
|
1569
|
-
};
|
|
1570
|
-
|
|
1571
|
-
/**
|
|
1572
|
-
* @param {Y.XmlElement|Y.XmlText|Y.XmlHook} ytype
|
|
1573
|
-
* @param {any|Array<any>} pnode
|
|
1574
|
-
*/
|
|
1575
|
-
const equalYTypePNode = (ytype, pnode) => {
|
|
1576
|
-
if (
|
|
1577
|
-
ytype instanceof YXmlElement && !(pnode instanceof Array) &&
|
|
1578
|
-
matchNodeName(ytype, pnode)
|
|
1579
|
-
) {
|
|
1580
|
-
const normalizedContent = normalizePNodeContent(pnode);
|
|
1581
|
-
return ytype._length === normalizedContent.length &&
|
|
1582
|
-
equalAttrs(ytype.getAttributes(), pnode.attrs) &&
|
|
1583
|
-
ytype.toArray().every((ychild, i) =>
|
|
1584
|
-
equalYTypePNode(ychild, normalizedContent[i])
|
|
1585
|
-
)
|
|
1586
|
-
}
|
|
1587
|
-
return ytype instanceof YXmlText && pnode instanceof Array &&
|
|
1588
|
-
equalYTextPText(ytype, pnode)
|
|
1589
|
-
};
|
|
1590
|
-
|
|
1591
|
-
/**
|
|
1592
|
-
* @param {PModel.Node | Array<PModel.Node> | undefined} mapped
|
|
1593
|
-
* @param {PModel.Node | Array<PModel.Node>} pcontent
|
|
1594
|
-
*/
|
|
1595
|
-
const mappedIdentity = (mapped, pcontent) =>
|
|
1596
|
-
mapped === pcontent ||
|
|
1597
|
-
(mapped instanceof Array && pcontent instanceof Array &&
|
|
1598
|
-
mapped.length === pcontent.length && mapped.every((a, i) =>
|
|
1599
|
-
pcontent[i] === a
|
|
1600
|
-
));
|
|
1601
|
-
|
|
1602
|
-
/**
|
|
1603
|
-
* @param {Y.XmlElement} ytype
|
|
1604
|
-
* @param {PModel.Node} pnode
|
|
1605
|
-
* @param {BindingMetadata} meta
|
|
1606
|
-
* @return {{ foundMappedChild: boolean, equalityFactor: number }}
|
|
1607
|
-
*/
|
|
1608
|
-
const computeChildEqualityFactor = (ytype, pnode, meta) => {
|
|
1609
|
-
const yChildren = ytype.toArray();
|
|
1610
|
-
const pChildren = normalizePNodeContent(pnode);
|
|
1611
|
-
const pChildCnt = pChildren.length;
|
|
1612
|
-
const yChildCnt = yChildren.length;
|
|
1613
|
-
const minCnt = min(yChildCnt, pChildCnt);
|
|
1614
|
-
let left = 0;
|
|
1615
|
-
let right = 0;
|
|
1616
|
-
let foundMappedChild = false;
|
|
1617
|
-
for (; left < minCnt; left++) {
|
|
1618
|
-
const leftY = yChildren[left];
|
|
1619
|
-
const leftP = pChildren[left];
|
|
1620
|
-
if (mappedIdentity(meta.mapping.get(leftY), leftP)) {
|
|
1621
|
-
foundMappedChild = true; // definite (good) match!
|
|
1622
|
-
} else if (!equalYTypePNode(leftY, leftP)) {
|
|
1623
|
-
break
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
for (; left + right < minCnt; right++) {
|
|
1627
|
-
const rightY = yChildren[yChildCnt - right - 1];
|
|
1628
|
-
const rightP = pChildren[pChildCnt - right - 1];
|
|
1629
|
-
if (mappedIdentity(meta.mapping.get(rightY), rightP)) {
|
|
1630
|
-
foundMappedChild = true;
|
|
1631
|
-
} else if (!equalYTypePNode(rightY, rightP)) {
|
|
1632
|
-
break
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
return {
|
|
1636
|
-
equalityFactor: left + right,
|
|
1637
|
-
foundMappedChild
|
|
1638
|
-
}
|
|
1639
|
-
};
|
|
1640
|
-
|
|
1641
|
-
/**
|
|
1642
|
-
* @param {Y.Text} ytext
|
|
1643
|
-
*/
|
|
1644
|
-
const ytextTrans = (ytext) => {
|
|
1645
|
-
let str = '';
|
|
1646
|
-
/**
|
|
1647
|
-
* @type {Y.Item|null}
|
|
1648
|
-
*/
|
|
1649
|
-
let n = ytext._start;
|
|
1650
|
-
const nAttrs = {};
|
|
1651
|
-
while (n !== null) {
|
|
1652
|
-
if (!n.deleted) {
|
|
1653
|
-
if (n.countable && n.content instanceof ContentString) {
|
|
1654
|
-
str += n.content.str;
|
|
1655
|
-
} else if (n.content instanceof ContentFormat) {
|
|
1656
|
-
nAttrs[n.content.key] = null;
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
n = n.right;
|
|
1660
|
-
}
|
|
1661
|
-
return {
|
|
1662
|
-
str,
|
|
1663
|
-
nAttrs
|
|
1664
|
-
}
|
|
1665
|
-
};
|
|
1666
|
-
|
|
1667
|
-
/**
|
|
1668
|
-
* @todo test this more
|
|
1669
|
-
*
|
|
1670
|
-
* @param {Y.Text} ytext
|
|
1671
|
-
* @param {Array<any>} ptexts
|
|
1672
|
-
* @param {BindingMetadata} meta
|
|
1673
|
-
*/
|
|
1674
|
-
const updateYText = (ytext, ptexts, meta) => {
|
|
1675
|
-
meta.mapping.set(ytext, ptexts);
|
|
1676
|
-
const { nAttrs, str } = ytextTrans(ytext);
|
|
1677
|
-
const content = ptexts.map((p) => ({
|
|
1678
|
-
insert: /** @type {any} */ (p).text,
|
|
1679
|
-
attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks, meta))
|
|
1680
|
-
}));
|
|
1681
|
-
const { insert, remove, index } = simpleDiff(
|
|
1682
|
-
str,
|
|
1683
|
-
content.map((c) => c.insert).join('')
|
|
1684
|
-
);
|
|
1685
|
-
ytext.delete(index, remove);
|
|
1686
|
-
ytext.insert(index, insert);
|
|
1687
|
-
ytext.applyDelta(
|
|
1688
|
-
content.map((c) => ({ retain: c.insert.length, attributes: c.attributes }))
|
|
1689
|
-
);
|
|
1690
|
-
};
|
|
1691
|
-
|
|
1692
|
-
const hashedMarkNameRegex = /(.*)(--[a-zA-Z0-9+/=]{8})$/;
|
|
1693
|
-
/**
|
|
1694
|
-
* @param {string} attrName
|
|
1695
|
-
*/
|
|
1696
|
-
const yattr2markname = attrName => hashedMarkNameRegex.exec(attrName)?.[1] ?? attrName;
|
|
1697
|
-
|
|
1698
|
-
/**
|
|
1699
|
-
* @todo move this to markstoattributes
|
|
1700
|
-
*
|
|
1701
|
-
* @param {Object<string, any>} attrs
|
|
1702
|
-
* @param {import('prosemirror-model').Schema} schema
|
|
1703
|
-
*/
|
|
1704
|
-
const attributesToMarks = (attrs, schema) => {
|
|
1705
|
-
/**
|
|
1706
|
-
* @type {Array<import('prosemirror-model').Mark>}
|
|
1707
|
-
*/
|
|
1708
|
-
const marks = [];
|
|
1709
|
-
for (const markName in attrs) {
|
|
1710
|
-
// remove hashes if necessary
|
|
1711
|
-
marks.push(schema.mark(yattr2markname(markName), attrs[markName]));
|
|
1712
|
-
}
|
|
1713
|
-
return marks
|
|
1714
|
-
};
|
|
1715
|
-
|
|
1716
|
-
/**
|
|
1717
|
-
* @param {Array<import('prosemirror-model').Mark>} marks
|
|
1718
|
-
* @param {BindingMetadata} meta
|
|
1719
|
-
*/
|
|
1720
|
-
const marksToAttributes = (marks, meta) => {
|
|
1721
|
-
const pattrs = {};
|
|
1722
|
-
marks.forEach((mark) => {
|
|
1723
|
-
if (mark.type.name !== 'ychange') {
|
|
1724
|
-
const isOverlapping = setIfUndefined(meta.isOMark, mark.type, () => !mark.type.excludes(mark.type));
|
|
1725
|
-
pattrs[isOverlapping ? `${mark.type.name}--${hashOfJSON(mark.toJSON())}` : mark.type.name] = mark.attrs;
|
|
1726
|
-
}
|
|
1727
|
-
});
|
|
1728
|
-
return pattrs
|
|
1729
|
-
};
|
|
1730
|
-
|
|
1731
|
-
/**
|
|
1732
|
-
* Update a yDom node by syncing the current content of the prosemirror node.
|
|
1733
|
-
*
|
|
1734
|
-
* This is a y-prosemirror internal feature that you can use at your own risk.
|
|
1735
|
-
*
|
|
1736
|
-
* @private
|
|
1737
|
-
* @unstable
|
|
1738
|
-
*
|
|
1739
|
-
* @param {{transact: Function}} y
|
|
1740
|
-
* @param {Y.XmlFragment} yDomFragment
|
|
1741
|
-
* @param {any} pNode
|
|
1742
|
-
* @param {BindingMetadata} meta
|
|
1743
|
-
*/
|
|
1744
|
-
const updateYFragment = (y, yDomFragment, pNode, meta) => {
|
|
1745
|
-
if (
|
|
1746
|
-
yDomFragment instanceof YXmlElement &&
|
|
1747
|
-
yDomFragment.nodeName !== pNode.type.name
|
|
1748
|
-
) {
|
|
1749
|
-
throw new Error('node name mismatch!')
|
|
1750
|
-
}
|
|
1751
|
-
meta.mapping.set(yDomFragment, pNode);
|
|
1752
|
-
// update attributes
|
|
1753
|
-
if (yDomFragment instanceof YXmlElement) {
|
|
1754
|
-
const yDomAttrs = yDomFragment.getAttributes();
|
|
1755
|
-
const pAttrs = pNode.attrs;
|
|
1756
|
-
for (const key in pAttrs) {
|
|
1757
|
-
if (pAttrs[key] !== null) {
|
|
1758
|
-
if (yDomAttrs[key] !== pAttrs[key] && key !== 'ychange') {
|
|
1759
|
-
yDomFragment.setAttribute(key, pAttrs[key]);
|
|
1760
|
-
}
|
|
1761
|
-
} else {
|
|
1762
|
-
yDomFragment.removeAttribute(key);
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
// remove all keys that are no longer in pAttrs
|
|
1766
|
-
for (const key in yDomAttrs) {
|
|
1767
|
-
if (pAttrs[key] === undefined) {
|
|
1768
|
-
yDomFragment.removeAttribute(key);
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
// update children
|
|
1773
|
-
const pChildren = normalizePNodeContent(pNode);
|
|
1774
|
-
const pChildCnt = pChildren.length;
|
|
1775
|
-
const yChildren = yDomFragment.toArray();
|
|
1776
|
-
const yChildCnt = yChildren.length;
|
|
1777
|
-
const minCnt = min(pChildCnt, yChildCnt);
|
|
1778
|
-
let left = 0;
|
|
1779
|
-
let right = 0;
|
|
1780
|
-
// find number of matching elements from left
|
|
1781
|
-
for (; left < minCnt; left++) {
|
|
1782
|
-
const leftY = yChildren[left];
|
|
1783
|
-
const leftP = pChildren[left];
|
|
1784
|
-
if (!mappedIdentity(meta.mapping.get(leftY), leftP)) {
|
|
1785
|
-
if (equalYTypePNode(leftY, leftP)) {
|
|
1786
|
-
// update mapping
|
|
1787
|
-
meta.mapping.set(leftY, leftP);
|
|
1788
|
-
} else {
|
|
1789
|
-
break
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
// find number of matching elements from right
|
|
1794
|
-
for (; right + left < minCnt; right++) {
|
|
1795
|
-
const rightY = yChildren[yChildCnt - right - 1];
|
|
1796
|
-
const rightP = pChildren[pChildCnt - right - 1];
|
|
1797
|
-
if (!mappedIdentity(meta.mapping.get(rightY), rightP)) {
|
|
1798
|
-
if (equalYTypePNode(rightY, rightP)) {
|
|
1799
|
-
// update mapping
|
|
1800
|
-
meta.mapping.set(rightY, rightP);
|
|
1801
|
-
} else {
|
|
1802
|
-
break
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
y.transact(() => {
|
|
1807
|
-
// try to compare and update
|
|
1808
|
-
while (yChildCnt - left - right > 0 && pChildCnt - left - right > 0) {
|
|
1809
|
-
const leftY = yChildren[left];
|
|
1810
|
-
const leftP = pChildren[left];
|
|
1811
|
-
const rightY = yChildren[yChildCnt - right - 1];
|
|
1812
|
-
const rightP = pChildren[pChildCnt - right - 1];
|
|
1813
|
-
if (leftY instanceof YXmlText && leftP instanceof Array) {
|
|
1814
|
-
if (!equalYTextPText(leftY, leftP)) {
|
|
1815
|
-
updateYText(leftY, leftP, meta);
|
|
1816
|
-
}
|
|
1817
|
-
left += 1;
|
|
1818
|
-
} else {
|
|
1819
|
-
let updateLeft = leftY instanceof YXmlElement &&
|
|
1820
|
-
matchNodeName(leftY, leftP);
|
|
1821
|
-
let updateRight = rightY instanceof YXmlElement &&
|
|
1822
|
-
matchNodeName(rightY, rightP);
|
|
1823
|
-
if (updateLeft && updateRight) {
|
|
1824
|
-
// decide which which element to update
|
|
1825
|
-
const equalityLeft = computeChildEqualityFactor(
|
|
1826
|
-
/** @type {Y.XmlElement} */ (leftY),
|
|
1827
|
-
/** @type {PModel.Node} */ (leftP),
|
|
1828
|
-
meta
|
|
1829
|
-
);
|
|
1830
|
-
const equalityRight = computeChildEqualityFactor(
|
|
1831
|
-
/** @type {Y.XmlElement} */ (rightY),
|
|
1832
|
-
/** @type {PModel.Node} */ (rightP),
|
|
1833
|
-
meta
|
|
1834
|
-
);
|
|
1835
|
-
if (
|
|
1836
|
-
equalityLeft.foundMappedChild && !equalityRight.foundMappedChild
|
|
1837
|
-
) {
|
|
1838
|
-
updateRight = false;
|
|
1839
|
-
} else if (
|
|
1840
|
-
!equalityLeft.foundMappedChild && equalityRight.foundMappedChild
|
|
1841
|
-
) {
|
|
1842
|
-
updateLeft = false;
|
|
1843
|
-
} else if (
|
|
1844
|
-
equalityLeft.equalityFactor < equalityRight.equalityFactor
|
|
1845
|
-
) {
|
|
1846
|
-
updateLeft = false;
|
|
1847
|
-
} else {
|
|
1848
|
-
updateRight = false;
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
if (updateLeft) {
|
|
1852
|
-
updateYFragment(
|
|
1853
|
-
y,
|
|
1854
|
-
/** @type {Y.XmlFragment} */ (leftY),
|
|
1855
|
-
/** @type {PModel.Node} */ (leftP),
|
|
1856
|
-
meta
|
|
1857
|
-
);
|
|
1858
|
-
left += 1;
|
|
1859
|
-
} else if (updateRight) {
|
|
1860
|
-
updateYFragment(
|
|
1861
|
-
y,
|
|
1862
|
-
/** @type {Y.XmlFragment} */ (rightY),
|
|
1863
|
-
/** @type {PModel.Node} */ (rightP),
|
|
1864
|
-
meta
|
|
1865
|
-
);
|
|
1866
|
-
right += 1;
|
|
1867
|
-
} else {
|
|
1868
|
-
meta.mapping.delete(yDomFragment.get(left));
|
|
1869
|
-
yDomFragment.delete(left, 1);
|
|
1870
|
-
yDomFragment.insert(left, [
|
|
1871
|
-
createTypeFromTextOrElementNode(leftP, meta)
|
|
1872
|
-
]);
|
|
1873
|
-
left += 1;
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
const yDelLen = yChildCnt - left - right;
|
|
1878
|
-
if (
|
|
1879
|
-
yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof YXmlText
|
|
1880
|
-
) {
|
|
1881
|
-
meta.mapping.delete(yChildren[0]);
|
|
1882
|
-
// Edge case handling https://github.com/yjs/y-prosemirror/issues/108
|
|
1883
|
-
// Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
|
|
1884
|
-
yChildren[0].delete(0, yChildren[0].length);
|
|
1885
|
-
} else if (yDelLen > 0) {
|
|
1886
|
-
yDomFragment.slice(left, left + yDelLen).forEach(type => meta.mapping.delete(type));
|
|
1887
|
-
yDomFragment.delete(left, yDelLen);
|
|
1888
|
-
}
|
|
1889
|
-
if (left + right < pChildCnt) {
|
|
1890
|
-
const ins = [];
|
|
1891
|
-
for (let i = left; i < pChildCnt - right; i++) {
|
|
1892
|
-
ins.push(createTypeFromTextOrElementNode(pChildren[i], meta));
|
|
1893
|
-
}
|
|
1894
|
-
yDomFragment.insert(left, ins);
|
|
1895
|
-
}
|
|
1896
|
-
}, ySyncPluginKey);
|
|
1897
|
-
};
|
|
1898
|
-
|
|
1899
|
-
/**
|
|
1900
|
-
* @function
|
|
1901
|
-
* @param {Y.XmlElement} yElement
|
|
1902
|
-
* @param {any} pNode Prosemirror Node
|
|
1903
|
-
*/
|
|
1904
|
-
const matchNodeName = (yElement, pNode) =>
|
|
1905
|
-
!(pNode instanceof Array) && yElement.nodeName === pNode.type.name;
|
|
1906
|
-
|
|
1907
|
-
/**
|
|
1908
|
-
* Transforms a Prosemirror based absolute position to a Yjs Cursor (relative position in the Yjs model).
|
|
1909
|
-
*
|
|
1910
|
-
* @param {number} pos
|
|
1911
|
-
* @param {Y.XmlFragment} type
|
|
1912
|
-
* @param {ProsemirrorMapping} mapping
|
|
1913
|
-
* @return {any} relative position
|
|
1914
|
-
*/
|
|
1915
|
-
const absolutePositionToRelativePosition = (pos, type, mapping) => {
|
|
1916
|
-
if (pos === 0) {
|
|
1917
|
-
// if the type is later populated, we want to retain the 0 position (hence assoc=-1)
|
|
1918
|
-
return createRelativePositionFromTypeIndex(type, 0, type.length === 0 ? -1 : 0)
|
|
1919
|
-
}
|
|
1920
|
-
/**
|
|
1921
|
-
* @type {any}
|
|
1922
|
-
*/
|
|
1923
|
-
let n = type._first === null ? null : /** @type {Y.ContentType} */ (type._first.content).type;
|
|
1924
|
-
while (n !== null && type !== n) {
|
|
1925
|
-
if (n instanceof YXmlText) {
|
|
1926
|
-
if (n._length >= pos) {
|
|
1927
|
-
return createRelativePositionFromTypeIndex(n, pos, type.length === 0 ? -1 : 0)
|
|
1928
|
-
} else {
|
|
1929
|
-
pos -= n._length;
|
|
1930
|
-
}
|
|
1931
|
-
if (n._item !== null && n._item.next !== null) {
|
|
1932
|
-
n = /** @type {Y.ContentType} */ (n._item.next.content).type;
|
|
1933
|
-
} else {
|
|
1934
|
-
do {
|
|
1935
|
-
n = n._item === null ? null : n._item.parent;
|
|
1936
|
-
pos--;
|
|
1937
|
-
} while (n !== type && n !== null && n._item !== null && n._item.next === null)
|
|
1938
|
-
if (n !== null && n !== type) {
|
|
1939
|
-
// @ts-gnore we know that n.next !== null because of above loop conditition
|
|
1940
|
-
n = n._item === null ? null : /** @type {Y.ContentType} */ (/** @type Y.Item */ (n._item.next).content).type;
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
} else {
|
|
1944
|
-
const pNodeSize = /** @type {any} */ (mapping.get(n) || { nodeSize: 0 }).nodeSize;
|
|
1945
|
-
if (n._first !== null && pos < pNodeSize) {
|
|
1946
|
-
n = /** @type {Y.ContentType} */ (n._first.content).type;
|
|
1947
|
-
pos--;
|
|
1948
|
-
} else {
|
|
1949
|
-
if (pos === 1 && n._length === 0 && pNodeSize > 1) {
|
|
1950
|
-
// edge case, should end in this paragraph
|
|
1951
|
-
return new RelativePosition(n._item === null ? null : n._item.id, n._item === null ? findRootTypeKey(n) : null, null)
|
|
1952
|
-
}
|
|
1953
|
-
pos -= pNodeSize;
|
|
1954
|
-
if (n._item !== null && n._item.next !== null) {
|
|
1955
|
-
n = /** @type {Y.ContentType} */ (n._item.next.content).type;
|
|
1956
|
-
} else {
|
|
1957
|
-
if (pos === 0) {
|
|
1958
|
-
// set to end of n.parent
|
|
1959
|
-
n = n._item === null ? n : n._item.parent;
|
|
1960
|
-
return new RelativePosition(n._item === null ? null : n._item.id, n._item === null ? findRootTypeKey(n) : null, null)
|
|
1961
|
-
}
|
|
1962
|
-
do {
|
|
1963
|
-
n = /** @type {Y.Item} */ (n._item).parent;
|
|
1964
|
-
pos--;
|
|
1965
|
-
} while (n !== type && /** @type {Y.Item} */ (n._item).next === null)
|
|
1966
|
-
// if n is null at this point, we have an unexpected case
|
|
1967
|
-
if (n !== type) {
|
|
1968
|
-
// We know that n._item.next is defined because of above loop condition
|
|
1969
|
-
n = /** @type {Y.ContentType} */ (/** @type {Y.Item} */ (/** @type {Y.Item} */ (n._item).next).content).type;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
if (n === null) {
|
|
1975
|
-
throw unexpectedCase()
|
|
1976
|
-
}
|
|
1977
|
-
if (pos === 0 && n.constructor !== YXmlText && n !== type) { // TODO: set to <= 0
|
|
1978
|
-
return createRelativePosition(n._item.parent, n._item)
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
return createRelativePositionFromTypeIndex(type, type._length, type.length === 0 ? -1 : 0)
|
|
1982
|
-
};
|
|
1983
|
-
|
|
1984
|
-
const createRelativePosition = (type, item) => {
|
|
1985
|
-
let typeid = null;
|
|
1986
|
-
let tname = null;
|
|
1987
|
-
if (type._item === null) {
|
|
1988
|
-
tname = findRootTypeKey(type);
|
|
1989
|
-
} else {
|
|
1990
|
-
typeid = createID(type._item.id.client, type._item.id.clock);
|
|
1991
|
-
}
|
|
1992
|
-
return new RelativePosition(typeid, tname, item.id)
|
|
1993
|
-
};
|
|
1994
|
-
|
|
1995
|
-
/**
|
|
1996
|
-
* @param {Y.Doc} y
|
|
1997
|
-
* @param {Y.XmlFragment} documentType Top level type that is bound to pView
|
|
1998
|
-
* @param {any} relPos Encoded Yjs based relative position
|
|
1999
|
-
* @param {ProsemirrorMapping} mapping
|
|
2000
|
-
* @return {null|number}
|
|
2001
|
-
*/
|
|
2002
|
-
const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) => {
|
|
2003
|
-
const decodedPos = createAbsolutePositionFromRelativePosition(relPos, y);
|
|
2004
|
-
if (decodedPos === null || (decodedPos.type !== documentType && !isParentOf(documentType, decodedPos.type._item))) {
|
|
2005
|
-
return null
|
|
2006
|
-
}
|
|
2007
|
-
let type = decodedPos.type;
|
|
2008
|
-
let pos = 0;
|
|
2009
|
-
if (type.constructor === YXmlText) {
|
|
2010
|
-
pos = decodedPos.index;
|
|
2011
|
-
} else if (type._item === null || !type._item.deleted) {
|
|
2012
|
-
let n = type._first;
|
|
2013
|
-
let i = 0;
|
|
2014
|
-
while (i < type._length && i < decodedPos.index && n !== null) {
|
|
2015
|
-
if (!n.deleted) {
|
|
2016
|
-
const t = /** @type {Y.ContentType} */ (n.content).type;
|
|
2017
|
-
i++;
|
|
2018
|
-
if (t instanceof YXmlText) {
|
|
2019
|
-
pos += t._length;
|
|
2020
|
-
} else {
|
|
2021
|
-
pos += /** @type {any} */ (mapping.get(t)).nodeSize;
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
n = /** @type {Y.Item} */ (n.right);
|
|
2025
|
-
}
|
|
2026
|
-
pos += 1; // increase because we go out of n
|
|
2027
|
-
}
|
|
2028
|
-
while (type !== documentType && type._item !== null) {
|
|
2029
|
-
// @ts-ignore
|
|
2030
|
-
const parent = type._item.parent;
|
|
2031
|
-
// @ts-ignore
|
|
2032
|
-
if (parent._item === null || !parent._item.deleted) {
|
|
2033
|
-
pos += 1; // the start tag
|
|
2034
|
-
let n = /** @type {Y.AbstractType} */ (parent)._first;
|
|
2035
|
-
// now iterate until we found type
|
|
2036
|
-
while (n !== null) {
|
|
2037
|
-
const contentType = /** @type {Y.ContentType} */ (n.content).type;
|
|
2038
|
-
if (contentType === type) {
|
|
2039
|
-
break
|
|
2040
|
-
}
|
|
2041
|
-
if (!n.deleted) {
|
|
2042
|
-
if (contentType instanceof YXmlText) {
|
|
2043
|
-
pos += contentType._length;
|
|
2044
|
-
} else {
|
|
2045
|
-
pos += /** @type {any} */ (mapping.get(contentType)).nodeSize;
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
n = n.right;
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
type = /** @type {Y.AbstractType} */ (parent);
|
|
2052
|
-
}
|
|
2053
|
-
return pos - 1 // we don't count the most outer tag, because it is a fragment
|
|
2054
|
-
};
|
|
2055
|
-
|
|
2056
|
-
/**
|
|
2057
|
-
* @deprecated Use `yXmlFragmentToProseMirrorRootNode` instead
|
|
2058
|
-
*
|
|
2059
|
-
* Utility method to convert a Y.Doc to Prosemirror compatible JSON.
|
|
2060
|
-
*
|
|
2061
|
-
* @param {Y.XmlFragment} xmlFragment The fragment, which must be part of a Y.Doc.
|
|
2062
|
-
* @return {Record<string, any>}
|
|
2063
|
-
*/
|
|
2064
|
-
function yXmlFragmentToProsemirrorJSON (xmlFragment) {
|
|
2065
|
-
const items = xmlFragment.toArray();
|
|
2066
|
-
|
|
2067
|
-
/**
|
|
2068
|
-
* @param {Y.AbstractType} item
|
|
2069
|
-
*/
|
|
2070
|
-
const serialize = item => {
|
|
2071
|
-
/**
|
|
2072
|
-
* @type {Object} NodeObject
|
|
2073
|
-
* @property {string} NodeObject.type
|
|
2074
|
-
* @property {Record<string, string>=} NodeObject.attrs
|
|
2075
|
-
* @property {Array<NodeObject>=} NodeObject.content
|
|
2076
|
-
*/
|
|
2077
|
-
let response;
|
|
2078
|
-
|
|
2079
|
-
// TODO: Must be a better way to detect text nodes than this
|
|
2080
|
-
if (item instanceof YXmlText) {
|
|
2081
|
-
const delta = item.toDelta();
|
|
2082
|
-
response = delta.map(/** @param {any} d */ (d) => {
|
|
2083
|
-
const text = {
|
|
2084
|
-
type: 'text',
|
|
2085
|
-
text: d.insert
|
|
2086
|
-
};
|
|
2087
|
-
if (d.attributes) {
|
|
2088
|
-
text.marks = Object.keys(d.attributes).map((type_) => {
|
|
2089
|
-
const attrs = d.attributes[type_];
|
|
2090
|
-
const type = yattr2markname(type_);
|
|
2091
|
-
const mark = {
|
|
2092
|
-
type
|
|
2093
|
-
};
|
|
2094
|
-
if (Object.keys(attrs)) {
|
|
2095
|
-
mark.attrs = attrs;
|
|
2096
|
-
}
|
|
2097
|
-
return mark
|
|
2098
|
-
});
|
|
2099
|
-
}
|
|
2100
|
-
return text
|
|
2101
|
-
});
|
|
2102
|
-
} else if (item instanceof YXmlElement) {
|
|
2103
|
-
response = {
|
|
2104
|
-
type: item.nodeName
|
|
2105
|
-
};
|
|
2106
|
-
|
|
2107
|
-
const attrs = item.getAttributes();
|
|
2108
|
-
if (Object.keys(attrs).length) {
|
|
2109
|
-
response.attrs = attrs;
|
|
2110
|
-
}
|
|
2111
|
-
|
|
2112
|
-
const children = item.toArray();
|
|
2113
|
-
if (children.length) {
|
|
2114
|
-
response.content = children.map(serialize).flat();
|
|
2115
|
-
}
|
|
2116
|
-
} else {
|
|
2117
|
-
// expected either Y.XmlElement or Y.XmlText
|
|
2118
|
-
unexpectedCase();
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
return response
|
|
2122
|
-
};
|
|
2123
|
-
|
|
2124
|
-
return {
|
|
2125
|
-
type: 'doc',
|
|
2126
|
-
content: items.map(serialize)
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
/**
|
|
2131
|
-
* @typedef {Object} UndoPluginState
|
|
2132
|
-
* @property {import('yjs').UndoManager} undoManager
|
|
2133
|
-
* @property {ReturnType<typeof getRelativeSelection> | null} prevSel
|
|
2134
|
-
* @property {boolean} hasUndoOps
|
|
2135
|
-
* @property {boolean} hasRedoOps
|
|
2136
|
-
*/
|
|
2137
|
-
|
|
2138
|
-
/**
|
|
2139
|
-
* Undo the last user action
|
|
2140
|
-
*
|
|
2141
|
-
* @param {import('prosemirror-state').EditorState} state
|
|
2142
|
-
* @return {boolean} whether a change was undone
|
|
2143
|
-
*/
|
|
2144
|
-
const undo = state => yUndoPluginKey.getState(state)?.undoManager?.undo() != null;
|
|
2145
|
-
|
|
2146
|
-
/**
|
|
2147
|
-
* Redo the last user action
|
|
2148
|
-
*
|
|
2149
|
-
* @param {import('prosemirror-state').EditorState} state
|
|
2150
|
-
* @return {boolean} whether a change was undone
|
|
2151
|
-
*/
|
|
2152
|
-
const redo = state => yUndoPluginKey.getState(state)?.undoManager?.redo() != null;
|
|
2153
|
-
|
|
2154
|
-
const defaultProtectedNodes = new Set(['paragraph']);
|
|
2155
|
-
|
|
2156
|
-
/**
|
|
2157
|
-
* @param {import('yjs').Item} item
|
|
2158
|
-
* @param {Set<string>} protectedNodes
|
|
2159
|
-
* @returns {boolean}
|
|
2160
|
-
*/
|
|
2161
|
-
const defaultDeleteFilter = (item, protectedNodes) => !(item instanceof Item) ||
|
|
2162
|
-
!(item.content instanceof ContentType) ||
|
|
2163
|
-
!(item.content.type instanceof YText ||
|
|
2164
|
-
(item.content.type instanceof YXmlElement && protectedNodes.has(item.content.type.nodeName))) ||
|
|
2165
|
-
item.content.type._length === 0;
|
|
2166
|
-
|
|
2167
|
-
/**
|
|
2168
|
-
* @param {object} [options]
|
|
2169
|
-
* @param {Set<string>} [options.protectedNodes]
|
|
2170
|
-
* @param {any[]} [options.trackedOrigins]
|
|
2171
|
-
* @param {import('yjs').UndoManager | null} [options.undoManager]
|
|
2172
|
-
*/
|
|
2173
|
-
const yUndoPlugin = ({ protectedNodes = defaultProtectedNodes, trackedOrigins = [], undoManager = null } = {}) => new Plugin({
|
|
2174
|
-
key: yUndoPluginKey,
|
|
2175
|
-
state: {
|
|
2176
|
-
init: (initargs, state) => {
|
|
2177
|
-
// TODO: check if plugin order matches and fix
|
|
2178
|
-
const ystate = ySyncPluginKey.getState(state);
|
|
2179
|
-
const _undoManager = undoManager || new UndoManager(ystate.type, {
|
|
2180
|
-
trackedOrigins: new Set([ySyncPluginKey].concat(trackedOrigins)),
|
|
2181
|
-
deleteFilter: (item) => defaultDeleteFilter(item, protectedNodes),
|
|
2182
|
-
captureTransaction: tr => tr.meta.get('addToHistory') !== false
|
|
2183
|
-
});
|
|
2184
|
-
return {
|
|
2185
|
-
undoManager: _undoManager,
|
|
2186
|
-
prevSel: null,
|
|
2187
|
-
hasUndoOps: _undoManager.undoStack.length > 0,
|
|
2188
|
-
hasRedoOps: _undoManager.redoStack.length > 0
|
|
2189
|
-
}
|
|
2190
|
-
},
|
|
2191
|
-
apply: (tr, val, oldState, state) => {
|
|
2192
|
-
const binding = ySyncPluginKey.getState(state).binding;
|
|
2193
|
-
const undoManager = val.undoManager;
|
|
2194
|
-
const hasUndoOps = undoManager.undoStack.length > 0;
|
|
2195
|
-
const hasRedoOps = undoManager.redoStack.length > 0;
|
|
2196
|
-
if (binding) {
|
|
2197
|
-
return {
|
|
2198
|
-
undoManager,
|
|
2199
|
-
prevSel: getRelativeSelection(binding, oldState),
|
|
2200
|
-
hasUndoOps,
|
|
2201
|
-
hasRedoOps
|
|
2202
|
-
}
|
|
2203
|
-
} else {
|
|
2204
|
-
if (hasUndoOps !== val.hasUndoOps || hasRedoOps !== val.hasRedoOps) {
|
|
2205
|
-
return Object.assign({}, val, {
|
|
2206
|
-
hasUndoOps: undoManager.undoStack.length > 0,
|
|
2207
|
-
hasRedoOps: undoManager.redoStack.length > 0
|
|
2208
|
-
})
|
|
2209
|
-
} else { // nothing changed
|
|
2210
|
-
return val
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
},
|
|
2215
|
-
view: view => {
|
|
2216
|
-
const ystate = ySyncPluginKey.getState(view.state);
|
|
2217
|
-
const undoManager = yUndoPluginKey.getState(view.state).undoManager;
|
|
2218
|
-
undoManager.on('stack-item-added', ({ stackItem }) => {
|
|
2219
|
-
const binding = ystate.binding;
|
|
2220
|
-
if (binding) {
|
|
2221
|
-
stackItem.meta.set(binding, yUndoPluginKey.getState(view.state).prevSel);
|
|
2222
|
-
}
|
|
2223
|
-
});
|
|
2224
|
-
undoManager.on('stack-item-popped', ({ stackItem }) => {
|
|
2225
|
-
const binding = ystate.binding;
|
|
2226
|
-
if (binding) {
|
|
2227
|
-
binding.beforeTransactionSelection = stackItem.meta.get(binding) || binding.beforeTransactionSelection;
|
|
2228
|
-
}
|
|
2229
|
-
});
|
|
2230
|
-
return {
|
|
2231
|
-
destroy: () => {
|
|
2232
|
-
undoManager.destroy();
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
});
|
|
2237
|
-
|
|
2238
|
-
/**
|
|
2239
|
-
* This extension allows you to collaborate with others in real-time.
|
|
2240
|
-
* @see https://tiptap.dev/api/extensions/collaboration
|
|
2241
|
-
*/
|
|
2242
|
-
const Collaboration = Extension.create({
|
|
2243
|
-
name: 'collaboration',
|
|
2244
|
-
priority: 1000,
|
|
2245
|
-
addOptions() {
|
|
2246
|
-
return {
|
|
2247
|
-
document: null,
|
|
2248
|
-
field: 'default',
|
|
2249
|
-
fragment: null,
|
|
2250
|
-
};
|
|
2251
|
-
},
|
|
2252
|
-
addStorage() {
|
|
2253
|
-
return {
|
|
2254
|
-
isDisabled: false,
|
|
2255
|
-
};
|
|
2256
|
-
},
|
|
2257
|
-
onCreate() {
|
|
2258
|
-
if (this.editor.extensionManager.extensions.find(extension => extension.name === 'history')) {
|
|
2259
|
-
console.warn('[tiptap warn]: "@tiptap/extension-collaboration" comes with its own history support and is not compatible with "@tiptap/extension-history".');
|
|
2260
|
-
}
|
|
2261
|
-
},
|
|
2262
|
-
addCommands() {
|
|
2263
|
-
return {
|
|
2264
|
-
undo: () => ({ tr, state, dispatch }) => {
|
|
2265
|
-
tr.setMeta('preventDispatch', true);
|
|
2266
|
-
const undoManager = yUndoPluginKey.getState(state).undoManager;
|
|
2267
|
-
if (undoManager.undoStack.length === 0) {
|
|
2268
|
-
return false;
|
|
2269
|
-
}
|
|
2270
|
-
if (!dispatch) {
|
|
2271
|
-
return true;
|
|
2272
|
-
}
|
|
2273
|
-
return undo(state);
|
|
2274
|
-
},
|
|
2275
|
-
redo: () => ({ tr, state, dispatch }) => {
|
|
2276
|
-
tr.setMeta('preventDispatch', true);
|
|
2277
|
-
const undoManager = yUndoPluginKey.getState(state).undoManager;
|
|
2278
|
-
if (undoManager.redoStack.length === 0) {
|
|
2279
|
-
return false;
|
|
2280
|
-
}
|
|
2281
|
-
if (!dispatch) {
|
|
2282
|
-
return true;
|
|
2283
|
-
}
|
|
2284
|
-
return redo(state);
|
|
2285
|
-
},
|
|
2286
|
-
};
|
|
2287
|
-
},
|
|
2288
|
-
addKeyboardShortcuts() {
|
|
2289
|
-
return {
|
|
2290
|
-
'Mod-z': () => this.editor.commands.undo(),
|
|
2291
|
-
'Mod-y': () => this.editor.commands.redo(),
|
|
2292
|
-
'Shift-Mod-z': () => this.editor.commands.redo(),
|
|
2293
|
-
};
|
|
2294
|
-
},
|
|
2295
|
-
addProseMirrorPlugins() {
|
|
2296
|
-
var _a;
|
|
2297
|
-
const fragment = this.options.fragment
|
|
2298
|
-
? this.options.fragment
|
|
2299
|
-
: this.options.document.getXmlFragment(this.options.field);
|
|
2300
|
-
// Quick fix until there is an official implementation (thanks to @hamflx).
|
|
2301
|
-
// See https://github.com/yjs/y-prosemirror/issues/114 and https://github.com/yjs/y-prosemirror/issues/102
|
|
2302
|
-
const yUndoPluginInstance = yUndoPlugin(this.options.yUndoOptions);
|
|
2303
|
-
const originalUndoPluginView = yUndoPluginInstance.spec.view;
|
|
2304
|
-
yUndoPluginInstance.spec.view = (view) => {
|
|
2305
|
-
const { undoManager } = yUndoPluginKey.getState(view.state);
|
|
2306
|
-
if (undoManager.restore) {
|
|
2307
|
-
undoManager.restore();
|
|
2308
|
-
undoManager.restore = () => {
|
|
2309
|
-
// noop
|
|
2310
|
-
};
|
|
2311
|
-
}
|
|
2312
|
-
const viewRet = originalUndoPluginView ? originalUndoPluginView(view) : undefined;
|
|
2313
|
-
return {
|
|
2314
|
-
destroy: () => {
|
|
2315
|
-
const hasUndoManSelf = undoManager.trackedOrigins.has(undoManager);
|
|
2316
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
2317
|
-
const observers = undoManager._observers;
|
|
2318
|
-
undoManager.restore = () => {
|
|
2319
|
-
if (hasUndoManSelf) {
|
|
2320
|
-
undoManager.trackedOrigins.add(undoManager);
|
|
2321
|
-
}
|
|
2322
|
-
undoManager.doc.on('afterTransaction', undoManager.afterTransactionHandler);
|
|
2323
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
2324
|
-
undoManager._observers = observers;
|
|
2325
|
-
};
|
|
2326
|
-
if (viewRet === null || viewRet === void 0 ? void 0 : viewRet.destroy) {
|
|
2327
|
-
viewRet.destroy();
|
|
2328
|
-
}
|
|
2329
|
-
},
|
|
2330
|
-
};
|
|
2331
|
-
};
|
|
2332
|
-
const ySyncPluginOptions = {
|
|
2333
|
-
...this.options.ySyncOptions,
|
|
2334
|
-
onFirstRender: this.options.onFirstRender,
|
|
2335
|
-
};
|
|
2336
|
-
const ySyncPluginInstance = ySyncPlugin(fragment, ySyncPluginOptions);
|
|
2337
|
-
if (this.editor.options.enableContentCheck) {
|
|
2338
|
-
(_a = fragment.doc) === null || _a === void 0 ? void 0 : _a.on('beforeTransaction', () => {
|
|
2339
|
-
try {
|
|
2340
|
-
const jsonContent = (yXmlFragmentToProsemirrorJSON(fragment));
|
|
2341
|
-
if (jsonContent.content.length === 0) {
|
|
2342
|
-
return;
|
|
2343
|
-
}
|
|
2344
|
-
this.editor.schema.nodeFromJSON(jsonContent).check();
|
|
2345
|
-
}
|
|
2346
|
-
catch (error) {
|
|
2347
|
-
this.editor.emit('contentError', {
|
|
2348
|
-
error: error,
|
|
2349
|
-
editor: this.editor,
|
|
2350
|
-
disableCollaboration: () => {
|
|
2351
|
-
var _a;
|
|
2352
|
-
(_a = fragment.doc) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
2353
|
-
this.storage.isDisabled = true;
|
|
2354
|
-
},
|
|
2355
|
-
});
|
|
2356
|
-
// If the content is invalid, return false to prevent the transaction from being applied
|
|
2357
|
-
return false;
|
|
2358
|
-
}
|
|
2359
|
-
});
|
|
2360
|
-
}
|
|
2361
|
-
return [
|
|
2362
|
-
ySyncPluginInstance,
|
|
2363
|
-
yUndoPluginInstance,
|
|
2364
|
-
// Only add the filterInvalidContent plugin if content checking is enabled
|
|
2365
|
-
this.editor.options.enableContentCheck
|
|
2366
|
-
&& new Plugin({
|
|
2367
|
-
key: new PluginKey('filterInvalidContent'),
|
|
2368
|
-
filterTransaction: () => {
|
|
2369
|
-
var _a;
|
|
2370
|
-
// When collaboration is disabled, prevent any sync transactions from being applied
|
|
2371
|
-
if (this.storage.isDisabled) {
|
|
2372
|
-
// Destroy the Yjs document to prevent any further sync transactions
|
|
2373
|
-
(_a = fragment.doc) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
2374
|
-
return true;
|
|
2375
|
-
}
|
|
2376
|
-
return true;
|
|
2377
|
-
},
|
|
2378
|
-
}),
|
|
2379
|
-
].filter(Boolean);
|
|
2380
|
-
},
|
|
2381
|
-
});
|
|
2382
|
-
|
|
2383
|
-
const MARKDOWN_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".mdx"]);
|
|
2384
|
-
const MARKDOWN_MIME_TYPES = /* @__PURE__ */ new Set(["text/markdown", "text/x-markdown"]);
|
|
2385
|
-
function getExtension(name) {
|
|
2386
|
-
if (!name) return null;
|
|
2387
|
-
const lastDot = name.lastIndexOf(".");
|
|
2388
|
-
if (lastDot < 0) return null;
|
|
2389
|
-
return name.slice(lastDot).toLowerCase();
|
|
2390
|
-
}
|
|
2391
|
-
function isMarkdownFileName(name, mimeType) {
|
|
2392
|
-
if (mimeType && MARKDOWN_MIME_TYPES.has(mimeType)) return true;
|
|
2393
|
-
const ext = getExtension(name);
|
|
2394
|
-
return ext ? MARKDOWN_EXTENSIONS.has(ext) : false;
|
|
2395
|
-
}
|
|
2396
|
-
function inferNotebookDocKind(context) {
|
|
2397
|
-
const override = context.customData?.docKind;
|
|
2398
|
-
if (override === "markdown" || override === "notebook") return override;
|
|
2399
|
-
if (context.type === "notebook") return "notebook";
|
|
2400
|
-
const customData = context.customData;
|
|
2401
|
-
const mimeType = typeof context.mimeType === "string" ? context.mimeType : typeof customData?.mimeType === "string" ? customData.mimeType : typeof customData?.fileMeta?.mimeType === "string" ? customData.fileMeta.mimeType : void 0;
|
|
2402
|
-
if (isMarkdownFileName(context.resourceName, mimeType)) return "markdown";
|
|
2403
|
-
return "notebook";
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
const REMOTE_ORIGIN = "ds-remote";
|
|
2407
|
-
async function createNovelYjsProvider(options) {
|
|
2408
|
-
const { projectId, notebookId, readonly = false, onStatus, onReset } = options;
|
|
2409
|
-
const sync = new ProjectSyncClient(projectId, {
|
|
2410
|
-
authMode: "user",
|
|
2411
|
-
docKind: "notebook"
|
|
2412
|
-
});
|
|
2413
|
-
const ydoc = new Doc();
|
|
2414
|
-
let disposed = false;
|
|
2415
|
-
let flushTimer = null;
|
|
2416
|
-
let idleTimer = null;
|
|
2417
|
-
let pending = [];
|
|
2418
|
-
const setStatus = (status) => {
|
|
2419
|
-
if (!onStatus) return;
|
|
2420
|
-
onStatus(status);
|
|
2421
|
-
if (status === "saved") {
|
|
2422
|
-
if (idleTimer != null) window.clearTimeout(idleTimer);
|
|
2423
|
-
idleTimer = window.setTimeout(() => onStatus("idle"), 1500);
|
|
2424
|
-
}
|
|
2425
|
-
};
|
|
2426
|
-
const scheduleFlush = () => {
|
|
2427
|
-
if (flushTimer != null) window.clearTimeout(flushTimer);
|
|
2428
|
-
flushTimer = window.setTimeout(() => void flush(), 250);
|
|
2429
|
-
};
|
|
2430
|
-
const flush = async () => {
|
|
2431
|
-
if (disposed || readonly || pending.length === 0) return;
|
|
2432
|
-
const updates = pending;
|
|
2433
|
-
pending = [];
|
|
2434
|
-
try {
|
|
2435
|
-
setStatus("saving");
|
|
2436
|
-
const merged = updates.length === 1 ? updates[0] : mergeUpdates(updates);
|
|
2437
|
-
await sync.pushDocUpdate(notebookId, merged);
|
|
2438
|
-
setStatus("saved");
|
|
2439
|
-
} catch (error) {
|
|
2440
|
-
console.error("[NovelYjsProvider] Failed to push update:", error);
|
|
2441
|
-
setStatus("error");
|
|
2442
|
-
}
|
|
2443
|
-
};
|
|
2444
|
-
await sync.connect();
|
|
2445
|
-
const diff = await sync.loadDoc(notebookId, encodeStateVector(ydoc));
|
|
2446
|
-
if (diff?.missing) {
|
|
2447
|
-
applyUpdate(ydoc, diff.missing, REMOTE_ORIGIN);
|
|
2448
|
-
}
|
|
2449
|
-
const offRemote = sync.onDocUpdate((msg) => {
|
|
2450
|
-
if (msg.docId !== notebookId) return;
|
|
2451
|
-
applyUpdate(ydoc, msg.update, REMOTE_ORIGIN);
|
|
2452
|
-
});
|
|
2453
|
-
const offReset = sync.onDocReset((msg) => {
|
|
2454
|
-
if (msg.docId !== notebookId) return;
|
|
2455
|
-
onReset?.({ timestamp: msg.timestamp, reason: msg.reason, actorUserId: msg.actorUserId ?? null });
|
|
2456
|
-
});
|
|
2457
|
-
const onLocalUpdate = (update, origin) => {
|
|
2458
|
-
if (origin === REMOTE_ORIGIN) return;
|
|
2459
|
-
if (readonly) return;
|
|
2460
|
-
pending.push(update);
|
|
2461
|
-
scheduleFlush();
|
|
2462
|
-
};
|
|
2463
|
-
ydoc.on("update", onLocalUpdate);
|
|
2464
|
-
const dispose = () => {
|
|
2465
|
-
disposed = true;
|
|
2466
|
-
if (flushTimer != null) window.clearTimeout(flushTimer);
|
|
2467
|
-
if (idleTimer != null) window.clearTimeout(idleTimer);
|
|
2468
|
-
ydoc.off("update", onLocalUpdate);
|
|
2469
|
-
offRemote?.();
|
|
2470
|
-
offReset?.();
|
|
2471
|
-
sync.disconnect();
|
|
2472
|
-
};
|
|
2473
|
-
return { ydoc, dispose };
|
|
2474
|
-
}
|
|
2475
|
-
|
|
2476
|
-
function buildSuggestionItems(onImageUpload) {
|
|
2477
|
-
return [
|
|
2478
|
-
{
|
|
2479
|
-
title: "Send Feedback",
|
|
2480
|
-
description: "Let us know how we can improve.",
|
|
2481
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquarePlus, { size: 18 }),
|
|
2482
|
-
command: ({ editor, range }) => {
|
|
2483
|
-
editor.chain().focus().deleteRange(range).run();
|
|
2484
|
-
window.open("/feedback", "_blank");
|
|
2485
|
-
}
|
|
2486
|
-
},
|
|
2487
|
-
{
|
|
2488
|
-
title: "Text",
|
|
2489
|
-
description: "Just start typing with plain text.",
|
|
2490
|
-
searchTerms: ["p", "paragraph"],
|
|
2491
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: 18 }),
|
|
2492
|
-
command: ({ editor, range }) => {
|
|
2493
|
-
editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
|
|
2494
|
-
}
|
|
2495
|
-
},
|
|
2496
|
-
{
|
|
2497
|
-
title: "To-do List",
|
|
2498
|
-
description: "Track tasks with a to-do list.",
|
|
2499
|
-
searchTerms: ["todo", "task", "list", "check", "checkbox"],
|
|
2500
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SquareCheckBig, { size: 18 }),
|
|
2501
|
-
command: ({ editor, range }) => {
|
|
2502
|
-
editor.chain().focus().deleteRange(range).toggleTaskList().run();
|
|
2503
|
-
}
|
|
2504
|
-
},
|
|
2505
|
-
{
|
|
2506
|
-
title: "Heading 1",
|
|
2507
|
-
description: "Big section heading.",
|
|
2508
|
-
searchTerms: ["title", "big", "large"],
|
|
2509
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Heading1, { size: 18 }),
|
|
2510
|
-
command: ({ editor, range }) => {
|
|
2511
|
-
editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
|
|
2512
|
-
}
|
|
2513
|
-
},
|
|
2514
|
-
{
|
|
2515
|
-
title: "Heading 2",
|
|
2516
|
-
description: "Medium section heading.",
|
|
2517
|
-
searchTerms: ["subtitle", "medium"],
|
|
2518
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Heading2, { size: 18 }),
|
|
2519
|
-
command: ({ editor, range }) => {
|
|
2520
|
-
editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
|
|
2521
|
-
}
|
|
2522
|
-
},
|
|
2523
|
-
{
|
|
2524
|
-
title: "Heading 3",
|
|
2525
|
-
description: "Small section heading.",
|
|
2526
|
-
searchTerms: ["subtitle", "small"],
|
|
2527
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Heading3, { size: 18 }),
|
|
2528
|
-
command: ({ editor, range }) => {
|
|
2529
|
-
editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
|
|
2530
|
-
}
|
|
2531
|
-
},
|
|
2532
|
-
{
|
|
2533
|
-
title: "Bullet List",
|
|
2534
|
-
description: "Create a simple bullet list.",
|
|
2535
|
-
searchTerms: ["unordered", "point"],
|
|
2536
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { size: 18 }),
|
|
2537
|
-
command: ({ editor, range }) => {
|
|
2538
|
-
editor.chain().focus().deleteRange(range).toggleBulletList().run();
|
|
2539
|
-
}
|
|
2540
|
-
},
|
|
2541
|
-
{
|
|
2542
|
-
title: "Numbered List",
|
|
2543
|
-
description: "Create a list with numbering.",
|
|
2544
|
-
searchTerms: ["ordered"],
|
|
2545
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(ListOrdered, { size: 18 }),
|
|
2546
|
-
command: ({ editor, range }) => {
|
|
2547
|
-
editor.chain().focus().deleteRange(range).toggleOrderedList().run();
|
|
2548
|
-
}
|
|
2549
|
-
},
|
|
2550
|
-
{
|
|
2551
|
-
title: "Quote",
|
|
2552
|
-
description: "Capture a quote.",
|
|
2553
|
-
searchTerms: ["blockquote"],
|
|
2554
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TextQuote, { size: 18 }),
|
|
2555
|
-
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run()
|
|
2556
|
-
},
|
|
2557
|
-
{
|
|
2558
|
-
title: "Code",
|
|
2559
|
-
description: "Capture a code snippet.",
|
|
2560
|
-
searchTerms: ["codeblock"],
|
|
2561
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Code, { size: 18 }),
|
|
2562
|
-
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run()
|
|
2563
|
-
},
|
|
2564
|
-
{
|
|
2565
|
-
title: "Table",
|
|
2566
|
-
description: "Insert a table with rows and columns.",
|
|
2567
|
-
searchTerms: ["table", "grid", "spreadsheet", "data"],
|
|
2568
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Table2, { size: 18 }),
|
|
2569
|
-
command: ({ editor, range }) => {
|
|
2570
|
-
editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
|
|
2571
|
-
}
|
|
2572
|
-
},
|
|
2573
|
-
{
|
|
2574
|
-
title: "Image",
|
|
2575
|
-
description: "Upload an image from your computer.",
|
|
2576
|
-
searchTerms: ["photo", "picture", "media"],
|
|
2577
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 18 }),
|
|
2578
|
-
command: ({ editor, range }) => {
|
|
2579
|
-
editor.chain().focus().deleteRange(range).run();
|
|
2580
|
-
const input = document.createElement("input");
|
|
2581
|
-
input.type = "file";
|
|
2582
|
-
input.accept = "image/*";
|
|
2583
|
-
input.onchange = async () => {
|
|
2584
|
-
if (input.files?.length) {
|
|
2585
|
-
const file = input.files[0];
|
|
2586
|
-
await onImageUpload(file, editor);
|
|
2587
|
-
}
|
|
2588
|
-
};
|
|
2589
|
-
input.click();
|
|
2590
|
-
}
|
|
2591
|
-
},
|
|
2592
|
-
{
|
|
2593
|
-
title: "Youtube",
|
|
2594
|
-
description: "Embed a Youtube video.",
|
|
2595
|
-
searchTerms: ["video", "youtube", "embed"],
|
|
2596
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Youtube, { size: 18 }),
|
|
2597
|
-
command: ({ editor, range }) => {
|
|
2598
|
-
const videoLink = prompt("Please enter Youtube Video Link");
|
|
2599
|
-
if (!videoLink) return;
|
|
2600
|
-
const ytregex = new RegExp(
|
|
2601
|
-
/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/
|
|
2602
|
-
);
|
|
2603
|
-
if (ytregex.test(videoLink)) {
|
|
2604
|
-
editor.chain().focus().deleteRange(range).setYoutubeVideo({
|
|
2605
|
-
src: videoLink
|
|
2606
|
-
}).run();
|
|
2607
|
-
} else {
|
|
2608
|
-
alert("Please enter a correct Youtube Video Link");
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
},
|
|
2612
|
-
{
|
|
2613
|
-
title: "Twitter",
|
|
2614
|
-
description: "Embed a Tweet.",
|
|
2615
|
-
searchTerms: ["twitter", "embed"],
|
|
2616
|
-
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Twitter, { size: 18 }),
|
|
2617
|
-
command: ({ editor, range }) => {
|
|
2618
|
-
const tweetLink = prompt("Please enter Twitter Link");
|
|
2619
|
-
if (!tweetLink) return;
|
|
2620
|
-
const tweetRegex = new RegExp(
|
|
2621
|
-
/^https?:\/\/(www\.)?x\.com\/([a-zA-Z0-9_]{1,15})(\/status\/(\d+))?(\/\S*)?$/
|
|
2622
|
-
);
|
|
2623
|
-
if (tweetRegex.test(tweetLink)) {
|
|
2624
|
-
editor.chain().focus().deleteRange(range).setTweet({
|
|
2625
|
-
src: tweetLink
|
|
2626
|
-
}).run();
|
|
2627
|
-
} else {
|
|
2628
|
-
alert("Please enter a correct Twitter Link");
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
];
|
|
2633
|
-
}
|
|
2634
|
-
function createSlashCommand(items) {
|
|
2635
|
-
return me.configure({
|
|
2636
|
-
suggestion: {
|
|
2637
|
-
items: () => items,
|
|
2638
|
-
render: le
|
|
2639
|
-
}
|
|
2640
|
-
});
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
const items = [
|
|
2644
|
-
{
|
|
2645
|
-
name: "Text",
|
|
2646
|
-
icon: Text,
|
|
2647
|
-
command: (editor) => editor.chain().focus().clearNodes().run(),
|
|
2648
|
-
isActive: (editor) => editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList")
|
|
2649
|
-
},
|
|
2650
|
-
{
|
|
2651
|
-
name: "Heading 1",
|
|
2652
|
-
icon: Heading1,
|
|
2653
|
-
command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
|
|
2654
|
-
isActive: (editor) => editor.isActive("heading", { level: 1 })
|
|
2655
|
-
},
|
|
2656
|
-
{
|
|
2657
|
-
name: "Heading 2",
|
|
2658
|
-
icon: Heading2,
|
|
2659
|
-
command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
|
|
2660
|
-
isActive: (editor) => editor.isActive("heading", { level: 2 })
|
|
2661
|
-
},
|
|
2662
|
-
{
|
|
2663
|
-
name: "Heading 3",
|
|
2664
|
-
icon: Heading3,
|
|
2665
|
-
command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
|
|
2666
|
-
isActive: (editor) => editor.isActive("heading", { level: 3 })
|
|
2667
|
-
},
|
|
2668
|
-
{
|
|
2669
|
-
name: "To-do List",
|
|
2670
|
-
icon: SquareCheckBig,
|
|
2671
|
-
command: (editor) => editor.chain().focus().clearNodes().toggleTaskList().run(),
|
|
2672
|
-
isActive: (editor) => editor.isActive("taskItem")
|
|
2673
|
-
},
|
|
2674
|
-
{
|
|
2675
|
-
name: "Bullet List",
|
|
2676
|
-
icon: List,
|
|
2677
|
-
command: (editor) => editor.chain().focus().clearNodes().toggleBulletList().run(),
|
|
2678
|
-
isActive: (editor) => editor.isActive("bulletList")
|
|
2679
|
-
},
|
|
2680
|
-
{
|
|
2681
|
-
name: "Numbered List",
|
|
2682
|
-
icon: ListOrdered,
|
|
2683
|
-
command: (editor) => editor.chain().focus().clearNodes().toggleOrderedList().run(),
|
|
2684
|
-
isActive: (editor) => editor.isActive("orderedList")
|
|
2685
|
-
},
|
|
2686
|
-
{
|
|
2687
|
-
name: "Quote",
|
|
2688
|
-
icon: TextQuote,
|
|
2689
|
-
command: (editor) => editor.chain().focus().clearNodes().toggleBlockquote().run(),
|
|
2690
|
-
isActive: (editor) => editor.isActive("blockquote")
|
|
2691
|
-
},
|
|
2692
|
-
{
|
|
2693
|
-
name: "Code",
|
|
2694
|
-
icon: Code,
|
|
2695
|
-
command: (editor) => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
|
|
2696
|
-
isActive: (editor) => editor.isActive("codeBlock")
|
|
2697
|
-
}
|
|
2698
|
-
];
|
|
2699
|
-
const NodeSelector = ({ open, onOpenChange }) => {
|
|
2700
|
-
const { editor } = useCurrentEditor();
|
|
2701
|
-
if (!editor) return null;
|
|
2702
|
-
const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
|
|
2703
|
-
name: "Multiple"
|
|
2704
|
-
};
|
|
2705
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Popover, { open, onOpenChange, children: [
|
|
2706
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2707
|
-
Button,
|
|
2708
|
-
{
|
|
2709
|
-
size: "sm",
|
|
2710
|
-
variant: "ghost",
|
|
2711
|
-
className: "gap-2 rounded-none border-none hover:bg-accent focus:ring-0",
|
|
2712
|
-
children: [
|
|
2713
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap text-sm", children: activeItem.name }),
|
|
2714
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "h-4 w-4" })
|
|
2715
|
-
]
|
|
2716
|
-
}
|
|
2717
|
-
) }),
|
|
2718
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(PopoverContent, { sideOffset: 5, align: "start", className: "w-48 p-1", children: items.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2719
|
-
w,
|
|
2720
|
-
{
|
|
2721
|
-
onSelect: (ed) => {
|
|
2722
|
-
item.command(ed);
|
|
2723
|
-
onOpenChange(false);
|
|
2724
|
-
},
|
|
2725
|
-
className: "flex cursor-pointer items-center justify-between rounded-sm px-2 py-1 text-sm hover:bg-accent",
|
|
2726
|
-
children: [
|
|
2727
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
2728
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-sm border p-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(item.icon, { className: "h-3 w-3" }) }),
|
|
2729
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: item.name })
|
|
2730
|
-
] }),
|
|
2731
|
-
activeItem.name === item.name && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "h-4 w-4" })
|
|
2732
|
-
]
|
|
2733
|
-
},
|
|
2734
|
-
item.name
|
|
2735
|
-
)) })
|
|
2736
|
-
] });
|
|
2737
|
-
};
|
|
2738
|
-
|
|
2739
|
-
const TextButtons = () => {
|
|
2740
|
-
const { editor } = useCurrentEditor();
|
|
2741
|
-
if (!editor) return null;
|
|
2742
|
-
const items = [
|
|
2743
|
-
{
|
|
2744
|
-
name: "bold",
|
|
2745
|
-
isActive: (ed) => ed.isActive("bold"),
|
|
2746
|
-
command: (ed) => ed.chain().focus().toggleBold().run(),
|
|
2747
|
-
icon: Bold
|
|
2748
|
-
},
|
|
2749
|
-
{
|
|
2750
|
-
name: "italic",
|
|
2751
|
-
isActive: (ed) => ed.isActive("italic"),
|
|
2752
|
-
command: (ed) => ed.chain().focus().toggleItalic().run(),
|
|
2753
|
-
icon: Italic
|
|
2754
|
-
},
|
|
2755
|
-
{
|
|
2756
|
-
name: "underline",
|
|
2757
|
-
isActive: (ed) => ed.isActive("underline"),
|
|
2758
|
-
command: (ed) => ed.chain().focus().toggleUnderline().run(),
|
|
2759
|
-
icon: Underline
|
|
2760
|
-
},
|
|
2761
|
-
{
|
|
2762
|
-
name: "strike",
|
|
2763
|
-
isActive: (ed) => ed.isActive("strike"),
|
|
2764
|
-
command: (ed) => ed.chain().focus().toggleStrike().run(),
|
|
2765
|
-
icon: Strikethrough
|
|
2766
|
-
},
|
|
2767
|
-
{
|
|
2768
|
-
name: "code",
|
|
2769
|
-
isActive: (ed) => ed.isActive("code"),
|
|
2770
|
-
command: (ed) => ed.chain().focus().toggleCode().run(),
|
|
2771
|
-
icon: Code
|
|
2772
|
-
}
|
|
2773
|
-
];
|
|
2774
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex", children: items.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2775
|
-
w,
|
|
2776
|
-
{
|
|
2777
|
-
onSelect: (ed) => {
|
|
2778
|
-
item.command(ed);
|
|
2779
|
-
},
|
|
2780
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2781
|
-
Button,
|
|
2782
|
-
{
|
|
2783
|
-
size: "sm",
|
|
2784
|
-
className: "rounded-none",
|
|
2785
|
-
variant: "ghost",
|
|
2786
|
-
type: "button",
|
|
2787
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2788
|
-
item.icon,
|
|
2789
|
-
{
|
|
2790
|
-
className: cn("h-4 w-4", {
|
|
2791
|
-
"text-blue-500": item.isActive(editor)
|
|
2792
|
-
})
|
|
2793
|
-
}
|
|
2794
|
-
)
|
|
2795
|
-
}
|
|
2796
|
-
)
|
|
2797
|
-
},
|
|
2798
|
-
item.name
|
|
2799
|
-
)) });
|
|
2800
|
-
};
|
|
2801
|
-
|
|
2802
|
-
function isValidUrl(url) {
|
|
2803
|
-
try {
|
|
2804
|
-
new URL(url);
|
|
2805
|
-
return true;
|
|
2806
|
-
} catch (_e) {
|
|
2807
|
-
return false;
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
function getUrlFromString(str) {
|
|
2811
|
-
if (isValidUrl(str)) return str;
|
|
2812
|
-
try {
|
|
2813
|
-
if (str.includes(".") && !str.includes(" ")) {
|
|
2814
|
-
return new URL(`https://${str}`).toString();
|
|
2815
|
-
}
|
|
2816
|
-
} catch (_e) {
|
|
2817
|
-
return null;
|
|
2818
|
-
}
|
|
2819
|
-
return null;
|
|
2820
|
-
}
|
|
2821
|
-
const LinkSelector = ({ open, onOpenChange }) => {
|
|
2822
|
-
const inputRef = reactExports.useRef(null);
|
|
2823
|
-
const { editor } = useCurrentEditor();
|
|
2824
|
-
reactExports.useEffect(() => {
|
|
2825
|
-
if (open) {
|
|
2826
|
-
inputRef.current?.focus();
|
|
2827
|
-
}
|
|
2828
|
-
}, [open]);
|
|
2829
|
-
if (!editor) return null;
|
|
2830
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Popover, { open, onOpenChange, children: [
|
|
2831
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2832
|
-
Button,
|
|
2833
|
-
{
|
|
2834
|
-
size: "sm",
|
|
2835
|
-
variant: "ghost",
|
|
2836
|
-
className: "gap-2 rounded-none border-none",
|
|
2837
|
-
children: [
|
|
2838
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-base", children: "↗" }),
|
|
2839
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2840
|
-
"p",
|
|
2841
|
-
{
|
|
2842
|
-
className: cn("underline decoration-stone-400 underline-offset-4", {
|
|
2843
|
-
"text-blue-500": editor.isActive("link")
|
|
2844
|
-
}),
|
|
2845
|
-
children: "Link"
|
|
2846
|
-
}
|
|
2847
|
-
)
|
|
2848
|
-
]
|
|
2849
|
-
}
|
|
2850
|
-
) }),
|
|
2851
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(PopoverContent, { align: "start", className: "w-60 p-0", sideOffset: 10, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2852
|
-
"form",
|
|
2853
|
-
{
|
|
2854
|
-
onSubmit: (e) => {
|
|
2855
|
-
const target = e.currentTarget;
|
|
2856
|
-
e.preventDefault();
|
|
2857
|
-
const input = target[0];
|
|
2858
|
-
const url = getUrlFromString(input.value);
|
|
2859
|
-
if (url) {
|
|
2860
|
-
editor.chain().focus().setLink({ href: url }).run();
|
|
2861
|
-
onOpenChange(false);
|
|
2862
|
-
}
|
|
2863
|
-
},
|
|
2864
|
-
className: "flex p-1",
|
|
2865
|
-
children: [
|
|
2866
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2867
|
-
"input",
|
|
2868
|
-
{
|
|
2869
|
-
ref: inputRef,
|
|
2870
|
-
type: "text",
|
|
2871
|
-
placeholder: "Paste a link",
|
|
2872
|
-
className: "flex-1 bg-background p-1 text-sm outline-none",
|
|
2873
|
-
defaultValue: editor.getAttributes("link").href || ""
|
|
2874
|
-
}
|
|
2875
|
-
),
|
|
2876
|
-
editor.getAttributes("link").href ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2877
|
-
Button,
|
|
2878
|
-
{
|
|
2879
|
-
size: "icon",
|
|
2880
|
-
variant: "outline",
|
|
2881
|
-
type: "button",
|
|
2882
|
-
className: "flex h-8 items-center rounded-sm p-1 text-red-600 transition-all hover:bg-red-100 dark:hover:bg-red-800",
|
|
2883
|
-
onClick: () => {
|
|
2884
|
-
editor.chain().focus().unsetLink().run();
|
|
2885
|
-
if (inputRef.current) {
|
|
2886
|
-
inputRef.current.value = "";
|
|
2887
|
-
}
|
|
2888
|
-
onOpenChange(false);
|
|
2889
|
-
},
|
|
2890
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash, { className: "h-4 w-4" })
|
|
2891
|
-
}
|
|
2892
|
-
) : /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { size: "icon", type: "submit", className: "h-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "h-4 w-4" }) })
|
|
2893
|
-
]
|
|
2894
|
-
}
|
|
2895
|
-
) })
|
|
2896
|
-
] });
|
|
2897
|
-
};
|
|
2898
|
-
|
|
2899
|
-
const TEXT_COLORS = [
|
|
2900
|
-
{
|
|
2901
|
-
name: "Default",
|
|
2902
|
-
color: "var(--novel-black)"
|
|
2903
|
-
},
|
|
2904
|
-
{
|
|
2905
|
-
name: "Purple",
|
|
2906
|
-
color: "#9333EA"
|
|
2907
|
-
},
|
|
2908
|
-
{
|
|
2909
|
-
name: "Red",
|
|
2910
|
-
color: "#E00000"
|
|
2911
|
-
},
|
|
2912
|
-
{
|
|
2913
|
-
name: "Yellow",
|
|
2914
|
-
color: "#EAB308"
|
|
2915
|
-
},
|
|
2916
|
-
{
|
|
2917
|
-
name: "Blue",
|
|
2918
|
-
color: "#2563EB"
|
|
2919
|
-
},
|
|
2920
|
-
{
|
|
2921
|
-
name: "Green",
|
|
2922
|
-
color: "#008A00"
|
|
2923
|
-
},
|
|
2924
|
-
{
|
|
2925
|
-
name: "Orange",
|
|
2926
|
-
color: "#FFA500"
|
|
2927
|
-
},
|
|
2928
|
-
{
|
|
2929
|
-
name: "Pink",
|
|
2930
|
-
color: "#BA4081"
|
|
2931
|
-
},
|
|
2932
|
-
{
|
|
2933
|
-
name: "Gray",
|
|
2934
|
-
color: "#A8A29E"
|
|
2935
|
-
}
|
|
2936
|
-
];
|
|
2937
|
-
const HIGHLIGHT_COLORS = [
|
|
2938
|
-
{
|
|
2939
|
-
name: "Default",
|
|
2940
|
-
color: "var(--novel-highlight-default)"
|
|
2941
|
-
},
|
|
2942
|
-
{
|
|
2943
|
-
name: "Purple",
|
|
2944
|
-
color: "var(--novel-highlight-purple)"
|
|
2945
|
-
},
|
|
2946
|
-
{
|
|
2947
|
-
name: "Red",
|
|
2948
|
-
color: "var(--novel-highlight-red)"
|
|
2949
|
-
},
|
|
2950
|
-
{
|
|
2951
|
-
name: "Yellow",
|
|
2952
|
-
color: "var(--novel-highlight-yellow)"
|
|
2953
|
-
},
|
|
2954
|
-
{
|
|
2955
|
-
name: "Blue",
|
|
2956
|
-
color: "var(--novel-highlight-blue)"
|
|
2957
|
-
},
|
|
2958
|
-
{
|
|
2959
|
-
name: "Green",
|
|
2960
|
-
color: "var(--novel-highlight-green)"
|
|
2961
|
-
},
|
|
2962
|
-
{
|
|
2963
|
-
name: "Orange",
|
|
2964
|
-
color: "var(--novel-highlight-orange)"
|
|
2965
|
-
},
|
|
2966
|
-
{
|
|
2967
|
-
name: "Pink",
|
|
2968
|
-
color: "var(--novel-highlight-pink)"
|
|
2969
|
-
},
|
|
2970
|
-
{
|
|
2971
|
-
name: "Gray",
|
|
2972
|
-
color: "var(--novel-highlight-gray)"
|
|
2973
|
-
}
|
|
2974
|
-
];
|
|
2975
|
-
const ColorSelector = ({ open, onOpenChange }) => {
|
|
2976
|
-
const { editor } = useCurrentEditor();
|
|
2977
|
-
if (!editor) return null;
|
|
2978
|
-
const activeColorItem = TEXT_COLORS.find(
|
|
2979
|
-
({ color }) => editor.isActive("textStyle", { color })
|
|
2980
|
-
);
|
|
2981
|
-
const activeHighlightItem = HIGHLIGHT_COLORS.find(
|
|
2982
|
-
({ color }) => editor.isActive("highlight", { color })
|
|
2983
|
-
);
|
|
2984
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Popover, { open, onOpenChange, children: [
|
|
2985
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { size: "sm", className: "gap-2 rounded-none", variant: "ghost", children: [
|
|
2986
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2987
|
-
"span",
|
|
2988
|
-
{
|
|
2989
|
-
className: "rounded-sm px-1",
|
|
2990
|
-
style: {
|
|
2991
|
-
color: activeColorItem?.color,
|
|
2992
|
-
backgroundColor: activeHighlightItem?.color
|
|
2993
|
-
},
|
|
2994
|
-
children: "A"
|
|
2995
|
-
}
|
|
2996
|
-
),
|
|
2997
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "h-4 w-4" })
|
|
2998
|
-
] }) }),
|
|
2999
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3000
|
-
PopoverContent,
|
|
3001
|
-
{
|
|
3002
|
-
sideOffset: 5,
|
|
3003
|
-
className: "my-1 flex max-h-80 w-48 flex-col overflow-hidden overflow-y-auto rounded border p-1 shadow-xl",
|
|
3004
|
-
align: "start",
|
|
3005
|
-
children: [
|
|
3006
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
|
|
3007
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "my-1 px-2 text-sm font-semibold text-muted-foreground", children: "Color" }),
|
|
3008
|
-
TEXT_COLORS.map(({ name, color }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3009
|
-
w,
|
|
3010
|
-
{
|
|
3011
|
-
onSelect: () => {
|
|
3012
|
-
editor.commands.unsetColor();
|
|
3013
|
-
name !== "Default" && editor.chain().focus().setColor(color || "").run();
|
|
3014
|
-
onOpenChange(false);
|
|
3015
|
-
},
|
|
3016
|
-
className: "flex cursor-pointer items-center justify-between px-2 py-1 text-sm hover:bg-accent",
|
|
3017
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3018
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3019
|
-
"div",
|
|
3020
|
-
{
|
|
3021
|
-
className: "rounded-sm border px-2 py-px font-medium",
|
|
3022
|
-
style: { color },
|
|
3023
|
-
children: "A"
|
|
3024
|
-
}
|
|
3025
|
-
),
|
|
3026
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: name })
|
|
3027
|
-
] })
|
|
3028
|
-
},
|
|
3029
|
-
name
|
|
3030
|
-
))
|
|
3031
|
-
] }),
|
|
3032
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
3033
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "my-1 px-2 text-sm font-semibold text-muted-foreground", children: "Background" }),
|
|
3034
|
-
HIGHLIGHT_COLORS.map(({ name, color }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3035
|
-
w,
|
|
3036
|
-
{
|
|
3037
|
-
onSelect: () => {
|
|
3038
|
-
editor.commands.unsetHighlight();
|
|
3039
|
-
name !== "Default" && editor.chain().focus().setHighlight({ color }).run();
|
|
3040
|
-
onOpenChange(false);
|
|
3041
|
-
},
|
|
3042
|
-
className: "flex cursor-pointer items-center justify-between px-2 py-1 text-sm hover:bg-accent",
|
|
3043
|
-
children: [
|
|
3044
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3045
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3046
|
-
"div",
|
|
3047
|
-
{
|
|
3048
|
-
className: "rounded-sm border px-2 py-px font-medium",
|
|
3049
|
-
style: { backgroundColor: color },
|
|
3050
|
-
children: "A"
|
|
3051
|
-
}
|
|
3052
|
-
),
|
|
3053
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: name })
|
|
3054
|
-
] }),
|
|
3055
|
-
editor.isActive("highlight", { color }) && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "h-4 w-4" })
|
|
3056
|
-
]
|
|
3057
|
-
},
|
|
3058
|
-
name
|
|
3059
|
-
))
|
|
3060
|
-
] })
|
|
3061
|
-
]
|
|
3062
|
-
}
|
|
3063
|
-
)
|
|
3064
|
-
] });
|
|
3065
|
-
};
|
|
3066
|
-
|
|
3067
|
-
const MathSelector = () => {
|
|
3068
|
-
const { editor } = useCurrentEditor();
|
|
3069
|
-
if (!editor) return null;
|
|
3070
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3071
|
-
Button,
|
|
3072
|
-
{
|
|
3073
|
-
variant: "ghost",
|
|
3074
|
-
size: "sm",
|
|
3075
|
-
className: "rounded-none w-12",
|
|
3076
|
-
onClick: () => {
|
|
3077
|
-
if (editor.isActive("math")) {
|
|
3078
|
-
editor.chain().focus().unsetLatex().run();
|
|
3079
|
-
} else {
|
|
3080
|
-
const { from, to } = editor.state.selection;
|
|
3081
|
-
const latex = editor.state.doc.textBetween(from, to);
|
|
3082
|
-
if (!latex) return;
|
|
3083
|
-
editor.chain().focus().setLatex({ latex }).run();
|
|
3084
|
-
}
|
|
3085
|
-
},
|
|
3086
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3087
|
-
Sigma,
|
|
3088
|
-
{
|
|
3089
|
-
className: cn("size-4", { "text-blue-500": editor.isActive("math") }),
|
|
3090
|
-
strokeWidth: 2.3
|
|
3091
|
-
}
|
|
3092
|
-
)
|
|
3093
|
-
}
|
|
3094
|
-
);
|
|
3095
|
-
};
|
|
3096
|
-
|
|
3097
|
-
const TABLE_BUBBLE_MENU_KEY = "notebook-table-bubble-menu";
|
|
3098
|
-
const TableActionButton = ({
|
|
3099
|
-
label,
|
|
3100
|
-
onClick,
|
|
3101
|
-
disabled,
|
|
3102
|
-
icon: Icon,
|
|
3103
|
-
tone = "default"
|
|
3104
|
-
}) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3105
|
-
Button,
|
|
3106
|
-
{
|
|
3107
|
-
type: "button",
|
|
3108
|
-
size: "icon",
|
|
3109
|
-
variant: "ghost",
|
|
3110
|
-
disabled,
|
|
3111
|
-
onClick,
|
|
3112
|
-
title: label,
|
|
3113
|
-
className: cn(
|
|
3114
|
-
"h-8 w-8",
|
|
3115
|
-
tone === "destructive" && "text-destructive hover:text-destructive"
|
|
3116
|
-
),
|
|
3117
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { className: "h-4 w-4" })
|
|
3118
|
-
}
|
|
3119
|
-
);
|
|
3120
|
-
const TableControls = ({ className }) => {
|
|
3121
|
-
const { editor } = useCurrentEditor();
|
|
3122
|
-
if (!editor) return null;
|
|
3123
|
-
const can = editor.can();
|
|
3124
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3125
|
-
"div",
|
|
3126
|
-
{
|
|
3127
|
-
className: cn(
|
|
3128
|
-
"flex flex-wrap items-center gap-1 rounded-md border border-muted bg-background p-1 shadow-md",
|
|
3129
|
-
className
|
|
3130
|
-
),
|
|
3131
|
-
children: [
|
|
3132
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3133
|
-
TableActionButton,
|
|
3134
|
-
{
|
|
3135
|
-
label: "Add column before",
|
|
3136
|
-
icon: ArrowLeft,
|
|
3137
|
-
onClick: () => editor.chain().focus().addColumnBefore().run(),
|
|
3138
|
-
disabled: !can.addColumnBefore()
|
|
3139
|
-
}
|
|
3140
|
-
),
|
|
3141
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3142
|
-
TableActionButton,
|
|
3143
|
-
{
|
|
3144
|
-
label: "Add column after",
|
|
3145
|
-
icon: ArrowRight,
|
|
3146
|
-
onClick: () => editor.chain().focus().addColumnAfter().run(),
|
|
3147
|
-
disabled: !can.addColumnAfter()
|
|
3148
|
-
}
|
|
3149
|
-
),
|
|
3150
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { orientation: "vertical", className: "h-5" }),
|
|
3151
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3152
|
-
TableActionButton,
|
|
3153
|
-
{
|
|
3154
|
-
label: "Add row above",
|
|
3155
|
-
icon: ArrowUp,
|
|
3156
|
-
onClick: () => editor.chain().focus().addRowBefore().run(),
|
|
3157
|
-
disabled: !can.addRowBefore()
|
|
3158
|
-
}
|
|
3159
|
-
),
|
|
3160
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3161
|
-
TableActionButton,
|
|
3162
|
-
{
|
|
3163
|
-
label: "Add row below",
|
|
3164
|
-
icon: ArrowDown,
|
|
3165
|
-
onClick: () => editor.chain().focus().addRowAfter().run(),
|
|
3166
|
-
disabled: !can.addRowAfter()
|
|
3167
|
-
}
|
|
3168
|
-
),
|
|
3169
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { orientation: "vertical", className: "h-5" }),
|
|
3170
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3171
|
-
TableActionButton,
|
|
3172
|
-
{
|
|
3173
|
-
label: "Toggle header row",
|
|
3174
|
-
icon: Heading1,
|
|
3175
|
-
onClick: () => editor.chain().focus().toggleHeaderRow().run(),
|
|
3176
|
-
disabled: !can.toggleHeaderRow()
|
|
3177
|
-
}
|
|
3178
|
-
),
|
|
3179
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { orientation: "vertical", className: "h-5" }),
|
|
3180
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3181
|
-
TableActionButton,
|
|
3182
|
-
{
|
|
3183
|
-
label: "Delete column",
|
|
3184
|
-
icon: Minus,
|
|
3185
|
-
onClick: () => editor.chain().focus().deleteColumn().run(),
|
|
3186
|
-
disabled: !can.deleteColumn()
|
|
3187
|
-
}
|
|
3188
|
-
),
|
|
3189
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3190
|
-
TableActionButton,
|
|
3191
|
-
{
|
|
3192
|
-
label: "Delete row",
|
|
3193
|
-
icon: Minus,
|
|
3194
|
-
onClick: () => editor.chain().focus().deleteRow().run(),
|
|
3195
|
-
disabled: !can.deleteRow()
|
|
3196
|
-
}
|
|
3197
|
-
),
|
|
3198
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3199
|
-
TableActionButton,
|
|
3200
|
-
{
|
|
3201
|
-
label: "Delete table",
|
|
3202
|
-
icon: Trash2,
|
|
3203
|
-
onClick: () => editor.chain().focus().deleteTable().run(),
|
|
3204
|
-
disabled: !can.deleteTable(),
|
|
3205
|
-
tone: "destructive"
|
|
3206
|
-
}
|
|
3207
|
-
)
|
|
3208
|
-
]
|
|
3209
|
-
}
|
|
3210
|
-
);
|
|
3211
|
-
};
|
|
3212
|
-
function getActiveTableElement(editor) {
|
|
3213
|
-
const { $from } = editor.state.selection;
|
|
3214
|
-
const tableNode = findParentNodeClosestToPos(
|
|
3215
|
-
$from,
|
|
3216
|
-
(node) => node.type.name === "table"
|
|
3217
|
-
);
|
|
3218
|
-
if (!tableNode) return null;
|
|
3219
|
-
const dom = editor.view.nodeDOM(tableNode.pos);
|
|
3220
|
-
return dom instanceof HTMLElement ? dom : null;
|
|
3221
|
-
}
|
|
3222
|
-
const TableBubbleMenu = ({ className }) => {
|
|
3223
|
-
const { editor } = useCurrentEditor();
|
|
3224
|
-
if (!editor || editor.isDestroyed) return null;
|
|
3225
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3226
|
-
P,
|
|
3227
|
-
{
|
|
3228
|
-
pluginKey: TABLE_BUBBLE_MENU_KEY,
|
|
3229
|
-
shouldShow: ({ editor: current }) => current.isEditable && current.isActive("table"),
|
|
3230
|
-
tippyOptions: {
|
|
3231
|
-
placement: "bottom",
|
|
3232
|
-
moveTransition: "transform 0.15s ease-out"
|
|
3233
|
-
},
|
|
3234
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(TableControls, { className })
|
|
3235
|
-
}
|
|
3236
|
-
);
|
|
3237
|
-
};
|
|
3238
|
-
const TableToolbar = ({ className }) => {
|
|
3239
|
-
const { editor } = useCurrentEditor();
|
|
3240
|
-
const [position, setPosition] = reactExports.useState(null);
|
|
3241
|
-
const updatePosition = reactExports.useCallback(() => {
|
|
3242
|
-
if (!editor || editor.isDestroyed || !editor.isEditable) {
|
|
3243
|
-
setPosition(null);
|
|
3244
|
-
return;
|
|
3245
|
-
}
|
|
3246
|
-
if (!editor.isActive("table")) {
|
|
3247
|
-
setPosition(null);
|
|
3248
|
-
return;
|
|
3249
|
-
}
|
|
3250
|
-
const table = getActiveTableElement(editor);
|
|
3251
|
-
if (!table) {
|
|
3252
|
-
setPosition(null);
|
|
3253
|
-
return;
|
|
3254
|
-
}
|
|
3255
|
-
const container = editor.view.dom.parentElement || editor.view.dom;
|
|
3256
|
-
const containerRect = container.getBoundingClientRect();
|
|
3257
|
-
const tableRect = table.getBoundingClientRect();
|
|
3258
|
-
const top = tableRect.top - containerRect.top + container.scrollTop - 12;
|
|
3259
|
-
const left = tableRect.left - containerRect.left + container.scrollLeft;
|
|
3260
|
-
setPosition({ top, left, width: tableRect.width });
|
|
3261
|
-
}, [editor]);
|
|
3262
|
-
const scheduleUpdate = reactExports.useMemo(() => {
|
|
3263
|
-
let frame = 0;
|
|
3264
|
-
const handler = () => {
|
|
3265
|
-
if (frame) return;
|
|
3266
|
-
frame = window.requestAnimationFrame(() => {
|
|
3267
|
-
frame = 0;
|
|
3268
|
-
updatePosition();
|
|
3269
|
-
});
|
|
3270
|
-
};
|
|
3271
|
-
return handler;
|
|
3272
|
-
}, [updatePosition]);
|
|
3273
|
-
reactExports.useEffect(() => {
|
|
3274
|
-
if (!editor || editor.isDestroyed) return;
|
|
3275
|
-
const container = editor.view.dom.parentElement || editor.view.dom;
|
|
3276
|
-
updatePosition();
|
|
3277
|
-
editor.on("selectionUpdate", scheduleUpdate);
|
|
3278
|
-
editor.on("transaction", scheduleUpdate);
|
|
3279
|
-
editor.on("focus", scheduleUpdate);
|
|
3280
|
-
editor.on("blur", scheduleUpdate);
|
|
3281
|
-
container.addEventListener("scroll", scheduleUpdate, { passive: true });
|
|
3282
|
-
window.addEventListener("resize", scheduleUpdate);
|
|
3283
|
-
return () => {
|
|
3284
|
-
editor.off("selectionUpdate", scheduleUpdate);
|
|
3285
|
-
editor.off("transaction", scheduleUpdate);
|
|
3286
|
-
editor.off("focus", scheduleUpdate);
|
|
3287
|
-
editor.off("blur", scheduleUpdate);
|
|
3288
|
-
container.removeEventListener("scroll", scheduleUpdate);
|
|
3289
|
-
window.removeEventListener("resize", scheduleUpdate);
|
|
3290
|
-
};
|
|
3291
|
-
}, [editor, scheduleUpdate, updatePosition]);
|
|
3292
|
-
if (!editor || editor.isDestroyed || !position) return null;
|
|
3293
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3294
|
-
"div",
|
|
3295
|
-
{
|
|
3296
|
-
className: cn(
|
|
3297
|
-
"absolute z-30",
|
|
3298
|
-
className
|
|
3299
|
-
),
|
|
3300
|
-
style: {
|
|
3301
|
-
top: position.top,
|
|
3302
|
-
left: position.left,
|
|
3303
|
-
width: position.width
|
|
3304
|
-
},
|
|
3305
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(TableControls, { className: "w-full justify-start" })
|
|
3306
|
-
}
|
|
3307
|
-
);
|
|
3308
|
-
};
|
|
3309
|
-
|
|
3310
|
-
const EditorBubbleMenu = () => {
|
|
3311
|
-
const { editor } = useCurrentEditor();
|
|
3312
|
-
const [openNode, setOpenNode] = reactExports.useState(false);
|
|
3313
|
-
const [openColor, setOpenColor] = reactExports.useState(false);
|
|
3314
|
-
const [openLink, setOpenLink] = reactExports.useState(false);
|
|
3315
|
-
if (!editor) return null;
|
|
3316
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3317
|
-
P,
|
|
3318
|
-
{
|
|
3319
|
-
tippyOptions: {
|
|
3320
|
-
placement: "top",
|
|
3321
|
-
onHidden: () => {
|
|
3322
|
-
setOpenNode(false);
|
|
3323
|
-
setOpenColor(false);
|
|
3324
|
-
setOpenLink(false);
|
|
3325
|
-
}
|
|
3326
|
-
},
|
|
3327
|
-
className: "flex w-fit max-w-[90vw] overflow-hidden rounded-md border border-muted bg-background shadow-xl",
|
|
3328
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Fragment, { children: [
|
|
3329
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(NodeSelector, { open: openNode, onOpenChange: setOpenNode }),
|
|
3330
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { orientation: "vertical" }),
|
|
3331
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LinkSelector, { open: openLink, onOpenChange: setOpenLink }),
|
|
3332
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { orientation: "vertical" }),
|
|
3333
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(MathSelector, {}),
|
|
3334
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { orientation: "vertical" }),
|
|
3335
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TextButtons, {}),
|
|
3336
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { orientation: "vertical" }),
|
|
3337
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(ColorSelector, { open: openColor, onOpenChange: setOpenColor })
|
|
3338
|
-
] })
|
|
3339
|
-
}
|
|
3340
|
-
);
|
|
3341
|
-
};
|
|
3342
|
-
|
|
3343
|
-
function SkeletonLine({
|
|
3344
|
-
width,
|
|
3345
|
-
className = ""
|
|
3346
|
-
}) {
|
|
3347
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3348
|
-
"div",
|
|
3349
|
-
{
|
|
3350
|
-
className: `h-4 bg-muted rounded animate-pulse ${className}`,
|
|
3351
|
-
style: { width }
|
|
3352
|
-
}
|
|
3353
|
-
);
|
|
3354
|
-
}
|
|
3355
|
-
function SkeletonBlock() {
|
|
3356
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3357
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonLine, { width: "85%" }),
|
|
3358
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonLine, { width: "70%" }),
|
|
3359
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonLine, { width: "90%" })
|
|
3360
|
-
] });
|
|
3361
|
-
}
|
|
3362
|
-
function EditorLoading({ message = "Loading..." }) {
|
|
3363
|
-
const { t } = useI18n("notebook");
|
|
3364
|
-
const resolvedMessage = message === "Loading..." ? t("loading") : message;
|
|
3365
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "editor-loading h-full flex flex-col bg-background", children: [
|
|
3366
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b", children: [
|
|
3367
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-32 h-7 bg-muted rounded animate-pulse" }) }),
|
|
3368
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3369
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 bg-muted rounded animate-pulse" }),
|
|
3370
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 bg-muted rounded animate-pulse" }),
|
|
3371
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 bg-muted rounded animate-pulse" })
|
|
3372
|
-
] })
|
|
3373
|
-
] }),
|
|
3374
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-[900px] mx-auto px-16 py-12 space-y-8", children: [
|
|
3375
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-10 bg-muted rounded animate-pulse w-2/3" }),
|
|
3376
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
|
|
3377
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonBlock, {}),
|
|
3378
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonBlock, {}),
|
|
3379
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pt-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-6 bg-muted rounded animate-pulse w-1/2" }) }),
|
|
3380
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonBlock, {}),
|
|
3381
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-muted/50 rounded-md p-4 space-y-2", children: [
|
|
3382
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonLine, { width: "60%", className: "h-3" }),
|
|
3383
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonLine, { width: "80%", className: "h-3" }),
|
|
3384
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonLine, { width: "45%", className: "h-3" }),
|
|
3385
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonLine, { width: "70%", className: "h-3" })
|
|
3386
|
-
] }),
|
|
3387
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonBlock, {})
|
|
3388
|
-
] })
|
|
3389
|
-
] }) }),
|
|
3390
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-3 bg-background/80 px-6 py-4 rounded-lg shadow-lg backdrop-blur-sm", children: [
|
|
3391
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-8 h-8", children: [
|
|
3392
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 border-2 border-muted rounded-full" }),
|
|
3393
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 border-2 border-primary border-t-transparent rounded-full animate-spin" })
|
|
3394
|
-
] }),
|
|
3395
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-muted-foreground", children: resolvedMessage })
|
|
3396
|
-
] }) })
|
|
3397
|
-
] });
|
|
3398
|
-
}
|
|
3399
|
-
|
|
3400
|
-
function SaveStatusIndicator({ status }) {
|
|
3401
|
-
const { t } = useI18n("notebook");
|
|
3402
|
-
switch (status) {
|
|
3403
|
-
case "saving":
|
|
3404
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
3405
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-3 h-3 animate-spin" }),
|
|
3406
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("saving") })
|
|
3407
|
-
] });
|
|
3408
|
-
case "saved":
|
|
3409
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-green-600", children: [
|
|
3410
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "w-3 h-3" }),
|
|
3411
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("saved") })
|
|
3412
|
-
] });
|
|
3413
|
-
case "error":
|
|
3414
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive", children: [
|
|
3415
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "w-3 h-3" }),
|
|
3416
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("save_failed") })
|
|
3417
|
-
] });
|
|
3418
|
-
default:
|
|
3419
|
-
return null;
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
function NotebookToolbar({
|
|
3423
|
-
notebookId,
|
|
3424
|
-
autoSaveStatus,
|
|
3425
|
-
getMarkdown,
|
|
3426
|
-
allowCopy = true
|
|
3427
|
-
}) {
|
|
3428
|
-
const [isCopying, setIsCopying] = reactExports.useState(false);
|
|
3429
|
-
const { toast } = useToast();
|
|
3430
|
-
const { t } = useI18n("notebook");
|
|
3431
|
-
const canCopy = allowCopy && (Boolean(notebookId) || Boolean(getMarkdown));
|
|
3432
|
-
const resolveMarkdownContent = reactExports.useCallback(async () => {
|
|
3433
|
-
if (getMarkdown) {
|
|
3434
|
-
const content = await Promise.resolve(getMarkdown());
|
|
3435
|
-
if (typeof content === "string") return content;
|
|
3436
|
-
}
|
|
3437
|
-
if (!notebookId) return "";
|
|
3438
|
-
return await getFileContent(notebookId);
|
|
3439
|
-
}, [getMarkdown, notebookId]);
|
|
3440
|
-
const handleCopy = reactExports.useCallback(async () => {
|
|
3441
|
-
if (!canCopy) return;
|
|
3442
|
-
setIsCopying(true);
|
|
3443
|
-
try {
|
|
3444
|
-
const markdown = await resolveMarkdownContent();
|
|
3445
|
-
if (navigator?.clipboard?.writeText) {
|
|
3446
|
-
await navigator.clipboard.writeText(markdown);
|
|
3447
|
-
} else {
|
|
3448
|
-
const textarea = document.createElement("textarea");
|
|
3449
|
-
textarea.value = markdown;
|
|
3450
|
-
textarea.style.position = "fixed";
|
|
3451
|
-
textarea.style.opacity = "0";
|
|
3452
|
-
document.body.appendChild(textarea);
|
|
3453
|
-
textarea.focus();
|
|
3454
|
-
textarea.select();
|
|
3455
|
-
document.execCommand("copy");
|
|
3456
|
-
textarea.remove();
|
|
3457
|
-
}
|
|
3458
|
-
toast({
|
|
3459
|
-
title: t("copy_success_title"),
|
|
3460
|
-
description: t("copy_success_desc"),
|
|
3461
|
-
variant: "success"
|
|
3462
|
-
});
|
|
3463
|
-
} catch (error) {
|
|
3464
|
-
console.error("[NotebookToolbar] Copy failed:", error);
|
|
3465
|
-
toast({
|
|
3466
|
-
title: t("copy_failed_title"),
|
|
3467
|
-
description: t("try_again"),
|
|
3468
|
-
variant: "destructive"
|
|
3469
|
-
});
|
|
3470
|
-
} finally {
|
|
3471
|
-
setIsCopying(false);
|
|
3472
|
-
}
|
|
3473
|
-
}, [canCopy, resolveMarkdownContent, t, toast]);
|
|
3474
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "notebook-toolbar flex items-center justify-between px-4 py-2 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60", children: [
|
|
3475
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SaveStatusIndicator, { status: autoSaveStatus }) }),
|
|
3476
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1", children: canCopy ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3477
|
-
"button",
|
|
3478
|
-
{
|
|
3479
|
-
type: "button",
|
|
3480
|
-
className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors",
|
|
3481
|
-
onClick: handleCopy,
|
|
3482
|
-
title: t("copy"),
|
|
3483
|
-
children: isCopying ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "w-4 h-4" })
|
|
3484
|
-
}
|
|
3485
|
-
) : null })
|
|
3486
|
-
] });
|
|
3487
|
-
}
|
|
3488
|
-
|
|
3489
|
-
const DEFAULT_DOC = {
|
|
3490
|
-
type: "doc",
|
|
3491
|
-
content: [{ type: "paragraph" }]
|
|
3492
|
-
};
|
|
3493
|
-
const YJS_FIELD = "prosemirror";
|
|
3494
|
-
function isEditorDocEmpty(editor) {
|
|
3495
|
-
const doc = editor.state.doc;
|
|
3496
|
-
if (doc.childCount !== 1) return false;
|
|
3497
|
-
const first = doc.firstChild;
|
|
3498
|
-
if (!first) return true;
|
|
3499
|
-
if (first.type.name !== "paragraph") return false;
|
|
3500
|
-
return first.content.size === 0 && doc.textContent.trim().length === 0;
|
|
3501
|
-
}
|
|
3502
|
-
function NotebookEditor({
|
|
3503
|
-
context,
|
|
3504
|
-
setDirty,
|
|
3505
|
-
setTitle,
|
|
3506
|
-
className,
|
|
3507
|
-
onMarkdownChange
|
|
3508
|
-
}) {
|
|
3509
|
-
const notebookId = context.resourceId;
|
|
3510
|
-
const notebookName = context.resourceName || "Untitled Notebook";
|
|
3511
|
-
const inlineMarkdown = typeof context.customData?.inlineMarkdown === "string" ? context.customData.inlineMarkdown : null;
|
|
3512
|
-
const isInline = inlineMarkdown !== null;
|
|
3513
|
-
const isReadonly = context.customData?.readonly === true || isInline;
|
|
3514
|
-
const fallbackProjectId = useFileTreeStore((state) => state.projectId);
|
|
3515
|
-
const projectId = typeof context.customData?.projectId === "string" ? context.customData.projectId : fallbackProjectId || void 0;
|
|
3516
|
-
const filePath = context.resourcePath ?? void 0;
|
|
3517
|
-
const docKind = inferNotebookDocKind(context);
|
|
3518
|
-
const isMarkdownDoc = docKind === "markdown";
|
|
3519
|
-
const markdownAssetContext = reactExports.useMemo(
|
|
3520
|
-
() => isMarkdownDoc ? getQuestMarkdownContextFromFileId(notebookId) : null,
|
|
3521
|
-
[isMarkdownDoc, notebookId]
|
|
3522
|
-
);
|
|
3523
|
-
const providerRef = reactExports.useRef(null);
|
|
3524
|
-
const projectIdRef = reactExports.useRef(projectId);
|
|
3525
|
-
const saveTimeoutRef = reactExports.useRef(null);
|
|
3526
|
-
const markdownChangeTimeoutRef = reactExports.useRef(null);
|
|
3527
|
-
const baselineMarkdownRef = reactExports.useRef(null);
|
|
3528
|
-
const activeNotebookIdRef = reactExports.useRef(null);
|
|
3529
|
-
const currentNotebookIdRef = reactExports.useRef(null);
|
|
3530
|
-
const suppressMarkdownUpdateRef = reactExports.useRef(false);
|
|
3531
|
-
const [editor, setEditor] = reactExports.useState(null);
|
|
3532
|
-
const [ydoc, setYdoc] = reactExports.useState(null);
|
|
3533
|
-
const [initialMarkdown, setInitialMarkdown] = reactExports.useState(null);
|
|
3534
|
-
const baselineNotebookMarkdownRef = reactExports.useRef(null);
|
|
3535
|
-
const [loadedNotebookId, setLoadedNotebookId] = reactExports.useState(null);
|
|
3536
|
-
const [isInitializing, setIsInitializing] = reactExports.useState(true);
|
|
3537
|
-
const [error, setError] = reactExports.useState(null);
|
|
3538
|
-
const [autoSaveStatus, setAutoSaveStatus] = reactExports.useState("idle");
|
|
3539
|
-
const [resetNonce, setResetNonce] = reactExports.useState(0);
|
|
3540
|
-
const { diffEvent, clearDiff } = useFileDiffOverlay({
|
|
3541
|
-
fileId: notebookId ?? void 0,
|
|
3542
|
-
filePath,
|
|
3543
|
-
projectId
|
|
3544
|
-
});
|
|
3545
|
-
const getCurrentMarkdown = reactExports.useCallback(() => {
|
|
3546
|
-
if (editor) return getEditorMarkdown(editor);
|
|
3547
|
-
if (typeof initialMarkdown === "string") return initialMarkdown;
|
|
3548
|
-
if (typeof baselineNotebookMarkdownRef.current === "string") {
|
|
3549
|
-
return baselineNotebookMarkdownRef.current;
|
|
3550
|
-
}
|
|
3551
|
-
return "";
|
|
3552
|
-
}, [editor, initialMarkdown]);
|
|
3553
|
-
const toDisplayMarkdown = reactExports.useCallback(
|
|
3554
|
-
(markdown) => isMarkdownDoc ? rewriteQuestMarkdownForDisplay(markdown, markdownAssetContext) : markdown,
|
|
3555
|
-
[isMarkdownDoc, markdownAssetContext]
|
|
3556
|
-
);
|
|
3557
|
-
const toStoredMarkdown = reactExports.useCallback(
|
|
3558
|
-
(markdown) => isMarkdownDoc ? rewriteQuestMarkdownForSave(markdown, markdownAssetContext) : markdown,
|
|
3559
|
-
[isMarkdownDoc, markdownAssetContext]
|
|
3560
|
-
);
|
|
3561
|
-
const reloadMarkdownFromServer = reactExports.useCallback(
|
|
3562
|
-
async (force) => {
|
|
3563
|
-
if (!editor || !notebookId || !isMarkdownDoc) return;
|
|
3564
|
-
const currentMarkdown = getEditorMarkdown(editor);
|
|
3565
|
-
const baseline = baselineMarkdownRef.current ?? currentMarkdown;
|
|
3566
|
-
if (!force && currentMarkdown !== baseline) return;
|
|
3567
|
-
try {
|
|
3568
|
-
const latest = toDisplayMarkdown(await getFileContent(notebookId));
|
|
3569
|
-
suppressMarkdownUpdateRef.current = true;
|
|
3570
|
-
setEditorMarkdown(editor, latest);
|
|
3571
|
-
baselineMarkdownRef.current = latest;
|
|
3572
|
-
} catch (error2) {
|
|
3573
|
-
console.warn("[NotebookEditor] Failed to refresh markdown", error2);
|
|
3574
|
-
}
|
|
3575
|
-
},
|
|
3576
|
-
[editor, isMarkdownDoc, notebookId, toDisplayMarkdown]
|
|
3577
|
-
);
|
|
3578
|
-
const notebookIdValue = notebookId ?? null;
|
|
3579
|
-
if (currentNotebookIdRef.current !== notebookIdValue) {
|
|
3580
|
-
currentNotebookIdRef.current = notebookIdValue;
|
|
3581
|
-
activeNotebookIdRef.current = null;
|
|
3582
|
-
}
|
|
3583
|
-
reactExports.useEffect(() => {
|
|
3584
|
-
if (!diffEvent?.diff) return;
|
|
3585
|
-
void reloadMarkdownFromServer(false);
|
|
3586
|
-
}, [diffEvent, reloadMarkdownFromServer]);
|
|
3587
|
-
reactExports.useEffect(() => {
|
|
3588
|
-
if (!notebookId || isInline) return;
|
|
3589
|
-
const handleReload = (event) => {
|
|
3590
|
-
const detail = event.detail;
|
|
3591
|
-
if (!detail) return;
|
|
3592
|
-
if (detail.fileId && detail.fileId !== notebookId) return;
|
|
3593
|
-
if (!detail.fileId && detail.filePath && filePath && detail.filePath !== filePath) {
|
|
3594
|
-
return;
|
|
3595
|
-
}
|
|
3596
|
-
if (!detail.fileId && !detail.filePath) return;
|
|
3597
|
-
void reloadMarkdownFromServer(Boolean(detail.force));
|
|
3598
|
-
};
|
|
3599
|
-
window.addEventListener("ds:file:reload", handleReload);
|
|
3600
|
-
return () => window.removeEventListener("ds:file:reload", handleReload);
|
|
3601
|
-
}, [filePath, isInline, notebookId, reloadMarkdownFromServer]);
|
|
3602
|
-
const imageUploadFn = reactExports.useMemo(() => {
|
|
3603
|
-
return q({
|
|
3604
|
-
onUpload: async (file) => {
|
|
3605
|
-
const currentProjectId = projectIdRef.current;
|
|
3606
|
-
if (!currentProjectId) {
|
|
3607
|
-
console.warn("[NotebookEditor] upload skipped: missing projectId", {
|
|
3608
|
-
fileName: file.name,
|
|
3609
|
-
fileType: file.type
|
|
3610
|
-
});
|
|
3611
|
-
throw new Error("Missing project ID for upload.");
|
|
3612
|
-
}
|
|
3613
|
-
console.debug("[NotebookEditor] upload start", {
|
|
3614
|
-
fileName: file.name,
|
|
3615
|
-
fileType: file.type,
|
|
3616
|
-
fileSize: file.size
|
|
3617
|
-
});
|
|
3618
|
-
if (isMarkdownDoc && markdownAssetContext) {
|
|
3619
|
-
const uploaded = await uploadQuestDocumentAsset(
|
|
3620
|
-
currentProjectId,
|
|
3621
|
-
markdownAssetContext.baseDocumentId,
|
|
3622
|
-
file,
|
|
3623
|
-
"image"
|
|
3624
|
-
);
|
|
3625
|
-
return String(resolveNotebookAssetUrl(uploaded.asset_url));
|
|
3626
|
-
}
|
|
3627
|
-
return uploadNotebookAsset(currentProjectId, file, "image");
|
|
3628
|
-
},
|
|
3629
|
-
validateFn: (file) => {
|
|
3630
|
-
try {
|
|
3631
|
-
if (!projectIdRef.current) {
|
|
3632
|
-
console.warn("[NotebookEditor] validate failed: missing projectId", {
|
|
3633
|
-
fileName: file.name,
|
|
3634
|
-
fileType: file.type
|
|
3635
|
-
});
|
|
3636
|
-
return false;
|
|
3637
|
-
}
|
|
3638
|
-
if (!isSupportedNotebookAsset(file, "image")) {
|
|
3639
|
-
console.warn("[NotebookEditor] validate failed: unsupported image", {
|
|
3640
|
-
fileName: file.name,
|
|
3641
|
-
fileType: file.type
|
|
3642
|
-
});
|
|
3643
|
-
return false;
|
|
3644
|
-
}
|
|
3645
|
-
if (file.size > 10 * 1024 * 1024) {
|
|
3646
|
-
console.warn("[NotebookEditor] validate failed: size limit", {
|
|
3647
|
-
fileName: file.name,
|
|
3648
|
-
fileSize: file.size
|
|
3649
|
-
});
|
|
3650
|
-
return false;
|
|
3651
|
-
}
|
|
3652
|
-
return true;
|
|
3653
|
-
} catch {
|
|
3654
|
-
console.warn("[NotebookEditor] validate failed: unexpected error", {
|
|
3655
|
-
fileName: file.name,
|
|
3656
|
-
fileType: file.type
|
|
3657
|
-
});
|
|
3658
|
-
return false;
|
|
3659
|
-
}
|
|
3660
|
-
}
|
|
3661
|
-
});
|
|
3662
|
-
}, [isMarkdownDoc, markdownAssetContext]);
|
|
3663
|
-
reactExports.useEffect(() => {
|
|
3664
|
-
projectIdRef.current = projectId;
|
|
3665
|
-
}, [projectId]);
|
|
3666
|
-
const handleVideoFiles = reactExports.useCallback(
|
|
3667
|
-
async (files) => {
|
|
3668
|
-
const currentProjectId = projectIdRef.current;
|
|
3669
|
-
if (!editor || !currentProjectId) return;
|
|
3670
|
-
const videoFiles = files.filter(
|
|
3671
|
-
(file) => file.type.startsWith("video/")
|
|
3672
|
-
);
|
|
3673
|
-
if (videoFiles.length === 0) return;
|
|
3674
|
-
for (const file of videoFiles) {
|
|
3675
|
-
try {
|
|
3676
|
-
console.debug("[NotebookEditor] video upload start", {
|
|
3677
|
-
fileName: file.name,
|
|
3678
|
-
fileType: file.type,
|
|
3679
|
-
fileSize: file.size
|
|
3680
|
-
});
|
|
3681
|
-
const src = await uploadNotebookAsset(currentProjectId, file, "video");
|
|
3682
|
-
editor.chain().focus().insertContent({ type: "video", attrs: { src } }).run();
|
|
3683
|
-
} catch (err) {
|
|
3684
|
-
console.error("[NotebookEditor] Video upload failed:", err);
|
|
3685
|
-
}
|
|
3686
|
-
}
|
|
3687
|
-
},
|
|
3688
|
-
[editor]
|
|
3689
|
-
);
|
|
3690
|
-
reactExports.useEffect(() => {
|
|
3691
|
-
setTitle(notebookName);
|
|
3692
|
-
}, [notebookName, setTitle]);
|
|
3693
|
-
reactExports.useEffect(() => {
|
|
3694
|
-
setDirty(autoSaveStatus === "saving" || autoSaveStatus === "error");
|
|
3695
|
-
}, [autoSaveStatus, setDirty]);
|
|
3696
|
-
reactExports.useEffect(() => {
|
|
3697
|
-
if (!editor || initialMarkdown === null) return;
|
|
3698
|
-
if (!notebookId) return;
|
|
3699
|
-
if (activeNotebookIdRef.current !== notebookId) return;
|
|
3700
|
-
if (!initialMarkdown.trim()) return;
|
|
3701
|
-
if (!isEditorDocEmpty(editor)) return;
|
|
3702
|
-
suppressMarkdownUpdateRef.current = true;
|
|
3703
|
-
setEditorMarkdown(editor, initialMarkdown);
|
|
3704
|
-
if (isMarkdownDoc) {
|
|
3705
|
-
baselineMarkdownRef.current = initialMarkdown;
|
|
3706
|
-
} else {
|
|
3707
|
-
baselineNotebookMarkdownRef.current = initialMarkdown;
|
|
3708
|
-
}
|
|
3709
|
-
}, [editor, initialMarkdown, isMarkdownDoc, notebookId]);
|
|
3710
|
-
reactExports.useEffect(() => {
|
|
3711
|
-
if (!onMarkdownChange) return;
|
|
3712
|
-
if (!isMarkdownDoc) return;
|
|
3713
|
-
if (initialMarkdown === null) return;
|
|
3714
|
-
onMarkdownChange(initialMarkdown);
|
|
3715
|
-
}, [initialMarkdown, isMarkdownDoc, onMarkdownChange]);
|
|
3716
|
-
const handleImageUpload = reactExports.useCallback(
|
|
3717
|
-
async (file, editorInstance) => {
|
|
3718
|
-
const currentProjectId = projectIdRef.current;
|
|
3719
|
-
if (!currentProjectId) return;
|
|
3720
|
-
try {
|
|
3721
|
-
console.debug("[NotebookEditor] image upload start", {
|
|
3722
|
-
fileName: file.name,
|
|
3723
|
-
fileType: file.type,
|
|
3724
|
-
fileSize: file.size
|
|
3725
|
-
});
|
|
3726
|
-
const src = isMarkdownDoc && markdownAssetContext ? String(
|
|
3727
|
-
resolveNotebookAssetUrl(
|
|
3728
|
-
(await uploadQuestDocumentAsset(
|
|
3729
|
-
currentProjectId,
|
|
3730
|
-
markdownAssetContext.baseDocumentId,
|
|
3731
|
-
file,
|
|
3732
|
-
"image"
|
|
3733
|
-
)).asset_url
|
|
3734
|
-
)
|
|
3735
|
-
) : await uploadNotebookAsset(currentProjectId, file, "image");
|
|
3736
|
-
editorInstance.chain().focus().setImage({ src }).run();
|
|
3737
|
-
} catch (err) {
|
|
3738
|
-
console.error("[NotebookEditor] Image upload failed:", err);
|
|
3739
|
-
}
|
|
3740
|
-
},
|
|
3741
|
-
[isMarkdownDoc, markdownAssetContext]
|
|
3742
|
-
);
|
|
3743
|
-
const suggestionItems = reactExports.useMemo(
|
|
3744
|
-
() => buildSuggestionItems(handleImageUpload),
|
|
3745
|
-
[handleImageUpload]
|
|
3746
|
-
);
|
|
3747
|
-
const slashCommand = reactExports.useMemo(
|
|
3748
|
-
() => createSlashCommand(suggestionItems),
|
|
3749
|
-
[suggestionItems]
|
|
3750
|
-
);
|
|
3751
|
-
const extensions = reactExports.useMemo(() => {
|
|
3752
|
-
const list = [...defaultExtensions, slashCommand];
|
|
3753
|
-
if (ydoc) {
|
|
3754
|
-
list.unshift(
|
|
3755
|
-
Collaboration.configure({ document: ydoc, field: YJS_FIELD })
|
|
3756
|
-
);
|
|
3757
|
-
}
|
|
3758
|
-
return list;
|
|
3759
|
-
}, [ydoc, slashCommand]);
|
|
3760
|
-
reactExports.useEffect(() => {
|
|
3761
|
-
if (isInline) {
|
|
3762
|
-
setError(null);
|
|
3763
|
-
setIsInitializing(false);
|
|
3764
|
-
setAutoSaveStatus("idle");
|
|
3765
|
-
setInitialMarkdown(toDisplayMarkdown(inlineMarkdown ?? ""));
|
|
3766
|
-
setYdoc(null);
|
|
3767
|
-
setLoadedNotebookId(notebookIdValue);
|
|
3768
|
-
baselineMarkdownRef.current = toDisplayMarkdown(inlineMarkdown ?? "");
|
|
3769
|
-
baselineNotebookMarkdownRef.current = inlineMarkdown ?? "";
|
|
3770
|
-
suppressMarkdownUpdateRef.current = false;
|
|
3771
|
-
activeNotebookIdRef.current = notebookIdValue;
|
|
3772
|
-
return;
|
|
3773
|
-
}
|
|
3774
|
-
if (!notebookId) {
|
|
3775
|
-
setError("No notebook ID provided");
|
|
3776
|
-
setIsInitializing(false);
|
|
3777
|
-
return;
|
|
3778
|
-
}
|
|
3779
|
-
setError(null);
|
|
3780
|
-
setIsInitializing(true);
|
|
3781
|
-
setAutoSaveStatus("idle");
|
|
3782
|
-
setInitialMarkdown(null);
|
|
3783
|
-
setYdoc(null);
|
|
3784
|
-
setLoadedNotebookId(null);
|
|
3785
|
-
baselineMarkdownRef.current = null;
|
|
3786
|
-
baselineNotebookMarkdownRef.current = null;
|
|
3787
|
-
suppressMarkdownUpdateRef.current = false;
|
|
3788
|
-
activeNotebookIdRef.current = null;
|
|
3789
|
-
let canceled = false;
|
|
3790
|
-
const init = async () => {
|
|
3791
|
-
try {
|
|
3792
|
-
let markdown = null;
|
|
3793
|
-
try {
|
|
3794
|
-
markdown = await getFileContent(notebookId);
|
|
3795
|
-
} catch (err) {
|
|
3796
|
-
if (isMarkdownDoc) {
|
|
3797
|
-
throw err;
|
|
3798
|
-
}
|
|
3799
|
-
console.warn(
|
|
3800
|
-
"[NotebookEditor] Failed to load markdown for notebook:",
|
|
3801
|
-
err
|
|
3802
|
-
);
|
|
3803
|
-
}
|
|
3804
|
-
if (canceled) return;
|
|
3805
|
-
const displayMarkdown = toDisplayMarkdown(markdown ?? "");
|
|
3806
|
-
setInitialMarkdown(displayMarkdown);
|
|
3807
|
-
baselineNotebookMarkdownRef.current = markdown ?? "";
|
|
3808
|
-
if (isMarkdownDoc) {
|
|
3809
|
-
baselineMarkdownRef.current = displayMarkdown;
|
|
3810
|
-
activeNotebookIdRef.current = notebookId;
|
|
3811
|
-
setLoadedNotebookId(notebookId);
|
|
3812
|
-
setIsInitializing(false);
|
|
3813
|
-
return;
|
|
3814
|
-
}
|
|
3815
|
-
if (!projectId) {
|
|
3816
|
-
setError("No project ID provided");
|
|
3817
|
-
setIsInitializing(false);
|
|
3818
|
-
return;
|
|
3819
|
-
}
|
|
3820
|
-
const provider = await createNovelYjsProvider({
|
|
3821
|
-
projectId,
|
|
3822
|
-
notebookId,
|
|
3823
|
-
readonly: isReadonly,
|
|
3824
|
-
onStatus: setAutoSaveStatus,
|
|
3825
|
-
onReset: () => {
|
|
3826
|
-
setHistoryOpen(false);
|
|
3827
|
-
setCollaboratorsOpen(false);
|
|
3828
|
-
setResetNonce((v) => v + 1);
|
|
3829
|
-
}
|
|
3830
|
-
});
|
|
3831
|
-
if (canceled) {
|
|
3832
|
-
provider.dispose();
|
|
3833
|
-
return;
|
|
3834
|
-
}
|
|
3835
|
-
providerRef.current = provider;
|
|
3836
|
-
setYdoc(provider.ydoc);
|
|
3837
|
-
activeNotebookIdRef.current = notebookId;
|
|
3838
|
-
setLoadedNotebookId(notebookId);
|
|
3839
|
-
setIsInitializing(false);
|
|
3840
|
-
} catch (err) {
|
|
3841
|
-
if (canceled) return;
|
|
3842
|
-
console.error("[NotebookEditor] Initialization failed:", err);
|
|
3843
|
-
setError(
|
|
3844
|
-
err instanceof Error ? err.message : "Failed to initialize editor"
|
|
3845
|
-
);
|
|
3846
|
-
setIsInitializing(false);
|
|
3847
|
-
}
|
|
3848
|
-
};
|
|
3849
|
-
init();
|
|
3850
|
-
return () => {
|
|
3851
|
-
canceled = true;
|
|
3852
|
-
if (saveTimeoutRef.current) {
|
|
3853
|
-
clearTimeout(saveTimeoutRef.current);
|
|
3854
|
-
saveTimeoutRef.current = null;
|
|
3855
|
-
}
|
|
3856
|
-
if (markdownChangeTimeoutRef.current) {
|
|
3857
|
-
clearTimeout(markdownChangeTimeoutRef.current);
|
|
3858
|
-
markdownChangeTimeoutRef.current = null;
|
|
3859
|
-
}
|
|
3860
|
-
activeNotebookIdRef.current = null;
|
|
3861
|
-
providerRef.current?.dispose();
|
|
3862
|
-
providerRef.current = null;
|
|
3863
|
-
setEditor(null);
|
|
3864
|
-
};
|
|
3865
|
-
}, [inlineMarkdown, isInline, notebookId, notebookIdValue, projectId, isMarkdownDoc, isReadonly, resetNonce, toDisplayMarkdown]);
|
|
3866
|
-
const handleMarkdownUpdate = reactExports.useCallback(
|
|
3867
|
-
(instance) => {
|
|
3868
|
-
if (isReadonly || !isMarkdownDoc) return;
|
|
3869
|
-
if (!notebookId) return;
|
|
3870
|
-
if (activeNotebookIdRef.current !== notebookId) return;
|
|
3871
|
-
if (suppressMarkdownUpdateRef.current) {
|
|
3872
|
-
suppressMarkdownUpdateRef.current = false;
|
|
3873
|
-
return;
|
|
3874
|
-
}
|
|
3875
|
-
if (onMarkdownChange) {
|
|
3876
|
-
if (markdownChangeTimeoutRef.current) {
|
|
3877
|
-
clearTimeout(markdownChangeTimeoutRef.current);
|
|
3878
|
-
}
|
|
3879
|
-
const previewNotebookId = notebookId;
|
|
3880
|
-
markdownChangeTimeoutRef.current = setTimeout(() => {
|
|
3881
|
-
if (activeNotebookIdRef.current !== previewNotebookId) {
|
|
3882
|
-
return;
|
|
3883
|
-
}
|
|
3884
|
-
onMarkdownChange(getEditorMarkdown(instance));
|
|
3885
|
-
}, 120);
|
|
3886
|
-
}
|
|
3887
|
-
if (saveTimeoutRef.current) {
|
|
3888
|
-
clearTimeout(saveTimeoutRef.current);
|
|
3889
|
-
}
|
|
3890
|
-
setAutoSaveStatus("saving");
|
|
3891
|
-
const saveNotebookId = notebookId;
|
|
3892
|
-
saveTimeoutRef.current = setTimeout(async () => {
|
|
3893
|
-
try {
|
|
3894
|
-
if (activeNotebookIdRef.current !== saveNotebookId) {
|
|
3895
|
-
return;
|
|
3896
|
-
}
|
|
3897
|
-
const markdown = getEditorMarkdown(instance);
|
|
3898
|
-
const storedMarkdown = toStoredMarkdown(markdown);
|
|
3899
|
-
if (baselineMarkdownRef.current === markdown) {
|
|
3900
|
-
setAutoSaveStatus("saved");
|
|
3901
|
-
setTimeout(() => setAutoSaveStatus("idle"), 1500);
|
|
3902
|
-
return;
|
|
3903
|
-
}
|
|
3904
|
-
await updateFileContent(saveNotebookId, storedMarkdown);
|
|
3905
|
-
baselineMarkdownRef.current = markdown;
|
|
3906
|
-
setAutoSaveStatus("saved");
|
|
3907
|
-
setTimeout(() => setAutoSaveStatus("idle"), 1500);
|
|
3908
|
-
} catch (err) {
|
|
3909
|
-
console.error("[NotebookEditor] Markdown save failed:", err);
|
|
3910
|
-
setAutoSaveStatus("error");
|
|
3911
|
-
}
|
|
3912
|
-
}, 1500);
|
|
3913
|
-
},
|
|
3914
|
-
[isReadonly, isMarkdownDoc, notebookId, onMarkdownChange, toStoredMarkdown]
|
|
3915
|
-
);
|
|
3916
|
-
const handleNotebookMarkdownUpdate = reactExports.useCallback(
|
|
3917
|
-
(instance) => {
|
|
3918
|
-
if (isReadonly || isMarkdownDoc) return;
|
|
3919
|
-
if (!notebookId) return;
|
|
3920
|
-
if (activeNotebookIdRef.current !== notebookId) return;
|
|
3921
|
-
if (suppressMarkdownUpdateRef.current) {
|
|
3922
|
-
suppressMarkdownUpdateRef.current = false;
|
|
3923
|
-
return;
|
|
3924
|
-
}
|
|
3925
|
-
if (saveTimeoutRef.current) {
|
|
3926
|
-
clearTimeout(saveTimeoutRef.current);
|
|
3927
|
-
}
|
|
3928
|
-
setAutoSaveStatus("saving");
|
|
3929
|
-
const saveNotebookId = notebookId;
|
|
3930
|
-
saveTimeoutRef.current = setTimeout(async () => {
|
|
3931
|
-
try {
|
|
3932
|
-
if (activeNotebookIdRef.current !== saveNotebookId) {
|
|
3933
|
-
return;
|
|
3934
|
-
}
|
|
3935
|
-
const markdown = getEditorMarkdown(instance);
|
|
3936
|
-
if (baselineNotebookMarkdownRef.current === markdown) {
|
|
3937
|
-
setAutoSaveStatus("saved");
|
|
3938
|
-
setTimeout(() => setAutoSaveStatus("idle"), 1500);
|
|
3939
|
-
return;
|
|
3940
|
-
}
|
|
3941
|
-
await updateFileContent(saveNotebookId, markdown);
|
|
3942
|
-
baselineNotebookMarkdownRef.current = markdown;
|
|
3943
|
-
setAutoSaveStatus("saved");
|
|
3944
|
-
setTimeout(() => setAutoSaveStatus("idle"), 1500);
|
|
3945
|
-
} catch (err) {
|
|
3946
|
-
console.error("[NotebookEditor] Notebook markdown save failed:", err);
|
|
3947
|
-
setAutoSaveStatus("error");
|
|
3948
|
-
}
|
|
3949
|
-
}, 1500);
|
|
3950
|
-
},
|
|
3951
|
-
[isReadonly, isMarkdownDoc, notebookId]
|
|
3952
|
-
);
|
|
3953
|
-
const isFileTransfer = reactExports.useCallback((dataTransfer) => {
|
|
3954
|
-
if (!dataTransfer) return false;
|
|
3955
|
-
if (dataTransfer.files && dataTransfer.files.length > 0) return true;
|
|
3956
|
-
const items = dataTransfer.items ? Array.from(dataTransfer.items) : [];
|
|
3957
|
-
return items.some((item) => item.kind === "file");
|
|
3958
|
-
}, []);
|
|
3959
|
-
const getDroppedFiles = reactExports.useCallback((dataTransfer) => {
|
|
3960
|
-
if (!dataTransfer) return [];
|
|
3961
|
-
if (!isFileTransfer(dataTransfer)) return [];
|
|
3962
|
-
if (dataTransfer.files && dataTransfer.files.length > 0) {
|
|
3963
|
-
return Array.from(dataTransfer.files);
|
|
3964
|
-
}
|
|
3965
|
-
const items = dataTransfer.items ? Array.from(dataTransfer.items) : [];
|
|
3966
|
-
const files = [];
|
|
3967
|
-
for (const item of items) {
|
|
3968
|
-
if (item.kind === "file") {
|
|
3969
|
-
const file = item.getAsFile();
|
|
3970
|
-
if (file) files.push(file);
|
|
3971
|
-
}
|
|
3972
|
-
}
|
|
3973
|
-
return files;
|
|
3974
|
-
}, [isFileTransfer]);
|
|
3975
|
-
const handleContainerDragOver = reactExports.useCallback(
|
|
3976
|
-
(event) => {
|
|
3977
|
-
if (editor?.view.dragging) return;
|
|
3978
|
-
if (!isFileTransfer(event.dataTransfer)) return;
|
|
3979
|
-
const files = getDroppedFiles(event.dataTransfer);
|
|
3980
|
-
if (!files.length) return;
|
|
3981
|
-
event.preventDefault();
|
|
3982
|
-
event.dataTransfer.dropEffect = "copy";
|
|
3983
|
-
console.debug("[NotebookEditor] dragover", {
|
|
3984
|
-
fileCount: files.length,
|
|
3985
|
-
fileTypes: files.map((file) => file.type || "unknown"),
|
|
3986
|
-
dataTypes: Array.from(event.dataTransfer.types ?? []),
|
|
3987
|
-
projectId: projectIdRef.current
|
|
3988
|
-
});
|
|
3989
|
-
},
|
|
3990
|
-
[editor, getDroppedFiles, isFileTransfer]
|
|
3991
|
-
);
|
|
3992
|
-
const handleContainerDrop = reactExports.useCallback(
|
|
3993
|
-
(event) => {
|
|
3994
|
-
if (editor?.view.dragging) return;
|
|
3995
|
-
if (!isFileTransfer(event.dataTransfer)) return;
|
|
3996
|
-
const files = getDroppedFiles(event.dataTransfer);
|
|
3997
|
-
console.debug("[NotebookEditor] container drop", {
|
|
3998
|
-
fileCount: files.length,
|
|
3999
|
-
fileTypes: files.map((file) => file.type || "unknown"),
|
|
4000
|
-
dataTypes: Array.from(event.dataTransfer.types ?? []),
|
|
4001
|
-
hasUploadFn: Boolean(imageUploadFn),
|
|
4002
|
-
projectId: projectIdRef.current
|
|
4003
|
-
});
|
|
4004
|
-
if (!files.length) return;
|
|
4005
|
-
event.preventDefault();
|
|
4006
|
-
event.stopPropagation();
|
|
4007
|
-
if (!editor) {
|
|
4008
|
-
console.warn("[NotebookEditor] container drop ignored: editor missing");
|
|
4009
|
-
return;
|
|
4010
|
-
}
|
|
4011
|
-
const hasVideo = files.some((file) => file.type.startsWith("video/"));
|
|
4012
|
-
if (hasVideo) {
|
|
4013
|
-
handleVideoFiles(files);
|
|
4014
|
-
}
|
|
4015
|
-
if (imageUploadFn) {
|
|
4016
|
-
const handled = J(
|
|
4017
|
-
editor.view,
|
|
4018
|
-
event.nativeEvent,
|
|
4019
|
-
false,
|
|
4020
|
-
imageUploadFn
|
|
4021
|
-
);
|
|
4022
|
-
console.debug("[NotebookEditor] container drop handled", { handled });
|
|
4023
|
-
} else {
|
|
4024
|
-
console.warn("[NotebookEditor] container drop ignored: uploadFn missing");
|
|
4025
|
-
}
|
|
4026
|
-
},
|
|
4027
|
-
[editor, getDroppedFiles, handleVideoFiles, imageUploadFn, isFileTransfer]
|
|
4028
|
-
);
|
|
4029
|
-
if (error) {
|
|
4030
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "notebook-editor-error h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center space-y-4 p-8 max-w-md", children: [
|
|
4031
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-destructive text-lg font-medium", children: "Failed to load notebook" }),
|
|
4032
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-muted-foreground text-sm", children: error }),
|
|
4033
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-2 justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4034
|
-
"button",
|
|
4035
|
-
{
|
|
4036
|
-
className: "px-4 py-2 bg-primary text-primary-foreground rounded-md text-sm",
|
|
4037
|
-
onClick: () => window.location.reload(),
|
|
4038
|
-
children: "Retry"
|
|
4039
|
-
}
|
|
4040
|
-
) })
|
|
4041
|
-
] }) });
|
|
4042
|
-
}
|
|
4043
|
-
const isNotebookReady = loadedNotebookId === notebookIdValue && !isInitializing;
|
|
4044
|
-
if (!isNotebookReady) {
|
|
4045
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(EditorLoading, { message: "Initializing editor..." });
|
|
4046
|
-
}
|
|
4047
|
-
if (!isMarkdownDoc && !ydoc) {
|
|
4048
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(EditorLoading, { message: "Loading document..." });
|
|
4049
|
-
}
|
|
4050
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4051
|
-
"div",
|
|
4052
|
-
{
|
|
4053
|
-
className: `notebook-editor flex flex-col flex-1 min-h-0 ${className || ""}`,
|
|
4054
|
-
children: [
|
|
4055
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4056
|
-
NotebookToolbar,
|
|
4057
|
-
{
|
|
4058
|
-
notebookId: !isInline && notebookId ? notebookId : "",
|
|
4059
|
-
autoSaveStatus,
|
|
4060
|
-
getMarkdown: getCurrentMarkdown
|
|
4061
|
-
}
|
|
4062
|
-
),
|
|
4063
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-hidden flex justify-center px-4 py-4 bg-muted/10", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative h-full w-[min(62.5vw,100%)] max-w-[70rem]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4064
|
-
"div",
|
|
4065
|
-
{
|
|
4066
|
-
className: "notebook-editor-container relative h-full overflow-hidden rounded-2xl border border-border bg-background shadow-soft-card",
|
|
4067
|
-
"data-notebook-id": notebookId,
|
|
4068
|
-
onDragOver: handleContainerDragOver,
|
|
4069
|
-
onDrop: handleContainerDrop,
|
|
4070
|
-
children: [
|
|
4071
|
-
diffEvent?.diff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4072
|
-
FileDiffPanel,
|
|
4073
|
-
{
|
|
4074
|
-
diff: diffEvent.diff,
|
|
4075
|
-
changeType: diffEvent.changeType,
|
|
4076
|
-
title: "AI change",
|
|
4077
|
-
subtitle: notebookName,
|
|
4078
|
-
onClose: clearDiff,
|
|
4079
|
-
className: "absolute right-4 top-4 z-20 w-[min(420px,46vw)]"
|
|
4080
|
-
}
|
|
4081
|
-
) : null,
|
|
4082
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(U, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4083
|
-
I,
|
|
4084
|
-
{
|
|
4085
|
-
extensions,
|
|
4086
|
-
className: "notebook-doc-editor relative h-full w-full overflow-y-auto",
|
|
4087
|
-
editorProps: {
|
|
4088
|
-
handleDOMEvents: {
|
|
4089
|
-
keydown: (_view, event) => ce(event)
|
|
4090
|
-
},
|
|
4091
|
-
handlePaste: (view, event) => {
|
|
4092
|
-
const files = Array.from(event.clipboardData?.files ?? []);
|
|
4093
|
-
const hasVideo = files.some(
|
|
4094
|
-
(file) => file.type.startsWith("video/")
|
|
4095
|
-
);
|
|
4096
|
-
if (hasVideo) {
|
|
4097
|
-
event.preventDefault();
|
|
4098
|
-
handleVideoFiles(files);
|
|
4099
|
-
if (imageUploadFn) {
|
|
4100
|
-
G(view, event, imageUploadFn);
|
|
4101
|
-
}
|
|
4102
|
-
return true;
|
|
4103
|
-
}
|
|
4104
|
-
if (imageUploadFn) {
|
|
4105
|
-
return G(view, event, imageUploadFn);
|
|
4106
|
-
}
|
|
4107
|
-
return false;
|
|
4108
|
-
},
|
|
4109
|
-
handleDrop: (view, event, _slice, moved) => {
|
|
4110
|
-
const isFileDrop = isFileTransfer(event.dataTransfer);
|
|
4111
|
-
if (!isFileDrop) {
|
|
4112
|
-
return false;
|
|
4113
|
-
}
|
|
4114
|
-
if (moved) {
|
|
4115
|
-
console.debug("[NotebookEditor] drop ignored: internal move", {
|
|
4116
|
-
moved,
|
|
4117
|
-
projectId: projectIdRef.current
|
|
4118
|
-
});
|
|
4119
|
-
return false;
|
|
4120
|
-
}
|
|
4121
|
-
const files = Array.from(event.dataTransfer?.files ?? []);
|
|
4122
|
-
console.debug("[NotebookEditor] drop", {
|
|
4123
|
-
moved,
|
|
4124
|
-
fileCount: files.length,
|
|
4125
|
-
fileTypes: files.map((file) => file.type || "unknown"),
|
|
4126
|
-
dataTypes: Array.from(event.dataTransfer?.types ?? []),
|
|
4127
|
-
hasUploadFn: Boolean(imageUploadFn),
|
|
4128
|
-
projectId: projectIdRef.current
|
|
4129
|
-
});
|
|
4130
|
-
const hasVideo = files.some(
|
|
4131
|
-
(file) => file.type.startsWith("video/")
|
|
4132
|
-
);
|
|
4133
|
-
if (hasVideo) {
|
|
4134
|
-
event.preventDefault();
|
|
4135
|
-
handleVideoFiles(files);
|
|
4136
|
-
if (imageUploadFn) {
|
|
4137
|
-
const handled = J(view, event, moved, imageUploadFn);
|
|
4138
|
-
console.debug("[NotebookEditor] drop handled (video+image)", { handled });
|
|
4139
|
-
}
|
|
4140
|
-
return true;
|
|
4141
|
-
}
|
|
4142
|
-
if (imageUploadFn) {
|
|
4143
|
-
const handled = J(view, event, moved, imageUploadFn);
|
|
4144
|
-
console.debug("[NotebookEditor] drop handled (image)", { handled });
|
|
4145
|
-
return handled;
|
|
4146
|
-
}
|
|
4147
|
-
console.warn("[NotebookEditor] drop ignored: uploadFn missing");
|
|
4148
|
-
return false;
|
|
4149
|
-
},
|
|
4150
|
-
attributes: {
|
|
4151
|
-
class: "prose prose-lg dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full"
|
|
4152
|
-
}
|
|
4153
|
-
},
|
|
4154
|
-
onCreate: ({ editor: instance }) => {
|
|
4155
|
-
setEditor(instance);
|
|
4156
|
-
if (isMarkdownDoc && initialMarkdown !== null) {
|
|
4157
|
-
suppressMarkdownUpdateRef.current = true;
|
|
4158
|
-
setEditorMarkdown(instance, initialMarkdown);
|
|
4159
|
-
baselineMarkdownRef.current = initialMarkdown;
|
|
4160
|
-
}
|
|
4161
|
-
if (!isMarkdownDoc && ydoc) {
|
|
4162
|
-
const fragment = ydoc.getXmlFragment(YJS_FIELD);
|
|
4163
|
-
if (fragment.length === 0 && initialMarkdown !== null) {
|
|
4164
|
-
suppressMarkdownUpdateRef.current = true;
|
|
4165
|
-
setEditorMarkdown(instance, initialMarkdown);
|
|
4166
|
-
baselineNotebookMarkdownRef.current = initialMarkdown;
|
|
4167
|
-
} else if (fragment.length === 0 && !isReadonly) {
|
|
4168
|
-
instance.commands.setContent(DEFAULT_DOC);
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
4171
|
-
if (isReadonly) {
|
|
4172
|
-
instance.setEditable(false);
|
|
4173
|
-
}
|
|
4174
|
-
},
|
|
4175
|
-
onUpdate: ({ editor: instance }) => {
|
|
4176
|
-
handleMarkdownUpdate(instance);
|
|
4177
|
-
handleNotebookMarkdownUpdate(instance);
|
|
4178
|
-
},
|
|
4179
|
-
slotAfter: /* @__PURE__ */ jsxRuntimeExports.jsx(K$1, {}),
|
|
4180
|
-
children: [
|
|
4181
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(T, { className: "z-50 h-auto max-h-[330px] overflow-y-auto rounded-md border border-muted bg-background px-1 py-2 text-foreground shadow-md transition-all", children: [
|
|
4182
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(B, { className: "px-2 text-muted-foreground", children: "No results" }),
|
|
4183
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(O, { children: suggestionItems.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4184
|
-
S,
|
|
4185
|
-
{
|
|
4186
|
-
value: item.title,
|
|
4187
|
-
onCommand: (val) => item.command?.(val),
|
|
4188
|
-
className: "flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-accent aria-selected:bg-accent",
|
|
4189
|
-
children: [
|
|
4190
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-md border border-muted bg-background text-foreground", children: item.icon }),
|
|
4191
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
4192
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-medium", children: item.title }),
|
|
4193
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: item.description })
|
|
4194
|
-
] })
|
|
4195
|
-
]
|
|
4196
|
-
},
|
|
4197
|
-
item.title
|
|
4198
|
-
)) })
|
|
4199
|
-
] }),
|
|
4200
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TableToolbar, {}),
|
|
4201
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TableBubbleMenu, {}),
|
|
4202
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(EditorBubbleMenu, {})
|
|
4203
|
-
]
|
|
4204
|
-
}
|
|
4205
|
-
) })
|
|
4206
|
-
]
|
|
4207
|
-
}
|
|
4208
|
-
) }) })
|
|
4209
|
-
]
|
|
4210
|
-
}
|
|
4211
|
-
);
|
|
4212
|
-
}
|
|
4213
|
-
|
|
4214
|
-
export { EditorLoading as E, NotebookEditor as N, NotebookToolbar as a };
|