@tldraw/commenting 5.6.0-next.d6710de420bf → 5.6.0-next.fb83f87b7388
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/dist-cjs/canvas/cluster-badge.js +7 -1
- package/dist-cjs/canvas/cluster-badge.js.map +2 -2
- package/dist-cjs/canvas/comment-reactions.js +10 -4
- package/dist-cjs/canvas/comment-reactions.js.map +2 -2
- package/dist-cjs/canvas/comment-render.js.map +2 -2
- package/dist-cjs/canvas/comment-tool.js +35 -15
- package/dist-cjs/canvas/comment-tool.js.map +2 -2
- package/dist-cjs/canvas/comments-filter-menu.js +13 -6
- package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-menu-item.js +4 -1
- package/dist-cjs/canvas/comments-menu-item.js.map +2 -2
- package/dist-cjs/canvas/comments-overflow-menu.js +9 -4
- package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-sidebar.js +14 -5
- package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
- package/dist-cjs/canvas/comments-visibility-toggle.js +5 -1
- package/dist-cjs/canvas/comments-visibility-toggle.js.map +2 -2
- package/dist-cjs/canvas/options.js.map +1 -1
- package/dist-cjs/canvas/pending-composer.js +7 -2
- package/dist-cjs/canvas/pending-composer.js.map +2 -2
- package/dist-cjs/canvas/thread-pin.js +13 -2
- package/dist-cjs/canvas/thread-pin.js.map +2 -2
- package/dist-cjs/canvas/thread-preview.js +4 -1
- package/dist-cjs/canvas/thread-preview.js.map +2 -2
- package/dist-cjs/canvas/thread-stack.js +18 -4
- package/dist-cjs/canvas/thread-stack.js.map +2 -2
- package/dist-cjs/canvas/thread-view.js +40 -20
- package/dist-cjs/canvas/thread-view.js.map +2 -2
- package/dist-cjs/index.d.ts +28 -8
- package/dist-cjs/index.js +2 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/ui/comment-composer.js +5 -0
- package/dist-cjs/ui/comment-composer.js.map +2 -2
- package/dist-cjs/ui/gesture-focus.js +37 -0
- package/dist-cjs/ui/gesture-focus.js.map +7 -0
- package/dist-cjs/ui/reaction-picker.js +5 -2
- package/dist-cjs/ui/reaction-picker.js.map +2 -2
- package/dist-cjs/ui/reaction.js +15 -5
- package/dist-cjs/ui/reaction.js.map +2 -2
- package/dist-cjs/ui/reply-count.js +8 -2
- package/dist-cjs/ui/reply-count.js.map +2 -2
- package/dist-esm/canvas/cluster-badge.mjs +8 -1
- package/dist-esm/canvas/cluster-badge.mjs.map +2 -2
- package/dist-esm/canvas/comment-reactions.mjs +12 -4
- package/dist-esm/canvas/comment-reactions.mjs.map +2 -2
- package/dist-esm/canvas/comment-render.mjs.map +2 -2
- package/dist-esm/canvas/comment-tool.mjs +37 -16
- package/dist-esm/canvas/comment-tool.mjs.map +2 -2
- package/dist-esm/canvas/comments-filter-menu.mjs +14 -6
- package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-menu-item.mjs +5 -2
- package/dist-esm/canvas/comments-menu-item.mjs.map +2 -2
- package/dist-esm/canvas/comments-overflow-menu.mjs +10 -4
- package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-sidebar.mjs +21 -6
- package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
- package/dist-esm/canvas/comments-visibility-toggle.mjs +6 -2
- package/dist-esm/canvas/comments-visibility-toggle.mjs.map +2 -2
- package/dist-esm/canvas/options.mjs.map +1 -1
- package/dist-esm/canvas/pending-composer.mjs +8 -2
- package/dist-esm/canvas/pending-composer.mjs.map +2 -2
- package/dist-esm/canvas/thread-pin.mjs +15 -3
- package/dist-esm/canvas/thread-pin.mjs.map +2 -2
- package/dist-esm/canvas/thread-preview.mjs +5 -1
- package/dist-esm/canvas/thread-preview.mjs.map +2 -2
- package/dist-esm/canvas/thread-stack.mjs +19 -4
- package/dist-esm/canvas/thread-stack.mjs.map +2 -2
- package/dist-esm/canvas/thread-view.mjs +42 -21
- package/dist-esm/canvas/thread-view.mjs.map +2 -2
- package/dist-esm/index.d.mts +28 -8
- package/dist-esm/index.mjs +8 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/ui/comment-composer.mjs +5 -0
- package/dist-esm/ui/comment-composer.mjs.map +2 -2
- package/dist-esm/ui/gesture-focus.mjs +17 -0
- package/dist-esm/ui/gesture-focus.mjs.map +7 -0
- package/dist-esm/ui/reaction-picker.mjs +6 -2
- package/dist-esm/ui/reaction-picker.mjs.map +2 -2
- package/dist-esm/ui/reaction.mjs +16 -8
- package/dist-esm/ui/reaction.mjs.map +2 -2
- package/dist-esm/ui/reply-count.mjs +8 -2
- package/dist-esm/ui/reply-count.mjs.map +2 -2
- package/package.json +5 -5
- package/src/canvas/cluster-badge.tsx +9 -1
- package/src/canvas/comment-reactions.tsx +18 -7
- package/src/canvas/comment-render.ts +6 -4
- package/src/canvas/comment-tool.test.ts +83 -7
- package/src/canvas/comment-tool.tsx +61 -19
- package/src/canvas/comments-filter-menu.tsx +15 -6
- package/src/canvas/comments-menu-item.tsx +6 -2
- package/src/canvas/comments-overflow-menu.tsx +11 -4
- package/src/canvas/comments-sidebar.tsx +23 -6
- package/src/canvas/comments-visibility-toggle.tsx +7 -2
- package/src/canvas/options.ts +1 -1
- package/src/canvas/pending-composer.tsx +11 -2
- package/src/canvas/thread-pin.tsx +17 -4
- package/src/canvas/thread-preview.tsx +6 -1
- package/src/canvas/thread-stack.tsx +21 -4
- package/src/canvas/thread-view.tsx +44 -22
- package/src/index.ts +6 -1
- package/src/ui/comment-composer.tsx +9 -0
- package/src/ui/gesture-focus.test.ts +45 -0
- package/src/ui/gesture-focus.ts +29 -0
- package/src/ui/reaction-picker.tsx +7 -2
- package/src/ui/reaction.test.tsx +22 -0
- package/src/ui/reaction.tsx +25 -14
- package/src/ui/reply-count.ts +9 -3
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
TLStateNodeConstructor,
|
|
7
7
|
TLUiOverrides,
|
|
8
8
|
VecLike,
|
|
9
|
+
defineMessages,
|
|
9
10
|
} from 'tldraw'
|
|
10
11
|
import { type CommentingOptions, defaultCommentingOptions, getCommentingOptions } from './options'
|
|
11
12
|
import { commentsSidebarOpen, pendingComment, regionDraft } from './state'
|
|
@@ -53,8 +54,8 @@ export function regionBetween(a: VecLike, b: VecLike): BoxModel {
|
|
|
53
54
|
* release — like placing a sticky note — settling on a point, or on a shape when released over one.
|
|
54
55
|
* With region comments enabled, dragging past the threshold draws a region rectangle instead.
|
|
55
56
|
* Placement only opens a composer; the records are created when the comment is posted. The tool
|
|
56
|
-
* stays active while the composer is open — posting returns to select, and clicking
|
|
57
|
-
*
|
|
57
|
+
* stays active while the composer is open — posting returns to select, and so does clicking the
|
|
58
|
+
* canvas elsewhere, which cancels the placement.
|
|
58
59
|
* @public
|
|
59
60
|
*/
|
|
60
61
|
export class CommentTool extends StateNode {
|
|
@@ -113,6 +114,44 @@ export class CommentTool extends StateNode {
|
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
function pendingCommentAt(editor: Editor, point: VecLike): PendingComment {
|
|
118
|
+
const hit = commentTargetShapeAt(editor, point)
|
|
119
|
+
const anchor: TLCommentAnchor = hit
|
|
120
|
+
? shapeAnchorAt(
|
|
121
|
+
editor,
|
|
122
|
+
hit.id,
|
|
123
|
+
point,
|
|
124
|
+
getCommentingOptions(editor).shouldBePrecise(editor, {
|
|
125
|
+
shapeId: hit.id,
|
|
126
|
+
point,
|
|
127
|
+
altKey: editor.inputs.getAltKey(),
|
|
128
|
+
})
|
|
129
|
+
)
|
|
130
|
+
: { type: 'point', x: point.x, y: point.y }
|
|
131
|
+
return { anchor, point: { x: point.x, y: point.y } }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Open the comment composer at a page point, anchored as a comment tool click there would be. Use
|
|
136
|
+
* it to start a comment from outside the tool, such as a context menu item. Enters the comment tool
|
|
137
|
+
* (which must be registered), since the draft composer closes when the tool exits. It doesn't check
|
|
138
|
+
* `canComment`: a viewer who can't comment sees the `ComposerFallback` slot instead.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* <TldrawUiMenuItem id="comment" label="Comment" onSelect={() => startCommentAt(editor, point)} />
|
|
143
|
+
* ```
|
|
144
|
+
* @public
|
|
145
|
+
*/
|
|
146
|
+
export function startCommentAt(editor: Editor, point: VecLike) {
|
|
147
|
+
editor.setCurrentTool('comment')
|
|
148
|
+
const pending = pendingCommentAt(editor, point)
|
|
149
|
+
pendingComment.set(editor, pending)
|
|
150
|
+
// Entering the tool hints the shape under the live pointer, which is wherever the caller's UI
|
|
151
|
+
// was (a menu item), not `point`.
|
|
152
|
+
editor.setHintingShapes(pending.anchor.type === 'shape' ? [pending.anchor.shapeId] : [])
|
|
153
|
+
}
|
|
154
|
+
|
|
116
155
|
/** Hint the shape a comment placed at the pointer would anchor to, using the same hit-test as the
|
|
117
156
|
* anchor resolution on release. Hinting shapes render an indicator ungated by the active tool, so
|
|
118
157
|
* this shows the select-style outline while the comment tool — not select — is active. */
|
|
@@ -142,6 +181,12 @@ class CommentIdle extends StateNode {
|
|
|
142
181
|
}
|
|
143
182
|
|
|
144
183
|
override onPointerDown() {
|
|
184
|
+
// A press away from an open composer cancels the placement rather than moving it. The
|
|
185
|
+
// tool's onExit drops the draft composer; its text survives in the draft store.
|
|
186
|
+
if (pendingComment.get(this.editor)) {
|
|
187
|
+
this.editor.setCurrentTool('select')
|
|
188
|
+
return
|
|
189
|
+
}
|
|
145
190
|
this.parent.transition('pointing')
|
|
146
191
|
}
|
|
147
192
|
}
|
|
@@ -183,20 +228,7 @@ class CommentPointing extends StateNode {
|
|
|
183
228
|
override onPointerUp() {
|
|
184
229
|
const { editor } = this
|
|
185
230
|
const point = editor.inputs.getCurrentPagePoint()
|
|
186
|
-
|
|
187
|
-
const anchor: TLCommentAnchor = hit
|
|
188
|
-
? shapeAnchorAt(
|
|
189
|
-
editor,
|
|
190
|
-
hit.id,
|
|
191
|
-
point,
|
|
192
|
-
getCommentingOptions(editor).shouldBePrecise(editor, {
|
|
193
|
-
shapeId: hit.id,
|
|
194
|
-
point,
|
|
195
|
-
altKey: editor.inputs.getAltKey(),
|
|
196
|
-
})
|
|
197
|
-
)
|
|
198
|
-
: { type: 'point', x: point.x, y: point.y }
|
|
199
|
-
pendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } })
|
|
231
|
+
pendingComment.set(editor, pendingCommentAt(editor, point))
|
|
200
232
|
// Stay in the tool while the composer is open — the interaction isn't over until the
|
|
201
233
|
// comment is posted or dismissed, and staying keeps the surrounding UI (style panel,
|
|
202
234
|
// sidebar) from churning mid-placement.
|
|
@@ -277,17 +309,27 @@ class CommentDragging extends StateNode {
|
|
|
277
309
|
/** @public */
|
|
278
310
|
export const commentTools = [CommentTool]
|
|
279
311
|
|
|
312
|
+
const messages = defineMessages({
|
|
313
|
+
toolComment: { id: 'tool.comment', defaultMessage: 'Comment' },
|
|
314
|
+
})
|
|
315
|
+
|
|
280
316
|
/** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.
|
|
281
|
-
* Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button
|
|
317
|
+
* Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button, and its
|
|
318
|
+
* `DefaultContextMenuContent` shows a comment item that opens the composer where the menu was
|
|
319
|
+
* opened.
|
|
282
320
|
* @public */
|
|
283
321
|
export const commentToolOverrides: TLUiOverrides = {
|
|
284
322
|
tools(editor, tools) {
|
|
285
323
|
tools.comment = {
|
|
286
324
|
id: 'comment',
|
|
287
325
|
icon: 'comment',
|
|
288
|
-
label:
|
|
326
|
+
label: messages.toolComment.id,
|
|
289
327
|
kbd: 'c',
|
|
290
|
-
onSelect: () =>
|
|
328
|
+
onSelect: (source) => {
|
|
329
|
+
// The menu was opened by the last pointer down; the live pointer is over the menu now.
|
|
330
|
+
if (source === 'context-menu') startCommentAt(editor, editor.inputs.getOriginPagePoint())
|
|
331
|
+
else editor.setCurrentTool('comment')
|
|
332
|
+
},
|
|
291
333
|
}
|
|
292
334
|
return tools
|
|
293
335
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
defineMessages,
|
|
2
3
|
TldrawUiButton,
|
|
3
4
|
TldrawUiDropdownMenuContent,
|
|
4
5
|
TldrawUiDropdownMenuRoot,
|
|
@@ -14,6 +15,14 @@ import { MoreMenuIcon } from '../ui/icons'
|
|
|
14
15
|
import { SidebarFilters } from './sidebar-filters'
|
|
15
16
|
import { sidebarFilters } from './state'
|
|
16
17
|
|
|
18
|
+
const messages = defineMessages({
|
|
19
|
+
commentsFilter: { id: 'comments.filter', defaultMessage: 'Filter comments' },
|
|
20
|
+
commentsOnlyMyComments: { id: 'comments.only-my-comments', defaultMessage: 'Only my comments' },
|
|
21
|
+
commentsOnlyUnread: { id: 'comments.only-unread', defaultMessage: 'Only unread' },
|
|
22
|
+
commentsShowAllPages: { id: 'comments.show-all-pages', defaultMessage: 'Show all pages' },
|
|
23
|
+
commentsShowResolved: { id: 'comments.show-resolved', defaultMessage: 'Show resolved comments' },
|
|
24
|
+
})
|
|
25
|
+
|
|
17
26
|
/** @public */
|
|
18
27
|
export interface CommentsFilterMenuProps {
|
|
19
28
|
/** Whether to offer the "only your threads" toggle (needs a known current user). */
|
|
@@ -40,8 +49,8 @@ export function CommentsFilterMenu({
|
|
|
40
49
|
<TldrawUiDropdownMenuTrigger>
|
|
41
50
|
<TldrawUiButton
|
|
42
51
|
type="icon"
|
|
43
|
-
tooltip={msg(
|
|
44
|
-
title={msg(
|
|
52
|
+
tooltip={msg(messages.commentsFilter)}
|
|
53
|
+
title={msg(messages.commentsFilter)}
|
|
45
54
|
className="tlui-cmt-header-btn"
|
|
46
55
|
>
|
|
47
56
|
<MoreMenuIcon />
|
|
@@ -54,14 +63,14 @@ export function CommentsFilterMenu({
|
|
|
54
63
|
<TldrawUiMenuGroup id="comments-filter">
|
|
55
64
|
<TldrawUiMenuCheckboxItem
|
|
56
65
|
id="show-all-pages"
|
|
57
|
-
label=
|
|
66
|
+
label={messages.commentsShowAllPages.id}
|
|
58
67
|
checked={!filters.onlyCurrentPage}
|
|
59
68
|
onSelect={() => toggle('onlyCurrentPage')}
|
|
60
69
|
/>
|
|
61
70
|
{canFilterByAuthor && (
|
|
62
71
|
<TldrawUiMenuCheckboxItem
|
|
63
72
|
id="only-my-comments"
|
|
64
|
-
label=
|
|
73
|
+
label={messages.commentsOnlyMyComments.id}
|
|
65
74
|
checked={filters.onlyMine}
|
|
66
75
|
onSelect={() => toggle('onlyMine')}
|
|
67
76
|
/>
|
|
@@ -69,14 +78,14 @@ export function CommentsFilterMenu({
|
|
|
69
78
|
{canFilterByUnread && (
|
|
70
79
|
<TldrawUiMenuCheckboxItem
|
|
71
80
|
id="only-unread"
|
|
72
|
-
label=
|
|
81
|
+
label={messages.commentsOnlyUnread.id}
|
|
73
82
|
checked={filters.onlyUnread}
|
|
74
83
|
onSelect={() => toggle('onlyUnread')}
|
|
75
84
|
/>
|
|
76
85
|
)}
|
|
77
86
|
<TldrawUiMenuCheckboxItem
|
|
78
87
|
id="show-resolved"
|
|
79
|
-
label=
|
|
88
|
+
label={messages.commentsShowResolved.id}
|
|
80
89
|
checked={filters.showResolved}
|
|
81
90
|
onSelect={() => toggle('showResolved')}
|
|
82
91
|
/>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { TldrawUiMenuCheckboxItem, useEditor, useValue } from 'tldraw'
|
|
1
|
+
import { defineMessages, TldrawUiMenuCheckboxItem, useEditor, useValue } from 'tldraw'
|
|
2
2
|
import { commentsHidden, toggleCommentsHidden } from './state'
|
|
3
3
|
|
|
4
|
+
const messages = defineMessages({
|
|
5
|
+
commentsTitle: { id: 'comments.title', defaultMessage: 'Comments' },
|
|
6
|
+
})
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
9
|
* A checkbox menu item for a "View" menu that shows/hides comment pins on the canvas — checked
|
|
6
10
|
* when comments are visible, like tldraw's own grid/snap toggles. Reads and toggles the shared
|
|
@@ -14,7 +18,7 @@ export function CommentsMenuItem() {
|
|
|
14
18
|
return (
|
|
15
19
|
<TldrawUiMenuCheckboxItem
|
|
16
20
|
id="comments-visible"
|
|
17
|
-
label=
|
|
21
|
+
label={messages.commentsTitle.id}
|
|
18
22
|
kbd="!c"
|
|
19
23
|
readonlyOk
|
|
20
24
|
checked={!hidden}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
defineMessages,
|
|
2
3
|
TldrawUiButton,
|
|
3
4
|
TldrawUiDropdownMenuContent,
|
|
4
5
|
TldrawUiDropdownMenuGroup,
|
|
@@ -12,6 +13,12 @@ import {
|
|
|
12
13
|
} from 'tldraw'
|
|
13
14
|
import { commentsHidden, toggleCommentsHidden } from './state'
|
|
14
15
|
|
|
16
|
+
const messages = defineMessages({
|
|
17
|
+
commentsMoreOptions: { id: 'comments.more-options', defaultMessage: 'More options' },
|
|
18
|
+
commentsHide: { id: 'comments.hide', defaultMessage: 'Hide comments' },
|
|
19
|
+
commentsShow: { id: 'comments.show', defaultMessage: 'Show comments' },
|
|
20
|
+
})
|
|
21
|
+
|
|
15
22
|
// A keyboard-shortcut glyph, not translatable copy — kept out of JSX as a constant.
|
|
16
23
|
const HIDE_SHORTCUT = '⇧C'
|
|
17
24
|
|
|
@@ -28,11 +35,11 @@ export function CommentsOverflowMenu() {
|
|
|
28
35
|
<TldrawUiDropdownMenuTrigger>
|
|
29
36
|
<TldrawUiButton
|
|
30
37
|
type="icon"
|
|
31
|
-
tooltip={msg(
|
|
32
|
-
title={msg(
|
|
38
|
+
tooltip={msg(messages.commentsMoreOptions)}
|
|
39
|
+
title={msg(messages.commentsMoreOptions)}
|
|
33
40
|
className="tlui-cmt-header-btn"
|
|
34
41
|
>
|
|
35
|
-
<TldrawUiIcon icon="dots-vertical" label={msg(
|
|
42
|
+
<TldrawUiIcon icon="dots-vertical" label={msg(messages.commentsMoreOptions)} small />
|
|
36
43
|
</TldrawUiButton>
|
|
37
44
|
</TldrawUiDropdownMenuTrigger>
|
|
38
45
|
<TldrawUiDropdownMenuContent
|
|
@@ -49,7 +56,7 @@ export function CommentsOverflowMenu() {
|
|
|
49
56
|
className="tlui-cmt-menu-item"
|
|
50
57
|
onClick={() => toggleCommentsHidden(editor)}
|
|
51
58
|
>
|
|
52
|
-
<span>{hidden ? msg(
|
|
59
|
+
<span>{hidden ? msg(messages.commentsShow) : msg(messages.commentsHide)}</span>
|
|
53
60
|
<span className="tlui-cmt-menu-item__shortcut">{HIDE_SHORTCUT}</span>
|
|
54
61
|
</button>
|
|
55
62
|
</TldrawUiDropdownMenuItem>
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { ReactNode, useCallback } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
defineMessages,
|
|
4
|
+
EditorPortal,
|
|
5
|
+
TLCommentThread,
|
|
6
|
+
useEditor,
|
|
7
|
+
useTranslation,
|
|
8
|
+
useValue,
|
|
9
|
+
} from 'tldraw'
|
|
3
10
|
import { CommentListItemProps, CommentListItemRenderProps, CommentsList } from '../ui/comments-list'
|
|
4
11
|
import { UNKNOWN_COMMENT_AUTHOR } from './comment-render'
|
|
5
12
|
import { CommentsFilterMenu } from './comments-filter-menu'
|
|
@@ -12,6 +19,16 @@ import { richTextToPlaintext } from './rich-text'
|
|
|
12
19
|
import { commentsSidebarOpen, openThreadId, sidebarFilters } from './state'
|
|
13
20
|
import { focusThread } from './thread-state'
|
|
14
21
|
|
|
22
|
+
const messages = defineMessages({
|
|
23
|
+
commentsEmpty: { id: 'comments.empty', defaultMessage: 'No comments on this page yet.' },
|
|
24
|
+
commentsEmptyFiltered: {
|
|
25
|
+
id: 'comments.empty-filtered',
|
|
26
|
+
defaultMessage: 'No comments match your filters.',
|
|
27
|
+
},
|
|
28
|
+
commentsResolved: { id: 'comments.resolved', defaultMessage: 'Resolved' },
|
|
29
|
+
commentsTitle: { id: 'comments.title', defaultMessage: 'Comments' },
|
|
30
|
+
})
|
|
31
|
+
|
|
15
32
|
/**
|
|
16
33
|
* The host wiring for {@link CanvasCommentsSidebar}: the {@link CommentingContext} fields it reads,
|
|
17
34
|
* plus the panel's own slots. A non-null `currentUserId` enables the "only your threads" filter, and
|
|
@@ -146,22 +163,22 @@ export function CanvasCommentsSidebar(props: CanvasCommentsSidebarProps) {
|
|
|
146
163
|
<SidebarPanel>
|
|
147
164
|
<CommentsList
|
|
148
165
|
items={items}
|
|
149
|
-
header={header ?? msg(
|
|
166
|
+
header={header ?? msg(messages.commentsTitle)}
|
|
150
167
|
headerAction={
|
|
151
168
|
<div className="tlui-cmt-list__header-actions">
|
|
169
|
+
<CommentsVisibilityToggle />
|
|
152
170
|
<CommentsFilterMenu
|
|
153
171
|
canFilterByAuthor={currentUserId !== null}
|
|
154
172
|
canFilterByUnread={isCommentUnread !== undefined}
|
|
155
173
|
/>
|
|
156
|
-
<CommentsVisibilityToggle />
|
|
157
174
|
</div>
|
|
158
175
|
}
|
|
159
176
|
empty={
|
|
160
177
|
items.length === 0 && pageThreads.length > 0
|
|
161
|
-
? msg(
|
|
162
|
-
: (empty ?? msg(
|
|
178
|
+
? msg(messages.commentsEmptyFiltered)
|
|
179
|
+
: (empty ?? msg(messages.commentsEmpty))
|
|
163
180
|
}
|
|
164
|
-
resolvedLabel={msg(
|
|
181
|
+
resolvedLabel={msg(messages.commentsResolved)}
|
|
165
182
|
onSelect={focus}
|
|
166
183
|
renderItem={renderItem}
|
|
167
184
|
/>
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { TldrawUiButton, useEditor, useTranslation, useValue } from 'tldraw'
|
|
1
|
+
import { defineMessages, TldrawUiButton, useEditor, useTranslation, useValue } from 'tldraw'
|
|
2
2
|
import { EyeClosedIcon, EyeOpenIcon } from '../ui/icons'
|
|
3
3
|
import { commentsHidden, toggleCommentsHidden } from './state'
|
|
4
4
|
|
|
5
|
+
const messages = defineMessages({
|
|
6
|
+
commentsHide: { id: 'comments.hide', defaultMessage: 'Hide comments' },
|
|
7
|
+
commentsShow: { id: 'comments.show', defaultMessage: 'Show comments' },
|
|
8
|
+
})
|
|
9
|
+
|
|
5
10
|
/** The sidebar header's show/hide toggle for comment pins — an eye that closes while comments
|
|
6
11
|
* are hidden. The same state as the Shift+C shortcut.
|
|
7
12
|
* @public @react */
|
|
@@ -9,7 +14,7 @@ export function CommentsVisibilityToggle() {
|
|
|
9
14
|
const editor = useEditor()
|
|
10
15
|
const msg = useTranslation()
|
|
11
16
|
const hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])
|
|
12
|
-
const label = hidden ? msg(
|
|
17
|
+
const label = hidden ? msg(messages.commentsShow) : msg(messages.commentsHide)
|
|
13
18
|
|
|
14
19
|
return (
|
|
15
20
|
<TldrawUiButton
|
package/src/canvas/options.ts
CHANGED
|
@@ -91,7 +91,7 @@ export interface CommentingComponents {
|
|
|
91
91
|
/**
|
|
92
92
|
* The hover affordance naming who reacted with an emoji. Receives the reactors and the pill (as
|
|
93
93
|
* `children`) and owns the whole thing — box, size, shape, position. For a wording change,
|
|
94
|
-
* translate the `comments.reacted
|
|
94
|
+
* translate the `comments.reacted` string instead.
|
|
95
95
|
*/
|
|
96
96
|
ReactionTooltip?: ComponentType<ReactionTooltipProps>
|
|
97
97
|
/**
|
|
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react'
|
|
|
3
3
|
import {
|
|
4
4
|
createComment,
|
|
5
5
|
createCommentThread,
|
|
6
|
+
defineMessages,
|
|
6
7
|
Editor,
|
|
7
8
|
EditorPortal,
|
|
8
9
|
TLRichText,
|
|
@@ -27,6 +28,11 @@ import { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'
|
|
|
27
28
|
import { useCanComment, useCommentingOptions } from './options'
|
|
28
29
|
import { pendingComment } from './state'
|
|
29
30
|
|
|
31
|
+
const messages = defineMessages({
|
|
32
|
+
commentsSend: { id: 'comments.send', defaultMessage: 'Send' },
|
|
33
|
+
commentsAddPlaceholder: { id: 'comments.add-placeholder', defaultMessage: 'Add a comment…' },
|
|
34
|
+
})
|
|
35
|
+
|
|
30
36
|
const stop = (e: { stopPropagation(): void }) => e.stopPropagation()
|
|
31
37
|
|
|
32
38
|
/**
|
|
@@ -80,6 +86,9 @@ export function PendingComposer({
|
|
|
80
86
|
// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is
|
|
81
87
|
// not "outside" — keep the draft open so the pick can insert.
|
|
82
88
|
if (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return
|
|
89
|
+
// A press on the canvas is the comment tool's to handle: it ends the placement and returns
|
|
90
|
+
// to select. Clearing here first would let that press re-place the composer instead.
|
|
91
|
+
if (editor.getContainer().querySelector('.tl-canvas')?.contains(target)) return
|
|
83
92
|
pendingComment.set(editor, null)
|
|
84
93
|
}
|
|
85
94
|
document.addEventListener('pointerdown', onPointerDown, true)
|
|
@@ -137,8 +146,8 @@ export function PendingComposer({
|
|
|
137
146
|
{canComment ? (
|
|
138
147
|
<CommentComposer
|
|
139
148
|
author={me ?? UNKNOWN_COMMENT_AUTHOR}
|
|
140
|
-
placeholder={msg(
|
|
141
|
-
sendLabel={msg(
|
|
149
|
+
placeholder={msg(messages.commentsAddPlaceholder)}
|
|
150
|
+
sendLabel={msg(messages.commentsSend)}
|
|
142
151
|
value={text}
|
|
143
152
|
onChange={(value) => {
|
|
144
153
|
setText(value)
|
|
@@ -8,9 +8,10 @@ import {
|
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react'
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
defineMessages,
|
|
12
12
|
Editor,
|
|
13
13
|
TLCommentThread,
|
|
14
|
+
type BoxModel,
|
|
14
15
|
useContainer,
|
|
15
16
|
usePassThroughWheelEvents,
|
|
16
17
|
useTranslation,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
import { CommentPin } from '../ui/comment-pin'
|
|
20
21
|
import { forwardPointerEventToCanvas, isCanvasPanGesture } from './canvas-events'
|
|
21
22
|
import { commitCommentMutation } from './comment-mutations'
|
|
22
|
-
import {
|
|
23
|
+
import { UNKNOWN_COMMENT_AUTHOR } from './comment-render'
|
|
23
24
|
import { type CommentingContext } from './context'
|
|
24
25
|
import { useThreadComments } from './hooks'
|
|
25
26
|
import { getCommentingOptions, useCanComment, useCommentingOptions } from './options'
|
|
@@ -46,6 +47,15 @@ import {
|
|
|
46
47
|
} from './thread-state'
|
|
47
48
|
import { POPOVER_OFFSET, ThreadPopover, ThreadView } from './thread-view'
|
|
48
49
|
|
|
50
|
+
const messages = defineMessages({
|
|
51
|
+
commentsUnknownAuthor: { id: 'comments.unknown-author', defaultMessage: 'Someone' },
|
|
52
|
+
commentsPinLabel: { id: 'comments.pin-label', defaultMessage: 'Comment by {name}' },
|
|
53
|
+
commentsPinLabelResolved: {
|
|
54
|
+
id: 'comments.pin-label-resolved',
|
|
55
|
+
defaultMessage: 'Resolved comment by {name}',
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
49
59
|
/** The opened popover has a header row the hover preview lacks, so it opens this much higher — the
|
|
50
60
|
* first comment then lands where the preview's sat. Re-measure if the header height or the preview
|
|
51
61
|
* panel padding changes. */
|
|
@@ -195,8 +205,11 @@ export const ThreadPin = memo(function ThreadPin({
|
|
|
195
205
|
<Avatar author={threadAuthor ?? UNKNOWN_COMMENT_AUTHOR} />
|
|
196
206
|
)
|
|
197
207
|
const pinLabel = msg(
|
|
198
|
-
thread.resolved ?
|
|
199
|
-
|
|
208
|
+
thread.resolved ? messages.commentsPinLabelResolved : messages.commentsPinLabel,
|
|
209
|
+
{
|
|
210
|
+
name: threadAuthor?.name ?? msg(messages.commentsUnknownAuthor),
|
|
211
|
+
}
|
|
212
|
+
)
|
|
200
213
|
|
|
201
214
|
// Drag the marker to move the thread: position is overridden locally while dragging, then re-anchored
|
|
202
215
|
// on drop. A region translates keeping its size; a barely-moved pointer is a click.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, type CSSProperties } from 'react'
|
|
2
2
|
import {
|
|
3
|
+
defineMessages,
|
|
3
4
|
Editor,
|
|
4
5
|
EditorAtom,
|
|
5
6
|
EditorPortal,
|
|
@@ -17,6 +18,10 @@ import { useCommentingOptions } from './options'
|
|
|
17
18
|
import { openStackId, openThreadId } from './state'
|
|
18
19
|
import { POPOVER_OFFSET, toCardProps, useResolveName } from './thread-view'
|
|
19
20
|
|
|
21
|
+
const messages = defineMessages({
|
|
22
|
+
commentsPreviewMore: { id: 'comments.preview-more', defaultMessage: '{count} more' },
|
|
23
|
+
})
|
|
24
|
+
|
|
20
25
|
/**
|
|
21
26
|
* Hover previews for every canvas marker — a single pin, a coincident stack, or a cluster badge.
|
|
22
27
|
* Hovering shows the thread(s) behind the marker as cards; the marker's own click is unchanged.
|
|
@@ -277,7 +282,7 @@ export function ThreadPreview({
|
|
|
277
282
|
})}
|
|
278
283
|
{overflow > 0 && (
|
|
279
284
|
<div className="tlui-cmt-preview-more">
|
|
280
|
-
{msg(
|
|
285
|
+
{msg(messages.commentsPreviewMore, { count: overflow })}
|
|
281
286
|
</div>
|
|
282
287
|
)}
|
|
283
288
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type MouseEvent as ReactMouseEvent, memo, useEffect, useRef } from 'react'
|
|
2
2
|
import {
|
|
3
|
+
defineMessages,
|
|
3
4
|
Editor,
|
|
4
5
|
TLCommentThread,
|
|
5
6
|
usePassThroughWheelEvents,
|
|
@@ -8,7 +9,6 @@ import {
|
|
|
8
9
|
} from 'tldraw'
|
|
9
10
|
import { CommentCard } from '../ui/comment-card'
|
|
10
11
|
import { CountBadge } from '../ui/count-badge'
|
|
11
|
-
import { UNKNOWN_AUTHOR } from './comment-render'
|
|
12
12
|
import { type CommentingContext } from './context'
|
|
13
13
|
import { useThreadComments } from './hooks'
|
|
14
14
|
import { useCommentingOptions } from './options'
|
|
@@ -24,6 +24,19 @@ import {
|
|
|
24
24
|
useResolveName,
|
|
25
25
|
} from './thread-view'
|
|
26
26
|
|
|
27
|
+
const messages = defineMessages({
|
|
28
|
+
commentsUnknownAuthor: { id: 'comments.unknown-author', defaultMessage: 'Someone' },
|
|
29
|
+
commentsPinLabel: { id: 'comments.pin-label', defaultMessage: 'Comment by {name}' },
|
|
30
|
+
commentsPinLabelResolved: {
|
|
31
|
+
id: 'comments.pin-label-resolved',
|
|
32
|
+
defaultMessage: 'Resolved comment by {name}',
|
|
33
|
+
},
|
|
34
|
+
commentsStackLabel: {
|
|
35
|
+
id: 'comments.stack-label',
|
|
36
|
+
defaultMessage: '{count, plural, one {# comment at this spot} other {# comments at this spot}}',
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
27
40
|
/**
|
|
28
41
|
* The pin for threads whose anchors resolve to the same page point — pins zooming can never
|
|
29
42
|
* separate, so they share one count badge. Clicking it lists each thread as a card; clicking a card
|
|
@@ -137,7 +150,7 @@ export const ThreadStackPin = memo(function ThreadStackPin({
|
|
|
137
150
|
ref={badgeRef}
|
|
138
151
|
type="button"
|
|
139
152
|
className="tlui-cmt-button tlui-cmt-canvas-stack-badge"
|
|
140
|
-
aria-label={msg(
|
|
153
|
+
aria-label={msg(messages.commentsStackLabel, { count: threads.length })}
|
|
141
154
|
aria-expanded={open}
|
|
142
155
|
onPointerDown={(e) => e.stopPropagation()}
|
|
143
156
|
onClick={(e) => {
|
|
@@ -228,8 +241,12 @@ function StackThreadCard({
|
|
|
228
241
|
type="button"
|
|
229
242
|
className="tlui-cmt-button tlui-cmt-stack-list__card-action"
|
|
230
243
|
aria-label={msg(
|
|
231
|
-
thread.resolved ?
|
|
232
|
-
|
|
244
|
+
thread.resolved ? messages.commentsPinLabelResolved : messages.commentsPinLabel,
|
|
245
|
+
{
|
|
246
|
+
name:
|
|
247
|
+
props.resolveAuthor(thread.createdBy)?.name ?? msg(messages.commentsUnknownAuthor),
|
|
248
|
+
}
|
|
249
|
+
)}
|
|
233
250
|
onClick={open}
|
|
234
251
|
/>
|
|
235
252
|
<CommentCard {...toCardProps(first, props, options.components, resolveName)} />
|