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
@@ -2,6 +2,7 @@ import {
2
2
  isBaseFilePath,
3
3
  isDiagramFilePath,
4
4
  isMarkdownFilePath,
5
+ isStructurizrFilePath,
5
6
  } from '../../domain/file-kind.js';
6
7
  import { resolveApiUrl } from '../domain/runtime-paths.js';
7
8
 
@@ -18,13 +19,16 @@ export class WorkspacePreviewController {
18
19
  isExcalidrawFile,
19
20
  isBaseFile,
20
21
  isImageFile,
22
+ isPdfFile,
21
23
  isMermaidFile,
22
24
  isPlantUmlFile,
23
25
  layoutController,
24
26
  outlineController,
25
27
  previewRenderer,
28
+ pdfPreview = null,
26
29
  schedulePreviewLayoutSync,
27
30
  scrollSyncController,
31
+ structurizrPreview = null,
28
32
  videoEmbed,
29
33
  }) {
30
34
  this.backlinksPanel = backlinksPanel;
@@ -46,13 +50,20 @@ export class WorkspacePreviewController {
46
50
  this.isExcalidrawFile = isExcalidrawFile ?? (() => false);
47
51
  this.isBaseFile = isBaseFile ?? isBaseFilePath;
48
52
  this.isImageFile = isImageFile ?? (() => false);
53
+ this.isPdfFile = isPdfFile ?? (() => false);
49
54
  this.isMermaidFile = isMermaidFile ?? (() => false);
50
55
  this.isPlantUmlFile = isPlantUmlFile ?? (() => false);
51
56
  this.layoutController = layoutController;
52
57
  this.outlineController = outlineController;
53
58
  this.previewRenderer = previewRenderer;
59
+ this.pdfPreview = pdfPreview ?? { cancel() {}, render() {} };
54
60
  this.schedulePreviewLayoutSyncCallback = schedulePreviewLayoutSync;
55
61
  this.scrollSyncController = scrollSyncController;
62
+ this.structurizrPreview = structurizrPreview ?? {
63
+ queueSync() {},
64
+ render: async () => false,
65
+ reset() {},
66
+ };
56
67
  this.videoEmbed = videoEmbed;
57
68
  }
58
69
 
@@ -81,12 +92,17 @@ export class WorkspacePreviewController {
81
92
  }
82
93
 
83
94
  resetPreviewMode() {
95
+ this.pdfPreview.cancel();
96
+ this.elements.previewContainer?.classList?.remove('is-pdf-file-preview');
84
97
  this.elements.previewContent?.classList.remove('is-drawio-file-preview');
85
98
  this.elements.previewContent?.classList.remove('is-excalidraw-file-preview');
86
99
  this.elements.previewContent?.classList.remove('is-base-file-preview');
87
100
  this.elements.previewContent?.classList.remove('is-image-file-preview');
101
+ this.elements.previewContent?.classList.remove('is-pdf-file-preview');
88
102
  this.elements.previewContent?.classList.remove('is-mermaid-file-preview');
89
103
  this.elements.previewContent?.classList.remove('is-plantuml-file-preview');
104
+ this.elements.previewContent?.classList.remove('is-structurizr-file-preview');
105
+ this.structurizrPreview.reset();
90
106
  }
91
107
 
92
108
  syncFileChrome(filePath, { drawioMode = null, preferPreviewForBase = false } = {}) {
@@ -94,9 +110,11 @@ export class WorkspacePreviewController {
94
110
  const isExcalidraw = this.isExcalidrawFile(filePath);
95
111
  const isBase = this.isBaseFile(filePath);
96
112
  const isImage = this.isImageFile(filePath);
113
+ const isPdf = this.isPdfFile(filePath);
97
114
  const isMarkdown = isMarkdownFilePath(filePath);
98
115
  const isMermaid = this.isMermaidFile(filePath);
99
116
  const isPlantUml = this.isPlantUmlFile(filePath);
117
+ const isStructurizr = isStructurizrFilePath(filePath);
100
118
  const isDiagramFile = isDiagramFilePath(filePath);
101
119
  const usesHeaderBacklinks = isExcalidraw || (isDrawio && drawioMode !== 'text');
102
120
 
@@ -105,11 +123,17 @@ export class WorkspacePreviewController {
105
123
  this.elements.editorFindButton?.classList.toggle('hidden', !isMarkdown);
106
124
  this.elements.markdownToolbar?.classList.toggle('hidden', !isMarkdown);
107
125
  this.elements.exportMenuGroup?.classList.toggle('hidden', !isMarkdown);
108
- this.elements.outlineToggle?.classList.toggle('hidden', isDiagramFile || isImage || isBase);
126
+ this.elements.outlineToggle?.classList.toggle('hidden', isDiagramFile || isImage || isPdf || isBase);
109
127
  this.elements.previewContent?.classList.toggle('is-mermaid-file-preview', isMermaid);
110
128
  this.elements.previewContent?.classList.toggle('is-plantuml-file-preview', isPlantUml);
129
+ this.elements.previewContent?.classList.toggle('is-structurizr-file-preview', isStructurizr);
130
+
131
+ if (isStructurizr) {
132
+ this.outlineController.close();
133
+ this.backlinksPanel.clear();
134
+ }
111
135
 
112
- if ((isDrawio && drawioMode !== 'text') || isExcalidraw || isImage || (isBase && preferPreviewForBase)) {
136
+ if ((isDrawio && drawioMode !== 'text') || isExcalidraw || isImage || isPdf || (isBase && preferPreviewForBase)) {
113
137
  this.layoutController.setView('preview', { persist: false });
114
138
  this.outlineController.close();
115
139
  this.backlinksPanel.clear();
@@ -238,6 +262,39 @@ export class WorkspacePreviewController {
238
262
  this.schedulePreviewLayoutSyncCallback({ delayMs: 0 });
239
263
  }
240
264
 
265
+ renderPdfFilePreview(filePath) {
266
+ const previewElement = this.elements.previewContent;
267
+ if (!previewElement) {
268
+ return;
269
+ }
270
+
271
+ this.videoEmbed?.detachForCommit();
272
+ this.drawioEmbed.detachForCommit();
273
+ this.excalidrawEmbed.detachForCommit();
274
+ this.resetPreviewMode();
275
+ this.elements.previewContainer?.classList?.add('is-pdf-file-preview');
276
+ previewElement.classList.add('is-pdf-file-preview');
277
+ const renderHost = this.previewRenderer.ensureRenderHost();
278
+ this.previewRenderer.normalizePreviewChildren(renderHost);
279
+
280
+ if (renderHost) {
281
+ this.pdfPreview.render({
282
+ displayName: this.getDisplayName(filePath),
283
+ filePath,
284
+ renderHost,
285
+ });
286
+ renderHost.style.minHeight = '';
287
+ }
288
+
289
+ previewElement.dataset.renderPhase = 'ready';
290
+ this.outlineController.close();
291
+ this.backlinksPanel.clear();
292
+ this.scrollSyncController.setLargeDocumentMode(false);
293
+ this.scrollSyncController.invalidatePreviewBlocks();
294
+ this.videoEmbed?.reconcileEmbeds(previewElement);
295
+ this.schedulePreviewLayoutSyncCallback({ delayMs: 0 });
296
+ }
297
+
241
298
  async renderBaseFilePreview(filePath, { source = null } = {}) {
242
299
  const previewElement = this.elements.previewContent;
243
300
  if (!previewElement) {
@@ -253,7 +310,6 @@ export class WorkspacePreviewController {
253
310
  this.previewRenderer.normalizePreviewChildren(renderHost);
254
311
 
255
312
  if (renderHost) {
256
- renderHost.replaceChildren();
257
313
  renderHost.style.minHeight = '';
258
314
  }
259
315
 
@@ -273,6 +329,38 @@ export class WorkspacePreviewController {
273
329
  this.schedulePreviewLayoutSyncCallback({ delayMs: 0 });
274
330
  }
275
331
 
332
+ async renderStructurizrFilePreview(filePath, { source = null } = {}) {
333
+ const previewElement = this.elements.previewContent;
334
+ if (!previewElement) {
335
+ return;
336
+ }
337
+
338
+ this.videoEmbed?.detachForCommit();
339
+ this.drawioEmbed.detachForCommit();
340
+ this.excalidrawEmbed.detachForCommit();
341
+ this.resetPreviewMode();
342
+ previewElement.classList.add('is-structurizr-file-preview');
343
+ const renderHost = this.previewRenderer.ensureRenderHost();
344
+ this.previewRenderer.normalizePreviewChildren(renderHost);
345
+ if (renderHost) {
346
+ renderHost.style.minHeight = '';
347
+ }
348
+
349
+ await this.structurizrPreview.render({
350
+ filePath,
351
+ renderHost,
352
+ source: typeof source === 'string' ? source : (this.getSession()?.getText?.() ?? ''),
353
+ });
354
+
355
+ previewElement.dataset.renderPhase = 'ready';
356
+ this.outlineController.close();
357
+ this.backlinksPanel.clear();
358
+ this.scrollSyncController.setLargeDocumentMode(false);
359
+ this.scrollSyncController.invalidatePreviewBlocks();
360
+ this.videoEmbed?.reconcileEmbeds(previewElement);
361
+ this.schedulePreviewLayoutSyncCallback({ delayMs: 0 });
362
+ }
363
+
276
364
  renderTextFilePreview({ content = '' } = {}) {
277
365
  const previewElement = this.elements.previewContent;
278
366
  if (!previewElement) {
@@ -364,8 +452,6 @@ export class WorkspacePreviewController {
364
452
  return;
365
453
  }
366
454
 
367
- this.previewRenderer.scheduleActiveMermaidRefit();
368
- this.previewRenderer.scheduleActivePlantUmlRefit();
369
455
  this.videoEmbed?.syncLayout();
370
456
  this.drawioEmbed.syncLayout();
371
457
  this.excalidrawEmbed.syncLayout();
@@ -1,8 +1,10 @@
1
+ import { PdfPreviewController } from '../application/pdf-preview-controller.js';
1
2
  import { PreviewRenderer } from '../application/preview-renderer.js';
2
3
  import { ensureQuickSwitcherInstance, toggleQuickSwitcherInstance } from '../application/quick-switcher-loader.js';
3
4
  import { WorkspaceRouteController } from '../application/workspace-route-controller.js';
4
5
  import { WikiLinkFileController } from '../application/wiki-link-file-controller.js';
5
6
  import { WorkspacePreviewController } from '../application/workspace-preview-controller.js';
7
+ import { StructurizrPreviewController } from '../application/structurizr-preview-controller.js';
6
8
  import { WorkspaceCoordinator } from '../application/workspace-coordinator.js';
7
9
  import { bindAppShellElements } from '../application/app-shell-elements.js';
8
10
  import { chatFeature } from '../application/app-shell/chat-feature.js';
@@ -17,10 +19,9 @@ import { uiFeatureTabActivityMethods } from '../application/app-shell/ui-feature
17
19
  import { uiFeatureToolbarMethods } from '../application/app-shell/ui-feature-toolbar.js';
18
20
  import { workspaceFeature } from '../application/app-shell/workspace-feature.js';
19
21
  import { LOBBY_CHAT_MESSAGE_MAX_LENGTH, LobbyPresence } from '../infrastructure/lobby-presence.js';
20
- import { BrowserNotificationPort } from '../infrastructure/browser-notification-port.js';
21
22
  import { BrowserPreferencesPort } from '../infrastructure/browser-preferences-port.js';
23
+ import { BrowserNotificationPort } from '../infrastructure/browser-notification-port.js';
22
24
  import { AppVersionMonitor } from '../infrastructure/app-version-monitor.js';
23
- import { backlinksApiClient } from '../infrastructure/backlinks-api-client.js';
24
25
  import { gitApiClient } from '../infrastructure/git-api-client.js';
25
26
  import {
26
27
  getHashRoute,
@@ -32,7 +33,6 @@ import {
32
33
  navigateToGitFilePreview,
33
34
  navigateToGitHistory,
34
35
  } from '../infrastructure/runtime-config.js';
35
- import { plantUmlApiClient } from '../infrastructure/plantuml-api-client.js';
36
36
  import { TabActivityLock } from '../infrastructure/tab-activity-lock.js';
37
37
  import { vaultApiClient } from '../infrastructure/vault-api-client.js';
38
38
  import { WorkspaceSyncClient } from '../infrastructure/workspace-sync-client.js';
@@ -40,6 +40,12 @@ import { BacklinksPanel } from '../presentation/backlinks-panel.js';
40
40
  import { CommentOverviewController } from '../presentation/comment-overview-controller.js';
41
41
  import { CommentUiController } from '../presentation/comment-ui-controller.js';
42
42
  import { FileExplorerController } from '../presentation/file-explorer-controller.js';
43
+ import { FileHistoryViewController } from '../presentation/file-history-view-controller.js';
44
+ import { BasesPreviewController } from '../presentation/bases-preview-controller.js';
45
+ import { DrawioEmbedController } from '../presentation/drawio-embed-controller.js';
46
+ import { ExcalidrawEmbedController } from '../presentation/excalidraw-embed-controller.js';
47
+ import { GitDiffViewController } from '../presentation/git-diff-view-controller.js';
48
+ import { GitPanelController } from '../presentation/git-panel-controller.js';
43
49
  import { LayoutController } from '../presentation/layout-controller.js';
44
50
  import { OutlineController } from '../presentation/outline-controller.js';
45
51
  import { ScrollSyncController } from '../presentation/scroll-sync-controller.js';
@@ -47,14 +53,12 @@ import { ThemeController } from '../presentation/theme-controller.js';
47
53
  import { ToastController } from '../presentation/toast-controller.js';
48
54
  import { VideoEmbedController } from '../presentation/video-embed-controller.js';
49
55
  import { ImageLightboxController } from '../presentation/image-lightbox-controller.js';
50
- import { lazyControllerFeature } from './lazy-controller-feature.js';
51
56
 
52
57
  const APP_SHELL_FEATURES = [
53
58
  chatFeature,
54
59
  commentsFeature,
55
60
  exportFeature,
56
61
  gitFeature,
57
- lazyControllerFeature,
58
62
  presenceFeature,
59
63
  {
60
64
  ...uiFeatureShellMethods,
@@ -103,18 +107,19 @@ export class CollabMdAppShell {
103
107
  navigateToGitHistory,
104
108
  };
105
109
  this.preferences = new BrowserPreferencesPort({
106
- chatNotificationsKey: 'collabmd-chat-notifications-enabled',
110
+ fileTreeShowExtensionsKey: 'collabmd-file-tree-show-extensions',
107
111
  lineWrappingKey: 'collabmd-editor-line-wrap',
112
+ recentFilesKey: 'collabmd-recent-files',
113
+ vimModeKey: 'collabmd-editor-vim-mode',
108
114
  sidebarVisibleKey: 'collabmd-sidebar-visible',
109
115
  userNameKey: 'collabmd-user-name',
110
116
  });
111
117
  this.notifications = new BrowserNotificationPort();
112
- this.backlinksApiClient = backlinksApiClient;
113
118
  this.gitApiClient = gitApiClient;
114
- this.plantUmlApiClient = plantUmlApiClient;
115
119
  this.vaultApiClient = vaultApiClient;
116
120
  this._session = null;
117
121
  this._hasPromptedForDisplayName = false;
122
+ this._basePreviewRenderTimer = null;
118
123
  this._backlinkRefreshTimer = null;
119
124
  this._pendingPreviewLayoutSync = false;
120
125
  this._previewHydrationPaused = false;
@@ -124,13 +129,10 @@ export class CollabMdAppShell {
124
129
  this._staticPreviewDocument = null;
125
130
  this.pendingGitResetPath = null;
126
131
  this.chatTimeFormatter = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' });
127
- this.chatNotificationsEnabled = this.preferences.getChatNotificationsEnabled();
128
- this.chatNotificationPermission = this.notifications.getPermission();
129
132
  this.lobbyChatMessageMaxLength = LOBBY_CHAT_MESSAGE_MAX_LENGTH;
130
133
  this.quickSwitcher = null;
131
134
  this.quickSwitcherModulePromise = null;
132
135
  this.fileExplorerReadyPromise = Promise.resolve();
133
- this._gitControllerPrewarmHandle = null;
134
136
  this.mobileBreakpointQuery = window.matchMedia('(max-width: 768px)');
135
137
  this.pendingWorkspaceRequestIds = new Set();
136
138
  this._fileOpenPerf = null;
@@ -147,13 +149,9 @@ export class CollabMdAppShell {
147
149
  });
148
150
  this.workspaceSync = new WorkspaceSyncClient({
149
151
  onTreeChange: (tree, metadata = {}) => {
150
- const wasReady = this.fileExplorerReady;
151
152
  this.fileExplorer.setTree(tree, metadata);
152
153
  this.handleCommentOverviewWorkspaceTreeChange?.();
153
154
  this.fileExplorerReady = true;
154
- if (!wasReady && this.isTabActive) {
155
- void this.handleHashChange();
156
- }
157
155
  },
158
156
  onWorkspaceEvent: (event) => {
159
157
  void this.handleIncomingWorkspaceEvent(event);
@@ -165,8 +163,14 @@ export class CollabMdAppShell {
165
163
  this.fileExplorer = new FileExplorerController({
166
164
  mobileBreakpointQuery: this.mobileBreakpointQuery,
167
165
  onFileDelete: () => this.navigation.navigateToFile(null),
168
- onFileSelect: (filePath) => this.handleFileSelection(filePath, { closeSidebarOnMobile: true }),
166
+ onFileSelect: (filePath) => this.workspaceRouteController.handleFileSelection(filePath, {
167
+ closeSidebarOnMobile: true,
168
+ }),
169
+ onShowFileExtensionsChange: (showFileExtensions) => {
170
+ this.preferences.setFileTreeShowExtensions(showFileExtensions);
171
+ },
169
172
  pendingWorkspaceRequestIds: this.pendingWorkspaceRequestIds,
173
+ showFileExtensions: this.preferences.getFileTreeShowExtensions(),
170
174
  toastController: this.toastController,
171
175
  vaultClient: this.vaultApiClient,
172
176
  });
@@ -179,7 +183,23 @@ export class CollabMdAppShell {
179
183
  toastController: this.toastController,
180
184
  vaultApiClient: this.vaultApiClient,
181
185
  });
182
- this.gitPanel = this.createLazyGitPanelController();
186
+ this.gitPanel = new GitPanelController({
187
+ enabled: this.runtimeConfig.gitEnabled !== false,
188
+ gitApiClient: this.gitApiClient,
189
+ onCommitStaged: () => this.openGitCommitDialog(),
190
+ onOpenPullBackup: (filePath) => filePath && this.navigation.navigateToFile(filePath),
191
+ onPullBranch: () => this.pullGitBranch(),
192
+ onPushBranch: () => this.pushGitBranch(),
193
+ onRepoChange: (isGitRepo, status) => this.handleGitRepoChange(isGitRepo, status),
194
+ onResetFile: (filePath, { scope }) => this.openGitResetDialog(filePath, { scope }),
195
+ onSelectCommit: (hash, { path }) => this.handleGitCommitSelection(hash, { closeSidebarOnMobile: true, path }),
196
+ onSelectDiff: (filePath, { scope }) => this.handleGitDiffSelection(filePath, { closeSidebarOnMobile: true, scope }),
197
+ onStageFile: (filePath, { scope }) => this.stageGitFile(filePath, { scope }),
198
+ onUnstageFile: (filePath, { scope }) => this.unstageGitFile(filePath, { scope }),
199
+ onViewAllDiff: () => this.handleGitDiffSelection(null, { closeSidebarOnMobile: true, scope: 'all' }),
200
+ searchInput: this.elements.gitSearchInput,
201
+ toastController: this.toastController,
202
+ });
183
203
  this.outlineController = new OutlineController({
184
204
  mobileBreakpointQuery: this.mobileBreakpointQuery,
185
205
  onNavigateToHeading: ({ sourceLine }) => {
@@ -192,7 +212,19 @@ export class CollabMdAppShell {
192
212
  this.videoEmbed = new VideoEmbedController({
193
213
  previewElement: this.elements.previewContent,
194
214
  });
195
- this.basesPreview = this.createLazyBasesPreviewController();
215
+ this.basesPreview = new BasesPreviewController({
216
+ getActiveFilePath: () => this.currentFilePath,
217
+ getSession: () => this.session,
218
+ onOpenFile: (filePath) => filePath && this.navigation.navigateToFile(filePath),
219
+ previewElement: this.elements.previewContent,
220
+ replaceBaseSource: ({ path, source }) => {
221
+ if (path && path === this.currentFilePath) {
222
+ this.session?.replaceText?.(source);
223
+ }
224
+ },
225
+ toastController: this.toastController,
226
+ vaultApiClient: this.vaultApiClient,
227
+ });
196
228
  this.imageLightbox = new ImageLightboxController({
197
229
  previewElement: this.elements.previewContent,
198
230
  });
@@ -211,12 +243,9 @@ export class CollabMdAppShell {
211
243
  renderVersion: stats?.renderVersion ?? 0,
212
244
  });
213
245
  this.videoEmbed.reconcileEmbeds(this.elements.previewContent);
214
- this.videoEmbed.syncLayout();
215
246
  this.basesPreview.reconcileEmbeds(this.elements.previewContent);
216
247
  this.drawioEmbed.reconcileEmbeds(this.elements.previewContent);
217
- this.drawioEmbed.syncLayout();
218
248
  this.excalidrawEmbed.reconcileEmbeds(this.elements.previewContent, { isLargeDocument: stats.isLargeDocument });
219
- this.excalidrawEmbed.syncLayout();
220
249
  this.scrollSyncController.setLargeDocumentMode(stats.isLargeDocument);
221
250
  this.syncPreviewHeadingLinkButtons();
222
251
  this.applyPendingPreviewRouteAnchor({ behavior: 'auto', clearMissing: true });
@@ -235,15 +264,12 @@ export class CollabMdAppShell {
235
264
  this.refreshCommentUiLayout();
236
265
  },
237
266
  onRenderComplete: () => {
238
- this.videoEmbed.syncLayout();
239
- this.drawioEmbed.syncLayout();
240
- this.excalidrawEmbed.syncLayout();
241
267
  this.applyPendingPreviewRouteAnchor({ allowExpired: true, behavior: 'auto', clearMissing: true });
242
268
  this.schedulePreviewLayoutSync({ delayMs: 0 });
243
269
  this.refreshCommentUiLayout();
244
270
  },
245
271
  outlineController: this.outlineController,
246
- plantUmlRenderClient: this.plantUmlApiClient,
272
+ plantUmlRenderClient: this.vaultApiClient,
247
273
  previewContainer: this.elements.previewContainer,
248
274
  previewElement: this.elements.previewContent,
249
275
  toastController: this.toastController,
@@ -261,15 +287,43 @@ export class CollabMdAppShell {
261
287
  previewElement: this.elements.previewContent,
262
288
  scrollEditorToLine: (lineNumber, viewportRatio) => this.session?.scrollToLine(lineNumber, viewportRatio),
263
289
  });
290
+ this.pdfPreview = new PdfPreviewController({
291
+ outlineController: this.outlineController,
292
+ previewContainer: this.elements.previewContainer,
293
+ });
264
294
  this.backlinksPanel = new BacklinksPanel({
265
295
  headerPanelElement: this.elements.backlinksHeaderPanel,
266
296
  inlinePanelElement: this.elements.backlinksInlinePanel,
267
- loadBacklinks: (filePath, options = {}) => this.backlinksApiClient.readBacklinks(filePath, options),
268
- onFileSelect: (filePath) => this.handleFileSelection(filePath, { closeSidebarOnMobile: true }),
297
+ loadBacklinks: (filePath, options = {}) => this.vaultApiClient.readBacklinks(filePath, options),
298
+ onFileSelect: (filePath) => this.workspaceRouteController.handleFileSelection(filePath, {
299
+ closeSidebarOnMobile: true,
300
+ }),
269
301
  panelElement: this.elements.backlinksPanel,
270
302
  });
271
- this.excalidrawEmbed = this.createLazyExcalidrawEmbedController();
272
- this.drawioEmbed = this.createLazyDrawioEmbedController();
303
+ this.excalidrawEmbed = new ExcalidrawEmbedController({
304
+ getLocalUser: () => this.lobby.getLocalUser(),
305
+ getTheme: () => this.themeController.getTheme(),
306
+ onOpenFile: (filePath) => filePath && this.navigation.navigateToFile(filePath),
307
+ onToggleQuickSwitcher: () => {
308
+ void this.toggleQuickSwitcher();
309
+ },
310
+ previewContainer: this.elements.previewContainer,
311
+ previewElement: this.elements.previewContent,
312
+ toastController: this.toastController,
313
+ });
314
+ this.drawioEmbed = new DrawioEmbedController({
315
+ getLocalUser: () => this.lobby.getLocalUser(),
316
+ getTheme: () => this.themeController.getTheme(),
317
+ onOpenFile: (filePath) => filePath && this.navigation.navigateToFile(filePath),
318
+ onOpenTextFile: (filePath) => filePath && this.navigation.navigateToFile(filePath, { drawioMode: 'text' }),
319
+ onToggleQuickSwitcher: () => {
320
+ void this.toggleQuickSwitcher();
321
+ },
322
+ previewContainer: this.elements.previewContainer,
323
+ previewElement: this.elements.previewContent,
324
+ toastController: this.toastController,
325
+ vaultApiClient: this.vaultApiClient,
326
+ });
273
327
  this.commentUi = new CommentUiController({
274
328
  commentSelectionButton: this.elements.commentSelectionButton,
275
329
  commentsDrawer: this.elements.commentsDrawer,
@@ -300,6 +354,10 @@ export class CollabMdAppShell {
300
354
  onToggleReaction: (threadId, messageId, emoji) => this.session?.toggleCommentReaction(threadId, messageId, emoji),
301
355
  onResolveThread: (threadId) => this.resolveCommentThread(threadId),
302
356
  });
357
+ this.structurizrPreview = new StructurizrPreviewController({
358
+ enabled: this.runtimeConfig.structurizrEnabled === true,
359
+ syncWorkspace: (payload) => this.vaultApiClient.syncStructurizrWorkspace(payload),
360
+ });
303
361
  this.workspacePreviewController = new WorkspacePreviewController({
304
362
  backlinksPanel: this.backlinksPanel,
305
363
  basesPreview: this.basesPreview,
@@ -312,10 +370,13 @@ export class CollabMdAppShell {
312
370
  isDrawioFile: (filePath) => this.isDrawioFile(filePath),
313
371
  isExcalidrawFile: (filePath) => this.isExcalidrawFile(filePath),
314
372
  isImageFile: (filePath) => this.isImageFile(filePath),
373
+ isPdfFile: (filePath) => this.isPdfFile(filePath),
315
374
  isMermaidFile: (filePath) => this.isMermaidFile(filePath),
316
375
  isPlantUmlFile: (filePath) => this.isPlantUmlFile(filePath),
317
376
  layoutController: this.layoutController,
377
+ structurizrPreview: this.structurizrPreview,
318
378
  outlineController: this.outlineController,
379
+ pdfPreview: this.pdfPreview,
319
380
  previewRenderer: this.previewRenderer,
320
381
  schedulePreviewLayoutSync: (options) => this.schedulePreviewLayoutSync(options),
321
382
  scrollSyncController: this.scrollSyncController,
@@ -329,8 +390,43 @@ export class CollabMdAppShell {
329
390
  vaultApiClient: this.vaultApiClient,
330
391
  wikiLinkAutoCreate: this.runtimeConfig.wikiLinkAutoCreate !== false,
331
392
  });
332
- this.gitDiffView = this.createLazyGitDiffViewController();
333
- this.fileHistoryView = this.createLazyFileHistoryViewController();
393
+ this.gitDiffView = new GitDiffViewController({
394
+ getTheme: () => this.themeController.getTheme(),
395
+ gitApiClient: this.gitApiClient,
396
+ onBackToHistory: ({ historyFilePath } = {}) => {
397
+ if (historyFilePath) {
398
+ this.navigation.navigateToGitFileHistory({ filePath: historyFilePath });
399
+ return;
400
+ }
401
+ this.navigation.navigateToGitHistory();
402
+ },
403
+ onCommitStaged: () => this.openGitCommitDialog(),
404
+ onOpenFile: (filePath) => filePath && this.navigation.navigateToFile(filePath),
405
+ onStageFile: (filePath, { scope }) => this.stageGitFile(filePath, { scope }),
406
+ onUnstageFile: (filePath, { scope }) => this.unstageGitFile(filePath, { scope }),
407
+ toastController: this.toastController,
408
+ vaultApiClient: this.vaultApiClient,
409
+ });
410
+ this.fileHistoryView = new FileHistoryViewController({
411
+ diffRenderer: this.gitDiffView,
412
+ gitApiClient: this.gitApiClient,
413
+ onOpenCommitDiff: (hash, { historyFilePath, path }) => this.handleGitCommitSelection(hash, {
414
+ closeSidebarOnMobile: false,
415
+ historyFilePath,
416
+ path,
417
+ }),
418
+ onOpenFile: (filePath) => filePath && this.navigation.navigateToFile(filePath),
419
+ onOpenPreview: ({ hash, path, currentFilePath }) => this.handleGitFilePreviewSelection({
420
+ hash,
421
+ path,
422
+ currentFilePath,
423
+ }),
424
+ onOpenWorkspaceDiff: (filePath) => this.handleGitDiffSelection(filePath, {
425
+ closeSidebarOnMobile: false,
426
+ scope: 'all',
427
+ }),
428
+ toastController: this.toastController,
429
+ });
334
430
  this.tabActivityLock = new TabActivityLock({
335
431
  onActivated: ({ takeover }) => this.handleTabActivated({ takeover }),
336
432
  onBlocked: () => this.handleTabBlocked({ reason: 'active-elsewhere' }),
@@ -353,6 +449,7 @@ export class CollabMdAppShell {
353
449
  lineInfoElement: this.elements.lineInfo,
354
450
  lineWrappingEnabled: options.lineWrappingEnabled,
355
451
  localUser: options.localUser,
452
+ vimModeEnabled: options.vimModeEnabled,
356
453
  onImagePaste: options.onImagePaste,
357
454
  onAwarenessChange: options.onAwarenessChange,
358
455
  onCommentsChange: options.onCommentsChange,
@@ -364,6 +461,7 @@ export class CollabMdAppShell {
364
461
  getDisplayName: (filePath) => this.getDisplayName(filePath),
365
462
  getFileList: () => this.fileExplorer.flatDocumentFiles,
366
463
  getLineWrappingEnabled: () => this.getStoredLineWrapping(),
464
+ getVimModeEnabled: () => this.getStoredVimMode(),
367
465
  getLocalUser: () => this.lobby.getLocalUser(),
368
466
  getStoredUserName: () => this.getStoredUserName(),
369
467
  getTheme: () => this.themeController.getTheme(),
@@ -371,8 +469,10 @@ export class CollabMdAppShell {
371
469
  isDrawioFile: (filePath) => this.isDrawioFile(filePath),
372
470
  isExcalidrawFile: (filePath) => this.isExcalidrawFile(filePath),
373
471
  isImageFile: (filePath) => this.isImageFile(filePath),
472
+ isPdfFile: (filePath) => this.isPdfFile(filePath),
374
473
  isMermaidFile: (filePath) => this.isMermaidFile(filePath),
375
474
  isPlantUmlFile: (filePath) => this.isPlantUmlFile(filePath),
475
+ isStructurizrWorkspaceFile: (filePath) => this.isStructurizrWorkspaceFile(filePath),
376
476
  isTabActive: () => this.isTabActive,
377
477
  loadBootstrapContent: async (filePath) => {
378
478
  const response = await this.vaultApiClient.readFile(filePath);
@@ -381,20 +481,30 @@ export class CollabMdAppShell {
381
481
  loadEditorSessionClass: () => this.loadEditorSessionClass(),
382
482
  loadBacklinks: (filePath) => this.backlinksPanel.load(filePath),
383
483
  onBeforeFileOpen: () => {
484
+ clearTimeout(this._basePreviewRenderTimer);
485
+ this._basePreviewRenderTimer = null;
384
486
  this.session = null;
385
487
  this.commentUi.attachSession(null);
386
488
  this.layoutController.reset();
387
- this.resetPreviewMode();
489
+ this.workspacePreviewController.resetPreviewMode();
388
490
  this.elements.emptyState?.classList.add('hidden');
389
491
  this.elements.editorPage?.classList.remove('hidden');
390
492
  this.elements.diffPage?.classList.add('hidden');
391
493
  this.clearInitialFileBootstrap();
392
494
  },
393
495
  onConnectionChange: (state) => this.handleConnectionChange(state),
394
- onContentChange: ({ isBase, isMermaid, isPlantUml }) => {
496
+ onContentChange: ({ isBase, isMermaid, isPlantUml, isStructurizrWorkspace }) => {
395
497
  this.handleCommentEditorContentChange();
396
498
  if (isBase) {
397
- void this.renderBaseFilePreview(this.currentFilePath, {
499
+ this.scheduleBaseFilePreview(this.currentFilePath, {
500
+ source: this.session?.getText?.() ?? '',
501
+ });
502
+ return;
503
+ }
504
+
505
+ if (isStructurizrWorkspace) {
506
+ this.structurizrPreview.queueSync({
507
+ filePath: this.currentFilePath,
398
508
  source: this.session?.getText?.() ?? '',
399
509
  });
400
510
  return;
@@ -414,6 +524,9 @@ export class CollabMdAppShell {
414
524
  },
415
525
  onFileOpenReady: () => {
416
526
  this.hideEditorLoading();
527
+ if (this.currentFilePath) {
528
+ this.preferences.recordRecentFile(this.currentFilePath);
529
+ }
417
530
  },
418
531
  onSelectionChange: (anchor) => this.handleCommentSelectionChange(anchor),
419
532
  onImagePaste: (file) => this.handleEditorImageInsert(file),
@@ -422,10 +535,12 @@ export class CollabMdAppShell {
422
535
  this.session = session;
423
536
  this.commentUi.attachSession(session);
424
537
  },
425
- onRenderDrawioPreview: (filePath) => this.renderDrawioFilePreview(filePath),
538
+ onRenderDrawioPreview: (filePath) => this.workspacePreviewController.renderDrawioFilePreview(filePath),
426
539
  onRenderBasePreview: (filePath) => this.renderBaseFilePreview(filePath),
427
- onRenderExcalidrawPreview: (filePath) => this.renderExcalidrawFilePreview(filePath),
428
- onRenderImagePreview: (filePath) => this.renderImageFilePreview(filePath),
540
+ onRenderExcalidrawPreview: (filePath) => this.workspacePreviewController.renderExcalidrawFilePreview(filePath),
541
+ onRenderImagePreview: (filePath) => this.workspacePreviewController.renderImageFilePreview(filePath),
542
+ onRenderPdfPreview: (filePath) => this.workspacePreviewController.renderPdfFilePreview(filePath),
543
+ onRenderStructurizrPreview: (filePath, options) => this.workspacePreviewController.renderStructurizrFilePreview(filePath, options),
429
544
  onSyncWrapToggle: () => this.syncWrapToggle(),
430
545
  onUpdateActiveFile: (filePath) => this.fileExplorer.setActiveFile(filePath),
431
546
  onUpdateCurrentFile: (filePath) => {
@@ -433,7 +548,7 @@ export class CollabMdAppShell {
433
548
  },
434
549
  onUpdateLobbyCurrentFile: (filePath) => this.lobby.setCurrentFile(filePath),
435
550
  onUpdateVisibleChrome: (filePath, { displayName }) => {
436
- this.syncFileChrome(filePath, {
551
+ this.workspacePreviewController.syncFileChrome(filePath, {
437
552
  drawioMode: this.currentDrawioMode,
438
553
  preferPreviewForBase: this.isBaseFile(filePath),
439
554
  });
@@ -443,7 +558,7 @@ export class CollabMdAppShell {
443
558
  this.elements.activeFileName.textContent = displayName;
444
559
  }
445
560
  },
446
- onViewModeReset: () => this.resetPreviewMode(),
561
+ onViewModeReset: () => this.workspacePreviewController.resetPreviewMode(),
447
562
  renderPresence: () => this.renderPresence(),
448
563
  scrollContainerForSession: (session) => session.getScrollContainer(),
449
564
  shouldUseDrawioPreview: () => Boolean(this.runtimeConfig.drawioBaseUrl),
@@ -472,7 +587,7 @@ export class CollabMdAppShell {
472
587
  requestPreviewRouteAnchor: (anchorId, filePath) => this.requestPreviewRouteAnchor(anchorId, filePath),
473
588
  renderAvatars: () => this.renderAvatars(),
474
589
  renderPresence: () => this.renderPresence(),
475
- resetPreviewMode: () => this.resetPreviewMode(),
590
+ resetPreviewMode: () => this.workspacePreviewController.resetPreviewMode(),
476
591
  scrollSyncController: this.scrollSyncController,
477
592
  setCurrentFilePath: (value) => {
478
593
  this.currentFilePath = value;
@@ -501,9 +616,6 @@ export class CollabMdAppShell {
501
616
  workspaceCoordinator: this.workspaceCoordinator,
502
617
  });
503
618
 
504
- if (this.chatNotificationPermission !== 'granted') {
505
- this.chatNotificationsEnabled = false;
506
- }
507
619
  }
508
620
 
509
621
  get session() { return this._session; }
@@ -2,6 +2,50 @@ function getWindowRef() {
2
2
  return typeof window === 'undefined' ? globalThis : window;
3
3
  }
4
4
 
5
+ export function pickFiles({ accept = '', multiple = false } = {}) {
6
+ return new Promise((resolve) => {
7
+ const input = document.createElement('input');
8
+ const windowRef = getWindowRef();
9
+ input.type = 'file';
10
+ input.accept = accept;
11
+ input.multiple = multiple;
12
+ input.style.position = 'fixed';
13
+ input.style.left = '-9999px';
14
+ document.body.appendChild(input);
15
+ let settled = false;
16
+ let focusTimer = null;
17
+
18
+ const cleanup = (files = []) => {
19
+ if (settled) {
20
+ return;
21
+ }
22
+
23
+ settled = true;
24
+ if (focusTimer) {
25
+ windowRef.clearTimeout(focusTimer);
26
+ }
27
+ windowRef.removeEventListener('focus', handleWindowFocus);
28
+ input.remove();
29
+ resolve(Array.from(files ?? []));
30
+ };
31
+
32
+ const handleWindowFocus = () => {
33
+ focusTimer = windowRef.setTimeout(() => {
34
+ if (settled || input.files?.length) {
35
+ return;
36
+ }
37
+
38
+ cleanup();
39
+ }, 250);
40
+ };
41
+
42
+ input.addEventListener('change', () => cleanup(input.files), { once: true });
43
+ input.addEventListener('cancel', () => cleanup(), { once: true });
44
+ windowRef.addEventListener('focus', handleWindowFocus, { once: true });
45
+ input.click();
46
+ });
47
+ }
48
+
5
49
  export function requestIdleRender(callback, timeout) {
6
50
  const windowRef = getWindowRef();
7
51
  if (typeof windowRef.requestIdleCallback === 'function') {