@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
@@ -0,0 +1,452 @@
1
+ import {
2
+ commentSchemaRecords,
3
+ createCommentThread,
4
+ createShapeId,
5
+ createTLSchema,
6
+ createTLStore,
7
+ defaultBindingUtils,
8
+ defaultShapeUtils,
9
+ defaultTools,
10
+ Editor,
11
+ TLComment,
12
+ TLCommentThread,
13
+ toRichText,
14
+ createComment as tlCreateComment,
15
+ } from 'tldraw'
16
+ import { afterEach, describe, expect, it } from 'vitest'
17
+ import {
18
+ commitCommentMutation,
19
+ deleteComment,
20
+ deleteThread,
21
+ editComment,
22
+ putCommentRecords,
23
+ removeCommentRecords,
24
+ reopenThread,
25
+ resolveThread,
26
+ } from './comment-mutations'
27
+ import {
28
+ getCommentRecord,
29
+ getComments,
30
+ getCommentThreads,
31
+ getLiveComments,
32
+ getLiveCommentThreads,
33
+ } from './comment-store'
34
+ import { CommentTool } from './comment-tool'
35
+ import { openThreadId } from './state'
36
+
37
+ /**
38
+ * These need a real editor: what's under test is how each write interacts with the store and the
39
+ * undo stack, which is exactly what the history option governs.
40
+ */
41
+
42
+ let editors: Editor[] = []
43
+
44
+ afterEach(() => {
45
+ for (const editor of editors) editor.dispose()
46
+ editors = []
47
+ })
48
+
49
+ function makeEditor(tool?: ReturnType<typeof CommentTool.configure>): Editor {
50
+ const editor = new Editor({
51
+ store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
52
+ shapeUtils: defaultShapeUtils,
53
+ bindingUtils: defaultBindingUtils,
54
+ tools: tool ? [...defaultTools, tool] : defaultTools,
55
+ getContainer: () => document.body,
56
+ })
57
+ editors.push(editor)
58
+ return editor
59
+ }
60
+
61
+ function makeThread(editor: Editor, createdBy = 'me') {
62
+ const thread = createCommentThread({
63
+ pageId: editor.getCurrentPageId(),
64
+ anchor: { type: 'point', x: 0, y: 0 },
65
+ createdBy,
66
+ })
67
+ const comment = tlCreateComment({
68
+ threadId: thread.id,
69
+ pageId: thread.pageId,
70
+ authorId: createdBy,
71
+ body: toRichText('hello'),
72
+ })
73
+ putCommentRecords(editor, [thread, comment])
74
+ return { thread, comment }
75
+ }
76
+
77
+ function addComment(editor: Editor, thread: TLCommentThread, authorId = 'me') {
78
+ const comment = tlCreateComment({
79
+ threadId: thread.id,
80
+ pageId: thread.pageId,
81
+ authorId,
82
+ body: toRichText('reply'),
83
+ })
84
+ putCommentRecords(editor, [comment])
85
+ return comment
86
+ }
87
+
88
+ function readComment(editor: Editor, comment: TLComment) {
89
+ return getCommentRecord(editor, comment.id) as TLComment | undefined
90
+ }
91
+
92
+ function readThread(editor: Editor, thread: TLCommentThread) {
93
+ return getCommentRecord(editor, thread.id) as TLCommentThread | undefined
94
+ }
95
+
96
+ describe('editComment', () => {
97
+ it('replaces the body and stamps editedAt', () => {
98
+ const editor = makeEditor()
99
+ const { comment } = makeThread(editor)
100
+ expect(comment.editedAt).toBe(null)
101
+
102
+ editComment(editor, comment, toRichText('changed'))
103
+
104
+ const updated = readComment(editor, comment)!
105
+ expect(updated.body).toEqual(toRichText('changed'))
106
+ expect(typeof updated.editedAt).toBe('number')
107
+ })
108
+
109
+ // The caller's record is a snapshot. Writing it back would revert every field it doesn't mean
110
+ // to touch — here `meta`, set after the caller took its copy.
111
+ it('edits the version in the store, not the copy it was handed', () => {
112
+ const editor = makeEditor()
113
+ const { comment } = makeThread(editor)
114
+ putCommentRecords(editor, [{ ...comment, meta: { pinned: true } }])
115
+
116
+ editComment(editor, comment, toRichText('changed'))
117
+
118
+ expect(readComment(editor, comment)).toMatchObject({
119
+ body: toRichText('changed'),
120
+ meta: { pinned: true },
121
+ })
122
+ })
123
+
124
+ // `put` is an upsert, so writing a snapshot back would re-create the record — with
125
+ // `isDeleted: false` — after someone else's delete had already taken it away.
126
+ it('does not resurrect a comment that has been removed since', () => {
127
+ const editor = makeEditor()
128
+ const { comment } = makeThread(editor)
129
+ removeCommentRecords(editor, [comment.id])
130
+
131
+ editComment(editor, comment, toRichText('changed'))
132
+
133
+ expect(readComment(editor, comment)).toBeUndefined()
134
+ })
135
+ })
136
+
137
+ describe('resolveThread and reopenThread', () => {
138
+ it('stamps who resolved the thread and when', () => {
139
+ const editor = makeEditor()
140
+ const { thread } = makeThread(editor)
141
+
142
+ resolveThread(editor, thread, 'ada')
143
+
144
+ const resolved = readThread(editor, thread)!.resolved!
145
+ expect(resolved.by).toBe('ada')
146
+ expect(typeof resolved.at).toBe('number')
147
+ })
148
+
149
+ it('clears the resolution when reopened', () => {
150
+ const editor = makeEditor()
151
+ const { thread } = makeThread(editor)
152
+ resolveThread(editor, thread, 'ada')
153
+
154
+ reopenThread(editor, readThread(editor, thread)!)
155
+
156
+ expect(readThread(editor, thread)!.resolved).toBe(null)
157
+ })
158
+
159
+ // A thread's anchor moves without anyone touching the thread — a pin drag, or a pinned shape's
160
+ // delete converting it to a point. Resolving from a copy taken before that must not undo it.
161
+ it('resolves the version in the store, leaving an anchor moved since where it is', () => {
162
+ const editor = makeEditor()
163
+ const { thread } = makeThread(editor)
164
+ putCommentRecords(editor, [{ ...thread, anchor: { type: 'point', x: 50, y: 50 } }])
165
+
166
+ resolveThread(editor, thread, 'ada')
167
+
168
+ const updated = readThread(editor, thread)!
169
+ expect(updated.anchor).toEqual({ type: 'point', x: 50, y: 50 })
170
+ expect(updated.resolved!.by).toBe('ada')
171
+ })
172
+
173
+ it('reopens the version in the store, leaving an anchor moved since where it is', () => {
174
+ const editor = makeEditor()
175
+ const { thread } = makeThread(editor)
176
+ resolveThread(editor, thread, 'ada')
177
+ putCommentRecords(editor, [
178
+ { ...readThread(editor, thread)!, anchor: { type: 'point', x: 50, y: 50 } },
179
+ ])
180
+
181
+ reopenThread(editor, thread)
182
+
183
+ const updated = readThread(editor, thread)!
184
+ expect(updated.anchor).toEqual({ type: 'point', x: 50, y: 50 })
185
+ expect(updated.resolved).toBe(null)
186
+ })
187
+
188
+ it('does nothing to a thread that has been removed since', () => {
189
+ const editor = makeEditor()
190
+ const { thread } = makeThread(editor)
191
+ removeCommentRecords(editor, [thread.id])
192
+
193
+ resolveThread(editor, thread, 'ada')
194
+ reopenThread(editor, thread)
195
+
196
+ expect(readThread(editor, thread)).toBeUndefined()
197
+ })
198
+ })
199
+
200
+ describe('deleteComment', () => {
201
+ it('soft-deletes: the record stays put with its flag set, for the server to prune', () => {
202
+ const editor = makeEditor()
203
+ const { comment } = makeThread(editor)
204
+
205
+ deleteComment(editor, comment)
206
+
207
+ expect(readComment(editor, comment)).toMatchObject({ isDeleted: true })
208
+ expect(getComments(editor)).toHaveLength(1)
209
+ expect(getLiveComments(editor)).toHaveLength(0)
210
+ })
211
+
212
+ it('closes the thread when its last comment goes', () => {
213
+ const editor = makeEditor()
214
+ const { thread, comment } = makeThread(editor)
215
+ openThreadId.set(editor, thread.id)
216
+
217
+ deleteComment(editor, comment)
218
+
219
+ expect(openThreadId.get(editor)).toBe(null)
220
+ })
221
+
222
+ it('leaves the thread open while it still has comments', () => {
223
+ const editor = makeEditor()
224
+ const { thread, comment } = makeThread(editor)
225
+ addComment(editor, thread)
226
+ openThreadId.set(editor, thread.id)
227
+
228
+ deleteComment(editor, comment)
229
+
230
+ expect(openThreadId.get(editor)).toBe(thread.id)
231
+ })
232
+
233
+ // Deleting the same comment twice — a double activation, a handler holding a copy taken before
234
+ // the first delete — shouldn't read as "the last comment went" while a sibling is still live.
235
+ it('leaves the thread open when the comment was already deleted', () => {
236
+ const editor = makeEditor()
237
+ const { thread, comment } = makeThread(editor)
238
+ addComment(editor, thread)
239
+ deleteComment(editor, comment)
240
+ openThreadId.set(editor, thread.id)
241
+
242
+ deleteComment(editor, comment)
243
+
244
+ expect(openThreadId.get(editor)).toBe(thread.id)
245
+ })
246
+
247
+ it('does not resurrect a comment that has been removed since', () => {
248
+ const editor = makeEditor()
249
+ const { comment } = makeThread(editor)
250
+ removeCommentRecords(editor, [comment.id])
251
+
252
+ deleteComment(editor, comment)
253
+
254
+ expect(readComment(editor, comment)).toBeUndefined()
255
+ })
256
+
257
+ it('leaves a different open thread alone', () => {
258
+ const editor = makeEditor()
259
+ const { comment } = makeThread(editor)
260
+ const other = makeThread(editor)
261
+ openThreadId.set(editor, other.thread.id)
262
+
263
+ deleteComment(editor, comment)
264
+
265
+ expect(openThreadId.get(editor)).toBe(other.thread.id)
266
+ })
267
+ })
268
+
269
+ describe('deleteThread', () => {
270
+ it('soft-deletes the thread record', () => {
271
+ const editor = makeEditor()
272
+ const { thread } = makeThread(editor)
273
+
274
+ deleteThread(editor, thread)
275
+
276
+ expect(readThread(editor, thread)).toMatchObject({ isDeleted: true })
277
+ expect(getCommentThreads(editor)).toHaveLength(1)
278
+ expect(getLiveCommentThreads(editor)).toHaveLength(0)
279
+ })
280
+
281
+ it('closes the thread when it is the open one', () => {
282
+ const editor = makeEditor()
283
+ const { thread } = makeThread(editor)
284
+ openThreadId.set(editor, thread.id)
285
+
286
+ deleteThread(editor, thread)
287
+
288
+ expect(openThreadId.get(editor)).toBe(null)
289
+ })
290
+
291
+ it('flags the version in the store, leaving an anchor moved since where it is', () => {
292
+ const editor = makeEditor()
293
+ const { thread } = makeThread(editor)
294
+ putCommentRecords(editor, [{ ...thread, anchor: { type: 'point', x: 50, y: 50 } }])
295
+
296
+ deleteThread(editor, thread)
297
+
298
+ expect(readThread(editor, thread)).toMatchObject({
299
+ isDeleted: true,
300
+ anchor: { type: 'point', x: 50, y: 50 },
301
+ })
302
+ })
303
+
304
+ it('does not resurrect a thread that has been removed since', () => {
305
+ const editor = makeEditor()
306
+ const { thread } = makeThread(editor)
307
+ removeCommentRecords(editor, [thread.id])
308
+
309
+ deleteThread(editor, thread)
310
+
311
+ expect(readThread(editor, thread)).toBeUndefined()
312
+ })
313
+ })
314
+
315
+ describe('history', () => {
316
+ it('does not put comment writes on the undo stack by default', () => {
317
+ const editor = makeEditor()
318
+ editor.markHistoryStoppingPoint()
319
+ const { comment } = makeThread(editor)
320
+
321
+ editor.undo()
322
+
323
+ expect(readComment(editor, comment)).toBeDefined()
324
+ })
325
+
326
+ it('puts them on the undo stack when the history option asks for it', () => {
327
+ const editor = makeEditor(CommentTool.configure({ history: 'record' }))
328
+ editor.markHistoryStoppingPoint()
329
+ const { comment } = makeThread(editor)
330
+
331
+ editor.undo()
332
+
333
+ expect(readComment(editor, comment)).toBeUndefined()
334
+ })
335
+
336
+ // The flag is write-once server-side, so an undo clearing it would be vetoed and rebased —
337
+ // leaving the UI showing a comment the server has already pruned. The shape gives the undo
338
+ // something recorded to rewind, so what's under test is that the delete isn't on the stack
339
+ // rather than that the stack is empty.
340
+ it('never makes a delete undoable, even with history: record', () => {
341
+ const editor = makeEditor(CommentTool.configure({ history: 'record' }))
342
+ const { comment } = makeThread(editor)
343
+ editor.markHistoryStoppingPoint()
344
+ const shapeId = createShapeId()
345
+ editor.createShape({ id: shapeId, type: 'geo', x: 0, y: 0 })
346
+
347
+ deleteComment(editor, comment)
348
+ editor.undo()
349
+
350
+ expect(editor.getShape(shapeId)).toBeUndefined()
351
+ expect(readComment(editor, comment)).toMatchObject({ isDeleted: true })
352
+ })
353
+
354
+ it('never makes a thread delete undoable, even with history: record', () => {
355
+ const editor = makeEditor(CommentTool.configure({ history: 'record' }))
356
+ const { thread } = makeThread(editor)
357
+ editor.markHistoryStoppingPoint()
358
+ const shapeId = createShapeId()
359
+ editor.createShape({ id: shapeId, type: 'geo', x: 0, y: 0 })
360
+
361
+ deleteThread(editor, thread)
362
+ editor.undo()
363
+
364
+ expect(editor.getShape(shapeId)).toBeUndefined()
365
+ expect(readThread(editor, thread)).toMatchObject({ isDeleted: true })
366
+ })
367
+
368
+ it('rejects a nested mutation that resolves to a different history mode', () => {
369
+ const editor = makeEditor(CommentTool.configure({ history: 'record' }))
370
+ const { comment } = makeThread(editor)
371
+
372
+ // A delete always ignores history, so it can't run inside a `record` commit.
373
+ expect(() => commitCommentMutation(editor, () => deleteComment(editor, comment))).toThrow(
374
+ "records history as 'ignore' can't run inside one recording it as 'record'"
375
+ )
376
+
377
+ deleteComment(editor, comment)
378
+ expect(readComment(editor, comment)).toMatchObject({ isDeleted: true })
379
+ })
380
+
381
+ // Store history flushes synchronously under test, so this listener writes from inside the commit
382
+ // that triggered it — where a side effect always sits, with no "after the mutation" to defer to.
383
+ // Matching modes have nothing to disagree about, so its write goes through.
384
+ it('lets a store listener write comments during a commit when the modes match', () => {
385
+ const editor = makeEditor()
386
+ const { thread, comment } = makeThread(editor)
387
+ let hasReacted = false
388
+ editor.store.listen(() => {
389
+ if (hasReacted) return
390
+ hasReacted = true
391
+ putCommentRecords(editor, [{ ...thread, meta: { lastEditedComment: comment.id } }])
392
+ })
393
+
394
+ editComment(editor, comment, toRichText('edited'))
395
+
396
+ expect(readThread(editor, thread)!.meta).toEqual({ lastEditedComment: comment.id })
397
+ })
398
+
399
+ it('rejects a writer used after its commit', () => {
400
+ const editor = makeEditor()
401
+ const { thread } = makeThread(editor)
402
+ let writeAfterCommit: () => void
403
+
404
+ commitCommentMutation(editor, ({ put }) => {
405
+ writeAfterCommit = () => put([{ ...thread, resolved: { at: 1, by: 'ada' } }])
406
+ })
407
+
408
+ expect(() => writeAfterCommit!()).toThrow('cannot be used after its commit has finished')
409
+ })
410
+
411
+ // A host can want pin drags undoable while posts and edits aren't. The drag owns its commit, and
412
+ // its records go through the writer, which keeps `dragHistory` in charge of them.
413
+ it('lets dragHistory govern a drag on its own', () => {
414
+ const editor = makeEditor(CommentTool.configure({ history: 'ignore', dragHistory: 'record' }))
415
+ const { thread } = makeThread(editor)
416
+ editor.markHistoryStoppingPoint()
417
+
418
+ commitCommentMutation(
419
+ editor,
420
+ ({ put }) => put([{ ...thread, anchor: { type: 'point', x: 50, y: 50 } }]),
421
+ 'drag'
422
+ )
423
+ editor.undo()
424
+
425
+ expect(readThread(editor, thread)!.anchor).toEqual({ type: 'point', x: 0, y: 0 })
426
+ })
427
+ })
428
+
429
+ describe('getLiveComments and getLiveCommentThreads', () => {
430
+ it('hides a thread whose comments have all been deleted', () => {
431
+ const editor = makeEditor()
432
+ const { thread, comment } = makeThread(editor)
433
+
434
+ deleteComment(editor, comment)
435
+
436
+ // The thread record itself is untouched — the deleter may not be its creator — but with no
437
+ // live comment left it has no surface.
438
+ expect(readThread(editor, thread)!.isDeleted).toBe(false)
439
+ expect(getLiveCommentThreads(editor)).toHaveLength(0)
440
+ })
441
+
442
+ it('keeps a thread that still has a live comment', () => {
443
+ const editor = makeEditor()
444
+ const { thread, comment } = makeThread(editor)
445
+ addComment(editor, thread)
446
+
447
+ deleteComment(editor, comment)
448
+
449
+ expect(getLiveComments(editor)).toHaveLength(1)
450
+ expect(getLiveCommentThreads(editor)).toEqual([readThread(editor, thread)])
451
+ })
452
+ })