collabmd 0.1.44 → 0.1.46

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.
Files changed (291) hide show
  1. package/README.md +39 -19
  2. package/bin/collabmd.js +27 -1
  3. package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-CYM5nxUi.js +1 -0
  4. package/dist/client/assets/browser-utils--H_DkyEF.js +1 -0
  5. package/dist/client/assets/chunk-ERSWUTIN-hHdvnhaY.js +41 -0
  6. package/dist/client/assets/{chunk-CZQZEIFA-DT3N9fo7.js → chunk-YGJVY4SA-CjSe7Bx7.js} +2 -2
  7. package/dist/client/assets/comment-thread-store-CdcwrObc.js +1 -0
  8. package/dist/client/assets/comment-threads-9J6Em-_y.js +4 -0
  9. package/dist/client/assets/comment-ui-shared-BC4pt4w8.js +7 -0
  10. package/dist/client/assets/dist-B5HTH2BO.js +1 -0
  11. package/dist/client/assets/{dist-DhLvC8DS.js → dist-B5Jf1SLZ.js} +2 -2
  12. package/dist/client/assets/{dist-CnL7Y2sM.js → dist-BDt3rz7l.js} +1 -1
  13. package/dist/client/assets/dist-BEwK4csK.js +1 -0
  14. package/dist/client/assets/dist-BKW4PALF.js +1 -0
  15. package/dist/client/assets/dist-BWcHHOx-.js +1 -0
  16. package/dist/client/assets/{dist-CYZmbokb.js → dist-BalScOwZ.js} +9 -9
  17. package/dist/client/assets/dist-Bf8f4HmM.js +6 -0
  18. package/dist/client/assets/dist-BjHJDtyU.js +1 -0
  19. package/dist/client/assets/dist-Bm6pf9Bl.js +1 -0
  20. package/dist/client/assets/dist-BoVBdSjk.js +1 -0
  21. package/dist/client/assets/dist-CC5IrQMG.js +1 -0
  22. package/dist/client/assets/{dist-C11hHqv_.js → dist-CCP_AKPG.js} +12 -12
  23. package/dist/client/assets/dist-CZgOmxK6.js +1 -0
  24. package/dist/client/assets/{dist-D2AQ-76g.js → dist-Cqhv5LAk.js} +1 -1
  25. package/dist/client/assets/{dist-MvcV4190.js → dist-D-Z6FLem.js} +1 -1
  26. package/dist/client/assets/dist-DLMiIsNC.js +11 -0
  27. package/dist/client/assets/{dist-3bgCp7iY.js → dist-DZKu9Cs8.js} +1 -1
  28. package/dist/client/assets/dist-DaYdhcVs.js +1 -0
  29. package/dist/client/assets/dist-FWJsgGYI.js +1 -0
  30. package/dist/client/assets/dist-Kh2COra_.js +1 -0
  31. package/dist/client/assets/{dist-JYEDRrPT.js → dist-PDZ3bSqm.js} +1 -1
  32. package/dist/client/assets/{dist-Drp8uu5J.js → dist-TjMh8n1u.js} +1 -1
  33. package/dist/client/assets/{dist-DGb27Dl6.js → dist-dtAdgL39.js} +1 -1
  34. package/dist/client/assets/dist-jv4WdPnK.js +9 -0
  35. package/dist/client/assets/{dist-DK5Gs2wa.js → dist-rGBmqxC7.js} +1 -1
  36. package/dist/client/assets/{dist-DCzuX1AF.js → dist-shrl6K_0.js} +1 -1
  37. package/dist/client/assets/dist-tOaegDYS2.js +1 -0
  38. package/dist/client/assets/dist-ule2n6ak.js +1 -0
  39. package/dist/client/assets/dist-xElaxbia.js +1 -0
  40. package/dist/client/assets/drawio-editor-FzY1cVw8.js +9 -0
  41. package/dist/client/assets/drawioEditor-BjiKJqlc.js +2 -0
  42. package/dist/client/assets/editor-session-Cm9ekeNo.js +54 -0
  43. package/dist/client/assets/embedded-editor-base-CwTkk911.css +1 -0
  44. package/dist/client/assets/{en-GADW4C6J-DQ6zyCEM.js → en-7NFCYGKX-BrgYZAEe.js} +1 -1
  45. package/dist/client/assets/{preview-render-profile-BZzGSg6K.js → es-DXGV3RHd.js} +13 -13
  46. package/dist/client/assets/excalidraw-editor-B46zsTFI.js +9 -0
  47. package/dist/client/assets/excalidraw-editor-Bt7gMSoh.css +1 -0
  48. package/dist/client/assets/excalidraw-scene-BOben2gr.js +1 -0
  49. package/dist/client/assets/excalidrawEditor-CT2pHq7e.js +2 -0
  50. package/dist/client/assets/export-page-A7NHawJY.js +39 -0
  51. package/dist/client/assets/exportDocument-B2L8fe34.css +1 -0
  52. package/dist/client/assets/exportDocument-BaqcOAAW.js +2 -0
  53. package/dist/client/assets/file-icon-svg-CQAjlWjK.js +1 -0
  54. package/dist/client/assets/file-kind-BbqgGzpc.js +1 -0
  55. package/dist/client/assets/image-QXRYHUNP-SWEV8H0J.js +1 -0
  56. package/dist/client/assets/index-ClbjMBVt.js +2 -0
  57. package/dist/client/assets/index-Pum8ET-5.css +1 -0
  58. package/dist/client/assets/main-B3uPyqic.js +893 -0
  59. package/dist/client/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
  60. package/dist/client/assets/pdf-BL9Pr2D5.js +54 -0
  61. package/dist/client/assets/pdf.worker-CLesOks4.mjs +64857 -0
  62. package/dist/client/assets/pdf_viewer-FVkkThnz.js +551 -0
  63. package/dist/client/assets/preview-diagram-utils-txBCoW8d.js +1 -0
  64. package/dist/client/assets/{preview-render-compiler-BSaQO_p3.js → preview-render-compiler-BD-XhkS2.js} +2 -2
  65. package/dist/client/assets/preview-render-compiler-BKedaMxx.js +1 -0
  66. package/dist/client/assets/preview-render-profile-CE-5rVTC.js +1 -0
  67. package/dist/client/assets/{preview-render-worker-B42LWvrD.js → preview-render-worker-CiPqc9n6.js} +20 -20
  68. package/dist/client/assets/prod-Dy20X7eT.js +1 -0
  69. package/dist/client/assets/prod-hnwh7wRM.js +177 -0
  70. package/dist/client/assets/quick-switcher-controller-fqOYR7qR.js +12 -0
  71. package/dist/client/assets/runtime-config-BLrEKVO1.js +1 -0
  72. package/dist/client/assets/vault-api-client-_LpV1aIn.js +6 -0
  73. package/dist/client/assets/{yjs-provider-reset-guard-Cox4nxwu.js → yjs-provider-reset-guard-DE0vINDP.js} +1 -1
  74. package/dist/client/drawio-editor.html +3 -2
  75. package/dist/client/excalidraw-editor.html +3 -2
  76. package/dist/client/export-document.html +4 -3
  77. package/dist/client/index.html +109 -27
  78. package/docker-compose.yml +42 -0
  79. package/package.json +26 -15
  80. package/scripts/local-structurizr-compose.mjs +101 -0
  81. package/src/client/app/drawio-editor-entry.js +0 -3
  82. package/src/client/app/drawio-editor.html +1 -0
  83. package/src/client/app/excalidraw-editor-entry.js +0 -3
  84. package/src/client/app/excalidraw-editor.html +1 -0
  85. package/src/client/app/export-document-entry.js +0 -3
  86. package/src/client/app/export-document.html +1 -0
  87. package/src/client/app/index.html +106 -24
  88. package/src/client/app/main-entry.js +0 -5
  89. package/src/client/application/app-shell/chat-feature.js +180 -124
  90. package/src/client/application/app-shell/comments-feature.js +20 -11
  91. package/src/client/application/app-shell/git-feature.js +16 -9
  92. package/src/client/application/app-shell/presence-feature.js +18 -11
  93. package/src/client/application/app-shell/ui-feature-shell.js +78 -17
  94. package/src/client/application/app-shell/ui-feature-sidebar.js +74 -1
  95. package/src/client/application/app-shell/ui-feature-tab-activity.js +2 -2
  96. package/src/client/application/app-shell/ui-feature-toolbar.js +2 -52
  97. package/src/client/application/app-shell/workspace-feature.js +27 -84
  98. package/src/client/application/app-shell-elements.js +9 -1
  99. package/src/client/application/diagram-chrome.js +161 -166
  100. package/src/client/application/diagram-preview-export.js +7 -34
  101. package/src/client/application/diagram-preview-hydrator.js +1 -19
  102. package/src/client/application/markdown-frontmatter.js +0 -2
  103. package/src/client/application/mermaid-preview-hydrator.js +1 -14
  104. package/src/client/application/pdf-preview-controller.js +992 -0
  105. package/src/client/application/plantuml-preview-hydrator.js +0 -10
  106. package/src/client/application/preview-diagram-utils.js +31 -2
  107. package/src/client/application/preview-render-compiler.js +2 -1
  108. package/src/client/application/preview-renderer.js +2 -2
  109. package/src/client/application/quick-switcher-loader.js +3 -1
  110. package/src/client/application/structurizr-preview-controller.js +395 -0
  111. package/src/client/application/workspace-coordinator.js +25 -3
  112. package/src/client/application/workspace-preview-controller.js +91 -5
  113. package/src/client/bootstrap/collabmd-app-shell.js +155 -43
  114. package/src/client/browser-utils.js +44 -0
  115. package/src/client/domain/excalidraw-collaboration.js +0 -2
  116. package/src/client/domain/room.js +38 -0
  117. package/src/client/domain/runtime-paths.js +26 -5
  118. package/src/client/domain/structurizr-language.js +99 -0
  119. package/src/client/domain/vault-paths.js +48 -0
  120. package/src/client/excalidraw-editor.js +833 -51
  121. package/src/client/export/export-pipeline.js +6 -34
  122. package/src/client/infrastructure/auth-client.js +4 -10
  123. package/src/client/infrastructure/browser-notification-port.js +33 -20
  124. package/src/client/infrastructure/browser-preferences-port.js +46 -9
  125. package/src/client/infrastructure/comment-thread-store.js +34 -13
  126. package/src/client/infrastructure/editor-session.js +10 -0
  127. package/src/client/infrastructure/editor-view-adapter.js +133 -5
  128. package/src/client/infrastructure/excalidraw-room-client.js +103 -14
  129. package/src/client/infrastructure/git-api-client.js +5 -0
  130. package/src/client/infrastructure/lobby-presence.js +1 -0
  131. package/src/client/infrastructure/vault-api-client.js +51 -0
  132. package/src/client/infrastructure/workspace-sync-client.js +13 -1
  133. package/src/client/presentation/comment-overview-controller.js +18 -53
  134. package/src/client/presentation/comment-ui/comment-ui-card.js +1 -12
  135. package/src/client/presentation/comment-ui/comment-ui-layout.js +242 -219
  136. package/src/client/presentation/comment-ui/comment-ui-render.js +32 -45
  137. package/src/client/presentation/comment-ui/comment-ui-shared.js +68 -13
  138. package/src/client/presentation/comment-ui-controller.js +5 -1
  139. package/src/client/presentation/drawio-embed-controller.js +9 -102
  140. package/src/client/presentation/drawio-viewer.js +118 -0
  141. package/src/client/presentation/excalidraw-embed-controller.js +74 -8
  142. package/src/client/presentation/file-action-controller.js +134 -0
  143. package/src/client/presentation/file-explorer-controller.js +33 -3
  144. package/src/client/presentation/file-explorer-view.js +59 -34
  145. package/src/client/presentation/file-icon-svg.js +40 -0
  146. package/src/client/presentation/file-tree-state.js +12 -4
  147. package/src/client/presentation/git-diff-view-controller.js +731 -3
  148. package/src/client/presentation/outline-controller.js +86 -0
  149. package/src/client/presentation/quick-switcher-controller.js +257 -74
  150. package/src/client/presentation/quick-switcher-text-search.js +3 -3
  151. package/src/client/presentation/toast-controller.js +3 -0
  152. package/src/client/styles/components/feedback.css +6 -0
  153. package/src/client/styles/components/navigation.css +11 -1
  154. package/src/client/styles/features/collaboration-chat.css +49 -53
  155. package/src/client/styles/features/collaboration-presence.css +9 -7
  156. package/src/client/styles/features/comment-card.css +25 -11
  157. package/src/client/styles/features/comment-overlays.css +0 -4
  158. package/src/client/styles/features/comment-overview.css +44 -21
  159. package/src/client/styles/features/comments-drawer.css +2 -100
  160. package/src/client/styles/features/diagram-preview.css +81 -18
  161. package/src/client/styles/features/diff-special.css +309 -0
  162. package/src/client/styles/features/embedded-preview.css +8 -33
  163. package/src/client/styles/features/quick-switcher.css +8 -0
  164. package/src/client/styles/foundation/themes.css +6 -0
  165. package/src/client/styles/foundation/tokens.css +1 -0
  166. package/src/client/styles/layout/editor-page.css +40 -0
  167. package/src/client/styles/layout/responsive.css +31 -1
  168. package/src/client/styles/layout/shell.css +383 -7
  169. package/src/client/styles/layout/sidebar.css +47 -15
  170. package/src/client/styles/layout/view-modes.css +1 -0
  171. package/src/client/styles/overrides/codemirror.css +217 -15
  172. package/src/client/styles/style.css +1 -0
  173. package/src/client/styles/surfaces/excalidraw-editor.css +294 -0
  174. package/src/domain/comment-threads.js +63 -5
  175. package/src/domain/excalidraw-room-codec.js +6 -4
  176. package/src/domain/file-kind.js +35 -3
  177. package/src/server/application/workspace-reconciliation.js +1 -1
  178. package/src/server/config/env.js +79 -0
  179. package/src/server/create-app-server.js +14 -3
  180. package/src/server/domain/bases/base-definition.js +1 -1
  181. package/src/server/domain/bases/base-expression-runtime.js +1 -4
  182. package/src/server/domain/bases/base-index-snapshot-store.js +1 -4
  183. package/src/server/domain/bases/base-query-service.js +1 -77
  184. package/src/server/domain/bases/base-transform.js +1 -4
  185. package/src/server/domain/collaboration/collaboration-room.js +55 -21
  186. package/src/server/domain/comment-overview.js +25 -8
  187. package/src/server/domain/docx-exporter.js +24 -33
  188. package/src/server/domain/ripgrep-search-service.js +7 -1
  189. package/src/server/domain/workspace-state.js +32 -11
  190. package/src/server/infrastructure/git/diff-service.js +29 -13
  191. package/src/server/infrastructure/git/git-service.js +61 -10
  192. package/src/server/infrastructure/git/history-service.js +96 -29
  193. package/src/server/infrastructure/git/image-mime.js +14 -0
  194. package/src/server/infrastructure/git/path-utils.js +1 -1
  195. package/src/server/infrastructure/git/responses.js +0 -95
  196. package/src/server/infrastructure/git/untracked-files.js +28 -1
  197. package/src/server/infrastructure/github/github-app-client.js +22 -15
  198. package/src/server/infrastructure/http/create-git-api-query-handler.js +31 -1
  199. package/src/server/infrastructure/http/create-request-handler.js +18 -31
  200. package/src/server/infrastructure/http/create-static-handler.js +5 -2
  201. package/src/server/infrastructure/http/create-structurizr-api-handler.js +145 -0
  202. package/src/server/infrastructure/http/create-vault-api-command-handler.js +70 -8
  203. package/src/server/infrastructure/http/create-vault-api-query-handler.js +51 -3
  204. package/src/server/infrastructure/http/http-errors.js +7 -1
  205. package/src/server/infrastructure/http/http-request-helpers.js +22 -0
  206. package/src/server/infrastructure/persistence/path-utils.js +1 -1
  207. package/src/server/infrastructure/persistence/vault-file-store.js +3 -0
  208. package/src/server/infrastructure/structurizr/structurizr-embed.css +43 -0
  209. package/src/server/infrastructure/structurizr/structurizr-workspace-service.js +374 -0
  210. package/src/server/infrastructure/websocket/attach-collaboration-gateway.js +4 -22
  211. package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-CbSISC1w.js +0 -1
  212. package/dist/client/assets/bases-preview-controller-2pLHk0uk.js +0 -143
  213. package/dist/client/assets/browser-utils-CKtI_rZl.js +0 -1
  214. package/dist/client/assets/button-BCEiw4Rw.js +0 -1
  215. package/dist/client/assets/chunk-AXR3VHVW-0GTk_evH.js +0 -41
  216. package/dist/client/assets/comment-threads-D-iKphIp.js +0 -4
  217. package/dist/client/assets/diagram-action-icons-aa_KOv9h.js +0 -36
  218. package/dist/client/assets/dist-BH87JoHx.js +0 -11
  219. package/dist/client/assets/dist-BIkPRZO7.js +0 -6
  220. package/dist/client/assets/dist-BqCsFpx4.js +0 -1
  221. package/dist/client/assets/dist-C2JHCt8U.js +0 -1
  222. package/dist/client/assets/dist-CZjTtmTu.js +0 -1
  223. package/dist/client/assets/dist-Cig4Ov_X.js +0 -1
  224. package/dist/client/assets/dist-CqQEC8gR.js +0 -1
  225. package/dist/client/assets/dist-D4Rzqod1.js +0 -1
  226. package/dist/client/assets/dist-D5sn-mwk.js +0 -9
  227. package/dist/client/assets/dist-D7G-n87v.js +0 -1
  228. package/dist/client/assets/dist-DRqOrNgN.js +0 -1
  229. package/dist/client/assets/dist-DetkMS_4.js +0 -1
  230. package/dist/client/assets/dist-Dp6FVkQ1.js +0 -1
  231. package/dist/client/assets/dist-DtbjNB28.js +0 -1
  232. package/dist/client/assets/dist-DyrYcvpo2.js +0 -1
  233. package/dist/client/assets/dist-GrygD2zE.js +0 -1
  234. package/dist/client/assets/dist-HmEpWUow.js +0 -1
  235. package/dist/client/assets/dist-RTa81Km7.js +0 -1
  236. package/dist/client/assets/drawio-editor-DDvFoarP.js +0 -9
  237. package/dist/client/assets/drawio-embed-controller-BD1GTRwn.js +0 -1
  238. package/dist/client/assets/drawioEditor-Bu5grlH5.js +0 -2
  239. package/dist/client/assets/editor-session-DGlpiAsK.js +0 -16
  240. package/dist/client/assets/embedded-editor-base-C9roKYpW.css +0 -1
  241. package/dist/client/assets/excalidraw-editor-BHu6k8d4.js +0 -9
  242. package/dist/client/assets/excalidraw-editor-Cr_184EF.css +0 -1
  243. package/dist/client/assets/excalidraw-embed-controller-y2fenN_g.js +0 -1
  244. package/dist/client/assets/excalidraw-embed-reconciler-DGiagExg.js +0 -1
  245. package/dist/client/assets/excalidraw-scene-BvsQ6mLP.js +0 -176
  246. package/dist/client/assets/excalidrawEditor-4vFE--Uw.js +0 -2
  247. package/dist/client/assets/export-page-Cby8JzPO.js +0 -39
  248. package/dist/client/assets/exportDocument-C9v-bc7I.css +0 -1
  249. package/dist/client/assets/exportDocument-CRHCfsdo.js +0 -2
  250. package/dist/client/assets/file-history-view-controller-BDjEV9xJ.js +0 -112
  251. package/dist/client/assets/file-kind-BKPOIRPE.js +0 -1
  252. package/dist/client/assets/git-diff-view-controller-DgdM1aqb.js +0 -150
  253. package/dist/client/assets/git-panel-controller-BDTR2HVp.js +0 -207
  254. package/dist/client/assets/image-XGD7JQR6-XDUWHRpO.js +0 -1
  255. package/dist/client/assets/index-7xOyXDhP.css +0 -1
  256. package/dist/client/assets/index-Bz1KuQ6y.js +0 -2
  257. package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  258. package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  259. package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  260. package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  261. package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  262. package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  263. package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  264. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  265. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  266. package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  267. package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  268. package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  269. package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  270. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  271. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  272. package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  273. package/dist/client/assets/main-Ci07m9oP.js +0 -152
  274. package/dist/client/assets/preview-diagram-utils-n_bbJA9V.js +0 -1
  275. package/dist/client/assets/preview-render-compiler-csGM5ZFz.js +0 -1
  276. package/dist/client/assets/quick-switcher-controller-CoiYvllm.js +0 -12
  277. package/dist/client/assets/room-CDrmIdDt.js +0 -1
  278. package/dist/client/assets/runtime-config-CDgeuPWN.js +0 -1
  279. package/dist/client/assets/runtime-config-loader-BHGniHP5.js +0 -1
  280. package/dist/client/assets/runtime-paths-DvpihCEq.js +0 -1
  281. package/dist/client/assets/segmented-control-DJrHSz8M.js +0 -1
  282. package/dist/client/assets/vault-api-client-sLB2bo56.js +0 -1
  283. package/dist/client/assets/vault-paths-CVkuh0j-.js +0 -5
  284. package/src/client/bootstrap/lazy-controller-feature.js +0 -433
  285. package/src/client/infrastructure/backlinks-api-client.js +0 -16
  286. package/src/client/infrastructure/plantuml-api-client.js +0 -19
  287. package/src/client/infrastructure/runtime-config-loader.js +0 -56
  288. package/src/server/domain/collaboration/room-persistence-controller.js +0 -80
  289. package/src/server/infrastructure/git/command-runner.js +0 -49
  290. package/src/server/infrastructure/git/errors.js +0 -8
  291. package/src/server/infrastructure/http/create-esm-proxy-handler.js +0 -130
@@ -5,49 +5,32 @@ import {
5
5
  isExcalidrawFilePath,
6
6
  isImageAttachmentFilePath,
7
7
  isMermaidFilePath,
8
+ isPdfFilePath,
8
9
  isPlantUmlFilePath,
10
+ isStructurizrFilePath,
9
11
  stripVaultFileExtension,
10
12
  } from '../../../domain/file-kind.js';
11
13
 
12
14
  export const workspaceFeature = {
13
- isExcalidrawFile(filePath) {
14
- return isExcalidrawFilePath(filePath);
15
- },
16
-
17
- isBaseFile(filePath) {
18
- return isBaseFilePath(filePath);
19
- },
20
-
21
- isDrawioFile(filePath) {
22
- return isDrawioFilePath(filePath);
23
- },
24
-
25
- isImageFile(filePath) {
26
- return isImageAttachmentFilePath(filePath);
27
- },
28
-
29
- isMermaidFile(filePath) {
30
- return isMermaidFilePath(filePath);
31
- },
32
-
33
- isPlantUmlFile(filePath) {
34
- return isPlantUmlFilePath(filePath);
35
- },
36
-
37
- createDiagramPreviewDocument(language, source = '') {
38
- return this.workspacePreviewController.createDiagramPreviewDocument(language, source);
39
- },
15
+ isBaseFile: isBaseFilePath,
16
+ isDrawioFile: isDrawioFilePath,
17
+ isExcalidrawFile: isExcalidrawFilePath,
18
+ isImageFile: isImageAttachmentFilePath,
19
+ isPdfFile: isPdfFilePath,
20
+ isMermaidFile: isMermaidFilePath,
21
+ isPlantUmlFile: isPlantUmlFilePath,
22
+ isStructurizrWorkspaceFile: isStructurizrFilePath,
40
23
 
41
24
  getPreviewSource() {
42
25
  const previewDocument = this.getStaticPreviewDocument?.();
43
26
  const previewFilePath = previewDocument?.currentFilePath ?? previewDocument?.filePath ?? null;
44
27
  if (previewDocument && previewFilePath && previewFilePath === this.currentFilePath) {
45
28
  if (this.isMermaidFile(this.currentFilePath)) {
46
- return this.createDiagramPreviewDocument('mermaid', previewDocument.content);
29
+ return this.workspacePreviewController.createDiagramPreviewDocument('mermaid', previewDocument.content);
47
30
  }
48
31
 
49
32
  if (this.isPlantUmlFile(this.currentFilePath)) {
50
- return this.createDiagramPreviewDocument('plantuml', previewDocument.content);
33
+ return this.workspacePreviewController.createDiagramPreviewDocument('plantuml', previewDocument.content);
51
34
  }
52
35
 
53
36
  return String(previewDocument.content ?? '');
@@ -82,7 +65,7 @@ export const workspaceFeature = {
82
65
 
83
66
  supportsFileHistory(filePath) {
84
67
  const kind = getVaultFileKind(filePath);
85
- return kind !== null && kind !== 'image';
68
+ return kind !== null && kind !== 'image' && kind !== 'pdf';
86
69
  },
87
70
 
88
71
  getDisplayName(filePath) {
@@ -91,14 +74,6 @@ export const workspaceFeature = {
91
74
  .pop());
92
75
  },
93
76
 
94
- resetPreviewMode() {
95
- this.workspacePreviewController.resetPreviewMode();
96
- },
97
-
98
- syncFileChrome(filePath, options = {}) {
99
- this.workspacePreviewController.syncFileChrome(filePath, options);
100
- },
101
-
102
77
  handleLayoutViewRequest(view) {
103
78
  if (!this.currentFilePath || !this.isDrawioFile(this.currentFilePath)) {
104
79
  return true;
@@ -125,24 +100,22 @@ export const workspaceFeature = {
125
100
  return false;
126
101
  },
127
102
 
128
- renderExcalidrawFilePreview(filePath) {
129
- this.workspacePreviewController.renderExcalidrawFilePreview(filePath);
130
- },
131
-
132
- renderDrawioFilePreview(filePath) {
133
- this.workspacePreviewController.renderDrawioFilePreview(filePath);
103
+ renderBaseFilePreview(filePath, options) {
104
+ clearTimeout(this._basePreviewRenderTimer);
105
+ this._basePreviewRenderTimer = null;
106
+ return this.workspacePreviewController.renderBaseFilePreview(filePath, options);
134
107
  },
135
108
 
136
- renderImageFilePreview(filePath) {
137
- this.workspacePreviewController.renderImageFilePreview(filePath);
138
- },
139
-
140
- renderBaseFilePreview(filePath) {
141
- return this.workspacePreviewController.renderBaseFilePreview(filePath);
142
- },
143
-
144
- renderTextFilePreview(payload) {
145
- this.workspacePreviewController.renderTextFilePreview(payload);
109
+ scheduleBaseFilePreview(filePath, options) {
110
+ const loadToken = this.sessionLoadToken;
111
+ clearTimeout(this._basePreviewRenderTimer);
112
+ this._basePreviewRenderTimer = setTimeout(() => {
113
+ this._basePreviewRenderTimer = null;
114
+ if (filePath !== this.currentFilePath || loadToken !== this.sessionLoadToken) {
115
+ return;
116
+ }
117
+ void this.renderBaseFilePreview(filePath, options);
118
+ }, 180);
146
119
  },
147
120
 
148
121
  createResizeHandler() {
@@ -187,10 +160,6 @@ export const workspaceFeature = {
187
160
  });
188
161
  },
189
162
 
190
- async handleHashChange() {
191
- await this.workspaceRouteController.handleHashChange();
192
- },
193
-
194
163
  showEmptyState() {
195
164
  clearTimeout(this._previewLayoutSyncTimer);
196
165
  this._previewLayoutSyncTimer = null;
@@ -207,30 +176,4 @@ export const workspaceFeature = {
207
176
  this.workspaceRouteController.showDiffState();
208
177
  },
209
178
 
210
- async openFile(filePath) {
211
- await this.workspaceRouteController.openFile(filePath);
212
- },
213
-
214
- cleanupSession() {
215
- this.workspaceRouteController.cleanupSession();
216
- },
217
-
218
- handleWikiLinkClick(target) {
219
- this.wikiLinkFileController.handleWikiLinkClick(target);
220
- },
221
-
222
- normalizeNewWikiFilePath(target) {
223
- return this.wikiLinkFileController.normalizeNewWikiFilePath(target);
224
- },
225
-
226
- async createAndOpenFile(filePath, displayName) {
227
- await this.wikiLinkFileController.createAndOpenFile(filePath, displayName);
228
- },
229
-
230
- handleFileSelection(filePath, { closeSidebarOnMobile = false, revealInTree = false } = {}) {
231
- this.workspaceRouteController.handleFileSelection(filePath, {
232
- closeSidebarOnMobile,
233
- revealInTree,
234
- });
235
- },
236
179
  };
@@ -6,10 +6,13 @@ export function bindAppShellElements(doc = document) {
6
6
  chatForm: doc.getElementById('chatForm'),
7
7
  chatInput: doc.getElementById('chatInput'),
8
8
  chatMessages: doc.getElementById('chatMessages'),
9
- chatNotificationButton: doc.getElementById('chatNotificationBtn'),
10
9
  chatPanel: doc.getElementById('chatPanel'),
10
+ chatCloseButton: doc.getElementById('chatCloseBtn'),
11
11
  chatStatus: doc.getElementById('chatStatus'),
12
12
  chatToastContainer: doc.getElementById('chatToastContainer'),
13
+ chatNotificationButton: doc.getElementById('chatNotificationBtn'),
14
+ chatNotificationMuteButton: doc.getElementById('chatNotificationMuteBtn'),
15
+ chatNotificationStatus: doc.getElementById('chatNotificationStatus'),
13
16
  chatToggleBadge: doc.getElementById('chatToggleBadge'),
14
17
  chatToggleButton: doc.getElementById('chatToggleBtn'),
15
18
  gitCommitCancel: doc.getElementById('gitCommitCancel'),
@@ -57,17 +60,21 @@ export function bindAppShellElements(doc = document) {
57
60
  presencePanelList: doc.getElementById('presencePanelList'),
58
61
  presencePanelStatus: doc.getElementById('presencePanelStatus'),
59
62
  searchFilesButton: doc.getElementById('searchFilesBtn'),
63
+ toolbarSearchButton: doc.getElementById('toolbarSearchBtn'),
60
64
  shareButton: doc.getElementById('shareBtn'),
61
65
  toastContainer: doc.getElementById('toastContainer'),
62
66
  toggleWrapButton: doc.getElementById('toggleWrapBtn'),
67
+ toggleVimModeButton: doc.getElementById('toggleVimModeBtn'),
63
68
  userAvatars: doc.getElementById('userAvatars'),
64
69
  userCount: doc.getElementById('userCount'),
65
70
  wrapToggleLabel: doc.getElementById('wrapToggleLabel'),
71
+ vimModeToggleLabel: doc.getElementById('vimModeToggleLabel'),
66
72
  activeFileName: doc.getElementById('activeFileName'),
67
73
  sidebarToggle: doc.getElementById('sidebarToggle'),
68
74
  sidebarClose: doc.getElementById('sidebarClose'),
69
75
  sidebarBackdrop: doc.getElementById('sidebarBackdrop'),
70
76
  sidebar: doc.getElementById('sidebar'),
77
+ sidebarResizer: doc.getElementById('sidebarResizer'),
71
78
  emptyState: doc.getElementById('emptyState'),
72
79
  emptyStateNewFileBtn: doc.getElementById('emptyStateNewFileBtn'),
73
80
  emptyStateSearchBtn: doc.getElementById('emptyStateSearchBtn'),
@@ -90,6 +97,7 @@ export function bindAppShellElements(doc = document) {
90
97
  toolbarDiffBadge: doc.getElementById('toolbarDiffBadge'),
91
98
  toolbarOverflowMenu: doc.getElementById('toolbarOverflowMenu'),
92
99
  toolbarOverflowToggle: doc.getElementById('toolbarOverflowToggle'),
100
+ toolbarViewToggle: doc.getElementById('toolbarViewToggle'),
93
101
  sidebarTabs: doc.getElementById('sidebarTabs'),
94
102
  };
95
103
  }
@@ -1,6 +1,5 @@
1
1
  import { setDiagramActionButtonIcon } from '../domain/diagram-action-icons.js';
2
2
  import {
3
- downloadBlob,
4
3
  rasterizeSvgMarkupToPngBlob,
5
4
  writeBlobToClipboard,
6
5
  } from './diagram-preview-export.js';
@@ -9,13 +8,15 @@ import {
9
8
  getFrameViewportSize,
10
9
  } from './preview-diagram-utils.js';
11
10
  import { clamp } from '../domain/vault-utils.js';
11
+ import { downloadBlob } from '../browser-utils.js';
12
12
 
13
- export const DIAGRAM_CHROME_ZOOM = Object.freeze({
13
+ const DIAGRAM_CHROME_ZOOM = Object.freeze({
14
14
  animationDurationMs: 160,
15
15
  default: 1,
16
16
  max: 3,
17
17
  min: 0.1,
18
18
  step: 0.1,
19
+ wheelSensitivity: 0.01,
19
20
  });
20
21
 
21
22
  const DIAGRAM_CHROME_ZOOM_POLICY = Object.freeze({
@@ -80,7 +81,6 @@ export class DiagramChrome {
80
81
  this.shellControllers = new WeakMap();
81
82
  this.shellViewStates = new WeakMap();
82
83
  this.shellRefits = new WeakMap();
83
- this.resizeFrameId = null;
84
84
  }
85
85
 
86
86
  destroy() {
@@ -89,10 +89,6 @@ export class DiagramChrome {
89
89
  this.resizeObservers.clear();
90
90
  this.maximizedRoots.forEach((root) => root.remove());
91
91
  this.maximizedRoots.clear();
92
- if (this.resizeFrameId) {
93
- this.window.cancelAnimationFrame(this.resizeFrameId);
94
- this.resizeFrameId = null;
95
- }
96
92
  this.activeMaximizedShell = null;
97
93
  }
98
94
 
@@ -186,17 +182,6 @@ export class DiagramChrome {
186
182
  });
187
183
  }
188
184
 
189
- scheduleActiveRefitOnNextFrame() {
190
- if (this.resizeFrameId) {
191
- this.window.cancelAnimationFrame(this.resizeFrameId);
192
- }
193
-
194
- this.resizeFrameId = this.window.requestAnimationFrame(() => {
195
- this.resizeFrameId = null;
196
- this.scheduleActiveRefit();
197
- });
198
- }
199
-
200
185
  ensureMaximizedRoot(kind) {
201
186
  const config = getKindConfig(kind);
202
187
  const existing = this.maximizedRoots.get(kind);
@@ -282,12 +267,11 @@ export class DiagramChrome {
282
267
  const toolbar = this.document.createElement('div');
283
268
  toolbar.className = config.toolbarClassName;
284
269
  const leftGroup = this.document.createElement('div');
285
- leftGroup.className = 'diagram-preview-toolbar-group diagram-preview-toolbar-group--zoom';
270
+ leftGroup.className = 'diagram-preview-toolbar-group';
286
271
  const rightGroup = this.document.createElement('div');
287
272
  rightGroup.className = 'diagram-preview-toolbar-group diagram-preview-toolbar-group--actions';
288
273
 
289
- const decreaseButton = this.createButton(kind, '-', 'Zoom out');
290
- decreaseButton.textContent = '−';
274
+ const decreaseButton = this.createButton(kind, '', 'Zoom out');
291
275
  const increaseButton = this.createButton(kind, '+', 'Zoom in');
292
276
  const resetButton = this.createButton(kind, '', 'Reset zoom', { icon: 'fit' });
293
277
  const copyButton = this.createButton(kind, '', 'Copy image', { icon: 'copy' });
@@ -470,11 +454,11 @@ export class DiagramChrome {
470
454
  }
471
455
 
472
456
  let currentZoom = zoomPolicy.default;
473
- let defaultZoom = 1;
474
457
  let zoomAnimationFrameId = null;
458
+ let zoomAnimationTarget = null;
475
459
  let resetZoomFrameId = null;
476
- let layoutFrameId = null;
477
460
  let replacementFrameId = null;
461
+ let pinchState = null;
478
462
  let hasManualZoom = Boolean(
479
463
  previousViewState?.hasManualZoom
480
464
  || previousViewState?.scrollLeft > 0
@@ -484,15 +468,7 @@ export class DiagramChrome {
484
468
  const hasRestorableViewState = Number.isFinite(previousViewState?.zoom);
485
469
  let lastAutoFitViewportWidth = hasInitialViewport ? previousViewportWidth : 0;
486
470
  let shouldForceScheduledReset = false;
487
- let isPanning = false;
488
- let activePointerId = null;
489
- let panStartX = 0;
490
- let panStartY = 0;
491
- let panStartScrollLeft = 0;
492
- let panStartScrollTop = 0;
493
-
494
- diagramElement.style.display = 'block';
495
- diagramElement.style.margin = '0 auto';
471
+
496
472
  diagramElement.style.maxWidth = 'none';
497
473
 
498
474
  const calculateDefaultZoom = () => {
@@ -518,52 +494,56 @@ export class DiagramChrome {
518
494
  decreaseButton.disabled = currentZoom <= zoomPolicy.min;
519
495
  increaseButton.disabled = currentZoom >= zoomPolicy.max;
520
496
 
521
- const viewport = getFrameViewportSize(frame);
522
- const isPannable = (baseWidth * currentZoom) > viewport.width || (baseHeight * currentZoom) > viewport.height;
523
- frame.classList.toggle('is-pannable', isPannable);
524
- diagramElement.style.margin = isPannable ? '0' : '0 auto';
525
497
  };
526
498
 
527
- // ponytail: size from the current viewport for the first paint; hidden shells refit later.
528
499
  const initialZoom = hasRestorableViewState
529
500
  ? clamp(previousViewState.zoom, zoomPolicy.min, zoomPolicy.max)
530
501
  : hasInitialViewport
531
502
  ? clamp(previousViewportWidth / baseWidth, zoomPolicy.min, zoomPolicy.fitMax)
532
503
  : zoomPolicy.default;
533
- applyZoom(initialZoom);
534
504
 
535
505
  const getViewportCenter = () => ({
536
506
  x: frame.scrollLeft + (frame.clientWidth / 2),
537
507
  y: frame.scrollTop + (frame.clientHeight / 2),
538
508
  });
539
509
 
540
- const restoreViewportCenter = (previousZoom, nextZoom, center) => {
510
+ const restoreViewportPoint = (previousZoom, nextZoom, contentPoint, viewportPoint) => {
541
511
  if (previousZoom === 0) {
542
512
  return;
543
513
  }
544
514
 
545
515
  const scale = nextZoom / previousZoom;
546
- frame.scrollLeft = (center.x * scale) - (frame.clientWidth / 2);
547
- frame.scrollTop = (center.y * scale) - (frame.clientHeight / 2);
516
+ frame.scrollLeft = (contentPoint.x * scale) - viewportPoint.x;
517
+ frame.scrollTop = (contentPoint.y * scale) - viewportPoint.y;
518
+ };
519
+
520
+ const restoreViewportCenter = (previousZoom, nextZoom, center) => {
521
+ restoreViewportPoint(previousZoom, nextZoom, center, {
522
+ x: frame.clientWidth / 2,
523
+ y: frame.clientHeight / 2,
524
+ });
548
525
  };
549
526
 
550
527
  const animateZoomTo = (nextZoom) => {
551
528
  const targetZoom = clamp(nextZoom, zoomPolicy.min, zoomPolicy.max);
529
+ if (zoomAnimationFrameId) {
530
+ zoomAnimationTarget = targetZoom;
531
+ return;
532
+ }
533
+
552
534
  const startZoom = currentZoom;
553
535
  if (targetZoom === startZoom) {
554
536
  return;
555
537
  }
556
538
 
539
+ zoomAnimationTarget = targetZoom;
557
540
  const center = getViewportCenter();
558
541
  const startedAt = performance.now();
559
- if (zoomAnimationFrameId) {
560
- this.window.cancelAnimationFrame(zoomAnimationFrameId);
561
- }
562
542
 
563
543
  const tick = (now) => {
564
544
  const progress = clamp((now - startedAt) / zoomPolicy.animationDurationMs, 0, 1);
565
545
  const easedProgress = easeOutCubic(progress);
566
- const animatedZoom = startZoom + ((targetZoom - startZoom) * easedProgress);
546
+ const animatedZoom = startZoom + ((zoomAnimationTarget - startZoom) * easedProgress);
567
547
  applyZoom(animatedZoom);
568
548
  restoreViewportCenter(startZoom, animatedZoom, center);
569
549
 
@@ -573,33 +553,35 @@ export class DiagramChrome {
573
553
  }
574
554
 
575
555
  zoomAnimationFrameId = null;
576
- applyZoom(targetZoom);
577
- restoreViewportCenter(startZoom, targetZoom, center);
556
+ const completedZoom = zoomAnimationTarget;
557
+ zoomAnimationTarget = null;
558
+ applyZoom(completedZoom);
559
+ restoreViewportCenter(startZoom, completedZoom, center);
578
560
  };
579
561
 
580
562
  zoomAnimationFrameId = this.window.requestAnimationFrame(tick);
581
563
  };
582
564
 
565
+ const cancelZoomAnimation = () => {
566
+ if (zoomAnimationFrameId) {
567
+ this.window.cancelAnimationFrame(zoomAnimationFrameId);
568
+ zoomAnimationFrameId = null;
569
+ }
570
+
571
+ zoomAnimationTarget = null;
572
+ };
573
+
583
574
  const zoomBy = (delta) => {
584
575
  hasManualZoom = true;
585
- animateZoomTo(currentZoom + delta);
576
+ animateZoomTo((zoomAnimationTarget ?? currentZoom) + delta);
586
577
  };
587
578
 
588
- const resetZoomToFit = ({ animate = false } = {}) => {
589
- defaultZoom = calculateDefaultZoom();
579
+ const resetZoomToFit = () => {
580
+ const defaultZoom = calculateDefaultZoom();
590
581
  hasManualZoom = false;
591
582
  lastAutoFitViewportWidth = getFrameViewportSize(frame).width;
592
-
593
- if (animate && Math.abs(defaultZoom - currentZoom) > 0.001) {
594
- animateZoomTo(defaultZoom);
595
- return;
596
- }
597
-
598
- if (zoomAnimationFrameId) {
599
- this.window.cancelAnimationFrame(zoomAnimationFrameId);
600
- zoomAnimationFrameId = null;
601
- }
602
-
583
+ pinchState = null;
584
+ cancelZoomAnimation();
603
585
  applyZoom(defaultZoom);
604
586
  frame.scrollLeft = 0;
605
587
  frame.scrollTop = 0;
@@ -610,40 +592,133 @@ export class DiagramChrome {
610
592
  if (resetZoomFrameId) {
611
593
  this.window.cancelAnimationFrame(resetZoomFrameId);
612
594
  }
613
- if (layoutFrameId) {
614
- this.window.cancelAnimationFrame(layoutFrameId);
615
- }
616
595
 
617
596
  resetZoomFrameId = this.window.requestAnimationFrame(() => {
618
- layoutFrameId = this.window.requestAnimationFrame(() => {
619
- layoutFrameId = this.window.requestAnimationFrame(() => {
620
- layoutFrameId = null;
621
- resetZoomFrameId = null;
622
- if (!shell.isConnected) {
623
- return;
624
- }
625
-
626
- const shouldForce = shouldForceScheduledReset;
627
- shouldForceScheduledReset = false;
628
- const viewportWidth = getFrameViewportSize(frame).width;
629
- const viewportChanged = Math.abs(viewportWidth - lastAutoFitViewportWidth) > 1;
630
- if (!shouldForce && hasManualZoom) {
631
- return;
632
- }
633
- if (!shouldForce && viewportWidth > 0 && lastAutoFitViewportWidth > 0 && !viewportChanged) {
634
- return;
635
- }
636
-
637
- resetZoomToFit();
638
- });
639
- });
597
+ resetZoomFrameId = null;
598
+ if (!shell.isConnected) {
599
+ return;
600
+ }
601
+
602
+ const shouldForce = shouldForceScheduledReset;
603
+ shouldForceScheduledReset = false;
604
+ const viewportWidth = getFrameViewportSize(frame).width;
605
+ const viewportChanged = Math.abs(viewportWidth - lastAutoFitViewportWidth) > 1;
606
+ if (!shouldForce && hasManualZoom) {
607
+ return;
608
+ }
609
+ if (!shouldForce && viewportWidth > 0 && lastAutoFitViewportWidth > 0 && !viewportChanged) {
610
+ return;
611
+ }
612
+
613
+ resetZoomToFit();
640
614
  });
641
615
  };
642
616
 
643
617
  const resizeObserver = this.attachShellResizeObserver(shell, frame, () => scheduleResetZoomToFit());
644
618
 
619
+ const getFrameViewportPoint = (clientX, clientY) => {
620
+ const rect = frame.getBoundingClientRect();
621
+ return {
622
+ x: Number(clientX) - rect.left,
623
+ y: Number(clientY) - rect.top,
624
+ };
625
+ };
626
+
627
+ const getPinchGesture = (touches) => {
628
+ if (!touches || touches.length !== 2) {
629
+ return null;
630
+ }
631
+
632
+ const first = touches[0];
633
+ const second = touches[1];
634
+ const center = getFrameViewportPoint(
635
+ (first.clientX + second.clientX) / 2,
636
+ (first.clientY + second.clientY) / 2,
637
+ );
638
+ const distance = Math.hypot(
639
+ second.clientX - first.clientX,
640
+ second.clientY - first.clientY,
641
+ );
642
+ if (!Number.isFinite(distance) || distance <= 0) {
643
+ return null;
644
+ }
645
+
646
+ return { center, distance };
647
+ };
648
+
649
+ const startPinch = (touches) => {
650
+ const gesture = getPinchGesture(touches);
651
+ if (!gesture) {
652
+ return false;
653
+ }
654
+
655
+ cancelZoomAnimation();
656
+ pinchState = {
657
+ contentPoint: {
658
+ x: frame.scrollLeft + gesture.center.x,
659
+ y: frame.scrollTop + gesture.center.y,
660
+ },
661
+ distance: gesture.distance,
662
+ zoom: currentZoom,
663
+ };
664
+ return true;
665
+ };
666
+
667
+ const updatePinch = (touches) => {
668
+ const gesture = getPinchGesture(touches);
669
+ if (!gesture) {
670
+ return false;
671
+ }
672
+ if (!pinchState && !startPinch(touches)) {
673
+ return false;
674
+ }
675
+
676
+ hasManualZoom = true;
677
+ applyZoom(pinchState.zoom * (gesture.distance / pinchState.distance));
678
+ restoreViewportPoint(
679
+ pinchState.zoom,
680
+ currentZoom,
681
+ pinchState.contentPoint,
682
+ gesture.center,
683
+ );
684
+ return true;
685
+ };
686
+
645
687
  decreaseButton.addEventListener('click', () => zoomBy(-zoomPolicy.step));
646
688
  increaseButton.addEventListener('click', () => zoomBy(zoomPolicy.step));
689
+ frame.addEventListener('wheel', (event) => {
690
+ if (!event.ctrlKey) {
691
+ return;
692
+ }
693
+
694
+ event.preventDefault();
695
+ const deltaY = Number.isFinite(event.deltaY) ? event.deltaY : 0;
696
+ const wheelDelta = clamp(
697
+ -deltaY * zoomPolicy.wheelSensitivity,
698
+ -zoomPolicy.step / 2,
699
+ zoomPolicy.step / 2,
700
+ );
701
+ if (wheelDelta !== 0) {
702
+ zoomBy(wheelDelta);
703
+ }
704
+ }, { passive: false });
705
+ frame.addEventListener('touchstart', (event) => {
706
+ if (event.touches.length === 2) {
707
+ startPinch(event.touches);
708
+ }
709
+ }, { passive: true });
710
+ frame.addEventListener('touchmove', (event) => {
711
+ if (event.touches.length === 2 && updatePinch(event.touches)) {
712
+ event.preventDefault();
713
+ }
714
+ }, { passive: false });
715
+ const finishPinch = (event) => {
716
+ if (event.touches.length !== 2) {
717
+ pinchState = null;
718
+ }
719
+ };
720
+ frame.addEventListener('touchend', finishPinch);
721
+ frame.addEventListener('touchcancel', finishPinch);
647
722
  resetButton.addEventListener('click', () => {
648
723
  scheduleResetZoomToFit({ force: true });
649
724
  });
@@ -660,82 +735,6 @@ export class DiagramChrome {
660
735
  );
661
736
  });
662
737
 
663
- const stopPanning = () => {
664
- if (!isPanning) {
665
- return;
666
- }
667
-
668
- isPanning = false;
669
- frame.classList.remove('is-dragging');
670
- this.window.removeEventListener('pointerup', stopPanning, true);
671
- this.window.removeEventListener('pointercancel', stopPanning, true);
672
- this.window.removeEventListener('mouseup', stopPanning, true);
673
- this.window.removeEventListener('touchend', stopPanning, true);
674
- this.window.removeEventListener('touchcancel', stopPanning, true);
675
- this.window.removeEventListener('blur', stopPanning, true);
676
-
677
- if (activePointerId !== null && typeof frame.releasePointerCapture === 'function') {
678
- try {
679
- frame.releasePointerCapture(activePointerId);
680
- } catch {
681
- // Ignore capture release issues during drag end.
682
- }
683
- }
684
-
685
- activePointerId = null;
686
- };
687
-
688
- frame.addEventListener('pointerdown', (event) => {
689
- if (event.button !== 0 || !frame.classList.contains('is-pannable')) {
690
- return;
691
- }
692
-
693
- if (zoomAnimationFrameId) {
694
- this.window.cancelAnimationFrame(zoomAnimationFrameId);
695
- zoomAnimationFrameId = null;
696
- }
697
-
698
- isPanning = true;
699
- activePointerId = event.pointerId;
700
- panStartX = event.clientX;
701
- panStartY = event.clientY;
702
- panStartScrollLeft = frame.scrollLeft;
703
- panStartScrollTop = frame.scrollTop;
704
-
705
- frame.classList.add('is-dragging');
706
- this.window.addEventListener('pointerup', stopPanning, true);
707
- this.window.addEventListener('pointercancel', stopPanning, true);
708
- this.window.addEventListener('mouseup', stopPanning, true);
709
- this.window.addEventListener('touchend', stopPanning, true);
710
- this.window.addEventListener('touchcancel', stopPanning, true);
711
- this.window.addEventListener('blur', stopPanning, true);
712
- if (typeof frame.setPointerCapture === 'function') {
713
- try {
714
- frame.setPointerCapture(event.pointerId);
715
- } catch {
716
- // Safari can reject pointer capture during rapid layout transitions.
717
- }
718
- }
719
- event.preventDefault();
720
- });
721
-
722
- frame.addEventListener('pointermove', (event) => {
723
- if (!isPanning) {
724
- return;
725
- }
726
-
727
- frame.scrollLeft = panStartScrollLeft - (event.clientX - panStartX);
728
- frame.scrollTop = panStartScrollTop - (event.clientY - panStartY);
729
- });
730
-
731
- frame.addEventListener('pointerup', stopPanning);
732
- frame.addEventListener('pointercancel', stopPanning);
733
- frame.addEventListener('lostpointercapture', stopPanning);
734
- frame.addEventListener('mouseleave', stopPanning);
735
- frame.addEventListener('mouseup', stopPanning);
736
- frame.addEventListener('touchend', stopPanning);
737
- frame.addEventListener('touchcancel', stopPanning);
738
-
739
738
  frame.appendChild(diagramElement);
740
739
  const sourceNode = sourceSelector ? shell.querySelector(sourceSelector) : null;
741
740
  const nextChildren = sourceNode ? [sourceNode, toolbar, frame] : [toolbar, frame];
@@ -789,16 +788,12 @@ export class DiagramChrome {
789
788
 
790
789
  const controller = {
791
790
  destroy: () => {
792
- stopPanning();
793
791
  if (zoomAnimationFrameId) {
794
792
  this.window.cancelAnimationFrame(zoomAnimationFrameId);
795
793
  }
796
794
  if (resetZoomFrameId) {
797
795
  this.window.cancelAnimationFrame(resetZoomFrameId);
798
796
  }
799
- if (layoutFrameId) {
800
- this.window.cancelAnimationFrame(layoutFrameId);
801
- }
802
797
  if (replacementFrameId) {
803
798
  this.window.cancelAnimationFrame(replacementFrameId);
804
799
  }