@tldraw/commenting 5.6.0-canary.e9e8ec003426 → 5.6.0-canary.f283055556fc
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 +8 -2
- 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 +6 -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 +12 -6
- package/dist-cjs/index.js +1 -1
- 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 +10 -3
- 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 +7 -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 +12 -6
- package/dist-esm/index.mjs +1 -1
- 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 +45 -1
- package/src/canvas/comment-tool.tsx +14 -3
- 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 +8 -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/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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comments-filter-menu.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\tTldrawUiMenuCheckboxItem,\n\tTldrawUiMenuContextProvider,\n\tTldrawUiMenuGroup,\n\tuseEditor,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { MoreMenuIcon } from '../ui/icons'\nimport { SidebarFilters } from './sidebar-filters'\nimport { sidebarFilters } from './state'\n\n/** @public */\nexport interface CommentsFilterMenuProps {\n\t/** Whether to offer the \"only your threads\" toggle (needs a known current user). */\n\tcanFilterByAuthor?: boolean\n\t/** Whether to offer the \"only unread\" toggle (needs a read-status source). */\n\tcanFilterByUnread?: boolean\n}\n\n/** The funnel dropdown in the sidebar header: toggles for which threads the list shows.\n * @public @react */\nexport function CommentsFilterMenu({\n\tcanFilterByAuthor,\n\tcanFilterByUnread,\n}: CommentsFilterMenuProps) {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\tconst filters = useValue('sidebar filters', () => sidebarFilters.get(editor), [editor])\n\tconst toggle = (key: keyof SidebarFilters) => {\n\t\tsidebarFilters.update(editor, (f) => ({ ...f, [key]: !f[key] }))\n\t}\n\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id=\"comments-filter\">\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(
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import {\n\tdefineMessages,\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\tTldrawUiMenuCheckboxItem,\n\tTldrawUiMenuContextProvider,\n\tTldrawUiMenuGroup,\n\tuseEditor,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { MoreMenuIcon } from '../ui/icons'\nimport { SidebarFilters } from './sidebar-filters'\nimport { sidebarFilters } from './state'\n\nconst messages = defineMessages({\n\tcommentsFilter: { id: 'comments.filter', defaultMessage: 'Filter comments' },\n\tcommentsOnlyMyComments: { id: 'comments.only-my-comments', defaultMessage: 'Only my comments' },\n\tcommentsOnlyUnread: { id: 'comments.only-unread', defaultMessage: 'Only unread' },\n\tcommentsShowAllPages: { id: 'comments.show-all-pages', defaultMessage: 'Show all pages' },\n\tcommentsShowResolved: { id: 'comments.show-resolved', defaultMessage: 'Show resolved comments' },\n})\n\n/** @public */\nexport interface CommentsFilterMenuProps {\n\t/** Whether to offer the \"only your threads\" toggle (needs a known current user). */\n\tcanFilterByAuthor?: boolean\n\t/** Whether to offer the \"only unread\" toggle (needs a read-status source). */\n\tcanFilterByUnread?: boolean\n}\n\n/** The funnel dropdown in the sidebar header: toggles for which threads the list shows.\n * @public @react */\nexport function CommentsFilterMenu({\n\tcanFilterByAuthor,\n\tcanFilterByUnread,\n}: CommentsFilterMenuProps) {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\tconst filters = useValue('sidebar filters', () => sidebarFilters.get(editor), [editor])\n\tconst toggle = (key: keyof SidebarFilters) => {\n\t\tsidebarFilters.update(editor, (f) => ({ ...f, [key]: !f[key] }))\n\t}\n\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id=\"comments-filter\">\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.commentsFilter)}\n\t\t\t\t\ttitle={msg(messages.commentsFilter)}\n\t\t\t\t\tclassName=\"tlui-cmt-header-btn\"\n\t\t\t\t>\n\t\t\t\t\t<MoreMenuIcon />\n\t\t\t\t</TldrawUiButton>\n\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t{/* No tlui-cmt-menu here: the rows are tldraw menu items, which carry their own insets \u2014\n\t\t\t the extra padding would set this menu apart from the canvas menus it should match. */}\n\t\t\t<TldrawUiDropdownMenuContent side=\"bottom\" align=\"end\" alignOffset={0}>\n\t\t\t\t<TldrawUiMenuContextProvider type=\"menu\" sourceId=\"menu\">\n\t\t\t\t\t<TldrawUiMenuGroup id=\"comments-filter\">\n\t\t\t\t\t\t<TldrawUiMenuCheckboxItem\n\t\t\t\t\t\t\tid=\"show-all-pages\"\n\t\t\t\t\t\t\tlabel={messages.commentsShowAllPages.id}\n\t\t\t\t\t\t\tchecked={!filters.onlyCurrentPage}\n\t\t\t\t\t\t\tonSelect={() => toggle('onlyCurrentPage')}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{canFilterByAuthor && (\n\t\t\t\t\t\t\t<TldrawUiMenuCheckboxItem\n\t\t\t\t\t\t\t\tid=\"only-my-comments\"\n\t\t\t\t\t\t\t\tlabel={messages.commentsOnlyMyComments.id}\n\t\t\t\t\t\t\t\tchecked={filters.onlyMine}\n\t\t\t\t\t\t\t\tonSelect={() => toggle('onlyMine')}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{canFilterByUnread && (\n\t\t\t\t\t\t\t<TldrawUiMenuCheckboxItem\n\t\t\t\t\t\t\t\tid=\"only-unread\"\n\t\t\t\t\t\t\t\tlabel={messages.commentsOnlyUnread.id}\n\t\t\t\t\t\t\t\tchecked={filters.onlyUnread}\n\t\t\t\t\t\t\t\tonSelect={() => toggle('onlyUnread')}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<TldrawUiMenuCheckboxItem\n\t\t\t\t\t\t\tid=\"show-resolved\"\n\t\t\t\t\t\t\tlabel={messages.commentsShowResolved.id}\n\t\t\t\t\t\t\tchecked={filters.showResolved}\n\t\t\t\t\t\t\tonSelect={() => toggle('showResolved')}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</TldrawUiMenuGroup>\n\t\t\t\t</TldrawUiMenuContextProvider>\n\t\t\t</TldrawUiDropdownMenuContent>\n\t\t</TldrawUiDropdownMenuRoot>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "AAuDK,cAOA,YAPA;AAvDL;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,oBAAoB;AAE7B,SAAS,sBAAsB;AAE/B,MAAM,WAAW,eAAe;AAAA,EAC/B,gBAAgB,EAAE,IAAI,mBAAmB,gBAAgB,kBAAkB;AAAA,EAC3E,wBAAwB,EAAE,IAAI,6BAA6B,gBAAgB,mBAAmB;AAAA,EAC9F,oBAAoB,EAAE,IAAI,wBAAwB,gBAAgB,cAAc;AAAA,EAChF,sBAAsB,EAAE,IAAI,2BAA2B,gBAAgB,iBAAiB;AAAA,EACxF,sBAAsB,EAAE,IAAI,0BAA0B,gBAAgB,yBAAyB;AAChG,CAAC;AAYM,SAAS,mBAAmB;AAAA,EAClC;AAAA,EACA;AACD,GAA4B;AAC3B,QAAM,SAAS,UAAU;AACzB,QAAM,MAAM,eAAe;AAC3B,QAAM,UAAU,SAAS,mBAAmB,MAAM,eAAe,IAAI,MAAM,GAAG,CAAC,MAAM,CAAC;AACtF,QAAM,SAAS,CAAC,QAA8B;AAC7C,mBAAe,OAAO,QAAQ,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE;AAAA,EAChE;AAEA,SACC,qBAAC,4BAAyB,IAAG,mBAC5B;AAAA,wBAAC,+BACA;AAAA,MAAC;AAAA;AAAA,QACA,MAAK;AAAA,QACL,SAAS,IAAI,SAAS,cAAc;AAAA,QACpC,OAAO,IAAI,SAAS,cAAc;AAAA,QAClC,WAAU;AAAA,QAEV,8BAAC,gBAAa;AAAA;AAAA,IACf,GACD;AAAA,IAGA,oBAAC,+BAA4B,MAAK,UAAS,OAAM,OAAM,aAAa,GACnE,8BAAC,+BAA4B,MAAK,QAAO,UAAS,QACjD,+BAAC,qBAAkB,IAAG,mBACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACA,IAAG;AAAA,UACH,OAAO,SAAS,qBAAqB;AAAA,UACrC,SAAS,CAAC,QAAQ;AAAA,UAClB,UAAU,MAAM,OAAO,iBAAiB;AAAA;AAAA,MACzC;AAAA,MACC,qBACA;AAAA,QAAC;AAAA;AAAA,UACA,IAAG;AAAA,UACH,OAAO,SAAS,uBAAuB;AAAA,UACvC,SAAS,QAAQ;AAAA,UACjB,UAAU,MAAM,OAAO,UAAU;AAAA;AAAA,MAClC;AAAA,MAEA,qBACA;AAAA,QAAC;AAAA;AAAA,UACA,IAAG;AAAA,UACH,OAAO,SAAS,mBAAmB;AAAA,UACnC,SAAS,QAAQ;AAAA,UACjB,UAAU,MAAM,OAAO,YAAY;AAAA;AAAA,MACpC;AAAA,MAED;AAAA,QAAC;AAAA;AAAA,UACA,IAAG;AAAA,UACH,OAAO,SAAS,qBAAqB;AAAA,UACrC,SAAS,QAAQ;AAAA,UACjB,UAAU,MAAM,OAAO,cAAc;AAAA;AAAA,MACtC;AAAA,OACD,GACD,GACD;AAAA,KACD;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { TldrawUiMenuCheckboxItem, useEditor, useValue } from "tldraw";
|
|
2
|
+
import { defineMessages, TldrawUiMenuCheckboxItem, useEditor, useValue } from "tldraw";
|
|
3
3
|
import { commentsHidden, toggleCommentsHidden } from "./state.mjs";
|
|
4
|
+
const messages = defineMessages({
|
|
5
|
+
commentsTitle: { id: "comments.title", defaultMessage: "Comments" }
|
|
6
|
+
});
|
|
4
7
|
function CommentsMenuItem() {
|
|
5
8
|
const editor = useEditor();
|
|
6
9
|
const hidden = useValue("comments hidden", () => commentsHidden.get(editor), [editor]);
|
|
@@ -8,7 +11,7 @@ function CommentsMenuItem() {
|
|
|
8
11
|
TldrawUiMenuCheckboxItem,
|
|
9
12
|
{
|
|
10
13
|
id: "comments-visible",
|
|
11
|
-
label:
|
|
14
|
+
label: messages.commentsTitle.id,
|
|
12
15
|
kbd: "!c",
|
|
13
16
|
readonlyOk: true,
|
|
14
17
|
checked: !hidden,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comments-menu-item.tsx"],
|
|
4
|
-
"sourcesContent": ["import { TldrawUiMenuCheckboxItem, useEditor, useValue } from 'tldraw'\nimport { commentsHidden, toggleCommentsHidden } from './state'\n\n/**\n * A checkbox menu item for a \"View\" menu that shows/hides comment pins on the canvas \u2014 checked\n * when comments are visible, like tldraw's own grid/snap toggles. Reads and toggles the shared\n * {@link commentsHidden} signal, so it stays in sync with the Shift+C shortcut and the sidebar\n * control. Drop it into whichever menu your app owns.\n * @public @react\n */\nexport function CommentsMenuItem() {\n\tconst editor = useEditor()\n\tconst hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n\treturn (\n\t\t<TldrawUiMenuCheckboxItem\n\t\t\tid=\"comments-visible\"\n\t\t\tlabel
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { defineMessages, TldrawUiMenuCheckboxItem, useEditor, useValue } from 'tldraw'\nimport { commentsHidden, toggleCommentsHidden } from './state'\n\nconst messages = defineMessages({\n\tcommentsTitle: { id: 'comments.title', defaultMessage: 'Comments' },\n})\n\n/**\n * A checkbox menu item for a \"View\" menu that shows/hides comment pins on the canvas \u2014 checked\n * when comments are visible, like tldraw's own grid/snap toggles. Reads and toggles the shared\n * {@link commentsHidden} signal, so it stays in sync with the Shift+C shortcut and the sidebar\n * control. Drop it into whichever menu your app owns.\n * @public @react\n */\nexport function CommentsMenuItem() {\n\tconst editor = useEditor()\n\tconst hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n\treturn (\n\t\t<TldrawUiMenuCheckboxItem\n\t\t\tid=\"comments-visible\"\n\t\t\tlabel={messages.commentsTitle.id}\n\t\t\tkbd=\"!c\"\n\t\t\treadonlyOk\n\t\t\tchecked={!hidden}\n\t\t\tonSelect={() => toggleCommentsHidden(editor)}\n\t\t/>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "AAkBE;AAlBF,SAAS,gBAAgB,0BAA0B,WAAW,gBAAgB;AAC9E,SAAS,gBAAgB,4BAA4B;AAErD,MAAM,WAAW,eAAe;AAAA,EAC/B,eAAe,EAAE,IAAI,kBAAkB,gBAAgB,WAAW;AACnE,CAAC;AASM,SAAS,mBAAmB;AAClC,QAAM,SAAS,UAAU;AACzB,QAAM,SAAS,SAAS,mBAAmB,MAAM,eAAe,IAAI,MAAM,GAAG,CAAC,MAAM,CAAC;AACrF,SACC;AAAA,IAAC;AAAA;AAAA,MACA,IAAG;AAAA,MACH,OAAO,SAAS,cAAc;AAAA,MAC9B,KAAI;AAAA,MACJ,YAAU;AAAA,MACV,SAAS,CAAC;AAAA,MACV,UAAU,MAAM,qBAAqB,MAAM;AAAA;AAAA,EAC5C;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import {
|
|
3
|
+
defineMessages,
|
|
3
4
|
TldrawUiButton,
|
|
4
5
|
TldrawUiDropdownMenuContent,
|
|
5
6
|
TldrawUiDropdownMenuGroup,
|
|
@@ -12,6 +13,11 @@ import {
|
|
|
12
13
|
useValue
|
|
13
14
|
} from "tldraw";
|
|
14
15
|
import { commentsHidden, toggleCommentsHidden } from "./state.mjs";
|
|
16
|
+
const messages = defineMessages({
|
|
17
|
+
commentsMoreOptions: { id: "comments.more-options", defaultMessage: "More options" },
|
|
18
|
+
commentsHide: { id: "comments.hide", defaultMessage: "Hide comments" },
|
|
19
|
+
commentsShow: { id: "comments.show", defaultMessage: "Show comments" }
|
|
20
|
+
});
|
|
15
21
|
const HIDE_SHORTCUT = "\u21E7C";
|
|
16
22
|
function CommentsOverflowMenu() {
|
|
17
23
|
const editor = useEditor();
|
|
@@ -22,10 +28,10 @@ function CommentsOverflowMenu() {
|
|
|
22
28
|
TldrawUiButton,
|
|
23
29
|
{
|
|
24
30
|
type: "icon",
|
|
25
|
-
tooltip: msg(
|
|
26
|
-
title: msg(
|
|
31
|
+
tooltip: msg(messages.commentsMoreOptions),
|
|
32
|
+
title: msg(messages.commentsMoreOptions),
|
|
27
33
|
className: "tlui-cmt-header-btn",
|
|
28
|
-
children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "dots-vertical", label: msg(
|
|
34
|
+
children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "dots-vertical", label: msg(messages.commentsMoreOptions), small: true })
|
|
29
35
|
}
|
|
30
36
|
) }),
|
|
31
37
|
/* @__PURE__ */ jsx(
|
|
@@ -43,7 +49,7 @@ function CommentsOverflowMenu() {
|
|
|
43
49
|
className: "tlui-cmt-menu-item",
|
|
44
50
|
onClick: () => toggleCommentsHidden(editor),
|
|
45
51
|
children: [
|
|
46
|
-
/* @__PURE__ */ jsx("span", { children: hidden ? msg(
|
|
52
|
+
/* @__PURE__ */ jsx("span", { children: hidden ? msg(messages.commentsShow) : msg(messages.commentsHide) }),
|
|
47
53
|
/* @__PURE__ */ jsx("span", { className: "tlui-cmt-menu-item__shortcut", children: HIDE_SHORTCUT })
|
|
48
54
|
]
|
|
49
55
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comments-overflow-menu.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuGroup,\n\tTldrawUiDropdownMenuItem,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\tTldrawUiIcon,\n\tuseEditor,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { commentsHidden, toggleCommentsHidden } from './state'\n\n// A keyboard-shortcut glyph, not translatable copy \u2014 kept out of JSX as a constant.\nconst HIDE_SHORTCUT = '\u21E7C'\n\n/** The overflow (\u22EF) dropdown in the sidebar header. For now it holds the hide/show-comments\n * toggle; it's the home for later comment-wide controls (notifications, mark all as read).\n * @public @react */\nexport function CommentsOverflowMenu() {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\tconst hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id=\"comments-overflow\">\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(
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import {\n\tdefineMessages,\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuGroup,\n\tTldrawUiDropdownMenuItem,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\tTldrawUiIcon,\n\tuseEditor,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { commentsHidden, toggleCommentsHidden } from './state'\n\nconst messages = defineMessages({\n\tcommentsMoreOptions: { id: 'comments.more-options', defaultMessage: 'More options' },\n\tcommentsHide: { id: 'comments.hide', defaultMessage: 'Hide comments' },\n\tcommentsShow: { id: 'comments.show', defaultMessage: 'Show comments' },\n})\n\n// A keyboard-shortcut glyph, not translatable copy \u2014 kept out of JSX as a constant.\nconst HIDE_SHORTCUT = '\u21E7C'\n\n/** The overflow (\u22EF) dropdown in the sidebar header. For now it holds the hide/show-comments\n * toggle; it's the home for later comment-wide controls (notifications, mark all as read).\n * @public @react */\nexport function CommentsOverflowMenu() {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\tconst hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id=\"comments-overflow\">\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-header-btn\"\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=\"end\"\n\t\t\t\talignOffset={0}\n\t\t\t\tsideOffset={4}\n\t\t\t>\n\t\t\t\t<TldrawUiDropdownMenuGroup>\n\t\t\t\t\t<TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu-item\"\n\t\t\t\t\t\t\tonClick={() => toggleCommentsHidden(editor)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span>{hidden ? msg(messages.commentsShow) : msg(messages.commentsHide)}</span>\n\t\t\t\t\t\t\t<span className=\"tlui-cmt-menu-item__shortcut\">{HIDE_SHORTCUT}</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</TldrawUiDropdownMenuItem>\n\t\t\t\t</TldrawUiDropdownMenuGroup>\n\t\t\t</TldrawUiDropdownMenuContent>\n\t\t</TldrawUiDropdownMenuRoot>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "AAyCK,cAYC,YAZD;AAzCL;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,gBAAgB,4BAA4B;AAErD,MAAM,WAAW,eAAe;AAAA,EAC/B,qBAAqB,EAAE,IAAI,yBAAyB,gBAAgB,eAAe;AAAA,EACnF,cAAc,EAAE,IAAI,iBAAiB,gBAAgB,gBAAgB;AAAA,EACrE,cAAc,EAAE,IAAI,iBAAiB,gBAAgB,gBAAgB;AACtE,CAAC;AAGD,MAAM,gBAAgB;AAKf,SAAS,uBAAuB;AACtC,QAAM,SAAS,UAAU;AACzB,QAAM,MAAM,eAAe;AAC3B,QAAM,SAAS,SAAS,mBAAmB,MAAM,eAAe,IAAI,MAAM,GAAG,CAAC,MAAM,CAAC;AAErF,SACC,qBAAC,4BAAyB,IAAG,qBAC5B;AAAA,wBAAC,+BACA;AAAA,MAAC;AAAA;AAAA,QACA,MAAK;AAAA,QACL,SAAS,IAAI,SAAS,mBAAmB;AAAA,QACzC,OAAO,IAAI,SAAS,mBAAmB;AAAA,QACvC,WAAU;AAAA,QAEV,8BAAC,gBAAa,MAAK,iBAAgB,OAAO,IAAI,SAAS,mBAAmB,GAAG,OAAK,MAAC;AAAA;AAAA,IACpF,GACD;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QACV,MAAK;AAAA,QACL,OAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,QAEZ,8BAAC,6BACA,8BAAC,4BACA;AAAA,UAAC;AAAA;AAAA,YACA,MAAK;AAAA,YACL,WAAU;AAAA,YACV,SAAS,MAAM,qBAAqB,MAAM;AAAA,YAE1C;AAAA,kCAAC,UAAM,mBAAS,IAAI,SAAS,YAAY,IAAI,IAAI,SAAS,YAAY,GAAE;AAAA,cACxE,oBAAC,UAAK,WAAU,gCAAgC,yBAAc;AAAA;AAAA;AAAA,QAC/D,GACD,GACD;AAAA;AAAA,IACD;AAAA,KACD;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
defineMessages,
|
|
5
|
+
EditorPortal,
|
|
6
|
+
useEditor,
|
|
7
|
+
useTranslation,
|
|
8
|
+
useValue
|
|
9
|
+
} from "tldraw";
|
|
4
10
|
import { CommentsList } from "../ui/comments-list.mjs";
|
|
5
11
|
import { UNKNOWN_COMMENT_AUTHOR } from "./comment-render.mjs";
|
|
6
12
|
import { CommentsFilterMenu } from "./comments-filter-menu.mjs";
|
|
@@ -11,6 +17,15 @@ import { useCommentingOptions } from "./options.mjs";
|
|
|
11
17
|
import { richTextToPlaintext } from "./rich-text.mjs";
|
|
12
18
|
import { commentsSidebarOpen, openThreadId, sidebarFilters } from "./state.mjs";
|
|
13
19
|
import { focusThread } from "./thread-state.mjs";
|
|
20
|
+
const messages = defineMessages({
|
|
21
|
+
commentsEmpty: { id: "comments.empty", defaultMessage: "No comments on this page yet." },
|
|
22
|
+
commentsEmptyFiltered: {
|
|
23
|
+
id: "comments.empty-filtered",
|
|
24
|
+
defaultMessage: "No comments match your filters."
|
|
25
|
+
},
|
|
26
|
+
commentsResolved: { id: "comments.resolved", defaultMessage: "Resolved" },
|
|
27
|
+
commentsTitle: { id: "comments.title", defaultMessage: "Comments" }
|
|
28
|
+
});
|
|
14
29
|
function CanvasCommentsSidebar(props) {
|
|
15
30
|
const { resolveAuthor, currentUserId, isCommentUnread, header, empty, getThreadHref } = props;
|
|
16
31
|
const resolveName = useCallback((id) => resolveAuthor(id)?.name, [resolveAuthor]);
|
|
@@ -83,19 +98,19 @@ function CanvasCommentsSidebar(props) {
|
|
|
83
98
|
CommentsList,
|
|
84
99
|
{
|
|
85
100
|
items,
|
|
86
|
-
header: header ?? msg(
|
|
101
|
+
header: header ?? msg(messages.commentsTitle),
|
|
87
102
|
headerAction: /* @__PURE__ */ jsxs("div", { className: "tlui-cmt-list__header-actions", children: [
|
|
103
|
+
/* @__PURE__ */ jsx(CommentsVisibilityToggle, {}),
|
|
88
104
|
/* @__PURE__ */ jsx(
|
|
89
105
|
CommentsFilterMenu,
|
|
90
106
|
{
|
|
91
107
|
canFilterByAuthor: currentUserId !== null,
|
|
92
108
|
canFilterByUnread: isCommentUnread !== void 0
|
|
93
109
|
}
|
|
94
|
-
)
|
|
95
|
-
/* @__PURE__ */ jsx(CommentsVisibilityToggle, {})
|
|
110
|
+
)
|
|
96
111
|
] }),
|
|
97
|
-
empty: items.length === 0 && pageThreads.length > 0 ? msg(
|
|
98
|
-
resolvedLabel: msg(
|
|
112
|
+
empty: items.length === 0 && pageThreads.length > 0 ? msg(messages.commentsEmptyFiltered) : empty ?? msg(messages.commentsEmpty),
|
|
113
|
+
resolvedLabel: msg(messages.commentsResolved),
|
|
99
114
|
onSelect: focus,
|
|
100
115
|
renderItem
|
|
101
116
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comments-sidebar.tsx"],
|
|
4
|
-
"sourcesContent": ["import { ReactNode, useCallback } from 'react'\nimport {
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { ReactNode, useCallback } from 'react'\nimport {\n\tdefineMessages,\n\tEditorPortal,\n\tTLCommentThread,\n\tuseEditor,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentListItemProps, CommentListItemRenderProps, CommentsList } from '../ui/comments-list'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { CommentsFilterMenu } from './comments-filter-menu'\nimport { CommentsVisibilityToggle } from './comments-visibility-toggle'\nimport { type CommentingContext } from './context'\nimport { getCommentsByThread, useCommentThreads } from './hooks'\nimport { useCommentingEnabled } from './license'\nimport { useCommentingOptions } from './options'\nimport { richTextToPlaintext } from './rich-text'\nimport { commentsSidebarOpen, openThreadId, sidebarFilters } from './state'\nimport { focusThread } from './thread-state'\n\nconst messages = defineMessages({\n\tcommentsEmpty: { id: 'comments.empty', defaultMessage: 'No comments on this page yet.' },\n\tcommentsEmptyFiltered: {\n\t\tid: 'comments.empty-filtered',\n\t\tdefaultMessage: 'No comments match your filters.',\n\t},\n\tcommentsResolved: { id: 'comments.resolved', defaultMessage: 'Resolved' },\n\tcommentsTitle: { id: 'comments.title', defaultMessage: 'Comments' },\n})\n\n/**\n * The host wiring for {@link CanvasCommentsSidebar}: the {@link CommentingContext} fields it reads,\n * plus the panel's own slots. A non-null `currentUserId` enables the \"only your threads\" filter, and\n * an `isCommentUnread` the \"only unread\" one. `CanvasComments` takes the same fields, so a host\n * mounting both can spread one object into each.\n *\n * @public\n */\nexport interface CanvasCommentsSidebarProps extends Pick<\n\tCommentingContext,\n\t'currentUserId' | 'resolveAuthor' | 'isCommentUnread' | 'getThreadHref'\n> {\n\t/** Header above the list. */\n\theader?: ReactNode\n\t/** Shown when the page has no threads. */\n\tempty?: ReactNode\n}\n\n/**\n * A comments list panel for the current page, shown while {@link commentsSidebarOpen} is set (e.g.\n * toggled by a button). Clicking a thread brings its pin into view and opens it. Batteries-included\n * over the store (a sibling to `CanvasComments`); `CommentsList` is exported for a differently-placed\n * or always-on list.\n * @public @react\n */\nexport function CanvasCommentsSidebar(props: CanvasCommentsSidebarProps) {\n\tconst { resolveAuthor, currentUserId, isCommentUnread, header, empty, getThreadHref } = props\n\t// Name-only view of the resolver, for the plaintext previews (which resolve @-mentions).\n\tconst resolveName = useCallback((id: string) => resolveAuthor(id)?.name, [resolveAuthor])\n\tconst editor = useEditor()\n\tconst options = useCommentingOptions()\n\tconst commentingEnabled = useCommentingEnabled()\n\tconst msg = useTranslation()\n\tconst threads = useCommentThreads(editor)\n\tconst byThread = useValue('comments by thread', () => getCommentsByThread(editor).get(), [editor])\n\tconst currentPageId = useValue('page id', () => editor.getCurrentPageId(), [editor])\n\tconst open = useValue('sidebar open', () => commentsSidebarOpen.get(editor), [editor])\n\tconst openId = useValue('open thread', () => openThreadId.get(editor), [editor])\n\tconst filters = useValue('sidebar filters', () => sidebarFilters.get(editor), [editor])\n\tconst pageNames = useValue(\n\t\t'page names',\n\t\t() => new Map(editor.getPages().map((page) => [page.id, page.name])),\n\t\t[editor]\n\t)\n\n\tif (!commentingEnabled || !open) return null\n\n\t// Page scoping is treated as scoping, not a filter: an empty page reads \"no comments yet\",\n\t// while a list emptied by the toggles below reads \"nothing matches your filters\".\n\tconst pageThreads = threads.filter(\n\t\t(thread) => !filters.onlyCurrentPage || thread.pageId === currentPageId\n\t)\n\n\tconst rows: SidebarRow[] = pageThreads\n\t\t.filter((thread) => filters.showResolved || thread.resolved == null)\n\t\t// \"Only mine\" is ignored without a known user \u2014 otherwise a persisted onlyMine=true would\n\t\t// empty the list for a signed-out viewer, with the (hidden) toggle giving no way to clear it.\n\t\t.filter(\n\t\t\t(thread) => !filters.onlyMine || currentUserId === null || thread.createdBy === currentUserId\n\t\t)\n\t\t// \"Only unread\" is likewise ignored without a read-status source.\n\t\t.filter(\n\t\t\t(thread) =>\n\t\t\t\t!filters.onlyUnread ||\n\t\t\t\tisCommentUnread === undefined ||\n\t\t\t\t(byThread.get(thread.id) ?? []).some((c) => isCommentUnread(c.id))\n\t\t)\n\t\t.map((thread) => {\n\t\t\tconst threadComments = byThread.get(thread.id) ?? []\n\t\t\tconst first = threadComments[0]\n\t\t\t// Comments arrive oldest-first, so the last one is the thread's most recent activity.\n\t\t\tconst last = threadComments[threadComments.length - 1]\n\t\t\tlet preview: ReactNode = ''\n\t\t\t// The `ThreadPreview` component slot overrides the built-in plaintext default.\n\t\t\tconst ThreadPreview = options.components.ThreadPreview\n\t\t\tif (first) {\n\t\t\t\tpreview = ThreadPreview ? (\n\t\t\t\t\t<ThreadPreview comment={first} />\n\t\t\t\t) : (\n\t\t\t\t\trichTextToPlaintext(first.body, resolveName)\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn {\n\t\t\t\titem: {\n\t\t\t\t\tid: thread.id,\n\t\t\t\t\tauthor: resolveAuthor(thread.createdBy) ?? UNKNOWN_COMMENT_AUTHOR,\n\t\t\t\t\tpreview,\n\t\t\t\t\tdate: new Date((first ?? thread).createdAt).toISOString(),\n\t\t\t\t\tresolved: thread.resolved != null,\n\t\t\t\t\t// The page label only earns its place when it adds information: multiple pages\n\t\t\t\t\t// exist, and the thread is somewhere other than where you already are.\n\t\t\t\t\tpage:\n\t\t\t\t\t\tpageNames.size > 1 && thread.pageId !== currentPageId\n\t\t\t\t\t\t\t? pageNames.get(thread.pageId)\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\tcount: threadComments.length,\n\t\t\t\t\tselected: openId === thread.id,\n\t\t\t\t\thref: getThreadHref?.(thread.id),\n\t\t\t\t},\n\t\t\t\tlastActivity: (last ?? thread).createdAt,\n\t\t\t}\n\t\t})\n\n\tconst items = sortSidebarRows(rows).map((row) => row.item)\n\n\tconst focus = (id: string) => {\n\t\tconst thread = threads.find((t) => t.id === id)\n\t\tif (thread) focusThread(editor, thread)\n\t}\n\n\t// The `ThreadRow` component slot overrides the built-in row. It gets the thread record alongside\n\t// the summarized row, so host state on `thread.meta` is in reach without a second lookup. Indexed\n\t// once rather than scanned per row \u2014 this runs for every row on every render of the panel.\n\tconst ThreadRow = options.components.ThreadRow\n\tconst renderItem = ThreadRow\n\t\t? (() => {\n\t\t\t\t// Keyed by plain string: a row's id comes back as one, not as a branded thread id.\n\t\t\t\tconst threadsById = new Map<string, TLCommentThread>(\n\t\t\t\t\tthreads.map((thread) => [thread.id, thread])\n\t\t\t\t)\n\t\t\t\treturn (rowProps: CommentListItemRenderProps) => {\n\t\t\t\t\tconst thread = threadsById.get(rowProps.id)\n\t\t\t\t\t// Can't happen \u2014 the items are built from these threads \u2014 but a row with no thread has\n\t\t\t\t\t// nothing to render, and the slot's contract says the record is there.\n\t\t\t\t\tif (!thread) return null\n\t\t\t\t\treturn <ThreadRow {...rowProps} thread={thread} />\n\t\t\t\t}\n\t\t\t})()\n\t\t: undefined\n\n\treturn (\n\t\t<SidebarPanel>\n\t\t\t<CommentsList\n\t\t\t\titems={items}\n\t\t\t\theader={header ?? msg(messages.commentsTitle)}\n\t\t\t\theaderAction={\n\t\t\t\t\t<div className=\"tlui-cmt-list__header-actions\">\n\t\t\t\t\t\t<CommentsVisibilityToggle />\n\t\t\t\t\t\t<CommentsFilterMenu\n\t\t\t\t\t\t\tcanFilterByAuthor={currentUserId !== null}\n\t\t\t\t\t\t\tcanFilterByUnread={isCommentUnread !== undefined}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\tempty={\n\t\t\t\t\titems.length === 0 && pageThreads.length > 0\n\t\t\t\t\t\t? msg(messages.commentsEmptyFiltered)\n\t\t\t\t\t\t: (empty ?? msg(messages.commentsEmpty))\n\t\t\t\t}\n\t\t\t\tresolvedLabel={msg(messages.commentsResolved)}\n\t\t\t\tonSelect={focus}\n\t\t\t\trenderItem={renderItem}\n\t\t\t/>\n\t\t</SidebarPanel>\n\t)\n}\n\n/** A list row paired with the sort key that isn't part of what the row displays. @public */\nexport interface SidebarRow {\n\titem: CommentListItemProps\n\t/** When the thread's most recent comment was posted \u2014 what the list orders by. */\n\tlastActivity: number\n}\n\n/**\n * Order the list: unresolved threads first, then by most recent activity, id as a stable tiebreak.\n * Recency is the thread's *latest* comment, not its first, so a thread someone just replied to rises\n * to the top instead of staying wherever it was started. (The row still shows the thread's opening\n * comment and its date \u2014 that's what identifies the thread; only the ordering follows the replies.)\n *\n * Exported so a hand-built list can match the sidebar's ordering instead of re-deriving it.\n *\n * @public\n */\nexport function sortSidebarRows(rows: readonly SidebarRow[]): readonly SidebarRow[] {\n\treturn [...rows].sort(\n\t\t(a, b) =>\n\t\t\tNumber(!!a.item.resolved) - Number(!!b.item.resolved) ||\n\t\t\tb.lastActivity - a.lastActivity ||\n\t\t\t(a.item.id < b.item.id ? -1 : 1)\n\t)\n}\n\n/** The sidebar surface, portaled into the container. It scrolls its own list, so \u2014 unlike tldraw's\n * wheel-transparent panels \u2014 a wheel over it doesn't pan the canvas. */\nfunction SidebarPanel({ children }: { children: ReactNode }) {\n\treturn (\n\t\t<EditorPortal>\n\t\t\t<div className=\"tlui-cmt-canvas-sidebar\" onContextMenu={(e) => e.stopPropagation()}>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { TldrawUiButton, useEditor, useTranslation, useValue } from "tldraw";
|
|
2
|
+
import { defineMessages, TldrawUiButton, useEditor, useTranslation, useValue } from "tldraw";
|
|
3
3
|
import { EyeClosedIcon, EyeOpenIcon } from "../ui/icons.mjs";
|
|
4
4
|
import { commentsHidden, toggleCommentsHidden } from "./state.mjs";
|
|
5
|
+
const messages = defineMessages({
|
|
6
|
+
commentsHide: { id: "comments.hide", defaultMessage: "Hide comments" },
|
|
7
|
+
commentsShow: { id: "comments.show", defaultMessage: "Show comments" }
|
|
8
|
+
});
|
|
5
9
|
function CommentsVisibilityToggle() {
|
|
6
10
|
const editor = useEditor();
|
|
7
11
|
const msg = useTranslation();
|
|
8
12
|
const hidden = useValue("comments hidden", () => commentsHidden.get(editor), [editor]);
|
|
9
|
-
const label = hidden ? msg(
|
|
13
|
+
const label = hidden ? msg(messages.commentsShow) : msg(messages.commentsHide);
|
|
10
14
|
return /* @__PURE__ */ jsx(
|
|
11
15
|
TldrawUiButton,
|
|
12
16
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comments-visibility-toggle.tsx"],
|
|
4
|
-
"sourcesContent": ["import { TldrawUiButton, useEditor, useTranslation, useValue } from 'tldraw'\nimport { EyeClosedIcon, EyeOpenIcon } from '../ui/icons'\nimport { commentsHidden, toggleCommentsHidden } from './state'\n\n/** The sidebar header's show/hide toggle for comment pins \u2014 an eye that closes while comments\n * are hidden. The same state as the Shift+C shortcut.\n * @public @react */\nexport function CommentsVisibilityToggle() {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\tconst hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n\tconst label = hidden ? msg(
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { defineMessages, TldrawUiButton, useEditor, useTranslation, useValue } from 'tldraw'\nimport { EyeClosedIcon, EyeOpenIcon } from '../ui/icons'\nimport { commentsHidden, toggleCommentsHidden } from './state'\n\nconst messages = defineMessages({\n\tcommentsHide: { id: 'comments.hide', defaultMessage: 'Hide comments' },\n\tcommentsShow: { id: 'comments.show', defaultMessage: 'Show comments' },\n})\n\n/** The sidebar header's show/hide toggle for comment pins \u2014 an eye that closes while comments\n * are hidden. The same state as the Shift+C shortcut.\n * @public @react */\nexport function CommentsVisibilityToggle() {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\tconst hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n\tconst label = hidden ? msg(messages.commentsShow) : msg(messages.commentsHide)\n\n\treturn (\n\t\t<TldrawUiButton\n\t\t\ttype=\"icon\"\n\t\t\ttooltip={label}\n\t\t\ttitle={label}\n\t\t\tclassName=\"tlui-cmt-header-btn\"\n\t\t\tonClick={() => toggleCommentsHidden(editor)}\n\t\t>\n\t\t\t{hidden ? <EyeClosedIcon /> : <EyeOpenIcon />}\n\t\t</TldrawUiButton>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "AA0Ba;AA1Bb,SAAS,gBAAgB,gBAAgB,WAAW,gBAAgB,gBAAgB;AACpF,SAAS,eAAe,mBAAmB;AAC3C,SAAS,gBAAgB,4BAA4B;AAErD,MAAM,WAAW,eAAe;AAAA,EAC/B,cAAc,EAAE,IAAI,iBAAiB,gBAAgB,gBAAgB;AAAA,EACrE,cAAc,EAAE,IAAI,iBAAiB,gBAAgB,gBAAgB;AACtE,CAAC;AAKM,SAAS,2BAA2B;AAC1C,QAAM,SAAS,UAAU;AACzB,QAAM,MAAM,eAAe;AAC3B,QAAM,SAAS,SAAS,mBAAmB,MAAM,eAAe,IAAI,MAAM,GAAG,CAAC,MAAM,CAAC;AACrF,QAAM,QAAQ,SAAS,IAAI,SAAS,YAAY,IAAI,IAAI,SAAS,YAAY;AAE7E,SACC;AAAA,IAAC;AAAA;AAAA,MACA,MAAK;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAU;AAAA,MACV,SAAS,MAAM,qBAAqB,MAAM;AAAA,MAEzC,mBAAS,oBAAC,iBAAc,IAAK,oBAAC,eAAY;AAAA;AAAA,EAC5C;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/options.ts"],
|
|
4
|
-
"sourcesContent": ["import { useMemo, type ComponentType } from 'react'\nimport {\n\ttype Editor,\n\ttype TLComment,\n\ttype TLCommentThread,\n\ttype TLHistoryBatchOptions,\n\ttype TLShapeId,\n\ttype VecLike,\n\tuseEditor,\n\tuseValue,\n} from 'tldraw'\nimport { type CommentListItemRenderProps } from '../ui/comments-list'\nimport { isAllowedReactionEmoji, type EmojiPickerProps } from '../ui/emoji-picker'\nimport { type ReactionTooltipProps } from '../ui/reaction'\n\n/**\n * The gesture that's creating a shape anchor, passed to\n * {@link CommentingOptions.shouldBePrecise}: the target shape, the page point of the release, and\n * whether Alt was held.\n *\n * @public\n */\nexport interface ShapeCommentPrecisionContext {\n\treadonly shapeId: TLShapeId\n\treadonly point: VecLike\n\treadonly altKey: boolean\n}\n\n/**\n * A commenting write that belongs to someone in particular, and the record it targets \u2014 the\n * argument to {@link CommentingOptions.canModifyComment}.\n *\n * Resolving, reopening, reacting, and moving a pin aren't here: none of them is anyone's in\n * particular, so {@link CommentingOptions.canComment} is the only gate on them.\n *\n * @public\n */\nexport type CommentModification =\n\t| { readonly action: 'edit-comment'; readonly comment: TLComment }\n\t| { readonly action: 'delete-comment'; readonly comment: TLComment }\n\t| { readonly action: 'delete-thread'; readonly thread: TLCommentThread }\n\n/**\n * The argument to {@link CommentingOptions.canModifyComment}: which write, against which record,\n * and by whom.\n *\n * @public\n */\nexport type CommentModificationContext = {\n\treadonly editor: Editor\n\treadonly currentUserId: string | null\n} & CommentModification\n\n/**\n * Component overrides for the batteries-included comments layer. Each slot replaces a built-in\n * piece; leave a slot unset to keep its default.\n *\n * @public\n */\nexport interface CommentingComponents {\n\t/** A comment's body. Replaces the default rich-text `<CommentBody>`. */\n\tCommentBody?: ComponentType<{ comment: TLComment }>\n\t/** A pin's inner content. Replaces the author-initial default. */\n\tPinContent?: ComponentType<{ thread: TLCommentThread; comments: TLComment[] }>\n\t/** A sidebar row's preview. Replaces the plaintext default. */\n\tThreadPreview?: ComponentType<{ comment: TLComment }>\n\t/**\n\t * A whole sidebar row. Replaces the default `<CommentListItem>`, which is exported \u2014 so a row\n\t * that only adds an unread dot or a status chip can spread these props into it. Use\n\t * `ThreadPreview` instead when only the preview text is changing.\n\t */\n\tThreadRow?: ComponentType<CommentListItemRenderProps & { thread: TLCommentThread }>\n\t/**\n\t * Extra controls in an open thread's header, added ahead of the built-in resolve and dismiss\n\t * buttons rather than replacing them. \"Copy link\" is already built in whenever the host supplies\n\t * `getThreadHref`.\n\t */\n\tThreadActions?: ComponentType<{ thread: TLCommentThread; comments: TLComment[] }>\n\t/**\n\t * A reaction's visual, given its token. The default renders the token string for the OS emoji\n\t * font. Override to draw a custom palette \u2014 an `<img>`, an SVG, anything. The token is what gets\n\t * stored and synced; this only controls how it's drawn.\n\t */\n\tReactionContent?: ComponentType<{ token: string }>\n\t/**\n\t * What the add-reaction button opens. Replaces the default `<EmojiPicker>` grid. Pairs with\n\t * `ReactionContent` (which draws the tokens this emits) and `isAllowedReaction` (which must\n\t * accept them).\n\t */\n\tReactionPalette?: ComponentType<EmojiPickerProps>\n\t/**\n\t * The hover affordance naming who reacted with an emoji. Receives the reactors and the pill (as\n\t * `children`) and owns the whole thing \u2014 box, size, shape, position. For a wording change,\n\t * translate the `comments.reacted-*` strings instead.\n\t */\n\tReactionTooltip?: ComponentType<ReactionTooltipProps>\n\t/**\n\t * Shown where a composer would sit when the viewer can't compose (see\n\t * {@link CommentingOptions.canComment}). `context` is the surface rendering it: an open thread\n\t * popover (`'thread'`) or the comment tool's placement popover (`'pending'`). Unset, those\n\t * surfaces render nothing.\n\t */\n\tComposerFallback?: ComponentType<{ context: 'pending' | 'thread' }>\n}\n\n/**\n * Configuration for the commenting layer. Static config only \u2014 pass it once via\n * `CommentTool.configure({ ... })`, mirroring `ShapeUtil.configure`. Live, reactive values\n * (`currentUserId`, author resolution, read-status callbacks) are the `CommentingContext`, passed as\n * props to each commenting surface.\n *\n * For defaults, see {@link defaultCommentingOptions}.\n *\n * @example\n * ```tsx\n * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />\n * ```\n *\n * @public\n */\nexport interface CommentingOptions {\n\t// History / undo\n\t/**\n\t * How comment mutations interact with the editor undo stack. Defaults to `'ignore'` \u2014 comments\n\t * are deliberately not undoable (see `TLComment`). `'record'` is a multiplayer footgun: undoing\n\t * a delete resurrects a thread a collaborator already removed. Safe only single-player.\n\t */\n\treadonly history: TLHistoryBatchOptions['history']\n\t/**\n\t * History mode for the pin drag-to-move re-anchor specifically. Unlike posts/edits this is a\n\t * spatial edit that may reasonably be undoable alongside a shape move. Defaults to `history`.\n\t */\n\treadonly dragHistory: TLHistoryBatchOptions['history'] | undefined\n\n\t// Feature toggles\n\t/** Fold nearby pins into count badges as the camera zooms out. */\n\treadonly enableClustering: boolean\n\t/**\n\t * Whether a user may hold several emoji reactions on one comment. `true` (the default) is the\n\t * Slack model: each emoji toggles independently. `false` is single-select: picking a new emoji\n\t * replaces the user's existing reaction. Note this is enforced client-side; the server accepts\n\t * per-emoji records either way.\n\t */\n\treadonly allowMultipleReactions: boolean\n\t/**\n\t * Whether a token may be added as a reaction. Defaults to {@link isAllowedReactionEmoji}, which\n\t * keeps a scripted client from writing junk values the picker would never offer. Override\n\t * alongside a custom `ReactionPalette`. Removals aren't checked \u2014 an off-palette reaction must\n\t * still be clearable.\n\t */\n\tisAllowedReaction(token: string): boolean\n\t/**\n\t * Whether dragging the comment tool out creates a region anchor \u2014 a comment attached to a\n\t * rectangular area, drawn as a dashed box with the pin on the corner the drag released on. Off\n\t * by default, where comments attach to points and shapes only and a drag trails the composer.\n\t */\n\treadonly enableRegions: boolean\n\n\t// Permissions\n\t/**\n\t * Whether the viewer may participate in commenting: composing, editing, deleting, resolving, and\n\t * moving pins. When false, {@link CommentingComponents.ComposerFallback} renders in the\n\t * composer's place and action affordances are hidden. Unset, participation is allowed exactly\n\t * when `currentUserId` is set.\n\t *\n\t * Called during render via {@link useCanComment}, so signal reads are tracked. Posting still\n\t * needs a `currentUserId`, so returning true for a signed-out viewer yields a composer whose\n\t * send button stays disabled. A callback that throws is logged and read as false, rather than\n\t * taking the comments layer down with it.\n\t */\n\treadonly canComment:\n\t\t| ((ctx: { editor: Editor; currentUserId: string | null }) => boolean)\n\t\t| undefined\n\t/**\n\t * Whether the viewer may make a particular write against a particular record: editing or\n\t * deleting a comment, or deleting a thread. Unset, each is its record's owner's to make\n\t * ({@link defaultCanModifyComment}) \u2014 you edit and delete your own comments, and delete threads\n\t * you started. Override it to widen that (a workspace admin or moderator who may remove\n\t * anyone's comment) or to narrow it (no edits after an hour). Where it returns false the\n\t * affordance isn't rendered.\n\t *\n\t * Checked after {@link CommentingOptions.canComment}, which gates commenting as a whole: a\n\t * viewer who may not participate gets no action affordances at all, whatever this returns.\n\t *\n\t * Called during render via {@link useCanModifyComment}, so reactive reads (signals) are tracked.\n\t * A callback that throws is logged and read as false: an affordance is withheld rather than the\n\t * comments layer lost, and a denial is what a server enforcing the same rule would have said.\n\t *\n\t * @example\n\t * ```tsx\n\t * CommentTool.configure({\n\t * \tcanModifyComment: (ctx) =>\n\t * \t\t// Moderators may delete anything; everything else stays the owner's to do.\n\t * \t\t(ctx.action !== 'edit-comment' && isModerator(ctx.currentUserId)) ||\n\t * \t\tdefaultCanModifyComment(ctx),\n\t * })\n\t * ```\n\t */\n\treadonly canModifyComment: ((ctx: CommentModificationContext) => boolean) | undefined\n\n\t// Anchoring\n\t/** Normalized (0\u20131) spot within a shape where imprecise shape pins sit. Default top-right. */\n\treadonly impreciseShapeAnchor: { readonly x: number; readonly y: number }\n\t/**\n\t * Whether a comment landing on a shape pins to the exact clicked spot, or to the shape as a\n\t * whole (rendered at `impreciseShapeAnchor`). Always precise by default; return `false`, or\n\t * decide from the context. Governs new placements only \u2014 existing anchors render as stored.\n\t */\n\tshouldBePrecise(editor: Editor, context: ShapeCommentPrecisionContext): boolean\n\n\t// Components\n\t/** Component overrides. See {@link CommentingComponents}. */\n\treadonly components: CommentingComponents\n}\n\n/**\n * The default {@link CommentingOptions}. Override via `CommentTool.configure({ ... })`.\n *\n * @public\n */\nexport const defaultCommentingOptions = {\n\thistory: 'ignore',\n\tdragHistory: undefined,\n\tenableClustering: true,\n\tallowMultipleReactions: true,\n\tisAllowedReaction: isAllowedReactionEmoji,\n\tenableRegions: false,\n\tcanComment: undefined,\n\tcanModifyComment: undefined,\n\timpreciseShapeAnchor: { x: 1, y: 0 },\n\tshouldBePrecise: () => true,\n\tcomponents: {},\n} as const satisfies CommentingOptions\n\n/**\n * The merged {@link CommentingOptions} for an editor, read off the registered comment tool (which\n * carries them via `CommentTool.configure`). Falls back to {@link defaultCommentingOptions} when\n * the comment tool isn't registered. Usable from anywhere with an `Editor` \u2014 including the tool's\n * own state, which has no React context.\n *\n * @public\n */\nexport function getCommentingOptions(editor: Editor): CommentingOptions {\n\tconst tool = editor.getStateDescendant('comment') as { options?: CommentingOptions } | undefined\n\treturn tool?.options ?? defaultCommentingOptions\n}\n\n/**\n * React hook for {@link getCommentingOptions}. Options are fixed per editor (set at tool\n * registration), so this doesn't need to be reactive.\n *\n * @public\n */\nexport function useCommentingOptions(): CommentingOptions {\n\tconst editor = useEditor()\n\treturn useMemo(() => getCommentingOptions(editor), [editor])\n}\n\n/**\n * Ask a host's permission callback, denying the write if it throws.\n *\n * These are called during render, so an exception in one would take the comments layer down with\n * the answer. Denying costs an affordance, which is what a `false` would have cost anyway, and it\n * can't offer a write a server enforcing the same rule would then reject.\n */\nfunction permits(option: string, check: () => boolean): boolean {\n\ttry {\n\t\treturn check()\n\t} catch (error) {\n\t\tconsole.error(`[tldraw] \\`${option}\\` threw, so the write is denied:`, error)\n\t\treturn false\n\t}\n}\n\n/**\n * Whether the viewer may participate in commenting, per {@link CommentingOptions.canComment}\n * (defaulting to `currentUserId != null` when unset). A callback that throws denies.\n *\n * This is a plain, untracked read \u2014 in React, use {@link useCanComment} instead.\n *\n * @public\n */\nexport function getCanComment(editor: Editor, currentUserId: string | null | undefined): boolean {\n\tconst { canComment } = getCommentingOptions(editor)\n\tif (!canComment) return currentUserId != null\n\treturn permits('canComment', () => canComment({ editor, currentUserId: currentUserId ?? null }))\n}\n\n/**\n * Reactive React hook for {@link getCanComment}: a `canComment` callback that reads signals\n * re-evaluates when they change.\n *\n * @public\n */\nexport function useCanComment(currentUserId: string | null | undefined): boolean {\n\tconst editor = useEditor()\n\treturn useValue('can comment', () => getCanComment(editor, currentUserId), [\n\t\teditor,\n\t\tcurrentUserId,\n\t])\n}\n\n/**\n * The default {@link CommentingOptions.canModifyComment}: a write is its record's owner's to make \u2014\n * a comment's author edits and deletes it, a thread's creator deletes the thread \u2014 and a viewer\n * with no identity may make none of them.\n *\n * Exported so a callback can widen the default rather than restate it:\n * `(ctx) => isModerator(ctx.currentUserId) || defaultCanModifyComment(ctx)`.\n *\n * @public\n */\nexport function defaultCanModifyComment(ctx: CommentModificationContext): boolean {\n\tconst { currentUserId } = ctx\n\tif (!currentUserId) return false\n\tconst owner = ctx.action === 'delete-thread' ? ctx.thread.createdBy : ctx.comment.authorId\n\treturn owner === currentUserId\n}\n\n/**\n * Whether the viewer may make a given write against a given record, per\n * {@link CommentingOptions.canModifyComment} (defaulting to {@link defaultCanModifyComment} when\n * unset). Where this is false the affordance isn't rendered.\n *\n * This is the per-record rule alone: the built-in UI additionally requires\n * {@link CommentingOptions.canComment}, since a viewer who may not participate gets no action\n * affordances at all.\n *\n * A plain, untracked read \u2014 a `canModifyComment` callback that reads signals is not observed. In\n * React, use {@link useCanModifyComment} instead.\n *\n * @public\n */\nexport function getCanModifyComment(\n\teditor: Editor,\n\tcurrentUserId: string | null | undefined,\n\tmodification: CommentModification\n): boolean {\n\tconst { canModifyComment } = getCommentingOptions(editor)\n\tconst ctx: CommentModificationContext = {\n\t\teditor,\n\t\tcurrentUserId: currentUserId ?? null,\n\t\t...modification,\n\t}\n\tif (!canModifyComment) return defaultCanModifyComment(ctx)\n\treturn permits('canModifyComment', () => canModifyComment(ctx))\n}\n\n/**\n * Reactive React hook for {@link getCanModifyComment}: a `canModifyComment` callback that reads\n * signals re-evaluates when they change.\n *\n * @public\n */\nexport function useCanModifyComment(\n\tcurrentUserId: string | null | undefined,\n\tmodification: CommentModification\n): boolean {\n\tconst editor = useEditor()\n\t// Comment records are immutable, so the record itself is what changes when the thing being\n\t// checked changes \u2014 `modification` is a fresh object on every render and can't be a dep.\n\tconst record =\n\t\tmodification.action === 'delete-thread' ? modification.thread : modification.comment\n\treturn useValue(\n\t\t'can modify comment',\n\t\t() => getCanModifyComment(editor, currentUserId, modification),\n\t\t[editor, currentUserId, modification.action, record]\n\t)\n}\n"],
|
|
4
|
+
"sourcesContent": ["import { useMemo, type ComponentType } from 'react'\nimport {\n\ttype Editor,\n\ttype TLComment,\n\ttype TLCommentThread,\n\ttype TLHistoryBatchOptions,\n\ttype TLShapeId,\n\ttype VecLike,\n\tuseEditor,\n\tuseValue,\n} from 'tldraw'\nimport { type CommentListItemRenderProps } from '../ui/comments-list'\nimport { isAllowedReactionEmoji, type EmojiPickerProps } from '../ui/emoji-picker'\nimport { type ReactionTooltipProps } from '../ui/reaction'\n\n/**\n * The gesture that's creating a shape anchor, passed to\n * {@link CommentingOptions.shouldBePrecise}: the target shape, the page point of the release, and\n * whether Alt was held.\n *\n * @public\n */\nexport interface ShapeCommentPrecisionContext {\n\treadonly shapeId: TLShapeId\n\treadonly point: VecLike\n\treadonly altKey: boolean\n}\n\n/**\n * A commenting write that belongs to someone in particular, and the record it targets \u2014 the\n * argument to {@link CommentingOptions.canModifyComment}.\n *\n * Resolving, reopening, reacting, and moving a pin aren't here: none of them is anyone's in\n * particular, so {@link CommentingOptions.canComment} is the only gate on them.\n *\n * @public\n */\nexport type CommentModification =\n\t| { readonly action: 'edit-comment'; readonly comment: TLComment }\n\t| { readonly action: 'delete-comment'; readonly comment: TLComment }\n\t| { readonly action: 'delete-thread'; readonly thread: TLCommentThread }\n\n/**\n * The argument to {@link CommentingOptions.canModifyComment}: which write, against which record,\n * and by whom.\n *\n * @public\n */\nexport type CommentModificationContext = {\n\treadonly editor: Editor\n\treadonly currentUserId: string | null\n} & CommentModification\n\n/**\n * Component overrides for the batteries-included comments layer. Each slot replaces a built-in\n * piece; leave a slot unset to keep its default.\n *\n * @public\n */\nexport interface CommentingComponents {\n\t/** A comment's body. Replaces the default rich-text `<CommentBody>`. */\n\tCommentBody?: ComponentType<{ comment: TLComment }>\n\t/** A pin's inner content. Replaces the author-initial default. */\n\tPinContent?: ComponentType<{ thread: TLCommentThread; comments: TLComment[] }>\n\t/** A sidebar row's preview. Replaces the plaintext default. */\n\tThreadPreview?: ComponentType<{ comment: TLComment }>\n\t/**\n\t * A whole sidebar row. Replaces the default `<CommentListItem>`, which is exported \u2014 so a row\n\t * that only adds an unread dot or a status chip can spread these props into it. Use\n\t * `ThreadPreview` instead when only the preview text is changing.\n\t */\n\tThreadRow?: ComponentType<CommentListItemRenderProps & { thread: TLCommentThread }>\n\t/**\n\t * Extra controls in an open thread's header, added ahead of the built-in resolve and dismiss\n\t * buttons rather than replacing them. \"Copy link\" is already built in whenever the host supplies\n\t * `getThreadHref`.\n\t */\n\tThreadActions?: ComponentType<{ thread: TLCommentThread; comments: TLComment[] }>\n\t/**\n\t * A reaction's visual, given its token. The default renders the token string for the OS emoji\n\t * font. Override to draw a custom palette \u2014 an `<img>`, an SVG, anything. The token is what gets\n\t * stored and synced; this only controls how it's drawn.\n\t */\n\tReactionContent?: ComponentType<{ token: string }>\n\t/**\n\t * What the add-reaction button opens. Replaces the default `<EmojiPicker>` grid. Pairs with\n\t * `ReactionContent` (which draws the tokens this emits) and `isAllowedReaction` (which must\n\t * accept them).\n\t */\n\tReactionPalette?: ComponentType<EmojiPickerProps>\n\t/**\n\t * The hover affordance naming who reacted with an emoji. Receives the reactors and the pill (as\n\t * `children`) and owns the whole thing \u2014 box, size, shape, position. For a wording change,\n\t * translate the `comments.reacted` string instead.\n\t */\n\tReactionTooltip?: ComponentType<ReactionTooltipProps>\n\t/**\n\t * Shown where a composer would sit when the viewer can't compose (see\n\t * {@link CommentingOptions.canComment}). `context` is the surface rendering it: an open thread\n\t * popover (`'thread'`) or the comment tool's placement popover (`'pending'`). Unset, those\n\t * surfaces render nothing.\n\t */\n\tComposerFallback?: ComponentType<{ context: 'pending' | 'thread' }>\n}\n\n/**\n * Configuration for the commenting layer. Static config only \u2014 pass it once via\n * `CommentTool.configure({ ... })`, mirroring `ShapeUtil.configure`. Live, reactive values\n * (`currentUserId`, author resolution, read-status callbacks) are the `CommentingContext`, passed as\n * props to each commenting surface.\n *\n * For defaults, see {@link defaultCommentingOptions}.\n *\n * @example\n * ```tsx\n * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />\n * ```\n *\n * @public\n */\nexport interface CommentingOptions {\n\t// History / undo\n\t/**\n\t * How comment mutations interact with the editor undo stack. Defaults to `'ignore'` \u2014 comments\n\t * are deliberately not undoable (see `TLComment`). `'record'` is a multiplayer footgun: undoing\n\t * a delete resurrects a thread a collaborator already removed. Safe only single-player.\n\t */\n\treadonly history: TLHistoryBatchOptions['history']\n\t/**\n\t * History mode for the pin drag-to-move re-anchor specifically. Unlike posts/edits this is a\n\t * spatial edit that may reasonably be undoable alongside a shape move. Defaults to `history`.\n\t */\n\treadonly dragHistory: TLHistoryBatchOptions['history'] | undefined\n\n\t// Feature toggles\n\t/** Fold nearby pins into count badges as the camera zooms out. */\n\treadonly enableClustering: boolean\n\t/**\n\t * Whether a user may hold several emoji reactions on one comment. `true` (the default) is the\n\t * Slack model: each emoji toggles independently. `false` is single-select: picking a new emoji\n\t * replaces the user's existing reaction. Note this is enforced client-side; the server accepts\n\t * per-emoji records either way.\n\t */\n\treadonly allowMultipleReactions: boolean\n\t/**\n\t * Whether a token may be added as a reaction. Defaults to {@link isAllowedReactionEmoji}, which\n\t * keeps a scripted client from writing junk values the picker would never offer. Override\n\t * alongside a custom `ReactionPalette`. Removals aren't checked \u2014 an off-palette reaction must\n\t * still be clearable.\n\t */\n\tisAllowedReaction(token: string): boolean\n\t/**\n\t * Whether dragging the comment tool out creates a region anchor \u2014 a comment attached to a\n\t * rectangular area, drawn as a dashed box with the pin on the corner the drag released on. Off\n\t * by default, where comments attach to points and shapes only and a drag trails the composer.\n\t */\n\treadonly enableRegions: boolean\n\n\t// Permissions\n\t/**\n\t * Whether the viewer may participate in commenting: composing, editing, deleting, resolving, and\n\t * moving pins. When false, {@link CommentingComponents.ComposerFallback} renders in the\n\t * composer's place and action affordances are hidden. Unset, participation is allowed exactly\n\t * when `currentUserId` is set.\n\t *\n\t * Called during render via {@link useCanComment}, so signal reads are tracked. Posting still\n\t * needs a `currentUserId`, so returning true for a signed-out viewer yields a composer whose\n\t * send button stays disabled. A callback that throws is logged and read as false, rather than\n\t * taking the comments layer down with it.\n\t */\n\treadonly canComment:\n\t\t| ((ctx: { editor: Editor; currentUserId: string | null }) => boolean)\n\t\t| undefined\n\t/**\n\t * Whether the viewer may make a particular write against a particular record: editing or\n\t * deleting a comment, or deleting a thread. Unset, each is its record's owner's to make\n\t * ({@link defaultCanModifyComment}) \u2014 you edit and delete your own comments, and delete threads\n\t * you started. Override it to widen that (a workspace admin or moderator who may remove\n\t * anyone's comment) or to narrow it (no edits after an hour). Where it returns false the\n\t * affordance isn't rendered.\n\t *\n\t * Checked after {@link CommentingOptions.canComment}, which gates commenting as a whole: a\n\t * viewer who may not participate gets no action affordances at all, whatever this returns.\n\t *\n\t * Called during render via {@link useCanModifyComment}, so reactive reads (signals) are tracked.\n\t * A callback that throws is logged and read as false: an affordance is withheld rather than the\n\t * comments layer lost, and a denial is what a server enforcing the same rule would have said.\n\t *\n\t * @example\n\t * ```tsx\n\t * CommentTool.configure({\n\t * \tcanModifyComment: (ctx) =>\n\t * \t\t// Moderators may delete anything; everything else stays the owner's to do.\n\t * \t\t(ctx.action !== 'edit-comment' && isModerator(ctx.currentUserId)) ||\n\t * \t\tdefaultCanModifyComment(ctx),\n\t * })\n\t * ```\n\t */\n\treadonly canModifyComment: ((ctx: CommentModificationContext) => boolean) | undefined\n\n\t// Anchoring\n\t/** Normalized (0\u20131) spot within a shape where imprecise shape pins sit. Default top-right. */\n\treadonly impreciseShapeAnchor: { readonly x: number; readonly y: number }\n\t/**\n\t * Whether a comment landing on a shape pins to the exact clicked spot, or to the shape as a\n\t * whole (rendered at `impreciseShapeAnchor`). Always precise by default; return `false`, or\n\t * decide from the context. Governs new placements only \u2014 existing anchors render as stored.\n\t */\n\tshouldBePrecise(editor: Editor, context: ShapeCommentPrecisionContext): boolean\n\n\t// Components\n\t/** Component overrides. See {@link CommentingComponents}. */\n\treadonly components: CommentingComponents\n}\n\n/**\n * The default {@link CommentingOptions}. Override via `CommentTool.configure({ ... })`.\n *\n * @public\n */\nexport const defaultCommentingOptions = {\n\thistory: 'ignore',\n\tdragHistory: undefined,\n\tenableClustering: true,\n\tallowMultipleReactions: true,\n\tisAllowedReaction: isAllowedReactionEmoji,\n\tenableRegions: false,\n\tcanComment: undefined,\n\tcanModifyComment: undefined,\n\timpreciseShapeAnchor: { x: 1, y: 0 },\n\tshouldBePrecise: () => true,\n\tcomponents: {},\n} as const satisfies CommentingOptions\n\n/**\n * The merged {@link CommentingOptions} for an editor, read off the registered comment tool (which\n * carries them via `CommentTool.configure`). Falls back to {@link defaultCommentingOptions} when\n * the comment tool isn't registered. Usable from anywhere with an `Editor` \u2014 including the tool's\n * own state, which has no React context.\n *\n * @public\n */\nexport function getCommentingOptions(editor: Editor): CommentingOptions {\n\tconst tool = editor.getStateDescendant('comment') as { options?: CommentingOptions } | undefined\n\treturn tool?.options ?? defaultCommentingOptions\n}\n\n/**\n * React hook for {@link getCommentingOptions}. Options are fixed per editor (set at tool\n * registration), so this doesn't need to be reactive.\n *\n * @public\n */\nexport function useCommentingOptions(): CommentingOptions {\n\tconst editor = useEditor()\n\treturn useMemo(() => getCommentingOptions(editor), [editor])\n}\n\n/**\n * Ask a host's permission callback, denying the write if it throws.\n *\n * These are called during render, so an exception in one would take the comments layer down with\n * the answer. Denying costs an affordance, which is what a `false` would have cost anyway, and it\n * can't offer a write a server enforcing the same rule would then reject.\n */\nfunction permits(option: string, check: () => boolean): boolean {\n\ttry {\n\t\treturn check()\n\t} catch (error) {\n\t\tconsole.error(`[tldraw] \\`${option}\\` threw, so the write is denied:`, error)\n\t\treturn false\n\t}\n}\n\n/**\n * Whether the viewer may participate in commenting, per {@link CommentingOptions.canComment}\n * (defaulting to `currentUserId != null` when unset). A callback that throws denies.\n *\n * This is a plain, untracked read \u2014 in React, use {@link useCanComment} instead.\n *\n * @public\n */\nexport function getCanComment(editor: Editor, currentUserId: string | null | undefined): boolean {\n\tconst { canComment } = getCommentingOptions(editor)\n\tif (!canComment) return currentUserId != null\n\treturn permits('canComment', () => canComment({ editor, currentUserId: currentUserId ?? null }))\n}\n\n/**\n * Reactive React hook for {@link getCanComment}: a `canComment` callback that reads signals\n * re-evaluates when they change.\n *\n * @public\n */\nexport function useCanComment(currentUserId: string | null | undefined): boolean {\n\tconst editor = useEditor()\n\treturn useValue('can comment', () => getCanComment(editor, currentUserId), [\n\t\teditor,\n\t\tcurrentUserId,\n\t])\n}\n\n/**\n * The default {@link CommentingOptions.canModifyComment}: a write is its record's owner's to make \u2014\n * a comment's author edits and deletes it, a thread's creator deletes the thread \u2014 and a viewer\n * with no identity may make none of them.\n *\n * Exported so a callback can widen the default rather than restate it:\n * `(ctx) => isModerator(ctx.currentUserId) || defaultCanModifyComment(ctx)`.\n *\n * @public\n */\nexport function defaultCanModifyComment(ctx: CommentModificationContext): boolean {\n\tconst { currentUserId } = ctx\n\tif (!currentUserId) return false\n\tconst owner = ctx.action === 'delete-thread' ? ctx.thread.createdBy : ctx.comment.authorId\n\treturn owner === currentUserId\n}\n\n/**\n * Whether the viewer may make a given write against a given record, per\n * {@link CommentingOptions.canModifyComment} (defaulting to {@link defaultCanModifyComment} when\n * unset). Where this is false the affordance isn't rendered.\n *\n * This is the per-record rule alone: the built-in UI additionally requires\n * {@link CommentingOptions.canComment}, since a viewer who may not participate gets no action\n * affordances at all.\n *\n * A plain, untracked read \u2014 a `canModifyComment` callback that reads signals is not observed. In\n * React, use {@link useCanModifyComment} instead.\n *\n * @public\n */\nexport function getCanModifyComment(\n\teditor: Editor,\n\tcurrentUserId: string | null | undefined,\n\tmodification: CommentModification\n): boolean {\n\tconst { canModifyComment } = getCommentingOptions(editor)\n\tconst ctx: CommentModificationContext = {\n\t\teditor,\n\t\tcurrentUserId: currentUserId ?? null,\n\t\t...modification,\n\t}\n\tif (!canModifyComment) return defaultCanModifyComment(ctx)\n\treturn permits('canModifyComment', () => canModifyComment(ctx))\n}\n\n/**\n * Reactive React hook for {@link getCanModifyComment}: a `canModifyComment` callback that reads\n * signals re-evaluates when they change.\n *\n * @public\n */\nexport function useCanModifyComment(\n\tcurrentUserId: string | null | undefined,\n\tmodification: CommentModification\n): boolean {\n\tconst editor = useEditor()\n\t// Comment records are immutable, so the record itself is what changes when the thing being\n\t// checked changes \u2014 `modification` is a fresh object on every render and can't be a dep.\n\tconst record =\n\t\tmodification.action === 'delete-thread' ? modification.thread : modification.comment\n\treturn useValue(\n\t\t'can modify comment',\n\t\t() => getCanModifyComment(editor, currentUserId, modification),\n\t\t[editor, currentUserId, modification.action, record]\n\t)\n}\n"],
|
|
5
5
|
"mappings": "AAAA,SAAS,eAAmC;AAC5C;AAAA,EAOC;AAAA,EACA;AAAA,OACM;AAEP,SAAS,8BAAqD;AAgNvD,MAAM,2BAA2B;AAAA,EACvC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,wBAAwB;AAAA,EACxB,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,sBAAsB,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,EACnC,iBAAiB,MAAM;AAAA,EACvB,YAAY,CAAC;AACd;AAUO,SAAS,qBAAqB,QAAmC;AACvE,QAAM,OAAO,OAAO,mBAAmB,SAAS;AAChD,SAAO,MAAM,WAAW;AACzB;AAQO,SAAS,uBAA0C;AACzD,QAAM,SAAS,UAAU;AACzB,SAAO,QAAQ,MAAM,qBAAqB,MAAM,GAAG,CAAC,MAAM,CAAC;AAC5D;AASA,SAAS,QAAQ,QAAgB,OAA+B;AAC/D,MAAI;AACH,WAAO,MAAM;AAAA,EACd,SAAS,OAAO;AACf,YAAQ,MAAM,cAAc,MAAM,qCAAqC,KAAK;AAC5E,WAAO;AAAA,EACR;AACD;AAUO,SAAS,cAAc,QAAgB,eAAmD;AAChG,QAAM,EAAE,WAAW,IAAI,qBAAqB,MAAM;AAClD,MAAI,CAAC,WAAY,QAAO,iBAAiB;AACzC,SAAO,QAAQ,cAAc,MAAM,WAAW,EAAE,QAAQ,eAAe,iBAAiB,KAAK,CAAC,CAAC;AAChG;AAQO,SAAS,cAAc,eAAmD;AAChF,QAAM,SAAS,UAAU;AACzB,SAAO,SAAS,eAAe,MAAM,cAAc,QAAQ,aAAa,GAAG;AAAA,IAC1E;AAAA,IACA;AAAA,EACD,CAAC;AACF;AAYO,SAAS,wBAAwB,KAA0C;AACjF,QAAM,EAAE,cAAc,IAAI;AAC1B,MAAI,CAAC,cAAe,QAAO;AAC3B,QAAM,QAAQ,IAAI,WAAW,kBAAkB,IAAI,OAAO,YAAY,IAAI,QAAQ;AAClF,SAAO,UAAU;AAClB;AAgBO,SAAS,oBACf,QACA,eACA,cACU;AACV,QAAM,EAAE,iBAAiB,IAAI,qBAAqB,MAAM;AACxD,QAAM,MAAkC;AAAA,IACvC;AAAA,IACA,eAAe,iBAAiB;AAAA,IAChC,GAAG;AAAA,EACJ;AACA,MAAI,CAAC,iBAAkB,QAAO,wBAAwB,GAAG;AACzD,SAAO,QAAQ,oBAAoB,MAAM,iBAAiB,GAAG,CAAC;AAC/D;AAQO,SAAS,oBACf,eACA,cACU;AACV,QAAM,SAAS,UAAU;AAGzB,QAAM,SACL,aAAa,WAAW,kBAAkB,aAAa,SAAS,aAAa;AAC9E,SAAO;AAAA,IACN;AAAA,IACA,MAAM,oBAAoB,QAAQ,eAAe,YAAY;AAAA,IAC7D,CAAC,QAAQ,eAAe,aAAa,QAAQ,MAAM;AAAA,EACpD;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
4
4
|
import {
|
|
5
5
|
createComment,
|
|
6
6
|
createCommentThread,
|
|
7
|
+
defineMessages,
|
|
7
8
|
EditorPortal,
|
|
8
9
|
usePassThroughWheelEvents,
|
|
9
10
|
useTranslation,
|
|
@@ -23,6 +24,10 @@ import { UNKNOWN_COMMENT_AUTHOR } from "./comment-render.mjs";
|
|
|
23
24
|
import { useIsMobileCommenting, useMobilePlacement } from "./mobile-placement.mjs";
|
|
24
25
|
import { useCanComment, useCommentingOptions } from "./options.mjs";
|
|
25
26
|
import { pendingComment } from "./state.mjs";
|
|
27
|
+
const messages = defineMessages({
|
|
28
|
+
commentsSend: { id: "comments.send", defaultMessage: "Send" },
|
|
29
|
+
commentsAddPlaceholder: { id: "comments.add-placeholder", defaultMessage: "Add a comment\u2026" }
|
|
30
|
+
});
|
|
26
31
|
const stop = (e) => e.stopPropagation();
|
|
27
32
|
function PendingComposer({
|
|
28
33
|
editor,
|
|
@@ -104,8 +109,8 @@ function PendingComposer({
|
|
|
104
109
|
CommentComposer,
|
|
105
110
|
{
|
|
106
111
|
author: me ?? UNKNOWN_COMMENT_AUTHOR,
|
|
107
|
-
placeholder: msg(
|
|
108
|
-
sendLabel: msg(
|
|
112
|
+
placeholder: msg(messages.commentsAddPlaceholder),
|
|
113
|
+
sendLabel: msg(messages.commentsSend),
|
|
109
114
|
value: text,
|
|
110
115
|
onChange: (value) => {
|
|
111
116
|
setText(value);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/pending-composer.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Avatar, isMentionPickerOpen } from '@tldraw/mentions'\nimport { useEffect, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tcreateCommentThread,\n\tEditor,\n\tEditorPortal,\n\tTLRichText,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\tNEW_COMMENT_DRAFT,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { PendingComment } from './comment-tool'\nimport { type CommentingContext } from './context'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport { useCanComment, useCommentingOptions } from './options'\nimport { pendingComment } from './state'\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/**\n * The composer for a thread that doesn't exist yet: the comment tool has placed a point (or\n * region) and is waiting on a first comment. Submitting creates the thread and its comment\n * together; clicking away keeps the draft for the next placement.\n */\nexport function PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveAuthor,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CommentingContext & { editor: Editor; pending: PendingComment }) {\n\tconst ComposerFallback = useCommentingOptions().components.ComposerFallback\n\tconst canComment = useCanComment(currentUserId)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// The leading pin previews the pin this draft becomes: a white pin holding the author's avatar.\n\tconst draftAvatar = (\n\t\t<CommentPin>\n\t\t\t<Avatar author={me ?? UNKNOWN_COMMENT_AUTHOR} />\n\t\t</CommentPin>\n\t)\n\t// Click-away keeps the draft (saved on every change) and the next placement composer\n\t// restores it \u2014 the flip side of dismissing without a discard warning.\n\tconst [text, setText] = useState<TLRichText>(\n\t\t() => getCommentDraft(NEW_COMMENT_DRAFT) ?? EMPTY_COMMENT\n\t)\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst msg = useTranslation()\n\t// Over this floating panel, a scroll reaches the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\n\t// On mobile the composer floats free of the pin so it can clear the software keyboard; desktop\n\t// keeps it pinned to the point.\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, point, isMobile)\n\n\t// Dismiss on a click anywhere outside the composer (capture-phase, ahead of stopPropagation).\n\tuseEffect(() => {\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst el = ref.current\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!el || !target) return\n\t\t\t// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is\n\t\t\t// not \"outside\" \u2014 keep the draft open so the pick can insert.\n\t\t\tif (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return\n\t\t\t// A press on the canvas is the comment tool's to handle: it ends the placement and returns\n\t\t\t// to select. Clearing here first would let that press re-place the composer instead.\n\t\t\tif (editor.getContainer().querySelector('.tl-canvas')?.contains(target)) return\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [editor])\n\n\tconst submit = () => {\n\t\tif (isCommentEmpty(text) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst pageId = editor.getCurrentPageId()\n\t\t\tconst thread = createCommentThread({\n\t\t\t\tpageId,\n\t\t\t\tanchor: pending.anchor,\n\t\t\t\tcreatedBy: currentUserId,\n\t\t\t})\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: text,\n\t\t\t})\n\t\t\tput([thread, comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tclearCommentDraft(NEW_COMMENT_DRAFT)\n\t\tpendingComment.set(editor, null)\n\t\t// Posting ends the placement interaction \u2014 hand the comment tool back to select. Only from\n\t\t// the settled idle state: Enter can land while a fresh press is mid-gesture (the composer\n\t\t// trails the pointer), and switching tools under a held pointer would strand the gesture.\n\t\tif (editor.isIn('comment.idle')) editor.setCurrentTool('select')\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 comment.\n\t\tonPostComment?.(comment)\n\t}\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={[\n\t\t\t\t\t'tlui-cmt-canvas-composer',\n\t\t\t\t\tpending.anchor.type === 'region' && 'tlui-cmt-canvas-composer--region',\n\t\t\t\t\t!canComment && 'tlui-cmt-canvas-composer--fallback',\n\t\t\t\t]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\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\tonKeyDown={(e) => {\n\t\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{canComment ? (\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={me ?? UNKNOWN_COMMENT_AUTHOR}\n\t\t\t\t\t\tplaceholder={msg(
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Avatar, isMentionPickerOpen } from '@tldraw/mentions'\nimport { useEffect, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tcreateCommentThread,\n\tdefineMessages,\n\tEditor,\n\tEditorPortal,\n\tTLRichText,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\tNEW_COMMENT_DRAFT,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { PendingComment } from './comment-tool'\nimport { type CommentingContext } from './context'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport { useCanComment, useCommentingOptions } from './options'\nimport { pendingComment } from './state'\n\nconst messages = defineMessages({\n\tcommentsSend: { id: 'comments.send', defaultMessage: 'Send' },\n\tcommentsAddPlaceholder: { id: 'comments.add-placeholder', defaultMessage: 'Add a comment\u2026' },\n})\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/**\n * The composer for a thread that doesn't exist yet: the comment tool has placed a point (or\n * region) and is waiting on a first comment. Submitting creates the thread and its comment\n * together; clicking away keeps the draft for the next placement.\n */\nexport function PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveAuthor,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CommentingContext & { editor: Editor; pending: PendingComment }) {\n\tconst ComposerFallback = useCommentingOptions().components.ComposerFallback\n\tconst canComment = useCanComment(currentUserId)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// The leading pin previews the pin this draft becomes: a white pin holding the author's avatar.\n\tconst draftAvatar = (\n\t\t<CommentPin>\n\t\t\t<Avatar author={me ?? UNKNOWN_COMMENT_AUTHOR} />\n\t\t</CommentPin>\n\t)\n\t// Click-away keeps the draft (saved on every change) and the next placement composer\n\t// restores it \u2014 the flip side of dismissing without a discard warning.\n\tconst [text, setText] = useState<TLRichText>(\n\t\t() => getCommentDraft(NEW_COMMENT_DRAFT) ?? EMPTY_COMMENT\n\t)\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst msg = useTranslation()\n\t// Over this floating panel, a scroll reaches the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\n\t// On mobile the composer floats free of the pin so it can clear the software keyboard; desktop\n\t// keeps it pinned to the point.\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, point, isMobile)\n\n\t// Dismiss on a click anywhere outside the composer (capture-phase, ahead of stopPropagation).\n\tuseEffect(() => {\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst el = ref.current\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!el || !target) return\n\t\t\t// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is\n\t\t\t// not \"outside\" \u2014 keep the draft open so the pick can insert.\n\t\t\tif (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return\n\t\t\t// A press on the canvas is the comment tool's to handle: it ends the placement and returns\n\t\t\t// to select. Clearing here first would let that press re-place the composer instead.\n\t\t\tif (editor.getContainer().querySelector('.tl-canvas')?.contains(target)) return\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [editor])\n\n\tconst submit = () => {\n\t\tif (isCommentEmpty(text) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst pageId = editor.getCurrentPageId()\n\t\t\tconst thread = createCommentThread({\n\t\t\t\tpageId,\n\t\t\t\tanchor: pending.anchor,\n\t\t\t\tcreatedBy: currentUserId,\n\t\t\t})\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: text,\n\t\t\t})\n\t\t\tput([thread, comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tclearCommentDraft(NEW_COMMENT_DRAFT)\n\t\tpendingComment.set(editor, null)\n\t\t// Posting ends the placement interaction \u2014 hand the comment tool back to select. Only from\n\t\t// the settled idle state: Enter can land while a fresh press is mid-gesture (the composer\n\t\t// trails the pointer), and switching tools under a held pointer would strand the gesture.\n\t\tif (editor.isIn('comment.idle')) editor.setCurrentTool('select')\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 comment.\n\t\tonPostComment?.(comment)\n\t}\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={[\n\t\t\t\t\t'tlui-cmt-canvas-composer',\n\t\t\t\t\tpending.anchor.type === 'region' && 'tlui-cmt-canvas-composer--region',\n\t\t\t\t\t!canComment && 'tlui-cmt-canvas-composer--fallback',\n\t\t\t\t]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\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\tonKeyDown={(e) => {\n\t\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{canComment ? (\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={me ?? UNKNOWN_COMMENT_AUTHOR}\n\t\t\t\t\t\tplaceholder={msg(messages.commentsAddPlaceholder)}\n\t\t\t\t\t\tsendLabel={msg(messages.commentsSend)}\n\t\t\t\t\t\tvalue={text}\n\t\t\t\t\t\tonChange={(value) => {\n\t\t\t\t\t\t\tsetText(value)\n\t\t\t\t\t\t\tsaveCommentDraft(NEW_COMMENT_DRAFT, value)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={submit}\n\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\tdisabled={isCommentEmpty(text) || !currentUserId}\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\tleading={draftAvatar}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\tComposerFallback && <ComposerFallback context=\"pending\" />\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["comment"]
|
|
7
7
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useState
|
|
9
9
|
} from "react";
|
|
10
10
|
import {
|
|
11
|
+
defineMessages,
|
|
11
12
|
useContainer,
|
|
12
13
|
usePassThroughWheelEvents,
|
|
13
14
|
useTranslation,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
import { CommentPin } from "../ui/comment-pin.mjs";
|
|
17
18
|
import { forwardPointerEventToCanvas, isCanvasPanGesture } from "./canvas-events.mjs";
|
|
18
19
|
import { commitCommentMutation } from "./comment-mutations.mjs";
|
|
19
|
-
import {
|
|
20
|
+
import { UNKNOWN_COMMENT_AUTHOR } from "./comment-render.mjs";
|
|
20
21
|
import { useThreadComments } from "./hooks.mjs";
|
|
21
22
|
import { getCommentingOptions, useCanComment, useCommentingOptions } from "./options.mjs";
|
|
22
23
|
import {
|
|
@@ -41,6 +42,14 @@ import {
|
|
|
41
42
|
shapeAnchorAt
|
|
42
43
|
} from "./thread-state.mjs";
|
|
43
44
|
import { POPOVER_OFFSET, ThreadPopover, ThreadView } from "./thread-view.mjs";
|
|
45
|
+
const messages = defineMessages({
|
|
46
|
+
commentsUnknownAuthor: { id: "comments.unknown-author", defaultMessage: "Someone" },
|
|
47
|
+
commentsPinLabel: { id: "comments.pin-label", defaultMessage: "Comment by {name}" },
|
|
48
|
+
commentsPinLabelResolved: {
|
|
49
|
+
id: "comments.pin-label-resolved",
|
|
50
|
+
defaultMessage: "Resolved comment by {name}"
|
|
51
|
+
}
|
|
52
|
+
});
|
|
44
53
|
const THREAD_HEADER_BLOCK = 36;
|
|
45
54
|
const ThreadPin = memo(function ThreadPin2({
|
|
46
55
|
editor,
|
|
@@ -124,8 +133,11 @@ const ThreadPin = memo(function ThreadPin2({
|
|
|
124
133
|
const threadAuthor = resolveAuthor(thread.createdBy);
|
|
125
134
|
const pinContent = PinContent ? /* @__PURE__ */ jsx(PinContent, { thread, comments }) : /* @__PURE__ */ jsx(Avatar, { author: threadAuthor ?? UNKNOWN_COMMENT_AUTHOR });
|
|
126
135
|
const pinLabel = msg(
|
|
127
|
-
thread.resolved ?
|
|
128
|
-
|
|
136
|
+
thread.resolved ? messages.commentsPinLabelResolved : messages.commentsPinLabel,
|
|
137
|
+
{
|
|
138
|
+
name: threadAuthor?.name ?? msg(messages.commentsUnknownAuthor)
|
|
139
|
+
}
|
|
140
|
+
);
|
|
129
141
|
const isRegion = thread.anchor.type === "region";
|
|
130
142
|
const startDrag = (e) => {
|
|
131
143
|
if (isCanvasPanGesture(editor, e)) {
|