@tldraw/commenting 0.0.0-bootstrap → 5.3.0-canary.04044ed9e96d

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 (338) hide show
  1. package/README.md +0 -2
  2. package/commenting.css +1152 -350
  3. package/dist-cjs/canvas/anchor-lifecycle.js +148 -0
  4. package/dist-cjs/canvas/anchor-lifecycle.js.map +7 -0
  5. package/dist-cjs/{ui/avatar.js → canvas/canvas-events.js} +14 -22
  6. package/dist-cjs/canvas/canvas-events.js.map +7 -0
  7. package/dist-cjs/canvas/cluster-badge.js +121 -0
  8. package/dist-cjs/canvas/cluster-badge.js.map +7 -0
  9. package/dist-cjs/canvas/cluster-fade.js +85 -0
  10. package/dist-cjs/canvas/cluster-fade.js.map +7 -0
  11. package/dist-cjs/canvas/cluster-input.js +53 -3
  12. package/dist-cjs/canvas/cluster-input.js.map +2 -2
  13. package/dist-cjs/canvas/cluster-model.js +252 -0
  14. package/dist-cjs/canvas/cluster-model.js.map +7 -0
  15. package/dist-cjs/canvas/comment-body.js +1 -1
  16. package/dist-cjs/canvas/comment-body.js.map +2 -2
  17. package/dist-cjs/canvas/comment-drafts.js +54 -0
  18. package/dist-cjs/canvas/comment-drafts.js.map +7 -0
  19. package/dist-cjs/canvas/comment-mutations.js +152 -0
  20. package/dist-cjs/canvas/comment-mutations.js.map +7 -0
  21. package/dist-cjs/canvas/comment-reactions.js +160 -0
  22. package/dist-cjs/canvas/comment-reactions.js.map +7 -0
  23. package/dist-cjs/canvas/comment-render.js +9 -6
  24. package/dist-cjs/canvas/comment-render.js.map +2 -2
  25. package/dist-cjs/canvas/comment-store.js +19 -9
  26. package/dist-cjs/canvas/comment-store.js.map +2 -2
  27. package/dist-cjs/canvas/comment-tool.js +63 -14
  28. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  29. package/dist-cjs/canvas/comments-filter-menu.js +17 -37
  30. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  31. package/dist-cjs/canvas/comments-overflow-menu.js +25 -22
  32. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  33. package/dist-cjs/canvas/comments-overlay.js +151 -989
  34. package/dist-cjs/canvas/comments-overlay.js.map +3 -3
  35. package/dist-cjs/canvas/comments-sidebar.js +49 -41
  36. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  37. package/dist-cjs/canvas/comments-visibility-toggle.js +45 -0
  38. package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
  39. package/dist-cjs/canvas/context.js +17 -0
  40. package/dist-cjs/canvas/context.js.map +7 -0
  41. package/dist-cjs/canvas/hooks.js +28 -3
  42. package/dist-cjs/canvas/hooks.js.map +2 -2
  43. package/dist-cjs/canvas/license.js.map +1 -1
  44. package/dist-cjs/canvas/mobile-placement.js +91 -0
  45. package/dist-cjs/canvas/mobile-placement.js.map +7 -0
  46. package/dist-cjs/canvas/options.js +57 -2
  47. package/dist-cjs/canvas/options.js.map +2 -2
  48. package/dist-cjs/canvas/pending-composer.js +134 -0
  49. package/dist-cjs/canvas/pending-composer.js.map +7 -0
  50. package/dist-cjs/canvas/pin-stacking.js +81 -0
  51. package/dist-cjs/canvas/pin-stacking.js.map +7 -0
  52. package/dist-cjs/canvas/region-box.js +113 -0
  53. package/dist-cjs/canvas/region-box.js.map +7 -0
  54. package/dist-cjs/canvas/sidebar-filters.js +2 -2
  55. package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
  56. package/dist-cjs/canvas/state.js +28 -14
  57. package/dist-cjs/canvas/state.js.map +2 -2
  58. package/dist-cjs/canvas/thread-pin.js +304 -0
  59. package/dist-cjs/canvas/thread-pin.js.map +7 -0
  60. package/dist-cjs/canvas/thread-preview.js +177 -0
  61. package/dist-cjs/canvas/thread-preview.js.map +7 -0
  62. package/dist-cjs/canvas/thread-stack.js +199 -0
  63. package/dist-cjs/canvas/thread-stack.js.map +7 -0
  64. package/dist-cjs/canvas/thread-state.js +82 -21
  65. package/dist-cjs/canvas/thread-state.js.map +2 -2
  66. package/dist-cjs/canvas/thread-view.js +450 -0
  67. package/dist-cjs/canvas/thread-view.js.map +7 -0
  68. package/dist-cjs/clustering/computeClusterTable.js +6 -6
  69. package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
  70. package/dist-cjs/clustering/replay.js +89 -22
  71. package/dist-cjs/clustering/replay.js.map +3 -3
  72. package/dist-cjs/clustering/runtime.js +126 -7
  73. package/dist-cjs/clustering/runtime.js.map +2 -2
  74. package/dist-cjs/clustering/types.js.map +1 -1
  75. package/dist-cjs/index.d.ts +904 -353
  76. package/dist-cjs/index.js +52 -28
  77. package/dist-cjs/index.js.map +2 -2
  78. package/dist-cjs/ui/byline.js +10 -6
  79. package/dist-cjs/ui/byline.js.map +2 -2
  80. package/dist-cjs/ui/comment-card.js +15 -8
  81. package/dist-cjs/ui/comment-card.js.map +2 -2
  82. package/dist-cjs/ui/comment-composer.js +124 -39
  83. package/dist-cjs/ui/comment-composer.js.map +2 -2
  84. package/dist-cjs/ui/comment-pin.js +11 -2
  85. package/dist-cjs/ui/comment-pin.js.map +2 -2
  86. package/dist-cjs/ui/comment-thread.js +9 -7
  87. package/dist-cjs/ui/comment-thread.js.map +2 -2
  88. package/dist-cjs/ui/comments-list.js +39 -40
  89. package/dist-cjs/ui/comments-list.js.map +2 -2
  90. package/dist-cjs/ui/count-badge.js +3 -2
  91. package/dist-cjs/ui/count-badge.js.map +2 -2
  92. package/dist-cjs/ui/emoji-picker.js +54 -0
  93. package/dist-cjs/ui/emoji-picker.js.map +7 -0
  94. package/dist-cjs/ui/empty-state.js +3 -3
  95. package/dist-cjs/ui/empty-state.js.map +2 -2
  96. package/dist-cjs/ui/format-time.js +16 -1
  97. package/dist-cjs/ui/format-time.js.map +2 -2
  98. package/dist-cjs/ui/icons.js +115 -0
  99. package/dist-cjs/ui/icons.js.map +7 -0
  100. package/dist-cjs/ui/reaction-picker.js +71 -0
  101. package/dist-cjs/ui/reaction-picker.js.map +7 -0
  102. package/dist-cjs/ui/reaction.js +58 -6
  103. package/dist-cjs/ui/reaction.js.map +2 -2
  104. package/dist-cjs/ui/reactions.js +20 -7
  105. package/dist-cjs/ui/reactions.js.map +2 -2
  106. package/dist-cjs/ui/{mention.js → reply-count.js} +9 -11
  107. package/dist-cjs/ui/reply-count.js.map +7 -0
  108. package/dist-cjs/ui/send-button.js +13 -1
  109. package/dist-cjs/ui/send-button.js.map +2 -2
  110. package/dist-cjs/ui/{comment-text.js → visual-viewport.js} +12 -9
  111. package/dist-cjs/ui/visual-viewport.js.map +7 -0
  112. package/dist-esm/canvas/anchor-lifecycle.mjs +128 -0
  113. package/dist-esm/canvas/anchor-lifecycle.mjs.map +7 -0
  114. package/dist-esm/canvas/canvas-events.mjs +15 -0
  115. package/dist-esm/canvas/canvas-events.mjs.map +7 -0
  116. package/dist-esm/canvas/cluster-badge.mjs +106 -0
  117. package/dist-esm/canvas/cluster-badge.mjs.map +7 -0
  118. package/dist-esm/canvas/cluster-fade.mjs +65 -0
  119. package/dist-esm/canvas/cluster-fade.mjs.map +7 -0
  120. package/dist-esm/canvas/cluster-input.mjs +54 -4
  121. package/dist-esm/canvas/cluster-input.mjs.map +2 -2
  122. package/dist-esm/canvas/cluster-model.mjs +236 -0
  123. package/dist-esm/canvas/cluster-model.mjs.map +7 -0
  124. package/dist-esm/canvas/comment-body.mjs +1 -1
  125. package/dist-esm/canvas/comment-body.mjs.map +2 -2
  126. package/dist-esm/canvas/comment-drafts.mjs +34 -0
  127. package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
  128. package/dist-esm/canvas/comment-mutations.mjs +132 -0
  129. package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
  130. package/dist-esm/canvas/comment-reactions.mjs +145 -0
  131. package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
  132. package/dist-esm/canvas/comment-render.mjs +10 -7
  133. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  134. package/dist-esm/canvas/comment-store.mjs +19 -9
  135. package/dist-esm/canvas/comment-store.mjs.map +2 -2
  136. package/dist-esm/canvas/comment-tool.mjs +66 -17
  137. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  138. package/dist-esm/canvas/comments-filter-menu.mjs +17 -36
  139. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  140. package/dist-esm/canvas/comments-overflow-menu.mjs +27 -22
  141. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  142. package/dist-esm/canvas/comments-overlay.mjs +157 -1010
  143. package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
  144. package/dist-esm/canvas/comments-sidebar.mjs +53 -46
  145. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  146. package/dist-esm/canvas/comments-visibility-toggle.mjs +25 -0
  147. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
  148. package/dist-esm/canvas/context.mjs +1 -0
  149. package/dist-esm/canvas/context.mjs.map +7 -0
  150. package/dist-esm/canvas/hooks.mjs +35 -5
  151. package/dist-esm/canvas/hooks.mjs.map +2 -2
  152. package/dist-esm/canvas/license.mjs.map +1 -1
  153. package/dist-esm/canvas/mobile-placement.mjs +71 -0
  154. package/dist-esm/canvas/mobile-placement.mjs.map +7 -0
  155. package/dist-esm/canvas/options.mjs +59 -3
  156. package/dist-esm/canvas/options.mjs.map +2 -2
  157. package/dist-esm/canvas/pending-composer.mjs +126 -0
  158. package/dist-esm/canvas/pending-composer.mjs.map +7 -0
  159. package/dist-esm/canvas/pin-stacking.mjs +61 -0
  160. package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
  161. package/dist-esm/canvas/region-box.mjs +93 -0
  162. package/dist-esm/canvas/region-box.mjs.map +7 -0
  163. package/dist-esm/canvas/sidebar-filters.mjs +2 -2
  164. package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
  165. package/dist-esm/canvas/state.mjs +29 -19
  166. package/dist-esm/canvas/state.mjs.map +2 -2
  167. package/dist-esm/canvas/thread-pin.mjs +310 -0
  168. package/dist-esm/canvas/thread-pin.mjs.map +7 -0
  169. package/dist-esm/canvas/thread-preview.mjs +163 -0
  170. package/dist-esm/canvas/thread-preview.mjs.map +7 -0
  171. package/dist-esm/canvas/thread-stack.mjs +189 -0
  172. package/dist-esm/canvas/thread-stack.mjs.map +7 -0
  173. package/dist-esm/canvas/thread-state.mjs +86 -22
  174. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  175. package/dist-esm/canvas/thread-view.mjs +461 -0
  176. package/dist-esm/canvas/thread-view.mjs.map +7 -0
  177. package/dist-esm/clustering/computeClusterTable.mjs +6 -6
  178. package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
  179. package/dist-esm/clustering/replay.mjs +89 -22
  180. package/dist-esm/clustering/replay.mjs.map +3 -3
  181. package/dist-esm/clustering/runtime.mjs +126 -7
  182. package/dist-esm/clustering/runtime.mjs.map +2 -2
  183. package/dist-esm/index.d.mts +904 -353
  184. package/dist-esm/index.mjs +102 -43
  185. package/dist-esm/index.mjs.map +2 -2
  186. package/dist-esm/ui/byline.mjs +11 -7
  187. package/dist-esm/ui/byline.mjs.map +2 -2
  188. package/dist-esm/ui/comment-card.mjs +15 -8
  189. package/dist-esm/ui/comment-card.mjs.map +2 -2
  190. package/dist-esm/ui/comment-composer.mjs +135 -39
  191. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  192. package/dist-esm/ui/comment-pin.mjs +11 -2
  193. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  194. package/dist-esm/ui/comment-thread.mjs +9 -7
  195. package/dist-esm/ui/comment-thread.mjs.map +2 -2
  196. package/dist-esm/ui/comments-list.mjs +51 -50
  197. package/dist-esm/ui/comments-list.mjs.map +2 -2
  198. package/dist-esm/ui/count-badge.mjs +3 -2
  199. package/dist-esm/ui/count-badge.mjs.map +2 -2
  200. package/dist-esm/ui/emoji-picker.mjs +34 -0
  201. package/dist-esm/ui/emoji-picker.mjs.map +7 -0
  202. package/dist-esm/ui/empty-state.mjs +3 -3
  203. package/dist-esm/ui/empty-state.mjs.map +2 -2
  204. package/dist-esm/ui/format-time.mjs +16 -1
  205. package/dist-esm/ui/format-time.mjs.map +2 -2
  206. package/dist-esm/ui/icons.mjs +95 -0
  207. package/dist-esm/ui/icons.mjs.map +7 -0
  208. package/dist-esm/ui/reaction-picker.mjs +59 -0
  209. package/dist-esm/ui/reaction-picker.mjs.map +7 -0
  210. package/dist-esm/ui/reaction.mjs +59 -7
  211. package/dist-esm/ui/reaction.mjs.map +2 -2
  212. package/dist-esm/ui/reactions.mjs +21 -8
  213. package/dist-esm/ui/reactions.mjs.map +2 -2
  214. package/dist-esm/ui/reply-count.mjs +9 -0
  215. package/dist-esm/ui/reply-count.mjs.map +7 -0
  216. package/dist-esm/ui/send-button.mjs +13 -1
  217. package/dist-esm/ui/send-button.mjs.map +2 -2
  218. package/dist-esm/ui/visual-viewport.mjs +12 -0
  219. package/dist-esm/ui/visual-viewport.mjs.map +7 -0
  220. package/package.json +6 -6
  221. package/src/canvas/anchor-lifecycle.test.ts +290 -0
  222. package/src/canvas/anchor-lifecycle.ts +191 -0
  223. package/src/canvas/canvas-events.ts +18 -0
  224. package/src/canvas/canvas.css +464 -55
  225. package/src/canvas/cluster-badge.tsx +133 -0
  226. package/src/canvas/cluster-fade.ts +102 -0
  227. package/src/canvas/cluster-input.test.ts +178 -36
  228. package/src/canvas/cluster-input.ts +95 -9
  229. package/src/canvas/cluster-model.ts +378 -0
  230. package/src/canvas/comment-body.tsx +1 -1
  231. package/src/canvas/comment-drafts.test.ts +55 -0
  232. package/src/canvas/comment-drafts.ts +46 -0
  233. package/src/canvas/comment-mutations.test.ts +452 -0
  234. package/src/canvas/comment-mutations.ts +299 -0
  235. package/src/canvas/comment-reactions.test.ts +154 -0
  236. package/src/canvas/comment-reactions.tsx +246 -0
  237. package/src/canvas/comment-render.test.ts +1 -1
  238. package/src/canvas/comment-render.ts +17 -13
  239. package/src/canvas/comment-store.ts +68 -33
  240. package/src/canvas/comment-tool.test.ts +162 -0
  241. package/src/canvas/comment-tool.tsx +103 -25
  242. package/src/canvas/comments-filter-menu.tsx +20 -36
  243. package/src/canvas/comments-overflow-menu.tsx +17 -19
  244. package/src/canvas/comments-overlay.tsx +252 -1369
  245. package/src/canvas/comments-sidebar.test.ts +45 -0
  246. package/src/canvas/comments-sidebar.tsx +112 -73
  247. package/src/canvas/comments-visibility-toggle.tsx +25 -0
  248. package/src/canvas/context.ts +48 -0
  249. package/src/canvas/hooks.test.ts +94 -0
  250. package/src/canvas/hooks.ts +66 -11
  251. package/src/canvas/license.ts +1 -1
  252. package/src/canvas/mobile-placement.ts +115 -0
  253. package/src/canvas/options.test.ts +176 -4
  254. package/src/canvas/options.ts +276 -18
  255. package/src/canvas/pending-composer.tsx +157 -0
  256. package/src/canvas/pin-stacking.test.ts +224 -0
  257. package/src/canvas/pin-stacking.ts +103 -0
  258. package/src/canvas/region-box.tsx +124 -0
  259. package/src/canvas/sidebar-filters.ts +4 -3
  260. package/src/canvas/state.ts +88 -39
  261. package/src/canvas/thread-pin.tsx +418 -0
  262. package/src/canvas/thread-preview.test.ts +91 -0
  263. package/src/canvas/thread-preview.tsx +294 -0
  264. package/src/canvas/thread-stack.tsx +242 -0
  265. package/src/canvas/thread-state.test.ts +260 -0
  266. package/src/canvas/thread-state.ts +171 -37
  267. package/src/canvas/thread-view.test.ts +72 -0
  268. package/src/canvas/thread-view.tsx +614 -0
  269. package/src/clustering/computeClusterTable.test.ts +24 -23
  270. package/src/clustering/computeClusterTable.ts +17 -8
  271. package/src/clustering/replay.test.ts +0 -7
  272. package/src/clustering/replay.ts +131 -32
  273. package/src/clustering/runtime.test.ts +267 -13
  274. package/src/clustering/runtime.ts +176 -19
  275. package/src/clustering/schedule.test.ts +0 -6
  276. package/src/clustering/screen-offsets.test.ts +171 -0
  277. package/src/clustering/types.ts +19 -9
  278. package/src/index.ts +91 -42
  279. package/src/ui/byline.tsx +21 -9
  280. package/src/ui/comment-card.tsx +20 -10
  281. package/src/ui/comment-composer.tsx +178 -56
  282. package/src/ui/comment-pin.tsx +13 -2
  283. package/src/ui/comment-thread.tsx +13 -6
  284. package/src/ui/comments-list.test.ts +31 -0
  285. package/src/ui/comments-list.tsx +82 -47
  286. package/src/ui/comments.css +577 -294
  287. package/src/ui/count-badge.tsx +9 -2
  288. package/src/ui/emoji-picker.test.ts +25 -0
  289. package/src/ui/emoji-picker.tsx +76 -0
  290. package/src/ui/empty-state.tsx +3 -3
  291. package/src/ui/format-time.test.ts +69 -0
  292. package/src/ui/format-time.ts +27 -2
  293. package/src/ui/icons.tsx +116 -0
  294. package/src/ui/reaction-picker.tsx +102 -0
  295. package/src/ui/reaction.tsx +125 -6
  296. package/src/ui/reactions.tsx +69 -10
  297. package/src/ui/reply-count.ts +16 -0
  298. package/src/ui/send-button.tsx +13 -3
  299. package/src/ui/visual-viewport.test.ts +36 -0
  300. package/src/ui/visual-viewport.ts +27 -0
  301. package/dist-cjs/canvas/region-options.js +0 -49
  302. package/dist-cjs/canvas/region-options.js.map +0 -7
  303. package/dist-cjs/ui/avatar.js.map +0 -7
  304. package/dist-cjs/ui/comment-mention.js +0 -42
  305. package/dist-cjs/ui/comment-mention.js.map +0 -7
  306. package/dist-cjs/ui/comment-text.js.map +0 -7
  307. package/dist-cjs/ui/mention-list.js +0 -70
  308. package/dist-cjs/ui/mention-list.js.map +0 -7
  309. package/dist-cjs/ui/mention-suggestion.js +0 -206
  310. package/dist-cjs/ui/mention-suggestion.js.map +0 -7
  311. package/dist-cjs/ui/mention.js.map +0 -7
  312. package/dist-cjs/ui/render-markdown.js +0 -63
  313. package/dist-cjs/ui/render-markdown.js.map +0 -7
  314. package/dist-esm/canvas/region-options.mjs +0 -29
  315. package/dist-esm/canvas/region-options.mjs.map +0 -7
  316. package/dist-esm/ui/avatar.mjs +0 -23
  317. package/dist-esm/ui/avatar.mjs.map +0 -7
  318. package/dist-esm/ui/comment-mention.mjs +0 -22
  319. package/dist-esm/ui/comment-mention.mjs.map +0 -7
  320. package/dist-esm/ui/comment-text.mjs +0 -9
  321. package/dist-esm/ui/comment-text.mjs.map +0 -7
  322. package/dist-esm/ui/mention-list.mjs +0 -50
  323. package/dist-esm/ui/mention-list.mjs.map +0 -7
  324. package/dist-esm/ui/mention-suggestion.mjs +0 -186
  325. package/dist-esm/ui/mention-suggestion.mjs.map +0 -7
  326. package/dist-esm/ui/mention.mjs +0 -11
  327. package/dist-esm/ui/mention.mjs.map +0 -7
  328. package/dist-esm/ui/render-markdown.mjs +0 -45
  329. package/dist-esm/ui/render-markdown.mjs.map +0 -7
  330. package/src/canvas/region-options.ts +0 -57
  331. package/src/ui/avatar.tsx +0 -31
  332. package/src/ui/comment-mention.ts +0 -47
  333. package/src/ui/comment-text.tsx +0 -12
  334. package/src/ui/mention-list.tsx +0 -106
  335. package/src/ui/mention-suggestion.test.ts +0 -18
  336. package/src/ui/mention-suggestion.tsx +0 -285
  337. package/src/ui/mention.tsx +0 -9
  338. package/src/ui/render-markdown.tsx +0 -72
@@ -1,92 +1,35 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { isMentionPickerOpen } from "@tldraw/mentions";
3
+ import { Fragment as Fragment2, useCallback, useEffect, useMemo, useRef } from "react";
4
+ import { EditorPortal, useEditor, useValue } from "tldraw";
5
+ import { registerCommentAnchorLifecycle } from "./anchor-lifecycle.mjs";
6
+ import { ClusterBadge } from "./cluster-badge.mjs";
7
+ import { clusterFadeClassName } from "./cluster-fade.mjs";
2
8
  import {
3
- memo,
4
- useCallback,
5
- useEffect,
6
- useMemo,
7
- useRef,
8
- useState
9
- } from "react";
10
- import { createPortal } from "react-dom";
11
- import {
12
- createComment,
13
- createCommentThread,
14
- getFirstCharacter,
15
- react,
16
- TldrawUiIcon,
17
- useContainer,
18
- useEditor,
19
- usePassThroughMouseOverEvents,
20
- usePassThroughWheelEvents,
21
- useTranslation,
22
- useValue
23
- } from "tldraw";
24
- import { computeClusterTable } from "../clustering/computeClusterTable.mjs";
25
- import { createClusterRuntime } from "../clustering/runtime.mjs";
26
- import { CommentCard } from "../ui/comment-card.mjs";
27
- import { CommentComposer } from "../ui/comment-composer.mjs";
28
- import { EMPTY_COMMENT, isCommentEmpty } from "../ui/comment-extensions.mjs";
29
- import { CommentPin } from "../ui/comment-pin.mjs";
30
- import { CommentThread } from "../ui/comment-thread.mjs";
31
- import { CountBadge } from "../ui/count-badge.mjs";
32
- import { isMentionPickerOpen } from "../ui/mention-suggestion.mjs";
33
- import { collectClusterLeaves } from "./cluster-input.mjs";
34
- import { CommentBody } from "./comment-body.mjs";
35
- import { UNKNOWN_AUTHOR } from "./comment-render.mjs";
36
- import { getCommentRecord, putCommentRecords, removeCommentRecords } from "./comment-store.mjs";
37
- import { useCommentThreads, useThreadComments } from "./hooks.mjs";
9
+ CLUSTER_EXPAND_ZOOM_MS,
10
+ revealThreadPin,
11
+ useClusterModel,
12
+ zoomToClusterSplit
13
+ } from "./cluster-model.mjs";
14
+ import { getCommentRecord } from "./comment-store.mjs";
15
+ import { useCommentThreads } from "./hooks.mjs";
38
16
  import { useCommentingEnabled } from "./license.mjs";
39
- import {
40
- getCommentingOptions,
41
- useCommentingOptions
42
- } from "./options.mjs";
43
- import {
44
- DEFAULT_REGION_COMMENT_OPTIONS,
45
- setRegionCommentOptions
46
- } from "./region-options.mjs";
17
+ import { useCanComment, useCommentingOptions } from "./options.mjs";
18
+ import { PendingComposer } from "./pending-composer.mjs";
19
+ import { computePinStacks, isOpenStackKeyLive, pinStacksEqual } from "./pin-stacking.mjs";
20
+ import { RegionBox, RegionDraftBox } from "./region-box.mjs";
47
21
  import {
48
22
  commentsHidden,
49
- commitCommentMutation,
23
+ openStackId,
50
24
  openThreadId,
51
25
  pendingComment,
52
- regionDraft,
26
+ revealThreadRequest,
27
+ sidebarFilters,
53
28
  toggleCommentsHidden,
54
29
  usePendingComment
55
30
  } from "./state.mjs";
56
- import { anchorPagePoint, regionPinPoint, shapeAnchorAt } from "./thread-state.mjs";
57
- const stop = (e) => e.stopPropagation();
58
- const initialOf = (name) => (getFirstCharacter(name.trim()) || "?").toUpperCase();
59
- const CLUSTER_FADE_MS = 150;
60
- const CLUSTER_EXPAND_ZOOM_MS = 450;
61
- const draftAvatar = /* @__PURE__ */ jsx(CommentPin, { children: /* @__PURE__ */ jsxs(
62
- "svg",
63
- {
64
- viewBox: "0 0 24 24",
65
- width: "15",
66
- height: "15",
67
- fill: "none",
68
- stroke: "currentColor",
69
- strokeWidth: "2",
70
- strokeLinecap: "round",
71
- strokeLinejoin: "round",
72
- "aria-hidden": "true",
73
- children: [
74
- /* @__PURE__ */ jsx("path", { d: "M12 20h9" }),
75
- /* @__PURE__ */ jsx("path", { d: "M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" })
76
- ]
77
- }
78
- ) });
79
- function toCardProps(comment, props, components) {
80
- const Body = components.CommentBody;
81
- const body = Body ? /* @__PURE__ */ jsx(Body, { comment }) : /* @__PURE__ */ jsx(CommentBody, { richText: comment.body, resolveName: props.resolveName });
82
- return {
83
- author: props.resolveName(comment.authorId) ?? UNKNOWN_AUTHOR,
84
- body,
85
- date: new Date(comment.createdAt).toISOString(),
86
- you: comment.authorId === props.currentUserId,
87
- edited: comment.editedAt != null
88
- };
89
- }
31
+ import { ThreadPin } from "./thread-pin.mjs";
32
+ import { ThreadStackPin } from "./thread-stack.mjs";
90
33
  function CanvasComments(props) {
91
34
  const commentingEnabled = useCommentingEnabled();
92
35
  if (!commentingEnabled) return null;
@@ -95,166 +38,112 @@ function CanvasComments(props) {
95
38
  function CanvasCommentsLayer(props) {
96
39
  const editor = useEditor();
97
40
  const options = useCommentingOptions();
98
- const container = useContainer();
99
- const regionOptions = useMemo(
100
- () => ({ ...DEFAULT_REGION_COMMENT_OPTIONS, ...props.regionOptions }),
101
- [props.regionOptions]
102
- );
103
- useEffect(() => setRegionCommentOptions(editor, regionOptions), [editor, regionOptions]);
104
- const layerRef = useRef(null);
105
- usePassThroughWheelEvents(layerRef);
106
- usePassThroughMouseOverEvents(layerRef);
107
- const deepLinkHandled = useRef(false);
108
- const threads = useCommentThreads(editor);
41
+ const allThreads = useCommentThreads(editor);
109
42
  const pending = usePendingComment();
110
- const openId = useValue("open thread id", () => openThreadId.get(editor), [editor]);
111
- const impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor;
112
- const [movedThreadIds, setMovedThreadIds] = useState(EMPTY_SET);
113
- const adoptOnRebuild = useRef(false);
114
- const clusterLeaves = useValue(
115
- "comment cluster leaves",
116
- () => collectClusterLeaves(
117
- editor,
118
- threads.filter((thread) => !movedThreadIds.has(thread.id)),
119
- openThreadId.get(editor),
120
- impreciseShapeAnchor
121
- ),
122
- [editor, threads, impreciseShapeAnchor, movedThreadIds]
123
- );
124
- const clusterZoomBounds = useValue(
125
- "comment cluster zoom bounds",
126
- () => getClusterZoomBounds(editor),
127
- [editor]
128
- );
129
- const latestModel = useMemo(() => {
130
- const table = computeClusterTable(clusterLeaves, clusterZoomBounds);
131
- const runtime = createClusterRuntime(table);
132
- runtime.seed(editor.getZoomLevel());
133
- return { runtime, table };
134
- }, [clusterLeaves, clusterZoomBounds, editor]);
135
- const [renderedModel, setRenderedModel] = useState(latestModel);
136
- let clusterModel = renderedModel;
137
- if (renderedModel !== latestModel && (adoptOnRebuild.current || hasRemovedLeaves(renderedModel.table, latestModel.table))) {
138
- adoptOnRebuild.current = false;
139
- latestModel.runtime.seedFrom(editor.getZoomLevel(), renderedModel.runtime.getVisible());
140
- setRenderedModel(latestModel);
141
- clusterModel = latestModel;
142
- }
143
- const newlyMovedIds = findMovedClusteredLeafIds(clusterModel, latestModel);
144
- if (newlyMovedIds.length > 0) {
145
- console.debug(`[comments] pins popped out of clustering: ${newlyMovedIds.join(", ")}`);
146
- const next = new Set(movedThreadIds);
147
- for (const id of newlyMovedIds) next.add(id);
148
- setMovedThreadIds(next);
149
- }
43
+ const canComment = useCanComment(props.currentUserId);
44
+ const canRenderComposer = canComment || options.components.ComposerFallback != null;
45
+ const showPendingComposer = pending != null && canRenderComposer;
150
46
  useEffect(() => {
151
- if (movedThreadIds.size === 0) return;
152
- let lastZoom = editor.getZoomLevel();
153
- return react("rejoin moved comment pins on zoom out", () => {
154
- const zoom = editor.getZoomLevel();
155
- const prevZoom = lastZoom;
156
- lastZoom = zoom;
157
- if (zoom >= prevZoom) return;
158
- adoptOnRebuild.current = true;
159
- setMovedThreadIds(EMPTY_SET);
160
- });
161
- }, [movedThreadIds, editor]);
162
- useEffect(() => {
163
- if (clusterModel === latestModel) return;
164
- let lastZoom = editor.getZoomLevel();
165
- return react("adopt pending cluster model on zoom out", () => {
166
- const zoom = editor.getZoomLevel();
167
- const prevZoom = lastZoom;
168
- lastZoom = zoom;
169
- if (zoom >= prevZoom) return;
170
- latestModel.runtime.seedFrom(zoom, clusterModel.runtime.getVisible());
171
- setRenderedModel(latestModel);
172
- });
173
- }, [clusterModel, latestModel, editor]);
174
- const orphanThreads = useMemo(() => {
175
- if (clusterModel === latestModel) return [];
176
- const renderedIds = new Set(clusterModel.table.leaves.map((leaf) => leaf.id));
177
- const latestIds = new Set(latestModel.table.leaves.map((leaf) => leaf.id));
178
- return threads.filter((thread) => latestIds.has(thread.id) && !renderedIds.has(thread.id));
179
- }, [clusterModel, latestModel, threads]);
180
- const movedThreads = useMemo(
181
- () => threads.filter((thread) => movedThreadIds.has(thread.id) && thread.id !== openId),
182
- [threads, movedThreadIds, openId]
183
- );
184
- const clusterCursor = useValue(
185
- "comment cluster cursor",
186
- () => {
187
- clusterModel.runtime.onCamera(editor.getZoomLevel());
188
- return clusterModel.runtime.k;
189
- },
190
- [clusterModel, editor]
47
+ if (pending && !showPendingComposer) pendingComment.set(editor, null);
48
+ }, [editor, pending, showPendingComposer]);
49
+ const openId = useValue("open thread id", () => openThreadId.get(editor), [editor]);
50
+ const showResolved = useValue("show resolved", () => sidebarFilters.get(editor).showResolved, [
51
+ editor
52
+ ]);
53
+ const threads = useMemo(
54
+ () => allThreads.filter((t) => showResolved || t.resolved == null || t.id === openId),
55
+ [allThreads, showResolved, openId]
191
56
  );
192
- const visibleNodes = useMemo(() => {
193
- const nodes = Array.from(clusterModel.runtime.getVisible().values());
194
- console.debug(
195
- `[comments] cluster cursor k=${clusterCursor} \u2192 re-rendering ${nodes.length} visible nodes`
196
- );
197
- return nodes;
198
- }, [clusterModel, clusterCursor]);
199
- const fadeNodes = useFadeVisibleNodes(visibleNodes, clusterModel);
57
+ useEffect(() => registerCommentAnchorLifecycle(editor), [editor]);
58
+ const {
59
+ model: clusterModel,
60
+ zoomBounds: clusterZoomBounds,
61
+ fadeNodes,
62
+ orphanThreads,
63
+ heldThreads
64
+ } = useClusterModel(editor, threads, openId);
200
65
  const threadsById = useMemo(
201
66
  () => new Map(threads.map((thread) => [thread.id, thread])),
202
67
  [threads]
203
68
  );
69
+ const pinStacksRef = useRef(/* @__PURE__ */ new Map());
70
+ const pinStacks = useValue(
71
+ "comment pin stacks",
72
+ () => {
73
+ const stacks = computePinStacks(editor, threads);
74
+ if (pinStacksEqual(pinStacksRef.current, stacks)) return pinStacksRef.current;
75
+ pinStacksRef.current = stacks;
76
+ return stacks;
77
+ },
78
+ [editor, threads]
79
+ );
204
80
  const openThread = openId ? threadsById.get(openId) : null;
205
81
  const hidden = useValue("comments hidden", () => commentsHidden.get(editor), [editor]);
206
82
  useEffect(() => {
207
83
  return () => {
208
84
  openThreadId.set(editor, null);
85
+ openStackId.set(editor, null);
209
86
  pendingComment.set(editor, null);
87
+ revealThreadRequest.set(editor, null);
210
88
  };
211
89
  }, [editor]);
90
+ const openStackKeyIsStale = useValue(
91
+ "open stack key stale",
92
+ () => {
93
+ const key = openStackId.get(editor);
94
+ if (!key) return false;
95
+ return !isOpenStackKeyLive(editor, key, pinStacks, threadsById);
96
+ },
97
+ [editor, pinStacks, threadsById]
98
+ );
212
99
  useEffect(() => {
213
- if (deepLinkHandled.current) return;
214
- const id = new URLSearchParams(window.location.search).get("comment");
215
- if (!id) {
216
- deepLinkHandled.current = true;
217
- return;
218
- }
219
- const record = getCommentRecord(editor, id);
220
- if (!record) return;
221
- let thread;
222
- if (record.typeName === "comment") {
223
- thread = threadsById.get(record.threadId);
224
- } else {
225
- thread = record;
226
- }
227
- if (!thread) return;
228
- deepLinkHandled.current = true;
229
- revealDeepLinkedThread(
230
- editor,
231
- thread,
232
- clusterModel.table,
233
- clusterZoomBounds,
234
- options,
235
- impreciseShapeAnchor
236
- );
237
- openThreadId.set(editor, thread.id);
238
- }, [clusterModel.table, clusterZoomBounds, editor, threadsById, impreciseShapeAnchor, options]);
239
- const zoomToClusterSplit = useCallback(
240
- (node) => {
241
- const event = clusterModel.table.events.find((e) => e.result.id === node.id);
242
- if (!event || !Number.isFinite(event.zSplit)) return;
243
- const zoom = clamp(
244
- event.zSplit * options.clusterSplitZoomFactor,
245
- clusterZoomBounds.minZoom,
246
- clusterZoomBounds.maxZoom
100
+ if (openStackKeyIsStale) openStackId.set(editor, null);
101
+ }, [editor, openStackKeyIsStale]);
102
+ const requestedRevealThread = useValue(
103
+ "requested reveal thread",
104
+ () => {
105
+ const id = revealThreadRequest.get(editor);
106
+ if (!id) return null;
107
+ const record = getCommentRecord(editor, id);
108
+ if (!record) return null;
109
+ const thread = record.typeName === "comment" ? getCommentRecord(editor, record.threadId) : record;
110
+ return thread?.typeName === "comment-thread" ? thread : null;
111
+ },
112
+ [editor]
113
+ );
114
+ useEffect(() => {
115
+ if (!requestedRevealThread) return;
116
+ revealThreadRequest.set(editor, null);
117
+ commentsHidden.set(editor, false);
118
+ revealThreadPin(editor, requestedRevealThread, clusterModel.table, clusterZoomBounds, options);
119
+ openThreadId.set(editor, requestedRevealThread.id);
120
+ }, [requestedRevealThread, clusterModel.table, clusterZoomBounds, editor, options]);
121
+ const revealClusteredThread = useCallback(
122
+ (thread) => {
123
+ revealThreadPin(
124
+ editor,
125
+ thread,
126
+ clusterModel.table,
127
+ clusterZoomBounds,
128
+ options,
129
+ CLUSTER_EXPAND_ZOOM_MS
247
130
  );
248
- centerOnPointAtZoom(editor, node.centroid, zoom, CLUSTER_EXPAND_ZOOM_MS);
131
+ openThreadId.set(editor, thread.id);
132
+ },
133
+ [clusterModel.table, clusterZoomBounds, editor, options]
134
+ );
135
+ const expandCluster = useCallback(
136
+ (node) => {
137
+ zoomToClusterSplit(editor, clusterModel.table, clusterZoomBounds, node);
249
138
  },
250
- [clusterModel, clusterZoomBounds, editor, options]
139
+ [clusterModel.table, clusterZoomBounds, editor]
251
140
  );
252
141
  useEffect(() => {
253
142
  const onKeyDown = (e) => {
254
143
  if (e.key !== "Escape" || openThreadId.get(editor) === null) return;
255
144
  if (isMentionPickerOpen()) return;
256
145
  const target = e.target;
257
- if (target && target.closest(".cmt-editing")) return;
146
+ if (target && target.closest(".tlui-cmt-editing")) return;
258
147
  openThreadId.set(editor, null);
259
148
  e.preventDefault();
260
149
  e.stopPropagation();
@@ -274,818 +163,76 @@ function CanvasCommentsLayer(props) {
274
163
  return () => document.removeEventListener("keydown", onKeyDown, true);
275
164
  }, [editor]);
276
165
  if (hidden) return null;
277
- return createPortal(
278
- /* @__PURE__ */ jsxs("div", { ref: layerRef, className: "cmt-canvas-layer", children: [
166
+ const stackGroupOf = (node) => {
167
+ const group = pinStacks.get(node.members[0]);
168
+ if (!group) return null;
169
+ return node.members.every((id) => group.includes(id)) ? group : null;
170
+ };
171
+ const renderedThreadIds = /* @__PURE__ */ new Set();
172
+ if (options.enableClustering) {
173
+ for (const { node } of fadeNodes) {
174
+ if (node.count === 1) renderedThreadIds.add(node.id);
175
+ else if (stackGroupOf(node)) for (const id of node.members) renderedThreadIds.add(id);
176
+ }
177
+ for (const thread of orphanThreads) renderedThreadIds.add(thread.id);
178
+ for (const thread of heldThreads) renderedThreadIds.add(thread.id);
179
+ } else {
180
+ for (const thread of threads) renderedThreadIds.add(thread.id);
181
+ }
182
+ if (openThread) renderedThreadIds.add(openThread.id);
183
+ const renderThreadPin = (thread) => {
184
+ const group = pinStacks.get(thread.id);
185
+ if (group) {
186
+ const owner = group.find((id) => renderedThreadIds.has(id));
187
+ if (owner !== thread.id) return null;
188
+ const stackThreads = group.map((id) => threadsById.get(id)).filter((t) => t !== void 0);
189
+ return /* @__PURE__ */ jsx(ThreadStackPin, { editor, threads: stackThreads, ...props });
190
+ }
191
+ return /* @__PURE__ */ jsx(ThreadPin, { editor, thread, ...props });
192
+ };
193
+ return (
194
+ /* @__PURE__ */ jsx(EditorPortal, { children: /* @__PURE__ */ jsxs("div", { className: "tlui-cmt-canvas-layer", children: [
279
195
  options.enableClustering ? /* @__PURE__ */ jsxs(Fragment, { children: [
280
196
  fadeNodes.map(({ node, phase }) => {
281
197
  let content;
198
+ const stackGroup = node.count > 1 ? stackGroupOf(node) : null;
282
199
  if (node.count === 1) {
283
200
  const thread = threadsById.get(node.id);
284
201
  if (!thread) return null;
202
+ content = renderThreadPin(thread);
203
+ } else if (stackGroup) {
204
+ const owner = stackGroup.find((id) => renderedThreadIds.has(id));
205
+ content = owner && node.members.includes(owner) ? renderThreadPin(threadsById.get(owner)) : null;
206
+ } else {
285
207
  content = /* @__PURE__ */ jsx(
286
- ThreadPin,
208
+ ClusterBadge,
287
209
  {
288
210
  editor,
289
- thread,
290
- ...props,
291
- regionOptions
211
+ node,
212
+ onExpand: expandCluster,
213
+ onSelectThread: revealClusteredThread,
214
+ threadsById,
215
+ currentUserId: props.currentUserId,
216
+ resolveAuthor: props.resolveAuthor
292
217
  }
293
218
  );
294
- } else {
295
- content = /* @__PURE__ */ jsx(ClusterBadge, { editor, node, onExpand: zoomToClusterSplit });
296
219
  }
297
220
  return /* @__PURE__ */ jsx("div", { className: clusterFadeClassName(phase), children: content }, `cluster-fade:${node.id}`);
298
221
  }),
299
- orphanThreads.map((thread) => /* @__PURE__ */ jsx(
300
- ThreadPin,
301
- {
302
- editor,
303
- thread,
304
- ...props,
305
- regionOptions
306
- },
307
- thread.id
308
- )),
309
- movedThreads.map((thread) => /* @__PURE__ */ jsx(
310
- ThreadPin,
311
- {
312
- editor,
313
- thread,
314
- ...props,
315
- regionOptions
316
- },
317
- thread.id
318
- ))
222
+ orphanThreads.map((thread) => /* @__PURE__ */ jsx(Fragment2, { children: renderThreadPin(thread) }, thread.id)),
223
+ heldThreads.map((thread) => /* @__PURE__ */ jsx(Fragment2, { children: renderThreadPin(thread) }, thread.id))
319
224
  ] }) : (
320
- // Clustering off: every thread renders as its own live pin (each returns null when it's
321
- // not on the current page or its anchor is missing). The open thread is excluded here and
322
- // rendered once below, mirroring how the clustering path keeps it out of the cluster leaves —
323
- // otherwise it would mount a second, stacked pin.
324
- (threads.filter((thread) => thread.id !== openId).map((thread) => /* @__PURE__ */ jsx(
325
- ThreadPin,
326
- {
327
- editor,
328
- thread,
329
- ...props,
330
- regionOptions
331
- },
332
- thread.id
333
- )))
334
- ),
335
- openThread && /* @__PURE__ */ jsx(
336
- ThreadPin,
337
- {
338
- editor,
339
- thread: openThread,
340
- ...props,
341
- regionOptions
342
- },
343
- `open:${openThread.id}`
225
+ // Clustering off: every thread renders its own pin. The open thread is excluded and
226
+ // rendered once below, or it would mount a second, stacked pin.
227
+ (threads.filter((thread) => thread.id !== openId).map(
228
+ (thread) => /* @__PURE__ */ jsx(Fragment2, { children: renderThreadPin(thread) }, thread.id)
229
+ ))
344
230
  ),
231
+ openThread && /* @__PURE__ */ jsx(Fragment2, { children: renderThreadPin(openThread) }, `open:${openThread.id}`),
345
232
  /* @__PURE__ */ jsx(RegionDraftBox, { editor }),
346
- pending?.anchor.type === "region" && /* @__PURE__ */ jsx(RegionBox, { editor, box: pending.anchor }),
347
- pending && props.currentUserId && /* @__PURE__ */ jsx(PendingComposer, { editor, pending, ...props })
348
- ] }),
349
- container
350
- );
351
- }
352
- const EMPTY_SET = /* @__PURE__ */ new Set();
353
- const MOVED_LEAF_EPSILON = 1e-6;
354
- function useFadeVisibleNodes(nodes, resetKey) {
355
- const resetKeyRef = useRef(resetKey);
356
- const didReset = resetKeyRef.current !== resetKey;
357
- if (didReset) {
358
- resetKeyRef.current = resetKey;
359
- }
360
- const [fadeNodes, setFadeNodes] = useState(() => toPresentFadeNodes(nodes));
361
- const renderedNodes = didReset ? toPresentFadeNodes(nodes) : fadeNodes;
362
- useEffect(() => {
363
- setFadeNodes(toPresentFadeNodes(nodes));
364
- }, [resetKey]);
365
- useEffect(() => {
366
- if (didReset) return;
367
- setFadeNodes((previous) => reconcileFadeNodes(previous, nodes));
368
- }, [didReset, nodes]);
369
- const hasEntering = renderedNodes.some((item) => item.phase === "entering");
370
- useEffect(() => {
371
- if (!hasEntering) return;
372
- const frame = requestClusterFadeFrame(() => {
373
- setFadeNodes(
374
- (previous) => previous.map((item) => item.phase === "entering" ? { ...item, phase: "present" } : item)
375
- );
376
- });
377
- return () => cancelClusterFadeFrame(frame);
378
- }, [hasEntering, renderedNodes]);
379
- const hasExiting = renderedNodes.some((item) => item.phase === "exiting");
380
- useEffect(() => {
381
- if (!hasExiting) return;
382
- const timeout = window.setTimeout(() => {
383
- setFadeNodes((previous) => previous.filter((item) => item.phase !== "exiting"));
384
- }, CLUSTER_FADE_MS);
385
- return () => window.clearTimeout(timeout);
386
- }, [hasExiting, renderedNodes]);
387
- return renderedNodes;
388
- }
389
- function toPresentFadeNodes(nodes) {
390
- return nodes.map((node) => ({ node, phase: "present" }));
391
- }
392
- function reconcileFadeNodes(previous, nextNodes) {
393
- const previousById = new Map(previous.map((item) => [item.node.id, item]));
394
- const nextIds = new Set(nextNodes.map((node) => node.id));
395
- const next = [];
396
- for (const node of nextNodes) {
397
- const previousItem = previousById.get(node.id);
398
- next.push({
399
- node,
400
- phase: previousItem && previousItem.phase !== "exiting" ? previousItem.phase : previousItem ? "present" : "entering"
401
- });
402
- }
403
- for (const item of previous) {
404
- if (nextIds.has(item.node.id)) continue;
405
- next.push(item.phase === "exiting" ? item : { ...item, phase: "exiting" });
406
- }
407
- return next;
408
- }
409
- function requestClusterFadeFrame(callback) {
410
- if (typeof requestAnimationFrame === "function") return requestAnimationFrame(callback);
411
- return window.setTimeout(() => callback(0), 16);
412
- }
413
- function cancelClusterFadeFrame(frame) {
414
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame);
415
- else window.clearTimeout(frame);
416
- }
417
- function clusterFadeClassName(phase) {
418
- return `cmt-cluster-fade cmt-cluster-fade--${phase}`;
419
- }
420
- function findMovedClusteredLeafIds(rendered, latest) {
421
- if (rendered.table === latest.table) return [];
422
- const visible = rendered.runtime.getVisible();
423
- const latestById = new Map(latest.table.leaves.map((leaf) => [leaf.id, leaf]));
424
- const moved = [];
425
- for (const leaf of rendered.table.leaves) {
426
- if (visible.has(leaf.id)) continue;
427
- const current = latestById.get(leaf.id);
428
- if (!current) continue;
429
- if (Math.abs(current.centroid.x - leaf.centroid.x) > MOVED_LEAF_EPSILON || Math.abs(current.centroid.y - leaf.centroid.y) > MOVED_LEAF_EPSILON) {
430
- moved.push(leaf.id);
431
- }
432
- }
433
- return moved;
434
- }
435
- function hasRemovedLeaves(rendered, latest) {
436
- if (rendered.leaves.length === 0) return false;
437
- const latestIds = new Set(latest.leaves.map((leaf) => leaf.id));
438
- return rendered.leaves.some((leaf) => !latestIds.has(leaf.id));
439
- }
440
- function getClusterZoomBounds(editor) {
441
- const cameraOptions = editor.getCameraOptions();
442
- const baseZoom = cameraOptions.constraints ? editor.getBaseZoom() : 1;
443
- const zoomSteps = cameraOptions.zoomSteps;
444
- return {
445
- minZoom: zoomSteps[0] * baseZoom,
446
- maxZoom: zoomSteps[zoomSteps.length - 1] * baseZoom
447
- };
448
- }
449
- function revealDeepLinkedThread(editor, thread, table, zoomBounds, options, impreciseShapeAnchor) {
450
- if (thread.pageId !== editor.getCurrentPageId()) {
451
- editor.setCurrentPage(thread.pageId);
452
- }
453
- const point = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor);
454
- if (!point) return;
455
- if (options.enableClustering) {
456
- const parentEvent = findDirectParentEvent(table, thread.id);
457
- if (parentEvent && Number.isFinite(parentEvent.zSplit) && parentEvent.zSplit <= zoomBounds.maxZoom) {
458
- const zoom = clamp(
459
- parentEvent.zSplit * options.clusterSplitZoomFactor,
460
- zoomBounds.minZoom,
461
- zoomBounds.maxZoom
462
- );
463
- centerOnPointAtZoom(editor, point, zoom);
464
- return;
465
- }
466
- }
467
- editor.centerOnPoint(point, { animation: { duration: 200 } });
468
- }
469
- function findDirectParentEvent(table, threadId) {
470
- return table.events.find((event) => event.children.some((child) => child.id === threadId));
471
- }
472
- function centerOnPointAtZoom(editor, point, zoom, duration = 200) {
473
- const viewport = editor.getViewportScreenBounds();
474
- editor.setCamera(
475
- {
476
- x: viewport.w / (2 * zoom) - point.x,
477
- y: viewport.h / (2 * zoom) - point.y,
478
- z: zoom
479
- },
480
- { animation: { duration } }
481
- );
482
- }
483
- function clamp(value, min, max) {
484
- return Math.max(min, Math.min(max, value));
485
- }
486
- const ClusterBadge = memo(function ClusterBadge2({
487
- editor,
488
- node,
489
- onExpand
490
- }) {
491
- const point = useValue(
492
- "cluster badge point",
493
- () => {
494
- const pagePoint = editor.pageToViewport(node.centroid);
495
- if (!isInInflatedViewport(editor, pagePoint)) return null;
496
- return pagePoint;
497
- },
498
- [editor, node]
499
- );
500
- if (!point) return null;
501
- return /* @__PURE__ */ jsx(
502
- "div",
503
- {
504
- className: "cmt-canvas-cluster",
505
- style: { left: point.x, top: point.y },
506
- onPointerDown: stop,
507
- onClick: (e) => {
508
- e.stopPropagation();
509
- onExpand(node);
510
- },
511
- children: /* @__PURE__ */ jsx(CountBadge, { count: node.count })
512
- }
513
- );
514
- });
515
- function isInInflatedViewport(editor, point) {
516
- const viewport = editor.getViewportScreenBounds();
517
- const margin = getCommentingOptions(editor).clusterCullMargin;
518
- return point.x >= -margin && point.y >= -margin && point.x <= viewport.w + margin && point.y <= viewport.h + margin;
519
- }
520
- function ThreadPopover({
521
- container,
522
- style,
523
- children
524
- }) {
525
- const ref = useRef(null);
526
- usePassThroughWheelEvents(ref);
527
- usePassThroughMouseOverEvents(ref);
528
- return createPortal(
529
- /* @__PURE__ */ jsx("div", { ref, className: "cmt-canvas-popover", style, onPointerDown: stop, children }),
530
- container
531
- );
532
- }
533
- function RegionBox({
534
- editor,
535
- box,
536
- movable,
537
- onPreview,
538
- onCommit
539
- }) {
540
- const rect = useValue(
541
- "region rect",
542
- () => {
543
- const topLeft = editor.pageToViewport({ x: box.x, y: box.y });
544
- const zoom = editor.getZoomLevel();
545
- return { left: topLeft.x, top: topLeft.y, width: box.w * zoom, height: box.h * zoom };
546
- },
547
- [editor, box.x, box.y, box.w, box.h]
548
- );
549
- const grabRef = useRef(null);
550
- const translated = (e) => {
551
- const g = grabRef.current;
552
- const p = editor.screenToPage({ x: e.clientX, y: e.clientY });
553
- return { ...g.box, x: g.box.x + (p.x - g.page.x), y: g.box.y + (p.y - g.page.y) };
554
- };
555
- const startMove = (e) => {
556
- e.stopPropagation();
557
- grabRef.current = { page: editor.screenToPage({ x: e.clientX, y: e.clientY }), box };
558
- e.currentTarget.setPointerCapture(e.pointerId);
559
- };
560
- const onMove = (e) => {
561
- if (grabRef.current) onPreview?.(translated(e));
562
- };
563
- const endMove = (e) => {
564
- if (!grabRef.current) return;
565
- const bounds = translated(e);
566
- grabRef.current = null;
567
- if (e.currentTarget.hasPointerCapture(e.pointerId))
568
- e.currentTarget.releasePointerCapture(e.pointerId);
569
- onCommit?.(bounds);
570
- };
571
- return /* @__PURE__ */ jsx(
572
- "div",
573
- {
574
- className: movable ? "cmt-canvas-region cmt-canvas-region--movable" : "cmt-canvas-region",
575
- style: rect,
576
- onPointerDown: movable ? startMove : void 0,
577
- onPointerMove: movable ? onMove : void 0,
578
- onPointerUp: movable ? endMove : void 0
579
- }
580
- );
581
- }
582
- function RegionDraftBox({ editor }) {
583
- const box = useValue("region draft", () => regionDraft.get(editor), [editor]);
584
- if (!box) return null;
585
- return /* @__PURE__ */ jsx(RegionBox, { editor, box });
586
- }
587
- const REGION_CORNERS = [
588
- { x: 0, y: 0, cursor: "nwse-resize" },
589
- { x: 1, y: 0, cursor: "nesw-resize" },
590
- { x: 0, y: 1, cursor: "nesw-resize" },
591
- { x: 1, y: 1, cursor: "nwse-resize" }
592
- ];
593
- const REGION_EDGES = [
594
- { x: 0.5, y: 0, cursor: "ns-resize" },
595
- { x: 1, y: 0.5, cursor: "ew-resize" },
596
- { x: 0.5, y: 1, cursor: "ns-resize" },
597
- { x: 0, y: 0.5, cursor: "ew-resize" }
598
- ];
599
- const REGION_HANDLE_MARGIN_PX = 12;
600
- function resizeRegion(box, handle, cursor) {
601
- const controlsX = handle.x !== 0.5;
602
- const controlsY = handle.y !== 0.5;
603
- const fixedX = box.x + (1 - handle.x) * box.w;
604
- const fixedY = box.y + (1 - handle.y) * box.h;
605
- return {
606
- x: controlsX ? Math.min(fixedX, cursor.x) : box.x,
607
- y: controlsY ? Math.min(fixedY, cursor.y) : box.y,
608
- w: controlsX ? Math.abs(cursor.x - fixedX) : box.w,
609
- h: controlsY ? Math.abs(cursor.y - fixedY) : box.h
610
- };
611
- }
612
- function RegionResizeHandles({
613
- editor,
614
- box,
615
- handles,
616
- onPreview,
617
- onCommit
618
- }) {
619
- const boxRef = useRef(null);
620
- const points = useValue(
621
- "region handle points",
622
- () => handles.map((h) => {
623
- const p = editor.pageToViewport({ x: box.x + h.x * box.w, y: box.y + h.y * box.h });
624
- return { ...h, key: `${h.x}-${h.y}`, left: p.x, top: p.y };
625
- }),
626
- [editor, box.x, box.y, box.w, box.h, handles]
627
- );
628
- const startResize = (e) => {
629
- e.stopPropagation();
630
- boxRef.current = box;
631
- e.currentTarget.setPointerCapture(e.pointerId);
632
- };
633
- const resizedTo = (h, e) => resizeRegion(boxRef.current, h, editor.screenToPage({ x: e.clientX, y: e.clientY }));
634
- const onResize = (h) => (e) => {
635
- if (boxRef.current) onPreview(resizedTo(h, e));
636
- };
637
- const endResize = (h) => (e) => {
638
- if (!boxRef.current) return;
639
- const bounds = resizedTo(h, e);
640
- boxRef.current = null;
641
- if (e.currentTarget.hasPointerCapture(e.pointerId))
642
- e.currentTarget.releasePointerCapture(e.pointerId);
643
- onCommit(bounds);
644
- };
645
- return /* @__PURE__ */ jsx(Fragment, { children: points.map((h) => /* @__PURE__ */ jsx(
646
- "div",
647
- {
648
- className: "cmt-canvas-region-handle",
649
- style: { left: h.left, top: h.top, cursor: h.cursor },
650
- onPointerDown: startResize,
651
- onPointerMove: onResize(h),
652
- onPointerUp: endResize(h)
653
- },
654
- h.key
655
- )) });
656
- }
657
- const ThreadPin = memo(function ThreadPin2({
658
- editor,
659
- thread,
660
- regionOptions,
661
- ...props
662
- }) {
663
- const {
664
- currentUserId,
665
- resolveName,
666
- onPostComment,
667
- isCommentUnread,
668
- onCommentRead,
669
- getMentionSuggestions,
670
- renderMentionSuggestion
671
- } = props;
672
- const options = useCommentingOptions();
673
- const impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor;
674
- const container = useContainer();
675
- const comments = useThreadComments(editor, thread.id);
676
- const msg = useTranslation();
677
- const open = useValue("thread open", () => openThreadId.get(editor) === thread.id, [
678
- editor,
679
- thread.id
680
- ]);
681
- const [reply, setReply] = useState(EMPTY_COMMENT);
682
- const [editingId, setEditingId] = useState(null);
683
- const [editText, setEditText] = useState(EMPTY_COMMENT);
684
- const [dragPagePoint, setDragPagePoint] = useState(null);
685
- const [resizeBounds, setResizeBounds] = useState(null);
686
- const [pinHovered, setPinHovered] = useState(false);
687
- const pointerInRegion = useValue(
688
- "pointer in region",
689
- () => {
690
- if (thread.anchor.type !== "region" || thread.pageId !== editor.getCurrentPageId())
691
- return false;
692
- const m = REGION_HANDLE_MARGIN_PX / editor.getZoomLevel();
693
- const p = editor.inputs.getCurrentPagePoint();
694
- const a = thread.anchor;
695
- return p.x >= a.x - m && p.x <= a.x + a.w + m && p.y >= a.y - m && p.y <= a.y + a.h + m;
696
- },
697
- [editor, thread.anchor, thread.pageId]
698
- );
699
- const revealed = open || resizeBounds != null || regionOptions.reveal === "pointer" && pointerInRegion || regionOptions.reveal === "pin-hover" && pinHovered;
700
- const resizeHandles = useMemo(
701
- () => regionOptions.resize === "edges" ? REGION_EDGES : REGION_CORNERS.filter(
702
- (c) => c.x !== regionOptions.pinCorner.x || c.y !== regionOptions.pinCorner.y
703
- ),
704
- [regionOptions.resize, regionOptions.pinCorner]
705
- );
706
- const dragRef = useRef(null);
707
- const markerRef = useRef(null);
708
- useEffect(() => {
709
- if (!open) return;
710
- const onPointerDown = (e) => {
711
- const target = e.target;
712
- if (!target) return;
713
- if (target.closest(".cmt-canvas-popover")) return;
714
- const marker = markerRef.current;
715
- if (marker && marker.contains(target)) return;
716
- if (target.closest(".cmt-canvas-region-handle, .cmt-canvas-region--movable")) return;
717
- if (target.closest(".tlui-menu, [data-radix-popper-content-wrapper], .cmt-mention-popup"))
718
- return;
719
- openThreadId.set(editor, null);
720
- };
721
- document.addEventListener("pointerdown", onPointerDown, true);
722
- return () => document.removeEventListener("pointerdown", onPointerDown, true);
723
- }, [open, editor]);
724
- const point = useValue(
725
- "pin point",
726
- () => {
727
- if (thread.pageId !== editor.getCurrentPageId()) return null;
728
- const pagePoint = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor);
729
- return pagePoint ? editor.pageToViewport(pagePoint) : null;
730
- },
731
- [editor, thread.anchor, thread.pageId, impreciseShapeAnchor]
732
- );
733
- const visible = point !== null;
734
- useEffect(() => {
735
- if (!open || !visible || !isCommentUnread || !onCommentRead) return;
736
- for (const comment of comments) {
737
- if (isCommentUnread(comment.id)) {
738
- onCommentRead(comment.id);
739
- }
740
- }
741
- }, [open, visible, comments, isCommentUnread, onCommentRead]);
742
- if (!point) return null;
743
- const postReply = () => {
744
- if (isCommentEmpty(reply) || !currentUserId) return;
745
- commitCommentMutation(editor, () => {
746
- const comment = createComment({
747
- threadId: thread.id,
748
- pageId: thread.pageId,
749
- authorId: currentUserId,
750
- body: reply
751
- });
752
- putCommentRecords(editor, [comment]);
753
- if (onPostComment) onPostComment(comment);
754
- });
755
- setReply(EMPTY_COMMENT);
756
- };
757
- const toggleResolve = () => {
758
- if (!currentUserId) return;
759
- commitCommentMutation(editor, () => {
760
- putCommentRecords(editor, [
761
- {
762
- ...thread,
763
- resolved: thread.resolved ? null : { at: Date.now(), by: currentUserId }
764
- }
765
- ]);
766
- });
767
- };
768
- const deleteThread = () => {
769
- openThreadId.set(editor, null);
770
- commitCommentMutation(
771
- editor,
772
- () => removeCommentRecords(editor, [thread.id, ...comments.map((c) => c.id)])
773
- );
774
- };
775
- const startEdit = (comment) => {
776
- setEditingId(comment.id);
777
- setEditText(comment.body);
778
- };
779
- const saveEdit = () => {
780
- const comment = comments.find((c) => c.id === editingId);
781
- if (!comment || isCommentEmpty(editText)) return;
782
- commitCommentMutation(editor, () => {
783
- putCommentRecords(editor, [{ ...comment, body: editText, editedAt: Date.now() }]);
784
- });
785
- setEditingId(null);
786
- };
787
- const renderComment = (card, index) => {
788
- const comment = comments[index];
789
- if (editingId === comment.id) {
790
- return /* @__PURE__ */ jsx(
791
- "div",
792
- {
793
- className: "cmt-editing",
794
- onKeyDown: (e) => {
795
- if (e.key === "Escape") {
796
- setEditingId(null);
797
- e.stopPropagation();
798
- }
799
- },
800
- children: /* @__PURE__ */ jsx(
801
- CommentComposer,
802
- {
803
- author: card.author,
804
- placeholder: msg("comments.edit-placeholder"),
805
- value: editText,
806
- onChange: setEditText,
807
- onSubmit: saveEdit,
808
- sendLabel: msg("comments.save"),
809
- disabled: isCommentEmpty(editText),
810
- getMentionSuggestions,
811
- renderMentionSuggestion,
812
- autoFocus: true
813
- }
814
- )
815
- }
816
- );
817
- }
818
- return /* @__PURE__ */ jsx(
819
- CommentCard,
820
- {
821
- ...card,
822
- actions: comment.authorId === currentUserId ? /* @__PURE__ */ jsx(
823
- "button",
824
- {
825
- className: "cmt-thread__action",
826
- title: msg("comments.edit"),
827
- onClick: () => startEdit(comment),
828
- children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "dots-horizontal", label: msg("comments.edit"), small: true })
829
- }
830
- ) : void 0
831
- }
832
- );
833
- };
834
- const headerActions = /* @__PURE__ */ jsxs(Fragment, { children: [
835
- currentUserId && /* @__PURE__ */ jsx(
836
- "button",
837
- {
838
- className: "cmt-thread__action",
839
- title: msg(thread.resolved ? "comments.reopen" : "comments.resolve"),
840
- onClick: toggleResolve,
841
- children: /* @__PURE__ */ jsx(
842
- TldrawUiIcon,
843
- {
844
- icon: "check",
845
- label: msg(thread.resolved ? "comments.reopen" : "comments.resolve"),
846
- small: true
847
- }
848
- )
849
- }
850
- ),
851
- currentUserId && /* @__PURE__ */ jsx(
852
- "button",
853
- {
854
- className: "cmt-thread__action",
855
- title: msg("comments.delete"),
856
- onClick: deleteThread,
857
- children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "trash", label: msg("comments.delete"), small: true })
858
- }
859
- ),
860
- /* @__PURE__ */ jsx(
861
- "button",
862
- {
863
- className: "cmt-thread__action",
864
- title: msg("comments.dismiss"),
865
- onClick: () => openThreadId.set(editor, null),
866
- children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "cross-2", label: msg("comments.dismiss"), small: true })
867
- }
868
- )
869
- ] });
870
- const PinContent = options.components.PinContent;
871
- const pinContent = PinContent ? /* @__PURE__ */ jsx(PinContent, { thread, comments }) : initialOf(resolveName(thread.createdBy) ?? UNKNOWN_AUTHOR);
872
- const isRegion = thread.anchor.type === "region";
873
- const pinMovable = regionOptions.move !== "body";
874
- const bodyMovable = regionOptions.move !== "pin";
875
- const startDrag = (e) => {
876
- e.stopPropagation();
877
- dragRef.current = { startX: e.clientX, startY: e.clientY, moved: false };
878
- e.currentTarget.setPointerCapture(e.pointerId);
879
- };
880
- const onDrag = (e) => {
881
- const drag = dragRef.current;
882
- if (!drag) return;
883
- if (isRegion && !pinMovable) return;
884
- if (!drag.moved && Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY) < 4) return;
885
- drag.moved = true;
886
- setDragPagePoint(editor.screenToPage({ x: e.clientX, y: e.clientY }));
887
- };
888
- const endDrag = (e) => {
889
- const drag = dragRef.current;
890
- dragRef.current = null;
891
- if (e.currentTarget.hasPointerCapture(e.pointerId)) {
892
- e.currentTarget.releasePointerCapture(e.pointerId);
893
- }
894
- if (!drag) return;
895
- if (!drag.moved) {
896
- openThreadId.set(editor, openThreadId.get(editor) === thread.id ? null : thread.id);
897
- return;
898
- }
899
- const pagePoint = editor.screenToPage({ x: e.clientX, y: e.clientY });
900
- setDragPagePoint(null);
901
- let anchor;
902
- if (thread.anchor.type === "region") {
903
- anchor = {
904
- ...thread.anchor,
905
- x: pagePoint.x - regionOptions.pinCorner.x * thread.anchor.w,
906
- y: pagePoint.y - regionOptions.pinCorner.y * thread.anchor.h
907
- };
908
- } else {
909
- const hit = editor.getShapeAtPoint(pagePoint, { hitInside: true });
910
- anchor = hit ? shapeAnchorAt(editor, hit.id, pagePoint, e.altKey) : { type: "point", x: pagePoint.x, y: pagePoint.y };
911
- }
912
- commitCommentMutation(editor, () => putCommentRecords(editor, [{ ...thread, anchor }]), "drag");
913
- };
914
- const livePinPage = resizeBounds ? regionPinPoint(resizeBounds, regionOptions.pinCorner) : dragPagePoint;
915
- const renderPoint = livePinPage ? editor.pageToViewport(livePinPage) : point;
916
- const regionAnchor = thread.anchor.type === "region" ? thread.anchor : void 0;
917
- const movedRegion = regionAnchor && dragPagePoint ? {
918
- ...regionAnchor,
919
- x: dragPagePoint.x - regionOptions.pinCorner.x * regionAnchor.w,
920
- y: dragPagePoint.y - regionOptions.pinCorner.y * regionAnchor.h
921
- } : regionAnchor;
922
- const regionBoxBounds = resizeBounds ?? movedRegion;
923
- const commitResize = (bounds) => {
924
- setResizeBounds(null);
925
- editor.run(
926
- () => putCommentRecords(editor, [{ ...thread, anchor: { type: "region", ...bounds } }]),
927
- {
928
- history: "ignore"
929
- }
930
- );
931
- };
932
- return /* @__PURE__ */ jsxs(Fragment, { children: [
933
- regionBoxBounds && (dragPagePoint || revealed) && /* @__PURE__ */ jsx(
934
- RegionBox,
935
- {
936
- editor,
937
- box: regionBoxBounds,
938
- movable: bodyMovable && !dragPagePoint,
939
- onPreview: setResizeBounds,
940
- onCommit: commitResize
941
- }
942
- ),
943
- regionBoxBounds && revealed && !dragPagePoint && regionOptions.resize !== "none" && /* @__PURE__ */ jsx(
944
- RegionResizeHandles,
945
- {
946
- editor,
947
- box: regionBoxBounds,
948
- handles: resizeHandles,
949
- onPreview: setResizeBounds,
950
- onCommit: commitResize
951
- }
952
- ),
953
- /* @__PURE__ */ jsxs(
954
- "div",
955
- {
956
- className: open ? "cmt-canvas-pin cmt-canvas-pin--open" : "cmt-canvas-pin",
957
- style: { left: renderPoint.x, top: renderPoint.y },
958
- children: [
959
- /* @__PURE__ */ jsx(
960
- "div",
961
- {
962
- ref: markerRef,
963
- className: "cmt-canvas-pin__marker",
964
- onPointerDown: startDrag,
965
- onPointerMove: onDrag,
966
- onPointerUp: endDrag,
967
- onPointerEnter: () => setPinHovered(true),
968
- onPointerLeave: () => setPinHovered(false),
969
- children: /* @__PURE__ */ jsx(CommentPin, { resolved: thread.resolved != null, open, children: pinContent })
970
- }
971
- ),
972
- open && /* @__PURE__ */ jsx(
973
- ThreadPopover,
974
- {
975
- container,
976
- style: { left: renderPoint.x + 36, top: renderPoint.y - 28 },
977
- children: /* @__PURE__ */ jsx(
978
- CommentThread,
979
- {
980
- header: msg("comments.thread-title"),
981
- headerActions,
982
- renderComment,
983
- comments: comments.map((c) => toCardProps(c, props, options.components)),
984
- resolvedBanner: thread.resolved ? msg("comments.resolved-by").replace(
985
- "{name}",
986
- resolveName(thread.resolved.by) ?? UNKNOWN_AUTHOR
987
- ) : void 0,
988
- composer: currentUserId && !thread.resolved ? {
989
- author: resolveName(currentUserId) ?? UNKNOWN_AUTHOR,
990
- placeholder: msg("comments.reply-placeholder"),
991
- sendLabel: msg("comments.send"),
992
- value: reply,
993
- onChange: setReply,
994
- onSubmit: postReply,
995
- disabled: isCommentEmpty(reply),
996
- getMentionSuggestions,
997
- renderMentionSuggestion
998
- } : void 0
999
- }
1000
- )
1001
- }
1002
- )
1003
- ]
1004
- }
1005
- )
1006
- ] });
1007
- });
1008
- function PendingComposer({
1009
- editor,
1010
- pending,
1011
- currentUserId,
1012
- resolveName,
1013
- onPostComment,
1014
- getMentionSuggestions,
1015
- renderMentionSuggestion
1016
- }) {
1017
- const [text, setText] = useState(EMPTY_COMMENT);
1018
- const ref = useRef(null);
1019
- const msg = useTranslation();
1020
- const container = useContainer();
1021
- usePassThroughWheelEvents(ref);
1022
- usePassThroughMouseOverEvents(ref);
1023
- const point = useValue("composer point", () => editor.pageToViewport(pending.point), [
1024
- editor,
1025
- pending.point
1026
- ]);
1027
- useEffect(() => {
1028
- const onPointerDown = (e) => {
1029
- const el = ref.current;
1030
- const target = e.target;
1031
- if (!el || !target) return;
1032
- if (el.contains(target) || target.closest(".cmt-mention-popup")) return;
1033
- pendingComment.set(editor, null);
1034
- };
1035
- document.addEventListener("pointerdown", onPointerDown, true);
1036
- return () => document.removeEventListener("pointerdown", onPointerDown, true);
1037
- }, [editor]);
1038
- const submit = () => {
1039
- if (isCommentEmpty(text) || !currentUserId) return;
1040
- commitCommentMutation(editor, () => {
1041
- const pageId = editor.getCurrentPageId();
1042
- const thread = createCommentThread({
1043
- pageId,
1044
- anchor: pending.anchor,
1045
- createdBy: currentUserId
1046
- });
1047
- const comment = createComment({
1048
- threadId: thread.id,
1049
- pageId,
1050
- authorId: currentUserId,
1051
- body: text
1052
- });
1053
- putCommentRecords(editor, [thread, comment]);
1054
- if (onPostComment) onPostComment(comment);
1055
- });
1056
- setText(EMPTY_COMMENT);
1057
- pendingComment.set(editor, null);
1058
- };
1059
- return createPortal(
1060
- /* @__PURE__ */ jsx(
1061
- "div",
1062
- {
1063
- ref,
1064
- className: "cmt-canvas-composer",
1065
- style: { left: point.x, top: point.y },
1066
- onPointerDown: stop,
1067
- onKeyDown: (e) => {
1068
- if (e.key === "Escape" && !isMentionPickerOpen()) pendingComment.set(editor, null);
1069
- },
1070
- children: /* @__PURE__ */ jsx(
1071
- CommentComposer,
1072
- {
1073
- author: currentUserId ? resolveName(currentUserId) ?? UNKNOWN_AUTHOR : "",
1074
- placeholder: msg("comments.add-placeholder"),
1075
- sendLabel: msg("comments.send"),
1076
- value: text,
1077
- onChange: setText,
1078
- onSubmit: submit,
1079
- disabled: isCommentEmpty(text),
1080
- getMentionSuggestions,
1081
- renderMentionSuggestion,
1082
- autoFocus: true,
1083
- leading: draftAvatar
1084
- }
1085
- )
1086
- }
1087
- ),
1088
- container
233
+ pending?.anchor.type === "region" && showPendingComposer && /* @__PURE__ */ jsx(RegionBox, { editor, box: pending.anchor }),
234
+ pending && showPendingComposer && /* @__PURE__ */ jsx(PendingComposer, { editor, pending, ...props })
235
+ ] }) })
1089
236
  );
1090
237
  }
1091
238
  export {