@tldraw/commenting 0.0.0-bootstrap → 5.3.0-canary.31036673d3f8

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 (279) hide show
  1. package/README.md +0 -2
  2. package/commenting.css +1139 -329
  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/canvas/cluster-input.js +2 -2
  6. package/dist-cjs/canvas/cluster-input.js.map +2 -2
  7. package/dist-cjs/canvas/comment-body.js +1 -1
  8. package/dist-cjs/canvas/comment-body.js.map +2 -2
  9. package/dist-cjs/canvas/comment-drafts.js +54 -0
  10. package/dist-cjs/canvas/comment-drafts.js.map +7 -0
  11. package/dist-cjs/canvas/comment-mutations.js +123 -0
  12. package/dist-cjs/canvas/comment-mutations.js.map +7 -0
  13. package/dist-cjs/canvas/comment-reactions.js +163 -0
  14. package/dist-cjs/canvas/comment-reactions.js.map +7 -0
  15. package/dist-cjs/canvas/comment-render.js +5 -3
  16. package/dist-cjs/canvas/comment-render.js.map +2 -2
  17. package/dist-cjs/canvas/comment-store.js +19 -9
  18. package/dist-cjs/canvas/comment-store.js.map +2 -2
  19. package/dist-cjs/canvas/comment-tool.js +64 -14
  20. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  21. package/dist-cjs/canvas/comments-filter-menu.js +20 -38
  22. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  23. package/dist-cjs/canvas/comments-overflow-menu.js +22 -27
  24. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  25. package/dist-cjs/canvas/comments-overlay.js +429 -474
  26. package/dist-cjs/canvas/comments-overlay.js.map +2 -2
  27. package/dist-cjs/canvas/comments-sidebar.js +35 -32
  28. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  29. package/dist-cjs/canvas/comments-visibility-toggle.js +65 -0
  30. package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
  31. package/dist-cjs/canvas/context.js +17 -0
  32. package/dist-cjs/canvas/context.js.map +7 -0
  33. package/dist-cjs/canvas/hooks.js +22 -3
  34. package/dist-cjs/canvas/hooks.js.map +2 -2
  35. package/dist-cjs/canvas/options.js +19 -2
  36. package/dist-cjs/canvas/options.js.map +2 -2
  37. package/dist-cjs/canvas/pin-stacking.js +56 -0
  38. package/dist-cjs/canvas/pin-stacking.js.map +7 -0
  39. package/dist-cjs/canvas/sidebar-filters.js +2 -2
  40. package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
  41. package/dist-cjs/canvas/state.js +28 -14
  42. package/dist-cjs/canvas/state.js.map +2 -2
  43. package/dist-cjs/canvas/thread-preview.js +185 -0
  44. package/dist-cjs/canvas/thread-preview.js.map +7 -0
  45. package/dist-cjs/canvas/thread-stack.js +199 -0
  46. package/dist-cjs/canvas/thread-stack.js.map +7 -0
  47. package/dist-cjs/canvas/thread-state.js +67 -21
  48. package/dist-cjs/canvas/thread-state.js.map +2 -2
  49. package/dist-cjs/canvas/thread-view.js +397 -0
  50. package/dist-cjs/canvas/thread-view.js.map +7 -0
  51. package/dist-cjs/clustering/computeClusterTable.js +4 -4
  52. package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
  53. package/dist-cjs/clustering/runtime.js +119 -7
  54. package/dist-cjs/clustering/runtime.js.map +2 -2
  55. package/dist-cjs/clustering/types.js.map +1 -1
  56. package/dist-cjs/index.d.ts +763 -333
  57. package/dist-cjs/index.js +46 -28
  58. package/dist-cjs/index.js.map +2 -2
  59. package/dist-cjs/ui/byline.js +6 -4
  60. package/dist-cjs/ui/byline.js.map +2 -2
  61. package/dist-cjs/ui/comment-card.js +15 -8
  62. package/dist-cjs/ui/comment-card.js.map +2 -2
  63. package/dist-cjs/ui/comment-composer.js +137 -36
  64. package/dist-cjs/ui/comment-composer.js.map +2 -2
  65. package/dist-cjs/ui/comment-pin.js +24 -2
  66. package/dist-cjs/ui/comment-pin.js.map +2 -2
  67. package/dist-cjs/ui/comment-thread.js +9 -7
  68. package/dist-cjs/ui/comment-thread.js.map +2 -2
  69. package/dist-cjs/ui/comments-list.js +30 -18
  70. package/dist-cjs/ui/comments-list.js.map +2 -2
  71. package/dist-cjs/ui/count-badge.js +3 -2
  72. package/dist-cjs/ui/count-badge.js.map +2 -2
  73. package/dist-cjs/ui/emoji-picker.js +54 -0
  74. package/dist-cjs/ui/emoji-picker.js.map +7 -0
  75. package/dist-cjs/ui/empty-state.js +3 -3
  76. package/dist-cjs/ui/empty-state.js.map +2 -2
  77. package/dist-cjs/ui/format-time.js +4 -1
  78. package/dist-cjs/ui/format-time.js.map +2 -2
  79. package/dist-cjs/ui/reaction-picker.js +73 -0
  80. package/dist-cjs/ui/reaction-picker.js.map +7 -0
  81. package/dist-cjs/ui/reaction.js +59 -6
  82. package/dist-cjs/ui/reaction.js.map +2 -2
  83. package/dist-cjs/ui/reactions.js +20 -7
  84. package/dist-cjs/ui/reactions.js.map +2 -2
  85. package/dist-cjs/ui/{mention.js → reply-count.js} +9 -11
  86. package/dist-cjs/ui/reply-count.js.map +7 -0
  87. package/dist-cjs/ui/send-button.js +20 -1
  88. package/dist-cjs/ui/send-button.js.map +2 -2
  89. package/dist-cjs/ui/{comment-text.js → tooltip-button.js} +12 -9
  90. package/dist-cjs/ui/tooltip-button.js.map +7 -0
  91. package/dist-esm/canvas/anchor-lifecycle.mjs +128 -0
  92. package/dist-esm/canvas/anchor-lifecycle.mjs.map +7 -0
  93. package/dist-esm/canvas/cluster-input.mjs +2 -2
  94. package/dist-esm/canvas/cluster-input.mjs.map +2 -2
  95. package/dist-esm/canvas/comment-body.mjs +1 -1
  96. package/dist-esm/canvas/comment-body.mjs.map +2 -2
  97. package/dist-esm/canvas/comment-drafts.mjs +34 -0
  98. package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
  99. package/dist-esm/canvas/comment-mutations.mjs +103 -0
  100. package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
  101. package/dist-esm/canvas/comment-reactions.mjs +152 -0
  102. package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
  103. package/dist-esm/canvas/comment-render.mjs +5 -3
  104. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  105. package/dist-esm/canvas/comment-store.mjs +19 -9
  106. package/dist-esm/canvas/comment-store.mjs.map +2 -2
  107. package/dist-esm/canvas/comment-tool.mjs +67 -17
  108. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  109. package/dist-esm/canvas/comments-filter-menu.mjs +20 -38
  110. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  111. package/dist-esm/canvas/comments-overflow-menu.mjs +23 -27
  112. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  113. package/dist-esm/canvas/comments-overlay.mjs +450 -479
  114. package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
  115. package/dist-esm/canvas/comments-sidebar.mjs +38 -35
  116. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  117. package/dist-esm/canvas/comments-visibility-toggle.mjs +45 -0
  118. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
  119. package/dist-esm/canvas/context.mjs +1 -0
  120. package/dist-esm/canvas/context.mjs.map +7 -0
  121. package/dist-esm/canvas/hooks.mjs +28 -5
  122. package/dist-esm/canvas/hooks.mjs.map +2 -2
  123. package/dist-esm/canvas/options.mjs +21 -3
  124. package/dist-esm/canvas/options.mjs.map +2 -2
  125. package/dist-esm/canvas/pin-stacking.mjs +36 -0
  126. package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
  127. package/dist-esm/canvas/sidebar-filters.mjs +2 -2
  128. package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
  129. package/dist-esm/canvas/state.mjs +29 -19
  130. package/dist-esm/canvas/state.mjs.map +2 -2
  131. package/dist-esm/canvas/thread-preview.mjs +170 -0
  132. package/dist-esm/canvas/thread-preview.mjs.map +7 -0
  133. package/dist-esm/canvas/thread-stack.mjs +190 -0
  134. package/dist-esm/canvas/thread-stack.mjs.map +7 -0
  135. package/dist-esm/canvas/thread-state.mjs +71 -22
  136. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  137. package/dist-esm/canvas/thread-view.mjs +402 -0
  138. package/dist-esm/canvas/thread-view.mjs.map +7 -0
  139. package/dist-esm/clustering/computeClusterTable.mjs +4 -4
  140. package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
  141. package/dist-esm/clustering/runtime.mjs +119 -7
  142. package/dist-esm/clustering/runtime.mjs.map +2 -2
  143. package/dist-esm/index.d.mts +763 -333
  144. package/dist-esm/index.mjs +87 -41
  145. package/dist-esm/index.mjs.map +2 -2
  146. package/dist-esm/ui/byline.mjs +6 -4
  147. package/dist-esm/ui/byline.mjs.map +2 -2
  148. package/dist-esm/ui/comment-card.mjs +15 -8
  149. package/dist-esm/ui/comment-card.mjs.map +2 -2
  150. package/dist-esm/ui/comment-composer.mjs +148 -36
  151. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  152. package/dist-esm/ui/comment-pin.mjs +24 -2
  153. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  154. package/dist-esm/ui/comment-thread.mjs +9 -7
  155. package/dist-esm/ui/comment-thread.mjs.map +2 -2
  156. package/dist-esm/ui/comments-list.mjs +43 -29
  157. package/dist-esm/ui/comments-list.mjs.map +2 -2
  158. package/dist-esm/ui/count-badge.mjs +3 -2
  159. package/dist-esm/ui/count-badge.mjs.map +2 -2
  160. package/dist-esm/ui/emoji-picker.mjs +34 -0
  161. package/dist-esm/ui/emoji-picker.mjs.map +7 -0
  162. package/dist-esm/ui/empty-state.mjs +3 -3
  163. package/dist-esm/ui/empty-state.mjs.map +2 -2
  164. package/dist-esm/ui/format-time.mjs +4 -1
  165. package/dist-esm/ui/format-time.mjs.map +2 -2
  166. package/dist-esm/ui/reaction-picker.mjs +60 -0
  167. package/dist-esm/ui/reaction-picker.mjs.map +7 -0
  168. package/dist-esm/ui/reaction.mjs +60 -7
  169. package/dist-esm/ui/reaction.mjs.map +2 -2
  170. package/dist-esm/ui/reactions.mjs +21 -8
  171. package/dist-esm/ui/reactions.mjs.map +2 -2
  172. package/dist-esm/ui/reply-count.mjs +9 -0
  173. package/dist-esm/ui/reply-count.mjs.map +7 -0
  174. package/dist-esm/ui/send-button.mjs +20 -1
  175. package/dist-esm/ui/send-button.mjs.map +2 -2
  176. package/dist-esm/ui/tooltip-button.mjs +12 -0
  177. package/dist-esm/ui/tooltip-button.mjs.map +7 -0
  178. package/package.json +6 -6
  179. package/src/canvas/anchor-lifecycle.test.ts +290 -0
  180. package/src/canvas/anchor-lifecycle.ts +205 -0
  181. package/src/canvas/canvas.css +461 -46
  182. package/src/canvas/cluster-input.test.ts +31 -23
  183. package/src/canvas/cluster-input.ts +3 -4
  184. package/src/canvas/comment-body.tsx +1 -1
  185. package/src/canvas/comment-drafts.test.ts +55 -0
  186. package/src/canvas/comment-drafts.ts +46 -0
  187. package/src/canvas/comment-mutations.test.ts +410 -0
  188. package/src/canvas/comment-mutations.ts +288 -0
  189. package/src/canvas/comment-reactions.test.ts +132 -0
  190. package/src/canvas/comment-reactions.tsx +245 -0
  191. package/src/canvas/comment-render.test.ts +1 -1
  192. package/src/canvas/comment-render.ts +9 -5
  193. package/src/canvas/comment-store.ts +67 -27
  194. package/src/canvas/comment-tool.test.ts +162 -0
  195. package/src/canvas/comment-tool.tsx +105 -23
  196. package/src/canvas/comments-filter-menu.tsx +23 -32
  197. package/src/canvas/comments-overflow-menu.tsx +14 -21
  198. package/src/canvas/comments-overlay.tsx +695 -622
  199. package/src/canvas/comments-sidebar.test.ts +45 -0
  200. package/src/canvas/comments-sidebar.tsx +76 -58
  201. package/src/canvas/comments-visibility-toggle.tsx +49 -0
  202. package/src/canvas/context.ts +47 -0
  203. package/src/canvas/hooks.ts +52 -11
  204. package/src/canvas/options.test.ts +52 -2
  205. package/src/canvas/options.ts +133 -9
  206. package/src/canvas/pin-stacking.test.ts +114 -0
  207. package/src/canvas/pin-stacking.ts +57 -0
  208. package/src/canvas/sidebar-filters.ts +4 -3
  209. package/src/canvas/state.ts +92 -35
  210. package/src/canvas/thread-preview.test.ts +91 -0
  211. package/src/canvas/thread-preview.tsx +304 -0
  212. package/src/canvas/thread-stack.tsx +252 -0
  213. package/src/canvas/thread-state.test.ts +209 -0
  214. package/src/canvas/thread-state.ts +137 -36
  215. package/src/canvas/thread-view.tsx +516 -0
  216. package/src/clustering/computeClusterTable.test.ts +24 -23
  217. package/src/clustering/computeClusterTable.ts +5 -5
  218. package/src/clustering/runtime.test.ts +217 -7
  219. package/src/clustering/runtime.ts +171 -17
  220. package/src/clustering/types.ts +9 -9
  221. package/src/index.ts +76 -40
  222. package/src/ui/byline.tsx +7 -5
  223. package/src/ui/comment-card.tsx +20 -10
  224. package/src/ui/comment-composer.tsx +206 -40
  225. package/src/ui/comment-pin.tsx +26 -2
  226. package/src/ui/comment-thread.tsx +13 -6
  227. package/src/ui/comments-list.test.ts +31 -0
  228. package/src/ui/comments-list.tsx +45 -21
  229. package/src/ui/comments.css +566 -281
  230. package/src/ui/count-badge.tsx +9 -2
  231. package/src/ui/emoji-picker.test.ts +25 -0
  232. package/src/ui/emoji-picker.tsx +76 -0
  233. package/src/ui/empty-state.tsx +3 -3
  234. package/src/ui/format-time.ts +7 -2
  235. package/src/ui/reaction-picker.tsx +109 -0
  236. package/src/ui/reaction.tsx +121 -6
  237. package/src/ui/reactions.tsx +69 -10
  238. package/src/ui/reply-count.ts +16 -0
  239. package/src/ui/send-button.tsx +18 -3
  240. package/src/ui/tooltip-button.tsx +20 -0
  241. package/dist-cjs/canvas/region-options.js +0 -49
  242. package/dist-cjs/canvas/region-options.js.map +0 -7
  243. package/dist-cjs/ui/avatar.js +0 -43
  244. package/dist-cjs/ui/avatar.js.map +0 -7
  245. package/dist-cjs/ui/comment-mention.js +0 -42
  246. package/dist-cjs/ui/comment-mention.js.map +0 -7
  247. package/dist-cjs/ui/comment-text.js.map +0 -7
  248. package/dist-cjs/ui/mention-list.js +0 -70
  249. package/dist-cjs/ui/mention-list.js.map +0 -7
  250. package/dist-cjs/ui/mention-suggestion.js +0 -206
  251. package/dist-cjs/ui/mention-suggestion.js.map +0 -7
  252. package/dist-cjs/ui/mention.js.map +0 -7
  253. package/dist-cjs/ui/render-markdown.js +0 -63
  254. package/dist-cjs/ui/render-markdown.js.map +0 -7
  255. package/dist-esm/canvas/region-options.mjs +0 -29
  256. package/dist-esm/canvas/region-options.mjs.map +0 -7
  257. package/dist-esm/ui/avatar.mjs +0 -23
  258. package/dist-esm/ui/avatar.mjs.map +0 -7
  259. package/dist-esm/ui/comment-mention.mjs +0 -22
  260. package/dist-esm/ui/comment-mention.mjs.map +0 -7
  261. package/dist-esm/ui/comment-text.mjs +0 -9
  262. package/dist-esm/ui/comment-text.mjs.map +0 -7
  263. package/dist-esm/ui/mention-list.mjs +0 -50
  264. package/dist-esm/ui/mention-list.mjs.map +0 -7
  265. package/dist-esm/ui/mention-suggestion.mjs +0 -186
  266. package/dist-esm/ui/mention-suggestion.mjs.map +0 -7
  267. package/dist-esm/ui/mention.mjs +0 -11
  268. package/dist-esm/ui/mention.mjs.map +0 -7
  269. package/dist-esm/ui/render-markdown.mjs +0 -45
  270. package/dist-esm/ui/render-markdown.mjs.map +0 -7
  271. package/src/canvas/region-options.ts +0 -57
  272. package/src/ui/avatar.tsx +0 -31
  273. package/src/ui/comment-mention.ts +0 -47
  274. package/src/ui/comment-text.tsx +0 -12
  275. package/src/ui/mention-list.tsx +0 -106
  276. package/src/ui/mention-suggestion.test.ts +0 -18
  277. package/src/ui/mention-suggestion.tsx +0 -285
  278. package/src/ui/mention.tsx +0 -9
  279. package/src/ui/render-markdown.tsx +0 -72
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/canvas/thread-state.ts"],
4
- "sourcesContent": ["import { BoxModel, Editor, TLCommentAnchor, TLCommentThread, TLShapeId, VecLike } from 'tldraw'\nimport { getRegionCommentOptions } from './region-options'\nimport { openThreadId } from './state'\n\n/** Where an imprecise shape comment sits by default: the shape's top-right corner. Overridable. @public */\nexport const DEFAULT_IMPRECISE_SHAPE_ANCHOR = { x: 1, y: 0 }\n\n/** The default corner a region's pin and composer sit on, as a normalized 0\u20131 offset (bottom-right).\n * Overridable per editor via region options; pin position, composer placement, region move, and\n * which corner has no resize handle all derive from the chosen corner. */\nexport const REGION_PIN_CORNER: VecLike = { x: 1, y: 1 }\n\n/** The page point of a region's pin corner. */\nexport function regionPinPoint(region: BoxModel, corner: VecLike = REGION_PIN_CORNER): VecLike {\n\treturn {\n\t\tx: region.x + corner.x * region.w,\n\t\ty: region.y + corner.y * region.h,\n\t}\n}\n\n/**\n * Where a thread's pin sits on the page, for each anchor kind. Null hides the pin. For imprecise\n * shape anchors the pin uses `impreciseShapeAnchor` (a normalized 0\u20131 spot, top-right by default)\n * rather than the stored `x`/`y`.\n * @public\n */\nexport function anchorPagePoint(\n\teditor: Editor,\n\tanchor: TLCommentAnchor,\n\timpreciseShapeAnchor: { x: number; y: number } = DEFAULT_IMPRECISE_SHAPE_ANCHOR\n): { x: number; y: number } | null {\n\tswitch (anchor.type) {\n\t\tcase 'shape': {\n\t\t\tconst bounds = editor.getShapePageBounds(anchor.shapeId as TLShapeId)\n\t\t\tif (!bounds) return null\n\t\t\t// Precise pins sit at their stored x/y; imprecise ones at the consumer's default spot.\n\t\t\tconst { x, y } = anchor.isPrecise ? anchor : impreciseShapeAnchor\n\t\t\treturn { x: bounds.minX + x * bounds.w, y: bounds.minY + y * bounds.h }\n\t\t}\n\t\tcase 'text-range': {\n\t\t\tconst bounds = editor.getShapePageBounds(anchor.shapeId as TLShapeId)\n\t\t\tif (!bounds) return null\n\t\t\treturn { x: bounds.maxX, y: bounds.minY }\n\t\t}\n\t\tcase 'point':\n\t\t\treturn { x: anchor.x, y: anchor.y }\n\t\tcase 'region':\n\t\t\treturn regionPinPoint(anchor, getRegionCommentOptions(editor).pinCorner)\n\t\tcase 'page':\n\t\t\treturn null\n\t}\n}\n\n/**\n * A shape anchor for a page point. `x`/`y` are the point's normalized (0\u20131) offset within the\n * shape's page bounds, remembered either way. When `precise` (Alt held) the pin sits at exactly\n * `x`/`y`; otherwise it sits at the consumer's imprecise default (top-right out of the box).\n * @public\n */\nexport function shapeAnchorAt(\n\teditor: Editor,\n\tshapeId: TLShapeId,\n\tpage: { x: number; y: number },\n\tprecise: boolean\n): TLCommentAnchor {\n\tconst bounds = editor.getShapePageBounds(shapeId)\n\tif (!bounds || bounds.w === 0 || bounds.h === 0) {\n\t\treturn { type: 'shape', shapeId, x: 0.5, y: 0.5, isPrecise: precise }\n\t}\n\treturn {\n\t\ttype: 'shape',\n\t\tshapeId,\n\t\tx: (page.x - bounds.minX) / bounds.w,\n\t\ty: (page.y - bounds.minY) / bounds.h,\n\t\tisPrecise: precise,\n\t}\n}\n\n/** Open a thread and bring it into view \u2014 switch to its page if needed, then center its pin. @public */\nexport function focusThread(\n\teditor: Editor,\n\tthread: TLCommentThread,\n\timpreciseShapeAnchor?: { x: number; y: number }\n): void {\n\tif (thread.pageId !== editor.getCurrentPageId()) {\n\t\teditor.setCurrentPage(thread.pageId as any)\n\t}\n\topenThreadId.set(editor, thread.id)\n\tconst point = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor)\n\tif (point) editor.centerOnPoint(point, { animation: { duration: 200 } })\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,4BAAwC;AACxC,mBAA6B;AAGtB,MAAM,iCAAiC,EAAE,GAAG,GAAG,GAAG,EAAE;AAKpD,MAAM,oBAA6B,EAAE,GAAG,GAAG,GAAG,EAAE;AAGhD,SAAS,eAAe,QAAkB,SAAkB,mBAA4B;AAC9F,SAAO;AAAA,IACN,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO;AAAA,IAChC,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO;AAAA,EACjC;AACD;AAQO,SAAS,gBACf,QACA,QACA,uBAAiD,gCACf;AAClC,UAAQ,OAAO,MAAM;AAAA,IACpB,KAAK,SAAS;AACb,YAAM,SAAS,OAAO,mBAAmB,OAAO,OAAoB;AACpE,UAAI,CAAC,OAAQ,QAAO;AAEpB,YAAM,EAAE,GAAG,EAAE,IAAI,OAAO,YAAY,SAAS;AAC7C,aAAO,EAAE,GAAG,OAAO,OAAO,IAAI,OAAO,GAAG,GAAG,OAAO,OAAO,IAAI,OAAO,EAAE;AAAA,IACvE;AAAA,IACA,KAAK,cAAc;AAClB,YAAM,SAAS,OAAO,mBAAmB,OAAO,OAAoB;AACpE,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,EAAE,GAAG,OAAO,MAAM,GAAG,OAAO,KAAK;AAAA,IACzC;AAAA,IACA,KAAK;AACJ,aAAO,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,EAAE;AAAA,IACnC,KAAK;AACJ,aAAO,eAAe,YAAQ,+CAAwB,MAAM,EAAE,SAAS;AAAA,IACxE,KAAK;AACJ,aAAO;AAAA,EACT;AACD;AAQO,SAAS,cACf,QACA,SACA,MACA,SACkB;AAClB,QAAM,SAAS,OAAO,mBAAmB,OAAO;AAChD,MAAI,CAAC,UAAU,OAAO,MAAM,KAAK,OAAO,MAAM,GAAG;AAChD,WAAO,EAAE,MAAM,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK,WAAW,QAAQ;AAAA,EACrE;AACA,SAAO;AAAA,IACN,MAAM;AAAA,IACN;AAAA,IACA,IAAI,KAAK,IAAI,OAAO,QAAQ,OAAO;AAAA,IACnC,IAAI,KAAK,IAAI,OAAO,QAAQ,OAAO;AAAA,IACnC,WAAW;AAAA,EACZ;AACD;AAGO,SAAS,YACf,QACA,QACA,sBACO;AACP,MAAI,OAAO,WAAW,OAAO,iBAAiB,GAAG;AAChD,WAAO,eAAe,OAAO,MAAa;AAAA,EAC3C;AACA,4BAAa,IAAI,QAAQ,OAAO,EAAE;AAClC,QAAM,QAAQ,gBAAgB,QAAQ,OAAO,QAAQ,oBAAoB;AACzE,MAAI,MAAO,QAAO,cAAc,OAAO,EAAE,WAAW,EAAE,UAAU,IAAI,EAAE,CAAC;AACxE;",
4
+ "sourcesContent": ["import {\n\tBoxModel,\n\tEditor,\n\tMat,\n\tTLCommentAnchor,\n\tTLCommentThread,\n\tTLShape,\n\tTLShapeId,\n\tVec,\n\tVecLike,\n} from 'tldraw'\nimport { getCommentingOptions } from './options'\nimport { commentsSidebarOpen, openThreadId } from './state'\nimport { POPOVER_OFFSET } from './thread-view'\n\n/** How far an imprecise shape pin steps inside the shape from its anchor spot, in screen px \u2014\n * most of the marker sits within the shape, with a small overhang past the corner. */\nexport const IMPRECISE_PIN_INSET_PX = 20\n\n/** Imprecise shape pins tuck inside the shape rather than hanging off its edge: the marker\n * extends up-right of its anchor point, so step it toward the shape's centre. Screen px \u2014 the\n * pin is screen-fixed while the shape scales with zoom. Null for anchors that need no inset. */\nexport function impreciseShapePinInset(\n\teditor: Editor,\n\tanchor: TLCommentThread['anchor']\n): { x: number; y: number } | null {\n\tif (anchor.type !== 'shape' || anchor.isPrecise) return null\n\tconst spot = getCommentingOptions(editor).impreciseShapeAnchor\n\tconst inset = {\n\t\tx: Math.sign(0.5 - spot.x) * IMPRECISE_PIN_INSET_PX,\n\t\ty: Math.sign(0.5 - spot.y) * IMPRECISE_PIN_INSET_PX,\n\t}\n\t// The spot is a corner of the shape's own bounds, so which way \"toward the centre\" points turns\n\t// with the shape: without this a rotated shape's pin would step out of the shape, not into it.\n\tconst rotation = editor.getShapePageTransform(anchor.shapeId as TLShapeId)?.rotation() ?? 0\n\treturn rotation === 0 ? inset : Vec.Rot(inset, rotation)\n}\n\n/** The corner a region's pin and composer sit on, as a normalized 0\u20131 offset (bottom-right). Pin\n * position, composer placement, region move, and which corner has no resize handle all derive\n * from it. */\nexport const REGION_PIN_CORNER: VecLike = { x: 1, y: 1 }\n\n/** A region anchor's pin corner: the corner its creating drag released on, when recorded, else\n * {@link REGION_PIN_CORNER}. @internal */\nexport function regionAnchorPinCorner(\n\tanchor: Extract<TLCommentAnchor, { type: 'region' }>\n): VecLike {\n\tif (anchor.pinX !== undefined && anchor.pinY !== undefined) {\n\t\treturn { x: anchor.pinX, y: anchor.pinY }\n\t}\n\treturn REGION_PIN_CORNER\n}\n\n/** The page point of a region's pin corner. */\nexport function regionPinPoint(region: BoxModel, corner: VecLike = REGION_PIN_CORNER): VecLike {\n\treturn {\n\t\tx: region.x + corner.x * region.w,\n\t\ty: region.y + corner.y * region.h,\n\t}\n}\n\n/**\n * Where a thread's pin sits on the page, for each anchor kind. Null hides the pin. For imprecise\n * shape anchors the pin uses the editor's {@link CommentingOptions.impreciseShapeAnchor} (a\n * normalized 0\u20131 spot, top-right by default) rather than the stored `x`/`y`.\n *\n * A shape anchor's `x`/`y` are normalized within the shape's own bounds and resolved through the\n * shape's page transform, so the pin rides every part of that transform \u2014 rotating the shape\n * carries the pin around with it instead of leaving it behind in the bounding box.\n * @public\n */\nexport function anchorPagePoint(\n\teditor: Editor,\n\tanchor: TLCommentAnchor\n): { x: number; y: number } | null {\n\tswitch (anchor.type) {\n\t\tcase 'shape': {\n\t\t\tconst shape = editor.getShape(anchor.shapeId as TLShapeId)\n\t\t\tif (!shape) return null\n\t\t\tconst transform = editor.getShapePageTransform(shape)\n\t\t\tif (!transform) return null\n\t\t\tconst { point, size } = editor.getShapeGeometry(shape).bounds\n\t\t\t// Precise pins sit at their stored x/y; imprecise ones at the editor's configured spot.\n\t\t\tconst spot = anchor.isPrecise ? anchor : getCommentingOptions(editor).impreciseShapeAnchor\n\t\t\treturn Mat.applyToPoint(transform, Vec.Add(point, Vec.MulV(spot, size)))\n\t\t}\n\t\tcase 'point':\n\t\t\treturn { x: anchor.x, y: anchor.y }\n\t\tcase 'region':\n\t\t\treturn regionPinPoint(anchor, regionAnchorPinCorner(anchor))\n\t\tcase 'page':\n\t\t\treturn null\n\t}\n}\n\n/**\n * The shape a comment placed at a page point should anchor to, or undefined for empty canvas.\n *\n * Uses the editor's hit-test margin, the same slack select and hover use. Without it, shapes whose\n * geometry is an open path \u2014 arrows, lines, draw strokes \u2014 are unhittable in practice: their\n * geometry reports a positive distance for every point off the stroke, so a zero margin only\n * matches a pixel-perfect click right on the line.\n *\n * `hitFrameInside` lets a click inside a frame's body anchor to the frame \u2014 without it a frame is\n * hit only on its edge/label (the select-tool convention), so the frame's interior would fall\n * through to a bare point. A child shape under the pointer still wins (children sort above the\n * frame), so only a frame's empty interior anchors the frame.\n *\n * @internal\n */\nexport function commentTargetShapeAt(editor: Editor, page: VecLike): TLShape | undefined {\n\treturn editor.getShapeAtPoint(page, {\n\t\thitInside: true,\n\t\thitFrameInside: true,\n\t\tmargin: editor.getHitTestMargin(),\n\t})\n}\n\n/**\n * A shape anchor for a page point. `x`/`y` are the point's normalized (0\u20131) offset within the\n * shape's own bounds \u2014 taken in the shape's own space, so a pin placed on a rotated shape records\n * the spot it was dropped on rather than a spot in the bounding box. Remembered either way: when\n * `precise` the pin sits at exactly `x`/`y`; otherwise it sits at the consumer's imprecise default\n * (top-right out of the box). Placement gestures get `precise` from the `shouldBePrecise`\n * commenting option (always precise, by default).\n * @public\n */\nexport function shapeAnchorAt(\n\teditor: Editor,\n\tshapeId: TLShapeId,\n\tpage: { x: number; y: number },\n\tprecise: boolean\n): TLCommentAnchor {\n\tconst shape = editor.getShape(shapeId)\n\tconst bounds = shape && editor.getShapeGeometry(shape).bounds\n\tif (!shape || !bounds || bounds.w === 0 || bounds.h === 0) {\n\t\treturn { type: 'shape', shapeId, x: 0.5, y: 0.5, isPrecise: precise }\n\t}\n\tconst local = editor.getPointInShapeSpace(shape, page)\n\treturn {\n\t\ttype: 'shape',\n\t\tshapeId,\n\t\tx: (local.x - bounds.minX) / bounds.w,\n\t\ty: (local.y - bounds.minY) / bounds.h,\n\t\tisPrecise: precise,\n\t}\n}\n\n/** Open a thread and bring it into view \u2014 switch to its page if needed, then center its pin. @public */\nexport function focusThread(editor: Editor, thread: TLCommentThread): void {\n\tif (thread.pageId !== editor.getCurrentPageId()) {\n\t\teditor.setCurrentPage(thread.pageId as any)\n\t}\n\topenThreadId.set(editor, thread.id)\n\tconst point = anchorPagePoint(editor, thread.anchor)\n\tif (!point) return\n\tconst offset = commentCenterScreenOffset(editor) / editor.getZoomLevel()\n\teditor.centerOnPoint({ x: point.x + offset, y: point.y }, { animation: { duration: 200 } })\n}\n\n/** The left inset a nudged pin never crosses, and the gap kept between thread UI and sidebar. */\nconst CENTER_MARGIN_PX = 8\n\n/** How far the open thread UI reaches right of its pin, in screen px: the popover's offset plus\n * the thread panel's fixed width (300px, `.tlui-cmt-thread`). */\nconst THREAD_UI_EXTENT_PX = POPOVER_OFFSET.thread.x + 300\n\n/**\n * How far right of a centered-on pin the true viewport center should sit, in screen px. While the\n * comments sidebar covers the viewport's right edge, centering a pin dead-center puts the thread\n * popover that opens on it under the sidebar \u2014 so centering aims the pin at the middle of the\n * uncovered area instead, nudged further left if the thread UI would still reach the sidebar, but\n * never past the viewport's left edge. Zero when the sidebar is closed or not on screen.\n * @internal\n */\nexport function commentCenterScreenOffset(editor: Editor): number {\n\tif (!commentsSidebarOpen.get(editor)) return 0\n\tconst sidebar = editor.getContainer().querySelector('.tlui-cmt-canvas-sidebar')\n\tif (!sidebar) return 0\n\tconst viewport = editor.getViewportScreenBounds()\n\t// Viewport screen bounds are the container's client rect, so this is container-local.\n\tconst sidebarLeft = sidebar.getBoundingClientRect().left - viewport.x\n\tif (sidebarLeft >= viewport.w) return 0\n\t// Aim the pin at the middle of the uncovered area, nudged left until the thread UI clears the\n\t// sidebar \u2014 but never past the left edge (the edge wins when there's no room for both).\n\tconst pinX = Math.max(\n\t\tMath.min(sidebarLeft / 2, sidebarLeft - CENTER_MARGIN_PX - THREAD_UI_EXTENT_PX),\n\t\tCENTER_MARGIN_PX\n\t)\n\treturn viewport.w / 2 - pinX\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,397 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var thread_view_exports = {};
20
+ __export(thread_view_exports, {
21
+ POPOVER_OFFSET: () => POPOVER_OFFSET,
22
+ ThreadPopover: () => ThreadPopover,
23
+ ThreadView: () => ThreadView,
24
+ toCardProps: () => toCardProps,
25
+ useResolveName: () => useResolveName
26
+ });
27
+ module.exports = __toCommonJS(thread_view_exports);
28
+ var import_jsx_runtime = require("react/jsx-runtime");
29
+ var import_react = require("react");
30
+ var import_react_dom = require("react-dom");
31
+ var import_tldraw = require("tldraw");
32
+ var import_comment_card = require("../ui/comment-card");
33
+ var import_comment_composer = require("../ui/comment-composer");
34
+ var import_comment_extensions = require("../ui/comment-extensions");
35
+ var import_comment_thread = require("../ui/comment-thread");
36
+ var import_tooltip_button = require("../ui/tooltip-button");
37
+ var import_comment_body = require("./comment-body");
38
+ var import_comment_drafts = require("./comment-drafts");
39
+ var import_comment_mutations = require("./comment-mutations");
40
+ var import_comment_reactions = require("./comment-reactions");
41
+ var import_comment_render = require("./comment-render");
42
+ var import_hooks = require("./hooks");
43
+ var import_options = require("./options");
44
+ var import_state = require("./state");
45
+ const stop = (e) => e.stopPropagation();
46
+ function useResolveName(resolveAuthor) {
47
+ return (0, import_react.useCallback)((id) => resolveAuthor(id)?.name, [resolveAuthor]);
48
+ }
49
+ function toCardProps(comment, props, components, resolveName) {
50
+ const Body = components.CommentBody;
51
+ const body = Body ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Body, { comment }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_body.CommentBody, { richText: comment.body, resolveName });
52
+ return {
53
+ author: props.resolveAuthor(comment.authorId) ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
54
+ body,
55
+ date: new Date(comment.createdAt).toISOString(),
56
+ you: comment.authorId === props.currentUserId,
57
+ edited: comment.editedAt != null
58
+ };
59
+ }
60
+ const PIN_SIZE = 28;
61
+ const MARKER_SIZE = PIN_SIZE;
62
+ const CARD_TOP_Y = -28;
63
+ const PREVIEW_GAP = 6;
64
+ const POPOVER_OFFSET = {
65
+ thread: { x: PIN_SIZE + PREVIEW_GAP, y: CARD_TOP_Y - PIN_SIZE / 2 },
66
+ list: { x: MARKER_SIZE + PREVIEW_GAP, y: CARD_TOP_Y - MARKER_SIZE / 2 }
67
+ };
68
+ function ThreadPopover({
69
+ container,
70
+ style,
71
+ children
72
+ }) {
73
+ const ref = (0, import_react.useRef)(null);
74
+ (0, import_tldraw.usePassThroughWheelEvents)(ref);
75
+ (0, import_tldraw.usePassThroughMouseOverEvents)(ref);
76
+ return (0, import_react_dom.createPortal)(
77
+ // contextmenu also stops here: portals bubble React events to the canvas's context-menu
78
+ // trigger (the layer mounts inside it), which would open the canvas menu over this panel.
79
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
80
+ "div",
81
+ {
82
+ ref,
83
+ className: "tlui-cmt-canvas-popover",
84
+ style,
85
+ onPointerDown: stop,
86
+ onContextMenu: stop,
87
+ children
88
+ }
89
+ ),
90
+ container
91
+ );
92
+ }
93
+ function ThreadView({
94
+ editor,
95
+ thread,
96
+ ...props
97
+ }) {
98
+ const {
99
+ currentUserId,
100
+ resolveAuthor,
101
+ onPostComment,
102
+ isCommentUnread,
103
+ onCommentRead,
104
+ getMentionSuggestions,
105
+ renderMentionSuggestion
106
+ } = props;
107
+ const options = (0, import_options.useCommentingOptions)();
108
+ const comments = (0, import_hooks.useThreadComments)(editor, thread.id);
109
+ const msg = (0, import_tldraw.useTranslation)();
110
+ const resolveName = useResolveName(resolveAuthor);
111
+ const me = currentUserId ? resolveAuthor(currentUserId) : void 0;
112
+ const canComment = (0, import_options.useCanComment)(currentUserId);
113
+ const ComposerFallback = options.components.ComposerFallback;
114
+ const [reply, setReply] = (0, import_react.useState)(
115
+ () => (0, import_comment_drafts.getCommentDraft)((0, import_comment_drafts.replyDraftSlot)(thread.id)) ?? import_comment_extensions.EMPTY_COMMENT
116
+ );
117
+ const [editingId, setEditingId] = (0, import_react.useState)(null);
118
+ const [editText, setEditText] = (0, import_react.useState)(import_comment_extensions.EMPTY_COMMENT);
119
+ const canReply = canComment && !thread.resolved;
120
+ const container = (0, import_tldraw.useContainer)();
121
+ const editReturnFocus = (0, import_react.useRef)(null);
122
+ const [focusReply, setFocusReply] = (0, import_react.useState)(false);
123
+ const tabTaken = (0, import_react.useRef)(false);
124
+ const swallowTabUp = (0, import_react.useRef)(false);
125
+ (0, import_react.useEffect)(() => {
126
+ if (!canReply) {
127
+ setFocusReply(false);
128
+ tabTaken.current = false;
129
+ return;
130
+ }
131
+ const doc = container.ownerDocument;
132
+ const onKeyDown = (e) => {
133
+ if (e.key !== "Tab" || e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return;
134
+ if (e.defaultPrevented || tabTaken.current) return;
135
+ if (e.target !== container && e.target !== doc.body) return;
136
+ tabTaken.current = true;
137
+ swallowTabUp.current = true;
138
+ setFocusReply(true);
139
+ e.preventDefault();
140
+ e.stopPropagation();
141
+ };
142
+ const onKeyUp = (e) => {
143
+ if (e.key !== "Tab" || !swallowTabUp.current) return;
144
+ swallowTabUp.current = false;
145
+ e.preventDefault();
146
+ e.stopPropagation();
147
+ };
148
+ doc.addEventListener("keydown", onKeyDown, true);
149
+ doc.addEventListener("keyup", onKeyUp, true);
150
+ return () => {
151
+ doc.removeEventListener("keydown", onKeyDown, true);
152
+ doc.removeEventListener("keyup", onKeyUp, true);
153
+ };
154
+ }, [canReply, container]);
155
+ (0, import_react.useEffect)(() => {
156
+ if (editingId !== null) return;
157
+ const resolve = editReturnFocus.current;
158
+ editReturnFocus.current = null;
159
+ resolve?.()?.focus();
160
+ }, [editingId]);
161
+ (0, import_react.useEffect)(() => {
162
+ if (!isCommentUnread || !onCommentRead) return;
163
+ for (const comment of comments) {
164
+ if (isCommentUnread(comment.id)) {
165
+ onCommentRead(comment.id);
166
+ }
167
+ }
168
+ }, [comments, isCommentUnread, onCommentRead]);
169
+ const postReply = () => {
170
+ if ((0, import_comment_extensions.isCommentEmpty)(reply) || !currentUserId) return;
171
+ (0, import_comment_mutations.commitCommentMutation)(editor, () => {
172
+ const comment = (0, import_tldraw.createComment)({
173
+ threadId: thread.id,
174
+ pageId: thread.pageId,
175
+ authorId: currentUserId,
176
+ body: reply
177
+ });
178
+ (0, import_comment_mutations.putRecordsInCommit)(editor, [comment]);
179
+ if (onPostComment) onPostComment(comment);
180
+ });
181
+ setReply(import_comment_extensions.EMPTY_COMMENT);
182
+ (0, import_comment_drafts.clearCommentDraft)((0, import_comment_drafts.replyDraftSlot)(thread.id));
183
+ };
184
+ const toggleResolve = () => {
185
+ if (!currentUserId) return;
186
+ if (thread.resolved) (0, import_comment_mutations.reopenThread)(editor, thread);
187
+ else (0, import_comment_mutations.resolveThread)(editor, thread, currentUserId);
188
+ };
189
+ const removeThread = () => {
190
+ if (!currentUserId) return;
191
+ (0, import_comment_mutations.deleteThread)(editor, thread);
192
+ };
193
+ const startEdit = (comment, { fromMoreMenu = false } = {}) => {
194
+ const active = container.ownerDocument.activeElement;
195
+ editReturnFocus.current = fromMoreMenu ? () => container.querySelector(`[data-cmt-more-for="${comment.id}"]`) : active instanceof HTMLElement && active !== container && active !== document.body ? () => active.isConnected ? active : null : null;
196
+ setEditingId(comment.id);
197
+ setEditText(comment.body);
198
+ };
199
+ const saveEdit = () => {
200
+ const comment = comments.find((c) => c.id === editingId);
201
+ if (!comment || (0, import_comment_extensions.isCommentEmpty)(editText)) return;
202
+ (0, import_comment_mutations.editComment)(editor, comment, editText);
203
+ setEditingId(null);
204
+ };
205
+ const renderComment = (card, index) => {
206
+ const comment = comments[index];
207
+ if (editingId === comment.id) {
208
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
209
+ "div",
210
+ {
211
+ className: "tlui-cmt-editing",
212
+ onKeyDown: (e) => {
213
+ if (e.key === "Escape") {
214
+ setEditingId(null);
215
+ e.stopPropagation();
216
+ }
217
+ },
218
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
219
+ import_comment_composer.CommentComposer,
220
+ {
221
+ author: card.author,
222
+ placeholder: msg("comments.edit-placeholder"),
223
+ value: editText,
224
+ onChange: setEditText,
225
+ onSubmit: saveEdit,
226
+ sendLabel: msg("comments.save"),
227
+ disabled: (0, import_comment_extensions.isCommentEmpty)(editText),
228
+ getMentionSuggestions,
229
+ renderMentionSuggestion,
230
+ autoFocus: true
231
+ }
232
+ )
233
+ }
234
+ );
235
+ }
236
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
237
+ import_comment_card.CommentCard,
238
+ {
239
+ ...card,
240
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
241
+ import_comment_reactions.CommentReactions,
242
+ {
243
+ comment,
244
+ currentUserId: canComment ? currentUserId : null,
245
+ resolveName
246
+ }
247
+ ),
248
+ actions: canComment && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_reactions.CommentReactionPicker, { comment, currentUserId }),
250
+ comment.authorId === currentUserId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_tldraw.TldrawUiDropdownMenuRoot, { id: `comment-actions-${comment.id}`, children: [
251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
252
+ import_tooltip_button.TooltipButton,
253
+ {
254
+ tooltip: msg("comments.more-options"),
255
+ className: "tlui-cmt-thread__action",
256
+ "data-cmt-more-for": comment.id,
257
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
258
+ import_tldraw.TldrawUiIcon,
259
+ {
260
+ icon: "dots-vertical",
261
+ label: msg("comments.more-options"),
262
+ small: true
263
+ }
264
+ )
265
+ }
266
+ ) }),
267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
268
+ import_tldraw.TldrawUiDropdownMenuContent,
269
+ {
270
+ className: "tlui-cmt-menu",
271
+ side: "bottom",
272
+ align: "start",
273
+ alignOffset: 0,
274
+ sideOffset: 4,
275
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_tldraw.TldrawUiDropdownMenuGroup, { children: [
276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
277
+ "button",
278
+ {
279
+ type: "button",
280
+ className: "tlui-cmt-menu-item",
281
+ onClick: () => startEdit(comment, { fromMoreMenu: true }),
282
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: msg("comments.edit") })
283
+ }
284
+ ) }),
285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
286
+ "button",
287
+ {
288
+ type: "button",
289
+ className: "tlui-cmt-menu-item tlui-cmt-menu-item--danger",
290
+ onClick: () => (0, import_comment_mutations.deleteComment)(editor, comment),
291
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: msg("action.delete") })
292
+ }
293
+ ) })
294
+ ] })
295
+ }
296
+ )
297
+ ] })
298
+ ] })
299
+ }
300
+ );
301
+ };
302
+ const headerActions = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
303
+ canComment && currentUserId && currentUserId === thread.createdBy && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_tldraw.TldrawUiDropdownMenuRoot, { id: `comment-thread-actions-${thread.id}`, children: [
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
305
+ import_tooltip_button.TooltipButton,
306
+ {
307
+ tooltip: msg("comments.more-options"),
308
+ className: "tlui-cmt-thread__action",
309
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "dots-vertical", label: msg("comments.more-options"), small: true })
310
+ }
311
+ ) }),
312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
313
+ import_tldraw.TldrawUiDropdownMenuContent,
314
+ {
315
+ className: "tlui-cmt-menu",
316
+ side: "bottom",
317
+ align: "start",
318
+ alignOffset: 0,
319
+ sideOffset: 4,
320
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuGroup, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
321
+ "button",
322
+ {
323
+ type: "button",
324
+ className: "tlui-cmt-menu-item tlui-cmt-menu-item--danger",
325
+ onClick: removeThread,
326
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: msg("comments.delete") })
327
+ }
328
+ ) }) })
329
+ }
330
+ )
331
+ ] }),
332
+ canComment && currentUserId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
333
+ import_tooltip_button.TooltipButton,
334
+ {
335
+ tooltip: msg(thread.resolved ? "comments.reopen" : "comments.resolve"),
336
+ className: "tlui-cmt-thread__action",
337
+ onClick: toggleResolve,
338
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
339
+ import_tldraw.TldrawUiIcon,
340
+ {
341
+ icon: "check",
342
+ label: msg(thread.resolved ? "comments.reopen" : "comments.resolve"),
343
+ small: true
344
+ }
345
+ )
346
+ }
347
+ ),
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
349
+ import_tooltip_button.TooltipButton,
350
+ {
351
+ tooltip: msg("comments.dismiss"),
352
+ className: "tlui-cmt-thread__action",
353
+ onClick: () => import_state.openThreadId.set(editor, null),
354
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "cross-2", label: msg("comments.dismiss"), small: true })
355
+ }
356
+ )
357
+ ] });
358
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
359
+ import_comment_thread.CommentThread,
360
+ {
361
+ header: msg("comments.thread-title"),
362
+ headerActions,
363
+ renderComment,
364
+ comments: comments.map((c) => toCardProps(c, props, options.components, resolveName)),
365
+ resolvedBanner: thread.resolved ? msg("comments.resolved-by").replace(
366
+ "{name}",
367
+ resolveAuthor(thread.resolved.by)?.name ?? import_comment_render.UNKNOWN_AUTHOR
368
+ ) : void 0,
369
+ composer: canReply ? {
370
+ author: me ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
371
+ placeholder: msg("comments.reply-placeholder"),
372
+ sendLabel: msg("comments.send"),
373
+ value: reply,
374
+ onChange: (value) => {
375
+ setReply(value);
376
+ (0, import_comment_drafts.saveCommentDraft)((0, import_comment_drafts.replyDraftSlot)(thread.id), value);
377
+ },
378
+ onSubmit: postReply,
379
+ // Up in the empty reply box edits the comment directly above it, chat-style —
380
+ // only when that comment is yours (the same gate as the Edit link).
381
+ onArrowUpWhenEmpty: () => {
382
+ const last = comments[comments.length - 1];
383
+ if (last && last.authorId === currentUserId) startEdit(last);
384
+ },
385
+ // No user, no author for the record — dead send button.
386
+ disabled: (0, import_comment_extensions.isCommentEmpty)(reply) || !currentUserId,
387
+ getMentionSuggestions,
388
+ renderMentionSuggestion,
389
+ // Reuses the composer's own focus path, so the caret lands at the end of a
390
+ // restored draft rather than in front of it.
391
+ autoFocus: focusReply
392
+ } : void 0,
393
+ footer: !canComment && !thread.resolved && ComposerFallback ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComposerFallback, { context: "thread" }) : void 0
394
+ }
395
+ );
396
+ }
397
+ //# sourceMappingURL=thread-view.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/canvas/thread-view.tsx"],
4
+ "sourcesContent": ["import { ReactNode, useCallback, useEffect, useRef, useState, type CSSProperties } from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n\tcreateComment,\n\tEditor,\n\tTLComment,\n\tTLCommentThread,\n\tTLRichText,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuGroup,\n\tTldrawUiDropdownMenuItem,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\tTldrawUiIcon,\n\tuseContainer,\n\tusePassThroughMouseOverEvents,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n} from 'tldraw'\nimport { CommentCard, CommentCardProps } from '../ui/comment-card'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentThread } from '../ui/comment-thread'\nimport { TooltipButton } from '../ui/tooltip-button'\nimport { CommentBody } from './comment-body'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\treplyDraftSlot,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport {\n\tcommitCommentMutation,\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\tputRecordsInCommit,\n\treopenThread,\n\tresolveThread,\n} from './comment-mutations'\nimport { CommentReactionPicker, CommentReactions } from './comment-reactions'\nimport { UNKNOWN_AUTHOR, UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { type CommentingComponents, useCanComment, useCommentingOptions } from './options'\nimport { openThreadId } from './state'\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/**\n * A name-only view of an author resolver, for the mention/rich-text paths. Stable identity, so\n * `CommentBody`'s memoized render doesn't recompute on every render of its host.\n */\nexport function useResolveName(resolveAuthor: CommentingContext['resolveAuthor']) {\n\treturn useCallback((id: string) => resolveAuthor(id)?.name, [resolveAuthor])\n}\n\nexport function toCardProps(\n\tcomment: TLComment,\n\tprops: Pick<CommentingContext, 'currentUserId' | 'resolveAuthor'>,\n\tcomponents: CommentingComponents,\n\tresolveName: (id: string) => string | undefined\n): CommentCardProps {\n\tconst Body = components.CommentBody\n\t// The `CommentBody` component slot overrides the built-in rich-text default (which resolves\n\t// mention ids to names).\n\tconst body = Body ? (\n\t\t<Body comment={comment} />\n\t) : (\n\t\t<CommentBody richText={comment.body} resolveName={resolveName} />\n\t)\n\treturn {\n\t\tauthor: props.resolveAuthor(comment.authorId) ?? UNKNOWN_COMMENT_AUTHOR,\n\t\tbody,\n\t\tdate: new Date(comment.createdAt).toISOString(),\n\t\tyou: comment.authorId === props.currentUserId,\n\t\tedited: comment.editedAt != null,\n\t}\n}\n\n/** A pin is this square (mirrors `--tlui-cmt-pin-size`). Needed because the two marker kinds are\n * different sizes *and* anchor at different points, and lining their previews up means\n * correcting for that. Keep in sync with the stylesheet. */\nconst PIN_SIZE = 28\n\n/** A cluster/stack badge is this square (`--tlui-cmt-marker-size`) \u2014 pin-sized, so the marker\n * kinds read as one family. Keep in sync with the stylesheet. */\nconst MARKER_SIZE = PIN_SIZE\n\n/** Where a popover's top card sits vertically, measured from the marker visual's middle. */\nconst CARD_TOP_Y = -28\n\n/** The horizontal gap between a marker visual's right edge and its popover/preview panel. The\n * preview's hover bridge spans exactly this (see `--tlui-cmt-preview-bridge`). */\nconst PREVIEW_GAP = 6\n\n/**\n * Where a marker's popover sits relative to the marker's anchor point.\n *\n * The hover preview places itself at these same origins, so the two views of a thread differ only\n * by the header the popover has \u2014 which its own stylesheet then compensates for. Moving a popover\n * here moves its preview with it.\n *\n * Both marker kinds hang off their point the same way (`translate(0, -100%)`): the anchor is the\n * visual's bottom-left corner. They differ only in size, so each offset clears its own width plus\n * the shared gap, and re-bases the shared card-top measure from its bottom anchor to its middle \u2014\n * keeping the two previews' top cards on the same line.\n */\nexport const POPOVER_OFFSET = {\n\tthread: { x: PIN_SIZE + PREVIEW_GAP, y: CARD_TOP_Y - PIN_SIZE / 2 },\n\tlist: { x: MARKER_SIZE + PREVIEW_GAP, y: CARD_TOP_Y - MARKER_SIZE / 2 },\n} as const\n\n/** The open thread's popover container, portaled above the UI panels. Over it, wheel and hover\n * events pass through to the canvas (unless it scrolls its own content), like tldraw's panels. */\nexport function ThreadPopover({\n\tcontainer,\n\tstyle,\n\tchildren,\n}: {\n\tcontainer: HTMLElement\n\tstyle: CSSProperties\n\tchildren: ReactNode\n}) {\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\tusePassThroughMouseOverEvents(ref)\n\treturn createPortal(\n\t\t// contextmenu also stops here: portals bubble React events to the canvas's context-menu\n\t\t// trigger (the layer mounts inside it), which would open the canvas menu over this panel.\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName=\"tlui-cmt-canvas-popover\"\n\t\t\tstyle={style}\n\t\t\tonPointerDown={stop}\n\t\t\tonContextMenu={stop}\n\t\t>\n\t\t\t{children}\n\t\t</div>,\n\t\tcontainer\n\t)\n}\n\n/**\n * One thread's interactive view: its comments, the reply composer, edit-in-place on your own\n * comments, and the resolve/delete actions. Reads and writes comment records via the editor's\n * store; read receipts are reported for every unread comment while mounted, so only mount it\n * where the thread is actually being shown.\n */\nexport function ThreadView({\n\teditor,\n\tthread,\n\t...props\n}: CommentingContext & { editor: Editor; thread: TLCommentThread }) {\n\tconst {\n\t\tcurrentUserId,\n\t\tresolveAuthor,\n\t\tonPostComment,\n\t\tisCommentUnread,\n\t\tonCommentRead,\n\t\tgetMentionSuggestions,\n\t\trenderMentionSuggestion,\n\t} = props\n\tconst options = useCommentingOptions()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst msg = useTranslation()\n\tconst resolveName = useResolveName(resolveAuthor)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// Composing, editing, deleting, and resolving are all commenting writes: gated on the viewer's\n\t// permission. Where it's withheld the composer gives way to the ComposerFallback slot (a\n\t// sign-in prompt, say) and the action affordances are hidden.\n\tconst canComment = useCanComment(currentUserId)\n\tconst ComposerFallback = options.components.ComposerFallback\n\t// An unsent reply survives closing the thread (saved on every change, keyed by thread id) \u2014\n\t// the flip side of dismissing without a discard warning.\n\tconst [reply, setReply] = useState<TLRichText>(\n\t\t() => getCommentDraft(replyDraftSlot(thread.id)) ?? EMPTY_COMMENT\n\t)\n\tconst [editingId, setEditingId] = useState<string | null>(null)\n\tconst [editText, setEditText] = useState<TLRichText>(EMPTY_COMMENT)\n\tconst canReply = canComment && !thread.resolved\n\tconst container = useContainer()\n\t// Where focus goes when the edit composer closes, resolved at that moment rather than held as an\n\t// element: the card (and its edit button with it) unmounts while the composer stands in its\n\t// place, so the node captured on the way in is gone by the way out.\n\tconst editReturnFocus = useRef<(() => HTMLElement | null) | null>(null)\n\n\t// Tab from the canvas drops the caret in the reply box. Clicking a pin opens the thread but\n\t// leaves focus on the editor container, so the first Tab would otherwise walk the app's own UI\n\t// instead of the panel the click just opened. Capture phase, ahead of the editor's own handling.\n\t// Fires once per open thread, so Tab inside the thread stays plain tab-through.\n\tconst [focusReply, setFocusReply] = useState(false)\n\tconst tabTaken = useRef(false)\n\tconst swallowTabUp = useRef(false)\n\tuseEffect(() => {\n\t\tif (!canReply) {\n\t\t\t// Resolving unmounts the reply box under an open thread. Reopening it should feel like a\n\t\t\t// freshly opened thread: no autoFocus left armed from the last Tab, and Tab available again.\n\t\t\tsetFocusReply(false)\n\t\t\ttabTaken.current = false\n\t\t\treturn\n\t\t}\n\t\tconst doc = container.ownerDocument\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Tab' || e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return\n\t\t\tif (e.defaultPrevented || tabTaken.current) return\n\t\t\t// Focus rests on the container (or nothing at all) after a pin click. Anywhere else means\n\t\t\t// it's already somewhere deliberate \u2014 the thread's own controls, the sidebar, a panel \u2014\n\t\t\t// and Tab belongs to whatever holds it.\n\t\t\tif (e.target !== container && e.target !== doc.body) return\n\t\t\ttabTaken.current = true\n\t\t\tswallowTabUp.current = true\n\t\t\tsetFocusReply(true)\n\t\t\te.preventDefault()\n\t\t\te.stopPropagation()\n\t\t}\n\t\t// The select tool navigates shapes on Tab's *keyup*, and the composer isn't focused until the\n\t\t// next frame \u2014 so a quick tap with shapes selected would both focus the reply and step the\n\t\t// selection. Swallow the release of the press we took, and only that one.\n\t\tconst onKeyUp = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Tab' || !swallowTabUp.current) return\n\t\t\tswallowTabUp.current = false\n\t\t\te.preventDefault()\n\t\t\te.stopPropagation()\n\t\t}\n\t\tdoc.addEventListener('keydown', onKeyDown, true)\n\t\tdoc.addEventListener('keyup', onKeyUp, true)\n\t\treturn () => {\n\t\t\tdoc.removeEventListener('keydown', onKeyDown, true)\n\t\t\tdoc.removeEventListener('keyup', onKeyUp, true)\n\t\t}\n\t}, [canReply, container])\n\n\t// Leaving the edit composer \u2014 Escape, or a save \u2014 unmounts it while it holds focus, and the\n\t// browser drops focus on the editor container. That's outside the thread, so a Tab from there\n\t// walks the app's UI instead of carrying on from where the edit left off (the thread's one\n\t// Tab-into-the-reply-box has usually been spent by then). Hand focus back to whatever opened the\n\t// composer instead: the card's edit button, or the reply box when the edit came from arrow-up.\n\t// The card's actions row reveals itself on `:focus-within`, so the button focus is visible.\n\tuseEffect(() => {\n\t\tif (editingId !== null) return\n\t\tconst resolve = editReturnFocus.current\n\t\teditReturnFocus.current = null\n\t\t// Runs after React has swapped the card back in, so the target is mounted again by now.\n\t\tresolve?.()?.focus()\n\t}, [editingId])\n\n\t// Every unread comment on display gets reported read \u2014 including replies that arrive while\n\t// the view stays mounted, since the effect re-runs as `comments` changes. The host's receipt\n\t// write flips isCommentUnread to false, so re-runs find nothing to report.\n\tuseEffect(() => {\n\t\tif (!isCommentUnread || !onCommentRead) return\n\t\tfor (const comment of comments) {\n\t\t\tif (isCommentUnread(comment.id)) {\n\t\t\t\tonCommentRead(comment.id)\n\t\t\t}\n\t\t}\n\t}, [comments, isCommentUnread, onCommentRead])\n\n\tconst postReply = () => {\n\t\tif (isCommentEmpty(reply) || !currentUserId) return\n\t\tcommitCommentMutation(editor, () => {\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId: thread.pageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: reply,\n\t\t\t})\n\t\t\tputRecordsInCommit(editor, [comment])\n\t\t\tif (onPostComment) onPostComment(comment)\n\t\t})\n\t\tsetReply(EMPTY_COMMENT)\n\t\tclearCommentDraft(replyDraftSlot(thread.id))\n\t}\n\n\tconst toggleResolve = () => {\n\t\tif (!currentUserId) return\n\t\tif (thread.resolved) reopenThread(editor, thread)\n\t\telse resolveThread(editor, thread, currentUserId)\n\t}\n\n\tconst removeThread = () => {\n\t\tif (!currentUserId) return\n\t\tdeleteThread(editor, thread)\n\t}\n\n\tconst startEdit = (comment: TLComment, { fromMoreMenu = false } = {}) => {\n\t\t// The \u22EF menu's button is the thing to come back to when Edit opened the composer \u2014 looked up\n\t\t// again on the way out, since the menu item that was clicked (and the card) unmount while the\n\t\t// composer stands in their place. Otherwise come back to whatever held focus: arrow-up-to-edit\n\t\t// comes straight from the reply box, which stays put. The document body and the editor\n\t\t// container are where focus falls when nothing holds it, so neither is somewhere to return to.\n\t\tconst active = container.ownerDocument.activeElement\n\t\teditReturnFocus.current = fromMoreMenu\n\t\t\t? () => container.querySelector<HTMLElement>(`[data-cmt-more-for=\"${comment.id}\"]`)\n\t\t\t: active instanceof HTMLElement && active !== container && active !== document.body\n\t\t\t\t? () => (active.isConnected ? active : null)\n\t\t\t\t: null\n\t\tsetEditingId(comment.id)\n\t\tsetEditText(comment.body)\n\t}\n\n\tconst saveEdit = () => {\n\t\tconst comment = comments.find((c) => c.id === editingId)\n\t\tif (!comment || isCommentEmpty(editText)) return\n\t\teditComment(editor, comment, editText)\n\t\tsetEditingId(null)\n\t}\n\n\t// Swap a comment for a pre-filled composer while it's being edited; otherwise show the card,\n\t// with an edit affordance on your own comments.\n\tconst renderComment = (card: CommentCardProps, index: number): ReactNode => {\n\t\tconst comment = comments[index]\n\t\tif (editingId === comment.id) {\n\t\t\treturn (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"tlui-cmt-editing\"\n\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\tif (e.key === 'Escape') {\n\t\t\t\t\t\t\tsetEditingId(null)\n\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={card.author}\n\t\t\t\t\t\tplaceholder={msg('comments.edit-placeholder')}\n\t\t\t\t\t\tvalue={editText}\n\t\t\t\t\t\tonChange={setEditText}\n\t\t\t\t\t\tonSubmit={saveEdit}\n\t\t\t\t\t\tsendLabel={msg('comments.save')}\n\t\t\t\t\t\tdisabled={isCommentEmpty(editText)}\n\t\t\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\t\t\tautoFocus\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)\n\t\t}\n\t\treturn (\n\t\t\t<CommentCard\n\t\t\t\t{...card}\n\t\t\t\tfooter={\n\t\t\t\t\t<CommentReactions\n\t\t\t\t\t\tcomment={comment}\n\t\t\t\t\t\t// Reacting is a commenting write: without `canComment` the tally renders\n\t\t\t\t\t\t// read-only (no identity \u2192 pills aren't clickable).\n\t\t\t\t\t\tcurrentUserId={canComment ? currentUserId : null}\n\t\t\t\t\t\tresolveName={resolveName}\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\tactions={\n\t\t\t\t\tcanComment && (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<CommentReactionPicker comment={comment} currentUserId={currentUserId} />\n\t\t\t\t\t\t\t{comment.authorId === currentUserId && (\n\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuRoot id={`comment-actions-${comment.id}`}>\n\t\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t\t\t\t\t\t<TooltipButton\n\t\t\t\t\t\t\t\t\t\t\ttooltip={msg('comments.more-options')}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\t\t\t\t\t\t\tdata-cmt-more-for={comment.id}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<TldrawUiIcon\n\t\t\t\t\t\t\t\t\t\t\t\ticon=\"dots-vertical\"\n\t\t\t\t\t\t\t\t\t\t\t\tlabel={msg('comments.more-options')}\n\t\t\t\t\t\t\t\t\t\t\t\tsmall\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</TooltipButton>\n\t\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuContent\n\t\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu\"\n\t\t\t\t\t\t\t\t\t\tside=\"bottom\"\n\t\t\t\t\t\t\t\t\t\talign=\"start\"\n\t\t\t\t\t\t\t\t\t\talignOffset={0}\n\t\t\t\t\t\t\t\t\t\tsideOffset={4}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuGroup>\n\t\t\t\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu-item\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => startEdit(comment, { fromMoreMenu: true })}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span>{msg('comments.edit')}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu-item tlui-cmt-menu-item--danger\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => deleteComment(editor, comment)}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span>{msg('action.delete')}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuGroup>\n\t\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuContent>\n\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuRoot>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</>\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t/>\n\t\t)\n\t}\n\n\t// Resolve and delete are commenting writes: behind `canComment`, plus the `currentUserId` a\n\t// resolve stamps into `resolved.by`.\n\tconst headerActions = (\n\t\t<>\n\t\t\t{/* Deleting a thread is creator-only (server-enforced), and it's the menu's only item. */}\n\t\t\t{canComment && currentUserId && currentUserId === thread.createdBy && (\n\t\t\t\t<TldrawUiDropdownMenuRoot id={`comment-thread-actions-${thread.id}`}>\n\t\t\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t\t<TooltipButton\n\t\t\t\t\t\t\ttooltip={msg('comments.more-options')}\n\t\t\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<TldrawUiIcon icon=\"dots-vertical\" label={msg('comments.more-options')} small />\n\t\t\t\t\t\t</TooltipButton>\n\t\t\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t<TldrawUiDropdownMenuContent\n\t\t\t\t\t\tclassName=\"tlui-cmt-menu\"\n\t\t\t\t\t\tside=\"bottom\"\n\t\t\t\t\t\talign=\"start\"\n\t\t\t\t\t\talignOffset={0}\n\t\t\t\t\t\tsideOffset={4}\n\t\t\t\t\t>\n\t\t\t\t\t\t<TldrawUiDropdownMenuGroup>\n\t\t\t\t\t\t\t<TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu-item tlui-cmt-menu-item--danger\"\n\t\t\t\t\t\t\t\t\tonClick={removeThread}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span>{msg('comments.delete')}</span>\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t</TldrawUiDropdownMenuGroup>\n\t\t\t\t\t</TldrawUiDropdownMenuContent>\n\t\t\t\t</TldrawUiDropdownMenuRoot>\n\t\t\t)}\n\t\t\t{canComment && currentUserId && (\n\t\t\t\t<TooltipButton\n\t\t\t\t\ttooltip={msg(thread.resolved ? 'comments.reopen' : 'comments.resolve')}\n\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\tonClick={toggleResolve}\n\t\t\t\t>\n\t\t\t\t\t<TldrawUiIcon\n\t\t\t\t\t\ticon=\"check\"\n\t\t\t\t\t\tlabel={msg(thread.resolved ? 'comments.reopen' : 'comments.resolve')}\n\t\t\t\t\t\tsmall\n\t\t\t\t\t/>\n\t\t\t\t</TooltipButton>\n\t\t\t)}\n\t\t\t<TooltipButton\n\t\t\t\ttooltip={msg('comments.dismiss')}\n\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\tonClick={() => openThreadId.set(editor, null)}\n\t\t\t>\n\t\t\t\t<TldrawUiIcon icon=\"cross-2\" label={msg('comments.dismiss')} small />\n\t\t\t</TooltipButton>\n\t\t</>\n\t)\n\n\treturn (\n\t\t<CommentThread\n\t\t\theader={msg('comments.thread-title')}\n\t\t\theaderActions={headerActions}\n\t\t\trenderComment={renderComment}\n\t\t\tcomments={comments.map((c) => toCardProps(c, props, options.components, resolveName))}\n\t\t\tresolvedBanner={\n\t\t\t\tthread.resolved\n\t\t\t\t\t? msg('comments.resolved-by').replace(\n\t\t\t\t\t\t\t'{name}',\n\t\t\t\t\t\t\tresolveAuthor(thread.resolved.by)?.name ?? UNKNOWN_AUTHOR\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined\n\t\t\t}\n\t\t\tcomposer={\n\t\t\t\tcanReply\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tauthor: me ?? UNKNOWN_COMMENT_AUTHOR,\n\t\t\t\t\t\t\tplaceholder: msg('comments.reply-placeholder'),\n\t\t\t\t\t\t\tsendLabel: msg('comments.send'),\n\t\t\t\t\t\t\tvalue: reply,\n\t\t\t\t\t\t\tonChange: (value: TLRichText) => {\n\t\t\t\t\t\t\t\tsetReply(value)\n\t\t\t\t\t\t\t\tsaveCommentDraft(replyDraftSlot(thread.id), value)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tonSubmit: postReply,\n\t\t\t\t\t\t\t// Up in the empty reply box edits the comment directly above it, chat-style \u2014\n\t\t\t\t\t\t\t// only when that comment is yours (the same gate as the Edit link).\n\t\t\t\t\t\t\tonArrowUpWhenEmpty: () => {\n\t\t\t\t\t\t\t\tconst last = comments[comments.length - 1]\n\t\t\t\t\t\t\t\tif (last && last.authorId === currentUserId) startEdit(last)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\t\tdisabled: isCommentEmpty(reply) || !currentUserId,\n\t\t\t\t\t\t\tgetMentionSuggestions,\n\t\t\t\t\t\t\trenderMentionSuggestion,\n\t\t\t\t\t\t\t// Reuses the composer's own focus path, so the caret lands at the end of a\n\t\t\t\t\t\t\t// restored draft rather than in front of it.\n\t\t\t\t\t\t\tautoFocus: focusReply,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined\n\t\t\t}\n\t\t\tfooter={\n\t\t\t\t!canComment && !thread.resolved && ComposerFallback ? (\n\t\t\t\t\t<ComposerFallback context=\"thread\" />\n\t\t\t\t) : undefined\n\t\t\t}\n\t\t/>\n\t)\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -43,10 +43,10 @@ function computeClusterTable(leaves, options) {
43
43
  return { events, leaves: leafNodes };
44
44
  }
45
45
  function resolveOptions(options) {
46
- const Tc = options.Tc ?? 40;
47
- const Tu = options.Tu ?? 1.5 * Tc;
48
- const eps = options.eps ?? 0.12;
49
- const Dmax = options.Dmax ?? 3 * Tc;
46
+ const Tc = options.Tc ?? 22;
47
+ const Tu = options.Tu ?? 1.2 * Tc;
48
+ const eps = options.eps ?? 0.7;
49
+ const Dmax = options.Dmax ?? 3.75 * Tc;
50
50
  const maxSplitZoom = options.maxSplitZoom ?? 6;
51
51
  const { minZoom, maxZoom } = options;
52
52
  if (!Number.isFinite(Tc) || Tc <= 0) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/clustering/computeClusterTable.ts"],
4
- "sourcesContent": ["import { mstEdges } from './mst'\nimport { cappedReplay } from './replay'\nimport { contract, finalize } from './schedule'\nimport type { ClusterNode, ClusterOptions, ClusterTable, LeafInput } from './types'\n\ninterface ResolvedClusterOptions {\n\tTc: number\n\tTu: number\n\teps: number\n\tDmax: number\n\tminZoom: number\n\tmaxZoom: number\n\tmaxSplitZoom: number\n}\n\n/** @public */\nexport function computeClusterTable(\n\tleaves: readonly LeafInput[],\n\toptions: ClusterOptions\n): ClusterTable {\n\tconst opts = resolveOptions(options)\n\tconst leafNodes = leaves.map(leafToNode)\n\tconst edges = mstEdges(leaves)\n\n\tif (leaves.length < 2) {\n\t\treturn { events: [], leaves: leafNodes }\n\t}\n\n\tconst raw = cappedReplay(leaves, edges, { Tc: opts.Tc, Dmax: opts.Dmax })\n\tconst contracted = contract(raw, opts.eps)\n\tconst events = finalize(contracted, {\n\t\tTc: opts.Tc,\n\t\tTu: opts.Tu,\n\t\tminZoom: opts.minZoom,\n\t\tmaxZoom: opts.maxZoom,\n\t\tmaxSplitZoom: opts.maxSplitZoom,\n\t})\n\n\treturn { events, leaves: leafNodes }\n}\n\nfunction resolveOptions(options: ClusterOptions): ResolvedClusterOptions {\n\tconst Tc = options.Tc ?? 40\n\tconst Tu = options.Tu ?? 1.5 * Tc\n\tconst eps = options.eps ?? 0.12\n\tconst Dmax = options.Dmax ?? 3 * Tc\n\tconst maxSplitZoom = options.maxSplitZoom ?? 6\n\tconst { minZoom, maxZoom } = options\n\n\tif (!Number.isFinite(Tc) || Tc <= 0) {\n\t\tthrow new Error('Tc must be finite and greater than 0')\n\t}\n\tif (!Number.isFinite(Tu) || Tu <= Tc) {\n\t\tthrow new Error('Tu must be finite and greater than Tc')\n\t}\n\tif (!Number.isFinite(eps) || eps < 0) {\n\t\tthrow new Error('eps must be finite and greater than or equal to 0')\n\t}\n\tif (!Number.isFinite(Dmax) || Dmax < Tc) {\n\t\tthrow new Error('Dmax must be finite and greater than or equal to Tc')\n\t}\n\tif (!Number.isFinite(minZoom) || minZoom <= 0) {\n\t\tthrow new Error('minZoom must be finite and greater than 0')\n\t}\n\tif (!Number.isFinite(maxZoom) || maxZoom <= minZoom) {\n\t\tthrow new Error('maxZoom must be finite and greater than minZoom')\n\t}\n\tif (!Number.isFinite(maxSplitZoom) || maxSplitZoom <= 0) {\n\t\tthrow new Error('maxSplitZoom must be finite and greater than 0')\n\t}\n\n\treturn { Tc, Tu, eps, Dmax, minZoom, maxZoom, maxSplitZoom }\n}\n\nfunction leafToNode(leaf: LeafInput): ClusterNode {\n\treturn {\n\t\tid: leaf.id,\n\t\tcentroid: { x: leaf.point.x, y: leaf.point.y },\n\t\tcount: 1,\n\t\tmembers: [leaf.id],\n\t}\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAyB;AACzB,oBAA6B;AAC7B,sBAAmC;AAc5B,SAAS,oBACf,QACA,SACe;AACf,QAAM,OAAO,eAAe,OAAO;AACnC,QAAM,YAAY,OAAO,IAAI,UAAU;AACvC,QAAM,YAAQ,qBAAS,MAAM;AAE7B,MAAI,OAAO,SAAS,GAAG;AACtB,WAAO,EAAE,QAAQ,CAAC,GAAG,QAAQ,UAAU;AAAA,EACxC;AAEA,QAAM,UAAM,4BAAa,QAAQ,OAAO,EAAE,IAAI,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC;AACxE,QAAM,iBAAa,0BAAS,KAAK,KAAK,GAAG;AACzC,QAAM,aAAS,0BAAS,YAAY;AAAA,IACnC,IAAI,KAAK;AAAA,IACT,IAAI,KAAK;AAAA,IACT,SAAS,KAAK;AAAA,IACd,SAAS,KAAK;AAAA,IACd,cAAc,KAAK;AAAA,EACpB,CAAC;AAED,SAAO,EAAE,QAAQ,QAAQ,UAAU;AACpC;AAEA,SAAS,eAAe,SAAiD;AACxE,QAAM,KAAK,QAAQ,MAAM;AACzB,QAAM,KAAK,QAAQ,MAAM,MAAM;AAC/B,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,OAAO,QAAQ,QAAQ,IAAI;AACjC,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,EAAE,SAAS,QAAQ,IAAI;AAE7B,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,MAAM,GAAG;AACpC,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACvD;AACA,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,MAAM,IAAI;AACrC,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACxD;AACA,MAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,GAAG;AACrC,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACpE;AACA,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI;AACxC,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACtE;AACA,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,GAAG;AAC9C,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC5D;AACA,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,SAAS;AACpD,UAAM,IAAI,MAAM,iDAAiD;AAAA,EAClE;AACA,MAAI,CAAC,OAAO,SAAS,YAAY,KAAK,gBAAgB,GAAG;AACxD,UAAM,IAAI,MAAM,gDAAgD;AAAA,EACjE;AAEA,SAAO,EAAE,IAAI,IAAI,KAAK,MAAM,SAAS,SAAS,aAAa;AAC5D;AAEA,SAAS,WAAW,MAA8B;AACjD,SAAO;AAAA,IACN,IAAI,KAAK;AAAA,IACT,UAAU,EAAE,GAAG,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,EAAE;AAAA,IAC7C,OAAO;AAAA,IACP,SAAS,CAAC,KAAK,EAAE;AAAA,EAClB;AACD;",
4
+ "sourcesContent": ["import { mstEdges } from './mst'\nimport { cappedReplay } from './replay'\nimport { contract, finalize } from './schedule'\nimport type { ClusterNode, ClusterOptions, ClusterTable, LeafInput } from './types'\n\ninterface ResolvedClusterOptions {\n\tTc: number\n\tTu: number\n\teps: number\n\tDmax: number\n\tminZoom: number\n\tmaxZoom: number\n\tmaxSplitZoom: number\n}\n\n/** @internal */\nexport function computeClusterTable(\n\tleaves: readonly LeafInput[],\n\toptions: ClusterOptions\n): ClusterTable {\n\tconst opts = resolveOptions(options)\n\tconst leafNodes = leaves.map(leafToNode)\n\tconst edges = mstEdges(leaves)\n\n\tif (leaves.length < 2) {\n\t\treturn { events: [], leaves: leafNodes }\n\t}\n\n\tconst raw = cappedReplay(leaves, edges, { Tc: opts.Tc, Dmax: opts.Dmax })\n\tconst contracted = contract(raw, opts.eps)\n\tconst events = finalize(contracted, {\n\t\tTc: opts.Tc,\n\t\tTu: opts.Tu,\n\t\tminZoom: opts.minZoom,\n\t\tmaxZoom: opts.maxZoom,\n\t\tmaxSplitZoom: opts.maxSplitZoom,\n\t})\n\n\treturn { events, leaves: leafNodes }\n}\n\nfunction resolveOptions(options: ClusterOptions): ResolvedClusterOptions {\n\tconst Tc = options.Tc ?? 22\n\tconst Tu = options.Tu ?? 1.2 * Tc\n\tconst eps = options.eps ?? 0.7\n\tconst Dmax = options.Dmax ?? 3.75 * Tc\n\tconst maxSplitZoom = options.maxSplitZoom ?? 6\n\tconst { minZoom, maxZoom } = options\n\n\tif (!Number.isFinite(Tc) || Tc <= 0) {\n\t\tthrow new Error('Tc must be finite and greater than 0')\n\t}\n\tif (!Number.isFinite(Tu) || Tu <= Tc) {\n\t\tthrow new Error('Tu must be finite and greater than Tc')\n\t}\n\tif (!Number.isFinite(eps) || eps < 0) {\n\t\tthrow new Error('eps must be finite and greater than or equal to 0')\n\t}\n\tif (!Number.isFinite(Dmax) || Dmax < Tc) {\n\t\tthrow new Error('Dmax must be finite and greater than or equal to Tc')\n\t}\n\tif (!Number.isFinite(minZoom) || minZoom <= 0) {\n\t\tthrow new Error('minZoom must be finite and greater than 0')\n\t}\n\tif (!Number.isFinite(maxZoom) || maxZoom <= minZoom) {\n\t\tthrow new Error('maxZoom must be finite and greater than minZoom')\n\t}\n\tif (!Number.isFinite(maxSplitZoom) || maxSplitZoom <= 0) {\n\t\tthrow new Error('maxSplitZoom must be finite and greater than 0')\n\t}\n\n\treturn { Tc, Tu, eps, Dmax, minZoom, maxZoom, maxSplitZoom }\n}\n\nfunction leafToNode(leaf: LeafInput): ClusterNode {\n\treturn {\n\t\tid: leaf.id,\n\t\tcentroid: { x: leaf.point.x, y: leaf.point.y },\n\t\tcount: 1,\n\t\tmembers: [leaf.id],\n\t}\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAyB;AACzB,oBAA6B;AAC7B,sBAAmC;AAc5B,SAAS,oBACf,QACA,SACe;AACf,QAAM,OAAO,eAAe,OAAO;AACnC,QAAM,YAAY,OAAO,IAAI,UAAU;AACvC,QAAM,YAAQ,qBAAS,MAAM;AAE7B,MAAI,OAAO,SAAS,GAAG;AACtB,WAAO,EAAE,QAAQ,CAAC,GAAG,QAAQ,UAAU;AAAA,EACxC;AAEA,QAAM,UAAM,4BAAa,QAAQ,OAAO,EAAE,IAAI,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC;AACxE,QAAM,iBAAa,0BAAS,KAAK,KAAK,GAAG;AACzC,QAAM,aAAS,0BAAS,YAAY;AAAA,IACnC,IAAI,KAAK;AAAA,IACT,IAAI,KAAK;AAAA,IACT,SAAS,KAAK;AAAA,IACd,SAAS,KAAK;AAAA,IACd,cAAc,KAAK;AAAA,EACpB,CAAC;AAED,SAAO,EAAE,QAAQ,QAAQ,UAAU;AACpC;AAEA,SAAS,eAAe,SAAiD;AACxE,QAAM,KAAK,QAAQ,MAAM;AACzB,QAAM,KAAK,QAAQ,MAAM,MAAM;AAC/B,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,OAAO,QAAQ,QAAQ,OAAO;AACpC,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,EAAE,SAAS,QAAQ,IAAI;AAE7B,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,MAAM,GAAG;AACpC,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACvD;AACA,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,MAAM,IAAI;AACrC,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACxD;AACA,MAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,GAAG;AACrC,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACpE;AACA,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI;AACxC,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACtE;AACA,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,GAAG;AAC9C,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC5D;AACA,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,SAAS;AACpD,UAAM,IAAI,MAAM,iDAAiD;AAAA,EAClE;AACA,MAAI,CAAC,OAAO,SAAS,YAAY,KAAK,gBAAgB,GAAG;AACxD,UAAM,IAAI,MAAM,gDAAgD;AAAA,EACjE;AAEA,SAAO,EAAE,IAAI,IAAI,KAAK,MAAM,SAAS,SAAS,aAAa;AAC5D;AAEA,SAAS,WAAW,MAA8B;AACjD,SAAO;AAAA,IACN,IAAI,KAAK;AAAA,IACT,UAAU,EAAE,GAAG,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,EAAE;AAAA,IAC7C,OAAO;AAAA,IACP,SAAS,CAAC,KAAK,EAAE;AAAA,EAClB;AACD;",
6
6
  "names": []
7
7
  }