collabmd 0.1.48 → 0.1.49
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 +61 -5
- package/bin/collabmd.js +3 -0
- package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-C-nmgL5g.js +1 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CdVZqicp.js +1 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-I39sYjHI.js → architectureDiagram-5GKGNRK7-wUo0bXif.js} +3 -3
- package/dist/client/assets/{blockDiagram-NRAW4CY4-C6DtJy5Y.js → blockDiagram-I7D4REHJ-BRSK3m49.js} +4 -4
- package/dist/client/assets/browser-utils-DbXSMI-6.js +1 -0
- package/dist/client/assets/{c4Diagram-UCG6FXSJ-CtWtxS9_.js → c4Diagram-7LVT6UL2-yD2poxqw.js} +1 -1
- package/dist/client/assets/channel-Fa4GS_-F.js +1 -0
- package/dist/client/assets/chunk-2E4U76K2-BPnTT-J3.js +2 -0
- package/dist/client/assets/{chunk-TEH6E4GO-DVayPj-q.js → chunk-4HAMMTFA-DStbIfJ1.js} +20 -20
- package/dist/client/assets/{chunk-75Z2AOVW-CdFSaPjz.js → chunk-75Z2AOVW-C5pGd3W9.js} +1 -1
- package/dist/client/assets/{chunk-CLGD4ZFX-DQLgbQrv.js → chunk-CLGD4ZFX-BMeArk03.js} +1 -1
- package/dist/client/assets/chunk-DU6HZSFF-Zkp69c6c.js +127 -0
- package/dist/client/assets/{chunk-F27PBJKO-CNHIFhjo.js → chunk-F27PBJKO-D4xNct1r.js} +1 -1
- package/dist/client/assets/{chunk-GMAD6QVW-CbcBqPM5.js → chunk-GMAD6QVW-NZ6WpDb8.js} +1 -1
- package/dist/client/assets/{chunk-OBVCFTLP-BJR7u1jh.js → chunk-GVQU2GXP-D8SDprfX.js} +1 -1
- package/dist/client/assets/{chunk-G27WJ6UU-D9-eg537.js → chunk-IMKFNOWR-D8jEuU3Q.js} +2 -2
- package/dist/client/assets/{chunk-JQ64N6SF-DSFNH4bh.js → chunk-L3NEJ4N5-aijKP-eh.js} +1 -1
- package/dist/client/assets/chunk-OSK3NFVY-CCbLQAMa.js +10 -0
- package/dist/client/assets/{chunk-P2QGCYS3-BRpqY32H.js → chunk-P2QGCYS3-Oag_FdCq.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-Cb4SlVZF.js → chunk-POPQ4Y6H-CYnpIgXT.js} +1 -1
- package/dist/client/assets/{chunk-PWAF6VOD-Cz8IwBB5.js → chunk-PWAF6VOD-JSNrcBmP.js} +1 -1
- package/dist/client/assets/{chunk-RM4UPSZO-CWRwxNWJ.js → chunk-RM4UPSZO-DOWllDY8.js} +4 -4
- package/dist/client/assets/{chunk-RHFEMEQ7-CWY8cf4H.js → chunk-SHT3W25Y-BiQGljao.js} +5 -5
- package/dist/client/assets/{chunk-SVP7TREG-CyHOmcac.js → chunk-SVP7TREG-DB485R5x.js} +1 -1
- package/dist/client/assets/{chunk-LCL6LL3I-DmDkVukh.js → chunk-TICWLB2K-8rAVvoS3.js} +4 -4
- package/dist/client/assets/{chunk-CKBBP62Z-DG26UiBv.js → chunk-TLUHSLCS-DTY5v0aj.js} +2 -2
- package/dist/client/assets/classDiagram-ZZMXUADV-BsQWoR3Q.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BsQWoR3Q.js +1 -0
- package/dist/client/assets/{comment-thread-store-CdcwrObc.js → comment-thread-store-CX98zR6q.js} +1 -1
- package/dist/client/assets/comment-threads-BnRwo5B9.js +4 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CdTPi2yO.js → cynefinDiagram-5FMLGOSQ-rTz9RhFH.js} +1 -1
- package/dist/client/assets/{dagre-3AP2YEHR-5gtJtd0O.js → dagre-GXQ25YYZ-Cr67wLpQ.js} +2 -2
- package/dist/client/assets/{diagram-S7CK7UJ4-BudWz9WU.js → diagram-S7CK7UJ4-DywERe__.js} +2 -2
- package/dist/client/assets/{diagram-UQ7AKVKN-3v4rbH-s.js → diagram-UQ7AKVKN-CNYlXtu4.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-0H1a61Bn.js +3 -0
- package/dist/client/assets/{diagram-VX7I27RA-C_-rj0sw.js → diagram-VX7I27RA-CsJbsx5F.js} +1 -1
- package/dist/client/assets/{diagram-Z3DM3KII-B4yxavgj.js → diagram-Z3DM3KII-D0mLxEgI.js} +1 -1
- package/dist/client/assets/dist--hk9I5HQ.js +1 -0
- package/dist/client/assets/{dist-DutPuFmC.js → dist-B1XEa-Zs.js} +1 -1
- package/dist/client/assets/dist-Bd1JucQF.js +1 -0
- package/dist/client/assets/{dist-DZAAWTG-.js → dist-C0G3AaXG.js} +1 -1
- package/dist/client/assets/dist-Dm4w5UId.js +1 -0
- package/dist/client/assets/{dist-B_yQaEv_.js → dist-DpYDHR9p.js} +1 -1
- package/dist/client/assets/{dist-CietwTVS.js → dist-DzNmC81X.js} +1 -1
- package/dist/client/assets/{dist-DDUn5zTz.js → dist-GvvCNBe4.js} +1 -1
- package/dist/client/assets/dist-JaU3aRlD.js +19 -0
- package/dist/client/assets/{dist-Bw3VIE_v.js → dist-TW8iFRyW.js} +1 -1
- package/dist/client/assets/{dist-Dir5dDKZ.js → dist-jVU5Be5P.js} +1 -1
- package/dist/client/assets/document-formatter-rDDjRfZ2.js +2 -0
- package/dist/client/assets/drawioEditor-JubEEha7.js +9 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-HeTxHpCU.js +1 -0
- package/dist/client/assets/editor-session-Bk6oIvSr.js +54 -0
- package/dist/client/assets/{embedded-editor-base-B3l6Xy65.css → embedded-editor-base-BlCgz6mw.css} +1 -1
- package/dist/client/assets/{erDiagram-SSCWMZ5O-C-iy6VkX.js → erDiagram-RLTQ6QDP-CbrDdd5P.js} +2 -2
- package/dist/client/assets/{vault-utils-DvoSftJT.js → es-Bfdp096f.js} +13 -13
- package/dist/client/assets/excalidraw-element-link-YLIaTnVF.js +7 -0
- package/dist/client/assets/{excalidraw-scene-BOben2gr.js → excalidraw-scene-6uROebca.js} +1 -1
- package/dist/client/assets/excalidrawEditor-dBMbRX_S.js +9 -0
- package/dist/client/assets/export-page-CRRQ2NEl.js +57 -0
- package/dist/client/assets/exportDocument-BlQRd9_m.js +2 -0
- package/dist/client/assets/exportDocument-CNSbjZwu.css +1 -0
- package/dist/client/assets/flowDiagram-HODETNUW-D5Gy0wmg.js +1 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DtKHAbez.js → ganttDiagram-EL5Y4UJY-BHgOYiZz.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-oue8fQ5o.js → gitGraphDiagram-WWUBYQGX-Dl__X3Sk.js} +1 -1
- package/dist/client/assets/image-ZVKINSPA-DgzW5zAy.js +1 -0
- package/dist/client/assets/index-D3RER1SZ.js +1023 -0
- package/dist/client/assets/index-DREVKl_K.css +1 -0
- package/dist/client/assets/{infoDiagram-RXCK75RN-BMzo6KIn.js → infoDiagram-27XIBGKW-DBpoANC9.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BIM3h287.js → ishikawaDiagram-5VMMS53U-BMPC6hfp.js} +1 -1
- package/dist/client/assets/{journeyDiagram-EYS64GPL-mF6Lh8O-.js → journeyDiagram-3NMN7TZE-B1sApUmM.js} +2 -2
- package/dist/client/assets/{kanban-definition-3QL26DDD-DLN2NyoR.js → kanban-definition-UXKFOSKX-C9yP4td1.js} +4 -4
- package/dist/client/assets/{line-BfTWJmgf.js → line-BFl3ujec.js} +1 -1
- package/dist/client/assets/mermaid.core-BrY09czX.js +11 -0
- package/dist/client/assets/mermaid.core-DfcNfOTv.js +1 -0
- package/dist/client/assets/{mindmap-definition-FBJOCRG2-CRyHaHTk.js → mindmap-definition-YA3MSWOX-BUQHcYbK.js} +8 -8
- package/dist/client/assets/pegDiagram-XKGWAZYB-Bi2Iml7Z.js +1 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-B5OZPb88.js → pieDiagram-E7YTZNPT-_gYXnMoi.js} +1 -1
- package/dist/client/assets/preview-render-compiler-CfxJYldJ.js +41 -0
- package/dist/client/assets/preview-render-compiler-Cu8_i9iS.js +1 -0
- package/dist/client/assets/{preview-render-worker-_QTxqKvP.js → preview-render-worker-c4Yi93qW.js} +41 -35
- package/dist/client/assets/prod-BiJL30sl.js +1 -0
- package/dist/client/assets/prod-qAk7rHa4.js +177 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-Drrw14YH.js → quadrantDiagram-AXDQQJYC-DwexhEhd.js} +1 -1
- package/dist/client/assets/quick-switcher-controller-BoIK_wuG.js +1 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BVA5UbMu.js +1 -0
- package/dist/client/assets/{requirementDiagram-EFPCY7ZU-CRZG6mRB.js → requirementDiagram-BXWQKSXE-C4cKwiIw.js} +2 -2
- package/dist/client/assets/runtime-config-DcmnGcXU.js +1 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-DvarEJpo.js → sankeyDiagram-P5KCCOFB-B5HHkRT2.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-B4oWlb-w.js → sequenceDiagram-WJ2MYXX4-BX6jqJFD.js} +6 -6
- package/dist/client/assets/{stateDiagram-HBIQ2CUA-qbHIIqRZ.js → stateDiagram-D77RDMKH-CmqxyRGx.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-zoLLQVyh.js +1 -0
- package/dist/client/assets/{swimlanes-XN3QIQJK-xB6CtUGj.js → swimlanes-42K2YHIH-Bmgb9Zld.js} +1 -1
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DS-KQN37.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-Lj_yTNlY.js → timeline-definition-24CTP7MA-BA-lVH6P.js} +1 -1
- package/dist/client/assets/vault-api-client-iZLhMJKe.js +6 -0
- package/dist/client/assets/{vennDiagram-4TSXK5OY-BekRp5Y0.js → vennDiagram-4TSXK5OY-DNdUC_Xj.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-kdnV_Hdm.js → wardleyDiagram-VM6X3IG4-C2oY-YbF.js} +1 -1
- package/dist/client/assets/wiki-link-resolver-DFIwErjc.js +1 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DufGwNSB.js → xychartDiagram-S5SC5T6Z-CoIR8CBX.js} +1 -1
- package/dist/client/assets/{yjs-provider-reset-guard-DE0vINDP.js → yjs-provider-reset-guard-9ul1qF6k.js} +1 -1
- package/dist/client/drawio-editor.html +11 -3
- package/dist/client/excalidraw-editor.html +45 -2
- package/dist/client/export-document.html +3 -3
- package/dist/client/index.html +67 -35
- package/integrations/pi/index.ts +94 -0
- package/package.json +41 -32
- package/src/client/app/drawio-editor.html +2 -2
- package/src/client/app/excalidraw-editor.html +2 -2
- package/src/client/app/export-document.html +1 -1
- package/src/client/app/index.html +49 -33
- package/src/client/app/main-entry.js +15 -1
- package/src/client/application/app-shell/chat-feature.js +7 -22
- package/src/client/application/app-shell/git-feature.js +23 -5
- package/src/client/application/app-shell/presence-feature.js +8 -25
- package/src/client/application/app-shell/ui-feature-shell.js +36 -91
- package/src/client/application/app-shell/ui-feature-sidebar.js +43 -7
- package/src/client/application/app-shell/ui-feature-tab-activity.js +19 -6
- package/src/client/application/app-shell/ui-feature-toolbar.js +16 -129
- package/src/client/application/app-shell-elements.js +4 -0
- package/src/client/application/preview-render-compiler.js +1 -87
- package/src/client/application/preview-render-scheduler.js +8 -24
- package/src/client/application/preview-renderer.js +2 -2
- package/src/client/application/webmcp-diagram-renderer.js +47 -0
- package/src/client/application/workspace-coordinator.js +3 -1
- package/src/client/application/workspace-route-controller.js +7 -0
- package/src/client/bootstrap/collabmd-app-shell.js +78 -1
- package/src/client/domain/excalidraw-element-link.js +73 -0
- package/src/client/domain/excalidraw-scene.js +21 -0
- package/src/client/domain/hash-routes.js +8 -0
- package/src/client/domain/room.js +1 -5
- package/src/client/domain/runtime-paths.js +5 -0
- package/src/client/excalidraw-editor.js +135 -10
- package/src/client/export/export-host.js +3 -1
- package/src/client/export/export-pipeline.js +4 -17
- package/src/client/infrastructure/agent-connection-api-client.js +23 -0
- package/src/client/infrastructure/auth-client.js +3 -13
- package/src/client/infrastructure/editor-session.js +11 -0
- package/src/client/infrastructure/editor-view-adapter.js +13 -0
- package/src/client/infrastructure/excalidraw-room-client.js +80 -1
- package/src/client/infrastructure/git-api-client.js +39 -33
- package/src/client/infrastructure/lobby-presence.js +2 -5
- package/src/client/infrastructure/runtime-config.js +11 -1
- package/src/client/infrastructure/tab-activity-lock.js +2 -5
- package/src/client/infrastructure/vault-api-client.js +33 -26
- package/src/client/infrastructure/webmcp-tool-registry.js +283 -0
- package/src/client/infrastructure/workspace-sync-client.js +6 -4
- package/src/client/presentation/agent-connection-controller.js +255 -0
- package/src/client/presentation/backlinks-panel.js +3 -0
- package/src/client/presentation/bases-preview-controller.js +27 -18
- package/src/client/presentation/comment-ui/comment-ui-card.js +4 -2
- package/src/client/presentation/create-menu-presenter.js +19 -16
- package/src/client/presentation/excalidraw-embed-controller.js +164 -4
- package/src/client/presentation/file-explorer-view.js +70 -12
- package/src/client/presentation/file-history-view-controller.js +13 -11
- package/src/client/presentation/git-diff-view-controller.js +35 -43
- package/src/client/presentation/git-panel-controller.js +77 -27
- package/src/client/presentation/image-lightbox-controller.js +20 -16
- package/src/client/presentation/layout-controller.js +3 -1
- package/src/client/presentation/quick-switcher-controller.js +20 -78
- package/src/client/presentation/quick-switcher-text-search.js +10 -14
- package/src/client/styles/components/navigation.css +1 -2
- package/src/client/styles/features/agent-connection.css +202 -0
- package/src/client/styles/features/collaboration-chat.css +8 -1
- package/src/client/styles/features/collaboration-dialogs.css +16 -1
- package/src/client/styles/features/collaboration-presence.css +17 -4
- package/src/client/styles/features/diff-toolbar.css +1 -1
- package/src/client/styles/features/media-lightbox.css +12 -3
- package/src/client/styles/features/quick-switcher.css +20 -11
- package/src/client/styles/foundation/themes.css +2 -0
- package/src/client/styles/layout/editor-page.css +25 -11
- package/src/client/styles/layout/responsive.css +2 -20
- package/src/client/styles/layout/shell.css +1 -0
- package/src/client/styles/layout/sidebar.css +7 -4
- package/src/client/styles/primitives/controls.css +21 -54
- package/src/client/styles/style.css +1 -0
- package/src/domain/agent-tool-definitions.js +796 -0
- package/src/domain/collabmd-content-capabilities.js +149 -0
- package/src/domain/collaboration-protocol.js +2 -0
- package/src/domain/comment-threads.js +3 -5
- package/src/domain/diagram-source.js +50 -0
- package/src/domain/editable-content-revision.js +10 -0
- package/src/domain/editable-text.js +3 -0
- package/src/domain/exact-text-edits.js +69 -0
- package/src/domain/excalidraw-agent-scene.js +522 -0
- package/src/domain/excalidraw-agent-verification.js +460 -0
- package/src/domain/excalidraw-room-codec.js +1 -5
- package/src/domain/video-embed.js +76 -0
- package/src/server/application/agent-connection-service.js +178 -0
- package/src/server/application/agent-content-service.js +773 -0
- package/src/server/application/workspace-reconciliation.js +5 -7
- package/src/server/config/env.js +51 -1
- package/src/server/create-app-server.js +36 -0
- package/src/server/domain/bases/base-query-service.js +1 -5
- package/src/server/domain/collaboration/collaboration-room.js +121 -25
- package/src/server/domain/collaboration/protocol.js +4 -0
- package/src/server/domain/hosted-workspace.js +21 -0
- package/src/server/domain/markdown-reference-extractor.js +15 -1
- package/src/server/infrastructure/git/git-service.js +19 -1
- package/src/server/infrastructure/git/history-service.js +1 -1
- package/src/server/infrastructure/git/local-exclude.js +25 -0
- package/src/server/infrastructure/git/parsers.js +19 -17
- package/src/server/infrastructure/git/path-utils.js +0 -16
- package/src/server/infrastructure/git/status-service.js +1 -1
- package/src/server/infrastructure/git/untracked-files.js +12 -21
- package/src/server/infrastructure/http/create-agent-api-handler.js +171 -0
- package/src/server/infrastructure/http/create-git-api-command-handler.js +32 -0
- package/src/server/infrastructure/http/create-request-handler.js +28 -0
- package/src/server/infrastructure/http/create-static-handler.js +6 -0
- package/src/server/infrastructure/mcp/create-agent-mcp-handler.js +199 -0
- package/src/server/infrastructure/persistence/agent-connection-store.js +120 -0
- package/src/server/infrastructure/persistence/pull-backup-store.js +2 -27
- package/src/server/infrastructure/persistence/vault-file-store.js +9 -2
- package/src/server/shared/agent-tool-image.js +22 -0
- package/src/server/shared/agent-tool-rate-limiter.js +20 -0
- package/src/server/startup/git-remote-bootstrap.js +5 -33
- package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-BPH4FJkv.js +0 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-BNDLzJ7L.js +0 -1
- package/dist/client/assets/browser-utils--H_DkyEF.js +0 -1
- package/dist/client/assets/channel-CaoKA_We.js +0 -1
- package/dist/client/assets/chunk-DSNCTWBM-8xkug7fQ.js +0 -2
- package/dist/client/assets/chunk-DU6HZSFF-COXDzJK1.js +0 -127
- package/dist/client/assets/chunk-R7TYR2AO-CTd0Yj98.js +0 -10
- package/dist/client/assets/classDiagram-DTDB5LWJ-7fEmn0zV.js +0 -1
- package/dist/client/assets/classDiagram-v2-JRS7N3AN-7fEmn0zV.js +0 -1
- package/dist/client/assets/comment-threads-9J6Em-_y.js +0 -4
- package/dist/client/assets/comment-ui-shared-DuoyznXW.js +0 -7
- package/dist/client/assets/diagram-VSXAHHWV-CQRVEZR7.js +0 -3
- package/dist/client/assets/dist-73RPdcyU.js +0 -1
- package/dist/client/assets/dist-CH-sQu3r.js +0 -1
- package/dist/client/assets/document-formatter-Dxl_a8XO.js +0 -2
- package/dist/client/assets/drawio-editor-BVaNNtYp.js +0 -9
- package/dist/client/assets/drawioEditor-BkIBlXx0.js +0 -2
- package/dist/client/assets/ebnfDiagram-PWID7BFC-OXa8VEp1.js +0 -1
- package/dist/client/assets/editor-session-DMyVdHLj.js +0 -54
- package/dist/client/assets/excalidraw-editor-BjYaNEMY.js +0 -9
- package/dist/client/assets/excalidraw-mermaid-stub-BNckbX2n.js +0 -1
- package/dist/client/assets/excalidraw-mermaid-stub-DE5FTiZB.js +0 -1
- package/dist/client/assets/excalidrawEditor-ChqF_d82.js +0 -2
- package/dist/client/assets/export-page-Dyu0XCbu.js +0 -57
- package/dist/client/assets/exportDocument-CJBx4Xjx.css +0 -1
- package/dist/client/assets/exportDocument-Dgvfa2Sd.js +0 -2
- package/dist/client/assets/flowDiagram-A5DVABFB-CiW1eN8C.js +0 -1
- package/dist/client/assets/image-ZVKINSPA-0zXSnE7M.js +0 -1
- package/dist/client/assets/index-CSNIMyOP.css +0 -1
- package/dist/client/assets/index-DG3N2Ps-.js +0 -2
- package/dist/client/assets/main-D0wCxpVn.js +0 -903
- package/dist/client/assets/mermaid.core-BJRLUQZE.js +0 -11
- package/dist/client/assets/pegDiagram-XKGWAZYB-B31fKPR2.js +0 -1
- package/dist/client/assets/preview-render-compiler-CrJIooVD.js +0 -35
- package/dist/client/assets/preview-render-compiler-O0JxBVNF.js +0 -1
- package/dist/client/assets/preview-render-profile-B4RWYWHt.js +0 -1
- package/dist/client/assets/prod-BiYPMR1i.js +0 -177
- package/dist/client/assets/prod-VxdGZ5Ly.js +0 -1
- package/dist/client/assets/quick-switcher-controller-CaEjEtq_.js +0 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BPifP0MB.js +0 -1
- package/dist/client/assets/runtime-config-BLrEKVO1.js +0 -1
- package/dist/client/assets/stateDiagram-v2-4QOOHH4V-B--gjrit.js +0 -1
- package/dist/client/assets/swimlanesDiagram-VK2B7HYN-DO8XNXEg.js +0 -8
- package/dist/client/assets/vault-api-client-BkwdFjx0.js +0 -6
- package/src/client/app/drawio-editor-entry.js +0 -1
- package/src/client/app/excalidraw-editor-entry.js +0 -1
- package/src/client/excalidraw-mermaid-stub.js +0 -7
- package/src/client/main.js +0 -16
- /package/dist/client/assets/{drawio-editor-UQQ8XHou.css → drawioEditor-UQQ8XHou.css} +0 -0
- /package/dist/client/assets/{excalidraw-editor-BbSbI80a.css → excalidrawEditor-BbSbI80a.css} +0 -0
- /package/dist/client/assets/{sizeCapture-X5ZJPWSS-B0uUizjq.js → sizeCapture-INFHLROL-B0uUizjq.js} +0 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateNKeysBetween,
|
|
3
|
+
validateOrderKey,
|
|
4
|
+
} from '@excalidraw/fractional-indexing';
|
|
5
|
+
|
|
6
|
+
const ELEMENT_TYPES = new Set([
|
|
7
|
+
'arrow',
|
|
8
|
+
'diamond',
|
|
9
|
+
'ellipse',
|
|
10
|
+
'freedraw',
|
|
11
|
+
'line',
|
|
12
|
+
'rectangle',
|
|
13
|
+
'text',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const LINEAR_TYPES = new Set(['arrow', 'freedraw', 'line']);
|
|
17
|
+
|
|
18
|
+
function invalid(message) {
|
|
19
|
+
const error = new Error(message);
|
|
20
|
+
error.code = 'EXCALIDRAW_SCENE_INVALID';
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stableNumber(value) {
|
|
25
|
+
let hash = 2166136261;
|
|
26
|
+
for (const character of value) {
|
|
27
|
+
hash ^= character.codePointAt(0);
|
|
28
|
+
hash = Math.imul(hash, 16777619);
|
|
29
|
+
}
|
|
30
|
+
return (hash >>> 0) || 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function requireObject(value, name) {
|
|
34
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
35
|
+
throw invalid(`${name} must be an object`);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function requireNumber(value, name) {
|
|
41
|
+
if (!Number.isFinite(value)) throw invalid(`${name} must be a finite number`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizePoints(points, type) {
|
|
46
|
+
if (!LINEAR_TYPES.has(type)) return points;
|
|
47
|
+
const source = points ?? [[0, 0], [100, 0]];
|
|
48
|
+
if (!Array.isArray(source) || source.length < 2 || source.length > 1_000) {
|
|
49
|
+
throw invalid(`${type} points must contain 2 to 1000 coordinate pairs`);
|
|
50
|
+
}
|
|
51
|
+
return source.map((point) => {
|
|
52
|
+
if (!Array.isArray(point) || point.length !== 2) {
|
|
53
|
+
throw invalid(`${type} points must be [x, y] coordinate pairs`);
|
|
54
|
+
}
|
|
55
|
+
return [requireNumber(point[0], `${type} point x`), requireNumber(point[1], `${type} point y`)];
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizePointGeometry(points, x, y, type) {
|
|
60
|
+
const [offsetX, offsetY] = points[0];
|
|
61
|
+
const normalizedPoints = points.map(([pointX, pointY]) => [
|
|
62
|
+
pointX - offsetX,
|
|
63
|
+
pointY - offsetY,
|
|
64
|
+
]);
|
|
65
|
+
const xs = normalizedPoints.map(([pointX]) => pointX);
|
|
66
|
+
const ys = normalizedPoints.map(([, pointY]) => pointY);
|
|
67
|
+
const width = Math.max(...xs) - Math.min(...xs);
|
|
68
|
+
const height = Math.max(...ys) - Math.min(...ys);
|
|
69
|
+
if (width === 0 && height === 0) {
|
|
70
|
+
throw invalid(`${type} points must span a non-zero distance`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
height,
|
|
74
|
+
points: normalizedPoints,
|
|
75
|
+
width,
|
|
76
|
+
x: x + offsetX,
|
|
77
|
+
y: y + offsetY,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function readElementGeometry(raw, id, type) {
|
|
82
|
+
const x = requireNumber(raw.x, `Element ${id} x`);
|
|
83
|
+
const y = requireNumber(raw.y, `Element ${id} y`);
|
|
84
|
+
const points = normalizePoints(raw.points, type);
|
|
85
|
+
if (points) {
|
|
86
|
+
return {
|
|
87
|
+
...normalizePointGeometry(points, x, y, type),
|
|
88
|
+
fontSize: raw.fontSize,
|
|
89
|
+
text: raw.text,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const fontSize = type === 'text'
|
|
94
|
+
? requireNumber(raw.fontSize ?? 20, `Element ${id} fontSize`)
|
|
95
|
+
: raw.fontSize;
|
|
96
|
+
if (type === 'text' && fontSize <= 0) throw invalid(`Element ${id} fontSize must be positive`);
|
|
97
|
+
const text = type === 'text' ? String(raw.text ?? '') : raw.text;
|
|
98
|
+
const lineHeight = type === 'text'
|
|
99
|
+
? requireNumber(raw.lineHeight ?? 1.25, `Element ${id} lineHeight`)
|
|
100
|
+
: null;
|
|
101
|
+
if (type === 'text' && lineHeight <= 0) throw invalid(`Element ${id} lineHeight must be positive`);
|
|
102
|
+
const defaultWidth = type === 'text'
|
|
103
|
+
? Math.max(1, ...text.split('\n').map((line) => line.length)) * fontSize * 0.6
|
|
104
|
+
: 100;
|
|
105
|
+
const defaultHeight = type === 'text'
|
|
106
|
+
? Math.max(1, text.split('\n').length) * fontSize * lineHeight
|
|
107
|
+
: 100;
|
|
108
|
+
const width = requireNumber(raw.width ?? defaultWidth, `Element ${id} width`);
|
|
109
|
+
const height = requireNumber(raw.height ?? defaultHeight, `Element ${id} height`);
|
|
110
|
+
if (width <= 0 || height <= 0) {
|
|
111
|
+
throw invalid(`Element ${id} width and height must be positive`);
|
|
112
|
+
}
|
|
113
|
+
return { fontSize, height, points, text, width, x, y };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function createBaseElement(raw, {
|
|
117
|
+
height,
|
|
118
|
+
id,
|
|
119
|
+
now,
|
|
120
|
+
type,
|
|
121
|
+
width,
|
|
122
|
+
x,
|
|
123
|
+
y,
|
|
124
|
+
}) {
|
|
125
|
+
const rest = { ...raw };
|
|
126
|
+
delete rest.afterElementId;
|
|
127
|
+
delete rest.beforeElementId;
|
|
128
|
+
delete rest.endElementId;
|
|
129
|
+
delete rest.startElementId;
|
|
130
|
+
return {
|
|
131
|
+
...rest,
|
|
132
|
+
angle: raw.angle ?? 0,
|
|
133
|
+
backgroundColor: raw.backgroundColor ?? 'transparent',
|
|
134
|
+
boundElements: raw.boundElements ?? null,
|
|
135
|
+
fillStyle: raw.fillStyle ?? 'solid',
|
|
136
|
+
frameId: raw.frameId ?? null,
|
|
137
|
+
groupIds: raw.groupIds ?? [],
|
|
138
|
+
height,
|
|
139
|
+
id,
|
|
140
|
+
index: raw.index ?? null,
|
|
141
|
+
isDeleted: false,
|
|
142
|
+
link: raw.link ?? null,
|
|
143
|
+
locked: raw.locked ?? false,
|
|
144
|
+
opacity: raw.opacity ?? 100,
|
|
145
|
+
roughness: raw.roughness ?? 1,
|
|
146
|
+
roundness: raw.roundness ?? (['diamond', 'ellipse', 'rectangle'].includes(type) ? { type: 3 } : null),
|
|
147
|
+
seed: raw.seed ?? stableNumber(`${id}:seed`),
|
|
148
|
+
strokeColor: raw.strokeColor ?? '#1e1e1e',
|
|
149
|
+
strokeStyle: raw.strokeStyle ?? 'solid',
|
|
150
|
+
strokeWidth: raw.strokeWidth ?? 2,
|
|
151
|
+
type,
|
|
152
|
+
updated: raw.updated ?? now,
|
|
153
|
+
version: raw.version ?? 1,
|
|
154
|
+
versionNonce: raw.versionNonce ?? stableNumber(`${id}:nonce`),
|
|
155
|
+
width,
|
|
156
|
+
x,
|
|
157
|
+
y,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function addLinearElementFields(element, raw) {
|
|
162
|
+
const startBinding = raw.startElementId
|
|
163
|
+
? { elementId: raw.startElementId, fixedPoint: null, focus: 0, gap: 4 }
|
|
164
|
+
: raw.startBinding ?? null;
|
|
165
|
+
const endBinding = raw.endElementId
|
|
166
|
+
? { elementId: raw.endElementId, fixedPoint: null, focus: 0, gap: 4 }
|
|
167
|
+
: raw.endBinding ?? null;
|
|
168
|
+
element.endBinding = endBinding;
|
|
169
|
+
element.lastCommittedPoint = raw.lastCommittedPoint ?? null;
|
|
170
|
+
element.startBinding = startBinding;
|
|
171
|
+
if (element.type === 'arrow') {
|
|
172
|
+
element.endArrowhead = raw.endArrowhead ?? 'arrow';
|
|
173
|
+
element.startArrowhead = raw.startArrowhead ?? null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function addFreeDrawElementFields(element, raw) {
|
|
178
|
+
element.pressures = Array.isArray(raw.pressures) ? raw.pressures : [];
|
|
179
|
+
element.simulatePressure = raw.simulatePressure ?? true;
|
|
180
|
+
element.strokeOptions = raw.strokeOptions ?? {
|
|
181
|
+
streamline: 0.5,
|
|
182
|
+
variability: 'variable',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function addTextElementFields(element, raw, { fontSize, text }) {
|
|
187
|
+
element.autoResize = raw.autoResize ?? true;
|
|
188
|
+
element.containerId = raw.containerId ?? null;
|
|
189
|
+
element.fontFamily = raw.fontFamily ?? 1;
|
|
190
|
+
element.fontSize = fontSize;
|
|
191
|
+
element.lineHeight = raw.lineHeight ?? 1.25;
|
|
192
|
+
element.originalText = raw.originalText ?? text;
|
|
193
|
+
element.text = text;
|
|
194
|
+
element.textAlign = raw.textAlign ?? 'center';
|
|
195
|
+
element.verticalAlign = raw.verticalAlign ?? 'middle';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeElement(rawElement, now) {
|
|
199
|
+
const raw = requireObject(rawElement, 'Element');
|
|
200
|
+
const id = String(raw.id ?? '').trim();
|
|
201
|
+
if (!id || id.length > 128) throw invalid('Element id must contain 1 to 128 characters');
|
|
202
|
+
if (!ELEMENT_TYPES.has(raw.type)) throw invalid(`Unsupported Excalidraw element type: ${raw.type}`);
|
|
203
|
+
|
|
204
|
+
const type = raw.type;
|
|
205
|
+
const geometry = readElementGeometry(raw, id, type);
|
|
206
|
+
const element = createBaseElement(raw, { ...geometry, id, now, type });
|
|
207
|
+
if (geometry.points) element.points = geometry.points;
|
|
208
|
+
if (type === 'arrow' || type === 'line') addLinearElementFields(element, raw);
|
|
209
|
+
if (type === 'freedraw') addFreeDrawElementFields(element, raw);
|
|
210
|
+
if (type === 'text') addTextElementFields(element, raw, geometry);
|
|
211
|
+
return element;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function bumpElementVersion(element, now, reason) {
|
|
215
|
+
const version = (Number(element.version) || 0) + 1;
|
|
216
|
+
return {
|
|
217
|
+
...element,
|
|
218
|
+
updated: now,
|
|
219
|
+
version,
|
|
220
|
+
versionNonce: stableNumber(`${element.id}:${reason}:${version}:${now}`),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function moveElement(elements, id, directive, name) {
|
|
225
|
+
const { action, afterElementId, beforeElementId } = directive ?? {};
|
|
226
|
+
const instructions = [action, beforeElementId, afterElementId].filter((value) => value !== undefined);
|
|
227
|
+
if (instructions.length === 0) return;
|
|
228
|
+
if (instructions.length !== 1) {
|
|
229
|
+
throw invalid(`${name} must specify exactly one action, beforeElementId, or afterElementId`);
|
|
230
|
+
}
|
|
231
|
+
if (action !== undefined && !['bringToFront', 'sendToBack'].includes(action)) {
|
|
232
|
+
throw invalid(`${name} uses unsupported action ${action}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const currentIndex = elements.findIndex((element) => element.id === id && !element.isDeleted);
|
|
236
|
+
if (currentIndex < 0) throw invalid(`Excalidraw element not found: ${id}`);
|
|
237
|
+
const [element] = elements.splice(currentIndex, 1);
|
|
238
|
+
let targetIndex;
|
|
239
|
+
if (action === 'sendToBack') {
|
|
240
|
+
targetIndex = elements.findIndex((candidate) => !candidate.isDeleted);
|
|
241
|
+
if (targetIndex < 0) targetIndex = elements.length;
|
|
242
|
+
} else if (action === 'bringToFront') {
|
|
243
|
+
const lastActiveIndex = elements.findLastIndex((candidate) => !candidate.isDeleted);
|
|
244
|
+
targetIndex = lastActiveIndex < 0 ? elements.length : lastActiveIndex + 1;
|
|
245
|
+
} else {
|
|
246
|
+
const targetId = beforeElementId ?? afterElementId;
|
|
247
|
+
if (targetId === id) throw invalid(`${name} cannot target its own element`);
|
|
248
|
+
const referenceIndex = elements.findIndex((candidate) => (
|
|
249
|
+
candidate.id === targetId && !candidate.isDeleted
|
|
250
|
+
));
|
|
251
|
+
if (referenceIndex < 0) {
|
|
252
|
+
throw invalid(`${name} targets missing element ${targetId}`);
|
|
253
|
+
}
|
|
254
|
+
targetIndex = beforeElementId ? referenceIndex : referenceIndex + 1;
|
|
255
|
+
}
|
|
256
|
+
elements.splice(targetIndex, 0, element);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function hasValidElementOrder(elements) {
|
|
260
|
+
let previousIndex = null;
|
|
261
|
+
for (const element of elements) {
|
|
262
|
+
if (element.isDeleted) continue;
|
|
263
|
+
if (typeof element.index !== 'string') return false;
|
|
264
|
+
try {
|
|
265
|
+
validateOrderKey(element.index);
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (previousIndex !== null && element.index <= previousIndex) return false;
|
|
270
|
+
previousIndex = element.index;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function normalizeElementOrder(elements, now, alreadyVersionedIds = new Set()) {
|
|
276
|
+
if (hasValidElementOrder(elements)) return elements;
|
|
277
|
+
const activeElements = elements.filter((element) => !element.isDeleted);
|
|
278
|
+
const indices = generateNKeysBetween(null, null, activeElements.length);
|
|
279
|
+
let activeIndex = 0;
|
|
280
|
+
return elements.map((element) => {
|
|
281
|
+
if (element.isDeleted) return element;
|
|
282
|
+
const index = indices[activeIndex];
|
|
283
|
+
activeIndex += 1;
|
|
284
|
+
if (element.index === index) return element;
|
|
285
|
+
if (alreadyVersionedIds.has(element.id)) {
|
|
286
|
+
return { ...element, index };
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
...bumpElementVersion(element, now, `index:${index}`),
|
|
290
|
+
index,
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function refreshArrowBindings(elements) {
|
|
296
|
+
const activeById = new Map(elements.filter((element) => !element.isDeleted).map((element) => [element.id, element]));
|
|
297
|
+
for (const element of activeById.values()) {
|
|
298
|
+
if (Array.isArray(element.boundElements)) {
|
|
299
|
+
element.boundElements = element.boundElements.filter(({ type }) => type !== 'arrow' && type !== 'text');
|
|
300
|
+
if (element.boundElements.length === 0) element.boundElements = null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
for (const element of activeById.values()) {
|
|
304
|
+
if (element.type === 'text' && element.containerId) {
|
|
305
|
+
const container = activeById.get(element.containerId);
|
|
306
|
+
if (!container) throw invalid(`Element ${element.id} binds to missing container ${element.containerId}`);
|
|
307
|
+
container.boundElements = [...(container.boundElements ?? []), { id: element.id, type: 'text' }];
|
|
308
|
+
}
|
|
309
|
+
if (element.type !== 'arrow' && element.type !== 'line') continue;
|
|
310
|
+
for (const binding of [element.startBinding, element.endBinding]) {
|
|
311
|
+
if (!binding?.elementId) continue;
|
|
312
|
+
const target = activeById.get(binding.elementId);
|
|
313
|
+
if (!target) throw invalid(`Element ${element.id} binds to missing element ${binding.elementId}`);
|
|
314
|
+
target.boundElements = [...(target.boundElements ?? []), { id: element.id, type: 'arrow' }];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function validateUniqueIds(elements) {
|
|
320
|
+
const ids = new Set();
|
|
321
|
+
for (const element of elements) {
|
|
322
|
+
if (ids.has(element.id)) throw invalid(`Duplicate Excalidraw element id: ${element.id}`);
|
|
323
|
+
ids.add(element.id);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveTranslationIds(elements, requestedIds) {
|
|
328
|
+
const resolved = new Set(requestedIds);
|
|
329
|
+
for (const id of resolved) {
|
|
330
|
+
const element = elements.find((candidate) => candidate.id === id && !candidate.isDeleted);
|
|
331
|
+
if (!element) throw invalid(`Excalidraw element not found: ${id}`);
|
|
332
|
+
if (element.containerId) resolved.add(element.containerId);
|
|
333
|
+
const groupIds = Array.isArray(element.groupIds) ? element.groupIds : [];
|
|
334
|
+
for (const candidate of elements) {
|
|
335
|
+
if (candidate.isDeleted) continue;
|
|
336
|
+
const sharesTranslationGroup = Array.isArray(candidate.groupIds)
|
|
337
|
+
&& candidate.groupIds.some((groupId) => groupIds.includes(groupId));
|
|
338
|
+
if (candidate.containerId === id || sharesTranslationGroup) resolved.add(candidate.id);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return [...resolved];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function createAgentExcalidrawScene(rawElements) {
|
|
345
|
+
if (!Array.isArray(rawElements) || rawElements.length < 1 || rawElements.length > 200) {
|
|
346
|
+
throw invalid('Excalidraw scene must contain 1 to 200 elements');
|
|
347
|
+
}
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
let elements = rawElements.map((element) => normalizeElement(element, now));
|
|
350
|
+
validateUniqueIds(elements);
|
|
351
|
+
rawElements.forEach((rawElement) => {
|
|
352
|
+
moveElement(
|
|
353
|
+
elements,
|
|
354
|
+
String(rawElement?.id ?? '').trim(),
|
|
355
|
+
rawElement,
|
|
356
|
+
`Element ${rawElement?.id ?? ''} layer placement`,
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
elements = normalizeElementOrder(elements, now, new Set(elements.map(({ id }) => id)));
|
|
360
|
+
refreshArrowBindings(elements);
|
|
361
|
+
return {
|
|
362
|
+
appState: {
|
|
363
|
+
gridSize: null,
|
|
364
|
+
viewBackgroundColor: '#ffffff',
|
|
365
|
+
},
|
|
366
|
+
elements,
|
|
367
|
+
files: {},
|
|
368
|
+
source: 'collabmd',
|
|
369
|
+
type: 'excalidraw',
|
|
370
|
+
version: 2,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function applyAgentExcalidrawEdits(scene, edits = {}) {
|
|
375
|
+
requireObject(scene, 'Excalidraw scene');
|
|
376
|
+
if (scene.type !== 'excalidraw' || !Array.isArray(scene.elements)) {
|
|
377
|
+
throw invalid('Document is not a valid Excalidraw scene');
|
|
378
|
+
}
|
|
379
|
+
requireObject(edits, 'Excalidraw edits');
|
|
380
|
+
const create = edits.create ?? [];
|
|
381
|
+
const update = edits.update ?? [];
|
|
382
|
+
const replace = edits.replace ?? [];
|
|
383
|
+
const remove = edits.delete ?? [];
|
|
384
|
+
const reorder = edits.reorder ?? [];
|
|
385
|
+
const translate = edits.translate ?? null;
|
|
386
|
+
const operations = [create, update, replace, remove, reorder];
|
|
387
|
+
if (!operations.every(Array.isArray)) {
|
|
388
|
+
throw invalid('Excalidraw edit operations must be arrays');
|
|
389
|
+
}
|
|
390
|
+
let translatedIds = [];
|
|
391
|
+
let translateDx = 0;
|
|
392
|
+
let translateDy = 0;
|
|
393
|
+
if (translate !== null) {
|
|
394
|
+
const operation = requireObject(translate, 'Translate operation');
|
|
395
|
+
if (!Array.isArray(operation.ids) || operation.ids.length === 0) {
|
|
396
|
+
throw invalid('Translate operation must include at least one element id');
|
|
397
|
+
}
|
|
398
|
+
translatedIds = operation.ids.map((id) => String(id ?? '').trim());
|
|
399
|
+
if (translatedIds.some((id) => !id) || new Set(translatedIds).size !== translatedIds.length) {
|
|
400
|
+
throw invalid('Translate operation element ids must be non-empty and unique');
|
|
401
|
+
}
|
|
402
|
+
translateDx = requireNumber(operation.dx, 'Translate dx');
|
|
403
|
+
translateDy = requireNumber(operation.dy, 'Translate dy');
|
|
404
|
+
if (translateDx === 0 && translateDy === 0) {
|
|
405
|
+
throw invalid('Translate operation must move elements');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const operationCount = operations.reduce((count, entries) => count + entries.length, translatedIds.length);
|
|
409
|
+
if (operationCount < 1) {
|
|
410
|
+
throw invalid('Excalidraw edits must include at least one operation');
|
|
411
|
+
}
|
|
412
|
+
if (operationCount > 200) {
|
|
413
|
+
throw invalid('Excalidraw edits cannot exceed 200 operations');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const now = Date.now();
|
|
417
|
+
let elements = scene.elements.map((element) => ({ ...element }));
|
|
418
|
+
const byId = new Map(elements.map((element) => [element.id, element]));
|
|
419
|
+
const alreadyVersionedIds = new Set();
|
|
420
|
+
const createdElements = create.map((raw) => {
|
|
421
|
+
const element = normalizeElement(raw, now);
|
|
422
|
+
if (byId.has(element.id)) throw invalid(`Excalidraw element already exists: ${element.id}`);
|
|
423
|
+
elements.push(element);
|
|
424
|
+
byId.set(element.id, element);
|
|
425
|
+
alreadyVersionedIds.add(element.id);
|
|
426
|
+
return { element, raw };
|
|
427
|
+
});
|
|
428
|
+
createdElements.forEach(({ element, raw }) => {
|
|
429
|
+
moveElement(elements, element.id, raw, `Element ${element.id} layer placement`);
|
|
430
|
+
});
|
|
431
|
+
update.forEach((operation) => {
|
|
432
|
+
requireObject(operation, 'Update operation');
|
|
433
|
+
const id = String(operation.id ?? '').trim();
|
|
434
|
+
const current = byId.get(id);
|
|
435
|
+
if (!current || current.isDeleted) throw invalid(`Excalidraw element not found: ${id}`);
|
|
436
|
+
const set = requireObject(operation.set, `Update ${id} set`);
|
|
437
|
+
if (
|
|
438
|
+
['id', 'index', 'isDeleted', 'type', 'version', 'versionNonce']
|
|
439
|
+
.some((field) => Object.hasOwn(set, field))
|
|
440
|
+
) {
|
|
441
|
+
throw invalid(`Update ${id} cannot change identity, type, deletion, version, or layer fields`);
|
|
442
|
+
}
|
|
443
|
+
const raw = { ...current, ...set };
|
|
444
|
+
if (current.type === 'text' && Object.hasOwn(set, 'text') && !Object.hasOwn(set, 'originalText')) {
|
|
445
|
+
raw.originalText = String(set.text ?? '');
|
|
446
|
+
}
|
|
447
|
+
const resizesStandaloneText = current.type === 'text'
|
|
448
|
+
&& !raw.containerId
|
|
449
|
+
&& raw.autoResize !== false
|
|
450
|
+
&& ['autoResize', 'fontFamily', 'fontSize', 'lineHeight', 'text']
|
|
451
|
+
.some((field) => Object.hasOwn(set, field));
|
|
452
|
+
if (resizesStandaloneText) {
|
|
453
|
+
delete raw.width;
|
|
454
|
+
delete raw.height;
|
|
455
|
+
}
|
|
456
|
+
const next = bumpElementVersion(normalizeElement(raw, now), now, 'update');
|
|
457
|
+
elements[elements.indexOf(current)] = next;
|
|
458
|
+
byId.set(id, next);
|
|
459
|
+
alreadyVersionedIds.add(id);
|
|
460
|
+
});
|
|
461
|
+
replace.forEach((operation) => {
|
|
462
|
+
requireObject(operation, 'Replace operation');
|
|
463
|
+
const id = String(operation.id ?? '').trim();
|
|
464
|
+
const current = byId.get(id);
|
|
465
|
+
if (!current || current.isDeleted) throw invalid(`Excalidraw element not found: ${id}`);
|
|
466
|
+
const rawElement = requireObject(operation.element, `Replace ${id} element`);
|
|
467
|
+
if (rawElement.id !== undefined && String(rawElement.id) !== id) {
|
|
468
|
+
throw invalid(`Replace ${id} element id must match the replaced element`);
|
|
469
|
+
}
|
|
470
|
+
const next = bumpElementVersion(normalizeElement({
|
|
471
|
+
...rawElement,
|
|
472
|
+
id,
|
|
473
|
+
index: current.index,
|
|
474
|
+
updated: current.updated,
|
|
475
|
+
version: current.version,
|
|
476
|
+
versionNonce: current.versionNonce,
|
|
477
|
+
}, now), now, 'replace');
|
|
478
|
+
elements[elements.indexOf(current)] = next;
|
|
479
|
+
byId.set(id, next);
|
|
480
|
+
alreadyVersionedIds.add(id);
|
|
481
|
+
});
|
|
482
|
+
if (translatedIds.length > 0) {
|
|
483
|
+
translatedIds = resolveTranslationIds(elements, translatedIds);
|
|
484
|
+
const expandedOperationCount = operations.reduce((count, entries) => count + entries.length, translatedIds.length);
|
|
485
|
+
if (expandedOperationCount > 200) {
|
|
486
|
+
throw invalid('Excalidraw edits cannot exceed 200 operations including related translations');
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
translatedIds.forEach((id) => {
|
|
490
|
+
const current = byId.get(id);
|
|
491
|
+
if (!current || current.isDeleted) throw invalid(`Excalidraw element not found: ${id}`);
|
|
492
|
+
const next = bumpElementVersion({
|
|
493
|
+
...current,
|
|
494
|
+
x: requireNumber(current.x, `Element ${id} x`) + translateDx,
|
|
495
|
+
y: requireNumber(current.y, `Element ${id} y`) + translateDy,
|
|
496
|
+
}, now, 'translate');
|
|
497
|
+
elements[elements.indexOf(current)] = next;
|
|
498
|
+
byId.set(id, next);
|
|
499
|
+
alreadyVersionedIds.add(id);
|
|
500
|
+
});
|
|
501
|
+
remove.forEach((rawId) => {
|
|
502
|
+
const id = String(rawId ?? '').trim();
|
|
503
|
+
const current = byId.get(id);
|
|
504
|
+
if (!current || current.isDeleted) throw invalid(`Excalidraw element not found: ${id}`);
|
|
505
|
+
const deleted = {
|
|
506
|
+
...bumpElementVersion(current, now, 'deleted'),
|
|
507
|
+
isDeleted: true,
|
|
508
|
+
};
|
|
509
|
+
elements[elements.indexOf(current)] = deleted;
|
|
510
|
+
byId.set(id, deleted);
|
|
511
|
+
alreadyVersionedIds.add(id);
|
|
512
|
+
});
|
|
513
|
+
reorder.forEach((operation) => {
|
|
514
|
+
requireObject(operation, 'Reorder operation');
|
|
515
|
+
const id = String(operation.id ?? '').trim();
|
|
516
|
+
moveElement(elements, id, operation, `Reorder ${id}`);
|
|
517
|
+
});
|
|
518
|
+
validateUniqueIds(elements);
|
|
519
|
+
elements = normalizeElementOrder(elements, now, alreadyVersionedIds);
|
|
520
|
+
refreshArrowBindings(elements);
|
|
521
|
+
return { ...scene, elements };
|
|
522
|
+
}
|