@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.
- package/README.md +0 -2
- package/commenting.css +1152 -350
- package/dist-cjs/canvas/anchor-lifecycle.js +148 -0
- package/dist-cjs/canvas/anchor-lifecycle.js.map +7 -0
- package/dist-cjs/{ui/avatar.js → canvas/canvas-events.js} +14 -22
- package/dist-cjs/canvas/canvas-events.js.map +7 -0
- package/dist-cjs/canvas/cluster-badge.js +121 -0
- package/dist-cjs/canvas/cluster-badge.js.map +7 -0
- package/dist-cjs/canvas/cluster-fade.js +85 -0
- package/dist-cjs/canvas/cluster-fade.js.map +7 -0
- package/dist-cjs/canvas/cluster-input.js +53 -3
- package/dist-cjs/canvas/cluster-input.js.map +2 -2
- package/dist-cjs/canvas/cluster-model.js +252 -0
- package/dist-cjs/canvas/cluster-model.js.map +7 -0
- package/dist-cjs/canvas/comment-body.js +1 -1
- package/dist-cjs/canvas/comment-body.js.map +2 -2
- package/dist-cjs/canvas/comment-drafts.js +54 -0
- package/dist-cjs/canvas/comment-drafts.js.map +7 -0
- package/dist-cjs/canvas/comment-mutations.js +152 -0
- package/dist-cjs/canvas/comment-mutations.js.map +7 -0
- package/dist-cjs/canvas/comment-reactions.js +160 -0
- package/dist-cjs/canvas/comment-reactions.js.map +7 -0
- package/dist-cjs/canvas/comment-render.js +9 -6
- package/dist-cjs/canvas/comment-render.js.map +2 -2
- package/dist-cjs/canvas/comment-store.js +19 -9
- package/dist-cjs/canvas/comment-store.js.map +2 -2
- package/dist-cjs/canvas/comment-tool.js +63 -14
- package/dist-cjs/canvas/comment-tool.js.map +2 -2
- package/dist-cjs/canvas/comments-filter-menu.js +17 -37
- package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overflow-menu.js +25 -22
- package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overlay.js +151 -989
- package/dist-cjs/canvas/comments-overlay.js.map +3 -3
- package/dist-cjs/canvas/comments-sidebar.js +49 -41
- package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
- package/dist-cjs/canvas/comments-visibility-toggle.js +45 -0
- package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
- package/dist-cjs/canvas/context.js +17 -0
- package/dist-cjs/canvas/context.js.map +7 -0
- package/dist-cjs/canvas/hooks.js +28 -3
- package/dist-cjs/canvas/hooks.js.map +2 -2
- package/dist-cjs/canvas/license.js.map +1 -1
- package/dist-cjs/canvas/mobile-placement.js +91 -0
- package/dist-cjs/canvas/mobile-placement.js.map +7 -0
- package/dist-cjs/canvas/options.js +57 -2
- package/dist-cjs/canvas/options.js.map +2 -2
- package/dist-cjs/canvas/pending-composer.js +134 -0
- package/dist-cjs/canvas/pending-composer.js.map +7 -0
- package/dist-cjs/canvas/pin-stacking.js +81 -0
- package/dist-cjs/canvas/pin-stacking.js.map +7 -0
- package/dist-cjs/canvas/region-box.js +113 -0
- package/dist-cjs/canvas/region-box.js.map +7 -0
- package/dist-cjs/canvas/sidebar-filters.js +2 -2
- package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
- package/dist-cjs/canvas/state.js +28 -14
- package/dist-cjs/canvas/state.js.map +2 -2
- package/dist-cjs/canvas/thread-pin.js +304 -0
- package/dist-cjs/canvas/thread-pin.js.map +7 -0
- package/dist-cjs/canvas/thread-preview.js +177 -0
- package/dist-cjs/canvas/thread-preview.js.map +7 -0
- package/dist-cjs/canvas/thread-stack.js +199 -0
- package/dist-cjs/canvas/thread-stack.js.map +7 -0
- package/dist-cjs/canvas/thread-state.js +82 -21
- package/dist-cjs/canvas/thread-state.js.map +2 -2
- package/dist-cjs/canvas/thread-view.js +450 -0
- package/dist-cjs/canvas/thread-view.js.map +7 -0
- package/dist-cjs/clustering/computeClusterTable.js +6 -6
- package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
- package/dist-cjs/clustering/replay.js +89 -22
- package/dist-cjs/clustering/replay.js.map +3 -3
- package/dist-cjs/clustering/runtime.js +126 -7
- package/dist-cjs/clustering/runtime.js.map +2 -2
- package/dist-cjs/clustering/types.js.map +1 -1
- package/dist-cjs/index.d.ts +904 -353
- package/dist-cjs/index.js +52 -28
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/ui/byline.js +10 -6
- package/dist-cjs/ui/byline.js.map +2 -2
- package/dist-cjs/ui/comment-card.js +15 -8
- package/dist-cjs/ui/comment-card.js.map +2 -2
- package/dist-cjs/ui/comment-composer.js +124 -39
- package/dist-cjs/ui/comment-composer.js.map +2 -2
- package/dist-cjs/ui/comment-pin.js +11 -2
- package/dist-cjs/ui/comment-pin.js.map +2 -2
- package/dist-cjs/ui/comment-thread.js +9 -7
- package/dist-cjs/ui/comment-thread.js.map +2 -2
- package/dist-cjs/ui/comments-list.js +39 -40
- package/dist-cjs/ui/comments-list.js.map +2 -2
- package/dist-cjs/ui/count-badge.js +3 -2
- package/dist-cjs/ui/count-badge.js.map +2 -2
- package/dist-cjs/ui/emoji-picker.js +54 -0
- package/dist-cjs/ui/emoji-picker.js.map +7 -0
- package/dist-cjs/ui/empty-state.js +3 -3
- package/dist-cjs/ui/empty-state.js.map +2 -2
- package/dist-cjs/ui/format-time.js +16 -1
- package/dist-cjs/ui/format-time.js.map +2 -2
- package/dist-cjs/ui/icons.js +115 -0
- package/dist-cjs/ui/icons.js.map +7 -0
- package/dist-cjs/ui/reaction-picker.js +71 -0
- package/dist-cjs/ui/reaction-picker.js.map +7 -0
- package/dist-cjs/ui/reaction.js +58 -6
- package/dist-cjs/ui/reaction.js.map +2 -2
- package/dist-cjs/ui/reactions.js +20 -7
- package/dist-cjs/ui/reactions.js.map +2 -2
- package/dist-cjs/ui/{mention.js → reply-count.js} +9 -11
- package/dist-cjs/ui/reply-count.js.map +7 -0
- package/dist-cjs/ui/send-button.js +13 -1
- package/dist-cjs/ui/send-button.js.map +2 -2
- package/dist-cjs/ui/{comment-text.js → visual-viewport.js} +12 -9
- package/dist-cjs/ui/visual-viewport.js.map +7 -0
- package/dist-esm/canvas/anchor-lifecycle.mjs +128 -0
- package/dist-esm/canvas/anchor-lifecycle.mjs.map +7 -0
- package/dist-esm/canvas/canvas-events.mjs +15 -0
- package/dist-esm/canvas/canvas-events.mjs.map +7 -0
- package/dist-esm/canvas/cluster-badge.mjs +106 -0
- package/dist-esm/canvas/cluster-badge.mjs.map +7 -0
- package/dist-esm/canvas/cluster-fade.mjs +65 -0
- package/dist-esm/canvas/cluster-fade.mjs.map +7 -0
- package/dist-esm/canvas/cluster-input.mjs +54 -4
- package/dist-esm/canvas/cluster-input.mjs.map +2 -2
- package/dist-esm/canvas/cluster-model.mjs +236 -0
- package/dist-esm/canvas/cluster-model.mjs.map +7 -0
- package/dist-esm/canvas/comment-body.mjs +1 -1
- package/dist-esm/canvas/comment-body.mjs.map +2 -2
- package/dist-esm/canvas/comment-drafts.mjs +34 -0
- package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
- package/dist-esm/canvas/comment-mutations.mjs +132 -0
- package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
- package/dist-esm/canvas/comment-reactions.mjs +145 -0
- package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
- package/dist-esm/canvas/comment-render.mjs +10 -7
- package/dist-esm/canvas/comment-render.mjs.map +2 -2
- package/dist-esm/canvas/comment-store.mjs +19 -9
- package/dist-esm/canvas/comment-store.mjs.map +2 -2
- package/dist-esm/canvas/comment-tool.mjs +66 -17
- package/dist-esm/canvas/comment-tool.mjs.map +2 -2
- package/dist-esm/canvas/comments-filter-menu.mjs +17 -36
- package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overflow-menu.mjs +27 -22
- package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overlay.mjs +157 -1010
- package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
- package/dist-esm/canvas/comments-sidebar.mjs +53 -46
- package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
- package/dist-esm/canvas/comments-visibility-toggle.mjs +25 -0
- package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
- package/dist-esm/canvas/context.mjs +1 -0
- package/dist-esm/canvas/context.mjs.map +7 -0
- package/dist-esm/canvas/hooks.mjs +35 -5
- package/dist-esm/canvas/hooks.mjs.map +2 -2
- package/dist-esm/canvas/license.mjs.map +1 -1
- package/dist-esm/canvas/mobile-placement.mjs +71 -0
- package/dist-esm/canvas/mobile-placement.mjs.map +7 -0
- package/dist-esm/canvas/options.mjs +59 -3
- package/dist-esm/canvas/options.mjs.map +2 -2
- package/dist-esm/canvas/pending-composer.mjs +126 -0
- package/dist-esm/canvas/pending-composer.mjs.map +7 -0
- package/dist-esm/canvas/pin-stacking.mjs +61 -0
- package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
- package/dist-esm/canvas/region-box.mjs +93 -0
- package/dist-esm/canvas/region-box.mjs.map +7 -0
- package/dist-esm/canvas/sidebar-filters.mjs +2 -2
- package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
- package/dist-esm/canvas/state.mjs +29 -19
- package/dist-esm/canvas/state.mjs.map +2 -2
- package/dist-esm/canvas/thread-pin.mjs +310 -0
- package/dist-esm/canvas/thread-pin.mjs.map +7 -0
- package/dist-esm/canvas/thread-preview.mjs +163 -0
- package/dist-esm/canvas/thread-preview.mjs.map +7 -0
- package/dist-esm/canvas/thread-stack.mjs +189 -0
- package/dist-esm/canvas/thread-stack.mjs.map +7 -0
- package/dist-esm/canvas/thread-state.mjs +86 -22
- package/dist-esm/canvas/thread-state.mjs.map +2 -2
- package/dist-esm/canvas/thread-view.mjs +461 -0
- package/dist-esm/canvas/thread-view.mjs.map +7 -0
- package/dist-esm/clustering/computeClusterTable.mjs +6 -6
- package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
- package/dist-esm/clustering/replay.mjs +89 -22
- package/dist-esm/clustering/replay.mjs.map +3 -3
- package/dist-esm/clustering/runtime.mjs +126 -7
- package/dist-esm/clustering/runtime.mjs.map +2 -2
- package/dist-esm/index.d.mts +904 -353
- package/dist-esm/index.mjs +102 -43
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/ui/byline.mjs +11 -7
- package/dist-esm/ui/byline.mjs.map +2 -2
- package/dist-esm/ui/comment-card.mjs +15 -8
- package/dist-esm/ui/comment-card.mjs.map +2 -2
- package/dist-esm/ui/comment-composer.mjs +135 -39
- package/dist-esm/ui/comment-composer.mjs.map +2 -2
- package/dist-esm/ui/comment-pin.mjs +11 -2
- package/dist-esm/ui/comment-pin.mjs.map +2 -2
- package/dist-esm/ui/comment-thread.mjs +9 -7
- package/dist-esm/ui/comment-thread.mjs.map +2 -2
- package/dist-esm/ui/comments-list.mjs +51 -50
- package/dist-esm/ui/comments-list.mjs.map +2 -2
- package/dist-esm/ui/count-badge.mjs +3 -2
- package/dist-esm/ui/count-badge.mjs.map +2 -2
- package/dist-esm/ui/emoji-picker.mjs +34 -0
- package/dist-esm/ui/emoji-picker.mjs.map +7 -0
- package/dist-esm/ui/empty-state.mjs +3 -3
- package/dist-esm/ui/empty-state.mjs.map +2 -2
- package/dist-esm/ui/format-time.mjs +16 -1
- package/dist-esm/ui/format-time.mjs.map +2 -2
- package/dist-esm/ui/icons.mjs +95 -0
- package/dist-esm/ui/icons.mjs.map +7 -0
- package/dist-esm/ui/reaction-picker.mjs +59 -0
- package/dist-esm/ui/reaction-picker.mjs.map +7 -0
- package/dist-esm/ui/reaction.mjs +59 -7
- package/dist-esm/ui/reaction.mjs.map +2 -2
- package/dist-esm/ui/reactions.mjs +21 -8
- package/dist-esm/ui/reactions.mjs.map +2 -2
- package/dist-esm/ui/reply-count.mjs +9 -0
- package/dist-esm/ui/reply-count.mjs.map +7 -0
- package/dist-esm/ui/send-button.mjs +13 -1
- package/dist-esm/ui/send-button.mjs.map +2 -2
- package/dist-esm/ui/visual-viewport.mjs +12 -0
- package/dist-esm/ui/visual-viewport.mjs.map +7 -0
- package/package.json +6 -6
- package/src/canvas/anchor-lifecycle.test.ts +290 -0
- package/src/canvas/anchor-lifecycle.ts +191 -0
- package/src/canvas/canvas-events.ts +18 -0
- package/src/canvas/canvas.css +464 -55
- package/src/canvas/cluster-badge.tsx +133 -0
- package/src/canvas/cluster-fade.ts +102 -0
- package/src/canvas/cluster-input.test.ts +178 -36
- package/src/canvas/cluster-input.ts +95 -9
- package/src/canvas/cluster-model.ts +378 -0
- package/src/canvas/comment-body.tsx +1 -1
- package/src/canvas/comment-drafts.test.ts +55 -0
- package/src/canvas/comment-drafts.ts +46 -0
- package/src/canvas/comment-mutations.test.ts +452 -0
- package/src/canvas/comment-mutations.ts +299 -0
- package/src/canvas/comment-reactions.test.ts +154 -0
- package/src/canvas/comment-reactions.tsx +246 -0
- package/src/canvas/comment-render.test.ts +1 -1
- package/src/canvas/comment-render.ts +17 -13
- package/src/canvas/comment-store.ts +68 -33
- package/src/canvas/comment-tool.test.ts +162 -0
- package/src/canvas/comment-tool.tsx +103 -25
- package/src/canvas/comments-filter-menu.tsx +20 -36
- package/src/canvas/comments-overflow-menu.tsx +17 -19
- package/src/canvas/comments-overlay.tsx +252 -1369
- package/src/canvas/comments-sidebar.test.ts +45 -0
- package/src/canvas/comments-sidebar.tsx +112 -73
- package/src/canvas/comments-visibility-toggle.tsx +25 -0
- package/src/canvas/context.ts +48 -0
- package/src/canvas/hooks.test.ts +94 -0
- package/src/canvas/hooks.ts +66 -11
- package/src/canvas/license.ts +1 -1
- package/src/canvas/mobile-placement.ts +115 -0
- package/src/canvas/options.test.ts +176 -4
- package/src/canvas/options.ts +276 -18
- package/src/canvas/pending-composer.tsx +157 -0
- package/src/canvas/pin-stacking.test.ts +224 -0
- package/src/canvas/pin-stacking.ts +103 -0
- package/src/canvas/region-box.tsx +124 -0
- package/src/canvas/sidebar-filters.ts +4 -3
- package/src/canvas/state.ts +88 -39
- package/src/canvas/thread-pin.tsx +418 -0
- package/src/canvas/thread-preview.test.ts +91 -0
- package/src/canvas/thread-preview.tsx +294 -0
- package/src/canvas/thread-stack.tsx +242 -0
- package/src/canvas/thread-state.test.ts +260 -0
- package/src/canvas/thread-state.ts +171 -37
- package/src/canvas/thread-view.test.ts +72 -0
- package/src/canvas/thread-view.tsx +614 -0
- package/src/clustering/computeClusterTable.test.ts +24 -23
- package/src/clustering/computeClusterTable.ts +17 -8
- package/src/clustering/replay.test.ts +0 -7
- package/src/clustering/replay.ts +131 -32
- package/src/clustering/runtime.test.ts +267 -13
- package/src/clustering/runtime.ts +176 -19
- package/src/clustering/schedule.test.ts +0 -6
- package/src/clustering/screen-offsets.test.ts +171 -0
- package/src/clustering/types.ts +19 -9
- package/src/index.ts +91 -42
- package/src/ui/byline.tsx +21 -9
- package/src/ui/comment-card.tsx +20 -10
- package/src/ui/comment-composer.tsx +178 -56
- package/src/ui/comment-pin.tsx +13 -2
- package/src/ui/comment-thread.tsx +13 -6
- package/src/ui/comments-list.test.ts +31 -0
- package/src/ui/comments-list.tsx +82 -47
- package/src/ui/comments.css +577 -294
- package/src/ui/count-badge.tsx +9 -2
- package/src/ui/emoji-picker.test.ts +25 -0
- package/src/ui/emoji-picker.tsx +76 -0
- package/src/ui/empty-state.tsx +3 -3
- package/src/ui/format-time.test.ts +69 -0
- package/src/ui/format-time.ts +27 -2
- package/src/ui/icons.tsx +116 -0
- package/src/ui/reaction-picker.tsx +102 -0
- package/src/ui/reaction.tsx +125 -6
- package/src/ui/reactions.tsx +69 -10
- package/src/ui/reply-count.ts +16 -0
- package/src/ui/send-button.tsx +13 -3
- package/src/ui/visual-viewport.test.ts +36 -0
- package/src/ui/visual-viewport.ts +27 -0
- package/dist-cjs/canvas/region-options.js +0 -49
- package/dist-cjs/canvas/region-options.js.map +0 -7
- package/dist-cjs/ui/avatar.js.map +0 -7
- package/dist-cjs/ui/comment-mention.js +0 -42
- package/dist-cjs/ui/comment-mention.js.map +0 -7
- package/dist-cjs/ui/comment-text.js.map +0 -7
- package/dist-cjs/ui/mention-list.js +0 -70
- package/dist-cjs/ui/mention-list.js.map +0 -7
- package/dist-cjs/ui/mention-suggestion.js +0 -206
- package/dist-cjs/ui/mention-suggestion.js.map +0 -7
- package/dist-cjs/ui/mention.js.map +0 -7
- package/dist-cjs/ui/render-markdown.js +0 -63
- package/dist-cjs/ui/render-markdown.js.map +0 -7
- package/dist-esm/canvas/region-options.mjs +0 -29
- package/dist-esm/canvas/region-options.mjs.map +0 -7
- package/dist-esm/ui/avatar.mjs +0 -23
- package/dist-esm/ui/avatar.mjs.map +0 -7
- package/dist-esm/ui/comment-mention.mjs +0 -22
- package/dist-esm/ui/comment-mention.mjs.map +0 -7
- package/dist-esm/ui/comment-text.mjs +0 -9
- package/dist-esm/ui/comment-text.mjs.map +0 -7
- package/dist-esm/ui/mention-list.mjs +0 -50
- package/dist-esm/ui/mention-list.mjs.map +0 -7
- package/dist-esm/ui/mention-suggestion.mjs +0 -186
- package/dist-esm/ui/mention-suggestion.mjs.map +0 -7
- package/dist-esm/ui/mention.mjs +0 -11
- package/dist-esm/ui/mention.mjs.map +0 -7
- package/dist-esm/ui/render-markdown.mjs +0 -45
- package/dist-esm/ui/render-markdown.mjs.map +0 -7
- package/src/canvas/region-options.ts +0 -57
- package/src/ui/avatar.tsx +0 -31
- package/src/ui/comment-mention.ts +0 -47
- package/src/ui/comment-text.tsx +0 -12
- package/src/ui/mention-list.tsx +0 -106
- package/src/ui/mention-suggestion.test.ts +0 -18
- package/src/ui/mention-suggestion.tsx +0 -285
- package/src/ui/mention.tsx +0 -9
- package/src/ui/render-markdown.tsx +0 -72
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { VecLike } from 'tldraw'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { computeClusterTable } from './computeClusterTable'
|
|
4
|
+
import type { LeafInput } from './types'
|
|
5
|
+
|
|
6
|
+
function leaf(id: string, x: number, y: number): LeafInput {
|
|
7
|
+
return { id, point: { x, y } }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function mulberry32(seed: number): () => number {
|
|
11
|
+
let a = seed >>> 0
|
|
12
|
+
return () => {
|
|
13
|
+
a = (a + 0x6d2b79f5) >>> 0
|
|
14
|
+
let t = a
|
|
15
|
+
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
16
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
|
17
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function randomLeaves(n: number, seed: number, scale = 1000): LeafInput[] {
|
|
22
|
+
const rand = mulberry32(seed)
|
|
23
|
+
return Array.from({ length: n }, (_, i) => leaf(`leaf-${i}`, rand() * scale, rand() * scale))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ZOOM_BOUNDS = { minZoom: 0.05, maxZoom: 8 }
|
|
27
|
+
// eps 0 keeps every merge as its own solo event, so the exact thresholds under test aren't
|
|
28
|
+
// snapped to a contraction window's anchor.
|
|
29
|
+
const EXACT_OPTS = { Tc: 22, Tu: 26.4, eps: 0, Dmax: 82.5, ...ZOOM_BOUNDS }
|
|
30
|
+
|
|
31
|
+
function offsets(entries: Record<string, VecLike>): Map<string, VecLike> {
|
|
32
|
+
return new Map(Object.entries(entries))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('computeClusterTable without screen offsets (the guarantee)', () => {
|
|
36
|
+
it('is deep-equal across omitted, undefined, and empty offset arguments', () => {
|
|
37
|
+
for (const seed of [3, 21]) {
|
|
38
|
+
const leaves = randomLeaves(40, seed)
|
|
39
|
+
const plain = computeClusterTable(leaves, { ...ZOOM_BOUNDS })
|
|
40
|
+
expect(computeClusterTable(leaves, { ...ZOOM_BOUNDS }, undefined)).toEqual(plain)
|
|
41
|
+
expect(computeClusterTable(leaves, { ...ZOOM_BOUNDS }, new Map())).toEqual(plain)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('is deep-equal when every leaf carries the same offset (Δō is always zero)', () => {
|
|
46
|
+
const leaves = randomLeaves(30, 11)
|
|
47
|
+
const uniform = new Map(leaves.map((l) => [l.id, { x: -20, y: 20 }]))
|
|
48
|
+
expect(computeClusterTable(leaves, { ...ZOOM_BOUNDS }, uniform)).toEqual(
|
|
49
|
+
computeClusterTable(leaves, { ...ZOOM_BOUNDS })
|
|
50
|
+
)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('never emits a zSplit override on the offset-free path', () => {
|
|
54
|
+
const table = computeClusterTable(randomLeaves(20, 5), { ...ZOOM_BOUNDS }, new Map())
|
|
55
|
+
for (const event of table.events) {
|
|
56
|
+
// finalize always derives ratio splits when no override was carried
|
|
57
|
+
expect(event.zSplit).toBeCloseTo(event.zMerge * 1.2, 10)
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('offset-aware pricing', () => {
|
|
63
|
+
const A = leaf('a', 0, 0)
|
|
64
|
+
const B = leaf('b', 100, 0)
|
|
65
|
+
|
|
66
|
+
it('merges earlier when the insets point toward each other, at the exact visual crossing', () => {
|
|
67
|
+
// Visual positions: a at 100·0·z + 20, b at 100z − 20 → distance |100z − 40|. Tc = 22
|
|
68
|
+
// crosses at z = 0.62 (raw anchors would say 0.22); the split derives from the Tu/Tc
|
|
69
|
+
// ratio, as for every event.
|
|
70
|
+
const table = computeClusterTable(
|
|
71
|
+
[A, B],
|
|
72
|
+
EXACT_OPTS,
|
|
73
|
+
offsets({ a: { x: 20, y: 0 }, b: { x: -20, y: 0 } })
|
|
74
|
+
)
|
|
75
|
+
expect(table.events).toHaveLength(1)
|
|
76
|
+
expect(table.events[0].zMerge).toBeCloseTo(0.62, 10)
|
|
77
|
+
expect(table.events[0].zSplit).toBeCloseTo(0.62 * 1.2, 10)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('merges later when the insets point apart, at the exact visual crossing', () => {
|
|
81
|
+
// Distance 100z + 10: Tc at z = 0.12 (raw: 0.22); ratio split at 0.144.
|
|
82
|
+
const table = computeClusterTable(
|
|
83
|
+
[A, B],
|
|
84
|
+
EXACT_OPTS,
|
|
85
|
+
offsets({ a: { x: -5, y: 0 }, b: { x: 5, y: 0 } })
|
|
86
|
+
)
|
|
87
|
+
expect(table.events).toHaveLength(1)
|
|
88
|
+
expect(table.events[0].zMerge).toBeCloseTo(0.12, 10)
|
|
89
|
+
expect(table.events[0].zSplit).toBeCloseTo(0.144, 10)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('never merges a pair whose visual distance never reaches Tc', () => {
|
|
93
|
+
// Distance 100z + 40 ≥ 40 > Tc at every zoom: priced 0 and pruned by the minZoom cut.
|
|
94
|
+
const table = computeClusterTable(
|
|
95
|
+
[A, B],
|
|
96
|
+
EXACT_OPTS,
|
|
97
|
+
offsets({ a: { x: -20, y: 0 }, b: { x: 20, y: 0 } })
|
|
98
|
+
)
|
|
99
|
+
expect(table.events).toEqual([])
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('still applies the Dmax fit cap to a corrected merge', () => {
|
|
103
|
+
// Exaggerated inward offsets put the Tc crossing at z = 1.42, above the spread cap
|
|
104
|
+
// Dmax/diag = 82.5/100 = 0.825, so the cap wins the min.
|
|
105
|
+
const table = computeClusterTable(
|
|
106
|
+
[A, B],
|
|
107
|
+
EXACT_OPTS,
|
|
108
|
+
offsets({ a: { x: 60, y: 0 }, b: { x: -60, y: 0 } })
|
|
109
|
+
)
|
|
110
|
+
expect(table.events).toHaveLength(1)
|
|
111
|
+
expect(table.events[0].zMerge).toBeCloseTo(0.825, 10)
|
|
112
|
+
expect(table.events[0].zSplit).toBeCloseTo(0.825 * 1.2, 10)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('treats coincident anchors with differing offsets as a constant visual distance', () => {
|
|
116
|
+
// Same anchor point, one pin tucked: constant 20px apart < Tc → merged at every zoom,
|
|
117
|
+
// entering the same maxSplitZoom band as a coincident pair (zMerge 6/1.2 = 5, zSplit 6).
|
|
118
|
+
const table = computeClusterTable(
|
|
119
|
+
[leaf('a', 50, 50), leaf('b', 50, 50)],
|
|
120
|
+
{ ...EXACT_OPTS, maxSplitZoom: 6 },
|
|
121
|
+
offsets({ a: { x: 20, y: 0 } })
|
|
122
|
+
)
|
|
123
|
+
expect(table.events).toHaveLength(1)
|
|
124
|
+
expect(table.events[0].zMerge).toBe(5)
|
|
125
|
+
expect(table.events[0].zSplit).toBe(6)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it("dilutes a cluster's mean offset as precise members fold in", () => {
|
|
129
|
+
// a (tucked 20px) and b (precise) share an anchor: constant 20px apart, always merged.
|
|
130
|
+
// Their cluster's mean offset is (10, 0); against precise c 100 units right the visual
|
|
131
|
+
// distance is |100z − 10| → Tc at z = 0.32 — half the lone pin's correction.
|
|
132
|
+
const table = computeClusterTable(
|
|
133
|
+
[leaf('a', 0, 0), leaf('b', 0, 0), leaf('c', 100, 0)],
|
|
134
|
+
EXACT_OPTS,
|
|
135
|
+
offsets({ a: { x: 20, y: 0 } })
|
|
136
|
+
)
|
|
137
|
+
expect(table.events).toHaveLength(2)
|
|
138
|
+
const outer = table.events.find((e) => e.result.count === 3)!
|
|
139
|
+
expect(outer.zMerge).toBeCloseTo(0.32, 10)
|
|
140
|
+
expect(outer.zSplit).toBeCloseTo(0.32 * 1.2, 10)
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
describe('table ordering with offsets', () => {
|
|
145
|
+
it('keeps zMerge and zSplit non-increasing (splits are always ratio-derived)', () => {
|
|
146
|
+
// Mixed offset and plain leaves at varied spacings: every split is zMerge · (Tu/Tc), so
|
|
147
|
+
// both threshold sequences inherit the table's non-increasing order — the invariant the
|
|
148
|
+
// runtime's prefix cursor, split walk, and seed bisection rely on.
|
|
149
|
+
const leaves = [
|
|
150
|
+
leaf('a', 0, 0),
|
|
151
|
+
leaf('b', 100, 0),
|
|
152
|
+
leaf('c', 130, 0),
|
|
153
|
+
leaf('d', 400, 0),
|
|
154
|
+
leaf('e', 470, 0),
|
|
155
|
+
leaf('f', 1000, 300),
|
|
156
|
+
]
|
|
157
|
+
const table = computeClusterTable(
|
|
158
|
+
leaves,
|
|
159
|
+
EXACT_OPTS,
|
|
160
|
+
offsets({ a: { x: -20, y: 0 }, b: { x: 14, y: 0 }, d: { x: 20, y: 14 } })
|
|
161
|
+
)
|
|
162
|
+
expect(table.events.length).toBeGreaterThan(2)
|
|
163
|
+
for (let i = 1; i < table.events.length; i++) {
|
|
164
|
+
expect(table.events[i].zMerge).toBeLessThanOrEqual(table.events[i - 1].zMerge)
|
|
165
|
+
expect(table.events[i].zSplit).toBeLessThanOrEqual(table.events[i - 1].zSplit)
|
|
166
|
+
}
|
|
167
|
+
for (const event of table.events) {
|
|
168
|
+
expect(event.zSplit).toBeCloseTo(event.zMerge * 1.2, 10)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
})
|
package/src/clustering/types.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { VecLike } from 'tldraw'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* One comment thread's pin, already resolved to a page-space anchor point.
|
|
5
|
-
* @
|
|
5
|
+
* @internal
|
|
6
6
|
*/
|
|
7
7
|
export interface LeafInput {
|
|
8
8
|
/** Unique. Thread id. Uniqueness is a precondition — throw on duplicates. */
|
|
@@ -11,6 +11,16 @@ export interface LeafInput {
|
|
|
11
11
|
point: VecLike
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Constant screen-px render offsets for markers that don't draw exactly on their anchor —
|
|
16
|
+
* imprecise shape pins, which tuck ~20px into their shape. Keyed by leaf id; leaves absent from
|
|
17
|
+
* the map render on their anchor. A marker's visual position at zoom z is `z · point + offset`.
|
|
18
|
+
* Passed as the optional third argument to `computeClusterTable`; when omitted or empty the
|
|
19
|
+
* algorithm's behavior (and output) is identical to an offset-unaware run.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export type LeafScreenOffsets = ReadonlyMap<string, VecLike>
|
|
23
|
+
|
|
14
24
|
/** An edge of the Euclidean MST over the leaf anchor points. */
|
|
15
25
|
export interface MstEdge {
|
|
16
26
|
/** Index into the input leaves array. Normalized: leaves[a].id < leaves[b].id (lexicographic). */
|
|
@@ -23,7 +33,7 @@ export interface MstEdge {
|
|
|
23
33
|
|
|
24
34
|
/**
|
|
25
35
|
* A cluster in the merge tree: a leaf (one thread) or a merged group.
|
|
26
|
-
* @
|
|
36
|
+
* @internal
|
|
27
37
|
*/
|
|
28
38
|
export interface ClusterNode {
|
|
29
39
|
/** Leaves: the thread id verbatim. Merged nodes: `cluster:${count}:${minMemberId}`. */
|
|
@@ -58,7 +68,7 @@ export interface ContractedEvent {
|
|
|
58
68
|
|
|
59
69
|
/**
|
|
60
70
|
* A finalized merge event, ready for the runtime.
|
|
61
|
-
* @
|
|
71
|
+
* @internal
|
|
62
72
|
*/
|
|
63
73
|
export interface MergeEvent {
|
|
64
74
|
zMerge: number
|
|
@@ -70,7 +80,7 @@ export interface MergeEvent {
|
|
|
70
80
|
|
|
71
81
|
/**
|
|
72
82
|
* The precomputed clustering schedule for one page's comments.
|
|
73
|
-
* @
|
|
83
|
+
* @internal
|
|
74
84
|
*/
|
|
75
85
|
export interface ClusterTable {
|
|
76
86
|
/** Sorted non-increasing by zMerge; satisfies the invariants of CLUSTERING.md §7.6. */
|
|
@@ -79,15 +89,15 @@ export interface ClusterTable {
|
|
|
79
89
|
leaves: readonly ClusterNode[]
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
/** @
|
|
92
|
+
/** @internal */
|
|
83
93
|
export interface ClusterOptions {
|
|
84
|
-
/** Cluster (merge) distance, screen px. Default
|
|
94
|
+
/** Cluster (merge) distance, screen px. Default 22. */
|
|
85
95
|
Tc?: number
|
|
86
|
-
/** Uncluster (split) distance, screen px. Must be \> Tc. Default 1.
|
|
96
|
+
/** Uncluster (split) distance, screen px. Must be \> Tc. Default 1.2 · Tc. */
|
|
87
97
|
Tu?: number
|
|
88
|
-
/** Contraction window ratio. Default 0.
|
|
98
|
+
/** Contraction window ratio. Default 0.7. */
|
|
89
99
|
eps?: number
|
|
90
|
-
/** Max cluster screen extent at birth, screen px. Must be \>= Tc. Default 3 · Tc. */
|
|
100
|
+
/** Max cluster screen extent at birth, screen px. Must be \>= Tc. Default 3.75 · Tc. */
|
|
91
101
|
Dmax?: number
|
|
92
102
|
/** Camera zoom bounds — pass the editor's camera constraints. Required. */
|
|
93
103
|
minZoom: number
|
package/src/index.ts
CHANGED
|
@@ -1,93 +1,142 @@
|
|
|
1
1
|
import { registerTldrawLibraryVersion } from '@tldraw/utils'
|
|
2
2
|
|
|
3
3
|
// Presentational commenting components. These are tldraw-independent and can be used to build
|
|
4
|
-
// custom commenting UI.
|
|
5
|
-
|
|
4
|
+
// custom commenting UI. The mention picker, pill, and avatar are re-exported from @tldraw/mentions
|
|
5
|
+
// so the commenting public API is unchanged.
|
|
6
|
+
export {
|
|
7
|
+
Avatar,
|
|
8
|
+
type AvatarProps,
|
|
9
|
+
type CommentAuthor,
|
|
10
|
+
createMentionSuggestion,
|
|
11
|
+
filterMentionMembers,
|
|
12
|
+
Mention,
|
|
13
|
+
MentionList,
|
|
14
|
+
type MentionListProps,
|
|
15
|
+
type MentionMember,
|
|
16
|
+
type MentionProps,
|
|
17
|
+
type MentionSuggestionOptions,
|
|
18
|
+
} from '@tldraw/mentions'
|
|
6
19
|
export { Byline, type BylineProps } from './ui/byline'
|
|
7
20
|
export { CommentCard, type CommentCardProps } from './ui/comment-card'
|
|
8
21
|
export { CommentComposer, type CommentComposerProps } from './ui/comment-composer'
|
|
9
22
|
export { CountBadge, type CountBadgeProps } from './ui/count-badge'
|
|
10
23
|
export { CommentPin, type CommentPinProps } from './ui/comment-pin'
|
|
11
|
-
export { CommentText, type CommentTextProps } from './ui/comment-text'
|
|
12
24
|
export { CommentThread, type CommentThreadProps } from './ui/comment-thread'
|
|
13
|
-
export {
|
|
25
|
+
export {
|
|
26
|
+
CommentListItem,
|
|
27
|
+
CommentsList,
|
|
28
|
+
isOpenInNewTabClick,
|
|
29
|
+
type CommentListItemProps,
|
|
30
|
+
type CommentListItemRenderProps,
|
|
31
|
+
type CommentsListProps,
|
|
32
|
+
} from './ui/comments-list'
|
|
14
33
|
export { EmptyState, type EmptyStateProps } from './ui/empty-state'
|
|
15
|
-
export { formatRelativeTime } from './ui/format-time'
|
|
16
|
-
export { Mention, type MentionProps } from './ui/mention'
|
|
17
|
-
export { MentionList, type MentionListProps, type MentionMember } from './ui/mention-list'
|
|
34
|
+
export { formatFullDateTime, formatRelativeTime } from './ui/format-time'
|
|
18
35
|
export {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
type
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
|
|
36
|
+
DEFAULT_REACTION_EMOJI,
|
|
37
|
+
EmojiPicker,
|
|
38
|
+
type EmojiPickerProps,
|
|
39
|
+
isAllowedReactionEmoji,
|
|
40
|
+
} from './ui/emoji-picker'
|
|
41
|
+
export {
|
|
42
|
+
Reaction,
|
|
43
|
+
type ReactionProps,
|
|
44
|
+
type RenderReaction,
|
|
45
|
+
defaultRenderReaction,
|
|
46
|
+
DefaultReactionTooltip,
|
|
47
|
+
DefaultReactionTooltipContent,
|
|
48
|
+
type ReactionTooltipProps,
|
|
49
|
+
} from './ui/reaction'
|
|
50
|
+
export { ReactionPicker, type ReactionPickerProps } from './ui/reaction-picker'
|
|
51
|
+
export {
|
|
52
|
+
Reactions,
|
|
53
|
+
type ReactionReactor,
|
|
54
|
+
type ReactionsProps,
|
|
55
|
+
type ReactionSummary,
|
|
56
|
+
} from './ui/reactions'
|
|
26
57
|
export { SendButton, type SendButtonProps } from './ui/send-button'
|
|
27
58
|
|
|
28
59
|
// The tldraw-coupled commenting layer: the comment tool, reactive hooks over the comment
|
|
29
60
|
// records, a rich-text body renderer, and a batteries-included <CanvasComments> overlay. Pairs
|
|
30
61
|
// with the presentational components above.
|
|
62
|
+
export { registerCommentAnchorLifecycle } from './canvas/anchor-lifecycle'
|
|
31
63
|
export { CommentBody, type CommentBodyProps } from './canvas/comment-body'
|
|
32
64
|
export {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
65
|
+
deleteComment,
|
|
66
|
+
deleteThread,
|
|
67
|
+
editComment,
|
|
68
|
+
putCommentRecords,
|
|
69
|
+
removeCommentRecords,
|
|
70
|
+
reopenThread,
|
|
71
|
+
resolveThread,
|
|
72
|
+
} from './canvas/comment-mutations'
|
|
73
|
+
export {
|
|
74
|
+
CommentReactionPicker,
|
|
75
|
+
type CommentReactionPickerProps,
|
|
76
|
+
CommentReactions,
|
|
77
|
+
type CommentReactionsProps,
|
|
78
|
+
type ReactionSummaryInput,
|
|
79
|
+
summarizeReactions,
|
|
80
|
+
toggleCommentReaction,
|
|
81
|
+
useCommentReactions,
|
|
82
|
+
} from './canvas/comment-reactions'
|
|
83
|
+
export { CommentTool, commentToolOverrides, commentTools } from './canvas/comment-tool'
|
|
40
84
|
export {
|
|
85
|
+
getCommentReactions,
|
|
41
86
|
getCommentRecord,
|
|
42
87
|
getComments,
|
|
43
88
|
getCommentThreads,
|
|
44
|
-
|
|
45
|
-
|
|
89
|
+
getLiveComments,
|
|
90
|
+
getLiveCommentThreads,
|
|
46
91
|
type TLCommentRecord,
|
|
47
92
|
} from './canvas/comment-store'
|
|
48
|
-
export {
|
|
49
|
-
export type {
|
|
50
|
-
ClusterNode,
|
|
51
|
-
ClusterOptions,
|
|
52
|
-
ClusterTable,
|
|
53
|
-
LeafInput,
|
|
54
|
-
MergeEvent,
|
|
55
|
-
} from './clustering/types'
|
|
93
|
+
export { type CommentingContext } from './canvas/context'
|
|
56
94
|
export { CommentsFilterMenu, type CommentsFilterMenuProps } from './canvas/comments-filter-menu'
|
|
57
95
|
export { CommentsMenuItem } from './canvas/comments-menu-item'
|
|
58
96
|
export { CanvasComments, type CanvasCommentsProps } from './canvas/comments-overlay'
|
|
59
97
|
export {
|
|
60
98
|
type CommentingComponents,
|
|
61
99
|
type CommentingOptions,
|
|
100
|
+
type CommentModification,
|
|
101
|
+
type CommentModificationContext,
|
|
102
|
+
defaultCanModifyComment,
|
|
62
103
|
defaultCommentingOptions,
|
|
104
|
+
getCanComment,
|
|
105
|
+
getCanModifyComment,
|
|
63
106
|
getCommentingOptions,
|
|
107
|
+
type ShapeCommentPrecisionContext,
|
|
108
|
+
useCanComment,
|
|
109
|
+
useCanModifyComment,
|
|
64
110
|
useCommentingOptions,
|
|
65
111
|
} from './canvas/options'
|
|
66
112
|
export { CommentsOverflowMenu } from './canvas/comments-overflow-menu'
|
|
67
|
-
export {
|
|
113
|
+
export { CommentsVisibilityToggle } from './canvas/comments-visibility-toggle'
|
|
114
|
+
export {
|
|
115
|
+
CanvasCommentsSidebar,
|
|
116
|
+
sortSidebarRows,
|
|
117
|
+
type CanvasCommentsSidebarProps,
|
|
118
|
+
type SidebarRow,
|
|
119
|
+
} from './canvas/comments-sidebar'
|
|
68
120
|
export { useComments, useCommentThreads, useThreadComments } from './canvas/hooks'
|
|
69
121
|
export { useCommentingEnabled } from './canvas/license'
|
|
70
|
-
export { DEFAULT_REGION_COMMENT_OPTIONS, type RegionCommentOptions } from './canvas/region-options'
|
|
71
122
|
export { richTextToPlaintext } from './canvas/rich-text'
|
|
72
123
|
export { DEFAULT_SIDEBAR_FILTERS, type SidebarFilters } from './canvas/sidebar-filters'
|
|
73
124
|
export {
|
|
74
125
|
commentsHidden,
|
|
75
|
-
|
|
126
|
+
commentsSidebarOpen,
|
|
127
|
+
getRevealThreadPending,
|
|
76
128
|
openThreadId,
|
|
77
|
-
|
|
129
|
+
revealThread,
|
|
78
130
|
sidebarFilters,
|
|
79
131
|
toggleCommentsHidden,
|
|
132
|
+
toggleCommentsSidebar,
|
|
80
133
|
useCommentsHidden,
|
|
134
|
+
useCommentsSidebarOpen,
|
|
81
135
|
useOpenThreadId,
|
|
82
|
-
|
|
136
|
+
useRevealThreadPending,
|
|
83
137
|
useSidebarFilters,
|
|
84
138
|
} from './canvas/state'
|
|
85
|
-
export {
|
|
86
|
-
anchorPagePoint,
|
|
87
|
-
DEFAULT_IMPRECISE_SHAPE_ANCHOR,
|
|
88
|
-
focusThread,
|
|
89
|
-
shapeAnchorAt,
|
|
90
|
-
} from './canvas/thread-state'
|
|
139
|
+
export { anchorPagePoint, focusThread, shapeAnchorAt } from './canvas/thread-state'
|
|
91
140
|
|
|
92
141
|
registerTldrawLibraryVersion(
|
|
93
142
|
(globalThis as any).TLDRAW_LIBRARY_NAME,
|
package/src/ui/byline.tsx
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Avatar, type CommentAuthor } from '@tldraw/mentions'
|
|
2
|
+
import { TldrawUiTooltip, useCurrentTranslation } from 'tldraw'
|
|
3
|
+
import { formatFullDateTime, formatRelativeTime } from './format-time'
|
|
2
4
|
|
|
3
5
|
/** @public */
|
|
4
6
|
export interface BylineProps {
|
|
5
|
-
author:
|
|
7
|
+
author: CommentAuthor
|
|
6
8
|
/** ISO datetime; formatted to relative time by the component. */
|
|
7
9
|
date: string
|
|
8
10
|
/** Shows an "edited" marker when the comment has been edited. */
|
|
9
11
|
edited?: boolean
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* A comment's metadata line: author name, relative time, and an edited marker. Hovering the time
|
|
16
|
+
* shows the full date and time in a tooltip.
|
|
17
|
+
* @public @react
|
|
18
|
+
*/
|
|
13
19
|
export function Byline({ author, date, edited }: BylineProps) {
|
|
20
|
+
// The relative time is the one piece of a byline that isn't the host's data, so it follows the
|
|
21
|
+
// same translation context as every other string in the UI rather than a prop of its own.
|
|
22
|
+
const { locale } = useCurrentTranslation()
|
|
14
23
|
return (
|
|
15
|
-
<div className="cmt-head">
|
|
16
|
-
<
|
|
17
|
-
<span className="cmt-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
<div className="tlui-cmt-head">
|
|
25
|
+
<Avatar author={author} />
|
|
26
|
+
<span className="tlui-cmt-author">{author.name}</span>
|
|
27
|
+
<TldrawUiTooltip content={formatFullDateTime(date, locale)}>
|
|
28
|
+
<span className="tlui-cmt-time">
|
|
29
|
+
{formatRelativeTime(date, locale)}
|
|
30
|
+
{edited && <span className="tlui-cmt-edited"> · edited</span>}
|
|
31
|
+
</span>
|
|
32
|
+
</TldrawUiTooltip>
|
|
21
33
|
</div>
|
|
22
34
|
)
|
|
23
35
|
}
|
package/src/ui/comment-card.tsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { type CommentAuthor } from '@tldraw/mentions'
|
|
1
2
|
import { ReactNode } from 'react'
|
|
2
|
-
import { Avatar } from './avatar'
|
|
3
3
|
import { Byline } from './byline'
|
|
4
4
|
|
|
5
5
|
/** @public */
|
|
6
6
|
export interface CommentCardProps {
|
|
7
|
-
author:
|
|
8
|
-
/** The rendered comment body. The card doesn't dictate a format — pass a `<
|
|
9
|
-
* for
|
|
7
|
+
author: CommentAuthor
|
|
8
|
+
/** The rendered comment body. The card doesn't dictate a format — pass a `<CommentBody>`
|
|
9
|
+
* for a comment's rich text, or any node of your own. */
|
|
10
10
|
body: ReactNode
|
|
11
11
|
/** ISO datetime; formatted to relative time by the component. */
|
|
12
12
|
date: string
|
|
@@ -15,18 +15,28 @@ export interface CommentCardProps {
|
|
|
15
15
|
edited?: boolean
|
|
16
16
|
/** Hover-revealed controls at the card's top-right (e.g. an edit affordance). */
|
|
17
17
|
actions?: ReactNode
|
|
18
|
+
/** Content under the body, aligned with it rather than the avatar (e.g. a `<Reactions>` row). */
|
|
19
|
+
footer?: ReactNode
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/** A single comment: Avatar, Byline, and a body slot the consumer renders. @public @react */
|
|
21
|
-
export function CommentCard({
|
|
23
|
+
export function CommentCard({
|
|
24
|
+
author,
|
|
25
|
+
body,
|
|
26
|
+
date,
|
|
27
|
+
you,
|
|
28
|
+
edited,
|
|
29
|
+
actions,
|
|
30
|
+
footer,
|
|
31
|
+
}: CommentCardProps) {
|
|
22
32
|
return (
|
|
23
|
-
<div className={you ? 'cmt-card cmt-card--you' : 'cmt-card'}>
|
|
24
|
-
<
|
|
25
|
-
<div className="cmt-body">
|
|
26
|
-
<Byline author={author} date={date} edited={edited} />
|
|
33
|
+
<div className={you ? 'tlui-cmt-card tlui-cmt-card--you' : 'tlui-cmt-card'}>
|
|
34
|
+
<Byline author={author} date={date} edited={edited} />
|
|
35
|
+
<div className="tlui-cmt-body">
|
|
27
36
|
{body}
|
|
37
|
+
{footer}
|
|
28
38
|
</div>
|
|
29
|
-
{actions !== undefined && <div className="cmt-card__actions">{actions}</div>}
|
|
39
|
+
{actions !== undefined && <div className="tlui-cmt-card__actions">{actions}</div>}
|
|
30
40
|
</div>
|
|
31
41
|
)
|
|
32
42
|
}
|