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,8 @@
1
- import { createRenderedCommentBody, formatAnchorLabel, getLatestGroupMessage } from './comment-ui-shared.js';
1
+ import {
2
+ createCommentOverviewThread,
3
+ formatAnchorLabel,
4
+ getLatestGroupMessage,
5
+ } from './comment-ui-shared.js';
2
6
 
3
7
  /**
4
8
  * @typedef {object} CommentUiRenderContext
@@ -61,18 +65,38 @@ function renderDrawer() {
61
65
  }
62
66
 
63
67
  this.commentsDrawer.classList.toggle('hidden', !this.supported || !this.drawerOpen);
68
+ if (!this.supported || !this.drawerOpen) {
69
+ return;
70
+ }
71
+
64
72
  this.commentsDrawerList.replaceChildren();
65
73
  const groups = this.getThreadGroups();
66
74
  this.commentsDrawerEmpty?.classList.toggle('hidden', groups.length > 0);
67
- if (!this.supported || groups.length === 0) {
75
+ if (groups.length === 0) {
68
76
  return;
69
77
  }
70
78
 
71
79
  const fragment = document.createDocumentFragment();
72
80
  groups.forEach((group) => {
73
- const button = document.createElement('button');
74
- button.type = 'button';
75
- button.className = 'ui-record-surface comments-drawer-item';
81
+ const latestMessage = getLatestGroupMessage(group);
82
+ const messageCount = group.threads.reduce(
83
+ (count, thread) => count + (Array.isArray(thread.messages) ? thread.messages.length : 0),
84
+ 0,
85
+ );
86
+ const button = createCommentOverviewThread({
87
+ authorName: latestMessage?.userName || group.threads[0]?.createdByName || 'Anonymous',
88
+ buttonClassName: 'comment-overview-thread comments-drawer-item',
89
+ footerClassName: 'comment-overview-thread-footer comments-drawer-item-footer',
90
+ headerClassName: 'comment-overview-thread-header comments-drawer-item-header',
91
+ lineClassName: 'comment-overview-thread-line comments-drawer-item-title',
92
+ lineLabel: formatAnchorLabel(group.anchor),
93
+ messageCount,
94
+ previewBody: latestMessage?.body || '',
95
+ previewClassName: 'comment-markdown comment-overview-thread-preview comments-drawer-item-preview',
96
+ quote: group.anchor?.quote || group.anchor?.excerpt || 'Source anchored comment',
97
+ quoteClassName: 'comment-overview-thread-quote comments-drawer-item-quote',
98
+ timestamp: latestMessage ? this.formatTimestamp(latestMessage.createdAt) : '',
99
+ });
76
100
  button.classList.toggle('is-active', this.activeCard?.groupKey === group.key);
77
101
  button.addEventListener('pointerdown', (event) => {
78
102
  event.preventDefault();
@@ -81,48 +105,11 @@ function renderDrawer() {
81
105
  this.onNavigateToLine?.(group.anchor?.startLine ?? 1);
82
106
  this.openThreadGroup(group, {
83
107
  anchor: group.anchor,
84
- origin: 'drawer',
85
- sourceRect: button.getBoundingClientRect(),
108
+ origin: 'editor',
109
+ sourceRect: this.session?.getCommentAnchorClientRect?.(group.anchor)
110
+ ?? button.getBoundingClientRect(),
86
111
  });
87
112
  });
88
-
89
- const header = document.createElement('div');
90
- header.className = 'ui-record-header comments-drawer-item-header';
91
-
92
- const title = document.createElement('span');
93
- title.className = 'comments-drawer-item-title';
94
- title.textContent = formatAnchorLabel(group.anchor);
95
-
96
- const count = document.createElement('span');
97
- count.className = 'ui-pill-badge ui-pill-badge--count ui-pill-badge--accent comments-drawer-item-count';
98
- count.textContent = String(group.threads.length);
99
-
100
- header.append(title, count);
101
-
102
- const quote = document.createElement('p');
103
- quote.className = 'comments-drawer-item-quote';
104
- quote.textContent = group.anchor.quote || group.anchor.excerpt || 'Source anchored comment';
105
-
106
- const latestMessage = getLatestGroupMessage(group);
107
- const preview = createRenderedCommentBody(
108
- latestMessage?.body || '',
109
- 'comment-markdown comments-drawer-item-preview',
110
- );
111
-
112
- const footer = document.createElement('div');
113
- footer.className = 'ui-record-meta comments-drawer-item-footer';
114
- const countLabel = document.createElement('span');
115
- countLabel.textContent = `${group.threads.length} thread${group.threads.length === 1 ? '' : 's'}`;
116
-
117
- const updatedLabel = document.createElement('span');
118
- updatedLabel.className = 'comments-drawer-item-updated';
119
- updatedLabel.textContent = latestMessage
120
- ? `${latestMessage.userName} • ${this.formatTimestamp(latestMessage.createdAt)}`
121
- : '';
122
-
123
- footer.append(countLabel, updatedLabel);
124
-
125
- button.append(header, quote, preview, footer);
126
113
  fragment.appendChild(button);
127
114
  });
128
115
 
@@ -13,10 +13,6 @@ export const COMMENT_CARD_WIDTH = 520;
13
13
  export const COMMENT_SELECTION_REVEAL_DELAY_MS = 150;
14
14
  export const COMMENT_SELECTION_CHIP_GAP = 12;
15
15
  export const COMMENT_CONTROL_SLOT_HEIGHT = 36;
16
- export const COMMENT_PREVIEW_BADGE_MIN_WIDTH = 28;
17
- export const COMMENT_PREVIEW_RAIL_SLOT_HEIGHT = 30;
18
- export const COMMENT_PREVIEW_RAIL_MIN_WIDTH = 400;
19
- export const COMMENT_PREVIEW_RAIL_BREAKPOINT = 769;
20
16
  export const COMMENT_REACTION_PRESET_EMOJIS = Object.freeze(['👍', '❤️', '🎉', '👀', '🚀']);
21
17
  export const COMMENT_REACTION_MORE_EMOJIS = Object.freeze(['😂', '🔥', '✅', '🙏', '💡', '🤔', '👏', '😄', '🎯', '🙌']);
22
18
 
@@ -55,19 +51,19 @@ export function areAnchorsEqual(left, right) {
55
51
  }
56
52
 
57
53
  export function formatAnchorLabel(anchor) {
58
- if (!anchor) {
59
- return 'No source anchor';
54
+ if (getAnchorKind(anchor) === 'diagram-element') {
55
+ return 'Diagram element';
60
56
  }
61
57
 
62
- if ((anchor.kind || anchor.anchorKind) === 'text' && anchor.quote) {
63
- return anchor.startLine === anchor.endLine
64
- ? `Line ${anchor.startLine}`
65
- : `Lines ${anchor.startLine}-${anchor.endLine}`;
58
+ const startLine = Number(anchor?.startLine || 0);
59
+ const endLine = Number(anchor?.endLine || startLine);
60
+ if (!Number.isFinite(startLine) || startLine <= 0) {
61
+ return 'No source anchor';
66
62
  }
67
63
 
68
- return anchor.startLine === anchor.endLine
69
- ? `Line ${anchor.startLine}`
70
- : `Lines ${anchor.startLine}-${anchor.endLine}`;
64
+ return startLine === endLine
65
+ ? `Line ${startLine}`
66
+ : `Lines ${startLine}-${endLine}`;
71
67
  }
72
68
 
73
69
  export function getAnchorGroupKey(anchor = {}) {
@@ -120,6 +116,65 @@ export function createRenderedCommentBody(body, className = 'comment-markdown')
120
116
  return container;
121
117
  }
122
118
 
119
+ export function createCommentOverviewThread({
120
+ authorName = 'Anonymous',
121
+ buttonClassName = 'comment-overview-thread',
122
+ footerClassName = 'comment-overview-thread-footer',
123
+ headerClassName = 'comment-overview-thread-header',
124
+ lineClassName = 'comment-overview-thread-line',
125
+ lineLabel = '',
126
+ messageCount = 0,
127
+ previewBody = '',
128
+ previewClassName = 'comment-markdown comment-overview-thread-preview',
129
+ quote = 'Source anchored comment',
130
+ quoteClassName = 'comment-overview-thread-quote',
131
+ timestamp = '',
132
+ } = {}) {
133
+ const button = document.createElement('button');
134
+ button.type = 'button';
135
+ button.className = buttonClassName;
136
+
137
+ const header = document.createElement('div');
138
+ header.className = headerClassName;
139
+
140
+ const line = document.createElement('span');
141
+ line.className = lineClassName;
142
+ line.textContent = lineLabel;
143
+
144
+ const meta = document.createElement('span');
145
+ meta.className = 'comment-overview-thread-meta';
146
+ meta.textContent = timestamp;
147
+ header.append(line, meta);
148
+
149
+ const body = document.createElement('div');
150
+ body.className = 'comment-overview-thread-body';
151
+ const preview = createRenderedCommentBody(previewBody, previewClassName);
152
+
153
+ const quoteSection = document.createElement('div');
154
+ quoteSection.className = 'comment-overview-thread-section comment-overview-thread-reference';
155
+ const quoteLabel = document.createElement('span');
156
+ quoteLabel.className = 'comment-overview-thread-section-label';
157
+ quoteLabel.textContent = 'Reference';
158
+ const quoteNode = document.createElement('span');
159
+ quoteNode.className = quoteClassName;
160
+ quoteNode.textContent = quote;
161
+ quoteSection.append(quoteLabel, quoteNode);
162
+ body.append(preview, quoteSection);
163
+
164
+ const footer = document.createElement('div');
165
+ footer.className = footerClassName;
166
+ const author = document.createElement('span');
167
+ author.className = 'comment-overview-thread-author';
168
+ author.textContent = authorName;
169
+ const messages = document.createElement('span');
170
+ messages.className = 'comment-overview-thread-message-count';
171
+ messages.textContent = `${messageCount} message${messageCount === 1 ? '' : 's'}`;
172
+ footer.append(author, messages);
173
+
174
+ button.append(header, body, footer);
175
+ return button;
176
+ }
177
+
123
178
  export function hasLocalReaction(reaction, localUserId) {
124
179
  return Boolean(localUserId && reaction?.users?.some((user) => user?.userId === localUserId));
125
180
  }
@@ -89,9 +89,13 @@ export class CommentUiController {
89
89
  if (this.activeCard?.mode === 'create' && this.activeCard.origin === 'preview') {
90
90
  return;
91
91
  }
92
- this.previewSelection = this.supported && this.fileKind === 'markdown'
92
+ const previewSelection = this.supported && this.fileKind === 'markdown'
93
93
  ? createPreviewSelection(window.getSelection(), this.previewElement, this.session?.getText?.() ?? '')
94
94
  : null;
95
+ if (!previewSelection && !this.previewSelection) {
96
+ return;
97
+ }
98
+ this.previewSelection = previewSelection;
95
99
  this.scheduleLayoutRefresh();
96
100
  };
97
101
  this.handleCommentSelectionButtonPointerDown = (event) => {
@@ -1,6 +1,7 @@
1
1
  import { createDrawioLeaseRoomName } from '../../domain/drawio-room.js';
2
2
  import { setDiagramActionButtonIcon } from '../domain/diagram-action-icons.js';
3
3
  import { resolveAppUrl } from '../domain/runtime-paths.js';
4
+ import { renderDrawioViewer } from './drawio-viewer.js';
4
5
  import {
5
6
  cancelIdleRender,
6
7
  isNearViewport,
@@ -8,62 +9,6 @@ import {
8
9
  } from '../browser-utils.js';
9
10
 
10
11
  const HYDRATE_VIEWPORT_MARGIN_PX = 360;
11
- const DRAWIO_VIEWER_SCRIPT_URL = 'https://viewer.diagrams.net/js/viewer-static.min.js';
12
-
13
- let drawioViewerLoadPromise = null;
14
-
15
- function ensureDrawioViewerLoaded() {
16
- if (window.GraphViewer?.processElements) {
17
- return Promise.resolve(window.GraphViewer);
18
- }
19
-
20
- if (drawioViewerLoadPromise) {
21
- return drawioViewerLoadPromise;
22
- }
23
-
24
- drawioViewerLoadPromise = new Promise((resolve, reject) => {
25
- const existingScript = document.querySelector('script[data-collabmd-drawio-viewer]');
26
- const script = existingScript instanceof HTMLScriptElement
27
- ? existingScript
28
- : document.createElement('script');
29
-
30
- const cleanup = () => {
31
- script.removeEventListener('error', handleError);
32
- script.removeEventListener('load', handleLoad);
33
- };
34
-
35
- const handleError = () => {
36
- cleanup();
37
- drawioViewerLoadPromise = null;
38
- reject(new Error('Failed to load draw.io viewer'));
39
- };
40
-
41
- const handleLoad = () => {
42
- cleanup();
43
- if (!window.GraphViewer?.processElements) {
44
- drawioViewerLoadPromise = null;
45
- reject(new Error('draw.io viewer did not initialize'));
46
- return;
47
- }
48
-
49
- resolve(window.GraphViewer);
50
- };
51
-
52
- script.addEventListener('error', handleError, { once: true });
53
- script.addEventListener('load', handleLoad, { once: true });
54
-
55
- if (!existingScript) {
56
- script.src = DRAWIO_VIEWER_SCRIPT_URL;
57
- script.async = true;
58
- script.dataset.collabmdDrawioViewer = 'true';
59
- document.head.append(script);
60
- } else if (window.GraphViewer?.processElements) {
61
- handleLoad();
62
- }
63
- });
64
-
65
- return drawioViewerLoadPromise;
66
- }
67
12
 
68
13
  export class DrawioEmbedController {
69
14
  constructor({
@@ -350,59 +295,21 @@ export class DrawioEmbedController {
350
295
  }
351
296
 
352
297
  async renderViewerEntry(entry) {
353
- const [{ content }, viewer] = await Promise.all([
354
- this.vaultApiClient.readFile(entry.filePath),
355
- ensureDrawioViewerLoaded(),
356
- ]);
298
+ const { content } = await this.vaultApiClient.readFile(entry.filePath);
357
299
 
358
300
  if (!entry.wrapper?.isConnected || !entry.viewerElement?.isConnected) {
359
301
  return;
360
302
  }
361
303
 
362
304
  const theme = this.getTheme?.() === 'light' ? 'light' : 'dark';
363
- const graphElement = document.createElement('div');
364
- graphElement.className = 'mxgraph drawio-viewer-frame';
365
- graphElement.dataset.action = 'open-file';
366
- graphElement.dataset.filePath = entry.filePath;
367
- graphElement.setAttribute('role', 'button');
368
- graphElement.setAttribute('tabindex', '0');
369
- graphElement.setAttribute('aria-label', `Open ${entry.label.replace(/\.drawio$/i, '')}`);
370
- graphElement.addEventListener('click', (event) => {
371
- event.preventDefault();
372
- this.onOpenFile?.(entry.filePath);
373
- });
374
- graphElement.addEventListener('keydown', (event) => {
375
- if (event.key !== 'Enter' && event.key !== ' ') {
376
- return;
377
- }
378
-
379
- event.preventDefault();
380
- this.onOpenFile?.(entry.filePath);
381
- });
382
- graphElement.dataset.mxgraph = JSON.stringify({
383
- 'check-visible-state': false,
384
- center: true,
385
- border: 0,
386
- 'dark-mode': theme,
387
- editable: false,
388
- fit: 1,
389
- lightbox: false,
390
- nav: false,
391
- resize: false,
392
- tooltips: false,
393
- xml: String(content ?? ''),
394
- });
395
-
396
305
  const viewerHost = entry.wrapper.querySelector('.drawio-viewer-shell') ?? entry.viewerElement;
397
- viewerHost.replaceChildren(graphElement);
398
- entry.viewerElement = graphElement;
399
-
400
- if (typeof viewer.createViewerForElement === 'function') {
401
- viewer.createViewerForElement(graphElement);
402
- return;
403
- }
404
-
405
- viewer.processElements();
306
+ entry.viewerElement = await renderDrawioViewer(viewerHost, {
307
+ ariaLabel: `Open ${entry.label.replace(/\.drawio$/i, '')}`,
308
+ className: 'mxgraph drawio-viewer-frame',
309
+ onActivate: () => this.onOpenFile?.(entry.filePath),
310
+ source: String(content ?? ''),
311
+ theme,
312
+ });
406
313
  }
407
314
 
408
315
  buildIframeUrl(entry) {
@@ -0,0 +1,118 @@
1
+ const DRAWIO_VIEWER_SCRIPT_URL = 'https://viewer.diagrams.net/js/viewer-static.min.js';
2
+
3
+ let drawioViewerLoadPromise = null;
4
+
5
+ export function ensureDrawioViewerLoaded() {
6
+ if (window.GraphViewer?.processElements) {
7
+ return Promise.resolve(window.GraphViewer);
8
+ }
9
+
10
+ if (drawioViewerLoadPromise) {
11
+ return drawioViewerLoadPromise;
12
+ }
13
+
14
+ drawioViewerLoadPromise = new Promise((resolve, reject) => {
15
+ const existingScript = document.querySelector('script[data-collabmd-drawio-viewer]');
16
+ const script = existingScript instanceof HTMLScriptElement
17
+ ? existingScript
18
+ : document.createElement('script');
19
+
20
+ const cleanup = () => {
21
+ script.removeEventListener('error', handleError);
22
+ script.removeEventListener('load', handleLoad);
23
+ };
24
+
25
+ const handleError = () => {
26
+ cleanup();
27
+ drawioViewerLoadPromise = null;
28
+ reject(new Error('Failed to load draw.io viewer'));
29
+ };
30
+
31
+ const handleLoad = () => {
32
+ cleanup();
33
+ if (!window.GraphViewer?.processElements) {
34
+ drawioViewerLoadPromise = null;
35
+ reject(new Error('draw.io viewer did not initialize'));
36
+ return;
37
+ }
38
+
39
+ resolve(window.GraphViewer);
40
+ };
41
+
42
+ script.addEventListener('error', handleError, { once: true });
43
+ script.addEventListener('load', handleLoad, { once: true });
44
+
45
+ if (!existingScript) {
46
+ script.src = DRAWIO_VIEWER_SCRIPT_URL;
47
+ script.async = true;
48
+ script.dataset.collabmdDrawioViewer = 'true';
49
+ document.head.append(script);
50
+ } else if (window.GraphViewer?.processElements) {
51
+ handleLoad();
52
+ }
53
+ });
54
+
55
+ return drawioViewerLoadPromise;
56
+ }
57
+
58
+ export function createDrawioViewerElement({
59
+ ariaLabel = '',
60
+ className = 'drawio-viewer-frame',
61
+ onActivate = null,
62
+ source = '',
63
+ theme = 'dark',
64
+ } = {}) {
65
+ const graphElement = document.createElement('div');
66
+ graphElement.className = className;
67
+ if (ariaLabel) {
68
+ graphElement.setAttribute('aria-label', ariaLabel);
69
+ }
70
+
71
+ if (typeof onActivate === 'function') {
72
+ graphElement.dataset.action = 'open-file';
73
+ graphElement.setAttribute('role', 'button');
74
+ graphElement.setAttribute('tabindex', '0');
75
+ graphElement.addEventListener('click', (event) => {
76
+ event.preventDefault();
77
+ onActivate();
78
+ });
79
+ graphElement.addEventListener('keydown', (event) => {
80
+ if (event.key !== 'Enter' && event.key !== ' ') {
81
+ return;
82
+ }
83
+
84
+ event.preventDefault();
85
+ onActivate();
86
+ });
87
+ }
88
+
89
+ graphElement.dataset.mxgraph = JSON.stringify({
90
+ 'check-visible-state': false,
91
+ center: true,
92
+ border: 0,
93
+ 'dark-mode': theme === 'light' ? 'light' : 'dark',
94
+ editable: false,
95
+ fit: 1,
96
+ lightbox: false,
97
+ nav: false,
98
+ resize: false,
99
+ tooltips: false,
100
+ xml: String(source ?? ''),
101
+ });
102
+
103
+ return graphElement;
104
+ }
105
+
106
+ export async function renderDrawioViewer(host, options = {}) {
107
+ const viewer = await ensureDrawioViewerLoaded();
108
+ const graphElement = createDrawioViewerElement(options);
109
+ host.replaceChildren(graphElement);
110
+
111
+ if (typeof viewer.createViewerForElement === 'function') {
112
+ viewer.createViewerForElement(graphElement);
113
+ } else {
114
+ viewer.processElements();
115
+ }
116
+
117
+ return graphElement;
118
+ }
@@ -1,6 +1,6 @@
1
1
  import { setDiagramActionButtonIcon } from '../domain/diagram-action-icons.js';
2
2
  import { reconcileEmbedEntries } from './excalidraw-embed-reconciler.js';
3
- import { resolveAppUrl } from '../domain/runtime-paths.js';
3
+ import { resolveAppUrl, resolveWsServerOverride } from '../domain/runtime-paths.js';
4
4
  import {
5
5
  cancelIdleRender,
6
6
  isNearViewport,
@@ -15,6 +15,13 @@ const MAX_IFRAME_BOOT_ATTEMPTS = 3;
15
15
  const MIN_HEIGHT = 200;
16
16
  const EDITOR_IFRAME_PATH = '/excalidraw-editor.html';
17
17
 
18
+ function findEmbedPlaceholder(previewElement, key) {
19
+ const placeholders = previewElement?.querySelectorAll?.(
20
+ '.excalidraw-embed-placeholder[data-embed-key]',
21
+ );
22
+ return Array.from(placeholders || []).find((placeholder) => placeholder.dataset?.embedKey === key) || null;
23
+ }
24
+
18
25
  export class ExcalidrawEmbedController {
19
26
  constructor({
20
27
  getTheme,
@@ -70,12 +77,16 @@ export class ExcalidrawEmbedController {
70
77
 
71
78
  this.embedEntries.forEach((entry) => {
72
79
  this._clearEntryBootTimeout(entry);
80
+ entry.resizerCleanup?.();
81
+ entry.resizerCleanup = null;
73
82
  entry.wrapper?.remove();
74
83
  entry.placeholder = null;
75
84
  });
76
85
  this.embedEntries.clear();
77
86
  if (this.warmEntry) {
78
87
  this._clearEntryBootTimeout(this.warmEntry);
88
+ this.warmEntry.resizerCleanup?.();
89
+ this.warmEntry.resizerCleanup = null;
79
90
  this.warmEntry.wrapper?.remove();
80
91
  this.warmEntry = null;
81
92
  }
@@ -219,6 +230,25 @@ export class ExcalidrawEmbedController {
219
230
  });
220
231
  }
221
232
 
233
+ openCommentThread(filePath, threadId) {
234
+ if (!filePath || !threadId) {
235
+ return false;
236
+ }
237
+
238
+ const entry = this._findEntryByFilePath(filePath);
239
+ if (!entry) {
240
+ return false;
241
+ }
242
+
243
+ entry.pendingCommentThreadId = threadId;
244
+ if (entry.isReady) {
245
+ this._postPendingCommentThread(entry);
246
+ } else if (!entry.wrapper) {
247
+ this._enqueueHydration(entry, { prioritize: true });
248
+ }
249
+ return true;
250
+ }
251
+
222
252
  prepareFileDisconnect(filePath, { timeoutMs = 10000 } = {}) {
223
253
  const entry = this._findEntryByFilePath(filePath);
224
254
  if (!entry?.iframe?.contentWindow) {
@@ -394,6 +424,7 @@ export class ExcalidrawEmbedController {
394
424
  entry.maxButton = mount.maxButton;
395
425
  entry.openButton = mount.openButton;
396
426
  entry.instanceId = mount.instanceId;
427
+ entry.resizerCleanup = mount.resizerCleanup;
397
428
  entry.isReady = false;
398
429
  entry.bootFilePath = entry.filePath;
399
430
  entry.bootMode = this._getEntryMode(entry);
@@ -414,7 +445,7 @@ export class ExcalidrawEmbedController {
414
445
  _attachWrapper(entry) {
415
446
  const placeholder = entry.placeholder?.isConnected
416
447
  ? entry.placeholder
417
- : this.previewElement?.querySelector(`.excalidraw-embed-placeholder[data-embed-key="${entry.key}"]`);
448
+ : findEmbedPlaceholder(this.previewElement, entry.key);
418
449
 
419
450
  if (!placeholder) {
420
451
  return;
@@ -476,6 +507,8 @@ export class ExcalidrawEmbedController {
476
507
  }
477
508
 
478
509
  this._clearEntryBootTimeout(entry);
510
+ entry.resizerCleanup?.();
511
+ entry.resizerCleanup = null;
479
512
  if (this.warmEntry === entry) {
480
513
  this.warmEntry = null;
481
514
  }
@@ -634,7 +667,7 @@ export class ExcalidrawEmbedController {
634
667
  }
635
668
 
636
669
  const hostSearchParams = new URLSearchParams(window.location.search);
637
- const serverOverride = hostSearchParams.get('server');
670
+ const serverOverride = resolveWsServerOverride();
638
671
  if (serverOverride) {
639
672
  iframeUrl.searchParams.set('server', serverOverride);
640
673
  }
@@ -702,7 +735,7 @@ export class ExcalidrawEmbedController {
702
735
  const resizer = document.createElement('div');
703
736
  resizer.className = 'excalidraw-embed-resizer';
704
737
  resizer.title = 'Drag to resize';
705
- this._setupResizer(resizer, iframe, () => {
738
+ const resizerCleanup = this._setupResizer(resizer, iframe, () => {
706
739
  this._syncEntryLayout(entry);
707
740
  this._requestPreviewViewportFit(entry);
708
741
  });
@@ -769,6 +802,7 @@ export class ExcalidrawEmbedController {
769
802
  labelElement: label,
770
803
  maxButton: maxBtn,
771
804
  openButton: openBtn,
805
+ resizerCleanup,
772
806
  wrapper,
773
807
  };
774
808
  }
@@ -793,6 +827,7 @@ export class ExcalidrawEmbedController {
793
827
  _setupResizer(resizer, iframe, onResizeEnd = null) {
794
828
  let startY = 0;
795
829
  let startHeight = 0;
830
+ let isDragging = false;
796
831
 
797
832
  const onPointerMove = (event) => {
798
833
  const delta = event.clientY - startY;
@@ -801,20 +836,37 @@ export class ExcalidrawEmbedController {
801
836
  iframe.style.pointerEvents = 'none';
802
837
  };
803
838
 
804
- const onPointerUp = () => {
839
+ const stopDragging = () => {
840
+ if (!isDragging) {
841
+ return;
842
+ }
843
+
844
+ isDragging = false;
805
845
  iframe.style.pointerEvents = '';
806
846
  document.removeEventListener('pointermove', onPointerMove);
807
847
  document.removeEventListener('pointerup', onPointerUp);
848
+ };
849
+
850
+ const onPointerUp = () => {
851
+ stopDragging();
808
852
  onResizeEnd?.();
809
853
  };
810
854
 
811
- resizer.addEventListener('pointerdown', (event) => {
855
+ const onPointerDown = (event) => {
812
856
  event.preventDefault();
857
+ isDragging = true;
813
858
  startY = event.clientY;
814
859
  startHeight = iframe.offsetHeight;
815
860
  document.addEventListener('pointermove', onPointerMove);
816
861
  document.addEventListener('pointerup', onPointerUp);
817
- });
862
+ };
863
+
864
+ resizer.addEventListener('pointerdown', onPointerDown);
865
+
866
+ return () => {
867
+ resizer.removeEventListener?.('pointerdown', onPointerDown);
868
+ stopDragging();
869
+ };
818
870
  }
819
871
 
820
872
  _ensureOverlayRoot() {
@@ -1064,7 +1116,7 @@ export class ExcalidrawEmbedController {
1064
1116
 
1065
1117
  const placeholder = entry.placeholder?.isConnected
1066
1118
  ? entry.placeholder
1067
- : this.previewElement?.querySelector(`.excalidraw-embed-placeholder[data-embed-key="${entry.key}"]`);
1119
+ : findEmbedPlaceholder(this.previewElement, entry.key);
1068
1120
 
1069
1121
  if (!placeholder) {
1070
1122
  return;
@@ -1163,6 +1215,7 @@ export class ExcalidrawEmbedController {
1163
1215
  theme: this.getTheme?.() || 'dark',
1164
1216
  });
1165
1217
  this._syncEntryFollowState(entry);
1218
+ this._postPendingCommentThread?.(entry);
1166
1219
  if (this._entryNeedsHardReload(entry)) {
1167
1220
  void this._hydrateEntry(entry);
1168
1221
  }
@@ -1227,6 +1280,19 @@ export class ExcalidrawEmbedController {
1227
1280
  entry?.iframe?.contentWindow?.postMessage(payload, window.location.origin);
1228
1281
  }
1229
1282
 
1283
+ _postPendingCommentThread(entry) {
1284
+ if (!entry?.isReady || !entry.pendingCommentThreadId) {
1285
+ return;
1286
+ }
1287
+
1288
+ this._postMessageToEntry(entry, {
1289
+ source: 'collabmd-host',
1290
+ threadId: entry.pendingCommentThreadId,
1291
+ type: 'open-comment-thread',
1292
+ });
1293
+ entry.pendingCommentThreadId = null;
1294
+ }
1295
+
1230
1296
  _requestPreviewViewportFit(entry) {
1231
1297
  if (this._isFilePreviewEntry(entry)) {
1232
1298
  return;