collabmd 0.1.45 → 0.1.47

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 (356) hide show
  1. package/README.md +40 -19
  2. package/bin/collabmd.js +27 -1
  3. package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-BPH4FJkv.js +1 -0
  4. package/dist/client/assets/{abnfDiagram-N423BO3Z-CUMTFJhI.js → abnfDiagram-VCTEODGH-BNDLzJ7L.js} +1 -1
  5. package/dist/client/assets/architecture-7GRP2DOG-BMOPAkUC.js +1 -0
  6. package/dist/client/assets/{architectureDiagram-T3A2C74G-w25m71oV.js → architectureDiagram-5GKGNRK7-I39sYjHI.js} +1 -1
  7. package/dist/client/assets/base-BYODPHdS.js +1 -0
  8. package/dist/client/assets/blockDiagram-NRAW4CY4-C6DtJy5Y.js +129 -0
  9. package/dist/client/assets/browser-BKLM0ThC-IxjwuOIz-BBqQnUEb.js +1 -0
  10. package/dist/client/assets/browser-utils--H_DkyEF.js +1 -0
  11. package/dist/client/assets/c4Diagram-UCG6FXSJ-CtWtxS9_.js +38 -0
  12. package/dist/client/assets/channel-CaoKA_We.js +1 -0
  13. package/dist/client/assets/{chunk-U6XO7XAA-BFxjP5U3.js → chunk-2ZTRR5NV-C2qZJila.js} +1 -1
  14. package/dist/client/assets/{chunk-YOTPTUD7-D0vRKqYp.js → chunk-3Z5EZCMW-DMniOlPd.js} +1 -1
  15. package/dist/client/assets/{chunk-BIQX33UG-DZEsGuSn.js → chunk-5V3GS4D5-i-ZDOEnP.js} +1 -1
  16. package/dist/client/assets/{chunk-MOZMSUNE-XVucA-n6.js → chunk-6AZGARVD-BB6FqMl-.js} +1 -1
  17. package/dist/client/assets/{chunk-5JV3BV7I-Cy5pj3QY.js → chunk-6EIED4P4-DdUJkP0f.js} +1 -1
  18. package/dist/client/assets/{chunk-R7FJI6CG-Cnn3gE-Y.js → chunk-6K3QC6MW-CqVsVn-y.js} +1 -1
  19. package/dist/client/assets/{chunk-OSBZ3O6U-CrKZOUOr.js → chunk-6TQVIW2G-DKlLbNIP.js} +1 -1
  20. package/dist/client/assets/{chunk-JG7HCLWE-BYfiSaVt.js → chunk-747NJXEK-BFkZPlab.js} +1 -1
  21. package/dist/client/assets/{chunk-NSK5VX7P-TLH37VXI.js → chunk-75Z2AOVW-CdFSaPjz.js} +1 -1
  22. package/dist/client/assets/chunk-CKBBP62Z-DG26UiBv.js +2 -0
  23. package/dist/client/assets/{chunk-3NCLNEKW-Y2Jf_LI-.js → chunk-CLGD4ZFX-DQLgbQrv.js} +1 -1
  24. package/dist/client/assets/chunk-DSNCTWBM-8xkug7fQ.js +2 -0
  25. package/dist/client/assets/chunk-DU6HZSFF-COXDzJK1.js +127 -0
  26. package/dist/client/assets/{chunk-2GRJ4B5K-DhnlUv3F.js → chunk-F27PBJKO-CNHIFhjo.js} +1 -1
  27. package/dist/client/assets/{chunk-YGJVY4SA-CjSe7Bx7.js → chunk-FLYJHZSQ-Be7lKF17.js} +1 -1
  28. package/dist/client/assets/{chunk-KEIR6QF5-BICK3FdT.js → chunk-FOHPRMQF-DHwB1DNv.js} +2 -2
  29. package/dist/client/assets/{chunk-5RXB4S5H-BWrwFqMI.js → chunk-G27WJ6UU-D9-eg537.js} +2 -2
  30. package/dist/client/assets/chunk-GMAD6QVW-CbcBqPM5.js +72 -0
  31. package/dist/client/assets/{chunk-QBLGF6JB-CxAyS2mW.js → chunk-I5DQTOEV-Hm2LfiYI.js} +1 -1
  32. package/dist/client/assets/{chunk-5FCAYU7R-Dh5nylU8.js → chunk-ICYGCRZG-749o-pGJ.js} +1 -1
  33. package/dist/client/assets/{chunk-CQNSW5MT-B8S09OIh.js → chunk-IH6LHLGP-CC6I9F3i.js} +1 -1
  34. package/dist/client/assets/chunk-JQ64N6SF-DSFNH4bh.js +1 -0
  35. package/dist/client/assets/{chunk-CYSBUYHQ-C1Rf4art.js → chunk-KI3K4JFJ-Rma-0k_M.js} +1 -1
  36. package/dist/client/assets/{chunk-GF5L2VYU-DkJpO7ff.js → chunk-LCL6LL3I-DmDkVukh.js} +1 -1
  37. package/dist/client/assets/chunk-LNGE3PJU-DWpGwHrW.js +34 -0
  38. package/dist/client/assets/chunk-OBVCFTLP-BJR7u1jh.js +1 -0
  39. package/dist/client/assets/{chunk-5TONJI2A-DQOY3q1H.js → chunk-OUJLGHUK-D0j2JRSg.js} +1 -1
  40. package/dist/client/assets/chunk-P2QGCYS3-BRpqY32H.js +1 -0
  41. package/dist/client/assets/{chunk-KBJHAD2P-CB_3zWUf.js → chunk-POPQ4Y6H-Cb4SlVZF.js} +1 -1
  42. package/dist/client/assets/{chunk-4I5QYGJK-CCB2YcIH.js → chunk-PWAF6VOD-Cz8IwBB5.js} +1 -1
  43. package/dist/client/assets/chunk-R7TYR2AO-CTd0Yj98.js +10 -0
  44. package/dist/client/assets/chunk-RHFEMEQ7-CWY8cf4H.js +168 -0
  45. package/dist/client/assets/chunk-RM4UPSZO-CWRwxNWJ.js +41 -0
  46. package/dist/client/assets/{chunk-6Q2QTUOP-CPlWzBtc.js → chunk-SVP7TREG-CyHOmcac.js} +1 -1
  47. package/dist/client/assets/chunk-TEH6E4GO-DVayPj-q.js +62 -0
  48. package/dist/client/assets/{chunk-EMLP6XTP-RLD4nXpI.js → chunk-UY3FDG6J-CnmaAkG5.js} +1 -1
  49. package/dist/client/assets/{chunk-5HE753X5-Bw-yg4kO.js → chunk-XHIXRSVI-BFQ6B8BE.js} +1 -1
  50. package/dist/client/assets/classDiagram-DTDB5LWJ-7fEmn0zV.js +1 -0
  51. package/dist/client/assets/classDiagram-v2-JRS7N3AN-7fEmn0zV.js +1 -0
  52. package/dist/client/assets/comment-thread-store-CdcwrObc.js +1 -0
  53. package/dist/client/assets/comment-threads-9J6Em-_y.js +4 -0
  54. package/dist/client/assets/comment-ui-shared-DWkk9Vh3.js +7 -0
  55. package/dist/client/assets/cynefin-OW5HDTMX-7tP7QfM3.js +1 -0
  56. package/dist/client/assets/{cynefinDiagram-MW4NZA55-CJxXfJek.js → cynefinDiagram-5FMLGOSQ-CdTPi2yO.js} +1 -1
  57. package/dist/client/assets/dagre-3AP2YEHR-5gtJtd0O.js +4 -0
  58. package/dist/client/assets/dagre-PrKaheQc.js +1 -0
  59. package/dist/client/assets/{diagram-7IWD3JNH-B2d2Bm15.js → diagram-S7CK7UJ4-BudWz9WU.js} +4 -4
  60. package/dist/client/assets/{diagram-UB23O5K3-DJcKCwv9.js → diagram-UQ7AKVKN-3v4rbH-s.js} +1 -1
  61. package/dist/client/assets/{diagram-B4RE2ZJO-NP4UAwjR.js → diagram-VSXAHHWV-CQRVEZR7.js} +1 -1
  62. package/dist/client/assets/{diagram-Q27KOJAE-DylDuwor.js → diagram-VX7I27RA-C_-rj0sw.js} +1 -1
  63. package/dist/client/assets/{diagram-LBJQPF4R-D1FJQV8t.js → diagram-Z3DM3KII-B4yxavgj.js} +1 -1
  64. package/dist/client/assets/direct-engine-DdrQKpgq-CtKfQzCt.js +5 -0
  65. package/dist/client/assets/dist-73RPdcyU.js +1 -0
  66. package/dist/client/assets/dist-BJ1bJs33.js +1 -0
  67. package/dist/client/assets/{dist-CYZmbokb.js → dist-BK9QvQrc.js} +9 -9
  68. package/dist/client/assets/dist-BPHLJ09L.js +11 -0
  69. package/dist/client/assets/dist-B_yQaEv_.js +6 -0
  70. package/dist/client/assets/dist-BeajGMhO.js +1 -0
  71. package/dist/client/assets/{dist-JYEDRrPT.js → dist-Bw3VIE_v.js} +1 -1
  72. package/dist/client/assets/dist-ByrG2qBA.js +1 -0
  73. package/dist/client/assets/dist-C5NgpdG5.js +1 -0
  74. package/dist/client/assets/dist-CH-sQu3r.js +1 -0
  75. package/dist/client/assets/dist-CKS_-are.js +1 -0
  76. package/dist/client/assets/{dist-MvcV4190.js → dist-CdmKGRGr.js} +1 -1
  77. package/dist/client/assets/{dist-DGb27Dl6.js → dist-CietwTVS.js} +1 -1
  78. package/dist/client/assets/dist-D37Hyu3h.js +9 -0
  79. package/dist/client/assets/dist-D3fehAZ2.js +2 -0
  80. package/dist/client/assets/dist-D5wCC32z.js +1 -0
  81. package/dist/client/assets/{dist-DCzuX1AF.js → dist-DDUn5zTz.js} +1 -1
  82. package/dist/client/assets/dist-DGEbEfw4.js +1 -0
  83. package/dist/client/assets/dist-DVy5woUy.js +1 -0
  84. package/dist/client/assets/{dist-3bgCp7iY.js → dist-DZAAWTG-.js} +1 -1
  85. package/dist/client/assets/{dist-Drp8uu5J.js → dist-Dir5dDKZ.js} +1 -1
  86. package/dist/client/assets/dist-Dl_tpDSr.js +1 -0
  87. package/dist/client/assets/dist-DuH3ZrHo.js +1 -0
  88. package/dist/client/assets/dist-DutPuFmC.js +1 -0
  89. package/dist/client/assets/{dist-D2AQ-76g.js → dist-LQ4kDCKz.js} +1 -1
  90. package/dist/client/assets/{dist-C11hHqv_.js → dist-RKBTaQMb.js} +12 -12
  91. package/dist/client/assets/dist-Tw-Fxpeb.js +1 -0
  92. package/dist/client/assets/dist-eBx8q6Sy.js +1 -0
  93. package/dist/client/assets/{dist-DK5Gs2wa.js → dist-mj2Ht75J.js} +1 -1
  94. package/dist/client/assets/{dist-CnL7Y2sM.js → dist-netZOQtD.js} +1 -1
  95. package/dist/client/assets/{drawio-editor-ClE8jpoG.js → drawio-editor-BjrxTe3l.js} +1 -1
  96. package/dist/client/assets/drawioEditor-DUVZjSSr.js +2 -0
  97. package/dist/client/assets/{ebnfDiagram-BXEA7PRR-nw0wmA6y.js → ebnfDiagram-PWID7BFC-OXa8VEp1.js} +1 -1
  98. package/dist/client/assets/editor-session-DfZuRgB_.js +54 -0
  99. package/dist/client/assets/embedded-editor-base-DvMynhKu.css +1 -0
  100. package/dist/client/assets/embedpdf-BmE13ZZ0.js +1 -0
  101. package/dist/client/assets/embedpdf-DA4bA119-BPqZxwjc.js +2363 -0
  102. package/dist/client/assets/{en-7NFCYGKX-BrgYZAEe.js → en-SJ2YIMFF-CJBL3nkq.js} +1 -1
  103. package/dist/client/assets/erDiagram-SSCWMZ5O-C-iy6VkX.js +99 -0
  104. package/dist/client/assets/es-CAsWR6hG.js +22 -0
  105. package/dist/client/assets/eventmodeling-NTZA5JFV-BR2FzK4R.js +1 -0
  106. package/dist/client/assets/excalidraw-editor-BlcN6umJ.css +1 -0
  107. package/dist/client/assets/excalidraw-editor-D7pnUCLH.js +9 -0
  108. package/dist/client/assets/excalidraw-scene-BOben2gr.js +1 -0
  109. package/dist/client/assets/excalidrawEditor-B0jOK3lv.js +2 -0
  110. package/dist/client/assets/export-page-Bs3xE1fg.js +57 -0
  111. package/dist/client/assets/exportDocument-Dg9MjkIk.js +2 -0
  112. package/dist/client/assets/exportDocument-PnYU3v1s.css +1 -0
  113. package/dist/client/assets/file-icon-svg-CQAjlWjK.js +1 -0
  114. package/dist/client/assets/file-kind-BbqgGzpc.js +1 -0
  115. package/dist/client/assets/flowDiagram-A5DVABFB-CiW1eN8C.js +1 -0
  116. package/dist/client/assets/{ganttDiagram-PKOTCBZU-BnM97BCT.js → ganttDiagram-EL5Y4UJY-DtKHAbez.js} +1 -1
  117. package/dist/client/assets/gitGraph-4MIJSDKK-DYlXv_yJ.js +1 -0
  118. package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-BQpQhjuN.js → gitGraphDiagram-WWUBYQGX-oue8fQ5o.js} +1 -1
  119. package/dist/client/assets/github-dark.min-BV82VuKq.css +1 -0
  120. package/dist/client/assets/github.min-v3dd8CNH.css +1 -0
  121. package/dist/client/assets/image-ZVKINSPA-0zXSnE7M.js +1 -0
  122. package/dist/client/assets/index-BIpgxyGZ.css +1 -0
  123. package/dist/client/assets/index-Z5sI0Bj-.js +2 -0
  124. package/dist/client/assets/info-A6RAGUB7-BlU-8_xu.js +1 -0
  125. package/dist/client/assets/{infoDiagram-6WML65LV-DYB8HCDQ.js → infoDiagram-RXCK75RN-BMzo6KIn.js} +1 -1
  126. package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-Coz_P3t6.js → ishikawaDiagram-5VMMS53U-BIM3h287.js} +1 -1
  127. package/dist/client/assets/{journeyDiagram-NVQOT4AX-B6edChce.js → journeyDiagram-EYS64GPL-mF6Lh8O-.js} +1 -1
  128. package/dist/client/assets/{kanban-definition-27J2QSJJ-BFd9-cLc.js → kanban-definition-3QL26DDD-DLN2NyoR.js} +2 -2
  129. package/dist/client/assets/{line-B_S_S8kV.js → line-BfTWJmgf.js} +1 -1
  130. package/dist/client/assets/main-Dwnj9Gaq.js +903 -0
  131. package/dist/client/assets/{mermaid-parser.core-CUVdebAV.js → mermaid-parser.core-Bl0efOMt.js} +2 -2
  132. package/dist/client/assets/mermaid.core-CLZBlpIW.js +11 -0
  133. package/dist/client/assets/{mindmap-definition-FAOFIHXS-C9PQ38Al.js → mindmap-definition-FBJOCRG2-CRyHaHTk.js} +1 -1
  134. package/dist/client/assets/packet-AYTQ26CC-BSJEH9Lb.js +1 -0
  135. package/dist/client/assets/pdfium-RAgkpwfK.wasm +0 -0
  136. package/dist/client/assets/{pegDiagram-VL7TDLO6-BMZUrSaU.js → pegDiagram-XKGWAZYB-B31fKPR2.js} +1 -1
  137. package/dist/client/assets/pie-WAS4IAKB-qxv01Y8W.js +1 -0
  138. package/dist/client/assets/{pieDiagram-7S7Q4E2Y-h8kbCL9o.js → pieDiagram-E7YTZNPT-B5OZPb88.js} +1 -1
  139. package/dist/client/assets/preview-render-compiler-BApvlLLo.js +35 -0
  140. package/dist/client/assets/preview-render-compiler-BQ_3h9vD.js +1 -0
  141. package/dist/client/assets/preview-render-profile-CE-5rVTC.js +1 -0
  142. package/dist/client/assets/preview-render-worker-BmN79bVc.js +60 -0
  143. package/dist/client/assets/prod-BiYPMR1i.js +177 -0
  144. package/dist/client/assets/prod-VxdGZ5Ly.js +1 -0
  145. package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-CkElyfMi.js → quadrantDiagram-AXDQQJYC-Drrw14YH.js} +1 -1
  146. package/dist/client/assets/quick-switcher-controller-ButWPmCI.js +12 -0
  147. package/dist/client/assets/radar-RG4KPBEZ-B6h6pWOs.js +1 -0
  148. package/dist/client/assets/railroad-74A4TZTK-CGtTWdGc.js +1 -0
  149. package/dist/client/assets/railroad-abnf-HS5TGJTU-Cq7y22IA.js +1 -0
  150. package/dist/client/assets/railroad-ebnf-LZEXJU2U-V0aQVv9m.js +1 -0
  151. package/dist/client/assets/railroad-peg-WCYAUIDC-BQiIsX_p.js +1 -0
  152. package/dist/client/assets/{railroadDiagram-AXF67PYL-c75gnikD.js → railroadDiagram-O6MQD6OU-BPifP0MB.js} +1 -1
  153. package/dist/client/assets/{requirementDiagram-LRYGKXZP-mNxKLuHS.js → requirementDiagram-EFPCY7ZU-CRZG6mRB.js} +2 -2
  154. package/dist/client/assets/runtime-config-BLrEKVO1.js +1 -0
  155. package/dist/client/assets/{sankeyDiagram-W5VNT64P-0OhplksI.js → sankeyDiagram-P5KCCOFB-DvarEJpo.js} +1 -1
  156. package/dist/client/assets/{sequenceDiagram-SI44F4Z6-BJ_yAocC.js → sequenceDiagram-WJ2MYXX4-B4oWlb-w.js} +1 -1
  157. package/dist/client/assets/{stateDiagram-OKZ733FA-Cc83O86s.js → stateDiagram-HBIQ2CUA-qbHIIqRZ.js} +1 -1
  158. package/dist/client/assets/stateDiagram-v2-4QOOHH4V-B--gjrit.js +1 -0
  159. package/dist/client/assets/swimlanes-XN3QIQJK-xB6CtUGj.js +1 -0
  160. package/dist/client/assets/swimlanesDiagram-VK2B7HYN-DO8XNXEg.js +8 -0
  161. package/dist/client/assets/{timeline-definition-Z64GVDOM-BWDAGTHz.js → timeline-definition-24CTP7MA-Lj_yTNlY.js} +1 -1
  162. package/dist/client/assets/treeView-Q6P3EWNA-BTTYcvrF.js +1 -0
  163. package/dist/client/assets/treemap-WGGIJYW6-Cg7tyfH8.js +1 -0
  164. package/dist/client/assets/vault-api-client-_LpV1aIn.js +6 -0
  165. package/dist/client/assets/{vennDiagram-T6HMQDX7-LOSsnlMB.js → vennDiagram-4TSXK5OY-BekRp5Y0.js} +1 -1
  166. package/dist/client/assets/wardley-WFR3VGLG-C6N1rULY.js +1 -0
  167. package/dist/client/assets/{wardleyDiagram-T6FBY63Y-BxII3foY.js → wardleyDiagram-VM6X3IG4-kdnV_Hdm.js} +1 -1
  168. package/dist/client/assets/worker-engine-m0rlHn8R-S8mpn_O0.js +17369 -0
  169. package/dist/client/assets/{xychartDiagram-ELKLHX3M-B-pTEjju.js → xychartDiagram-S5SC5T6Z-DufGwNSB.js} +3 -3
  170. package/dist/client/drawio-editor.html +1 -1
  171. package/dist/client/excalidraw-editor.html +1 -1
  172. package/dist/client/export-document.html +3 -3
  173. package/dist/client/index.html +30 -8
  174. package/docker-compose.yml +42 -0
  175. package/package.json +32 -18
  176. package/scripts/local-structurizr-compose.mjs +101 -0
  177. package/src/client/app/index.html +27 -5
  178. package/src/client/application/app-shell/comments-feature.js +21 -13
  179. package/src/client/application/app-shell/git-feature.js +14 -2
  180. package/src/client/application/app-shell/presence-feature.js +4 -3
  181. package/src/client/application/app-shell/ui-feature-shell.js +109 -2
  182. package/src/client/application/app-shell/ui-feature-toolbar.js +2 -52
  183. package/src/client/application/app-shell/workspace-feature.js +5 -1
  184. package/src/client/application/app-shell-elements.js +3 -0
  185. package/src/client/application/pdf-preview-controller.js +75 -0
  186. package/src/client/application/preview-render-compiler.js +3 -0
  187. package/src/client/application/quick-switcher-loader.js +1 -1
  188. package/src/client/application/structurizr-preview-controller.js +395 -0
  189. package/src/client/application/workspace-coordinator.js +25 -3
  190. package/src/client/application/workspace-preview-controller.js +88 -2
  191. package/src/client/assets/vendor/highlight/github-dark.min.css +2 -10
  192. package/src/client/assets/vendor/highlight/github.min.css +2 -10
  193. package/src/client/bootstrap/collabmd-app-shell.js +39 -6
  194. package/src/client/browser-utils.js +44 -0
  195. package/src/client/domain/runtime-paths.js +26 -5
  196. package/src/client/domain/structurizr-language.js +99 -0
  197. package/src/client/domain/vault-paths.js +48 -0
  198. package/src/client/excalidraw-editor.js +844 -51
  199. package/src/client/export/export-host.js +1 -1
  200. package/src/client/export/export-page.js +6 -2
  201. package/src/client/export/export-pipeline.js +60 -1
  202. package/src/client/infrastructure/browser-preferences-port.js +10 -0
  203. package/src/client/infrastructure/comment-thread-store.js +34 -13
  204. package/src/client/infrastructure/editor-session.js +10 -0
  205. package/src/client/infrastructure/editor-view-adapter.js +46 -2
  206. package/src/client/infrastructure/excalidraw-room-client.js +103 -14
  207. package/src/client/infrastructure/git-api-client.js +5 -0
  208. package/src/client/infrastructure/vault-api-client.js +26 -0
  209. package/src/client/presentation/comment-ui/comment-ui-render.js +4 -0
  210. package/src/client/presentation/comment-ui/comment-ui-shared.js +4 -0
  211. package/src/client/presentation/comment-ui-controller.js +2 -0
  212. package/src/client/presentation/drawio-embed-controller.js +9 -102
  213. package/src/client/presentation/drawio-viewer.js +118 -0
  214. package/src/client/presentation/excalidraw-embed-controller.js +81 -8
  215. package/src/client/presentation/file-action-controller.js +102 -0
  216. package/src/client/presentation/file-explorer-controller.js +0 -5
  217. package/src/client/presentation/file-explorer-view.js +51 -73
  218. package/src/client/presentation/file-icon-svg.js +40 -0
  219. package/src/client/presentation/file-tree-state.js +6 -3
  220. package/src/client/presentation/git-diff-view-controller.js +731 -3
  221. package/src/client/presentation/git-panel-controller.js +2 -2
  222. package/src/client/presentation/outline-controller.js +86 -0
  223. package/src/client/presentation/quick-switcher-controller.js +73 -22
  224. package/src/client/presentation/quick-switcher-text-search.js +1 -1
  225. package/src/client/styles/components/navigation.css +11 -1
  226. package/src/client/styles/features/diagram-preview.css +76 -0
  227. package/src/client/styles/features/diff-special.css +309 -0
  228. package/src/client/styles/features/preview-markdown.css +25 -0
  229. package/src/client/styles/features/quick-switcher.css +8 -0
  230. package/src/client/styles/foundation/themes.css +7 -0
  231. package/src/client/styles/layout/shell.css +40 -1
  232. package/src/client/styles/layout/sidebar.css +42 -4
  233. package/src/client/styles/layout/view-modes.css +1 -0
  234. package/src/client/styles/overrides/highlightjs.css +20 -4
  235. package/src/client/styles/style.css +1 -0
  236. package/src/client/styles/surfaces/excalidraw-editor.css +294 -0
  237. package/src/domain/comment-threads.js +63 -5
  238. package/src/domain/excalidraw-room-codec.js +6 -4
  239. package/src/domain/file-kind.js +35 -3
  240. package/src/server/config/env.js +79 -0
  241. package/src/server/create-app-server.js +12 -0
  242. package/src/server/domain/bases/base-definition.js +1 -1
  243. package/src/server/domain/bases/base-expression-runtime.js +1 -4
  244. package/src/server/domain/bases/base-index-snapshot-store.js +1 -4
  245. package/src/server/domain/bases/base-transform.js +1 -4
  246. package/src/server/domain/collaboration/collaboration-room.js +55 -21
  247. package/src/server/domain/comment-overview.js +15 -5
  248. package/src/server/domain/ripgrep-search-service.js +4 -0
  249. package/src/server/infrastructure/git/diff-service.js +5 -0
  250. package/src/server/infrastructure/git/git-service.js +44 -11
  251. package/src/server/infrastructure/git/history-service.js +54 -0
  252. package/src/server/infrastructure/git/image-mime.js +14 -0
  253. package/src/server/infrastructure/git/untracked-files.js +28 -1
  254. package/src/server/infrastructure/http/create-git-api-command-handler.js +3 -1
  255. package/src/server/infrastructure/http/create-git-api-query-handler.js +31 -1
  256. package/src/server/infrastructure/http/create-request-handler.js +16 -23
  257. package/src/server/infrastructure/http/create-static-handler.js +5 -2
  258. package/src/server/infrastructure/http/create-structurizr-api-handler.js +147 -0
  259. package/src/server/infrastructure/http/create-vault-api-command-handler.js +66 -4
  260. package/src/server/infrastructure/http/create-vault-api-query-handler.js +51 -3
  261. package/src/server/infrastructure/http/http-request-helpers.js +22 -0
  262. package/src/server/infrastructure/persistence/path-utils.js +1 -1
  263. package/src/server/infrastructure/persistence/vault-file-store.js +3 -0
  264. package/src/server/infrastructure/structurizr/structurizr-embed.css +43 -0
  265. package/src/server/infrastructure/structurizr/structurizr-workspace-service.js +400 -0
  266. package/src/server/infrastructure/websocket/attach-collaboration-gateway.js +4 -22
  267. package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-CbSISC1w.js +0 -1
  268. package/dist/client/assets/architecture-TIHT7OUA-CsHfPErM.js +0 -1
  269. package/dist/client/assets/base-CxPEiHuy.js +0 -1
  270. package/dist/client/assets/blockDiagram-VBNYF7ZC-CipJt9Jl.js +0 -132
  271. package/dist/client/assets/browser-utils-CKtI_rZl.js +0 -1
  272. package/dist/client/assets/c4Diagram-5PPSVZJV-B5ktVx6g.js +0 -10
  273. package/dist/client/assets/channel-4Jkmq93I.js +0 -1
  274. package/dist/client/assets/chunk-7BUUIJ7U-Bb538aSH.js +0 -1
  275. package/dist/client/assets/chunk-7Z6QIM7H-BP9Pdsm9.js +0 -10
  276. package/dist/client/assets/chunk-ERSWUTIN-ME9RVNel.js +0 -41
  277. package/dist/client/assets/chunk-I66GZJ75-CVFiiffQ.js +0 -127
  278. package/dist/client/assets/chunk-J7OUQ5F2-4CEb7pvH.js +0 -2
  279. package/dist/client/assets/chunk-JQJVKLGR-cx8X_24v.js +0 -156
  280. package/dist/client/assets/chunk-QR6OTTB3-CDAA_5Ib.js +0 -62
  281. package/dist/client/assets/chunk-RYQCIY6F-D-a0zH8K.js +0 -1
  282. package/dist/client/assets/chunk-UBXNYLIW-Bhk-PbFK.js +0 -1
  283. package/dist/client/assets/chunk-W5SLKNZC-BdNSCxZ9.js +0 -1
  284. package/dist/client/assets/chunk-WRU74C26-h6_XkD3_.js +0 -70
  285. package/dist/client/assets/chunk-ZIRB5QZD-O39ZBUk3.js +0 -32
  286. package/dist/client/assets/classDiagram-JCYQIIEL-L7lsyW_w.js +0 -1
  287. package/dist/client/assets/classDiagram-v2-OCEON4UE-L7lsyW_w.js +0 -1
  288. package/dist/client/assets/comment-threads-DDegmaFO.js +0 -4
  289. package/dist/client/assets/cynefin-VYW2F7L2-Ds2Fw2Y0.js +0 -1
  290. package/dist/client/assets/dagre-Buvkdvvj.js +0 -1
  291. package/dist/client/assets/dagre-VZM6K2ZE-L1xArOGs.js +0 -4
  292. package/dist/client/assets/dist-BH87JoHx.js +0 -11
  293. package/dist/client/assets/dist-BIkPRZO7.js +0 -6
  294. package/dist/client/assets/dist-BqCsFpx4.js +0 -1
  295. package/dist/client/assets/dist-C2JHCt8U.js +0 -1
  296. package/dist/client/assets/dist-CZjTtmTu.js +0 -1
  297. package/dist/client/assets/dist-Cig4Ov_X.js +0 -1
  298. package/dist/client/assets/dist-CqQEC8gR.js +0 -1
  299. package/dist/client/assets/dist-D4Rzqod1.js +0 -1
  300. package/dist/client/assets/dist-D5sn-mwk.js +0 -9
  301. package/dist/client/assets/dist-D7G-n87v.js +0 -1
  302. package/dist/client/assets/dist-DRqOrNgN.js +0 -1
  303. package/dist/client/assets/dist-DetkMS_4.js +0 -1
  304. package/dist/client/assets/dist-DhLvC8DS.js +0 -2
  305. package/dist/client/assets/dist-Dp6FVkQ1.js +0 -1
  306. package/dist/client/assets/dist-DtbjNB28.js +0 -1
  307. package/dist/client/assets/dist-DyrYcvpo2.js +0 -1
  308. package/dist/client/assets/dist-GrygD2zE.js +0 -1
  309. package/dist/client/assets/dist-HmEpWUow.js +0 -1
  310. package/dist/client/assets/dist-RTa81Km7.js +0 -1
  311. package/dist/client/assets/drawioEditor-CXaTM9Ds.js +0 -2
  312. package/dist/client/assets/editor-session-GTwZS6Df.js +0 -16
  313. package/dist/client/assets/embedded-editor-base-CiNEc8vy.css +0 -1
  314. package/dist/client/assets/erDiagram-JOGREHBK-cYUpEKpf.js +0 -85
  315. package/dist/client/assets/eventmodeling-45OFAUF4-CLEiVx5s.js +0 -1
  316. package/dist/client/assets/excalidraw-editor-BZVM81BO.js +0 -9
  317. package/dist/client/assets/excalidraw-editor-CyGwOhLz.css +0 -1
  318. package/dist/client/assets/excalidraw-scene-C8n9CHsK.js +0 -176
  319. package/dist/client/assets/excalidrawEditor-DxJanCze.js +0 -2
  320. package/dist/client/assets/export-page-CUfws2T-.js +0 -39
  321. package/dist/client/assets/exportDocument-BCuxqXDY.js +0 -2
  322. package/dist/client/assets/exportDocument-DV-OX74E.css +0 -1
  323. package/dist/client/assets/file-kind-BKPOIRPE.js +0 -1
  324. package/dist/client/assets/flowDiagram-UKHOOZJN-D0ypP0Ez.js +0 -1
  325. package/dist/client/assets/gitGraph-TEB2WS4Q-CZFBqwW-.js +0 -1
  326. package/dist/client/assets/github-dark.min-Z5exjX_n.css +0 -1
  327. package/dist/client/assets/github.min-D2BCvnWf.css +0 -1
  328. package/dist/client/assets/image-QXRYHUNP-lfTHE3bb.js +0 -1
  329. package/dist/client/assets/index-C3YnUgMD.css +0 -1
  330. package/dist/client/assets/index-D2wXraJ1.js +0 -2
  331. package/dist/client/assets/info-DKCQHKI2--1InqQeT.js +0 -1
  332. package/dist/client/assets/main-DtiGz7uI.js +0 -795
  333. package/dist/client/assets/map-BaFkSB1l.js +0 -1
  334. package/dist/client/assets/mermaid.core-DN-lF51U.js +0 -11
  335. package/dist/client/assets/packet-7NZHBO7P-BGMXB0XL.js +0 -1
  336. package/dist/client/assets/pie-RZYD4A2V-DgYg3tCt.js +0 -1
  337. package/dist/client/assets/preview-render-compiler-BSaQO_p3.js +0 -35
  338. package/dist/client/assets/preview-render-compiler-csGM5ZFz.js +0 -1
  339. package/dist/client/assets/preview-render-profile-BZzGSg6K.js +0 -22
  340. package/dist/client/assets/preview-render-worker-B42LWvrD.js +0 -60
  341. package/dist/client/assets/quick-switcher-controller-shW_6vPY.js +0 -12
  342. package/dist/client/assets/radar-I7S5WNFK-Ag-zp_Rq.js +0 -1
  343. package/dist/client/assets/railroad-3IZDKUUU-aibJDIDV.js +0 -1
  344. package/dist/client/assets/railroad-abnf-AHOZXSZD-BBMEuvIl.js +0 -1
  345. package/dist/client/assets/railroad-ebnf-EBAXGLYW-Be1nseej.js +0 -1
  346. package/dist/client/assets/railroad-peg-LSFZ7HO6-DIvD-Eyb.js +0 -1
  347. package/dist/client/assets/room-CQLoqtMo.js +0 -1
  348. package/dist/client/assets/runtime-config-DSGmGE9V.js +0 -1
  349. package/dist/client/assets/stateDiagram-v2-UEYNNEHI-B1-BufUk.js +0 -1
  350. package/dist/client/assets/swimlanes-SLNWSIFB-oVTWEJIj.js +0 -2
  351. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-C5OTVwEu.js +0 -8
  352. package/dist/client/assets/treeView-QDETBFTQ-uHZ-3Fz8.js +0 -1
  353. package/dist/client/assets/treemap-6X3UGDF4-BnUKyy3l.js +0 -1
  354. package/dist/client/assets/vault-api-client-C6bcGwy-.js +0 -5
  355. package/dist/client/assets/wardley-OPB4EBWU-Da7TLC2F.js +0 -1
  356. package/src/server/domain/collaboration/room-persistence-controller.js +0 -80
@@ -4,9 +4,14 @@ import { createGitApiQueryHandler } from './create-git-api-query-handler.js';
4
4
  import { createHostedApiHandler } from './create-hosted-api-handler.js';
5
5
  import { createPlantUmlApiHandler } from './create-plantuml-api-handler.js';
6
6
  import { createStaticHandler } from './create-static-handler.js';
7
+ import { createStructurizrApiHandler } from './create-structurizr-api-handler.js';
7
8
  import { createVaultApiCommandHandler } from './create-vault-api-command-handler.js';
8
9
  import { createVaultApiQueryHandler } from './create-vault-api-query-handler.js';
9
10
  import { parseJsonBody } from './request-body.js';
11
+ import {
12
+ createRequestUrlWithPathname,
13
+ stripBasePath,
14
+ } from './http-request-helpers.js';
10
15
  import {
11
16
  applyCorsHeaders,
12
17
  jsonResponse,
@@ -16,28 +21,6 @@ import {
16
21
  WRITE_METHODS,
17
22
  } from './http-response.js';
18
23
 
19
- function stripBasePath(pathname, basePath) {
20
- if (!basePath) {
21
- return pathname;
22
- }
23
-
24
- if (pathname === basePath) {
25
- return '/';
26
- }
27
-
28
- if (pathname.startsWith(`${basePath}/`)) {
29
- return pathname.slice(basePath.length) || '/';
30
- }
31
-
32
- return pathname;
33
- }
34
-
35
- function createRequestUrlWithPathname(requestUrl, pathname) {
36
- const nextUrl = new URL(requestUrl.toString());
37
- nextUrl.pathname = pathname || '/';
38
- return nextUrl;
39
- }
40
-
41
24
  export function createRequestHandler(
42
25
  config,
43
26
  authService,
@@ -54,6 +37,7 @@ export function createRequestHandler(
54
37
  fileSystemSyncService = null,
55
38
  hostedWorkspaceService = null,
56
39
  githubSetupFlow = null,
40
+ structurizrWorkspaceService = null,
57
41
  ) {
58
42
  const handleStaticRequest = createStaticHandler(config, authService, searchService);
59
43
  const handleAuthApi = createAuthApiHandler({ authService });
@@ -78,12 +62,17 @@ export function createRequestHandler(
78
62
  });
79
63
  const handleVaultApiCommand = createVaultApiCommandHandler({
80
64
  backlinkIndex,
65
+ maxPdfUploadBytes: config.maxPdfUploadBytes,
81
66
  renderDocx,
82
67
  roomRegistry,
83
68
  vaultFileStore,
84
69
  workspaceMutationCoordinator,
85
70
  });
86
71
  const handlePlantUmlApi = createPlantUmlApiHandler({ plantUmlRenderer });
72
+ const handleStructurizrApi = createStructurizrApiHandler({
73
+ basePath: config.basePath,
74
+ service: structurizrWorkspaceService,
75
+ });
87
76
 
88
77
  function handleBasePathRedirect(req, res, originalRequestUrl) {
89
78
  if (
@@ -193,7 +182,7 @@ export function createRequestHandler(
193
182
  return;
194
183
  }
195
184
 
196
- if (requestUrl.pathname.startsWith('/api/')) {
185
+ if (requestUrl.pathname.startsWith('/api/') || handleStructurizrApi.requiresAuthorization(requestUrl)) {
197
186
  const authorization = authService.authorizeApiRequest(req);
198
187
  if (!authorization.ok) {
199
188
  jsonResponse(req, res, authorization.statusCode, authorization.body);
@@ -209,6 +198,10 @@ export function createRequestHandler(
209
198
  }
210
199
  }
211
200
 
201
+ if (await handleStructurizrApi(req, res, requestUrl)) {
202
+ return;
203
+ }
204
+
212
205
  if (requestUrl.pathname.startsWith('/api/')) {
213
206
  if (await handleVaultApiQuery(req, res, requestUrl)) {
214
207
  return;
@@ -7,12 +7,13 @@ const CONTENT_TYPES = {
7
7
  '.css': 'text/css; charset=utf-8',
8
8
  '.html': 'text/html; charset=utf-8',
9
9
  '.js': 'text/javascript; charset=utf-8',
10
+ '.mjs': 'text/javascript; charset=utf-8',
10
11
  '.json': 'application/json; charset=utf-8',
11
12
  '.svg': 'image/svg+xml',
12
13
  };
13
14
 
14
15
  function isVersionedAssetPath(pathname = '') {
15
- return /-[A-Za-z0-9_-]{8,}\.(?:css|js|woff2?|ttf|svg)$/u.test(pathname);
16
+ return /-[A-Za-z0-9_-]{8,}\.(?:css|js|mjs|woff2?|ttf|svg)$/u.test(pathname);
16
17
  }
17
18
 
18
19
  function getStaticCacheControl(pathname = '', extension = '') {
@@ -33,7 +34,7 @@ function getStaticCacheControl(pathname = '', extension = '') {
33
34
  return 'public, max-age=31536000, immutable';
34
35
  }
35
36
 
36
- if (extension === '.css' || extension === '.js') {
37
+ if (extension === '.css' || extension === '.js' || extension === '.mjs') {
37
38
  return 'public, max-age=0, must-revalidate';
38
39
  }
39
40
 
@@ -53,6 +54,7 @@ function buildRuntimeConfig({
53
54
  nodeEnv,
54
55
  publicWsBaseUrl,
55
56
  search,
57
+ structurizr,
56
58
  wikiLinkAutoCreate,
57
59
  wsBasePath,
58
60
  }) {
@@ -68,6 +70,7 @@ function buildRuntimeConfig({
68
70
  },
69
71
  publicWsBaseUrl,
70
72
  search,
73
+ structurizrEnabled: Boolean(structurizr?.enabled ?? structurizr?.serverUrl),
71
74
  wikiLinkAutoCreate,
72
75
  wsBasePath,
73
76
  })};\n`;
@@ -0,0 +1,147 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ import { getRequestErrorStatusCode } from './http-errors.js';
4
+ import { jsonResponse, sendResponse, textResponse } from './http-response.js';
5
+ import { parseJsonBody, readBinaryRequestBody } from './request-body.js';
6
+
7
+ const EMBED_STYLESHEET_PATH = new URL('../structurizr/structurizr-embed.css', import.meta.url);
8
+
9
+ function isSyncRequest(requestUrl, req) {
10
+ return requestUrl.pathname === '/api/structurizr/sync' && req.method === 'POST';
11
+ }
12
+
13
+ function isEmbedStylesheetRequest(requestUrl) {
14
+ return requestUrl.pathname === '/api/structurizr/embed.css';
15
+ }
16
+
17
+ function rewriteBasePath(body, contentType, basePath, { injectEmbedStylesheet = false } = {}) {
18
+ if (!/(?:text\/(?:html|css)|javascript|ecmascript)/iu.test(contentType)) {
19
+ return body;
20
+ }
21
+
22
+ let source = body.toString('utf8');
23
+ if (basePath) {
24
+ source = source.replace(
25
+ /(["'`(=])\/(static|workspace|api\/workspace)(?=[/"'`?#)])/gu,
26
+ `$1${basePath}/$2`,
27
+ );
28
+ }
29
+
30
+ if (injectEmbedStylesheet && /text\/html/iu.test(contentType)) {
31
+ const normalizedBasePath = String(basePath || '').replace(/\/+$/u, '');
32
+ const stylesheetPath = `${normalizedBasePath}/api/structurizr/embed.css`;
33
+ source = source.replace(
34
+ /<head(?:\s[^>]*)?>/iu,
35
+ (head) => `${head}\n<link rel="stylesheet" href="${stylesheetPath}">`,
36
+ );
37
+ }
38
+
39
+ return Buffer.from(source);
40
+ }
41
+
42
+ export function createStructurizrApiHandler({ basePath = '', service = null } = {}) {
43
+ async function handleRequest(req, res, requestUrl) {
44
+ if (isSyncRequest(requestUrl, req)) {
45
+ try {
46
+ const body = await parseJsonBody(req);
47
+ if (!body || typeof body.path !== 'string') {
48
+ jsonResponse(req, res, 400, { error: 'Missing Structurizr workspace path' });
49
+ return true;
50
+ }
51
+
52
+ if (!service?.isEnabled()) {
53
+ jsonResponse(req, res, 503, { error: 'Structurizr renderer is not configured' });
54
+ return true;
55
+ }
56
+
57
+ jsonResponse(req, res, 200, await service.sync({
58
+ content: body.source,
59
+ rootPath: body.path,
60
+ }));
61
+ } catch (error) {
62
+ const statusCode = getRequestErrorStatusCode(error) || Number(error?.statusCode) || 502;
63
+ if (statusCode >= 500) {
64
+ console.error('[api] Failed to sync Structurizr workspace:', error.message);
65
+ }
66
+ jsonResponse(req, res, statusCode, {
67
+ code: error?.requestCode,
68
+ error: error instanceof Error ? error.message : 'Failed to sync Structurizr workspace',
69
+ });
70
+ }
71
+ return true;
72
+ }
73
+
74
+ if (isEmbedStylesheetRequest(requestUrl)) {
75
+ if (!service?.isEnabled()) {
76
+ return false;
77
+ }
78
+
79
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
80
+ textResponse(req, res, 405, 'Method Not Allowed', { Allow: 'GET, HEAD' });
81
+ return true;
82
+ }
83
+
84
+ try {
85
+ sendResponse(req, res, {
86
+ body: await readFile(EMBED_STYLESHEET_PATH),
87
+ headers: {
88
+ 'Cache-Control': 'no-store',
89
+ 'Content-Type': 'text/css; charset=utf-8',
90
+ },
91
+ statusCode: 200,
92
+ });
93
+ } catch (error) {
94
+ console.error('[http] Failed to serve Structurizr embed stylesheet:', error.message);
95
+ textResponse(req, res, 500, 'Internal Server Error');
96
+ }
97
+ return true;
98
+ }
99
+
100
+ if (!service?.isProxyPath(requestUrl.pathname)) {
101
+ return false;
102
+ }
103
+
104
+ const isThumbnailPut = req.method === 'PUT' && service.isThumbnailPath(requestUrl.pathname);
105
+ if (req.method !== 'GET' && req.method !== 'HEAD' && !isThumbnailPut) {
106
+ textResponse(req, res, 405, 'Method Not Allowed', { Allow: 'GET, HEAD' });
107
+ return true;
108
+ }
109
+
110
+ try {
111
+ const response = await service.proxy(requestUrl, {
112
+ body: isThumbnailPut ? await readBinaryRequestBody(req) : null,
113
+ headers: req.headers,
114
+ method: req.method,
115
+ });
116
+ if (!response) {
117
+ return false;
118
+ }
119
+
120
+ sendResponse(req, res, {
121
+ body: rewriteBasePath(response.body, response.contentType, basePath, {
122
+ injectEmbedStylesheet: requestUrl.pathname === '/workspace/1/diagrams',
123
+ }),
124
+ headers: {
125
+ 'Cache-Control': requestUrl.pathname.startsWith('/static/')
126
+ ? 'public, max-age=31536000, immutable'
127
+ : 'no-store',
128
+ 'Content-Type': response.contentType,
129
+ },
130
+ statusCode: response.statusCode,
131
+ });
132
+ } catch (error) {
133
+ const statusCode = getRequestErrorStatusCode(error) || Number(error?.statusCode) || 502;
134
+ console.error('[http] Failed to proxy Structurizr request:', error.message);
135
+ textResponse(req, res, statusCode, error.message || 'Structurizr renderer unavailable');
136
+ }
137
+
138
+ return true;
139
+ }
140
+
141
+ handleRequest.requiresAuthorization = (requestUrl) => (
142
+ Boolean(service?.isProxyPath(requestUrl.pathname))
143
+ && !requestUrl.pathname.startsWith('/api/')
144
+ );
145
+
146
+ return handleRequest;
147
+ }
@@ -1,4 +1,5 @@
1
1
  import { basename } from 'node:path';
2
+ import { isPdfFilePath } from '../../../domain/file-kind.js';
2
3
  import { createRequestError } from './http-errors.js';
3
4
  import { handleApiError, readRequestId } from './http-request-helpers.js';
4
5
  import {
@@ -7,10 +8,15 @@ import {
7
8
  jsonResponse,
8
9
  sendResponse,
9
10
  } from './http-response.js';
10
- import { parseJsonBody, readBinaryRequestBody } from './request-body.js';
11
+ import {
12
+ parseJsonBody,
13
+ readBinaryRequestBody,
14
+ REQUEST_BODY_LIMIT_BYTES,
15
+ } from './request-body.js';
11
16
 
12
17
  const DOCX_EXPORT_REQUEST_LIMIT_BYTES = 33_554_432;
13
18
  const DOCX_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
19
+ const PDF_SIGNATURE = Buffer.from('%PDF-');
14
20
 
15
21
  function createDocxDownloadHeaders(filePath) {
16
22
  const fileName = basename(String(filePath ?? 'document')).replace(/\.[^.]+$/u, '') || 'document';
@@ -23,7 +29,7 @@ function createDocxDownloadHeaders(filePath) {
23
29
  };
24
30
  }
25
31
 
26
- function decodeHeaderMetadata(value) {
32
+ function decodeHeaderMetadata(value, label = 'attachment') {
27
33
  const normalized = String(value ?? '').trim();
28
34
  if (!normalized) {
29
35
  return '';
@@ -32,7 +38,7 @@ function decodeHeaderMetadata(value) {
32
38
  try {
33
39
  return decodeURIComponent(normalized);
34
40
  } catch {
35
- throw createRequestError(400, 'Invalid attachment metadata header encoding');
41
+ throw createRequestError(400, `Invalid ${label} metadata header encoding`);
36
42
  }
37
43
  }
38
44
 
@@ -129,6 +135,55 @@ async function handleUploadAttachment({ workspaceMutationCoordinator }, req, res
129
135
  return true;
130
136
  }
131
137
 
138
+ async function handleUploadFile({ maxPdfUploadBytes, workspaceMutationCoordinator }, req, res) {
139
+ try {
140
+ const filePath = decodeHeaderMetadata(req.headers['x-collabmd-file-path'], 'file upload');
141
+ if (!filePath) {
142
+ jsonResponse(req, res, 400, { error: 'Missing file path' });
143
+ return true;
144
+ }
145
+
146
+ if (typeof workspaceMutationCoordinator?.createFile !== 'function') {
147
+ jsonResponse(req, res, 503, { error: 'File upload is unavailable' });
148
+ return true;
149
+ }
150
+
151
+ const isPdfUpload = isPdfFilePath(filePath);
152
+ if (isPdfUpload) {
153
+ const contentType = String(req.headers['content-type'] || '')
154
+ .split(';', 1)[0]
155
+ .trim()
156
+ .toLowerCase();
157
+ if (contentType !== 'application/pdf') {
158
+ jsonResponse(req, res, 400, { error: 'PDF uploads must use application/pdf' });
159
+ return true;
160
+ }
161
+ }
162
+
163
+ const maxUploadBytes = isPdfUpload ? maxPdfUploadBytes : REQUEST_BODY_LIMIT_BYTES;
164
+ const content = await readBinaryRequestBody(req, maxUploadBytes);
165
+ if (isPdfUpload && !content.subarray(0, PDF_SIGNATURE.length).equals(PDF_SIGNATURE)) {
166
+ jsonResponse(req, res, 400, { error: 'Invalid PDF file' });
167
+ return true;
168
+ }
169
+
170
+ const result = await workspaceMutationCoordinator.createFile({
171
+ content,
172
+ path: filePath,
173
+ requestId: readRequestId(req),
174
+ });
175
+ if (!result.ok) {
176
+ jsonResponse(req, res, result.error === 'File already exists' ? 409 : 400, { error: result.error });
177
+ return true;
178
+ }
179
+
180
+ jsonResponse(req, res, 201, { ok: true, path: filePath });
181
+ } catch (error) {
182
+ handleApiError(req, res, error, '[api] Failed to upload file:', 'Failed to upload file');
183
+ }
184
+ return true;
185
+ }
186
+
132
187
  async function handleCreateFile({ workspaceMutationCoordinator }, req, res) {
133
188
  try {
134
189
  const body = await parseJsonBody(req);
@@ -280,6 +335,7 @@ const ROUTE_TABLE = [
280
335
  { method: 'POST', path: '/api/export/docx', handler: handleExportDocx },
281
336
  { method: 'PUT', path: '/api/file', handler: handleWriteFile },
282
337
  { method: 'POST', path: '/api/attachments', handler: handleUploadAttachment },
338
+ { method: 'POST', path: '/api/file/upload', handler: handleUploadFile },
283
339
  { method: 'POST', path: '/api/file', handler: handleCreateFile },
284
340
  { method: 'DELETE', path: '/api/file', handler: handleDeleteFile },
285
341
  { method: 'PATCH', path: '/api/file', handler: handleRenameFile },
@@ -289,11 +345,17 @@ const ROUTE_TABLE = [
289
345
  ];
290
346
 
291
347
  export function createVaultApiCommandHandler({
348
+ maxPdfUploadBytes = REQUEST_BODY_LIMIT_BYTES,
292
349
  renderDocx = null,
293
350
  vaultFileStore,
294
351
  workspaceMutationCoordinator = null,
295
352
  }) {
296
- const context = { renderDocx, vaultFileStore, workspaceMutationCoordinator };
353
+ const context = {
354
+ maxPdfUploadBytes,
355
+ renderDocx,
356
+ vaultFileStore,
357
+ workspaceMutationCoordinator,
358
+ };
297
359
 
298
360
  return async function handleVaultApiCommand(req, res, requestUrl) {
299
361
  for (const route of ROUTE_TABLE) {
@@ -3,7 +3,7 @@ import { readdir } from 'node:fs/promises';
3
3
  import { basename } from 'node:path';
4
4
  import { join } from 'node:path';
5
5
 
6
- import { isImageAttachmentFilePath } from '../../../domain/file-kind.js';
6
+ import { getVaultFileKind, isImageAttachmentFilePath } from '../../../domain/file-kind.js';
7
7
  import { isIgnoredVaultEntry } from '../persistence/path-utils.js';
8
8
  import { parseJsonBody } from './request-body.js';
9
9
  import {
@@ -19,6 +19,36 @@ const SVG_ATTACHMENT_CSP = "default-src 'none'; img-src 'self' data: blob:; styl
19
19
  const DEFAULT_MAX_ARCHIVE_ENTRIES = 10_000;
20
20
  const DEFAULT_MAX_DOWNLOAD_FILE_BYTES = 268_435_456;
21
21
 
22
+ function createSearchCancellation(req, res) {
23
+ const controller = new AbortController();
24
+
25
+ const abort = () => {
26
+ if (controller.signal.aborted || res.writableEnded || res.writableFinished) {
27
+ return;
28
+ }
29
+
30
+ controller.abort();
31
+ };
32
+ const handleRequestClose = () => {
33
+ if (!req.complete) {
34
+ abort();
35
+ }
36
+ };
37
+
38
+ req.once('aborted', abort);
39
+ req.once('close', handleRequestClose);
40
+ res.once('close', abort);
41
+
42
+ return {
43
+ cleanup() {
44
+ req.off('aborted', abort);
45
+ req.off('close', handleRequestClose);
46
+ res.off('close', abort);
47
+ },
48
+ signal: controller.signal,
49
+ };
50
+ }
51
+
22
52
  function createAttachmentHeaders(attachment) {
23
53
  const fileName = basename(String(attachment?.path ?? 'attachment'));
24
54
  const headers = {
@@ -35,10 +65,10 @@ function createAttachmentHeaders(attachment) {
35
65
  return headers;
36
66
  }
37
67
 
38
- function createDownloadHeaders(fileName, contentType) {
68
+ function createDownloadHeaders(fileName, contentType, { inline = false } = {}) {
39
69
  return {
40
70
  'Cache-Control': 'no-store',
41
- 'Content-Disposition': `attachment; filename="${createSafeAsciiFilename(fileName)}"; filename*=UTF-8''${encodeContentDispositionFilename(fileName)}`,
71
+ 'Content-Disposition': `${inline ? 'inline' : 'attachment'}; filename="${createSafeAsciiFilename(fileName)}"; filename*=UTF-8''${encodeContentDispositionFilename(fileName)}`,
42
72
  'Content-Type': contentType,
43
73
  'X-Content-Type-Options': 'nosniff',
44
74
  };
@@ -304,6 +334,7 @@ async function handleFileDownload(req, res, requestUrl, { config, vaultFileStore
304
334
  headers: createDownloadHeaders(
305
335
  basename(String(download.path ?? 'download')),
306
336
  download.mimeType || 'application/octet-stream',
337
+ { inline: getVaultFileKind(download.path) === 'pdf' },
307
338
  ),
308
339
  statusCode: 200,
309
340
  stream: download.stream,
@@ -418,13 +449,28 @@ async function handleSearch(req, res, requestUrl, { searchService }) {
418
449
  return;
419
450
  }
420
451
 
452
+ const cancellation = createSearchCancellation(req, res);
453
+
421
454
  try {
422
455
  const result = await searchService.search({
423
456
  limit: requestUrl.searchParams.get('limit') || '',
424
457
  query: requestUrl.searchParams.get('q') || '',
458
+ signal: cancellation.signal,
425
459
  });
460
+ if (cancellation.signal.aborted || res.destroyed || res.writableEnded) {
461
+ return;
462
+ }
463
+
426
464
  jsonResponse(req, res, 200, result);
427
465
  } catch (error) {
466
+ if (
467
+ cancellation.signal.aborted
468
+ || res.destroyed
469
+ || res.writableEnded
470
+ ) {
471
+ return;
472
+ }
473
+
428
474
  const statusCode = Number(error?.statusCode) || 500;
429
475
  console.error('[api] Failed to search vault:', error.message);
430
476
  jsonResponse(req, res, statusCode, {
@@ -435,6 +481,8 @@ async function handleSearch(req, res, requestUrl, { searchService }) {
435
481
  backend: 'ripgrep',
436
482
  },
437
483
  });
484
+ } finally {
485
+ cancellation.cleanup();
438
486
  }
439
487
  }
440
488
 
@@ -1,6 +1,28 @@
1
1
  import { getRequestErrorStatusCode } from './http-errors.js';
2
2
  import { jsonResponse } from './http-response.js';
3
3
 
4
+ export function stripBasePath(pathname, basePath) {
5
+ if (!basePath) {
6
+ return pathname;
7
+ }
8
+
9
+ if (pathname === basePath) {
10
+ return '/';
11
+ }
12
+
13
+ if (pathname.startsWith(`${basePath}/`)) {
14
+ return pathname.slice(basePath.length) || '/';
15
+ }
16
+
17
+ return pathname;
18
+ }
19
+
20
+ export function createRequestUrlWithPathname(requestUrl, pathname) {
21
+ const nextUrl = new URL(requestUrl.toString());
22
+ nextUrl.pathname = pathname || '/';
23
+ return nextUrl;
24
+ }
25
+
4
26
  export function readRequestId(req) {
5
27
  const value = String(req.headers['x-collabmd-request-id'] || '').trim();
6
28
  return value ? value.slice(0, 120) : null;
@@ -3,7 +3,7 @@ import { isAbsolute, normalize, relative, resolve } from 'path';
3
3
  import { isVaultFilePath } from '../../../domain/file-kind.js';
4
4
 
5
5
  export const IGNORED_DIRECTORIES = new Set(['.git', '.obsidian', '.trash', 'node_modules', '.DS_Store']);
6
- export const VAULT_FILE_PATH_REQUIREMENT = '.md, .base, .excalidraw, .drawio, .mmd, .mermaid, .puml, .plantuml, .png, .jpg, .jpeg, .webp, .gif, or .svg';
6
+ export const VAULT_FILE_PATH_REQUIREMENT = '.md, .markdown, .mdx, .base, .excalidraw, .drawio, .mmd, .mermaid, .puml, .plantuml, .dsl, .png, .jpg, .jpeg, .webp, .gif, or .svg';
7
7
  export const INVALID_VAULT_FILE_PATH_ERROR = `Invalid file path — must end in ${VAULT_FILE_PATH_REQUIREMENT}`;
8
8
  export const INVALID_DIRECTORY_PATH_ERROR = 'Invalid directory path';
9
9
 
@@ -31,6 +31,7 @@ const EDITABLE_VAULT_CONTENT_PATH_ERRORS = {
31
31
  markdown: 'Invalid file path',
32
32
  mermaid: 'Invalid file path — must end in .mmd or .mermaid',
33
33
  plantuml: 'Invalid file path — must end in .puml or .plantuml',
34
+ structurizr: 'Invalid file path — must end in .dsl',
34
35
  };
35
36
 
36
37
  function getEditableVaultContentKind(filePath) {
@@ -67,7 +68,9 @@ const TEXT_FILE_MIME_TYPES = Object.freeze({
67
68
  excalidraw: 'application/json; charset=utf-8',
68
69
  markdown: 'text/markdown; charset=utf-8',
69
70
  mermaid: 'text/plain; charset=utf-8',
71
+ pdf: 'application/pdf',
70
72
  plantuml: 'text/plain; charset=utf-8',
73
+ structurizr: 'text/plain; charset=utf-8',
71
74
  });
72
75
 
73
76
  function createTransactionalPath(targetPath, label) {
@@ -0,0 +1,43 @@
1
+ html, body, #content {
2
+ height: 100% !important;
3
+ min-height: 0 !important;
4
+ }
5
+
6
+ body {
7
+ margin: 0 !important;
8
+ overflow: hidden !important;
9
+ background: transparent !important;
10
+ }
11
+
12
+ #diagramControls,
13
+ #diagramNavigationPanel,
14
+ #embeddedControls {
15
+ display: none !important;
16
+ }
17
+
18
+ #diagramControls + .row,
19
+ #diagramControls + .row > .col-10,
20
+ #diagramControls + .row #diagram,
21
+ #diagram-viewport {
22
+ height: 100% !important;
23
+ min-height: 0 !important;
24
+ }
25
+
26
+ #diagramControls + .row > .col-10 {
27
+ width: 100% !important;
28
+ }
29
+
30
+ #diagram {
31
+ background: transparent !important;
32
+ }
33
+
34
+ #diagram-viewport {
35
+ overflow: auto !important;
36
+ touch-action: pan-x pan-y !important;
37
+ overscroll-behavior: contain !important;
38
+ background: transparent !important;
39
+ }
40
+
41
+ #diagram-canvas {
42
+ margin: 0 auto !important;
43
+ }