@tldraw/commenting 5.4.2 → 5.5.0-canary.0241f11f2e99
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 +0 -32
- 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 +5 -5
- 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 +0 -32
- 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
|
@@ -383,35 +383,6 @@
|
|
|
383
383
|
user-select: none;
|
|
384
384
|
}
|
|
385
385
|
|
|
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
386
|
/* reactions */
|
|
416
387
|
.tlui-cmt-reactions {
|
|
417
388
|
display: flex;
|
|
@@ -648,9 +619,6 @@ button.tlui-cmt-reaction--active:hover {
|
|
|
648
619
|
.tlui-button.tlui-cmt-thread__action:hover {
|
|
649
620
|
color: var(--tl-color-text-1);
|
|
650
621
|
}
|
|
651
|
-
.tlui-button.tlui-cmt-thread__action--danger:hover {
|
|
652
|
-
color: var(--tl-color-danger);
|
|
653
|
-
}
|
|
654
622
|
.tlui-cmt-thread__resolved {
|
|
655
623
|
margin: 4px 12px;
|
|
656
624
|
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
|
}
|