@tldraw/commenting 5.5.0-next.f9308d324e1c → 5.6.0-canary.5bfc37740468
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/commenting.css +1 -38
- package/dist-cjs/canvas/anchor-lifecycle.js +4 -5
- package/dist-cjs/canvas/anchor-lifecycle.js.map +2 -2
- package/dist-cjs/canvas/cluster-fade.js +1 -1
- package/dist-cjs/canvas/cluster-fade.js.map +2 -2
- package/dist-cjs/canvas/cluster-model.js +23 -31
- package/dist-cjs/canvas/cluster-model.js.map +2 -2
- package/dist-cjs/canvas/comment-render.js +19 -19
- package/dist-cjs/canvas/comment-render.js.map +2 -2
- package/dist-cjs/canvas/comment-store.js +10 -5
- package/dist-cjs/canvas/comment-store.js.map +2 -2
- package/dist-cjs/canvas/comments-overlay.js +12 -16
- package/dist-cjs/canvas/comments-overlay.js.map +2 -2
- package/dist-cjs/canvas/comments-sidebar.js +1 -7
- package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
- package/dist-cjs/canvas/hooks.js.map +1 -1
- package/dist-cjs/canvas/thread-pin.js +14 -26
- package/dist-cjs/canvas/thread-pin.js.map +2 -2
- package/dist-cjs/canvas/thread-stack.js +2 -6
- package/dist-cjs/canvas/thread-stack.js.map +2 -2
- package/dist-cjs/canvas/thread-state.js +13 -0
- package/dist-cjs/canvas/thread-state.js.map +2 -2
- package/dist-cjs/canvas/thread-view.js +67 -78
- package/dist-cjs/canvas/thread-view.js.map +2 -2
- package/dist-cjs/clustering/computeClusterTable.js +2 -9
- package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
- package/dist-cjs/clustering/replay.js +10 -23
- package/dist-cjs/clustering/replay.js.map +2 -2
- package/dist-cjs/clustering/runtime.js +9 -15
- package/dist-cjs/clustering/runtime.js.map +2 -2
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/ui/comment-composer.js +2 -4
- package/dist-cjs/ui/comment-composer.js.map +2 -2
- package/dist-cjs/ui/format-time.js +20 -8
- package/dist-cjs/ui/format-time.js.map +2 -2
- package/dist-cjs/ui/reaction.js +5 -15
- package/dist-cjs/ui/reaction.js.map +2 -2
- package/dist-esm/canvas/anchor-lifecycle.mjs +5 -6
- package/dist-esm/canvas/anchor-lifecycle.mjs.map +2 -2
- package/dist-esm/canvas/cluster-fade.mjs +1 -1
- package/dist-esm/canvas/cluster-fade.mjs.map +2 -2
- package/dist-esm/canvas/cluster-model.mjs +24 -32
- package/dist-esm/canvas/cluster-model.mjs.map +2 -2
- package/dist-esm/canvas/comment-render.mjs +19 -19
- package/dist-esm/canvas/comment-render.mjs.map +2 -2
- package/dist-esm/canvas/comment-store.mjs +10 -5
- package/dist-esm/canvas/comment-store.mjs.map +2 -2
- package/dist-esm/canvas/comments-overlay.mjs +13 -17
- package/dist-esm/canvas/comments-overlay.mjs.map +2 -2
- package/dist-esm/canvas/comments-sidebar.mjs +3 -14
- package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
- package/dist-esm/canvas/hooks.mjs.map +1 -1
- package/dist-esm/canvas/thread-pin.mjs +16 -26
- package/dist-esm/canvas/thread-pin.mjs.map +2 -2
- package/dist-esm/canvas/thread-stack.mjs +3 -7
- package/dist-esm/canvas/thread-stack.mjs.map +2 -2
- package/dist-esm/canvas/thread-state.mjs +13 -0
- package/dist-esm/canvas/thread-state.mjs.map +2 -2
- package/dist-esm/canvas/thread-view.mjs +67 -78
- package/dist-esm/canvas/thread-view.mjs.map +2 -2
- package/dist-esm/clustering/computeClusterTable.mjs +2 -9
- package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
- package/dist-esm/clustering/replay.mjs +10 -23
- package/dist-esm/clustering/replay.mjs.map +2 -2
- package/dist-esm/clustering/runtime.mjs +9 -15
- package/dist-esm/clustering/runtime.mjs.map +2 -2
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/ui/comment-composer.mjs +2 -4
- package/dist-esm/ui/comment-composer.mjs.map +2 -2
- package/dist-esm/ui/format-time.mjs +20 -8
- package/dist-esm/ui/format-time.mjs.map +2 -2
- package/dist-esm/ui/reaction.mjs +7 -15
- package/dist-esm/ui/reaction.mjs.map +2 -2
- package/package.json +16 -20
- package/src/canvas/anchor-lifecycle.ts +5 -6
- package/src/canvas/cluster-fade.ts +6 -6
- package/src/canvas/cluster-model.test.ts +64 -1
- package/src/canvas/cluster-model.ts +28 -33
- package/src/canvas/comment-render.ts +30 -28
- package/src/canvas/comment-store.ts +16 -6
- package/src/canvas/comments-overlay.tsx +18 -19
- package/src/canvas/comments-sidebar.tsx +3 -18
- package/src/canvas/hooks.ts +1 -1
- package/src/canvas/thread-pin.tsx +21 -31
- package/src/canvas/thread-stack.tsx +3 -7
- package/src/canvas/thread-state.test.ts +35 -0
- package/src/canvas/thread-state.ts +27 -0
- package/src/canvas/thread-view.tsx +93 -95
- package/src/clustering/computeClusterTable.ts +3 -20
- package/src/clustering/replay.ts +10 -23
- package/src/clustering/runtime.ts +9 -15
- package/src/ui/comment-composer.tsx +2 -4
- package/src/ui/comments.css +1 -34
- package/src/ui/format-time.ts +24 -11
- package/src/ui/reaction.tsx +11 -23
|
@@ -114,8 +114,7 @@ export function CommentComposer({
|
|
|
114
114
|
if (!expandedRef.current) setExpanded(true)
|
|
115
115
|
return
|
|
116
116
|
}
|
|
117
|
-
const
|
|
118
|
-
const send = field ? field.querySelector<HTMLElement>('.tlui-cmt-send') : null
|
|
117
|
+
const send = wrap.parentElement?.querySelector<HTMLElement>('.tlui-cmt-send')
|
|
119
118
|
const input = wrap.querySelector('.tlui-cmt-input')
|
|
120
119
|
if (!send || !input) return
|
|
121
120
|
mirror.innerHTML = input.innerHTML
|
|
@@ -263,8 +262,7 @@ export function CommentComposer({
|
|
|
263
262
|
// space beside/below a short line) focuses the editor rather than only the text glyphs being
|
|
264
263
|
// clickable. The input (caret placement) and the send button keep their own click handling.
|
|
265
264
|
const focusEditorFromField = (e: ReactMouseEvent<HTMLDivElement>) => {
|
|
266
|
-
|
|
267
|
-
if (target.closest('.tlui-cmt-input') || target.closest('.tlui-cmt-send')) return
|
|
265
|
+
if ((e.target as HTMLElement).closest('.tlui-cmt-input, .tlui-cmt-send')) return
|
|
268
266
|
e.preventDefault()
|
|
269
267
|
editor?.commands.focus('end')
|
|
270
268
|
}
|
package/src/ui/comments.css
CHANGED
|
@@ -136,8 +136,7 @@
|
|
|
136
136
|
text-decoration: underline;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
/* Avatar and mention styles live in @tldraw/mentions
|
|
140
|
-
commenting.css by copy-css-files.mjs. */
|
|
139
|
+
/* Avatar and mention styles live in @tldraw/mentions and are imported by commenting.css. */
|
|
141
140
|
|
|
142
141
|
/* composer — an avatar beside a rounded-rect field holding the rich-text input and the send button.
|
|
143
142
|
Formatting is applied via markdown and keyboard shortcuts (no toolbar). The avatar aligns to the
|
|
@@ -383,35 +382,6 @@
|
|
|
383
382
|
user-select: none;
|
|
384
383
|
}
|
|
385
384
|
|
|
386
|
-
/* thread */
|
|
387
|
-
.tlui-cmt-thread {
|
|
388
|
-
display: flex;
|
|
389
|
-
flex-direction: column;
|
|
390
|
-
width: 300px;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
.tlui-cmt-thread__head {
|
|
394
|
-
display: flex;
|
|
395
|
-
align-items: center;
|
|
396
|
-
gap: 8px;
|
|
397
|
-
font-size: 12px;
|
|
398
|
-
font-weight: 600;
|
|
399
|
-
color: var(--tl-color-text-3);
|
|
400
|
-
text-transform: uppercase;
|
|
401
|
-
letter-spacing: 0.04em;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
.tlui-cmt-badge {
|
|
405
|
-
font-size: 10px;
|
|
406
|
-
font-weight: 600;
|
|
407
|
-
text-transform: none;
|
|
408
|
-
letter-spacing: 0;
|
|
409
|
-
color: #fff;
|
|
410
|
-
background: var(--tl-color-muted-1);
|
|
411
|
-
padding: 1px 6px;
|
|
412
|
-
border-radius: 999px;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
385
|
/* reactions */
|
|
416
386
|
.tlui-cmt-reactions {
|
|
417
387
|
display: flex;
|
|
@@ -648,9 +618,6 @@ button.tlui-cmt-reaction--active:hover {
|
|
|
648
618
|
.tlui-button.tlui-cmt-thread__action:hover {
|
|
649
619
|
color: var(--tl-color-text-1);
|
|
650
620
|
}
|
|
651
|
-
.tlui-button.tlui-cmt-thread__action--danger:hover {
|
|
652
|
-
color: var(--tl-color-danger);
|
|
653
|
-
}
|
|
654
621
|
.tlui-cmt-thread__resolved {
|
|
655
622
|
margin: 4px 12px;
|
|
656
623
|
padding: 6px 10px;
|
package/src/ui/format-time.ts
CHANGED
|
@@ -8,6 +8,20 @@ const DIVISIONS = [
|
|
|
8
8
|
{ amount: Number.POSITIVE_INFINITY, unit: 'year' },
|
|
9
9
|
] as const
|
|
10
10
|
|
|
11
|
+
// Both formatters are cached per locale — comment cards re-render on every reply keystroke, and
|
|
12
|
+
// constructing an Intl formatter is far more expensive than formatting with one.
|
|
13
|
+
const relativeTimeFormatters = new Map<string, Intl.RelativeTimeFormat>()
|
|
14
|
+
const fullDateTimeFormatters = new Map<string, Intl.DateTimeFormat>()
|
|
15
|
+
|
|
16
|
+
function getCached<T>(cache: Map<string, T>, locale: string, create: () => T): T {
|
|
17
|
+
let formatter = cache.get(locale)
|
|
18
|
+
if (!formatter) {
|
|
19
|
+
formatter = create()
|
|
20
|
+
cache.set(locale, formatter)
|
|
21
|
+
}
|
|
22
|
+
return formatter
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
/**
|
|
12
26
|
* Format an ISO datetime as compact relative time ("2h ago", "yesterday", "last wk.").
|
|
13
27
|
* Locale-aware via Intl.RelativeTimeFormat.
|
|
@@ -17,7 +31,11 @@ export function formatRelativeTime(iso: string, locale = 'en'): string {
|
|
|
17
31
|
const then = new Date(iso).getTime()
|
|
18
32
|
if (Number.isNaN(then)) return ''
|
|
19
33
|
|
|
20
|
-
const rtf =
|
|
34
|
+
const rtf = getCached(
|
|
35
|
+
relativeTimeFormatters,
|
|
36
|
+
locale,
|
|
37
|
+
() => new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'narrow' })
|
|
38
|
+
)
|
|
21
39
|
let duration = (then - Date.now()) / 1000
|
|
22
40
|
// Under a minute is just "now" — with second granularity the label visibly ticks while
|
|
23
41
|
// typing a reply (every keystroke re-renders the card).
|
|
@@ -33,10 +51,6 @@ export function formatRelativeTime(iso: string, locale = 'en'): string {
|
|
|
33
51
|
return ''
|
|
34
52
|
}
|
|
35
53
|
|
|
36
|
-
// Cached per locale — comment cards re-render on every reply keystroke, and constructing a
|
|
37
|
-
// DateTimeFormat is far more expensive than formatting with one.
|
|
38
|
-
const fullDateTimeFormatters = new Map<string, Intl.DateTimeFormat>()
|
|
39
|
-
|
|
40
54
|
/**
|
|
41
55
|
* Format an ISO datetime as a full date and time ("Tuesday, July 22, 2025 at 4:44 PM").
|
|
42
56
|
* Locale-aware via Intl.DateTimeFormat.
|
|
@@ -45,10 +59,9 @@ const fullDateTimeFormatters = new Map<string, Intl.DateTimeFormat>()
|
|
|
45
59
|
export function formatFullDateTime(iso: string, locale = 'en'): string {
|
|
46
60
|
const date = new Date(iso)
|
|
47
61
|
if (Number.isNaN(date.getTime())) return ''
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return formatter.format(date)
|
|
62
|
+
return getCached(
|
|
63
|
+
fullDateTimeFormatters,
|
|
64
|
+
locale,
|
|
65
|
+
() => new Intl.DateTimeFormat(locale, { dateStyle: 'full', timeStyle: 'short' })
|
|
66
|
+
).format(date)
|
|
54
67
|
}
|
package/src/ui/reaction.tsx
CHANGED
|
@@ -108,28 +108,16 @@ export function DefaultReactionTooltipContent({ reactors }: { reactors: Reaction
|
|
|
108
108
|
const names = reactors.map((reactor) =>
|
|
109
109
|
reactor.you ? msg('comments.mention-you') : reactor.name
|
|
110
110
|
)
|
|
111
|
+
if (names.length === 0) return null
|
|
111
112
|
const [a, b, c] = names
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const others = names.length - 3
|
|
123
|
-
const template = others === 1 ? 'comments.reacted-more-one' : 'comments.reacted-more'
|
|
124
|
-
return (
|
|
125
|
-
<>
|
|
126
|
-
{msg(template)
|
|
127
|
-
.replace('{a}', a)
|
|
128
|
-
.replace('{b}', b)
|
|
129
|
-
.replace('{c}', c)
|
|
130
|
-
.replace('{count}', String(others))}
|
|
131
|
-
</>
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
113
|
+
const others = names.length - 3
|
|
114
|
+
const key =
|
|
115
|
+
names.length <= 3
|
|
116
|
+
? `comments.reacted-${names.length}`
|
|
117
|
+
: others === 1
|
|
118
|
+
? 'comments.reacted-more-one'
|
|
119
|
+
: 'comments.reacted-more'
|
|
120
|
+
// Single pass, so a substituted name containing a placeholder can't be re-substituted.
|
|
121
|
+
const vars: Record<string, string | undefined> = { a, b, c, count: String(others) }
|
|
122
|
+
return <>{msg(key).replace(/\{(a|b|c|count)\}/g, (_, k) => vars[k] ?? '')}</>
|
|
135
123
|
}
|