@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
|
@@ -26,7 +26,6 @@ var import_react = require("react");
|
|
|
26
26
|
var import_tldraw = require("tldraw");
|
|
27
27
|
var import_comment_card = require("../ui/comment-card");
|
|
28
28
|
var import_count_badge = require("../ui/count-badge");
|
|
29
|
-
var import_comment_render = require("./comment-render");
|
|
30
29
|
var import_hooks = require("./hooks");
|
|
31
30
|
var import_options = require("./options");
|
|
32
31
|
var import_pin_stacking = require("./pin-stacking");
|
|
@@ -34,6 +33,18 @@ var import_state = require("./state");
|
|
|
34
33
|
var import_thread_preview = require("./thread-preview");
|
|
35
34
|
var import_thread_state = require("./thread-state");
|
|
36
35
|
var import_thread_view = require("./thread-view");
|
|
36
|
+
const messages = (0, import_tldraw.defineMessages)({
|
|
37
|
+
commentsUnknownAuthor: { id: "comments.unknown-author", defaultMessage: "Someone" },
|
|
38
|
+
commentsPinLabel: { id: "comments.pin-label", defaultMessage: "Comment by {name}" },
|
|
39
|
+
commentsPinLabelResolved: {
|
|
40
|
+
id: "comments.pin-label-resolved",
|
|
41
|
+
defaultMessage: "Resolved comment by {name}"
|
|
42
|
+
},
|
|
43
|
+
commentsStackLabel: {
|
|
44
|
+
id: "comments.stack-label",
|
|
45
|
+
defaultMessage: "{count, plural, one {# comment at this spot} other {# comments at this spot}}"
|
|
46
|
+
}
|
|
47
|
+
});
|
|
37
48
|
const ThreadStackPin = (0, import_react.memo)(function ThreadStackPin2({
|
|
38
49
|
editor,
|
|
39
50
|
threads,
|
|
@@ -109,7 +120,7 @@ const ThreadStackPin = (0, import_react.memo)(function ThreadStackPin2({
|
|
|
109
120
|
ref: badgeRef,
|
|
110
121
|
type: "button",
|
|
111
122
|
className: "tlui-cmt-button tlui-cmt-canvas-stack-badge",
|
|
112
|
-
"aria-label": msg(
|
|
123
|
+
"aria-label": msg(messages.commentsStackLabel, { count: threads.length }),
|
|
113
124
|
"aria-expanded": open,
|
|
114
125
|
onPointerDown: (e) => e.stopPropagation(),
|
|
115
126
|
onClick: (e) => {
|
|
@@ -184,8 +195,11 @@ function StackThreadCard({
|
|
|
184
195
|
type: "button",
|
|
185
196
|
className: "tlui-cmt-button tlui-cmt-stack-list__card-action",
|
|
186
197
|
"aria-label": msg(
|
|
187
|
-
thread.resolved ?
|
|
188
|
-
|
|
198
|
+
thread.resolved ? messages.commentsPinLabelResolved : messages.commentsPinLabel,
|
|
199
|
+
{
|
|
200
|
+
name: props.resolveAuthor(thread.createdBy)?.name ?? msg(messages.commentsUnknownAuthor)
|
|
201
|
+
}
|
|
202
|
+
),
|
|
189
203
|
onClick: open
|
|
190
204
|
}
|
|
191
205
|
),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/thread-stack.tsx"],
|
|
4
|
-
"sourcesContent": ["import { type MouseEvent as ReactMouseEvent, memo, useEffect, useRef } from 'react'\nimport {\n\tEditor,\n\tTLCommentThread,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentCard } from '../ui/comment-card'\nimport { CountBadge } from '../ui/count-badge'\nimport { UNKNOWN_AUTHOR } from './comment-render'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { useCommentingOptions } from './options'\nimport { pinStackKey } from './pin-stacking'\nimport { openStackId, openThreadId } from './state'\nimport { ThreadPreview, useMarkerPreview } from './thread-preview'\nimport { anchorPagePoint, anchorViewportPoint } from './thread-state'\nimport {\n\tPOPOVER_OFFSET,\n\tThreadPopover,\n\tThreadView,\n\ttoCardProps,\n\tuseResolveName,\n} from './thread-view'\n\n/**\n * The pin for threads whose anchors resolve to the same page point \u2014 pins zooming can never\n * separate, so they share one count badge. Clicking it lists each thread as a card; clicking a card\n * expands that thread in place, via the single open-thread state.\n */\nexport const ThreadStackPin = memo(function ThreadStackPin({\n\teditor,\n\tthreads,\n\t...props\n}: CommentingContext & {\n\teditor: Editor\n\t/** The stack's threads, oldest first. All resolve to the same anchor point. */\n\tthreads: readonly TLCommentThread[]\n}) {\n\tconst msg = useTranslation()\n\tconst badgeRef = useRef<HTMLButtonElement>(null)\n\t// The badge takes pointer events (to open on click), so wheel input over it would otherwise be\n\t// swallowed instead of zooming \u2014 pass it through to the canvas, as the pin and cluster badge do.\n\tusePassThroughWheelEvents(badgeRef)\n\t// Hovering the badge previews its threads; clicking still opens them as the interactive list.\n\t// The preview is what makes the badge legible before you commit to opening it.\n\tconst { previewShown, previewHandlers } = useMarkerPreview(editor, `stack:${threads[0].id}`)\n\t// The list stays open while a member thread is expanded, so Escape steps back: expanded thread ->\n\t// card list -> closed. Held in editor state because this pin remounts as its owning render path\n\t// changes, and keyed by the coincident page point so the open state survives losing a member.\n\tconst stackId = useValue(\n\t\t'stack id',\n\t\t() => {\n\t\t\tconst pagePoint = anchorPagePoint(editor, threads[0].anchor)\n\t\t\treturn pagePoint ? pinStackKey(pagePoint) : threads[0].id\n\t\t},\n\t\t[editor, threads]\n\t)\n\tconst listOpen = useValue('stack list open', () => openStackId.get(editor) === stackId, [\n\t\teditor,\n\t\tstackId,\n\t])\n\tconst openId = useValue('open thread id', () => openThreadId.get(editor), [editor])\n\tconst openMember = threads.find((thread) => thread.id === openId)\n\tconst open = listOpen || openMember !== undefined\n\n\tconst point = useValue(\n\t\t'stack point',\n\t\t() => {\n\t\t\tconst first = threads[0]\n\t\t\tif (first.pageId !== editor.getCurrentPageId()) return null\n\t\t\t// The badge hangs off its anchor point bottom-left like a pin, and applies the same imprecise-shape\n\t\t\t// inset the pins it stands in for would (see ThreadPin) \u2014 otherwise the marker would snap from tucked\n\t\t\t// inside the shape to the raw corner the moment a second comment turns a pin into a stack.\n\t\t\treturn anchorViewportPoint(editor, first.anchor)\n\t\t},\n\t\t[editor, threads]\n\t)\n\n\t// Clicking outside the popover (and off the badge) closes the whole stack \u2014 mirrors the\n\t// single pin's dismiss. Capture phase + class checks, since the popover portals elsewhere.\n\tuseEffect(() => {\n\t\tif (!open) return\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!target) return\n\t\t\tif (target.closest('.tlui-cmt-canvas-popover')) return\n\t\t\tconst badge = badgeRef.current\n\t\t\tif (badge && badge.contains(target)) return\n\t\t\t// A click inside a menu/popover layered above us belongs to that layer; defer to its\n\t\t\t// own dismissal instead of closing the stack out from under it.\n\t\t\tif (\n\t\t\t\ttarget.closest('.tlui-menu, [data-radix-popper-content-wrapper], .tlui-cmt-mention-popup')\n\t\t\t)\n\t\t\t\treturn\n\t\t\topenStackId.set(editor, null)\n\t\t\topenThreadId.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [open, editor])\n\n\t// Escape with only the card list showing closes it. When a member thread is expanded, the\n\t// layer's Escape handler collapses that first and marks the event consumed \u2014 stepping back\n\t// to the list rather than closing everything at once.\n\tuseEffect(() => {\n\t\tif (!listOpen) return\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Escape' || e.defaultPrevented) return\n\t\t\topenStackId.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('keydown', onKeyDown, true)\n\t\treturn () => document.removeEventListener('keydown', onKeyDown, true)\n\t}, [listOpen, editor])\n\n\tif (!point) return null\n\n\tconst toggle = () => {\n\t\tif (open) {\n\t\t\topenStackId.set(editor, null)\n\t\t\topenThreadId.set(editor, null)\n\t\t} else {\n\t\t\topenStackId.set(editor, stackId)\n\t\t}\n\t}\n\n\t// The expanded thread gains a header that pushes its first comment down from where the hover preview\n\t// showed it. When that thread is the list's first, lift the whole list so its \"You\" holds position\n\t// across hover -> open. Only the first entry \u2014 lifting can't also hold a lower thread's neighbours.\n\tconst liftForHeader = threads[0].id === openId ? 42 : 0\n\n\treturn (\n\t\t<>\n\t\t\t<div className=\"tlui-cmt-canvas-pin\" style={{ left: point.x, top: point.y }}>\n\t\t\t\t<button\n\t\t\t\t\tref={badgeRef}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-canvas-stack-badge\"\n\t\t\t\t\taria-label={msg('comments.stack-label').replace('{count}', String(threads.length))}\n\t\t\t\t\taria-expanded={open}\n\t\t\t\t\tonPointerDown={(e) => e.stopPropagation()}\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\ttoggle()\n\t\t\t\t\t}}\n\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\tonFocus={previewHandlers.onPointerEnter}\n\t\t\t\t\tonBlur={previewHandlers.onPointerLeave}\n\t\t\t\t>\n\t\t\t\t\t<CountBadge count={threads.length} open={open} />\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{previewShown && !open && (\n\t\t\t\t<ThreadPreview\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tthreads={threads}\n\t\t\t\t\t// Lines the preview up with the stack list itself, so opening it leaves the cards\n\t\t\t\t\t// exactly where the preview had them.\n\t\t\t\t\tvariant=\"list\"\n\t\t\t\t\tpoint={point}\n\t\t\t\t\t// Picking a card from the preview lands in the same place clicking the badge and\n\t\t\t\t\t// then the card would: the list open, that thread expanded within it.\n\t\t\t\t\tonSelectThread={(thread) => {\n\t\t\t\t\t\topenStackId.set(editor, stackId)\n\t\t\t\t\t\topenThreadId.set(editor, thread.id)\n\t\t\t\t\t}}\n\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\tcurrentUserId={props.currentUserId}\n\t\t\t\t\tresolveAuthor={props.resolveAuthor}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t{open && (\n\t\t\t\t<ThreadPopover\n\t\t\t\t\tbase={{\n\t\t\t\t\t\tx: point.x + POPOVER_OFFSET.list.x,\n\t\t\t\t\t\ty: point.y + POPOVER_OFFSET.list.y - liftForHeader,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"tlui-cmt-stack-list\">\n\t\t\t\t\t\t{threads.map((thread) =>\n\t\t\t\t\t\t\tthread.id === openId ? (\n\t\t\t\t\t\t\t\t<div key={thread.id} className=\"tlui-cmt-stack-list__thread\">\n\t\t\t\t\t\t\t\t\t<ThreadView editor={editor} thread={thread} {...props} />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<StackThreadCard\n\t\t\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\t\t\tonOpen={() => openThreadId.set(editor, thread.id)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</ThreadPopover>\n\t\t\t)}\n\t\t</>\n\t)\n})\n\n/** A collapsed stack entry: the thread's first comment as a card; clicking expands the thread. */\nfunction StackThreadCard({\n\teditor,\n\tthread,\n\tonOpen,\n\t...props\n}: CommentingContext & { editor: Editor; thread: TLCommentThread; onOpen(): void }) {\n\tconst msg = useTranslation()\n\tconst options = useCommentingOptions()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst resolveName = useResolveName(props.resolveAuthor)\n\tconst first = comments[0]\n\tif (!first) return null\n\tconst open = (e: ReactMouseEvent) => {\n\t\te.stopPropagation()\n\t\tonOpen()\n\t}\n\treturn (\n\t\t<div className=\"tlui-cmt-stack-list__card\" onClick={open}>\n\t\t\t{/* The card's keyboard affordance. A button *wrapping* the card would put the comment\n\t\t\t body inside it, and a body renders its links as real anchors \u2014 interactive content\n\t\t\t nested in a button, which is an invalid content model and reads to assistive tech as a\n\t\t\t broken control. So the button covers the card as a sibling instead, leaving the body's\n\t\t\t own links above it and still reachable. */}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-stack-list__card-action\"\n\t\t\t\taria-label={msg(\n\t\t\t\t\tthread.resolved ? 'comments.pin-label-resolved' : 'comments.pin-label'\n\t\t\t\t).replace('{name}', props.resolveAuthor(thread.createdBy)?.name ?? UNKNOWN_AUTHOR)}\n\t\t\t\tonClick={open}\n\t\t\t/>\n\t\t\t<CommentCard {...toCardProps(first, props, options.components, resolveName)} />\n\t\t</div>\n\t)\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["import { type MouseEvent as ReactMouseEvent, memo, useEffect, useRef } from 'react'\nimport {\n\tdefineMessages,\n\tEditor,\n\tTLCommentThread,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentCard } from '../ui/comment-card'\nimport { CountBadge } from '../ui/count-badge'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { useCommentingOptions } from './options'\nimport { pinStackKey } from './pin-stacking'\nimport { openStackId, openThreadId } from './state'\nimport { ThreadPreview, useMarkerPreview } from './thread-preview'\nimport { anchorPagePoint, anchorViewportPoint } from './thread-state'\nimport {\n\tPOPOVER_OFFSET,\n\tThreadPopover,\n\tThreadView,\n\ttoCardProps,\n\tuseResolveName,\n} from './thread-view'\n\nconst messages = defineMessages({\n\tcommentsUnknownAuthor: { id: 'comments.unknown-author', defaultMessage: 'Someone' },\n\tcommentsPinLabel: { id: 'comments.pin-label', defaultMessage: 'Comment by {name}' },\n\tcommentsPinLabelResolved: {\n\t\tid: 'comments.pin-label-resolved',\n\t\tdefaultMessage: 'Resolved comment by {name}',\n\t},\n\tcommentsStackLabel: {\n\t\tid: 'comments.stack-label',\n\t\tdefaultMessage: '{count, plural, one {# comment at this spot} other {# comments at this spot}}',\n\t},\n})\n\n/**\n * The pin for threads whose anchors resolve to the same page point \u2014 pins zooming can never\n * separate, so they share one count badge. Clicking it lists each thread as a card; clicking a card\n * expands that thread in place, via the single open-thread state.\n */\nexport const ThreadStackPin = memo(function ThreadStackPin({\n\teditor,\n\tthreads,\n\t...props\n}: CommentingContext & {\n\teditor: Editor\n\t/** The stack's threads, oldest first. All resolve to the same anchor point. */\n\tthreads: readonly TLCommentThread[]\n}) {\n\tconst msg = useTranslation()\n\tconst badgeRef = useRef<HTMLButtonElement>(null)\n\t// The badge takes pointer events (to open on click), so wheel input over it would otherwise be\n\t// swallowed instead of zooming \u2014 pass it through to the canvas, as the pin and cluster badge do.\n\tusePassThroughWheelEvents(badgeRef)\n\t// Hovering the badge previews its threads; clicking still opens them as the interactive list.\n\t// The preview is what makes the badge legible before you commit to opening it.\n\tconst { previewShown, previewHandlers } = useMarkerPreview(editor, `stack:${threads[0].id}`)\n\t// The list stays open while a member thread is expanded, so Escape steps back: expanded thread ->\n\t// card list -> closed. Held in editor state because this pin remounts as its owning render path\n\t// changes, and keyed by the coincident page point so the open state survives losing a member.\n\tconst stackId = useValue(\n\t\t'stack id',\n\t\t() => {\n\t\t\tconst pagePoint = anchorPagePoint(editor, threads[0].anchor)\n\t\t\treturn pagePoint ? pinStackKey(pagePoint) : threads[0].id\n\t\t},\n\t\t[editor, threads]\n\t)\n\tconst listOpen = useValue('stack list open', () => openStackId.get(editor) === stackId, [\n\t\teditor,\n\t\tstackId,\n\t])\n\tconst openId = useValue('open thread id', () => openThreadId.get(editor), [editor])\n\tconst openMember = threads.find((thread) => thread.id === openId)\n\tconst open = listOpen || openMember !== undefined\n\n\tconst point = useValue(\n\t\t'stack point',\n\t\t() => {\n\t\t\tconst first = threads[0]\n\t\t\tif (first.pageId !== editor.getCurrentPageId()) return null\n\t\t\t// The badge hangs off its anchor point bottom-left like a pin, and applies the same imprecise-shape\n\t\t\t// inset the pins it stands in for would (see ThreadPin) \u2014 otherwise the marker would snap from tucked\n\t\t\t// inside the shape to the raw corner the moment a second comment turns a pin into a stack.\n\t\t\treturn anchorViewportPoint(editor, first.anchor)\n\t\t},\n\t\t[editor, threads]\n\t)\n\n\t// Clicking outside the popover (and off the badge) closes the whole stack \u2014 mirrors the\n\t// single pin's dismiss. Capture phase + class checks, since the popover portals elsewhere.\n\tuseEffect(() => {\n\t\tif (!open) return\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!target) return\n\t\t\tif (target.closest('.tlui-cmt-canvas-popover')) return\n\t\t\tconst badge = badgeRef.current\n\t\t\tif (badge && badge.contains(target)) return\n\t\t\t// A click inside a menu/popover layered above us belongs to that layer; defer to its\n\t\t\t// own dismissal instead of closing the stack out from under it.\n\t\t\tif (\n\t\t\t\ttarget.closest('.tlui-menu, [data-radix-popper-content-wrapper], .tlui-cmt-mention-popup')\n\t\t\t)\n\t\t\t\treturn\n\t\t\topenStackId.set(editor, null)\n\t\t\topenThreadId.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [open, editor])\n\n\t// Escape with only the card list showing closes it. When a member thread is expanded, the\n\t// layer's Escape handler collapses that first and marks the event consumed \u2014 stepping back\n\t// to the list rather than closing everything at once.\n\tuseEffect(() => {\n\t\tif (!listOpen) return\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Escape' || e.defaultPrevented) return\n\t\t\topenStackId.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('keydown', onKeyDown, true)\n\t\treturn () => document.removeEventListener('keydown', onKeyDown, true)\n\t}, [listOpen, editor])\n\n\tif (!point) return null\n\n\tconst toggle = () => {\n\t\tif (open) {\n\t\t\topenStackId.set(editor, null)\n\t\t\topenThreadId.set(editor, null)\n\t\t} else {\n\t\t\topenStackId.set(editor, stackId)\n\t\t}\n\t}\n\n\t// The expanded thread gains a header that pushes its first comment down from where the hover preview\n\t// showed it. When that thread is the list's first, lift the whole list so its \"You\" holds position\n\t// across hover -> open. Only the first entry \u2014 lifting can't also hold a lower thread's neighbours.\n\tconst liftForHeader = threads[0].id === openId ? 42 : 0\n\n\treturn (\n\t\t<>\n\t\t\t<div className=\"tlui-cmt-canvas-pin\" style={{ left: point.x, top: point.y }}>\n\t\t\t\t<button\n\t\t\t\t\tref={badgeRef}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-canvas-stack-badge\"\n\t\t\t\t\taria-label={msg(messages.commentsStackLabel, { count: threads.length })}\n\t\t\t\t\taria-expanded={open}\n\t\t\t\t\tonPointerDown={(e) => e.stopPropagation()}\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\ttoggle()\n\t\t\t\t\t}}\n\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\tonFocus={previewHandlers.onPointerEnter}\n\t\t\t\t\tonBlur={previewHandlers.onPointerLeave}\n\t\t\t\t>\n\t\t\t\t\t<CountBadge count={threads.length} open={open} />\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{previewShown && !open && (\n\t\t\t\t<ThreadPreview\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tthreads={threads}\n\t\t\t\t\t// Lines the preview up with the stack list itself, so opening it leaves the cards\n\t\t\t\t\t// exactly where the preview had them.\n\t\t\t\t\tvariant=\"list\"\n\t\t\t\t\tpoint={point}\n\t\t\t\t\t// Picking a card from the preview lands in the same place clicking the badge and\n\t\t\t\t\t// then the card would: the list open, that thread expanded within it.\n\t\t\t\t\tonSelectThread={(thread) => {\n\t\t\t\t\t\topenStackId.set(editor, stackId)\n\t\t\t\t\t\topenThreadId.set(editor, thread.id)\n\t\t\t\t\t}}\n\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\tcurrentUserId={props.currentUserId}\n\t\t\t\t\tresolveAuthor={props.resolveAuthor}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t{open && (\n\t\t\t\t<ThreadPopover\n\t\t\t\t\tbase={{\n\t\t\t\t\t\tx: point.x + POPOVER_OFFSET.list.x,\n\t\t\t\t\t\ty: point.y + POPOVER_OFFSET.list.y - liftForHeader,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"tlui-cmt-stack-list\">\n\t\t\t\t\t\t{threads.map((thread) =>\n\t\t\t\t\t\t\tthread.id === openId ? (\n\t\t\t\t\t\t\t\t<div key={thread.id} className=\"tlui-cmt-stack-list__thread\">\n\t\t\t\t\t\t\t\t\t<ThreadView editor={editor} thread={thread} {...props} />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<StackThreadCard\n\t\t\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\t\t\tonOpen={() => openThreadId.set(editor, thread.id)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</ThreadPopover>\n\t\t\t)}\n\t\t</>\n\t)\n})\n\n/** A collapsed stack entry: the thread's first comment as a card; clicking expands the thread. */\nfunction StackThreadCard({\n\teditor,\n\tthread,\n\tonOpen,\n\t...props\n}: CommentingContext & { editor: Editor; thread: TLCommentThread; onOpen(): void }) {\n\tconst msg = useTranslation()\n\tconst options = useCommentingOptions()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst resolveName = useResolveName(props.resolveAuthor)\n\tconst first = comments[0]\n\tif (!first) return null\n\tconst open = (e: ReactMouseEvent) => {\n\t\te.stopPropagation()\n\t\tonOpen()\n\t}\n\treturn (\n\t\t<div className=\"tlui-cmt-stack-list__card\" onClick={open}>\n\t\t\t{/* The card's keyboard affordance. A button *wrapping* the card would put the comment\n\t\t\t body inside it, and a body renders its links as real anchors \u2014 interactive content\n\t\t\t nested in a button, which is an invalid content model and reads to assistive tech as a\n\t\t\t broken control. So the button covers the card as a sibling instead, leaving the body's\n\t\t\t own links above it and still reachable. */}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-stack-list__card-action\"\n\t\t\t\taria-label={msg(\n\t\t\t\t\tthread.resolved ? messages.commentsPinLabelResolved : messages.commentsPinLabel,\n\t\t\t\t\t{\n\t\t\t\t\t\tname:\n\t\t\t\t\t\t\tprops.resolveAuthor(thread.createdBy)?.name ?? msg(messages.commentsUnknownAuthor),\n\t\t\t\t\t}\n\t\t\t\t)}\n\t\t\t\tonClick={open}\n\t\t\t/>\n\t\t\t<CommentCard {...toCardProps(first, props, options.components, resolveName)} />\n\t\t</div>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["ThreadStackPin"]
|
|
7
7
|
}
|
|
@@ -42,6 +42,27 @@ var import_hooks = require("./hooks");
|
|
|
42
42
|
var import_mobile_placement = require("./mobile-placement");
|
|
43
43
|
var import_options = require("./options");
|
|
44
44
|
var import_state = require("./state");
|
|
45
|
+
const messages = (0, import_tldraw.defineMessages)({
|
|
46
|
+
actionDelete: { id: "action.delete", defaultMessage: "Delete" },
|
|
47
|
+
commentsCopyLink: { id: "comments.copy-link", defaultMessage: "Copy link" },
|
|
48
|
+
commentsDelete: { id: "comments.delete", defaultMessage: "Delete thread" },
|
|
49
|
+
commentsDismiss: { id: "comments.dismiss", defaultMessage: "Dismiss" },
|
|
50
|
+
commentsEdit: { id: "comments.edit", defaultMessage: "Edit" },
|
|
51
|
+
commentsEditPlaceholder: {
|
|
52
|
+
id: "comments.edit-placeholder",
|
|
53
|
+
defaultMessage: "Edit comment\u2026"
|
|
54
|
+
},
|
|
55
|
+
commentsLinkCopied: { id: "comments.link-copied", defaultMessage: "Link copied" },
|
|
56
|
+
commentsMoreOptions: { id: "comments.more-options", defaultMessage: "More options" },
|
|
57
|
+
commentsReopen: { id: "comments.reopen", defaultMessage: "Reopen" },
|
|
58
|
+
commentsReplyPlaceholder: { id: "comments.reply-placeholder", defaultMessage: "Reply\u2026" },
|
|
59
|
+
commentsResolve: { id: "comments.resolve", defaultMessage: "Resolve" },
|
|
60
|
+
commentsResolvedBy: { id: "comments.resolved-by", defaultMessage: "Resolved by {name}" },
|
|
61
|
+
commentsSave: { id: "comments.save", defaultMessage: "Save" },
|
|
62
|
+
commentsSend: { id: "comments.send", defaultMessage: "Send" },
|
|
63
|
+
commentsThreadTitle: { id: "comments.thread-title", defaultMessage: "Comment" },
|
|
64
|
+
commentsUnknownAuthor: { id: "comments.unknown-author", defaultMessage: "Someone" }
|
|
65
|
+
});
|
|
45
66
|
const stop = (e) => e.stopPropagation();
|
|
46
67
|
function MoreOptionsMenu({
|
|
47
68
|
id,
|
|
@@ -54,11 +75,11 @@ function MoreOptionsMenu({
|
|
|
54
75
|
import_tldraw.TldrawUiButton,
|
|
55
76
|
{
|
|
56
77
|
type: "icon",
|
|
57
|
-
tooltip: msg(
|
|
58
|
-
title: msg(
|
|
78
|
+
tooltip: msg(messages.commentsMoreOptions),
|
|
79
|
+
title: msg(messages.commentsMoreOptions),
|
|
59
80
|
className: "tlui-cmt-thread__action",
|
|
60
81
|
"data-cmt-more-for": moreFor,
|
|
61
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "dots-vertical", label: msg(
|
|
82
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "dots-vertical", label: msg(messages.commentsMoreOptions), small: true })
|
|
62
83
|
}
|
|
63
84
|
) }),
|
|
64
85
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -311,11 +332,11 @@ const ThreadView = (0, import_react.memo)(function ThreadView2({
|
|
|
311
332
|
import_comment_composer.CommentComposer,
|
|
312
333
|
{
|
|
313
334
|
author: card.author,
|
|
314
|
-
placeholder: msg(
|
|
335
|
+
placeholder: msg(messages.commentsEditPlaceholder),
|
|
315
336
|
value: editText,
|
|
316
337
|
onChange: setEditText,
|
|
317
338
|
onSubmit: saveEdit,
|
|
318
|
-
sendLabel: msg(
|
|
339
|
+
sendLabel: msg(messages.commentsSave),
|
|
319
340
|
disabled: (0, import_comment_extensions.isCommentEmpty)(editText),
|
|
320
341
|
getMentionSuggestions,
|
|
321
342
|
renderMentionSuggestion,
|
|
@@ -342,14 +363,14 @@ const ThreadView = (0, import_react.memo)(function ThreadView2({
|
|
|
342
363
|
permissions?.edit && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
343
364
|
MenuItem,
|
|
344
365
|
{
|
|
345
|
-
label: msg(
|
|
366
|
+
label: msg(messages.commentsEdit),
|
|
346
367
|
onClick: () => startEdit(comment, { fromMoreMenu: true })
|
|
347
368
|
}
|
|
348
369
|
),
|
|
349
370
|
permissions?.delete && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
350
371
|
MenuItem,
|
|
351
372
|
{
|
|
352
|
-
label: msg(
|
|
373
|
+
label: msg(messages.actionDelete),
|
|
353
374
|
danger: true,
|
|
354
375
|
onClick: () => (0, import_comment_mutations.deleteComment)(editor, comment)
|
|
355
376
|
}
|
|
@@ -360,19 +381,19 @@ const ThreadView = (0, import_react.memo)(function ThreadView2({
|
|
|
360
381
|
}
|
|
361
382
|
);
|
|
362
383
|
};
|
|
363
|
-
const resolveLabel = msg(thread.resolved ?
|
|
384
|
+
const resolveLabel = msg(thread.resolved ? messages.commentsReopen : messages.commentsResolve);
|
|
364
385
|
const headerActions = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
365
386
|
ThreadActions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThreadActions, { thread, comments }),
|
|
366
387
|
(threadHref !== void 0 || canDeleteThread) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MoreOptionsMenu, { id: `comment-thread-actions-${thread.id}`, children: [
|
|
367
388
|
threadHref !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
368
389
|
MenuItem,
|
|
369
390
|
{
|
|
370
|
-
label: msg(linkCopied ?
|
|
391
|
+
label: msg(linkCopied ? messages.commentsLinkCopied : messages.commentsCopyLink),
|
|
371
392
|
noClose: true,
|
|
372
393
|
onClick: copyThreadLink
|
|
373
394
|
}
|
|
374
395
|
),
|
|
375
|
-
canDeleteThread && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, { label: msg(
|
|
396
|
+
canDeleteThread && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, { label: msg(messages.commentsDelete), danger: true, onClick: removeThread })
|
|
376
397
|
] }),
|
|
377
398
|
canComment && currentUserId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
378
399
|
import_tldraw.TldrawUiButton,
|
|
@@ -389,29 +410,28 @@ const ThreadView = (0, import_react.memo)(function ThreadView2({
|
|
|
389
410
|
import_tldraw.TldrawUiButton,
|
|
390
411
|
{
|
|
391
412
|
type: "icon",
|
|
392
|
-
tooltip: msg(
|
|
393
|
-
title: msg(
|
|
413
|
+
tooltip: msg(messages.commentsDismiss),
|
|
414
|
+
title: msg(messages.commentsDismiss),
|
|
394
415
|
className: "tlui-cmt-thread__action",
|
|
395
416
|
onClick: () => import_state.openThreadId.set(editor, null),
|
|
396
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "cross-2", label: msg(
|
|
417
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "cross-2", label: msg(messages.commentsDismiss), small: true })
|
|
397
418
|
}
|
|
398
419
|
)
|
|
399
420
|
] });
|
|
400
421
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
401
422
|
import_comment_thread.CommentThread,
|
|
402
423
|
{
|
|
403
|
-
header: msg(
|
|
424
|
+
header: msg(messages.commentsThreadTitle),
|
|
404
425
|
headerActions,
|
|
405
426
|
renderComment,
|
|
406
427
|
comments: cards,
|
|
407
|
-
resolvedBanner: thread.resolved ? msg(
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
) : void 0,
|
|
428
|
+
resolvedBanner: thread.resolved ? msg(messages.commentsResolvedBy, {
|
|
429
|
+
name: resolveAuthor(thread.resolved.by)?.name ?? msg(messages.commentsUnknownAuthor)
|
|
430
|
+
}) : void 0,
|
|
411
431
|
composer: canReply ? {
|
|
412
432
|
author: me ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
|
|
413
|
-
placeholder: msg(
|
|
414
|
-
sendLabel: msg(
|
|
433
|
+
placeholder: msg(messages.commentsReplyPlaceholder),
|
|
434
|
+
sendLabel: msg(messages.commentsSend),
|
|
415
435
|
value: reply,
|
|
416
436
|
onChange: (value) => {
|
|
417
437
|
setReply(value);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/thread-view.tsx"],
|
|
4
|
-
"sourcesContent": ["import { memo, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tEditor,\n\tEditorPortal,\n\tTLComment,\n\tTLCommentThread,\n\tTLRichText,\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuGroup,\n\tTldrawUiDropdownMenuItem,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\tTldrawUiIcon,\n\tuseContainer,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentCard, CommentCardProps } from '../ui/comment-card'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentThread } from '../ui/comment-thread'\nimport { CommentBody } from './comment-body'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\treplyDraftSlot,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport {\n\tcommitCommentMutation,\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\treopenThread,\n\tresolveThread,\n} from './comment-mutations'\nimport { CommentReactionPicker, CommentReactions } from './comment-reactions'\nimport { UNKNOWN_AUTHOR, UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport {\n\ttype CommentingComponents,\n\tgetCanModifyComment,\n\tuseCanComment,\n\tuseCanModifyComment,\n\tuseCommentingOptions,\n} from './options'\nimport { openThreadId } from './state'\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/** The \"\u22EF\" dropdown used for both a comment's and the thread's actions. */\nfunction MoreOptionsMenu({\n\tid,\n\tmoreFor,\n\tchildren,\n}: {\n\tid: string\n\t/** The comment whose \u22EF button this is, so `startEdit` can find it again to return focus. */\n\tmoreFor?: string\n\tchildren: ReactNode\n}) {\n\tconst msg = useTranslation()\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id={id}>\n\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t<TldrawUiButton\n\t\t\t\t\ttype=\"icon\"\n\t\t\t\t\ttooltip={msg('comments.more-options')}\n\t\t\t\t\ttitle={msg('comments.more-options')}\n\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\tdata-cmt-more-for={moreFor}\n\t\t\t\t>\n\t\t\t\t\t<TldrawUiIcon icon=\"dots-vertical\" label={msg('comments.more-options')} small />\n\t\t\t\t</TldrawUiButton>\n\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t<TldrawUiDropdownMenuContent\n\t\t\t\tclassName=\"tlui-cmt-menu\"\n\t\t\t\tside=\"bottom\"\n\t\t\t\talign=\"start\"\n\t\t\t\talignOffset={0}\n\t\t\t\tsideOffset={4}\n\t\t\t>\n\t\t\t\t<TldrawUiDropdownMenuGroup>{children}</TldrawUiDropdownMenuGroup>\n\t\t\t</TldrawUiDropdownMenuContent>\n\t\t</TldrawUiDropdownMenuRoot>\n\t)\n}\n\nfunction MenuItem({\n\tlabel,\n\tdanger,\n\tnoClose,\n\tonClick,\n}: {\n\tlabel: string\n\tdanger?: boolean\n\tnoClose?: boolean\n\tonClick(): void\n}) {\n\treturn (\n\t\t<TldrawUiDropdownMenuItem noClose={noClose}>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName={danger ? 'tlui-cmt-menu-item tlui-cmt-menu-item--danger' : 'tlui-cmt-menu-item'}\n\t\t\t\tonClick={onClick}\n\t\t\t>\n\t\t\t\t<span>{label}</span>\n\t\t\t</button>\n\t\t</TldrawUiDropdownMenuItem>\n\t)\n}\n\n/**\n * A name-only view of an author resolver, for the mention/rich-text paths. Stable identity, so\n * `CommentBody`'s memoized render doesn't recompute on every render of its host.\n */\nexport function useResolveName(resolveAuthor: CommentingContext['resolveAuthor']) {\n\treturn useCallback((id: string) => resolveAuthor(id)?.name, [resolveAuthor])\n}\n\n/** How long the copy-link item reads \"Link copied\" before reverting. */\nconst LINK_COPIED_MS = 2000\n\n/**\n * What a thread's `getThreadHref` should put on the clipboard. Hosts may hand back a relative href,\n * which is meaningless once pasted elsewhere, so it's resolved against the current document first.\n * An href the URL parser can't make sense of is copied as-is rather than dropped.\n *\n * @internal\n */\nexport function absoluteThreadLink(href: string, base = window.location.href): string {\n\ttry {\n\t\treturn new URL(href, base).toString()\n\t} catch {\n\t\treturn href\n\t}\n}\n\n/**\n * Copy a thread's link, with a flag that stays set briefly afterwards so the control can confirm.\n * Only set once the write resolves, so a withheld clipboard doesn't claim a copy that didn't happen.\n */\nfunction useCopyLink(href: string | undefined) {\n\tconst [copied, setCopied] = useState(false)\n\tuseEffect(() => {\n\t\tif (!copied) return\n\t\tconst timeout = window.setTimeout(() => setCopied(false), LINK_COPIED_MS)\n\t\treturn () => window.clearTimeout(timeout)\n\t}, [copied])\n\tconst copy = useCallback(() => {\n\t\tif (href === undefined) return\n\t\tnavigator.clipboard?.writeText(absoluteThreadLink(href)).then(\n\t\t\t() => setCopied(true),\n\t\t\t() => setCopied(false)\n\t\t)\n\t}, [href])\n\treturn [copied, copy] as const\n}\n\nexport function toCardProps(\n\tcomment: TLComment,\n\tprops: Pick<CommentingContext, 'currentUserId' | 'resolveAuthor'>,\n\tcomponents: CommentingComponents,\n\tresolveName: (id: string) => string | undefined\n): CommentCardProps {\n\tconst Body = components.CommentBody\n\t// The `CommentBody` component slot overrides the built-in rich-text default (which resolves\n\t// mention ids to names).\n\tconst body = Body ? (\n\t\t<Body comment={comment} />\n\t) : (\n\t\t<CommentBody richText={comment.body} resolveName={resolveName} />\n\t)\n\treturn {\n\t\tauthor: props.resolveAuthor(comment.authorId) ?? UNKNOWN_COMMENT_AUTHOR,\n\t\tbody,\n\t\tdate: new Date(comment.createdAt).toISOString(),\n\t\tyou: comment.authorId === props.currentUserId,\n\t\tedited: comment.editedAt != null,\n\t}\n}\n\n/** A pin is this square (mirrors `--tlui-cmt-pin-size`). The two marker kinds differ in size *and*\n * anchor point, so lining their previews up means correcting for both. Keep in sync with the CSS. */\nconst PIN_SIZE = 28\n\n/** A cluster/stack badge is this square (`--tlui-cmt-marker-size`) \u2014 pin-sized, so the marker\n * kinds read as one family. Keep in sync with the stylesheet. */\nconst MARKER_SIZE = PIN_SIZE\n\n/** Where a popover's top card sits vertically, measured from the marker visual's middle. */\nconst CARD_TOP_Y = -28\n\n/** The horizontal gap between a marker visual's right edge and its popover/preview panel. The\n * preview's hover bridge spans exactly this (see `--tlui-cmt-preview-bridge`). */\nconst PREVIEW_GAP = 6\n\n/**\n * Where a marker's popover sits relative to the marker's anchor point. The hover preview uses the\n * same origins, so moving a popover here moves its preview with it.\n *\n * Both marker kinds hang off their point the same way (`translate(0, -100%)`), differing only in\n * size \u2014 so each offset clears its own width plus the shared gap, and re-bases the shared card-top\n * measure from its bottom anchor to its middle.\n */\nexport const POPOVER_OFFSET = {\n\tthread: { x: PIN_SIZE + PREVIEW_GAP, y: CARD_TOP_Y - PIN_SIZE / 2 },\n\tlist: { x: MARKER_SIZE + PREVIEW_GAP, y: CARD_TOP_Y - MARKER_SIZE / 2 },\n} as const\n\n/** The open thread's popover container, portaled above the UI panels. A wheel over it passes\n * through to the canvas (unless it scrolls its own content), like tldraw's panels. */\nexport function ThreadPopover({\n\tbase,\n\tchildren,\n}: {\n\tbase: { x: number; y: number }\n\tchildren: ReactNode\n}) {\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\t// On mobile the popover slides off its fixed offset to stay above the software keyboard and\n\t// on-screen; desktop keeps the fixed offset (base returned unchanged).\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, base, isMobile)\n\treturn (\n\t\t<EditorPortal>\n\t\t\t{/* contextmenu also stops here: portals bubble React events to the canvas's context-menu\n\t\t\t trigger (the layer mounts inside it), which would open the canvas menu over this panel. */}\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName=\"tlui-cmt-canvas-popover\"\n\t\t\t\tstyle={{ left: placed.left, top: placed.top }}\n\t\t\t\tonPointerDown={stop}\n\t\t\t\tonContextMenu={stop}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n\n/**\n * One thread's interactive view: its comments, the reply composer, edit-in-place on your own\n * comments, and the resolve/delete actions. Read receipts are reported for every unread comment\n * while mounted, so only mount it where the thread is actually being shown.\n *\n * Memoized because the popover position rides the pin's per-frame render point, so the pin\n * re-renders on every camera frame while a thread is open.\n */\nexport const ThreadView = memo(function ThreadView({\n\teditor,\n\tthread,\n\t...props\n}: CommentingContext & { editor: Editor; thread: TLCommentThread }) {\n\tconst {\n\t\tcurrentUserId,\n\t\tresolveAuthor,\n\t\tonPostComment,\n\t\tisCommentUnread,\n\t\tonCommentsRead,\n\t\tgetMentionSuggestions,\n\t\trenderMentionSuggestion,\n\t\tgetThreadHref,\n\t} = props\n\tconst options = useCommentingOptions()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst msg = useTranslation()\n\tconst resolveName = useResolveName(resolveAuthor)\n\t// Rebuilt only when the comments change, not on every render \u2014 each of which would otherwise\n\t// re-allocate a date string and body element per comment.\n\tconst cards = useMemo(\n\t\t() =>\n\t\t\tcomments.map((c) =>\n\t\t\t\ttoCardProps(c, { currentUserId, resolveAuthor }, options.components, resolveName)\n\t\t\t),\n\t\t[comments, currentUserId, resolveAuthor, options.components, resolveName]\n\t)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// Composing, editing, deleting, and resolving are all gated on the viewer's permission. Where it's\n\t// withheld the composer gives way to the ComposerFallback slot and the affordances are hidden.\n\tconst canComment = useCanComment(currentUserId)\n\t// Editing and deleting are further per-record: the author's to do by default, wider or narrower\n\t// under a `canModifyComment` callback. Computed for the thread's comments in one pass, since\n\t// `renderComment` is a callback rather than a component and can't call a hook per comment.\n\tconst commentPermissions = useValue(\n\t\t'comment permissions',\n\t\t() =>\n\t\t\tnew Map(\n\t\t\t\tcomments.map((comment) => [\n\t\t\t\t\tcomment.id,\n\t\t\t\t\t{\n\t\t\t\t\t\tedit: getCanModifyComment(editor, currentUserId, { action: 'edit-comment', comment }),\n\t\t\t\t\t\tdelete: getCanModifyComment(editor, currentUserId, {\n\t\t\t\t\t\t\taction: 'delete-comment',\n\t\t\t\t\t\t\tcomment,\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t])\n\t\t\t),\n\t\t[editor, currentUserId, comments]\n\t)\n\t// Deleting a thread is its creator's by default (and server-enforced); `canModifyComment` is what\n\t// widens that. Still a commenting write, so `canComment` stays the outer gate.\n\tconst canModifyThread = useCanModifyComment(currentUserId, { action: 'delete-thread', thread })\n\tconst canDeleteThread = canComment && canModifyThread\n\tconst ComposerFallback = options.components.ComposerFallback\n\t// An unsent reply survives closing the thread (saved on every change, keyed by thread id) \u2014\n\t// the flip side of dismissing without a discard warning.\n\tconst [reply, setReply] = useState<TLRichText>(\n\t\t() => getCommentDraft(replyDraftSlot(thread.id)) ?? EMPTY_COMMENT\n\t)\n\tconst [editingId, setEditingId] = useState<string | null>(null)\n\tconst [editText, setEditText] = useState<TLRichText>(EMPTY_COMMENT)\n\tconst canReply = canComment && !thread.resolved\n\tconst container = useContainer()\n\t// Resolved when the composer closes rather than held as an element: the card unmounts while the\n\t// composer stands in its place, so the node captured on the way in is gone by the way out.\n\tconst editReturnFocus = useRef<(() => HTMLElement | null) | null>(null)\n\n\t// Tab from the canvas drops the caret in the reply box: a pin click leaves focus on the editor\n\t// container, so the first Tab would otherwise walk the app's UI. Capture phase, once per open thread.\n\tconst [focusReply, setFocusReply] = useState(false)\n\tconst tabTaken = useRef(false)\n\tconst swallowTabUp = useRef(false)\n\tuseEffect(() => {\n\t\tif (!canReply) {\n\t\t\t// Resolving unmounts the reply box under an open thread. Reopening it should feel like a\n\t\t\t// freshly opened thread: no autoFocus left armed from the last Tab, and Tab available again.\n\t\t\tsetFocusReply(false)\n\t\t\ttabTaken.current = false\n\t\t\treturn\n\t\t}\n\t\tconst doc = container.ownerDocument\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Tab' || e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return\n\t\t\tif (e.defaultPrevented || tabTaken.current) return\n\t\t\t// Focus rests on the container (or nothing) after a pin click. Anywhere else is deliberate, and\n\t\t\t// Tab belongs to whatever holds it.\n\t\t\tif (e.target !== container && e.target !== doc.body) return\n\t\t\ttabTaken.current = true\n\t\t\tswallowTabUp.current = true\n\t\t\tsetFocusReply(true)\n\t\t\te.preventDefault()\n\t\t\te.stopPropagation()\n\t\t}\n\t\t// The select tool navigates shapes on Tab's *keyup*, so a quick tap would both focus the reply and\n\t\t// step the selection. Swallow the release of the press we took, and only that one.\n\t\tconst onKeyUp = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Tab' || !swallowTabUp.current) return\n\t\t\tswallowTabUp.current = false\n\t\t\te.preventDefault()\n\t\t\te.stopPropagation()\n\t\t}\n\t\tdoc.addEventListener('keydown', onKeyDown, true)\n\t\tdoc.addEventListener('keyup', onKeyUp, true)\n\t\treturn () => {\n\t\t\tdoc.removeEventListener('keydown', onKeyDown, true)\n\t\t\tdoc.removeEventListener('keyup', onKeyUp, true)\n\t\t}\n\t}, [canReply, container])\n\n\t// Leaving the edit composer unmounts it while it holds focus, dropping focus to the editor\n\t// container \u2014 outside the thread, so a Tab from there walks the app's UI. Hand focus back to\n\t// whatever opened the composer instead.\n\tuseEffect(() => {\n\t\tif (editingId !== null) return\n\t\tconst resolve = editReturnFocus.current\n\t\teditReturnFocus.current = null\n\t\t// Runs after React has swapped the card back in, so the target is mounted again by now.\n\t\tresolve?.()?.focus()\n\t}, [editingId])\n\n\t// Reported as one batch so the host can record the receipts in a single write. The write flips\n\t// isCommentUnread to false, so re-runs find nothing to report.\n\tuseEffect(() => {\n\t\tif (!isCommentUnread || !onCommentsRead) return\n\t\tconst unreadIds = comments.filter((comment) => isCommentUnread(comment.id)).map((c) => c.id)\n\t\tif (unreadIds.length > 0) {\n\t\t\tonCommentsRead(unreadIds)\n\t\t}\n\t}, [comments, isCommentUnread, onCommentsRead])\n\n\tconst postReply = () => {\n\t\tif (isCommentEmpty(reply) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId: thread.pageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: reply,\n\t\t\t})\n\t\t\tput([comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetReply(EMPTY_COMMENT)\n\t\tclearCommentDraft(replyDraftSlot(thread.id))\n\t\t// The host's callback is its own operation, not part of the post's history scope. It runs\n\t\t// last so a throwing host can't strand the composer holding a draft of a posted reply.\n\t\tonPostComment?.(comment)\n\t}\n\n\tconst toggleResolve = () => {\n\t\tif (!currentUserId) return\n\t\tif (thread.resolved) reopenThread(editor, thread)\n\t\telse resolveThread(editor, thread, currentUserId)\n\t}\n\n\t// No `currentUserId` check: unlike a resolve, a delete stamps nothing on the record, so who may\n\t// make it is `canModifyComment`'s call alone (which withholds it from an unidentified viewer by\n\t// default).\n\tconst removeThread = () => deleteThread(editor, thread)\n\n\tconst ThreadActions = options.components.ThreadActions\n\t// The host's URL for this thread. Its presence is what puts \"copy link\" in the header menu.\n\tconst threadHref = getThreadHref?.(thread.id)\n\tconst [linkCopied, copyThreadLink] = useCopyLink(threadHref)\n\n\tconst startEdit = (comment: TLComment, { fromMoreMenu = false } = {}) => {\n\t\t// Edit from the \u22EF menu comes back to that button, looked up again on the way out since the card\n\t\t// unmounts. Otherwise return to whatever held focus, ignoring the body and the editor container.\n\t\tconst active = container.ownerDocument.activeElement\n\t\teditReturnFocus.current = fromMoreMenu\n\t\t\t? () => container.querySelector<HTMLElement>(`[data-cmt-more-for=\"${comment.id}\"]`)\n\t\t\t: active instanceof HTMLElement && active !== container && active !== document.body\n\t\t\t\t? () => (active.isConnected ? active : null)\n\t\t\t\t: null\n\t\tsetEditingId(comment.id)\n\t\tsetEditText(comment.body)\n\t}\n\n\tconst saveEdit = () => {\n\t\tconst comment = comments.find((c) => c.id === editingId)\n\t\tif (!comment || isCommentEmpty(editText)) return\n\t\teditComment(editor, comment, editText)\n\t\tsetEditingId(null)\n\t}\n\n\t// Swap a comment for a pre-filled composer while it's being edited; otherwise show the card,\n\t// with the affordances the viewer is allowed on it.\n\tconst renderComment = (card: CommentCardProps, index: number): ReactNode => {\n\t\tconst comment = comments[index]\n\t\tconst permissions = commentPermissions.get(comment.id)\n\t\tif (editingId === comment.id) {\n\t\t\treturn (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"tlui-cmt-editing\"\n\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\tif (e.key === 'Escape') {\n\t\t\t\t\t\t\tsetEditingId(null)\n\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={card.author}\n\t\t\t\t\t\tplaceholder={msg('comments.edit-placeholder')}\n\t\t\t\t\t\tvalue={editText}\n\t\t\t\t\t\tonChange={setEditText}\n\t\t\t\t\t\tonSubmit={saveEdit}\n\t\t\t\t\t\tsendLabel={msg('comments.save')}\n\t\t\t\t\t\tdisabled={isCommentEmpty(editText)}\n\t\t\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\t\t\tautoFocus\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)\n\t\t}\n\t\treturn (\n\t\t\t<CommentCard\n\t\t\t\t{...card}\n\t\t\t\tfooter={\n\t\t\t\t\t<CommentReactions\n\t\t\t\t\t\tcomment={comment}\n\t\t\t\t\t\t// Reacting is a commenting write: without `canComment` the tally renders\n\t\t\t\t\t\t// read-only (no identity \u2192 pills aren't clickable).\n\t\t\t\t\t\tcurrentUserId={canComment ? currentUserId : null}\n\t\t\t\t\t\tresolveName={resolveName}\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\tactions={\n\t\t\t\t\tcanComment && (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{(permissions?.edit || permissions?.delete) && (\n\t\t\t\t\t\t\t\t<MoreOptionsMenu id={`comment-actions-${comment.id}`} moreFor={comment.id}>\n\t\t\t\t\t\t\t\t\t{permissions?.edit && (\n\t\t\t\t\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\t\t\t\t\tlabel={msg('comments.edit')}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => startEdit(comment, { fromMoreMenu: true })}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t{permissions?.delete && (\n\t\t\t\t\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\t\t\t\t\tlabel={msg('action.delete')}\n\t\t\t\t\t\t\t\t\t\t\tdanger\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => deleteComment(editor, comment)}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</MoreOptionsMenu>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{/* Last so react is always the rightmost pill. */}\n\t\t\t\t\t\t\t<CommentReactionPicker comment={comment} currentUserId={currentUserId} />\n\t\t\t\t\t\t</>\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t/>\n\t\t)\n\t}\n\n\t// Resolve and delete are commenting writes: behind `canComment`, plus the `currentUserId` a\n\t// resolve stamps into `resolved.by`.\n\tconst resolveLabel = msg(thread.resolved ? 'comments.reopen' : 'comments.resolve')\n\tconst headerActions = (\n\t\t<>\n\t\t\t{/* Host verbs \u2014 assign, link a ticket \u2014 sit ahead of the built-in actions. */}\n\t\t\t{ThreadActions && <ThreadActions thread={thread} comments={comments} />}\n\t\t\t{(threadHref !== undefined || canDeleteThread) && (\n\t\t\t\t<MoreOptionsMenu id={`comment-thread-actions-${thread.id}`}>\n\t\t\t\t\t{/* A link is a read affordance: offered to anyone who can see the thread,\n\t\t\t\t\t including a viewer who can't comment. `noClose` keeps the menu up so the\n\t\t\t\t\t item can confirm the copy in place. */}\n\t\t\t\t\t{threadHref !== undefined && (\n\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\tlabel={msg(linkCopied ? 'comments.link-copied' : 'comments.copy-link')}\n\t\t\t\t\t\t\tnoClose\n\t\t\t\t\t\t\tonClick={copyThreadLink}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t\t{canDeleteThread && (\n\t\t\t\t\t\t<MenuItem label={msg('comments.delete')} danger onClick={removeThread} />\n\t\t\t\t\t)}\n\t\t\t\t</MoreOptionsMenu>\n\t\t\t)}\n\t\t\t{canComment && currentUserId && (\n\t\t\t\t<TldrawUiButton\n\t\t\t\t\ttype=\"icon\"\n\t\t\t\t\ttooltip={resolveLabel}\n\t\t\t\t\ttitle={resolveLabel}\n\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\tonClick={toggleResolve}\n\t\t\t\t>\n\t\t\t\t\t<TldrawUiIcon icon=\"check\" label={resolveLabel} small />\n\t\t\t\t</TldrawUiButton>\n\t\t\t)}\n\t\t\t<TldrawUiButton\n\t\t\t\ttype=\"icon\"\n\t\t\t\ttooltip={msg('comments.dismiss')}\n\t\t\t\ttitle={msg('comments.dismiss')}\n\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\tonClick={() => openThreadId.set(editor, null)}\n\t\t\t>\n\t\t\t\t<TldrawUiIcon icon=\"cross-2\" label={msg('comments.dismiss')} small />\n\t\t\t</TldrawUiButton>\n\t\t</>\n\t)\n\n\treturn (\n\t\t<CommentThread\n\t\t\theader={msg('comments.thread-title')}\n\t\t\theaderActions={headerActions}\n\t\t\trenderComment={renderComment}\n\t\t\tcomments={cards}\n\t\t\tresolvedBanner={\n\t\t\t\tthread.resolved\n\t\t\t\t\t? msg('comments.resolved-by').replace(\n\t\t\t\t\t\t\t'{name}',\n\t\t\t\t\t\t\tresolveAuthor(thread.resolved.by)?.name ?? UNKNOWN_AUTHOR\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined\n\t\t\t}\n\t\t\tcomposer={\n\t\t\t\tcanReply\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tauthor: me ?? UNKNOWN_COMMENT_AUTHOR,\n\t\t\t\t\t\t\tplaceholder: msg('comments.reply-placeholder'),\n\t\t\t\t\t\t\tsendLabel: msg('comments.send'),\n\t\t\t\t\t\t\tvalue: reply,\n\t\t\t\t\t\t\tonChange: (value: TLRichText) => {\n\t\t\t\t\t\t\t\tsetReply(value)\n\t\t\t\t\t\t\t\tsaveCommentDraft(replyDraftSlot(thread.id), value)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tonSubmit: postReply,\n\t\t\t\t\t\t\t// Up in the empty reply box edits the comment directly above it, chat-style \u2014\n\t\t\t\t\t\t\t// only when you're allowed to edit it (the same gate as the Edit link).\n\t\t\t\t\t\t\tonArrowUpWhenEmpty: () => {\n\t\t\t\t\t\t\t\tconst last = comments[comments.length - 1]\n\t\t\t\t\t\t\t\tif (last && commentPermissions.get(last.id)?.edit) startEdit(last)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\t\tdisabled: isCommentEmpty(reply) || !currentUserId,\n\t\t\t\t\t\t\tgetMentionSuggestions,\n\t\t\t\t\t\t\trenderMentionSuggestion,\n\t\t\t\t\t\t\t// Reuses the composer's own focus path, so the caret lands at the end of a\n\t\t\t\t\t\t\t// restored draft rather than in front of it.\n\t\t\t\t\t\t\tautoFocus: focusReply,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined\n\t\t\t}\n\t\t\tfooter={\n\t\t\t\t!canComment && !thread.resolved && ComposerFallback ? (\n\t\t\t\t\t<ComposerFallback context=\"thread\" />\n\t\t\t\t) : undefined\n\t\t\t}\n\t\t/>\n\t)\n})\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["import { memo, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tdefineMessages,\n\tEditor,\n\tEditorPortal,\n\tTLComment,\n\tTLCommentThread,\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuGroup,\n\tTldrawUiDropdownMenuItem,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\tTldrawUiIcon,\n\tTLRichText,\n\tuseContainer,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentCard, CommentCardProps } from '../ui/comment-card'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentThread } from '../ui/comment-thread'\nimport { CommentBody } from './comment-body'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\treplyDraftSlot,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport {\n\tcommitCommentMutation,\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\treopenThread,\n\tresolveThread,\n} from './comment-mutations'\nimport { CommentReactionPicker, CommentReactions } from './comment-reactions'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport {\n\ttype CommentingComponents,\n\tgetCanModifyComment,\n\tuseCanComment,\n\tuseCanModifyComment,\n\tuseCommentingOptions,\n} from './options'\nimport { openThreadId } from './state'\n\nconst messages = defineMessages({\n\tactionDelete: { id: 'action.delete', defaultMessage: 'Delete' },\n\tcommentsCopyLink: { id: 'comments.copy-link', defaultMessage: 'Copy link' },\n\tcommentsDelete: { id: 'comments.delete', defaultMessage: 'Delete thread' },\n\tcommentsDismiss: { id: 'comments.dismiss', defaultMessage: 'Dismiss' },\n\tcommentsEdit: { id: 'comments.edit', defaultMessage: 'Edit' },\n\tcommentsEditPlaceholder: {\n\t\tid: 'comments.edit-placeholder',\n\t\tdefaultMessage: 'Edit comment\u2026',\n\t},\n\tcommentsLinkCopied: { id: 'comments.link-copied', defaultMessage: 'Link copied' },\n\tcommentsMoreOptions: { id: 'comments.more-options', defaultMessage: 'More options' },\n\tcommentsReopen: { id: 'comments.reopen', defaultMessage: 'Reopen' },\n\tcommentsReplyPlaceholder: { id: 'comments.reply-placeholder', defaultMessage: 'Reply\u2026' },\n\tcommentsResolve: { id: 'comments.resolve', defaultMessage: 'Resolve' },\n\tcommentsResolvedBy: { id: 'comments.resolved-by', defaultMessage: 'Resolved by {name}' },\n\tcommentsSave: { id: 'comments.save', defaultMessage: 'Save' },\n\tcommentsSend: { id: 'comments.send', defaultMessage: 'Send' },\n\tcommentsThreadTitle: { id: 'comments.thread-title', defaultMessage: 'Comment' },\n\tcommentsUnknownAuthor: { id: 'comments.unknown-author', defaultMessage: 'Someone' },\n})\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/** The \"\u22EF\" dropdown used for both a comment's and the thread's actions. */\nfunction MoreOptionsMenu({\n\tid,\n\tmoreFor,\n\tchildren,\n}: {\n\tid: string\n\t/** The comment whose \u22EF button this is, so `startEdit` can find it again to return focus. */\n\tmoreFor?: string\n\tchildren: ReactNode\n}) {\n\tconst msg = useTranslation()\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id={id}>\n\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t<TldrawUiButton\n\t\t\t\t\ttype=\"icon\"\n\t\t\t\t\ttooltip={msg(messages.commentsMoreOptions)}\n\t\t\t\t\ttitle={msg(messages.commentsMoreOptions)}\n\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\tdata-cmt-more-for={moreFor}\n\t\t\t\t>\n\t\t\t\t\t<TldrawUiIcon icon=\"dots-vertical\" label={msg(messages.commentsMoreOptions)} small />\n\t\t\t\t</TldrawUiButton>\n\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t<TldrawUiDropdownMenuContent\n\t\t\t\tclassName=\"tlui-cmt-menu\"\n\t\t\t\tside=\"bottom\"\n\t\t\t\talign=\"start\"\n\t\t\t\talignOffset={0}\n\t\t\t\tsideOffset={4}\n\t\t\t>\n\t\t\t\t<TldrawUiDropdownMenuGroup>{children}</TldrawUiDropdownMenuGroup>\n\t\t\t</TldrawUiDropdownMenuContent>\n\t\t</TldrawUiDropdownMenuRoot>\n\t)\n}\n\nfunction MenuItem({\n\tlabel,\n\tdanger,\n\tnoClose,\n\tonClick,\n}: {\n\tlabel: string\n\tdanger?: boolean\n\tnoClose?: boolean\n\tonClick(): void\n}) {\n\treturn (\n\t\t<TldrawUiDropdownMenuItem noClose={noClose}>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName={danger ? 'tlui-cmt-menu-item tlui-cmt-menu-item--danger' : 'tlui-cmt-menu-item'}\n\t\t\t\tonClick={onClick}\n\t\t\t>\n\t\t\t\t<span>{label}</span>\n\t\t\t</button>\n\t\t</TldrawUiDropdownMenuItem>\n\t)\n}\n\n/**\n * A name-only view of an author resolver, for the mention/rich-text paths. Stable identity, so\n * `CommentBody`'s memoized render doesn't recompute on every render of its host.\n */\nexport function useResolveName(resolveAuthor: CommentingContext['resolveAuthor']) {\n\treturn useCallback((id: string) => resolveAuthor(id)?.name, [resolveAuthor])\n}\n\n/** How long the copy-link item reads \"Link copied\" before reverting. */\nconst LINK_COPIED_MS = 2000\n\n/**\n * What a thread's `getThreadHref` should put on the clipboard. Hosts may hand back a relative href,\n * which is meaningless once pasted elsewhere, so it's resolved against the current document first.\n * An href the URL parser can't make sense of is copied as-is rather than dropped.\n *\n * @internal\n */\nexport function absoluteThreadLink(href: string, base = window.location.href): string {\n\ttry {\n\t\treturn new URL(href, base).toString()\n\t} catch {\n\t\treturn href\n\t}\n}\n\n/**\n * Copy a thread's link, with a flag that stays set briefly afterwards so the control can confirm.\n * Only set once the write resolves, so a withheld clipboard doesn't claim a copy that didn't happen.\n */\nfunction useCopyLink(href: string | undefined) {\n\tconst [copied, setCopied] = useState(false)\n\tuseEffect(() => {\n\t\tif (!copied) return\n\t\tconst timeout = window.setTimeout(() => setCopied(false), LINK_COPIED_MS)\n\t\treturn () => window.clearTimeout(timeout)\n\t}, [copied])\n\tconst copy = useCallback(() => {\n\t\tif (href === undefined) return\n\t\tnavigator.clipboard?.writeText(absoluteThreadLink(href)).then(\n\t\t\t() => setCopied(true),\n\t\t\t() => setCopied(false)\n\t\t)\n\t}, [href])\n\treturn [copied, copy] as const\n}\n\nexport function toCardProps(\n\tcomment: TLComment,\n\tprops: Pick<CommentingContext, 'currentUserId' | 'resolveAuthor'>,\n\tcomponents: CommentingComponents,\n\tresolveName: (id: string) => string | undefined\n): CommentCardProps {\n\tconst Body = components.CommentBody\n\t// The `CommentBody` component slot overrides the built-in rich-text default (which resolves\n\t// mention ids to names).\n\tconst body = Body ? (\n\t\t<Body comment={comment} />\n\t) : (\n\t\t<CommentBody richText={comment.body} resolveName={resolveName} />\n\t)\n\treturn {\n\t\tauthor: props.resolveAuthor(comment.authorId) ?? UNKNOWN_COMMENT_AUTHOR,\n\t\tbody,\n\t\tdate: new Date(comment.createdAt).toISOString(),\n\t\tyou: comment.authorId === props.currentUserId,\n\t\tedited: comment.editedAt != null,\n\t}\n}\n\n/** A pin is this square (mirrors `--tlui-cmt-pin-size`). The two marker kinds differ in size *and*\n * anchor point, so lining their previews up means correcting for both. Keep in sync with the CSS. */\nconst PIN_SIZE = 28\n\n/** A cluster/stack badge is this square (`--tlui-cmt-marker-size`) \u2014 pin-sized, so the marker\n * kinds read as one family. Keep in sync with the stylesheet. */\nconst MARKER_SIZE = PIN_SIZE\n\n/** Where a popover's top card sits vertically, measured from the marker visual's middle. */\nconst CARD_TOP_Y = -28\n\n/** The horizontal gap between a marker visual's right edge and its popover/preview panel. The\n * preview's hover bridge spans exactly this (see `--tlui-cmt-preview-bridge`). */\nconst PREVIEW_GAP = 6\n\n/**\n * Where a marker's popover sits relative to the marker's anchor point. The hover preview uses the\n * same origins, so moving a popover here moves its preview with it.\n *\n * Both marker kinds hang off their point the same way (`translate(0, -100%)`), differing only in\n * size \u2014 so each offset clears its own width plus the shared gap, and re-bases the shared card-top\n * measure from its bottom anchor to its middle.\n */\nexport const POPOVER_OFFSET = {\n\tthread: { x: PIN_SIZE + PREVIEW_GAP, y: CARD_TOP_Y - PIN_SIZE / 2 },\n\tlist: { x: MARKER_SIZE + PREVIEW_GAP, y: CARD_TOP_Y - MARKER_SIZE / 2 },\n} as const\n\n/** The open thread's popover container, portaled above the UI panels. A wheel over it passes\n * through to the canvas (unless it scrolls its own content), like tldraw's panels. */\nexport function ThreadPopover({\n\tbase,\n\tchildren,\n}: {\n\tbase: { x: number; y: number }\n\tchildren: ReactNode\n}) {\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\t// On mobile the popover slides off its fixed offset to stay above the software keyboard and\n\t// on-screen; desktop keeps the fixed offset (base returned unchanged).\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, base, isMobile)\n\treturn (\n\t\t<EditorPortal>\n\t\t\t{/* contextmenu also stops here: portals bubble React events to the canvas's context-menu\n\t\t\t trigger (the layer mounts inside it), which would open the canvas menu over this panel. */}\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName=\"tlui-cmt-canvas-popover\"\n\t\t\t\tstyle={{ left: placed.left, top: placed.top }}\n\t\t\t\tonPointerDown={stop}\n\t\t\t\tonContextMenu={stop}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n\n/**\n * One thread's interactive view: its comments, the reply composer, edit-in-place on your own\n * comments, and the resolve/delete actions. Read receipts are reported for every unread comment\n * while mounted, so only mount it where the thread is actually being shown.\n *\n * Memoized because the popover position rides the pin's per-frame render point, so the pin\n * re-renders on every camera frame while a thread is open.\n */\nexport const ThreadView = memo(function ThreadView({\n\teditor,\n\tthread,\n\t...props\n}: CommentingContext & { editor: Editor; thread: TLCommentThread }) {\n\tconst {\n\t\tcurrentUserId,\n\t\tresolveAuthor,\n\t\tonPostComment,\n\t\tisCommentUnread,\n\t\tonCommentsRead,\n\t\tgetMentionSuggestions,\n\t\trenderMentionSuggestion,\n\t\tgetThreadHref,\n\t} = props\n\tconst options = useCommentingOptions()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst msg = useTranslation()\n\tconst resolveName = useResolveName(resolveAuthor)\n\t// Rebuilt only when the comments change, not on every render \u2014 each of which would otherwise\n\t// re-allocate a date string and body element per comment.\n\tconst cards = useMemo(\n\t\t() =>\n\t\t\tcomments.map((c) =>\n\t\t\t\ttoCardProps(c, { currentUserId, resolveAuthor }, options.components, resolveName)\n\t\t\t),\n\t\t[comments, currentUserId, resolveAuthor, options.components, resolveName]\n\t)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// Composing, editing, deleting, and resolving are all gated on the viewer's permission. Where it's\n\t// withheld the composer gives way to the ComposerFallback slot and the affordances are hidden.\n\tconst canComment = useCanComment(currentUserId)\n\t// Editing and deleting are further per-record: the author's to do by default, wider or narrower\n\t// under a `canModifyComment` callback. Computed for the thread's comments in one pass, since\n\t// `renderComment` is a callback rather than a component and can't call a hook per comment.\n\tconst commentPermissions = useValue(\n\t\t'comment permissions',\n\t\t() =>\n\t\t\tnew Map(\n\t\t\t\tcomments.map((comment) => [\n\t\t\t\t\tcomment.id,\n\t\t\t\t\t{\n\t\t\t\t\t\tedit: getCanModifyComment(editor, currentUserId, { action: 'edit-comment', comment }),\n\t\t\t\t\t\tdelete: getCanModifyComment(editor, currentUserId, {\n\t\t\t\t\t\t\taction: 'delete-comment',\n\t\t\t\t\t\t\tcomment,\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t])\n\t\t\t),\n\t\t[editor, currentUserId, comments]\n\t)\n\t// Deleting a thread is its creator's by default (and server-enforced); `canModifyComment` is what\n\t// widens that. Still a commenting write, so `canComment` stays the outer gate.\n\tconst canModifyThread = useCanModifyComment(currentUserId, { action: 'delete-thread', thread })\n\tconst canDeleteThread = canComment && canModifyThread\n\tconst ComposerFallback = options.components.ComposerFallback\n\t// An unsent reply survives closing the thread (saved on every change, keyed by thread id) \u2014\n\t// the flip side of dismissing without a discard warning.\n\tconst [reply, setReply] = useState<TLRichText>(\n\t\t() => getCommentDraft(replyDraftSlot(thread.id)) ?? EMPTY_COMMENT\n\t)\n\tconst [editingId, setEditingId] = useState<string | null>(null)\n\tconst [editText, setEditText] = useState<TLRichText>(EMPTY_COMMENT)\n\tconst canReply = canComment && !thread.resolved\n\tconst container = useContainer()\n\t// Resolved when the composer closes rather than held as an element: the card unmounts while the\n\t// composer stands in its place, so the node captured on the way in is gone by the way out.\n\tconst editReturnFocus = useRef<(() => HTMLElement | null) | null>(null)\n\n\t// Tab from the canvas drops the caret in the reply box: a pin click leaves focus on the editor\n\t// container, so the first Tab would otherwise walk the app's UI. Capture phase, once per open thread.\n\tconst [focusReply, setFocusReply] = useState(false)\n\tconst tabTaken = useRef(false)\n\tconst swallowTabUp = useRef(false)\n\tuseEffect(() => {\n\t\tif (!canReply) {\n\t\t\t// Resolving unmounts the reply box under an open thread. Reopening it should feel like a\n\t\t\t// freshly opened thread: no autoFocus left armed from the last Tab, and Tab available again.\n\t\t\tsetFocusReply(false)\n\t\t\ttabTaken.current = false\n\t\t\treturn\n\t\t}\n\t\tconst doc = container.ownerDocument\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Tab' || e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return\n\t\t\tif (e.defaultPrevented || tabTaken.current) return\n\t\t\t// Focus rests on the container (or nothing) after a pin click. Anywhere else is deliberate, and\n\t\t\t// Tab belongs to whatever holds it.\n\t\t\tif (e.target !== container && e.target !== doc.body) return\n\t\t\ttabTaken.current = true\n\t\t\tswallowTabUp.current = true\n\t\t\tsetFocusReply(true)\n\t\t\te.preventDefault()\n\t\t\te.stopPropagation()\n\t\t}\n\t\t// The select tool navigates shapes on Tab's *keyup*, so a quick tap would both focus the reply and\n\t\t// step the selection. Swallow the release of the press we took, and only that one.\n\t\tconst onKeyUp = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Tab' || !swallowTabUp.current) return\n\t\t\tswallowTabUp.current = false\n\t\t\te.preventDefault()\n\t\t\te.stopPropagation()\n\t\t}\n\t\tdoc.addEventListener('keydown', onKeyDown, true)\n\t\tdoc.addEventListener('keyup', onKeyUp, true)\n\t\treturn () => {\n\t\t\tdoc.removeEventListener('keydown', onKeyDown, true)\n\t\t\tdoc.removeEventListener('keyup', onKeyUp, true)\n\t\t}\n\t}, [canReply, container])\n\n\t// Leaving the edit composer unmounts it while it holds focus, dropping focus to the editor\n\t// container \u2014 outside the thread, so a Tab from there walks the app's UI. Hand focus back to\n\t// whatever opened the composer instead.\n\tuseEffect(() => {\n\t\tif (editingId !== null) return\n\t\tconst resolve = editReturnFocus.current\n\t\teditReturnFocus.current = null\n\t\t// Runs after React has swapped the card back in, so the target is mounted again by now.\n\t\tresolve?.()?.focus()\n\t}, [editingId])\n\n\t// Reported as one batch so the host can record the receipts in a single write. The write flips\n\t// isCommentUnread to false, so re-runs find nothing to report.\n\tuseEffect(() => {\n\t\tif (!isCommentUnread || !onCommentsRead) return\n\t\tconst unreadIds = comments.filter((comment) => isCommentUnread(comment.id)).map((c) => c.id)\n\t\tif (unreadIds.length > 0) {\n\t\t\tonCommentsRead(unreadIds)\n\t\t}\n\t}, [comments, isCommentUnread, onCommentsRead])\n\n\tconst postReply = () => {\n\t\tif (isCommentEmpty(reply) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId: thread.pageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: reply,\n\t\t\t})\n\t\t\tput([comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetReply(EMPTY_COMMENT)\n\t\tclearCommentDraft(replyDraftSlot(thread.id))\n\t\t// The host's callback is its own operation, not part of the post's history scope. It runs\n\t\t// last so a throwing host can't strand the composer holding a draft of a posted reply.\n\t\tonPostComment?.(comment)\n\t}\n\n\tconst toggleResolve = () => {\n\t\tif (!currentUserId) return\n\t\tif (thread.resolved) reopenThread(editor, thread)\n\t\telse resolveThread(editor, thread, currentUserId)\n\t}\n\n\t// No `currentUserId` check: unlike a resolve, a delete stamps nothing on the record, so who may\n\t// make it is `canModifyComment`'s call alone (which withholds it from an unidentified viewer by\n\t// default).\n\tconst removeThread = () => deleteThread(editor, thread)\n\n\tconst ThreadActions = options.components.ThreadActions\n\t// The host's URL for this thread. Its presence is what puts \"copy link\" in the header menu.\n\tconst threadHref = getThreadHref?.(thread.id)\n\tconst [linkCopied, copyThreadLink] = useCopyLink(threadHref)\n\n\tconst startEdit = (comment: TLComment, { fromMoreMenu = false } = {}) => {\n\t\t// Edit from the \u22EF menu comes back to that button, looked up again on the way out since the card\n\t\t// unmounts. Otherwise return to whatever held focus, ignoring the body and the editor container.\n\t\tconst active = container.ownerDocument.activeElement\n\t\teditReturnFocus.current = fromMoreMenu\n\t\t\t? () => container.querySelector<HTMLElement>(`[data-cmt-more-for=\"${comment.id}\"]`)\n\t\t\t: active instanceof HTMLElement && active !== container && active !== document.body\n\t\t\t\t? () => (active.isConnected ? active : null)\n\t\t\t\t: null\n\t\tsetEditingId(comment.id)\n\t\tsetEditText(comment.body)\n\t}\n\n\tconst saveEdit = () => {\n\t\tconst comment = comments.find((c) => c.id === editingId)\n\t\tif (!comment || isCommentEmpty(editText)) return\n\t\teditComment(editor, comment, editText)\n\t\tsetEditingId(null)\n\t}\n\n\t// Swap a comment for a pre-filled composer while it's being edited; otherwise show the card,\n\t// with the affordances the viewer is allowed on it.\n\tconst renderComment = (card: CommentCardProps, index: number): ReactNode => {\n\t\tconst comment = comments[index]\n\t\tconst permissions = commentPermissions.get(comment.id)\n\t\tif (editingId === comment.id) {\n\t\t\treturn (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"tlui-cmt-editing\"\n\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\tif (e.key === 'Escape') {\n\t\t\t\t\t\t\tsetEditingId(null)\n\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={card.author}\n\t\t\t\t\t\tplaceholder={msg(messages.commentsEditPlaceholder)}\n\t\t\t\t\t\tvalue={editText}\n\t\t\t\t\t\tonChange={setEditText}\n\t\t\t\t\t\tonSubmit={saveEdit}\n\t\t\t\t\t\tsendLabel={msg(messages.commentsSave)}\n\t\t\t\t\t\tdisabled={isCommentEmpty(editText)}\n\t\t\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\t\t\tautoFocus\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)\n\t\t}\n\t\treturn (\n\t\t\t<CommentCard\n\t\t\t\t{...card}\n\t\t\t\tfooter={\n\t\t\t\t\t<CommentReactions\n\t\t\t\t\t\tcomment={comment}\n\t\t\t\t\t\t// Reacting is a commenting write: without `canComment` the tally renders\n\t\t\t\t\t\t// read-only (no identity \u2192 pills aren't clickable).\n\t\t\t\t\t\tcurrentUserId={canComment ? currentUserId : null}\n\t\t\t\t\t\tresolveName={resolveName}\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\tactions={\n\t\t\t\t\tcanComment && (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{(permissions?.edit || permissions?.delete) && (\n\t\t\t\t\t\t\t\t<MoreOptionsMenu id={`comment-actions-${comment.id}`} moreFor={comment.id}>\n\t\t\t\t\t\t\t\t\t{permissions?.edit && (\n\t\t\t\t\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\t\t\t\t\tlabel={msg(messages.commentsEdit)}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => startEdit(comment, { fromMoreMenu: true })}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t{permissions?.delete && (\n\t\t\t\t\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\t\t\t\t\tlabel={msg(messages.actionDelete)}\n\t\t\t\t\t\t\t\t\t\t\tdanger\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => deleteComment(editor, comment)}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</MoreOptionsMenu>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{/* Last so react is always the rightmost pill. */}\n\t\t\t\t\t\t\t<CommentReactionPicker comment={comment} currentUserId={currentUserId} />\n\t\t\t\t\t\t</>\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t/>\n\t\t)\n\t}\n\n\t// Resolve and delete are commenting writes: behind `canComment`, plus the `currentUserId` a\n\t// resolve stamps into `resolved.by`.\n\tconst resolveLabel = msg(thread.resolved ? messages.commentsReopen : messages.commentsResolve)\n\tconst headerActions = (\n\t\t<>\n\t\t\t{/* Host verbs \u2014 assign, link a ticket \u2014 sit ahead of the built-in actions. */}\n\t\t\t{ThreadActions && <ThreadActions thread={thread} comments={comments} />}\n\t\t\t{(threadHref !== undefined || canDeleteThread) && (\n\t\t\t\t<MoreOptionsMenu id={`comment-thread-actions-${thread.id}`}>\n\t\t\t\t\t{/* A link is a read affordance: offered to anyone who can see the thread,\n\t\t\t\t\t including a viewer who can't comment. `noClose` keeps the menu up so the\n\t\t\t\t\t item can confirm the copy in place. */}\n\t\t\t\t\t{threadHref !== undefined && (\n\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\tlabel={msg(linkCopied ? messages.commentsLinkCopied : messages.commentsCopyLink)}\n\t\t\t\t\t\t\tnoClose\n\t\t\t\t\t\t\tonClick={copyThreadLink}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t\t{canDeleteThread && (\n\t\t\t\t\t\t<MenuItem label={msg(messages.commentsDelete)} danger onClick={removeThread} />\n\t\t\t\t\t)}\n\t\t\t\t</MoreOptionsMenu>\n\t\t\t)}\n\t\t\t{canComment && currentUserId && (\n\t\t\t\t<TldrawUiButton\n\t\t\t\t\ttype=\"icon\"\n\t\t\t\t\ttooltip={resolveLabel}\n\t\t\t\t\ttitle={resolveLabel}\n\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\tonClick={toggleResolve}\n\t\t\t\t>\n\t\t\t\t\t<TldrawUiIcon icon=\"check\" label={resolveLabel} small />\n\t\t\t\t</TldrawUiButton>\n\t\t\t)}\n\t\t\t<TldrawUiButton\n\t\t\t\ttype=\"icon\"\n\t\t\t\ttooltip={msg(messages.commentsDismiss)}\n\t\t\t\ttitle={msg(messages.commentsDismiss)}\n\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\tonClick={() => openThreadId.set(editor, null)}\n\t\t\t>\n\t\t\t\t<TldrawUiIcon icon=\"cross-2\" label={msg(messages.commentsDismiss)} small />\n\t\t\t</TldrawUiButton>\n\t\t</>\n\t)\n\n\treturn (\n\t\t<CommentThread\n\t\t\theader={msg(messages.commentsThreadTitle)}\n\t\t\theaderActions={headerActions}\n\t\t\trenderComment={renderComment}\n\t\t\tcomments={cards}\n\t\t\tresolvedBanner={\n\t\t\t\tthread.resolved\n\t\t\t\t\t? msg(messages.commentsResolvedBy, {\n\t\t\t\t\t\t\tname: resolveAuthor(thread.resolved.by)?.name ?? msg(messages.commentsUnknownAuthor),\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined\n\t\t\t}\n\t\t\tcomposer={\n\t\t\t\tcanReply\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tauthor: me ?? UNKNOWN_COMMENT_AUTHOR,\n\t\t\t\t\t\t\tplaceholder: msg(messages.commentsReplyPlaceholder),\n\t\t\t\t\t\t\tsendLabel: msg(messages.commentsSend),\n\t\t\t\t\t\t\tvalue: reply,\n\t\t\t\t\t\t\tonChange: (value: TLRichText) => {\n\t\t\t\t\t\t\t\tsetReply(value)\n\t\t\t\t\t\t\t\tsaveCommentDraft(replyDraftSlot(thread.id), value)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tonSubmit: postReply,\n\t\t\t\t\t\t\t// Up in the empty reply box edits the comment directly above it, chat-style \u2014\n\t\t\t\t\t\t\t// only when you're allowed to edit it (the same gate as the Edit link).\n\t\t\t\t\t\t\tonArrowUpWhenEmpty: () => {\n\t\t\t\t\t\t\t\tconst last = comments[comments.length - 1]\n\t\t\t\t\t\t\t\tif (last && commentPermissions.get(last.id)?.edit) startEdit(last)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\t\tdisabled: isCommentEmpty(reply) || !currentUserId,\n\t\t\t\t\t\t\tgetMentionSuggestions,\n\t\t\t\t\t\t\trenderMentionSuggestion,\n\t\t\t\t\t\t\t// Reuses the composer's own focus path, so the caret lands at the end of a\n\t\t\t\t\t\t\t// restored draft rather than in front of it.\n\t\t\t\t\t\t\tautoFocus: focusReply,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined\n\t\t\t}\n\t\t\tfooter={\n\t\t\t\t!canComment && !thread.resolved && ComposerFallback ? (\n\t\t\t\t\t<ComposerFallback context=\"thread\" />\n\t\t\t\t) : undefined\n\t\t\t}\n\t\t/>\n\t)\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["ThreadView", "comment"]
|
|
7
7
|
}
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -234,7 +234,7 @@ export declare interface CommentingComponents {
|
|
|
234
234
|
/**
|
|
235
235
|
* The hover affordance naming who reacted with an emoji. Receives the reactors and the pill (as
|
|
236
236
|
* `children`) and owns the whole thing — box, size, shape, position. For a wording change,
|
|
237
|
-
* translate the `comments.reacted
|
|
237
|
+
* translate the `comments.reacted` string instead.
|
|
238
238
|
*/
|
|
239
239
|
ReactionTooltip?: ComponentType<ReactionTooltipProps>;
|
|
240
240
|
/**
|
|
@@ -625,8 +625,8 @@ export declare interface CommentThreadProps {
|
|
|
625
625
|
* release — like placing a sticky note — settling on a point, or on a shape when released over one.
|
|
626
626
|
* With region comments enabled, dragging past the threshold draws a region rectangle instead.
|
|
627
627
|
* Placement only opens a composer; the records are created when the comment is posted. The tool
|
|
628
|
-
* stays active while the composer is open — posting returns to select, and clicking
|
|
629
|
-
*
|
|
628
|
+
* stays active while the composer is open — posting returns to select, and so does clicking the
|
|
629
|
+
* canvas elsewhere, which cancels the placement.
|
|
630
630
|
* @public
|
|
631
631
|
*/
|
|
632
632
|
export declare class CommentTool extends StateNode {
|
|
@@ -657,7 +657,9 @@ export declare class CommentTool extends StateNode {
|
|
|
657
657
|
}
|
|
658
658
|
|
|
659
659
|
/** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.
|
|
660
|
-
* Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button
|
|
660
|
+
* Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button, and its
|
|
661
|
+
* `DefaultContextMenuContent` shows a comment item that opens the composer where the menu was
|
|
662
|
+
* opened.
|
|
661
663
|
* @public */
|
|
662
664
|
export declare const commentToolOverrides: TLUiOverrides;
|
|
663
665
|
|
|
@@ -738,8 +740,9 @@ export declare function DefaultReactionTooltip({ reactors, children }: ReactionT
|
|
|
738
740
|
/**
|
|
739
741
|
* The default reactor sentence naming who reacted — up to three names spelled out, then "and N
|
|
740
742
|
* others" (e.g. "You reacted", "You and Bo reacted", "You, Bo and Ada reacted", "You, Bo, Ada and 2
|
|
741
|
-
* others reacted"). The wording lives in the `comments.reacted
|
|
742
|
-
*
|
|
743
|
+
* others reacted"). The wording lives in the `comments.reacted` translation string, whose ICU
|
|
744
|
+
* plural picks the phrasing, so each locale controls both the grammar and how it counts. Exported
|
|
745
|
+
* so a custom `ReactionTooltip` can reuse it inside its own box.
|
|
743
746
|
* @public @react
|
|
744
747
|
*/
|
|
745
748
|
export declare function DefaultReactionTooltipContent({ reactors }: {
|
|
@@ -1287,15 +1290,32 @@ export declare interface SidebarRow {
|
|
|
1287
1290
|
*/
|
|
1288
1291
|
export declare function sortSidebarRows(rows: readonly SidebarRow[]): readonly SidebarRow[];
|
|
1289
1292
|
|
|
1293
|
+
/**
|
|
1294
|
+
* Open the comment composer at a page point, anchored as a comment tool click there would be. Use
|
|
1295
|
+
* it to start a comment from outside the tool, such as a context menu item. Enters the comment tool
|
|
1296
|
+
* (which must be registered), since the draft composer closes when the tool exits. It doesn't check
|
|
1297
|
+
* `canComment`: a viewer who can't comment sees the `ComposerFallback` slot instead.
|
|
1298
|
+
*
|
|
1299
|
+
* @example
|
|
1300
|
+
* ```tsx
|
|
1301
|
+
* <TldrawUiMenuItem id="comment" label="Comment" onSelect={() => startCommentAt(editor, point)} />
|
|
1302
|
+
* ```
|
|
1303
|
+
* @public
|
|
1304
|
+
*/
|
|
1305
|
+
export declare function startCommentAt(editor: Editor, point: VecLike): void;
|
|
1306
|
+
|
|
1290
1307
|
/**
|
|
1291
1308
|
* Tally a comment's reactions into an entry per emoji, ordered by when that emoji was first used so
|
|
1292
1309
|
* the row stays stable as later reactions arrive. `active` marks the current user's emoji and
|
|
1293
1310
|
* `reactors` lists who reacted, in reaction order. `resolveName` names each reactor; an id it can't
|
|
1294
|
-
* name falls back to
|
|
1311
|
+
* name falls back to `unknownAuthorName`, never the raw user id. Pass a translated name for that:
|
|
1312
|
+
* this is a plain function, so it can't reach the translations itself.
|
|
1295
1313
|
*
|
|
1296
1314
|
* @public
|
|
1297
1315
|
*/
|
|
1298
|
-
export declare function summarizeReactions(reactions: readonly ReactionSummaryInput[], currentUserId?: null | string, resolveName?: (userId: string) => string | undefined
|
|
1316
|
+
export declare function summarizeReactions(reactions: readonly ReactionSummaryInput[], currentUserId?: null | string, resolveName?: (userId: string) => string | undefined, opts?: {
|
|
1317
|
+
unknownAuthorName?: string;
|
|
1318
|
+
}): ReactionSummary[];
|
|
1299
1319
|
|
|
1300
1320
|
/**
|
|
1301
1321
|
* Typed reads of comment records on the editor store.
|
package/dist-cjs/index.js
CHANGED
|
@@ -88,6 +88,7 @@ __export(index_exports, {
|
|
|
88
88
|
shapeAnchorAt: () => import_thread_state.shapeAnchorAt,
|
|
89
89
|
sidebarFilters: () => import_state.sidebarFilters,
|
|
90
90
|
sortSidebarRows: () => import_comments_sidebar.sortSidebarRows,
|
|
91
|
+
startCommentAt: () => import_comment_tool.startCommentAt,
|
|
91
92
|
summarizeReactions: () => import_comment_reactions.summarizeReactions,
|
|
92
93
|
toggleCommentReaction: () => import_comment_reactions.toggleCommentReaction,
|
|
93
94
|
toggleCommentsHidden: () => import_state.toggleCommentsHidden,
|
|
@@ -144,7 +145,7 @@ var import_state = require("./canvas/state");
|
|
|
144
145
|
var import_thread_state = require("./canvas/thread-state");
|
|
145
146
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
146
147
|
"@tldraw/commenting",
|
|
147
|
-
"5.6.0-next.
|
|
148
|
+
"5.6.0-next.fb83f87b7388",
|
|
148
149
|
"cjs"
|
|
149
150
|
);
|
|
150
151
|
//# sourceMappingURL=index.js.map
|
package/dist-cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\n\n// Presentational commenting components. These are tldraw-independent and can be used to build\n// custom commenting UI. The mention picker, pill, and avatar are re-exported from @tldraw/mentions\n// so the commenting public API is unchanged.\nexport {\n\tAvatar,\n\ttype AvatarProps,\n\ttype CommentAuthor,\n\tcreateMentionSuggestion,\n\tfilterMentionMembers,\n\tMention,\n\tMentionList,\n\ttype MentionListProps,\n\ttype MentionMember,\n\ttype MentionProps,\n\ttype MentionSuggestionOptions,\n} from '@tldraw/mentions'\nexport { Byline, type BylineProps } from './ui/byline'\nexport { CommentCard, type CommentCardProps } from './ui/comment-card'\nexport { CommentComposer, type CommentComposerProps } from './ui/comment-composer'\nexport { CountBadge, type CountBadgeProps } from './ui/count-badge'\nexport { CommentPin, type CommentPinProps } from './ui/comment-pin'\nexport { CommentThread, type CommentThreadProps } from './ui/comment-thread'\nexport {\n\tCommentListItem,\n\tCommentsList,\n\tisOpenInNewTabClick,\n\ttype CommentListItemProps,\n\ttype CommentListItemRenderProps,\n\ttype CommentsListProps,\n} from './ui/comments-list'\nexport { EmptyState, type EmptyStateProps } from './ui/empty-state'\nexport { formatFullDateTime, formatRelativeTime } from './ui/format-time'\nexport {\n\tDEFAULT_REACTION_EMOJI,\n\tEmojiPicker,\n\ttype EmojiPickerProps,\n\tisAllowedReactionEmoji,\n} from './ui/emoji-picker'\nexport {\n\tReaction,\n\ttype ReactionProps,\n\ttype RenderReaction,\n\tdefaultRenderReaction,\n\tDefaultReactionTooltip,\n\tDefaultReactionTooltipContent,\n\ttype ReactionTooltipProps,\n} from './ui/reaction'\nexport { ReactionPicker, type ReactionPickerProps } from './ui/reaction-picker'\nexport {\n\tReactions,\n\ttype ReactionReactor,\n\ttype ReactionsProps,\n\ttype ReactionSummary,\n} from './ui/reactions'\nexport { SendButton, type SendButtonProps } from './ui/send-button'\n\n// The tldraw-coupled commenting layer: the comment tool, reactive hooks over the comment\n// records, a rich-text body renderer, and a batteries-included <CanvasComments> overlay. Pairs\n// with the presentational components above.\nexport { registerCommentAnchorLifecycle } from './canvas/anchor-lifecycle'\nexport { CommentBody, type CommentBodyProps } from './canvas/comment-body'\nexport {\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\tputCommentRecords,\n\tremoveCommentRecords,\n\treopenThread,\n\tresolveThread,\n} from './canvas/comment-mutations'\nexport {\n\tCommentReactionPicker,\n\ttype CommentReactionPickerProps,\n\tCommentReactions,\n\ttype CommentReactionsProps,\n\ttype ReactionSummaryInput,\n\tsummarizeReactions,\n\ttoggleCommentReaction,\n\tuseCommentReactions,\n} from './canvas/comment-reactions'\nexport {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAK7C,sBAYO;AACP,oBAAyC;AACzC,0BAAmD;AACnD,8BAA2D;AAC3D,yBAAiD;AACjD,yBAAiD;AACjD,4BAAuD;AACvD,2BAOO;AACP,yBAAiD;AACjD,yBAAuD;AACvD,0BAKO;AACP,sBAQO;AACP,6BAAyD;AACzD,uBAKO;AACP,yBAAiD;AAKjD,8BAA+C;AAC/C,0BAAmD;AACnD,+BAQO;AACP,+BASO;AACP,
|
|
4
|
+
"sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\n\n// Presentational commenting components. These are tldraw-independent and can be used to build\n// custom commenting UI. The mention picker, pill, and avatar are re-exported from @tldraw/mentions\n// so the commenting public API is unchanged.\nexport {\n\tAvatar,\n\ttype AvatarProps,\n\ttype CommentAuthor,\n\tcreateMentionSuggestion,\n\tfilterMentionMembers,\n\tMention,\n\tMentionList,\n\ttype MentionListProps,\n\ttype MentionMember,\n\ttype MentionProps,\n\ttype MentionSuggestionOptions,\n} from '@tldraw/mentions'\nexport { Byline, type BylineProps } from './ui/byline'\nexport { CommentCard, type CommentCardProps } from './ui/comment-card'\nexport { CommentComposer, type CommentComposerProps } from './ui/comment-composer'\nexport { CountBadge, type CountBadgeProps } from './ui/count-badge'\nexport { CommentPin, type CommentPinProps } from './ui/comment-pin'\nexport { CommentThread, type CommentThreadProps } from './ui/comment-thread'\nexport {\n\tCommentListItem,\n\tCommentsList,\n\tisOpenInNewTabClick,\n\ttype CommentListItemProps,\n\ttype CommentListItemRenderProps,\n\ttype CommentsListProps,\n} from './ui/comments-list'\nexport { EmptyState, type EmptyStateProps } from './ui/empty-state'\nexport { formatFullDateTime, formatRelativeTime } from './ui/format-time'\nexport {\n\tDEFAULT_REACTION_EMOJI,\n\tEmojiPicker,\n\ttype EmojiPickerProps,\n\tisAllowedReactionEmoji,\n} from './ui/emoji-picker'\nexport {\n\tReaction,\n\ttype ReactionProps,\n\ttype RenderReaction,\n\tdefaultRenderReaction,\n\tDefaultReactionTooltip,\n\tDefaultReactionTooltipContent,\n\ttype ReactionTooltipProps,\n} from './ui/reaction'\nexport { ReactionPicker, type ReactionPickerProps } from './ui/reaction-picker'\nexport {\n\tReactions,\n\ttype ReactionReactor,\n\ttype ReactionsProps,\n\ttype ReactionSummary,\n} from './ui/reactions'\nexport { SendButton, type SendButtonProps } from './ui/send-button'\n\n// The tldraw-coupled commenting layer: the comment tool, reactive hooks over the comment\n// records, a rich-text body renderer, and a batteries-included <CanvasComments> overlay. Pairs\n// with the presentational components above.\nexport { registerCommentAnchorLifecycle } from './canvas/anchor-lifecycle'\nexport { CommentBody, type CommentBodyProps } from './canvas/comment-body'\nexport {\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\tputCommentRecords,\n\tremoveCommentRecords,\n\treopenThread,\n\tresolveThread,\n} from './canvas/comment-mutations'\nexport {\n\tCommentReactionPicker,\n\ttype CommentReactionPickerProps,\n\tCommentReactions,\n\ttype CommentReactionsProps,\n\ttype ReactionSummaryInput,\n\tsummarizeReactions,\n\ttoggleCommentReaction,\n\tuseCommentReactions,\n} from './canvas/comment-reactions'\nexport {\n\tCommentTool,\n\tcommentToolOverrides,\n\tcommentTools,\n\tstartCommentAt,\n} from './canvas/comment-tool'\nexport {\n\tgetCommentReactions,\n\tgetCommentRecord,\n\tgetComments,\n\tgetCommentThreads,\n\tgetLiveComments,\n\tgetLiveCommentThreads,\n\ttype TLCommentRecord,\n} from './canvas/comment-store'\nexport { type CommentingContext } from './canvas/context'\nexport { CommentsFilterMenu, type CommentsFilterMenuProps } from './canvas/comments-filter-menu'\nexport { CommentsMenuItem } from './canvas/comments-menu-item'\nexport { CanvasComments, type CanvasCommentsProps } from './canvas/comments-overlay'\nexport {\n\ttype CommentingComponents,\n\ttype CommentingOptions,\n\ttype CommentModification,\n\ttype CommentModificationContext,\n\tdefaultCanModifyComment,\n\tdefaultCommentingOptions,\n\tgetCanComment,\n\tgetCanModifyComment,\n\tgetCommentingOptions,\n\ttype ShapeCommentPrecisionContext,\n\tuseCanComment,\n\tuseCanModifyComment,\n\tuseCommentingOptions,\n} from './canvas/options'\nexport { CommentsOverflowMenu } from './canvas/comments-overflow-menu'\nexport { CommentsVisibilityToggle } from './canvas/comments-visibility-toggle'\nexport {\n\tCanvasCommentsSidebar,\n\tsortSidebarRows,\n\ttype CanvasCommentsSidebarProps,\n\ttype SidebarRow,\n} from './canvas/comments-sidebar'\nexport { useComments, useCommentThreads, useThreadComments } from './canvas/hooks'\nexport { useCommentingEnabled } from './canvas/license'\nexport { richTextToPlaintext } from './canvas/rich-text'\nexport { DEFAULT_SIDEBAR_FILTERS, type SidebarFilters } from './canvas/sidebar-filters'\nexport {\n\tcommentsHidden,\n\tcommentsSidebarOpen,\n\tgetRevealThreadPending,\n\topenThreadId,\n\trevealThread,\n\tsidebarFilters,\n\ttoggleCommentsHidden,\n\ttoggleCommentsSidebar,\n\tuseCommentsHidden,\n\tuseCommentsSidebarOpen,\n\tuseOpenThreadId,\n\tuseRevealThreadPending,\n\tuseSidebarFilters,\n} from './canvas/state'\nexport { anchorPagePoint, focusThread, shapeAnchorAt } from './canvas/thread-state'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAK7C,sBAYO;AACP,oBAAyC;AACzC,0BAAmD;AACnD,8BAA2D;AAC3D,yBAAiD;AACjD,yBAAiD;AACjD,4BAAuD;AACvD,2BAOO;AACP,yBAAiD;AACjD,yBAAuD;AACvD,0BAKO;AACP,sBAQO;AACP,6BAAyD;AACzD,uBAKO;AACP,yBAAiD;AAKjD,8BAA+C;AAC/C,0BAAmD;AACnD,+BAQO;AACP,+BASO;AACP,0BAKO;AACP,2BAQO;AAEP,kCAAiE;AACjE,gCAAiC;AACjC,8BAAyD;AACzD,qBAcO;AACP,oCAAqC;AACrC,wCAAyC;AACzC,8BAKO;AACP,mBAAkE;AAClE,qBAAqC;AACrC,uBAAoC;AACpC,6BAA6D;AAC7D,mBAcO;AACP,0BAA4D;AAAA,IAE5D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -27,6 +27,7 @@ var import_mentions = require("@tldraw/mentions");
|
|
|
27
27
|
var import_react2 = require("react");
|
|
28
28
|
var import_tldraw = require("tldraw");
|
|
29
29
|
var import_comment_extensions = require("./comment-extensions");
|
|
30
|
+
var import_gesture_focus = require("./gesture-focus");
|
|
30
31
|
var import_send_button = require("./send-button");
|
|
31
32
|
function CommentComposer({
|
|
32
33
|
author,
|
|
@@ -182,6 +183,10 @@ function CommentComposer({
|
|
|
182
183
|
const raf = requestAnimationFrame(() => editor.commands.focus("end"));
|
|
183
184
|
return () => cancelAnimationFrame(raf);
|
|
184
185
|
}, [autoFocus, editor]);
|
|
186
|
+
(0, import_react2.useEffect)(() => {
|
|
187
|
+
if (!autoFocus || !editor) return;
|
|
188
|
+
return (0, import_gesture_focus.focusOnGestureEnd)(editor.view.dom.ownerDocument, () => editor.commands.focus("end"));
|
|
189
|
+
}, [autoFocus, editor]);
|
|
185
190
|
const focusEditorFromField = (e) => {
|
|
186
191
|
if (e.target.closest(".tlui-cmt-input, .tlui-cmt-send")) return;
|
|
187
192
|
e.preventDefault();
|