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
@@ -1,4 +1,7 @@
1
- import { getVaultFileKind, supportsCommentsForFilePath } from '../../../domain/file-kind.js';
1
+ import {
2
+ getVaultFileKind,
3
+ supportsCommentsForFilePath,
4
+ } from '../../../domain/file-kind.js';
2
5
 
3
6
  export const commentsFeature = {
4
7
  createCommentThread(payload) {
@@ -82,26 +85,32 @@ export const commentsFeature = {
82
85
  return;
83
86
  }
84
87
 
88
+ const isDiagram = getVaultFileKind(filePath) === 'excalidraw';
85
89
  const line = Number.isFinite(anchor?.startLine) ? anchor.startLine : null;
86
- this._pendingCommentOverviewFocus = {
87
- attempts: 0,
88
- filePath,
89
- line,
90
- threadId,
91
- };
90
+ if (!isDiagram) {
91
+ this._pendingCommentOverviewFocus = {
92
+ attempts: 0,
93
+ filePath,
94
+ line,
95
+ threadId,
96
+ };
97
+ }
92
98
  this.setSidebarTab('comments');
93
99
  this.setSidebarVisibility(true);
94
100
  this.workspaceRouteController?.preserveSidebarTabForNextFileRoute?.(filePath);
95
- this.navigation.navigateToFile(filePath, {
96
- line,
97
- });
101
+ this.navigation.navigateToFile(filePath, isDiagram ? undefined : { line });
98
102
 
99
103
  if (this.currentFilePath !== filePath || !this.session) {
100
- await this.openFile(filePath);
104
+ await this.workspaceRouteController.openFile(filePath);
101
105
  }
102
106
 
103
107
  this.setSidebarTab('comments');
104
108
  this.setSidebarVisibility(true);
109
+ if (isDiagram) {
110
+ this.excalidrawEmbed?.openCommentThread?.(filePath, threadId);
111
+ return;
112
+ }
113
+
105
114
  this.focusPendingCommentOverviewThread();
106
115
  },
107
116
 
@@ -148,7 +148,7 @@ export const gitFeature = {
148
148
 
149
149
  syncMainChrome({ badgeLabel = '', mode, title = null } = {}) {
150
150
  const isSpecialMode = mode === 'diff' || mode === 'history' || mode === 'history-preview';
151
- this.elements.toolbarCenter?.classList.toggle('hidden', isSpecialMode);
151
+ this.elements.toolbarViewToggle?.classList.toggle('hidden', isSpecialMode);
152
152
  this.elements.mobileViewToggle?.classList.toggle('hidden', isSpecialMode);
153
153
  this.elements.userCount?.classList.toggle('hidden', isSpecialMode);
154
154
  this.elements.toolbarDiffBadge?.classList.toggle('hidden', !badgeLabel);
@@ -164,7 +164,6 @@ export const gitFeature = {
164
164
  },
165
165
 
166
166
  async showGitDiff({ filePath = null, scope = 'all' } = {}) {
167
- await this.ensureGitControllers?.();
168
167
  this.clearStaticPreviewDocument?.();
169
168
  this.gitPanel.setSelection(filePath ? { path: filePath, scope, source: 'workspace' } : {});
170
169
  this.gitPanel.setMode('changes');
@@ -179,7 +178,6 @@ export const gitFeature = {
179
178
  },
180
179
 
181
180
  async showGitCommit({ hash, path = null, historyFilePath = null } = {}) {
182
- await this.ensureGitControllers?.();
183
181
  this.clearStaticPreviewDocument?.();
184
182
  this.gitPanel.setMode('history');
185
183
  this.gitPanel.setSelection(hash ? { commitHash: hash, path, source: 'commit' } : { source: 'commit' });
@@ -194,7 +192,6 @@ export const gitFeature = {
194
192
  },
195
193
 
196
194
  async showGitHistory() {
197
- await this.ensureGitControllers?.();
198
195
  this.clearStaticPreviewDocument?.();
199
196
  this.gitPanel.setMode('history');
200
197
  this.gitPanel.setSelection({ source: 'commit' });
@@ -207,7 +204,6 @@ export const gitFeature = {
207
204
  },
208
205
 
209
206
  async showGitFileHistory({ filePath = null } = {}) {
210
- await this.ensureGitControllers?.();
211
207
  if (!filePath) {
212
208
  this.workspaceRouteController?.showEmptyState?.();
213
209
  return;
@@ -240,7 +236,7 @@ export const gitFeature = {
240
236
  ...data,
241
237
  currentFilePath: resolvedCurrentFilePath,
242
238
  });
243
- this.resetPreviewMode();
239
+ this.workspacePreviewController.resetPreviewMode();
244
240
  this.elements.markdownToolbar?.classList.add('hidden');
245
241
  this.elements.outlineToggle?.classList.toggle('hidden', data.fileKind !== 'markdown');
246
242
 
@@ -256,7 +252,7 @@ export const gitFeature = {
256
252
  }
257
253
 
258
254
  this.elements.outlineToggle?.classList.add('hidden');
259
- this.renderTextFilePreview?.({
255
+ this.workspacePreviewController.renderTextFilePreview({
260
256
  content: data.content,
261
257
  filePath: resolvedCurrentFilePath,
262
258
  });
@@ -266,7 +262,7 @@ export const gitFeature = {
266
262
  console.error('[git-preview] Failed to load file snapshot:', error);
267
263
  this.clearStaticPreviewDocument?.();
268
264
  this.toastController.show('Failed to load historical file preview');
269
- this.renderTextFilePreview?.({
265
+ this.workspacePreviewController.renderTextFilePreview({
270
266
  content: 'Failed to load historical file preview.',
271
267
  filePath: resolvedCurrentFilePath,
272
268
  });
@@ -335,7 +331,6 @@ export const gitFeature = {
335
331
  },
336
332
 
337
333
  async refreshGitAfterAction({ filePath = null, preferredScope = null } = {}) {
338
- await this.ensureGitControllers?.();
339
334
  await this.gitPanel.refresh({ force: true });
340
335
 
341
336
  const route = this.navigation.getHashRoute();
@@ -451,6 +446,18 @@ export const gitFeature = {
451
446
  showToast: true,
452
447
  });
453
448
 
449
+ const structurizrDependencyChanged = [
450
+ ...workspaceChange.changedPaths,
451
+ ...workspaceChange.deletedPaths,
452
+ ...workspaceChange.renamedPaths.flatMap((entry) => [entry.oldPath, entry.newPath]),
453
+ ].some((pathValue) => /(?:\.dsl$|(?:^|\/)workspace\.json$)/iu.test(pathValue));
454
+ if (this.currentFilePath && this.isStructurizrWorkspaceFile?.(this.currentFilePath) && structurizrDependencyChanged) {
455
+ this.structurizrPreview?.queueSync({
456
+ filePath: this.currentFilePath,
457
+ source: this.session?.getText?.() ?? '',
458
+ });
459
+ }
460
+
454
461
  if (
455
462
  event.origin === 'filesystem'
456
463
  && this.currentFilePath
@@ -1,3 +1,5 @@
1
+ import { getUserAvatarTextColor } from '../../domain/room.js';
2
+
1
3
  const MAX_INLINE_AVATARS = 5;
2
4
 
3
5
  function compareUserNames(left, right) {
@@ -11,8 +13,9 @@ function getConnectedPresenceState(context) {
11
13
  const isDrawioRoute = Boolean(context.currentFilePath && context.isDrawioFile?.(context.currentFilePath));
12
14
  const isExcalidrawRoute = Boolean(context.currentFilePath && context.isExcalidrawFile?.(context.currentFilePath));
13
15
  const isImageRoute = Boolean(context.currentFilePath && context.isImageFile?.(context.currentFilePath));
16
+ const isPdfRoute = Boolean(context.currentFilePath && context.isPdfFile?.(context.currentFilePath));
14
17
  const shouldUseLobbyState = !hasEditorSession
15
- && (!context.currentFilePath || isDrawioRoute || isExcalidrawRoute || isImageRoute);
18
+ && (!context.currentFilePath || isDrawioRoute || isExcalidrawRoute || isImageRoute || isPdfRoute);
16
19
  return shouldUseLobbyState
17
20
  ? context.lobby?.getConnectionState?.() ?? context.connectionState
18
21
  : context.connectionState;
@@ -65,7 +68,7 @@ export const presenceFeature = {
65
68
  this.globalUsers = users;
66
69
  this.syncFollowedUser();
67
70
  this.renderAvatars();
68
- this.renderChat();
71
+ this.renderChat({ messagesChanged: false });
69
72
  this.renderPresence();
70
73
  this.syncCurrentUserName();
71
74
  },
@@ -126,6 +129,7 @@ export const presenceFeature = {
126
129
  avatar.className = 'user-avatar';
127
130
  avatar.classList.toggle('is-local', user.isLocal);
128
131
  avatar.style.backgroundColor = user.color;
132
+ avatar.style.color = getUserAvatarTextColor(user.color);
129
133
  avatar.classList.toggle('is-following', user.clientId === this.followedUserClientId);
130
134
 
131
135
  const initial = document.createElement('span');
@@ -181,8 +185,6 @@ export const presenceFeature = {
181
185
  overflow.appendChild(overflowLabel);
182
186
  avatars.appendChild(overflow);
183
187
  }
184
-
185
- this.renderPresencePanel();
186
188
  },
187
189
 
188
190
  openPresencePanel() {
@@ -196,7 +198,6 @@ export const presenceFeature = {
196
198
  this.closeToolbarOverflowMenu?.();
197
199
  this.renderAvatars();
198
200
  this.renderPresence();
199
- this.renderPresencePanel();
200
201
  },
201
202
 
202
203
  closePresencePanel() {
@@ -207,7 +208,6 @@ export const presenceFeature = {
207
208
  this.presencePanelOpen = false;
208
209
  this.renderAvatars();
209
210
  this.renderPresence();
210
- this.renderPresencePanel();
211
211
  },
212
212
 
213
213
  togglePresencePanel() {
@@ -227,6 +227,9 @@ export const presenceFeature = {
227
227
 
228
228
  panel.classList.toggle('hidden', !this.presencePanelOpen);
229
229
  panel.setAttribute('aria-hidden', String(!this.presencePanelOpen));
230
+ if (!this.presencePanelOpen) {
231
+ return;
232
+ }
230
233
 
231
234
  const orderedUsers = getPanelUsers(this.globalUsers, this.followedUserClientId);
232
235
  const effectiveConnectionState = getConnectedPresenceState(this);
@@ -268,6 +271,7 @@ export const presenceFeature = {
268
271
  const avatar = document.createElement('span');
269
272
  avatar.className = 'presence-panel-user-avatar';
270
273
  avatar.style.backgroundColor = user.color;
274
+ avatar.style.color = getUserAvatarTextColor(user.color);
271
275
  avatar.textContent = user.name.charAt(0).toUpperCase();
272
276
  avatar.setAttribute('aria-hidden', 'true');
273
277
 
@@ -346,10 +350,11 @@ export const presenceFeature = {
346
350
 
347
351
  this.followedUserClientId = clientId;
348
352
  this.followedCursorSignature = '';
349
- this.renderAvatars();
350
-
351
- if (closePanel) {
353
+ if (closePanel && this.presencePanelOpen) {
352
354
  this.closePresencePanel();
355
+ } else {
356
+ this.renderAvatars();
357
+ this.renderPresencePanel();
353
358
  }
354
359
 
355
360
  if (user.currentFile && user.currentFile !== this.currentFilePath) {
@@ -382,9 +387,11 @@ export const presenceFeature = {
382
387
  }
383
388
  this.followedUserClientId = null;
384
389
  this.followedCursorSignature = '';
385
- this.renderAvatars();
386
- if (closePanel) {
390
+ if (closePanel && this.presencePanelOpen) {
387
391
  this.closePresencePanel();
392
+ } else {
393
+ this.renderAvatars();
394
+ this.renderPresencePanel();
388
395
  }
389
396
  },
390
397
 
@@ -36,6 +36,7 @@ const PREVIEW_HEADING_LINK_ICON = `
36
36
  * @property {any} themeController
37
37
  * @property {any} previewRenderer
38
38
  * @property {any} tabActivityLock
39
+ * @property {any} notifications
39
40
  * @property {any} toastController
40
41
  * @property {any} versionMonitor
41
42
  */
@@ -53,23 +54,27 @@ function initialize() {
53
54
  this.layoutController.initialize();
54
55
  this.scrollSyncController.initialize();
55
56
  this.fileExplorer.initialize();
57
+ this.gitPanel.initialize();
58
+ this.gitDiffView.initialize();
59
+ this.fileHistoryView.initialize();
56
60
  this.commentsOverview?.initialize?.();
57
61
  this.initializePreviewLayoutObserver();
58
62
  this.syncIdentityManagementUi();
59
63
  this.syncCurrentUserName();
60
64
  this.syncWrapToggle();
65
+ this.syncVimModeToggle();
61
66
  this.syncToolbarOverflowVisibility?.();
62
- this.syncChatNotificationButton();
63
67
  this.syncFileHistoryButton({ mode: 'empty' });
64
68
  this.renderChat();
65
69
  this.elements.chatInput?.setAttribute('maxlength', String(this.lobbyChatMessageMaxLength));
66
70
  this.bindEvents();
71
+ this.initializeSidebarResizer?.();
67
72
  this.restoreSidebarState();
68
73
  this.initializeVersionMonitoring();
69
74
  this.tabActivityLock.initialize();
70
75
  this.tabActivityLock.tryActivate();
71
76
 
72
- window.addEventListener('hashchange', () => this.handleHashChange());
77
+ window.addEventListener('hashchange', () => this.workspaceRouteController.handleHashChange());
73
78
  const handleResize = this.createResizeHandler();
74
79
  window.addEventListener('resize', () => {
75
80
  handleResize();
@@ -79,9 +84,11 @@ function initialize() {
79
84
  this.fileExplorerReadyPromise = this.fileExplorer.refresh().then(() => {
80
85
  this.fileExplorerReady = true;
81
86
  void this.commentsOverview?.refresh?.();
82
- this.scheduleGitControllerPrewarm?.();
87
+ if (this.runtimeConfig?.gitEnabled !== false) {
88
+ void this.gitPanel.refresh({ force: true });
89
+ }
83
90
  if (this.isTabActive) {
84
- return this.handleHashChange();
91
+ return this.workspaceRouteController.handleHashChange();
85
92
  }
86
93
  return undefined;
87
94
  });
@@ -148,15 +155,18 @@ function promptForVersionReload(payload = null) {
148
155
 
149
156
  /** @this {UiShellContext} */
150
157
  function bindToolbarEvents() {
158
+ const openQuickSwitcher = () => {
159
+ void this.toggleQuickSwitcher();
160
+ this.closeToolbarOverflowMenu?.();
161
+ };
162
+
151
163
  this.elements.shareButton?.addEventListener('click', () => {
152
164
  void this.copyCurrentLink();
153
165
  this.closeToolbarOverflowMenu?.();
154
166
  });
155
167
 
156
- this.elements.searchFilesButton?.addEventListener('click', () => {
157
- void this.toggleQuickSwitcher();
158
- this.closeToolbarOverflowMenu?.();
159
- });
168
+ this.elements.toolbarSearchButton?.addEventListener('click', openQuickSwitcher);
169
+ this.elements.searchFilesButton?.addEventListener('click', openQuickSwitcher);
160
170
 
161
171
  this.elements.exportDocxButton?.addEventListener('click', () => {
162
172
  void this.handleExportRequest?.('docx');
@@ -252,15 +262,23 @@ function bindEvents() {
252
262
  this.closeToolbarOverflowMenu?.();
253
263
  });
254
264
 
265
+ this.elements.chatNotificationButton?.addEventListener('click', () => {
266
+ void this.handleChatNotificationToggle();
267
+ });
268
+
269
+ this.elements.chatNotificationMuteButton?.addEventListener('click', () => {
270
+ this.toggleChatNotificationMute();
271
+ });
272
+
273
+ this.elements.chatCloseButton?.addEventListener('click', () => {
274
+ this.closeChatPanel({ restoreFocus: true });
275
+ });
276
+
255
277
  this.elements.chatForm?.addEventListener('submit', (event) => {
256
278
  event.preventDefault();
257
279
  this.handleChatSubmit();
258
280
  });
259
281
 
260
- this.elements.chatNotificationButton?.addEventListener('click', () => {
261
- void this.handleChatNotificationToggle();
262
- });
263
-
264
282
  bindToolbarEvents.call(this);
265
283
  bindDialogEvents.call(this);
266
284
 
@@ -280,6 +298,10 @@ function bindEvents() {
280
298
  this.toggleLineWrapping();
281
299
  });
282
300
 
301
+ this.elements.toggleVimModeButton?.addEventListener('click', () => {
302
+ this.toggleVimMode();
303
+ });
304
+
283
305
  this.elements.userCount?.addEventListener('click', (event) => {
284
306
  event.preventDefault();
285
307
  this.togglePresencePanel?.();
@@ -296,7 +318,8 @@ function bindEvents() {
296
318
  });
297
319
 
298
320
  this.elements.toolbarOverflowMenu?.addEventListener('click', (event) => {
299
- if (event.target instanceof Element && event.target.closest('button')) {
321
+ const button = event.target instanceof Element ? event.target.closest('button') : null;
322
+ if (button && button !== this.elements.toggleVimModeButton) {
300
323
  this.closeToolbarOverflowMenu();
301
324
  }
302
325
  });
@@ -372,7 +395,7 @@ function handleDocumentPointerDown(event) {
372
395
  return;
373
396
  }
374
397
 
375
- this.closeChatPanel();
398
+ this.closeChatPanel({ restoreFocus: false });
376
399
  }
377
400
 
378
401
  /** @this {UiShellContext} */
@@ -388,7 +411,7 @@ function handleDocumentKeydown(event) {
388
411
  }
389
412
 
390
413
  if (event.key === 'Escape' && this.chatIsOpen) {
391
- this.closeChatPanel();
414
+ this.closeChatPanel({ restoreFocus: true });
392
415
  return;
393
416
  }
394
417
 
@@ -626,7 +649,7 @@ function handlePreviewContentClick(event) {
626
649
  const wikiLink = event.target.closest('a.wiki-link[data-wiki-target]');
627
650
  if (wikiLink) {
628
651
  event.preventDefault();
629
- this.handleWikiLinkClick(wikiLink.dataset.wikiTarget);
652
+ this.wikiLinkFileController.handleWikiLinkClick(wikiLink.dataset.wikiTarget);
630
653
  return;
631
654
  }
632
655
 
@@ -714,7 +737,12 @@ function toggleToolbarOverflowMenu() {
714
737
  /** @this {UiShellContext} */
715
738
  function handleThemeChange(theme) {
716
739
  this.previewRenderer.applyTheme(theme);
717
- if (!this.isExcalidrawFile(this.currentFilePath) && !this.isImageFile?.(this.currentFilePath)) {
740
+ if (
741
+ !this.isExcalidrawFile(this.currentFilePath)
742
+ && !this.isImageFile?.(this.currentFilePath)
743
+ && !this.isPdfFile?.(this.currentFilePath)
744
+ && !this.isStructurizrWorkspaceFile?.(this.currentFilePath)
745
+ ) {
718
746
  this.previewRenderer.queueRender();
719
747
  }
720
748
  this.session?.applyTheme(theme);
@@ -731,6 +759,7 @@ function handleConnectionChange(state) {
731
759
  });
732
760
  }
733
761
  this.renderPresence();
762
+ this.renderChat({ messagesChanged: false });
734
763
 
735
764
  if (state.unreachable && !this.connectionHelpShown) {
736
765
  this.connectionHelpShown = true;
@@ -755,6 +784,35 @@ function getStoredLineWrapping() {
755
784
  return this.preferences.getLineWrappingEnabled();
756
785
  }
757
786
 
787
+ /** @this {UiShellContext} */
788
+ function toggleVimMode() {
789
+ const currentState = this.session?.isVimModeEnabled?.() ?? this.getStoredVimMode();
790
+ const nextState = !currentState;
791
+
792
+ this.session?.setVimMode?.(nextState);
793
+ this.preferences.setVimModeEnabled(nextState);
794
+ this.syncVimModeToggle(nextState);
795
+ }
796
+
797
+ /** @this {UiShellContext} */
798
+ function getStoredVimMode() {
799
+ return this.preferences.getVimModeEnabled();
800
+ }
801
+
802
+ /** @this {UiShellContext} */
803
+ function syncVimModeToggle(state) {
804
+ const enabled = state ?? this.session?.isVimModeEnabled?.() ?? this.getStoredVimMode();
805
+ const label = this.elements.vimModeToggleLabel;
806
+ const button = this.elements.toggleVimModeButton;
807
+ const nextLabel = enabled ? 'On' : 'Off';
808
+ if (label) label.textContent = nextLabel;
809
+ if (button) {
810
+ button.setAttribute('aria-label', `${enabled ? 'Disable' : 'Enable'} Vim keybindings`);
811
+ button.setAttribute('title', `${enabled ? 'Disable' : 'Enable'} Vim keybindings`);
812
+ button.setAttribute('aria-pressed', String(enabled));
813
+ }
814
+ }
815
+
758
816
  /** @this {UiShellContext} */
759
817
  function syncWrapToggle(state) {
760
818
  const enabled = state ?? this.session?.isLineWrappingEnabled() ?? this.getStoredLineWrapping();
@@ -818,6 +876,7 @@ export const uiFeatureShellMethods = {
818
876
  copyPreviewHeadingLink,
819
877
  createPreviewHeadingLinkUrl,
820
878
  getStoredLineWrapping,
879
+ getStoredVimMode,
821
880
  handleConnectionChange,
822
881
  handleDocumentKeydown,
823
882
  handleDocumentPointerDown,
@@ -838,7 +897,9 @@ export const uiFeatureShellMethods = {
838
897
  syncPreviewHeadingLinkButtons,
839
898
  syncVisualViewportBounds,
840
899
  syncToolbarOverflowVisibility,
900
+ syncVimModeToggle,
841
901
  syncWrapToggle,
842
902
  toggleToolbarOverflowMenu,
843
903
  toggleLineWrapping,
904
+ toggleVimMode,
844
905
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @typedef {object} UiSidebarContext
3
3
  * @property {string} activeSidebarTab
4
- * @property {{ sidebar?: HTMLElement | null, sidebarBackdrop?: HTMLElement | null, filesSidebarTab?: HTMLElement | null, commentsSidebarTab?: HTMLElement | null, gitSidebarTab?: HTMLElement | null, fileSearch?: HTMLElement | null, gitSearch?: HTMLElement | null, commentOverviewPanel?: HTMLElement | null }} elements
4
+ * @property {{ sidebar?: HTMLElement | null, sidebarBackdrop?: HTMLElement | null, sidebarResizer?: HTMLElement | null, filesSidebarTab?: HTMLElement | null, commentsSidebarTab?: HTMLElement | null, gitSidebarTab?: HTMLElement | null, fileSearch?: HTMLElement | null, gitSearch?: HTMLElement | null, commentOverviewPanel?: HTMLElement | null }} elements
5
5
  * @property {{ setSidebarVisible(showSidebar: boolean): void, getSidebarVisible(): string | null | undefined }} preferences
6
6
  * @property {{ setActive(active: boolean): void }} gitPanel
7
7
  * @property {boolean} gitRepoAvailable
@@ -11,6 +11,78 @@
11
11
  * @property {(showSidebar: boolean) => void} setSidebarVisibility
12
12
  */
13
13
 
14
+ const SIDEBAR_DEFAULT_WIDTH = 260;
15
+ const SIDEBAR_MIN_WIDTH = 220;
16
+ const SIDEBAR_MAX_WIDTH = 420;
17
+ const SIDEBAR_KEYBOARD_STEP = 16;
18
+
19
+ /** @this {UiSidebarContext} */
20
+ function initializeSidebarResizer() {
21
+ const sidebar = this.elements.sidebar;
22
+ const resizer = this.elements.sidebarResizer;
23
+ if (!sidebar || !resizer) return;
24
+
25
+ let sidebarWidth = SIDEBAR_DEFAULT_WIDTH;
26
+ let pointerId = null;
27
+ let startX = 0;
28
+ let startWidth = SIDEBAR_DEFAULT_WIDTH;
29
+
30
+ const clampWidth = (width) => Math.round(Math.max(SIDEBAR_MIN_WIDTH, Math.min(SIDEBAR_MAX_WIDTH, width)));
31
+ const applyWidth = (width) => {
32
+ sidebarWidth = clampWidth(width);
33
+ sidebar.style.setProperty('--sidebar-width', `${sidebarWidth}px`);
34
+ resizer.setAttribute('aria-valuenow', String(sidebarWidth));
35
+ };
36
+ const finishResize = (event) => {
37
+ if (event?.pointerId != null && event.pointerId !== pointerId) return;
38
+ if (pointerId === null) return;
39
+
40
+ pointerId = null;
41
+ sidebar.classList.remove('is-resizing');
42
+ document.body.style.cursor = '';
43
+ document.body.style.userSelect = '';
44
+ };
45
+
46
+ applyWidth(SIDEBAR_DEFAULT_WIDTH);
47
+
48
+ resizer.addEventListener('pointerdown', (event) => {
49
+ if (this.isMobileViewport() || sidebar.classList.contains('collapsed')) return;
50
+
51
+ pointerId = event.pointerId;
52
+ startX = event.clientX;
53
+ startWidth = sidebar.getBoundingClientRect().width || sidebarWidth;
54
+ sidebar.classList.add('is-resizing');
55
+ document.body.style.cursor = 'col-resize';
56
+ document.body.style.userSelect = 'none';
57
+ resizer.setPointerCapture?.(event.pointerId);
58
+ event.preventDefault();
59
+ });
60
+
61
+ resizer.addEventListener('pointermove', (event) => {
62
+ if (event.pointerId !== pointerId) return;
63
+ applyWidth(startWidth + event.clientX - startX);
64
+ });
65
+
66
+ resizer.addEventListener('pointerup', finishResize);
67
+ resizer.addEventListener('pointercancel', finishResize);
68
+ resizer.addEventListener('lostpointercapture', finishResize);
69
+
70
+ resizer.addEventListener('keydown', (event) => {
71
+ if (this.isMobileViewport() || sidebar.classList.contains('collapsed')) return;
72
+
73
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
74
+ event.preventDefault();
75
+ applyWidth(sidebarWidth + (event.key === 'ArrowRight' ? SIDEBAR_KEYBOARD_STEP : -SIDEBAR_KEYBOARD_STEP));
76
+ return;
77
+ }
78
+
79
+ if (event.key === 'Home' || event.key === 'End') {
80
+ event.preventDefault();
81
+ applyWidth(event.key === 'Home' ? SIDEBAR_MIN_WIDTH : SIDEBAR_MAX_WIDTH);
82
+ }
83
+ });
84
+ }
85
+
14
86
  /** @this {UiSidebarContext} */
15
87
  function isMobileViewport() {
16
88
  return this.mobileBreakpointQuery.matches;
@@ -108,6 +180,7 @@ function setSidebarTab(tab) {
108
180
  export const uiFeatureSidebarMethods = {
109
181
  applySidebarVisibility,
110
182
  closeSidebarOnMobile,
183
+ initializeSidebarResizer,
111
184
  isMobileViewport,
112
185
  restoreSidebarState,
113
186
  setSidebarTab,
@@ -16,7 +16,7 @@
16
16
  * @property {{ show(message: string): void }} toastController
17
17
  * @property {{ tryActivate(options?: { takeover?: boolean }): void }} tabActivityLock
18
18
  * @property {{ prepareFileDisconnect(filePath: string): Promise<void> }} excalidrawEmbed
19
- * @property {() => void} handleHashChange
19
+ * @property {{ handleHashChange(): Promise<void> }} workspaceRouteController
20
20
  * @property {() => boolean} isExcalidrawFile
21
21
  * @property {() => void} promptForDisplayNameIfNeeded
22
22
  * @property {() => void} renderChat
@@ -45,7 +45,7 @@ function handleTabActivated({ takeover = false } = {}) {
45
45
 
46
46
  if (wasInactive) {
47
47
  if (this.fileExplorerReady) {
48
- void this.handleHashChange();
48
+ void this.workspaceRouteController.handleHashChange();
49
49
  }
50
50
  this.promptForDisplayNameIfNeeded();
51
51
  }
@@ -1,4 +1,5 @@
1
1
  import { isMarkdownFilePath } from '../../../domain/file-kind.js';
2
+ import { pickFiles } from '../../browser-utils.js';
2
3
  import {
3
4
  getMarkdownBlockActionLabel,
4
5
  getMarkdownBlockMenuActions,
@@ -91,7 +92,6 @@ function renderMarkdownToolbarMarkup(activeAction) {
91
92
  * @property {{ show(message: string): void }} toastController
92
93
  * @property {{ uploadImageAttachment(payload: { file: File, fileName: string, sourcePath: string }): Promise<{ markdown?: string, path?: string }>} } vaultApiClient
93
94
  * @property {{ applyMarkdownToolbarAction(action: string): boolean, insertText(text: string): void, runEditorCommand?(commandId: string): boolean } | null} session
94
- * @property {() => Promise<File | null>} pickImageFile
95
95
  * @property {(file: File) => Promise<boolean>} handleEditorImageInsert
96
96
  * @property {() => Promise<void>} handleToolbarImageInsert
97
97
  */
@@ -395,7 +395,7 @@ function runEditorCommand(commandId) {
395
395
 
396
396
  /** @this {UiToolbarContext} */
397
397
  async function handleToolbarImageInsert() {
398
- const file = await this.pickImageFile();
398
+ const [file] = await pickFiles({ accept: IMAGE_FILE_PICKER_ACCEPT });
399
399
  if (!file) {
400
400
  return;
401
401
  }
@@ -403,55 +403,6 @@ async function handleToolbarImageInsert() {
403
403
  await this.handleEditorImageInsert(file);
404
404
  }
405
405
 
406
- /** @this {UiToolbarContext} */
407
- function pickImageFile() {
408
- return new Promise((resolve) => {
409
- const input = document.createElement('input');
410
- input.type = 'file';
411
- input.accept = IMAGE_FILE_PICKER_ACCEPT;
412
- input.style.position = 'fixed';
413
- input.style.left = '-9999px';
414
- document.body.appendChild(input);
415
- let settled = false;
416
- let focusTimer = null;
417
-
418
- const cleanup = (value) => {
419
- if (settled) {
420
- return;
421
- }
422
-
423
- settled = true;
424
- if (focusTimer) {
425
- window.clearTimeout(focusTimer);
426
- }
427
- window.removeEventListener('focus', handleWindowFocus);
428
- input.remove();
429
- resolve(value);
430
- };
431
-
432
- const handleWindowFocus = () => {
433
- focusTimer = window.setTimeout(() => {
434
- if (settled || input.files?.length) {
435
- return;
436
- }
437
-
438
- cleanup(null);
439
- }, 250);
440
- };
441
-
442
- input.addEventListener('change', () => {
443
- cleanup(input.files?.[0] ?? null);
444
- }, { once: true });
445
-
446
- input.addEventListener('cancel', () => {
447
- cleanup(null);
448
- }, { once: true });
449
-
450
- window.addEventListener('focus', handleWindowFocus, { once: true });
451
- input.click();
452
- });
453
- }
454
-
455
406
  /** @this {UiToolbarContext} */
456
407
  async function handleEditorImageInsert(file) {
457
408
  if (!this.session || !isMarkdownFilePath(this.currentFilePath)) {
@@ -526,7 +477,6 @@ export const uiFeatureToolbarMethods = {
526
477
  openMarkdownBlockMenu,
527
478
  positionMarkdownBlockMenu,
528
479
  renderMarkdownBlockMenuPopover,
529
- pickImageFile,
530
480
  renderMarkdownToolbar,
531
481
  runEditorCommand,
532
482
  setActiveMarkdownBlockAction,