collabmd 0.1.18

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 (430) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +453 -0
  3. package/bin/collabmd.js +241 -0
  4. package/docker-compose.yml +43 -0
  5. package/package.json +93 -0
  6. package/public/assets/css/base.css +1 -0
  7. package/public/assets/css/style.css +1 -0
  8. package/public/assets/js/assets/Assistant-Bold-ZDZZ6JHA.woff2 +0 -0
  9. package/public/assets/js/assets/Assistant-Medium-DZ25RZU3.woff2 +0 -0
  10. package/public/assets/js/assets/Assistant-Regular-PLF2XOGW.woff2 +0 -0
  11. package/public/assets/js/assets/Assistant-SemiBold-CZ5MX6FK.woff2 +0 -0
  12. package/public/assets/js/chunks/CodeMirrorEditor-UCMRAMCV-MAKZFDBE.js +12 -0
  13. package/public/assets/js/chunks/apl-C5P5JBDI.js +1 -0
  14. package/public/assets/js/chunks/ar-SA-NXDWD63E-MF6HMEA4.js +8 -0
  15. package/public/assets/js/chunks/architecture-PBZL5I3N-55LDZ5WS.js +1 -0
  16. package/public/assets/js/chunks/architectureDiagram-2XIMDMQ5-2VP562X7.js +36 -0
  17. package/public/assets/js/chunks/asciiarmor-3NCLLELT.js +1 -0
  18. package/public/assets/js/chunks/asn1-KF4WXU3M.js +1 -0
  19. package/public/assets/js/chunks/asterisk-EA3VIN65.js +1 -0
  20. package/public/assets/js/chunks/az-AZ-MXSORGZV-AIGO4TNU.js +1 -0
  21. package/public/assets/js/chunks/bg-BG-O6MV2KSF-X6AVO2AF.js +5 -0
  22. package/public/assets/js/chunks/blockDiagram-WCTKOSBZ-KM56WWDE.js +132 -0
  23. package/public/assets/js/chunks/bn-BD-T2VJNT7N-7OMHRIGC.js +3 -0
  24. package/public/assets/js/chunks/bn-IN-MMW2ORAC-EPKMNKG7.js +3 -0
  25. package/public/assets/js/chunks/brainfuck-A3CTQVFD.js +1 -0
  26. package/public/assets/js/chunks/c4Diagram-IC4MRINW-KX6UEGQZ.js +10 -0
  27. package/public/assets/js/chunks/ca-ES-MKBCGNOG-U44SUUTN.js +8 -0
  28. package/public/assets/js/chunks/chunk-2NTQWLZD.js +25 -0
  29. package/public/assets/js/chunks/chunk-2YRP2AWD.js +1 -0
  30. package/public/assets/js/chunks/chunk-3BUCGSSJ.js +1 -0
  31. package/public/assets/js/chunks/chunk-47L7JGE3.js +1 -0
  32. package/public/assets/js/chunks/chunk-4BCHPULB.js +1 -0
  33. package/public/assets/js/chunks/chunk-4NKM6YFO.js +161 -0
  34. package/public/assets/js/chunks/chunk-4WGMPBK5.js +1 -0
  35. package/public/assets/js/chunks/chunk-55WF6Y2Q.js +1 -0
  36. package/public/assets/js/chunks/chunk-775E65SU.js +1 -0
  37. package/public/assets/js/chunks/chunk-AJRGTECU.js +70 -0
  38. package/public/assets/js/chunks/chunk-B2J3PHGY.js +1 -0
  39. package/public/assets/js/chunks/chunk-B4JKB3BC.js +1 -0
  40. package/public/assets/js/chunks/chunk-BBHPYU2R.js +1 -0
  41. package/public/assets/js/chunks/chunk-CGVY4PKT.js +13 -0
  42. package/public/assets/js/chunks/chunk-CVTRX3TE.js +220 -0
  43. package/public/assets/js/chunks/chunk-DW6KK6VF.js +1 -0
  44. package/public/assets/js/chunks/chunk-E2HMNOQL.js +189 -0
  45. package/public/assets/js/chunks/chunk-EPTJZP5H.js +7 -0
  46. package/public/assets/js/chunks/chunk-G7NUGGE7.js +13 -0
  47. package/public/assets/js/chunks/chunk-GI7LBIBP.js +1 -0
  48. package/public/assets/js/chunks/chunk-GXRL27M7.js +1 -0
  49. package/public/assets/js/chunks/chunk-H6EYDHRW.js +1 -0
  50. package/public/assets/js/chunks/chunk-HOCBCDA5.js +1 -0
  51. package/public/assets/js/chunks/chunk-HS2CUOC6.js +1 -0
  52. package/public/assets/js/chunks/chunk-HUAS4BYI.js +12 -0
  53. package/public/assets/js/chunks/chunk-HVNNEPP5.js +2 -0
  54. package/public/assets/js/chunks/chunk-I2GVZI7W.js +38 -0
  55. package/public/assets/js/chunks/chunk-I6PVWGBN.js +14 -0
  56. package/public/assets/js/chunks/chunk-IR2DOYV4.js +7 -0
  57. package/public/assets/js/chunks/chunk-IZ3V3F2A.js +1 -0
  58. package/public/assets/js/chunks/chunk-KPAQHIOI.js +1 -0
  59. package/public/assets/js/chunks/chunk-M3EW4V74.js +1 -0
  60. package/public/assets/js/chunks/chunk-MK3AFU3C.js +90 -0
  61. package/public/assets/js/chunks/chunk-NM6J3ND7.js +1 -0
  62. package/public/assets/js/chunks/chunk-NO7UPWTU.js +1 -0
  63. package/public/assets/js/chunks/chunk-OG2TNZEU.js +9 -0
  64. package/public/assets/js/chunks/chunk-OHGDMMWM.js +1 -0
  65. package/public/assets/js/chunks/chunk-OOT2HALX.js +1 -0
  66. package/public/assets/js/chunks/chunk-OQLLZFFN.js +1 -0
  67. package/public/assets/js/chunks/chunk-PF6TP4EI.js +1 -0
  68. package/public/assets/js/chunks/chunk-PMOZMECZ.js +85 -0
  69. package/public/assets/js/chunks/chunk-QBGS6DJR.js +1 -0
  70. package/public/assets/js/chunks/chunk-QF3ZQ6UQ.js +1 -0
  71. package/public/assets/js/chunks/chunk-QFXMAUW4.js +1 -0
  72. package/public/assets/js/chunks/chunk-QSTBGTWJ.js +1 -0
  73. package/public/assets/js/chunks/chunk-QYKIT2IH.js +41 -0
  74. package/public/assets/js/chunks/chunk-S75BI2UQ.js +1 -0
  75. package/public/assets/js/chunks/chunk-SKEZZ3H6.js +1 -0
  76. package/public/assets/js/chunks/chunk-SO6QBCB6.js +1 -0
  77. package/public/assets/js/chunks/chunk-SR3U53EQ.js +1 -0
  78. package/public/assets/js/chunks/chunk-TFP6LRYJ.js +53 -0
  79. package/public/assets/js/chunks/chunk-U6APNHAF.js +15 -0
  80. package/public/assets/js/chunks/chunk-VOODXHXA.js +1 -0
  81. package/public/assets/js/chunks/chunk-W7WJUTSA.js +3 -0
  82. package/public/assets/js/chunks/chunk-WNXFO2F2.js +1 -0
  83. package/public/assets/js/chunks/chunk-X6EEYWV4.js +6 -0
  84. package/public/assets/js/chunks/chunk-YVW3ZETS.js +1 -0
  85. package/public/assets/js/chunks/chunk-ZR2QRTBG.js +339 -0
  86. package/public/assets/js/chunks/chunk-ZZJX4LQ2.js +1 -0
  87. package/public/assets/js/chunks/classDiagram-VBA2DB6C-IYW4O2RM.js +1 -0
  88. package/public/assets/js/chunks/classDiagram-v2-RAHNMMFH-DNKVN2WD.js +1 -0
  89. package/public/assets/js/chunks/clike-RTBH33GE.js +1 -0
  90. package/public/assets/js/chunks/clojure-BR2VJ2JI.js +1 -0
  91. package/public/assets/js/chunks/cmake-2HEWVFEE.js +1 -0
  92. package/public/assets/js/chunks/cobol-V6EIK6DN.js +1 -0
  93. package/public/assets/js/chunks/coffeescript-JW3ZOGMD.js +1 -0
  94. package/public/assets/js/chunks/commonlisp-UCWSIHYK.js +1 -0
  95. package/public/assets/js/chunks/cose-bilkent-S5V4N54A-QVYRFFNC.js +1 -0
  96. package/public/assets/js/chunks/crystal-GR4VLC3M.js +1 -0
  97. package/public/assets/js/chunks/cs-CZ-V77HGUP7-OLBAKFG5.js +7 -0
  98. package/public/assets/js/chunks/css-C5DVWGB4.js +1 -0
  99. package/public/assets/js/chunks/cypher-K7FZM5GC.js +1 -0
  100. package/public/assets/js/chunks/d-CF3QNNH5.js +1 -0
  101. package/public/assets/js/chunks/da-DK-I5RFNTM4-QJIG2SEZ.js +2 -0
  102. package/public/assets/js/chunks/dagre-KLK3FWXG-KVNBUDOY.js +4 -0
  103. package/public/assets/js/chunks/de-CH-WLRDEVBV-4MMJDMVD.js +7 -0
  104. package/public/assets/js/chunks/de-DE-DTI4WUSC-ML7C46TT.js +7 -0
  105. package/public/assets/js/chunks/diagram-E7M64L7V-ZP33VRUC.js +24 -0
  106. package/public/assets/js/chunks/diagram-IFDJBPK2-ZG35YWOD.js +43 -0
  107. package/public/assets/js/chunks/diagram-P4PSJMXO-ZGIGM7D2.js +24 -0
  108. package/public/assets/js/chunks/diff-KOMSZU7O.js +1 -0
  109. package/public/assets/js/chunks/dist-7RQKSOUX.js +7 -0
  110. package/public/assets/js/chunks/dist-ARWGZ6MN.js +1 -0
  111. package/public/assets/js/chunks/dist-DPBSGC3Y.js +2 -0
  112. package/public/assets/js/chunks/dist-E36PJE5M.js +1 -0
  113. package/public/assets/js/chunks/dist-E5NM7OBW.js +1 -0
  114. package/public/assets/js/chunks/dist-ERADYPEK.js +1 -0
  115. package/public/assets/js/chunks/dist-F43RIHBZ.js +1 -0
  116. package/public/assets/js/chunks/dist-HAB5NIXI.js +1 -0
  117. package/public/assets/js/chunks/dist-HG23OLRA.js +1 -0
  118. package/public/assets/js/chunks/dist-IEJXBSDJ.js +1 -0
  119. package/public/assets/js/chunks/dist-IQA4KVJK.js +1 -0
  120. package/public/assets/js/chunks/dist-OIQTNVT2.js +3 -0
  121. package/public/assets/js/chunks/dist-PMZJ734S.js +1 -0
  122. package/public/assets/js/chunks/dist-RCQFQRLD.js +1 -0
  123. package/public/assets/js/chunks/dist-RQLC4GVI.js +1 -0
  124. package/public/assets/js/chunks/dist-RSJGLSKC.js +1 -0
  125. package/public/assets/js/chunks/dist-SGNQZVVX.js +1 -0
  126. package/public/assets/js/chunks/dist-VCFTF7Z6.js +1 -0
  127. package/public/assets/js/chunks/dist-VTO23SHH.js +1 -0
  128. package/public/assets/js/chunks/dist-XOODBV4L.js +1 -0
  129. package/public/assets/js/chunks/dist-ZFZNCRZY.js +1 -0
  130. package/public/assets/js/chunks/dockerfile-XCIKYOSI.js +1 -0
  131. package/public/assets/js/chunks/dtd-3XFH7W6Z.js +1 -0
  132. package/public/assets/js/chunks/dylan-3UWPKVOR.js +1 -0
  133. package/public/assets/js/chunks/ebnf-OLHTL3VN.js +1 -0
  134. package/public/assets/js/chunks/ecl-43LZ3ZRM.js +1 -0
  135. package/public/assets/js/chunks/editor-session-IHFTYG5D.js +22 -0
  136. package/public/assets/js/chunks/eiffel-ISPO2UJF.js +1 -0
  137. package/public/assets/js/chunks/el-GR-ZXDJ2OS4-WSCEIO6J.js +7 -0
  138. package/public/assets/js/chunks/elm-CLK576PK.js +1 -0
  139. package/public/assets/js/chunks/en-CTDJDZ6K-IC3QAUOI.js +1 -0
  140. package/public/assets/js/chunks/erDiagram-INFDFZHY-ZMS6H2TH.js +70 -0
  141. package/public/assets/js/chunks/erlang-AVQXQFOT.js +1 -0
  142. package/public/assets/js/chunks/es-ES-DYQHAD43-XGB5X5IM.js +7 -0
  143. package/public/assets/js/chunks/eu-ES-QDSYU5Y3-LA3JTIJ2.js +7 -0
  144. package/public/assets/js/chunks/excalidraw-mermaid-stub-NYXTOJ4H.js +1 -0
  145. package/public/assets/js/chunks/fa-IR-26LZG4PM-PFALMEDX.js +9 -0
  146. package/public/assets/js/chunks/factor-CRTVBD6Q.js +1 -0
  147. package/public/assets/js/chunks/fcl-FX24LKTH.js +1 -0
  148. package/public/assets/js/chunks/fi-FI-6YS4KDE4-ES5MV6P4.js +6 -0
  149. package/public/assets/js/chunks/flowDiagram-PKNHOUZH-QD4L5IE5.js +162 -0
  150. package/public/assets/js/chunks/forth-YKDKCTVS.js +1 -0
  151. package/public/assets/js/chunks/fortran-UJWC6SLU.js +1 -0
  152. package/public/assets/js/chunks/fr-FR-BRLLPTZC-VMFE22YV.js +12 -0
  153. package/public/assets/js/chunks/ganttDiagram-A5KZAMGK-VTMTQESP.js +292 -0
  154. package/public/assets/js/chunks/gas-MYEMWGRE.js +1 -0
  155. package/public/assets/js/chunks/gherkin-JWHAU2YG.js +1 -0
  156. package/public/assets/js/chunks/gitGraph-HDMCJU4V-YPHS74XX.js +1 -0
  157. package/public/assets/js/chunks/gitGraphDiagram-K3NZZRJ6-Z3EZ634T.js +65 -0
  158. package/public/assets/js/chunks/gl-ES-OKUZTLYG-GPT42ZHV.js +6 -0
  159. package/public/assets/js/chunks/groovy-QZIZQIAK.js +1 -0
  160. package/public/assets/js/chunks/haskell-JR3G7YNW.js +1 -0
  161. package/public/assets/js/chunks/haxe-TVLAIYEN.js +1 -0
  162. package/public/assets/js/chunks/he-IL-26MGYIU7-VQ5XQYYY.js +8 -0
  163. package/public/assets/js/chunks/hi-IN-Q4ZOGTP2-2Z766MXA.js +3 -0
  164. package/public/assets/js/chunks/http-YXQRE77G.js +1 -0
  165. package/public/assets/js/chunks/hu-HU-P2OXPSN5-6XDVPGTE.js +3 -0
  166. package/public/assets/js/chunks/id-ID-2EIKXYN6-J5GWSY2S.js +8 -0
  167. package/public/assets/js/chunks/idl-RAQGJ6LJ.js +1 -0
  168. package/public/assets/js/chunks/image-32MOCVDX-JN2EUMQG.js +1 -0
  169. package/public/assets/js/chunks/image-blob-reduce.esm-K5OWL55S.js +13 -0
  170. package/public/assets/js/chunks/info-3K5VOQVL-CXJSPNNP.js +1 -0
  171. package/public/assets/js/chunks/infoDiagram-LFFYTUFH-WQ34MUJA.js +2 -0
  172. package/public/assets/js/chunks/ishikawaDiagram-PHBUUO56-4IARWKJK.js +70 -0
  173. package/public/assets/js/chunks/it-IT-4CFKVXI2-NMBWUWFF.js +12 -0
  174. package/public/assets/js/chunks/ja-JP-MAND2Z4V-GQZYXXOC.js +7 -0
  175. package/public/assets/js/chunks/javascript-NM7KDJUE.js +1 -0
  176. package/public/assets/js/chunks/journeyDiagram-4ABVD52K-FJ4LF4SX.js +139 -0
  177. package/public/assets/js/chunks/julia-LG3BH25B.js +1 -0
  178. package/public/assets/js/chunks/kaa-HQRTCXTJ-7D6PAKDJ.js +1 -0
  179. package/public/assets/js/chunks/kab-KAB-JKIKKTGH-TNKFXNAX.js +4 -0
  180. package/public/assets/js/chunks/kanban-definition-K7BYSVSG-TY4EVYI3.js +89 -0
  181. package/public/assets/js/chunks/katex-5YJCUCFD.js +261 -0
  182. package/public/assets/js/chunks/kk-KZ-HXQ63ZFC-T4HCMWED.js +1 -0
  183. package/public/assets/js/chunks/km-KH-PKL3LSGX-HTLHEJV6.js +7 -0
  184. package/public/assets/js/chunks/ko-KR-KXLREZX3-QMR52QVI.js +5 -0
  185. package/public/assets/js/chunks/ku-TR-MJ4IER3X-K5CNY4UO.js +6 -0
  186. package/public/assets/js/chunks/livescript-4CVWVI4M.js +1 -0
  187. package/public/assets/js/chunks/lt-LT-H6WFQ6FJ-IW25FAGX.js +3 -0
  188. package/public/assets/js/chunks/lua-VWGE7DQ7.js +1 -0
  189. package/public/assets/js/chunks/lv-LV-GOCK6SJW-KTDFW5OH.js +4 -0
  190. package/public/assets/js/chunks/mathematica-D7APQSEU.js +1 -0
  191. package/public/assets/js/chunks/mbox-BN3XQBYX.js +1 -0
  192. package/public/assets/js/chunks/mermaid-runtime-BIFNJ2CT.js +18 -0
  193. package/public/assets/js/chunks/mindmap-definition-YRQLILUH-5B565RNP.js +68 -0
  194. package/public/assets/js/chunks/mirc-565WBI76.js +1 -0
  195. package/public/assets/js/chunks/mllike-VV3KS46B.js +1 -0
  196. package/public/assets/js/chunks/modelica-EGX44FZV.js +1 -0
  197. package/public/assets/js/chunks/mr-IN-IXVLDMEQ-PHM5DT7Q.js +9 -0
  198. package/public/assets/js/chunks/mscgen-GFGTHENT.js +1 -0
  199. package/public/assets/js/chunks/mumps-POZYWSMI.js +1 -0
  200. package/public/assets/js/chunks/my-MM-YG6MYTTD-ZHLCMEVA.js +1 -0
  201. package/public/assets/js/chunks/nb-NO-XGWJISD2-KSDFG6YU.js +8 -0
  202. package/public/assets/js/chunks/nginx-JUBX55EU.js +1 -0
  203. package/public/assets/js/chunks/nl-NL-XVDVCPNC-KVF3ZO2R.js +13 -0
  204. package/public/assets/js/chunks/nn-NO-EMX5MJX5-34QKSHNR.js +4 -0
  205. package/public/assets/js/chunks/nsis-RC4CREGM.js +1 -0
  206. package/public/assets/js/chunks/ntriples-4WK6CNVE.js +1 -0
  207. package/public/assets/js/chunks/oc-FR-ROEINTVO-5GP4AOYO.js +4 -0
  208. package/public/assets/js/chunks/octave-GOKFN2V7.js +1 -0
  209. package/public/assets/js/chunks/oz-OQUMNC63.js +1 -0
  210. package/public/assets/js/chunks/pa-IN-ZYN6SUDY-MVL7O44W.js +4 -0
  211. package/public/assets/js/chunks/packet-RMMSAZCW-D7XTR5TT.js +1 -0
  212. package/public/assets/js/chunks/pascal-BBWQWWWD.js +1 -0
  213. package/public/assets/js/chunks/percentages-5DKR754L-DG7W23CV.js +1 -0
  214. package/public/assets/js/chunks/perl-KTPJTTAM.js +1 -0
  215. package/public/assets/js/chunks/pica-6G3NROWT.js +17 -0
  216. package/public/assets/js/chunks/pie-UPGHQEXC-KGC3O6LN.js +1 -0
  217. package/public/assets/js/chunks/pieDiagram-SKSYHLDU-KWKRDEZS.js +30 -0
  218. package/public/assets/js/chunks/pig-IJMBS4FZ.js +1 -0
  219. package/public/assets/js/chunks/pl-PL-6VTG7PHC-VADRWSX4.js +12 -0
  220. package/public/assets/js/chunks/powershell-6YGCBHDP.js +1 -0
  221. package/public/assets/js/chunks/preview-render-compiler-WD3A76I6.js +25 -0
  222. package/public/assets/js/chunks/properties-5QTD3364.js +1 -0
  223. package/public/assets/js/chunks/protobuf-LTRWBIK3.js +1 -0
  224. package/public/assets/js/chunks/pt-BR-5YFKRR5G-HHF4UPO4.js +6 -0
  225. package/public/assets/js/chunks/pt-PT-GEIXXZ67-TUTAUZJK.js +8 -0
  226. package/public/assets/js/chunks/pug-U4PV3WLG.js +1 -0
  227. package/public/assets/js/chunks/puppet-WGYJPR3Y.js +1 -0
  228. package/public/assets/js/chunks/python-6YGH4S47.js +1 -0
  229. package/public/assets/js/chunks/q-VIG4IJLI.js +1 -0
  230. package/public/assets/js/chunks/quadrantDiagram-337W2JSQ-GWQRUFHQ.js +7 -0
  231. package/public/assets/js/chunks/quick-switcher-controller-JYDIJVAJ.js +5 -0
  232. package/public/assets/js/chunks/r-NVPCXGTR.js +1 -0
  233. package/public/assets/js/chunks/radar-KQ55EAFF-OPO64DRI.js +1 -0
  234. package/public/assets/js/chunks/requirementDiagram-Z7DCOOCP-ASW7IPJL.js +73 -0
  235. package/public/assets/js/chunks/ro-RO-E2P6TFZI-RJFCULYF.js +12 -0
  236. package/public/assets/js/chunks/roundRect-Z3DX3W5S.js +1 -0
  237. package/public/assets/js/chunks/rpm-UEA257HD.js +1 -0
  238. package/public/assets/js/chunks/ru-RU-YBIIY43T-BF27TNVK.js +10 -0
  239. package/public/assets/js/chunks/ruby-KPQRAJGE.js +1 -0
  240. package/public/assets/js/chunks/sankeyDiagram-WA2Y5GQK-W6BBWCLW.js +10 -0
  241. package/public/assets/js/chunks/sas-L7VATVH5.js +1 -0
  242. package/public/assets/js/chunks/scheme-T2NOWR3P.js +1 -0
  243. package/public/assets/js/chunks/sequenceDiagram-2WXFIKYE-H4G3ISLN.js +145 -0
  244. package/public/assets/js/chunks/shell-DLVDUNFA.js +1 -0
  245. package/public/assets/js/chunks/si-LK-673C2CI2-IOFGU4U2.js +1 -0
  246. package/public/assets/js/chunks/sieve-BKBZYDBU.js +1 -0
  247. package/public/assets/js/chunks/sk-SK-CKZHQ4AP-6O5JGN26.js +7 -0
  248. package/public/assets/js/chunks/sl-SI-557H2GWM-2NEHLV7W.js +6 -0
  249. package/public/assets/js/chunks/smalltalk-WRJQKJSD.js +1 -0
  250. package/public/assets/js/chunks/solr-66B3BIDL.js +1 -0
  251. package/public/assets/js/chunks/sparql-S25Z47JG.js +1 -0
  252. package/public/assets/js/chunks/spreadsheet-B272EF7Y.js +1 -0
  253. package/public/assets/js/chunks/sql-BPLIIUYC.js +1 -0
  254. package/public/assets/js/chunks/stateDiagram-RAJIS63D-OF5XXRUS.js +1 -0
  255. package/public/assets/js/chunks/stateDiagram-v2-FVOUBMTO-IV2CSQSQ.js +1 -0
  256. package/public/assets/js/chunks/stex-4IJY64QX.js +1 -0
  257. package/public/assets/js/chunks/stylus-PJUHLGPT.js +1 -0
  258. package/public/assets/js/chunks/subset-shared.chunk-4NMWHS4D.js +1 -0
  259. package/public/assets/js/chunks/subset-worker.chunk-NTLP7Y66.js +1 -0
  260. package/public/assets/js/chunks/sv-SE-XOPKKDWS-Q2L6KGFV.js +8 -0
  261. package/public/assets/js/chunks/swift-UYHTO4SX.js +1 -0
  262. package/public/assets/js/chunks/ta-IN-XBH7DQI3-ISSUGZAK.js +6 -0
  263. package/public/assets/js/chunks/tcl-OQ5J3VO2.js +1 -0
  264. package/public/assets/js/chunks/textile-NOGVBEYF.js +1 -0
  265. package/public/assets/js/chunks/th-TH-TOCWH3R4-TABEAMJA.js +1 -0
  266. package/public/assets/js/chunks/tiddlywiki-SYTK4QUE.js +1 -0
  267. package/public/assets/js/chunks/tiki-P7K4ZLOM.js +1 -0
  268. package/public/assets/js/chunks/timeline-definition-YZTLITO2-HBCANIUC.js +61 -0
  269. package/public/assets/js/chunks/toml-AUZYRP36.js +1 -0
  270. package/public/assets/js/chunks/tr-TR-GXW6CYY5-5NQRN52Y.js +4 -0
  271. package/public/assets/js/chunks/treemap-KZPCXAKY-WYMCT5F7.js +1 -0
  272. package/public/assets/js/chunks/troff-XFYJNQOJ.js +1 -0
  273. package/public/assets/js/chunks/ttcn-AK2IXWU6.js +1 -0
  274. package/public/assets/js/chunks/ttcn-cfg-3IDPIYPU.js +1 -0
  275. package/public/assets/js/chunks/turtle-OIDMF46P.js +1 -0
  276. package/public/assets/js/chunks/uk-UA-IJKTNUDG-2IIL3TO5.js +6 -0
  277. package/public/assets/js/chunks/uz-UZ-TBZZEA4V-X22KVKBP.js +1 -0
  278. package/public/assets/js/chunks/vb-J6K4XVA2.js +1 -0
  279. package/public/assets/js/chunks/vbscript-FM7DDZZE.js +1 -0
  280. package/public/assets/js/chunks/velocity-6KZFEOFD.js +1 -0
  281. package/public/assets/js/chunks/vennDiagram-LZ73GAT5-EBRF3MZR.js +34 -0
  282. package/public/assets/js/chunks/verilog-EWWQPMNN.js +1 -0
  283. package/public/assets/js/chunks/vhdl-CJ3M4IHI.js +1 -0
  284. package/public/assets/js/chunks/vi-VN-MULSGNVB-TENM65C7.js +7 -0
  285. package/public/assets/js/chunks/webidl-XVBHXCRV.js +1 -0
  286. package/public/assets/js/chunks/xquery-7WZOZ4UK.js +1 -0
  287. package/public/assets/js/chunks/xychartDiagram-JWTSCODW-WIFVRQPR.js +7 -0
  288. package/public/assets/js/chunks/yacas-MZ2ZZPF3.js +1 -0
  289. package/public/assets/js/chunks/z80-I447CBF7.js +1 -0
  290. package/public/assets/js/chunks/zh-CN-MVX3E32U-S5VA2BA5.js +11 -0
  291. package/public/assets/js/chunks/zh-HK-JNU5RLIE-A4VUKZFV.js +1 -0
  292. package/public/assets/js/chunks/zh-TW-LENVCA6T-KFB6KHMM.js +9 -0
  293. package/public/assets/js/excalidraw-editor-7ORML5YA.css +1 -0
  294. package/public/assets/js/excalidraw-editor-LKW2AZBU.js +347 -0
  295. package/public/assets/js/excalidraw-editor.css +1 -0
  296. package/public/assets/js/excalidraw-editor.js +1 -0
  297. package/public/assets/js/main.js +354 -0
  298. package/public/assets/js/preview-render-worker.js +30 -0
  299. package/public/assets/vendor/highlight/github-dark.min.css +10 -0
  300. package/public/assets/vendor/highlight/github.min.css +10 -0
  301. package/public/excalidraw-editor.html +31 -0
  302. package/public/index.html +549 -0
  303. package/scripts/cloudflare-tunnel.mjs +145 -0
  304. package/scripts/local-plantuml-compose.mjs +92 -0
  305. package/server.js +1 -0
  306. package/src/client/application/app-shell/chat-feature.js +363 -0
  307. package/src/client/application/app-shell/comments-feature.js +33 -0
  308. package/src/client/application/app-shell/git-feature.js +394 -0
  309. package/src/client/application/app-shell/presence-feature.js +229 -0
  310. package/src/client/application/app-shell/ui-feature.js +535 -0
  311. package/src/client/application/app-shell/workspace-feature.js +132 -0
  312. package/src/client/application/app-shell-elements.js +78 -0
  313. package/src/client/application/diagram-preview-hydrator.js +382 -0
  314. package/src/client/application/file-open-lifecycle.js +26 -0
  315. package/src/client/application/mermaid-preview-hydrator.js +570 -0
  316. package/src/client/application/plantuml-preview-hydrator.js +563 -0
  317. package/src/client/application/preview-diagram-utils.js +223 -0
  318. package/src/client/application/preview-render-compiler.js +426 -0
  319. package/src/client/application/preview-render-executor.js +153 -0
  320. package/src/client/application/preview-render-profile.js +63 -0
  321. package/src/client/application/preview-render-scheduler.js +73 -0
  322. package/src/client/application/preview-render-worker.js +19 -0
  323. package/src/client/application/preview-renderer.js +452 -0
  324. package/src/client/application/wiki-link-file-controller.js +58 -0
  325. package/src/client/application/workspace-chrome-controller.js +84 -0
  326. package/src/client/application/workspace-coordinator.js +238 -0
  327. package/src/client/application/workspace-preview-controller.js +237 -0
  328. package/src/client/application/workspace-route-controller.js +170 -0
  329. package/src/client/application/workspace-state-store.js +46 -0
  330. package/src/client/bootstrap/collabmd-app-shell.js +479 -0
  331. package/src/client/domain/diagram-action-icons.js +41 -0
  332. package/src/client/domain/excalidraw-collaboration.js +153 -0
  333. package/src/client/domain/excalidraw-scene.js +87 -0
  334. package/src/client/domain/markdown-formatting.js +370 -0
  335. package/src/client/domain/plantuml-language.js +226 -0
  336. package/src/client/domain/room.js +49 -0
  337. package/src/client/domain/runtime-paths.js +136 -0
  338. package/src/client/domain/vault-api-client.js +1 -0
  339. package/src/client/domain/vault-paths.js +80 -0
  340. package/src/client/domain/vault-utils.js +49 -0
  341. package/src/client/domain/wiki-link-completions.js +78 -0
  342. package/src/client/excalidraw-editor.js +630 -0
  343. package/src/client/excalidraw-mermaid-stub.js +7 -0
  344. package/src/client/infrastructure/auth-client.js +383 -0
  345. package/src/client/infrastructure/browser-navigation-port.js +19 -0
  346. package/src/client/infrastructure/browser-notification-port.js +40 -0
  347. package/src/client/infrastructure/browser-preferences-port.js +79 -0
  348. package/src/client/infrastructure/comment-thread-store.js +293 -0
  349. package/src/client/infrastructure/editor-collaboration-client.js +240 -0
  350. package/src/client/infrastructure/editor-session.js +255 -0
  351. package/src/client/infrastructure/editor-view-adapter.js +578 -0
  352. package/src/client/infrastructure/excalidraw-room-client.js +651 -0
  353. package/src/client/infrastructure/lobby-presence.js +304 -0
  354. package/src/client/infrastructure/runtime-config.js +65 -0
  355. package/src/client/infrastructure/tab-activity-lock.js +193 -0
  356. package/src/client/infrastructure/vault-api-client.js +61 -0
  357. package/src/client/infrastructure/yjs-provider-reset-guard.js +19 -0
  358. package/src/client/main.js +16 -0
  359. package/src/client/mermaid-runtime.js +3 -0
  360. package/src/client/presentation/backlinks-panel.js +188 -0
  361. package/src/client/presentation/comment-ui-controller.js +1235 -0
  362. package/src/client/presentation/comments-panel.js +531 -0
  363. package/src/client/presentation/excalidraw-embed-controller.js +968 -0
  364. package/src/client/presentation/excalidraw-embed-reconciler.js +37 -0
  365. package/src/client/presentation/file-action-controller.js +596 -0
  366. package/src/client/presentation/file-explorer-controller.js +81 -0
  367. package/src/client/presentation/file-explorer-view.js +256 -0
  368. package/src/client/presentation/file-tree-state.js +77 -0
  369. package/src/client/presentation/git-diff-view-controller.js +656 -0
  370. package/src/client/presentation/git-panel-controller.js +516 -0
  371. package/src/client/presentation/layout-controller.js +222 -0
  372. package/src/client/presentation/outline-controller.js +239 -0
  373. package/src/client/presentation/quick-switcher-controller.js +300 -0
  374. package/src/client/presentation/scroll-sync-controller.js +500 -0
  375. package/src/client/presentation/theme-controller.js +64 -0
  376. package/src/client/presentation/toast-controller.js +21 -0
  377. package/src/client/presentation/video-embed-controller.js +264 -0
  378. package/src/client/styles/base.css +58 -0
  379. package/src/client/styles/style.css +4245 -0
  380. package/src/domain/comment-threads.js +233 -0
  381. package/src/domain/excalidraw-room-codec.js +427 -0
  382. package/src/domain/file-kind.js +104 -0
  383. package/src/domain/wiki-link-resolver.js +75 -0
  384. package/src/server/auth/create-auth-service.js +272 -0
  385. package/src/server/auth/session-cookie.js +125 -0
  386. package/src/server/config/env.js +186 -0
  387. package/src/server/create-app-server.js +143 -0
  388. package/src/server/domain/backlink-index.js +291 -0
  389. package/src/server/domain/collaboration/collaboration-document-store.js +155 -0
  390. package/src/server/domain/collaboration/collaboration-room.js +603 -0
  391. package/src/server/domain/collaboration/protocol.js +2 -0
  392. package/src/server/domain/collaboration/room-client-state-store.js +28 -0
  393. package/src/server/domain/collaboration/room-persistence-controller.js +80 -0
  394. package/src/server/domain/collaboration/room-registry.js +94 -0
  395. package/src/server/domain/git-service.js +1 -0
  396. package/src/server/domain/plantuml-encoder.js +49 -0
  397. package/src/server/domain/plantuml-renderer.js +1 -0
  398. package/src/server/index.js +72 -0
  399. package/src/server/infrastructure/git/command-runner.js +48 -0
  400. package/src/server/infrastructure/git/concurrency.js +19 -0
  401. package/src/server/infrastructure/git/diff-service.js +389 -0
  402. package/src/server/infrastructure/git/errors.js +5 -0
  403. package/src/server/infrastructure/git/git-service.js +216 -0
  404. package/src/server/infrastructure/git/parsers.js +401 -0
  405. package/src/server/infrastructure/git/path-utils.js +65 -0
  406. package/src/server/infrastructure/git/responses.js +108 -0
  407. package/src/server/infrastructure/git/status-service.js +99 -0
  408. package/src/server/infrastructure/git/untracked-files.js +101 -0
  409. package/src/server/infrastructure/http/create-auth-api-handler.js +48 -0
  410. package/src/server/infrastructure/http/create-esm-proxy-handler.js +130 -0
  411. package/src/server/infrastructure/http/create-git-api-command-handler.js +170 -0
  412. package/src/server/infrastructure/http/create-git-api-handler.js +40 -0
  413. package/src/server/infrastructure/http/create-git-api-query-handler.js +62 -0
  414. package/src/server/infrastructure/http/create-plantuml-api-handler.js +43 -0
  415. package/src/server/infrastructure/http/create-request-handler.js +138 -0
  416. package/src/server/infrastructure/http/create-static-handler.js +167 -0
  417. package/src/server/infrastructure/http/create-vault-api-command-handler.js +161 -0
  418. package/src/server/infrastructure/http/create-vault-api-handler.js +43 -0
  419. package/src/server/infrastructure/http/create-vault-api-query-handler.js +82 -0
  420. package/src/server/infrastructure/http/http-errors.js +13 -0
  421. package/src/server/infrastructure/http/http-response.js +167 -0
  422. package/src/server/infrastructure/http/request-body.js +56 -0
  423. package/src/server/infrastructure/persistence/path-utils.js +90 -0
  424. package/src/server/infrastructure/persistence/sidecar-store.js +150 -0
  425. package/src/server/infrastructure/persistence/vault-content-adapter.js +49 -0
  426. package/src/server/infrastructure/persistence/vault-file-store.js +435 -0
  427. package/src/server/infrastructure/plantuml/plantuml-renderer.js +56 -0
  428. package/src/server/infrastructure/websocket/attach-collaboration-gateway.js +194 -0
  429. package/src/server/infrastructure/websocket/client-socket-session.js +151 -0
  430. package/src/server/startup/git-remote-bootstrap.js +435 -0
@@ -0,0 +1,1235 @@
1
+ import {
2
+ COMMENT_BODY_MAX_LENGTH,
3
+ normalizeCommentQuoteForComparison,
4
+ } from '../../domain/comment-threads.js';
5
+
6
+ const COMMENT_CARD_OFFSET = 14;
7
+ const COMMENT_CARD_WIDTH = 360;
8
+ const COMMENT_SELECTION_REVEAL_DELAY_MS = 150;
9
+ const COMMENT_SELECTION_CHIP_GAP = 12;
10
+ const COMMENT_CONTROL_SLOT_HEIGHT = 36;
11
+
12
+ function clamp(value, min, max) {
13
+ return Math.min(Math.max(value, min), max);
14
+ }
15
+
16
+ function sortThreads(threads = []) {
17
+ return [...threads].sort((left, right) => (
18
+ (left.anchor?.startLine ?? 0) - (right.anchor?.startLine ?? 0)
19
+ || left.createdAt - right.createdAt
20
+ ));
21
+ }
22
+
23
+ function getAnchorKind(anchor) {
24
+ return anchor?.anchorKind || anchor?.kind || 'line';
25
+ }
26
+
27
+ function isTextSelectionAnchor(anchor) {
28
+ return getAnchorKind(anchor) === 'text'
29
+ && Number.isFinite(anchor?.startIndex)
30
+ && Number.isFinite(anchor?.endIndex)
31
+ && anchor.endIndex > anchor.startIndex;
32
+ }
33
+
34
+ function areAnchorsEqual(left, right) {
35
+ if (left === right) {
36
+ return true;
37
+ }
38
+ if (!left || !right) {
39
+ return false;
40
+ }
41
+
42
+ return getAnchorKind(left) === getAnchorKind(right)
43
+ && (left.startIndex ?? null) === (right.startIndex ?? null)
44
+ && (left.endIndex ?? null) === (right.endIndex ?? null)
45
+ && (left.startLine ?? null) === (right.startLine ?? null)
46
+ && (left.endLine ?? null) === (right.endLine ?? null)
47
+ && (left.anchorQuote ?? left.quote ?? '') === (right.anchorQuote ?? right.quote ?? '');
48
+ }
49
+
50
+ function formatAnchorLabel(anchor) {
51
+ if (!anchor) {
52
+ return 'No source anchor';
53
+ }
54
+
55
+ if ((anchor.kind || anchor.anchorKind) === 'text' && anchor.quote) {
56
+ return anchor.startLine === anchor.endLine
57
+ ? `Line ${anchor.startLine}`
58
+ : `Lines ${anchor.startLine}-${anchor.endLine}`;
59
+ }
60
+
61
+ return anchor.startLine === anchor.endLine
62
+ ? `Line ${anchor.startLine}`
63
+ : `Lines ${anchor.startLine}-${anchor.endLine}`;
64
+ }
65
+
66
+ function getAnchorGroupKey(anchor = {}) {
67
+ return [
68
+ anchor.kind || anchor.anchorKind || 'line',
69
+ anchor.startLine ?? 0,
70
+ anchor.endLine ?? 0,
71
+ anchor.quote || '',
72
+ ].join('::');
73
+ }
74
+
75
+ function isLeafSourceBlock(element) {
76
+ return element && !element.querySelector('[data-source-line]');
77
+ }
78
+
79
+ function parseLineNumber(value) {
80
+ const parsed = Number.parseInt(value || '', 10);
81
+ return Number.isFinite(parsed) ? parsed : null;
82
+ }
83
+
84
+ function overlapsAnchorRange(element, anchor) {
85
+ const startLine = parseLineNumber(element?.getAttribute?.('data-source-line'));
86
+ const endLine = parseLineNumber(element?.getAttribute?.('data-source-line-end')) ?? startLine;
87
+ if (!startLine || !endLine || !anchor) {
88
+ return false;
89
+ }
90
+
91
+ return anchor.startLine <= endLine && anchor.endLine >= startLine;
92
+ }
93
+
94
+ function createNormalizedTextIndex(root) {
95
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
96
+ let normalized = '';
97
+ const map = [];
98
+ let lastWasWhitespace = true;
99
+
100
+ while (walker.nextNode()) {
101
+ const node = walker.currentNode;
102
+ const text = node.textContent || '';
103
+ for (let index = 0; index < text.length; index += 1) {
104
+ const char = text[index];
105
+ const isWhitespace = /\s/.test(char);
106
+ if (isWhitespace) {
107
+ if (lastWasWhitespace) {
108
+ continue;
109
+ }
110
+
111
+ normalized += ' ';
112
+ map.push({ node, offset: index });
113
+ lastWasWhitespace = true;
114
+ continue;
115
+ }
116
+
117
+ normalized += char;
118
+ map.push({ node, offset: index });
119
+ lastWasWhitespace = false;
120
+ }
121
+ }
122
+
123
+ while (normalized.endsWith(' ')) {
124
+ normalized = normalized.slice(0, -1);
125
+ map.pop();
126
+ }
127
+
128
+ return { map, normalized };
129
+ }
130
+
131
+ function findUniqueQuoteRange(root, quote) {
132
+ const normalizedQuote = normalizeCommentQuoteForComparison(quote);
133
+ if (!root || !normalizedQuote) {
134
+ return null;
135
+ }
136
+
137
+ const index = createNormalizedTextIndex(root);
138
+ if (!index.normalized) {
139
+ return null;
140
+ }
141
+
142
+ const matchIndex = index.normalized.indexOf(normalizedQuote);
143
+ if (matchIndex < 0) {
144
+ return null;
145
+ }
146
+ if (index.normalized.indexOf(normalizedQuote, matchIndex + 1) >= 0) {
147
+ return null;
148
+ }
149
+
150
+ const start = index.map[matchIndex];
151
+ const end = index.map[matchIndex + normalizedQuote.length - 1];
152
+ if (!start || !end) {
153
+ return null;
154
+ }
155
+
156
+ const range = document.createRange();
157
+ range.setStart(start.node, start.offset);
158
+ range.setEnd(end.node, end.offset + 1);
159
+ return range;
160
+ }
161
+
162
+ function toRelativeRect(rect, containerRect) {
163
+ return {
164
+ bottom: rect.bottom - containerRect.top,
165
+ height: rect.height,
166
+ left: rect.left - containerRect.left,
167
+ right: rect.right - containerRect.left,
168
+ top: rect.top - containerRect.top,
169
+ width: rect.width,
170
+ };
171
+ }
172
+
173
+ function createRectFromRects(rects = []) {
174
+ if (!Array.isArray(rects) || rects.length === 0) {
175
+ return null;
176
+ }
177
+
178
+ const left = Math.min(...rects.map((rect) => rect.left));
179
+ const top = Math.min(...rects.map((rect) => rect.top));
180
+ const right = Math.max(...rects.map((rect) => rect.right));
181
+ const bottom = Math.max(...rects.map((rect) => rect.bottom));
182
+
183
+ return {
184
+ bottom,
185
+ height: bottom - top,
186
+ left,
187
+ right,
188
+ top,
189
+ width: right - left,
190
+ };
191
+ }
192
+
193
+ function createCommentMarkerContent(count) {
194
+ const fragment = document.createDocumentFragment();
195
+
196
+ const icon = document.createElement('span');
197
+ icon.className = 'comment-marker-icon';
198
+ icon.setAttribute('aria-hidden', 'true');
199
+ icon.innerHTML = `
200
+ <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
201
+ <path d="M3 4.75A1.75 1.75 0 0 1 4.75 3h6.5A1.75 1.75 0 0 1 13 4.75v4.5A1.75 1.75 0 0 1 11.25 11H8.9L6.5 13v-2H4.75A1.75 1.75 0 0 1 3 9.25v-4.5Z" stroke="currentColor" stroke-width="1.35" stroke-linejoin="round"/>
202
+ </svg>
203
+ `;
204
+ fragment.appendChild(icon);
205
+
206
+ if (count > 1) {
207
+ const countBadge = document.createElement('span');
208
+ countBadge.className = 'comment-marker-count';
209
+ countBadge.textContent = String(count);
210
+ fragment.appendChild(countBadge);
211
+ }
212
+
213
+ return fragment;
214
+ }
215
+
216
+ export class CommentUiController {
217
+ constructor({
218
+ commentSelectionButton,
219
+ commentsDrawer,
220
+ commentsDrawerEmpty,
221
+ commentsDrawerList,
222
+ commentsToggleButton,
223
+ editorContainer,
224
+ onCreateThread,
225
+ onNavigateToLine,
226
+ onReplyToThread,
227
+ onResolveThread,
228
+ previewContainer,
229
+ previewElement,
230
+ }) {
231
+ this.commentSelectionButton = commentSelectionButton;
232
+ this.commentsDrawer = commentsDrawer;
233
+ this.commentsDrawerEmpty = commentsDrawerEmpty;
234
+ this.commentsDrawerList = commentsDrawerList;
235
+ this.commentsToggleButton = commentsToggleButton;
236
+ this.editorContainer = editorContainer;
237
+ this.onCreateThread = onCreateThread;
238
+ this.onNavigateToLine = onNavigateToLine;
239
+ this.onReplyToThread = onReplyToThread;
240
+ this.onResolveThread = onResolveThread;
241
+ this.previewContainer = previewContainer;
242
+ this.previewElement = previewElement;
243
+
244
+ this.currentFile = null;
245
+ this.fileKind = 'markdown';
246
+ this.supported = false;
247
+ this.drawerOpen = false;
248
+ this.threads = [];
249
+ this.selectionAnchor = null;
250
+ this.pendingSelectionAnchor = null;
251
+ this.committedSelectionAnchor = null;
252
+ this.selectionRevealTimer = 0;
253
+ this.pointerSelecting = false;
254
+ this.session = null;
255
+ this.activeCard = null;
256
+ this.editorLayer = null;
257
+ this.previewLayer = null;
258
+ this.previewHighlightLayer = null;
259
+ this.cardRoot = null;
260
+ this.pendingCardFocusElement = null;
261
+ this.layoutFrame = 0;
262
+ this.timeFormatter = new Intl.DateTimeFormat(undefined, {
263
+ day: 'numeric',
264
+ hour: 'numeric',
265
+ minute: '2-digit',
266
+ month: 'short',
267
+ });
268
+ this.handleEditorScroll = () => this.scheduleLayoutRefresh();
269
+ this.handlePreviewScroll = () => this.scheduleLayoutRefresh();
270
+ this.handleWindowResize = () => this.scheduleLayoutRefresh();
271
+ this.handleEditorPointerDown = (event) => {
272
+ if (event.button !== 0 || !this.supported || !this.session) {
273
+ return;
274
+ }
275
+ if (!event.target?.closest?.('.cm-editor') || event.target?.closest?.('.comment-editor-layer')) {
276
+ return;
277
+ }
278
+
279
+ this.pointerSelecting = true;
280
+ this.clearSelectionRevealTimer();
281
+ if (this.committedSelectionAnchor) {
282
+ this.committedSelectionAnchor = null;
283
+ this.scheduleLayoutRefresh();
284
+ }
285
+ };
286
+ this.handleDocumentPointerUp = () => {
287
+ if (!this.pointerSelecting) {
288
+ return;
289
+ }
290
+
291
+ requestAnimationFrame(() => {
292
+ const anchor = this.supported ? (this.session?.getCurrentSelectionCommentAnchor?.() ?? null) : null;
293
+ this.pointerSelecting = false;
294
+ this.selectionAnchor = anchor;
295
+ this.renderToolbar();
296
+ this.pendingSelectionAnchor = isTextSelectionAnchor(anchor) ? anchor : null;
297
+ this.clearSelectionRevealTimer();
298
+ this.committedSelectionAnchor = (
299
+ isTextSelectionAnchor(anchor) && this.activeCard?.mode !== 'create'
300
+ ) ? anchor : null;
301
+ this.scheduleLayoutRefresh();
302
+ });
303
+ };
304
+ this.handleEditorFocusOut = (event) => {
305
+ const nextTarget = event.relatedTarget;
306
+ if (
307
+ nextTarget instanceof Node
308
+ && (
309
+ this.editorContainer?.contains(nextTarget)
310
+ || this.cardRoot?.contains(nextTarget)
311
+ || this.commentSelectionButton?.contains(nextTarget)
312
+ )
313
+ ) {
314
+ return;
315
+ }
316
+
317
+ this.clearSelectionRevealTimer();
318
+ this.pendingSelectionAnchor = null;
319
+ this.committedSelectionAnchor = null;
320
+ this.scheduleLayoutRefresh();
321
+ };
322
+ this.handleDocumentPointerDown = (event) => {
323
+ if (!this.activeCard || !this.cardRoot) {
324
+ return;
325
+ }
326
+ if (this.cardRoot.contains(event.target)) {
327
+ return;
328
+ }
329
+ this.closeCard();
330
+ };
331
+ this.handleDocumentKeyDown = (event) => {
332
+ if (event.key === 'Escape' && this.activeCard) {
333
+ this.closeCard();
334
+ }
335
+ if (event.key === 'Escape' && this.committedSelectionAnchor) {
336
+ this.clearSelectionRevealTimer();
337
+ this.pendingSelectionAnchor = null;
338
+ this.committedSelectionAnchor = null;
339
+ this.scheduleLayoutRefresh();
340
+ }
341
+ };
342
+
343
+ this.commentSelectionButton?.addEventListener('click', () => {
344
+ this.openComposerForSelection('toolbar');
345
+ });
346
+ this.commentsToggleButton?.addEventListener('click', () => {
347
+ this.drawerOpen = !this.drawerOpen;
348
+ this.render();
349
+ });
350
+ this.previewContainer?.addEventListener('scroll', this.handlePreviewScroll, { passive: true });
351
+ this.editorContainer?.addEventListener('pointerdown', this.handleEditorPointerDown);
352
+ this.editorContainer?.addEventListener('focusout', this.handleEditorFocusOut);
353
+ window.addEventListener('resize', this.handleWindowResize);
354
+ document.addEventListener('pointerup', this.handleDocumentPointerUp);
355
+ document.addEventListener('pointercancel', this.handleDocumentPointerUp);
356
+ document.addEventListener('pointerdown', this.handleDocumentPointerDown);
357
+ document.addEventListener('keydown', this.handleDocumentKeyDown);
358
+ }
359
+
360
+ destroy() {
361
+ if (this.layoutFrame) {
362
+ cancelAnimationFrame(this.layoutFrame);
363
+ this.layoutFrame = 0;
364
+ }
365
+ this.attachSession(null);
366
+ this.previewContainer?.removeEventListener('scroll', this.handlePreviewScroll);
367
+ this.editorContainer?.removeEventListener('pointerdown', this.handleEditorPointerDown);
368
+ this.editorContainer?.removeEventListener('focusout', this.handleEditorFocusOut);
369
+ window.removeEventListener('resize', this.handleWindowResize);
370
+ document.removeEventListener('pointerup', this.handleDocumentPointerUp);
371
+ document.removeEventListener('pointercancel', this.handleDocumentPointerUp);
372
+ document.removeEventListener('pointerdown', this.handleDocumentPointerDown);
373
+ document.removeEventListener('keydown', this.handleDocumentKeyDown);
374
+ this.cardRoot?.remove();
375
+ this.editorLayer?.remove();
376
+ this.previewLayer?.remove();
377
+ this.pendingCardFocusElement = null;
378
+ }
379
+
380
+ attachSession(session) {
381
+ this.session?.getScrollContainer?.()?.removeEventListener('scroll', this.handleEditorScroll);
382
+ this.session = session;
383
+ this.selectionAnchor = session?.getCurrentSelectionCommentAnchor?.() ?? null;
384
+ this.pendingSelectionAnchor = null;
385
+ this.committedSelectionAnchor = null;
386
+ this.clearSelectionRevealTimer();
387
+ this.pointerSelecting = false;
388
+ session?.getScrollContainer?.()?.addEventListener('scroll', this.handleEditorScroll, { passive: true });
389
+ this.render();
390
+ }
391
+
392
+ setCurrentFile(filePath, { fileKind = 'markdown', supported = false } = {}) {
393
+ const didChangeFile = this.currentFile !== filePath;
394
+ this.currentFile = filePath;
395
+ this.fileKind = fileKind;
396
+ this.supported = Boolean(filePath && supported);
397
+ if (didChangeFile) {
398
+ this.drawerOpen = false;
399
+ this.threads = [];
400
+ this.selectionAnchor = null;
401
+ this.pendingSelectionAnchor = null;
402
+ this.committedSelectionAnchor = null;
403
+ this.clearSelectionRevealTimer();
404
+ this.pointerSelecting = false;
405
+ this.activeCard = null;
406
+ }
407
+ if (!this.supported) {
408
+ this.drawerOpen = false;
409
+ this.activeCard = null;
410
+ this.threads = [];
411
+ this.selectionAnchor = null;
412
+ this.pendingSelectionAnchor = null;
413
+ this.committedSelectionAnchor = null;
414
+ this.clearSelectionRevealTimer();
415
+ this.pointerSelecting = false;
416
+ }
417
+ this.render();
418
+ }
419
+
420
+ setSelectionAnchor(anchor) {
421
+ this.selectionAnchor = this.supported ? anchor : null;
422
+ if (!this.supported || !this.selectionAnchor) {
423
+ this.pendingSelectionAnchor = null;
424
+ this.committedSelectionAnchor = null;
425
+ this.clearSelectionRevealTimer();
426
+ this.renderToolbar();
427
+ this.scheduleLayoutRefresh();
428
+ return;
429
+ }
430
+
431
+ if (!isTextSelectionAnchor(this.selectionAnchor)) {
432
+ this.pendingSelectionAnchor = null;
433
+ this.committedSelectionAnchor = null;
434
+ this.clearSelectionRevealTimer();
435
+ this.renderToolbar();
436
+ this.scheduleLayoutRefresh();
437
+ return;
438
+ }
439
+
440
+ this.pendingSelectionAnchor = this.selectionAnchor;
441
+ if (this.activeCard?.mode === 'create') {
442
+ this.clearSelectionRevealTimer();
443
+ this.renderToolbar();
444
+ this.scheduleLayoutRefresh();
445
+ return;
446
+ }
447
+
448
+ if (this.pointerSelecting) {
449
+ this.clearSelectionRevealTimer();
450
+ this.committedSelectionAnchor = null;
451
+ this.renderToolbar();
452
+ this.scheduleLayoutRefresh();
453
+ return;
454
+ }
455
+
456
+ if (areAnchorsEqual(this.committedSelectionAnchor, this.selectionAnchor)) {
457
+ this.renderToolbar();
458
+ this.scheduleLayoutRefresh();
459
+ return;
460
+ }
461
+
462
+ this.scheduleSelectionReveal(this.selectionAnchor);
463
+ this.renderToolbar();
464
+ this.scheduleLayoutRefresh();
465
+ }
466
+
467
+ handleEditorContentChange() {
468
+ if (this.activeCard?.mode === 'create') {
469
+ return;
470
+ }
471
+
472
+ this.clearSelectionRevealTimer();
473
+ this.pendingSelectionAnchor = null;
474
+ this.committedSelectionAnchor = null;
475
+ this.scheduleLayoutRefresh();
476
+ }
477
+
478
+ setThreads(threads = []) {
479
+ this.threads = sortThreads(threads);
480
+ if (
481
+ this.activeCard?.mode === 'group'
482
+ && !this.getThreadGroups().some((group) => group.key === this.activeCard.groupKey)
483
+ ) {
484
+ this.activeCard = null;
485
+ }
486
+ this.render();
487
+ }
488
+
489
+ refreshLayout() {
490
+ this.renderEditorLayer();
491
+ this.renderPreviewLayer();
492
+ this.repositionActiveCard();
493
+ }
494
+
495
+ scheduleLayoutRefresh() {
496
+ if (this.layoutFrame) {
497
+ return;
498
+ }
499
+
500
+ this.layoutFrame = requestAnimationFrame(() => {
501
+ this.layoutFrame = 0;
502
+ this.refreshLayout();
503
+ });
504
+ }
505
+
506
+ render() {
507
+ this.renderToolbar();
508
+ this.renderDrawer();
509
+ this.renderCard();
510
+ this.scheduleLayoutRefresh();
511
+ }
512
+
513
+ renderToolbar() {
514
+ const totalCount = this.threads.length;
515
+ const showControls = this.supported && Boolean(this.session);
516
+ this.commentSelectionButton?.classList.toggle('hidden', !showControls);
517
+ this.commentsToggleButton?.classList.toggle('hidden', !this.supported);
518
+ if (this.commentSelectionButton) {
519
+ this.commentSelectionButton.disabled = !this.selectionAnchor;
520
+ }
521
+ if (this.commentsToggleButton) {
522
+ this.commentsToggleButton.classList.toggle('active', this.drawerOpen);
523
+ this.commentsToggleButton.setAttribute('aria-expanded', String(this.drawerOpen));
524
+ const label = totalCount > 0 ? `Comments ${totalCount}` : 'Comments';
525
+ const labelElement = this.commentsToggleButton.querySelector('.pane-header-btn-label');
526
+ if (labelElement) {
527
+ labelElement.textContent = label;
528
+ } else {
529
+ this.commentsToggleButton.textContent = label;
530
+ }
531
+ }
532
+ }
533
+
534
+ renderDrawer() {
535
+ if (!this.commentsDrawer || !this.commentsDrawerList) {
536
+ return;
537
+ }
538
+
539
+ this.commentsDrawer.classList.toggle('hidden', !this.supported || !this.drawerOpen);
540
+ this.commentsDrawerList.replaceChildren();
541
+ const groups = this.getThreadGroups();
542
+ this.commentsDrawerEmpty?.classList.toggle('hidden', groups.length > 0);
543
+ if (!this.supported || groups.length === 0) {
544
+ return;
545
+ }
546
+
547
+ const fragment = document.createDocumentFragment();
548
+ groups.forEach((group) => {
549
+ const button = document.createElement('button');
550
+ button.type = 'button';
551
+ button.className = 'comments-drawer-item';
552
+ button.classList.toggle('is-active', this.activeCard?.groupKey === group.key);
553
+ button.addEventListener('pointerdown', (event) => {
554
+ event.preventDefault();
555
+ });
556
+ button.addEventListener('click', () => {
557
+ this.openThreadGroup(group, {
558
+ anchor: group.anchor,
559
+ origin: 'drawer',
560
+ sourceRect: button.getBoundingClientRect(),
561
+ });
562
+ });
563
+
564
+ const header = document.createElement('div');
565
+ header.className = 'comments-drawer-item-header';
566
+
567
+ const title = document.createElement('span');
568
+ title.className = 'comments-drawer-item-title';
569
+ title.textContent = formatAnchorLabel(group.anchor);
570
+
571
+ const count = document.createElement('span');
572
+ count.className = 'comments-drawer-item-count';
573
+ count.textContent = String(group.threads.length);
574
+
575
+ header.append(title, count);
576
+
577
+ const quote = document.createElement('p');
578
+ quote.className = 'comments-drawer-item-quote';
579
+ quote.textContent = group.anchor.quote || group.anchor.excerpt || 'Source anchored comment';
580
+
581
+ const footer = document.createElement('div');
582
+ footer.className = 'comments-drawer-item-footer';
583
+ footer.textContent = `${group.threads.length} thread${group.threads.length === 1 ? '' : 's'}`;
584
+
585
+ button.append(header, quote, footer);
586
+ fragment.appendChild(button);
587
+ });
588
+
589
+ this.commentsDrawerList.appendChild(fragment);
590
+ }
591
+
592
+ ensureEditorLayer() {
593
+ if (this.editorLayer?.isConnected && this.editorLayer.parentElement === this.editorContainer) {
594
+ return this.editorLayer;
595
+ }
596
+
597
+ const layer = document.createElement('div');
598
+ layer.className = 'comment-editor-layer';
599
+ this.editorContainer?.appendChild(layer);
600
+ this.editorLayer = layer;
601
+ return layer;
602
+ }
603
+
604
+ renderEditorLayer() {
605
+ const layer = this.ensureEditorLayer();
606
+ layer.replaceChildren();
607
+
608
+ if (!this.supported || !this.session) {
609
+ return;
610
+ }
611
+
612
+ const containerRect = this.editorContainer?.getBoundingClientRect?.();
613
+ if (!containerRect) {
614
+ return;
615
+ }
616
+
617
+ const groups = this.getThreadGroups();
618
+ const occupiedTops = [];
619
+ groups.forEach((group) => {
620
+ const rect = this.session.getCommentAnchorClientRect?.(group.anchor);
621
+ if (!rect) {
622
+ return;
623
+ }
624
+
625
+ const relativeRect = toRelativeRect(rect, containerRect);
626
+ const button = document.createElement('button');
627
+ button.type = 'button';
628
+ button.className = 'comment-editor-badge';
629
+ button.dataset.count = String(group.threads.length);
630
+ button.classList.toggle('is-active', this.activeCard?.groupKey === group.key);
631
+ button.setAttribute('aria-label', `${group.threads.length} comment thread${group.threads.length === 1 ? '' : 's'}`);
632
+ button.appendChild(createCommentMarkerContent(group.threads.length));
633
+ const top = Math.max(relativeRect.top, 8);
634
+ button.style.top = `${top}px`;
635
+ button.style.left = `${Math.max(containerRect.width - 36, 8)}px`;
636
+ button.title = `${group.threads.length} comment${group.threads.length === 1 ? '' : 's'}`;
637
+ button.addEventListener('pointerdown', (event) => {
638
+ event.preventDefault();
639
+ });
640
+ button.addEventListener('click', () => {
641
+ this.openThreadGroup(group, {
642
+ anchor: group.anchor,
643
+ origin: 'editor',
644
+ sourceRect: rect,
645
+ });
646
+ });
647
+ layer.appendChild(button);
648
+ occupiedTops.push(top);
649
+ });
650
+
651
+ if (!this.committedSelectionAnchor || this.activeCard?.mode === 'create') {
652
+ return;
653
+ }
654
+
655
+ const rect = this.session.getCommentAnchorClientRect?.(this.committedSelectionAnchor);
656
+ const chipRect = this.session.getSelectionChipClientRect?.(this.committedSelectionAnchor) ?? rect;
657
+ if (!chipRect) {
658
+ return;
659
+ }
660
+
661
+ const relativeRect = toRelativeRect(chipRect, containerRect);
662
+ if (relativeRect.bottom < 0 || relativeRect.top > containerRect.height) {
663
+ return;
664
+ }
665
+
666
+ let chipTop = clamp(relativeRect.top, 8, Math.max(containerRect.height - COMMENT_CONTROL_SLOT_HEIGHT, 8));
667
+ while (occupiedTops.some((top) => Math.abs(top - chipTop) < (COMMENT_CONTROL_SLOT_HEIGHT - 4))) {
668
+ chipTop = clamp(
669
+ chipTop + COMMENT_CONTROL_SLOT_HEIGHT,
670
+ 8,
671
+ Math.max(containerRect.height - COMMENT_CONTROL_SLOT_HEIGHT, 8),
672
+ );
673
+ }
674
+ const button = document.createElement('button');
675
+ button.type = 'button';
676
+ button.className = 'comment-selection-chip';
677
+ button.textContent = 'Comment';
678
+ button.style.top = `${chipTop}px`;
679
+ button.style.right = `${COMMENT_SELECTION_CHIP_GAP}px`;
680
+ button.addEventListener('pointerdown', (event) => {
681
+ event.preventDefault();
682
+ event.stopPropagation();
683
+ this.openComposerForSelection('editor', button.getBoundingClientRect());
684
+ });
685
+ layer.appendChild(button);
686
+ }
687
+
688
+ ensurePreviewLayer() {
689
+ if (this.previewLayer?.isConnected && this.previewLayer.parentElement === this.previewElement) {
690
+ return this.previewLayer;
691
+ }
692
+
693
+ const highlightLayer = document.createElement('div');
694
+ highlightLayer.className = 'comment-preview-highlights';
695
+ const markerLayer = document.createElement('div');
696
+ markerLayer.className = 'comment-preview-layer';
697
+ this.previewElement?.append(highlightLayer, markerLayer);
698
+ this.previewHighlightLayer = highlightLayer;
699
+ this.previewLayer = markerLayer;
700
+ return markerLayer;
701
+ }
702
+
703
+ renderPreviewLayer() {
704
+ this.ensurePreviewLayer();
705
+ this.previewLayer?.replaceChildren();
706
+ this.previewHighlightLayer?.replaceChildren();
707
+
708
+ if (!this.supported || !this.previewElement) {
709
+ return;
710
+ }
711
+
712
+ const previewRect = this.previewElement.getBoundingClientRect();
713
+ const groups = this.getThreadGroups();
714
+ groups.forEach((group) => {
715
+ const target = this.resolvePreviewTarget(group.anchor);
716
+ if (!target?.bubbleRect) {
717
+ return;
718
+ }
719
+
720
+ target.highlightRects?.forEach((rect) => {
721
+ const highlight = document.createElement('div');
722
+ highlight.className = 'comment-preview-highlight';
723
+ highlight.style.left = `${rect.left - previewRect.left}px`;
724
+ highlight.style.top = `${rect.top - previewRect.top}px`;
725
+ highlight.style.width = `${rect.width}px`;
726
+ highlight.style.height = `${rect.height}px`;
727
+ this.previewHighlightLayer?.appendChild(highlight);
728
+ });
729
+
730
+ const bubble = document.createElement('button');
731
+ bubble.type = 'button';
732
+ bubble.className = 'comment-preview-badge';
733
+ bubble.classList.toggle('is-active', this.activeCard?.groupKey === group.key);
734
+ bubble.setAttribute('aria-label', `${group.threads.length} comment thread${group.threads.length === 1 ? '' : 's'}`);
735
+ bubble.appendChild(createCommentMarkerContent(group.threads.length));
736
+ bubble.style.left = `${clamp(target.bubbleRect.right - previewRect.left + 6, 6, this.previewElement.clientWidth - 34)}px`;
737
+ bubble.style.top = `${Math.max(target.bubbleRect.top - previewRect.top, 6)}px`;
738
+ bubble.title = `${group.threads.length} comment${group.threads.length === 1 ? '' : 's'}`;
739
+ bubble.addEventListener('pointerdown', (event) => {
740
+ event.preventDefault();
741
+ });
742
+ bubble.addEventListener('click', () => {
743
+ this.openThreadGroup(group, {
744
+ anchor: group.anchor,
745
+ origin: 'preview',
746
+ sourceRect: target.bubbleRect,
747
+ });
748
+ });
749
+ this.previewLayer?.appendChild(bubble);
750
+ });
751
+ }
752
+
753
+ resolvePreviewTarget(anchor) {
754
+ if (!this.previewElement || !anchor) {
755
+ return null;
756
+ }
757
+
758
+ const diagramShell = Array.from(this.previewElement.querySelectorAll('.mermaid-shell, .plantuml-shell'))
759
+ .find((element) => overlapsAnchorRange(element, anchor));
760
+ if (diagramShell) {
761
+ return {
762
+ bubbleRect: diagramShell.getBoundingClientRect(),
763
+ highlightRects: [],
764
+ };
765
+ }
766
+
767
+ const candidates = Array.from(this.previewElement.querySelectorAll('[data-source-line]'))
768
+ .filter((element) => isLeafSourceBlock(element) && overlapsAnchorRange(element, anchor));
769
+
770
+ if (anchor.kind === 'text' && anchor.quote) {
771
+ const matches = candidates
772
+ .map((element) => ({ element, range: findUniqueQuoteRange(element, anchor.quote) }))
773
+ .filter((candidate) => candidate.range);
774
+ if (matches.length === 1) {
775
+ const rects = Array.from(matches[0].range.getClientRects());
776
+ const bubbleRect = createRectFromRects(rects) || matches[0].element.getBoundingClientRect();
777
+ return {
778
+ bubbleRect,
779
+ highlightRects: rects,
780
+ };
781
+ }
782
+ }
783
+
784
+ const fallback = candidates[0];
785
+ if (!fallback) {
786
+ return null;
787
+ }
788
+
789
+ return {
790
+ bubbleRect: fallback.getBoundingClientRect(),
791
+ highlightRects: [],
792
+ };
793
+ }
794
+
795
+ ensureCardRoot() {
796
+ if (this.cardRoot?.isConnected && this.cardRoot.parentElement === document.body) {
797
+ return this.cardRoot;
798
+ }
799
+
800
+ const root = document.createElement('div');
801
+ root.className = 'comment-card-root hidden';
802
+ document.body.appendChild(root);
803
+ this.cardRoot = root;
804
+ return root;
805
+ }
806
+
807
+ openComposerForSelection(origin = 'editor', sourceRect = null) {
808
+ const anchor = this.session?.getCurrentSelectionCommentAnchor?.();
809
+ if (!anchor) {
810
+ return;
811
+ }
812
+
813
+ this.selectionAnchor = anchor;
814
+ const nextOrigin = origin === 'editor' && sourceRect ? 'editor-chip' : origin;
815
+ const nextSourceRect = sourceRect ?? (origin === 'toolbar'
816
+ ? this.commentSelectionButton?.getBoundingClientRect?.()
817
+ : this.session?.getCommentAnchorClientRect?.(anchor));
818
+ this.activeCard = {
819
+ anchor,
820
+ mode: 'create',
821
+ origin: nextOrigin,
822
+ replyThreadId: null,
823
+ sourceRect: nextSourceRect,
824
+ };
825
+ this.render();
826
+ }
827
+
828
+ openThreadGroup(group, { anchor, origin, sourceRect }) {
829
+ this.activeCard = {
830
+ anchor,
831
+ groupKey: group.key,
832
+ mode: 'group',
833
+ origin,
834
+ replyThreadId: null,
835
+ sourceRect,
836
+ };
837
+ this.renderDrawer();
838
+ this.renderCard();
839
+ }
840
+
841
+ closeCard() {
842
+ this.activeCard = null;
843
+ this.pendingCardFocusElement = null;
844
+ this.renderCard();
845
+ this.scheduleLayoutRefresh();
846
+ }
847
+
848
+ getThreadGroups() {
849
+ const groups = new Map();
850
+ this.threads.forEach((thread) => {
851
+ const key = getAnchorGroupKey(thread.anchor);
852
+ const existing = groups.get(key);
853
+ if (existing) {
854
+ existing.threads.push(thread);
855
+ return;
856
+ }
857
+
858
+ groups.set(key, {
859
+ anchor: thread.anchor,
860
+ key,
861
+ threads: [thread],
862
+ });
863
+ });
864
+
865
+ return Array.from(groups.values()).sort((left, right) => (
866
+ (left.anchor?.startLine ?? 0) - (right.anchor?.startLine ?? 0)
867
+ ));
868
+ }
869
+
870
+ updateCardSourceRect() {
871
+ if (!this.activeCard) {
872
+ return null;
873
+ }
874
+
875
+ if (this.activeCard.origin === 'editor') {
876
+ return this.session?.getCommentAnchorClientRect?.(this.activeCard.anchor) ?? this.activeCard.sourceRect;
877
+ }
878
+ if (this.activeCard.origin === 'editor-chip') {
879
+ return this.activeCard.sourceRect;
880
+ }
881
+ if (this.activeCard.origin === 'preview') {
882
+ return this.resolvePreviewTarget(this.activeCard.anchor)?.bubbleRect ?? this.activeCard.sourceRect;
883
+ }
884
+ if (this.activeCard.origin === 'toolbar') {
885
+ return this.commentSelectionButton?.getBoundingClientRect?.() ?? this.activeCard.sourceRect;
886
+ }
887
+
888
+ return this.activeCard.sourceRect;
889
+ }
890
+
891
+ renderCard() {
892
+ const root = this.ensureCardRoot();
893
+ root.replaceChildren();
894
+ root.classList.toggle('hidden', !this.activeCard);
895
+ if (!this.activeCard) {
896
+ this.pendingCardFocusElement = null;
897
+ root.style.visibility = '';
898
+ return;
899
+ }
900
+
901
+ this.pendingCardFocusElement = null;
902
+
903
+ const card = document.createElement('section');
904
+ card.className = 'comment-card';
905
+
906
+ const header = document.createElement('div');
907
+ header.className = 'comment-card-header';
908
+
909
+ const titleWrap = document.createElement('div');
910
+ titleWrap.className = 'comment-card-title-wrap';
911
+
912
+ const title = document.createElement('h3');
913
+ title.className = 'comment-card-title';
914
+ title.textContent = this.activeCard.mode === 'create' ? 'New comment' : 'Comment threads';
915
+
916
+ const meta = document.createElement('p');
917
+ meta.className = 'comment-card-meta';
918
+ meta.textContent = formatAnchorLabel(this.activeCard.anchor);
919
+
920
+ titleWrap.append(title, meta);
921
+
922
+ const closeButton = document.createElement('button');
923
+ closeButton.type = 'button';
924
+ closeButton.className = 'comment-card-close';
925
+ closeButton.setAttribute('aria-label', 'Close comments');
926
+ closeButton.textContent = 'Close';
927
+ closeButton.addEventListener('click', () => this.closeCard());
928
+
929
+ header.append(titleWrap, closeButton);
930
+ card.appendChild(header);
931
+
932
+ if (this.activeCard.anchor?.quote) {
933
+ const quote = document.createElement('p');
934
+ quote.className = 'comment-card-quote';
935
+ quote.textContent = this.activeCard.anchor.quote;
936
+ card.appendChild(quote);
937
+ }
938
+
939
+ if (this.activeCard.mode === 'create') {
940
+ card.appendChild(this.createComposer());
941
+ } else {
942
+ const group = this.getThreadGroups().find((entry) => entry.key === this.activeCard.groupKey);
943
+ if (!group) {
944
+ this.closeCard();
945
+ return;
946
+ }
947
+
948
+ group.threads.forEach((thread) => {
949
+ card.appendChild(this.createThreadElement(thread));
950
+ });
951
+ }
952
+
953
+ root.appendChild(card);
954
+ this.flushPendingCardFocus();
955
+ root.style.visibility = 'hidden';
956
+ this.scheduleLayoutRefresh();
957
+ }
958
+
959
+ repositionActiveCard() {
960
+ const card = this.cardRoot?.firstElementChild;
961
+ if (!card || !this.activeCard) {
962
+ if (this.cardRoot) {
963
+ this.cardRoot.style.visibility = '';
964
+ }
965
+ return;
966
+ }
967
+
968
+ this.positionCard(card);
969
+ this.cardRoot.style.visibility = '';
970
+ this.flushPendingCardFocus();
971
+ }
972
+
973
+ createComposer() {
974
+ const form = document.createElement('form');
975
+ form.className = 'comment-card-form';
976
+
977
+ const textarea = document.createElement('textarea');
978
+ textarea.className = 'input comment-card-input';
979
+ textarea.rows = 4;
980
+ textarea.maxLength = COMMENT_BODY_MAX_LENGTH;
981
+ textarea.placeholder = 'Add context, feedback, or a question...';
982
+
983
+ const actions = document.createElement('div');
984
+ actions.className = 'comment-card-actions';
985
+
986
+ const cancel = document.createElement('button');
987
+ cancel.type = 'button';
988
+ cancel.className = 'btn btn-secondary';
989
+ cancel.textContent = 'Cancel';
990
+ cancel.addEventListener('click', () => this.closeCard());
991
+
992
+ const submit = document.createElement('button');
993
+ submit.type = 'submit';
994
+ submit.className = 'btn btn-primary';
995
+ submit.textContent = 'Post comment';
996
+
997
+ actions.append(cancel, submit);
998
+ form.append(textarea, actions);
999
+
1000
+ form.addEventListener('submit', async (event) => {
1001
+ event.preventDefault();
1002
+ const threadId = await this.onCreateThread?.({
1003
+ anchor: this.activeCard?.anchor,
1004
+ body: textarea.value,
1005
+ });
1006
+ if (!threadId) {
1007
+ textarea.focus();
1008
+ return;
1009
+ }
1010
+
1011
+ this.closeCard();
1012
+ });
1013
+
1014
+ this.pendingCardFocusElement = textarea;
1015
+ return form;
1016
+ }
1017
+
1018
+ createThreadElement(thread) {
1019
+ const article = document.createElement('article');
1020
+ article.className = 'comment-thread-card';
1021
+
1022
+ const header = document.createElement('div');
1023
+ header.className = 'comment-thread-card-header';
1024
+
1025
+ const author = document.createElement('span');
1026
+ author.className = 'comment-thread-card-author';
1027
+ author.textContent = thread.createdByName;
1028
+
1029
+ const time = document.createElement('span');
1030
+ time.className = 'comment-thread-card-time';
1031
+ time.textContent = this.formatTimestamp(thread.createdAt);
1032
+
1033
+ const actions = document.createElement('div');
1034
+ actions.className = 'comment-thread-card-actions';
1035
+
1036
+ const jump = document.createElement('button');
1037
+ jump.type = 'button';
1038
+ jump.className = 'comment-thread-card-action';
1039
+ jump.textContent = 'Jump';
1040
+ jump.addEventListener('click', () => this.onNavigateToLine?.(thread.anchor?.startLine ?? 1));
1041
+
1042
+ const reply = document.createElement('button');
1043
+ reply.type = 'button';
1044
+ reply.className = 'comment-thread-card-action';
1045
+ const isReplying = this.activeCard?.replyThreadId === thread.id;
1046
+ reply.classList.toggle('is-active', isReplying);
1047
+ reply.textContent = 'Reply';
1048
+ reply.setAttribute('aria-pressed', String(isReplying));
1049
+ reply.setAttribute('aria-label', isReplying ? 'Cancel reply' : 'Reply to thread');
1050
+ reply.title = isReplying ? 'Cancel reply' : 'Reply to thread';
1051
+ reply.addEventListener('click', () => {
1052
+ this.activeCard = {
1053
+ ...this.activeCard,
1054
+ replyThreadId: isReplying ? null : thread.id,
1055
+ };
1056
+ this.renderCard();
1057
+ });
1058
+
1059
+ const resolve = document.createElement('button');
1060
+ resolve.type = 'button';
1061
+ resolve.className = 'comment-thread-card-action is-danger';
1062
+ resolve.textContent = 'Resolve';
1063
+ resolve.addEventListener('click', async () => {
1064
+ await this.onResolveThread?.(thread.id);
1065
+ });
1066
+
1067
+ actions.append(jump, reply, resolve);
1068
+ header.append(author, time, actions);
1069
+
1070
+ article.append(header);
1071
+
1072
+ thread.messages.forEach((message) => {
1073
+ article.appendChild(this.createMessageElement(message));
1074
+ });
1075
+
1076
+ if (this.activeCard?.replyThreadId === thread.id) {
1077
+ article.appendChild(this.createReplyComposer(thread));
1078
+ }
1079
+
1080
+ return article;
1081
+ }
1082
+
1083
+ createMessageElement(message) {
1084
+ const container = document.createElement('div');
1085
+ container.className = 'comment-message-card';
1086
+
1087
+ const meta = document.createElement('div');
1088
+ meta.className = 'comment-message-card-meta';
1089
+
1090
+ const author = document.createElement('span');
1091
+ author.className = 'comment-message-card-author';
1092
+ author.textContent = message.userName;
1093
+
1094
+ const time = document.createElement('span');
1095
+ time.className = 'comment-message-card-time';
1096
+ time.textContent = this.formatTimestamp(message.createdAt);
1097
+
1098
+ const body = document.createElement('p');
1099
+ body.className = 'comment-message-card-body';
1100
+ body.textContent = message.body;
1101
+
1102
+ meta.append(author, time);
1103
+ container.append(meta, body);
1104
+ return container;
1105
+ }
1106
+
1107
+ createReplyComposer(thread) {
1108
+ const form = document.createElement('form');
1109
+ form.className = 'comment-reply-form';
1110
+
1111
+ const textarea = document.createElement('textarea');
1112
+ textarea.className = 'input comment-card-input';
1113
+ textarea.rows = 3;
1114
+ textarea.maxLength = COMMENT_BODY_MAX_LENGTH;
1115
+ textarea.placeholder = 'Reply to thread...';
1116
+
1117
+ const actions = document.createElement('div');
1118
+ actions.className = 'comment-card-actions';
1119
+
1120
+ const cancel = document.createElement('button');
1121
+ cancel.type = 'button';
1122
+ cancel.className = 'btn btn-secondary';
1123
+ cancel.textContent = 'Cancel';
1124
+ cancel.addEventListener('click', () => {
1125
+ this.activeCard = {
1126
+ ...this.activeCard,
1127
+ replyThreadId: null,
1128
+ };
1129
+ this.renderCard();
1130
+ });
1131
+
1132
+ const submit = document.createElement('button');
1133
+ submit.type = 'submit';
1134
+ submit.className = 'btn btn-primary';
1135
+ submit.textContent = 'Reply';
1136
+
1137
+ actions.append(cancel, submit);
1138
+ form.append(textarea, actions);
1139
+ this.pendingCardFocusElement = textarea;
1140
+
1141
+ form.addEventListener('submit', async (event) => {
1142
+ event.preventDefault();
1143
+ const messageId = await this.onReplyToThread?.(thread.id, textarea.value);
1144
+ if (!messageId) {
1145
+ textarea.focus();
1146
+ return;
1147
+ }
1148
+
1149
+ this.activeCard = {
1150
+ ...this.activeCard,
1151
+ replyThreadId: null,
1152
+ };
1153
+ this.renderCard();
1154
+ });
1155
+
1156
+ return form;
1157
+ }
1158
+
1159
+ positionCard(card) {
1160
+ const sourceRect = this.updateCardSourceRect();
1161
+ const viewportWidth = window.innerWidth;
1162
+ const viewportHeight = window.innerHeight;
1163
+ const cardRect = card.getBoundingClientRect();
1164
+ const fallbackLeft = clamp((viewportWidth - cardRect.width) / 2, 16, viewportWidth - cardRect.width - 16);
1165
+ const fallbackTop = clamp((viewportHeight - cardRect.height) / 4, 16, viewportHeight - cardRect.height - 16);
1166
+
1167
+ let left = fallbackLeft;
1168
+ let top = fallbackTop;
1169
+
1170
+ if (sourceRect) {
1171
+ left = clamp(
1172
+ sourceRect.left,
1173
+ 16,
1174
+ viewportWidth - Math.min(cardRect.width, COMMENT_CARD_WIDTH) - 16,
1175
+ );
1176
+ top = sourceRect.bottom + COMMENT_CARD_OFFSET;
1177
+ if (top + cardRect.height > viewportHeight - 16) {
1178
+ top = Math.max(sourceRect.top - cardRect.height - COMMENT_CARD_OFFSET, 16);
1179
+ }
1180
+ }
1181
+
1182
+ this.cardRoot.style.left = `${left}px`;
1183
+ this.cardRoot.style.top = `${top}px`;
1184
+ this.cardRoot.style.width = `${Math.min(Math.max(cardRect.width, COMMENT_CARD_WIDTH), viewportWidth - 32)}px`;
1185
+ }
1186
+
1187
+ formatTimestamp(value) {
1188
+ if (!Number.isFinite(value)) {
1189
+ return '';
1190
+ }
1191
+
1192
+ try {
1193
+ return this.timeFormatter.format(new Date(value));
1194
+ } catch {
1195
+ return '';
1196
+ }
1197
+ }
1198
+
1199
+ flushPendingCardFocus() {
1200
+ const element = this.pendingCardFocusElement;
1201
+ if (!(element instanceof HTMLElement) || !element.isConnected) {
1202
+ this.pendingCardFocusElement = null;
1203
+ return;
1204
+ }
1205
+
1206
+ this.pendingCardFocusElement = null;
1207
+ element.focus({ preventScroll: true });
1208
+ }
1209
+
1210
+ clearSelectionRevealTimer() {
1211
+ if (!this.selectionRevealTimer) {
1212
+ return;
1213
+ }
1214
+
1215
+ clearTimeout(this.selectionRevealTimer);
1216
+ this.selectionRevealTimer = 0;
1217
+ }
1218
+
1219
+ scheduleSelectionReveal(anchor) {
1220
+ this.clearSelectionRevealTimer();
1221
+ this.selectionRevealTimer = window.setTimeout(() => {
1222
+ this.selectionRevealTimer = 0;
1223
+ if (
1224
+ this.pointerSelecting
1225
+ || this.activeCard?.mode === 'create'
1226
+ || !areAnchorsEqual(this.pendingSelectionAnchor, anchor)
1227
+ ) {
1228
+ return;
1229
+ }
1230
+
1231
+ this.committedSelectionAnchor = anchor;
1232
+ this.scheduleLayoutRefresh();
1233
+ }, COMMENT_SELECTION_REVEAL_DELAY_MS);
1234
+ }
1235
+ }