@tldraw/commenting 0.0.0-bootstrap → 5.3.0-canary.31036673d3f8
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/README.md +0 -2
- package/commenting.css +1139 -329
- package/dist-cjs/canvas/anchor-lifecycle.js +148 -0
- package/dist-cjs/canvas/anchor-lifecycle.js.map +7 -0
- package/dist-cjs/canvas/cluster-input.js +2 -2
- package/dist-cjs/canvas/cluster-input.js.map +2 -2
- package/dist-cjs/canvas/comment-body.js +1 -1
- package/dist-cjs/canvas/comment-body.js.map +2 -2
- package/dist-cjs/canvas/comment-drafts.js +54 -0
- package/dist-cjs/canvas/comment-drafts.js.map +7 -0
- package/dist-cjs/canvas/comment-mutations.js +123 -0
- package/dist-cjs/canvas/comment-mutations.js.map +7 -0
- package/dist-cjs/canvas/comment-reactions.js +163 -0
- package/dist-cjs/canvas/comment-reactions.js.map +7 -0
- package/dist-cjs/canvas/comment-render.js +5 -3
- package/dist-cjs/canvas/comment-render.js.map +2 -2
- package/dist-cjs/canvas/comment-store.js +19 -9
- package/dist-cjs/canvas/comment-store.js.map +2 -2
- package/dist-cjs/canvas/comment-tool.js +64 -14
- package/dist-cjs/canvas/comment-tool.js.map +2 -2
- package/dist-cjs/canvas/comments-filter-menu.js +20 -38
- package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overflow-menu.js +22 -27
- package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overlay.js +429 -474
- package/dist-cjs/canvas/comments-overlay.js.map +2 -2
- package/dist-cjs/canvas/comments-sidebar.js +35 -32
- package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
- package/dist-cjs/canvas/comments-visibility-toggle.js +65 -0
- package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
- package/dist-cjs/canvas/context.js +17 -0
- package/dist-cjs/canvas/context.js.map +7 -0
- package/dist-cjs/canvas/hooks.js +22 -3
- package/dist-cjs/canvas/hooks.js.map +2 -2
- package/dist-cjs/canvas/options.js +19 -2
- package/dist-cjs/canvas/options.js.map +2 -2
- package/dist-cjs/canvas/pin-stacking.js +56 -0
- package/dist-cjs/canvas/pin-stacking.js.map +7 -0
- package/dist-cjs/canvas/sidebar-filters.js +2 -2
- package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
- package/dist-cjs/canvas/state.js +28 -14
- package/dist-cjs/canvas/state.js.map +2 -2
- package/dist-cjs/canvas/thread-preview.js +185 -0
- package/dist-cjs/canvas/thread-preview.js.map +7 -0
- package/dist-cjs/canvas/thread-stack.js +199 -0
- package/dist-cjs/canvas/thread-stack.js.map +7 -0
- package/dist-cjs/canvas/thread-state.js +67 -21
- package/dist-cjs/canvas/thread-state.js.map +2 -2
- package/dist-cjs/canvas/thread-view.js +397 -0
- package/dist-cjs/canvas/thread-view.js.map +7 -0
- package/dist-cjs/clustering/computeClusterTable.js +4 -4
- package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
- package/dist-cjs/clustering/runtime.js +119 -7
- package/dist-cjs/clustering/runtime.js.map +2 -2
- package/dist-cjs/clustering/types.js.map +1 -1
- package/dist-cjs/index.d.ts +763 -333
- package/dist-cjs/index.js +46 -28
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/ui/byline.js +6 -4
- package/dist-cjs/ui/byline.js.map +2 -2
- package/dist-cjs/ui/comment-card.js +15 -8
- package/dist-cjs/ui/comment-card.js.map +2 -2
- package/dist-cjs/ui/comment-composer.js +137 -36
- package/dist-cjs/ui/comment-composer.js.map +2 -2
- package/dist-cjs/ui/comment-pin.js +24 -2
- package/dist-cjs/ui/comment-pin.js.map +2 -2
- package/dist-cjs/ui/comment-thread.js +9 -7
- package/dist-cjs/ui/comment-thread.js.map +2 -2
- package/dist-cjs/ui/comments-list.js +30 -18
- package/dist-cjs/ui/comments-list.js.map +2 -2
- package/dist-cjs/ui/count-badge.js +3 -2
- package/dist-cjs/ui/count-badge.js.map +2 -2
- package/dist-cjs/ui/emoji-picker.js +54 -0
- package/dist-cjs/ui/emoji-picker.js.map +7 -0
- package/dist-cjs/ui/empty-state.js +3 -3
- package/dist-cjs/ui/empty-state.js.map +2 -2
- package/dist-cjs/ui/format-time.js +4 -1
- package/dist-cjs/ui/format-time.js.map +2 -2
- package/dist-cjs/ui/reaction-picker.js +73 -0
- package/dist-cjs/ui/reaction-picker.js.map +7 -0
- package/dist-cjs/ui/reaction.js +59 -6
- package/dist-cjs/ui/reaction.js.map +2 -2
- package/dist-cjs/ui/reactions.js +20 -7
- package/dist-cjs/ui/reactions.js.map +2 -2
- package/dist-cjs/ui/{mention.js → reply-count.js} +9 -11
- package/dist-cjs/ui/reply-count.js.map +7 -0
- package/dist-cjs/ui/send-button.js +20 -1
- package/dist-cjs/ui/send-button.js.map +2 -2
- package/dist-cjs/ui/{comment-text.js → tooltip-button.js} +12 -9
- package/dist-cjs/ui/tooltip-button.js.map +7 -0
- package/dist-esm/canvas/anchor-lifecycle.mjs +128 -0
- package/dist-esm/canvas/anchor-lifecycle.mjs.map +7 -0
- package/dist-esm/canvas/cluster-input.mjs +2 -2
- package/dist-esm/canvas/cluster-input.mjs.map +2 -2
- package/dist-esm/canvas/comment-body.mjs +1 -1
- package/dist-esm/canvas/comment-body.mjs.map +2 -2
- package/dist-esm/canvas/comment-drafts.mjs +34 -0
- package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
- package/dist-esm/canvas/comment-mutations.mjs +103 -0
- package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
- package/dist-esm/canvas/comment-reactions.mjs +152 -0
- package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
- package/dist-esm/canvas/comment-render.mjs +5 -3
- package/dist-esm/canvas/comment-render.mjs.map +2 -2
- package/dist-esm/canvas/comment-store.mjs +19 -9
- package/dist-esm/canvas/comment-store.mjs.map +2 -2
- package/dist-esm/canvas/comment-tool.mjs +67 -17
- package/dist-esm/canvas/comment-tool.mjs.map +2 -2
- package/dist-esm/canvas/comments-filter-menu.mjs +20 -38
- package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overflow-menu.mjs +23 -27
- package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overlay.mjs +450 -479
- package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
- package/dist-esm/canvas/comments-sidebar.mjs +38 -35
- package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
- package/dist-esm/canvas/comments-visibility-toggle.mjs +45 -0
- package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
- package/dist-esm/canvas/context.mjs +1 -0
- package/dist-esm/canvas/context.mjs.map +7 -0
- package/dist-esm/canvas/hooks.mjs +28 -5
- package/dist-esm/canvas/hooks.mjs.map +2 -2
- package/dist-esm/canvas/options.mjs +21 -3
- package/dist-esm/canvas/options.mjs.map +2 -2
- package/dist-esm/canvas/pin-stacking.mjs +36 -0
- package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
- package/dist-esm/canvas/sidebar-filters.mjs +2 -2
- package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
- package/dist-esm/canvas/state.mjs +29 -19
- package/dist-esm/canvas/state.mjs.map +2 -2
- package/dist-esm/canvas/thread-preview.mjs +170 -0
- package/dist-esm/canvas/thread-preview.mjs.map +7 -0
- package/dist-esm/canvas/thread-stack.mjs +190 -0
- package/dist-esm/canvas/thread-stack.mjs.map +7 -0
- package/dist-esm/canvas/thread-state.mjs +71 -22
- package/dist-esm/canvas/thread-state.mjs.map +2 -2
- package/dist-esm/canvas/thread-view.mjs +402 -0
- package/dist-esm/canvas/thread-view.mjs.map +7 -0
- package/dist-esm/clustering/computeClusterTable.mjs +4 -4
- package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
- package/dist-esm/clustering/runtime.mjs +119 -7
- package/dist-esm/clustering/runtime.mjs.map +2 -2
- package/dist-esm/index.d.mts +763 -333
- package/dist-esm/index.mjs +87 -41
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/ui/byline.mjs +6 -4
- package/dist-esm/ui/byline.mjs.map +2 -2
- package/dist-esm/ui/comment-card.mjs +15 -8
- package/dist-esm/ui/comment-card.mjs.map +2 -2
- package/dist-esm/ui/comment-composer.mjs +148 -36
- package/dist-esm/ui/comment-composer.mjs.map +2 -2
- package/dist-esm/ui/comment-pin.mjs +24 -2
- package/dist-esm/ui/comment-pin.mjs.map +2 -2
- package/dist-esm/ui/comment-thread.mjs +9 -7
- package/dist-esm/ui/comment-thread.mjs.map +2 -2
- package/dist-esm/ui/comments-list.mjs +43 -29
- package/dist-esm/ui/comments-list.mjs.map +2 -2
- package/dist-esm/ui/count-badge.mjs +3 -2
- package/dist-esm/ui/count-badge.mjs.map +2 -2
- package/dist-esm/ui/emoji-picker.mjs +34 -0
- package/dist-esm/ui/emoji-picker.mjs.map +7 -0
- package/dist-esm/ui/empty-state.mjs +3 -3
- package/dist-esm/ui/empty-state.mjs.map +2 -2
- package/dist-esm/ui/format-time.mjs +4 -1
- package/dist-esm/ui/format-time.mjs.map +2 -2
- package/dist-esm/ui/reaction-picker.mjs +60 -0
- package/dist-esm/ui/reaction-picker.mjs.map +7 -0
- package/dist-esm/ui/reaction.mjs +60 -7
- package/dist-esm/ui/reaction.mjs.map +2 -2
- package/dist-esm/ui/reactions.mjs +21 -8
- package/dist-esm/ui/reactions.mjs.map +2 -2
- package/dist-esm/ui/reply-count.mjs +9 -0
- package/dist-esm/ui/reply-count.mjs.map +7 -0
- package/dist-esm/ui/send-button.mjs +20 -1
- package/dist-esm/ui/send-button.mjs.map +2 -2
- package/dist-esm/ui/tooltip-button.mjs +12 -0
- package/dist-esm/ui/tooltip-button.mjs.map +7 -0
- package/package.json +6 -6
- package/src/canvas/anchor-lifecycle.test.ts +290 -0
- package/src/canvas/anchor-lifecycle.ts +205 -0
- package/src/canvas/canvas.css +461 -46
- package/src/canvas/cluster-input.test.ts +31 -23
- package/src/canvas/cluster-input.ts +3 -4
- package/src/canvas/comment-body.tsx +1 -1
- package/src/canvas/comment-drafts.test.ts +55 -0
- package/src/canvas/comment-drafts.ts +46 -0
- package/src/canvas/comment-mutations.test.ts +410 -0
- package/src/canvas/comment-mutations.ts +288 -0
- package/src/canvas/comment-reactions.test.ts +132 -0
- package/src/canvas/comment-reactions.tsx +245 -0
- package/src/canvas/comment-render.test.ts +1 -1
- package/src/canvas/comment-render.ts +9 -5
- package/src/canvas/comment-store.ts +67 -27
- package/src/canvas/comment-tool.test.ts +162 -0
- package/src/canvas/comment-tool.tsx +105 -23
- package/src/canvas/comments-filter-menu.tsx +23 -32
- package/src/canvas/comments-overflow-menu.tsx +14 -21
- package/src/canvas/comments-overlay.tsx +695 -622
- package/src/canvas/comments-sidebar.test.ts +45 -0
- package/src/canvas/comments-sidebar.tsx +76 -58
- package/src/canvas/comments-visibility-toggle.tsx +49 -0
- package/src/canvas/context.ts +47 -0
- package/src/canvas/hooks.ts +52 -11
- package/src/canvas/options.test.ts +52 -2
- package/src/canvas/options.ts +133 -9
- package/src/canvas/pin-stacking.test.ts +114 -0
- package/src/canvas/pin-stacking.ts +57 -0
- package/src/canvas/sidebar-filters.ts +4 -3
- package/src/canvas/state.ts +92 -35
- package/src/canvas/thread-preview.test.ts +91 -0
- package/src/canvas/thread-preview.tsx +304 -0
- package/src/canvas/thread-stack.tsx +252 -0
- package/src/canvas/thread-state.test.ts +209 -0
- package/src/canvas/thread-state.ts +137 -36
- package/src/canvas/thread-view.tsx +516 -0
- package/src/clustering/computeClusterTable.test.ts +24 -23
- package/src/clustering/computeClusterTable.ts +5 -5
- package/src/clustering/runtime.test.ts +217 -7
- package/src/clustering/runtime.ts +171 -17
- package/src/clustering/types.ts +9 -9
- package/src/index.ts +76 -40
- package/src/ui/byline.tsx +7 -5
- package/src/ui/comment-card.tsx +20 -10
- package/src/ui/comment-composer.tsx +206 -40
- package/src/ui/comment-pin.tsx +26 -2
- package/src/ui/comment-thread.tsx +13 -6
- package/src/ui/comments-list.test.ts +31 -0
- package/src/ui/comments-list.tsx +45 -21
- package/src/ui/comments.css +566 -281
- package/src/ui/count-badge.tsx +9 -2
- package/src/ui/emoji-picker.test.ts +25 -0
- package/src/ui/emoji-picker.tsx +76 -0
- package/src/ui/empty-state.tsx +3 -3
- package/src/ui/format-time.ts +7 -2
- package/src/ui/reaction-picker.tsx +109 -0
- package/src/ui/reaction.tsx +121 -6
- package/src/ui/reactions.tsx +69 -10
- package/src/ui/reply-count.ts +16 -0
- package/src/ui/send-button.tsx +18 -3
- package/src/ui/tooltip-button.tsx +20 -0
- package/dist-cjs/canvas/region-options.js +0 -49
- package/dist-cjs/canvas/region-options.js.map +0 -7
- package/dist-cjs/ui/avatar.js +0 -43
- package/dist-cjs/ui/avatar.js.map +0 -7
- package/dist-cjs/ui/comment-mention.js +0 -42
- package/dist-cjs/ui/comment-mention.js.map +0 -7
- package/dist-cjs/ui/comment-text.js.map +0 -7
- package/dist-cjs/ui/mention-list.js +0 -70
- package/dist-cjs/ui/mention-list.js.map +0 -7
- package/dist-cjs/ui/mention-suggestion.js +0 -206
- package/dist-cjs/ui/mention-suggestion.js.map +0 -7
- package/dist-cjs/ui/mention.js.map +0 -7
- package/dist-cjs/ui/render-markdown.js +0 -63
- package/dist-cjs/ui/render-markdown.js.map +0 -7
- package/dist-esm/canvas/region-options.mjs +0 -29
- package/dist-esm/canvas/region-options.mjs.map +0 -7
- package/dist-esm/ui/avatar.mjs +0 -23
- package/dist-esm/ui/avatar.mjs.map +0 -7
- package/dist-esm/ui/comment-mention.mjs +0 -22
- package/dist-esm/ui/comment-mention.mjs.map +0 -7
- package/dist-esm/ui/comment-text.mjs +0 -9
- package/dist-esm/ui/comment-text.mjs.map +0 -7
- package/dist-esm/ui/mention-list.mjs +0 -50
- package/dist-esm/ui/mention-list.mjs.map +0 -7
- package/dist-esm/ui/mention-suggestion.mjs +0 -186
- package/dist-esm/ui/mention-suggestion.mjs.map +0 -7
- package/dist-esm/ui/mention.mjs +0 -11
- package/dist-esm/ui/mention.mjs.map +0 -7
- package/dist-esm/ui/render-markdown.mjs +0 -45
- package/dist-esm/ui/render-markdown.mjs.map +0 -7
- package/src/canvas/region-options.ts +0 -57
- package/src/ui/avatar.tsx +0 -31
- package/src/ui/comment-mention.ts +0 -47
- package/src/ui/comment-text.tsx +0 -12
- package/src/ui/mention-list.tsx +0 -106
- package/src/ui/mention-suggestion.test.ts +0 -18
- package/src/ui/mention-suggestion.tsx +0 -285
- package/src/ui/mention.tsx +0 -9
- package/src/ui/render-markdown.tsx +0 -72
package/dist-esm/ui/reaction.mjs
CHANGED
|
@@ -1,11 +1,64 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { TldrawUiTooltip, useTranslation } from "tldraw";
|
|
3
|
+
function defaultRenderReaction(token) {
|
|
4
|
+
return token;
|
|
5
|
+
}
|
|
6
|
+
function Reaction({
|
|
7
|
+
emoji,
|
|
8
|
+
count,
|
|
9
|
+
active,
|
|
10
|
+
reactors,
|
|
11
|
+
enableHoverList = true,
|
|
12
|
+
renderReaction = defaultRenderReaction,
|
|
13
|
+
ReactionTooltip = DefaultReactionTooltip,
|
|
14
|
+
onClick
|
|
15
|
+
}) {
|
|
16
|
+
const pill = /* @__PURE__ */ jsxs(
|
|
17
|
+
"button",
|
|
18
|
+
{
|
|
19
|
+
className: active ? "tlui-cmt-reaction tlui-cmt-reaction--active" : "tlui-cmt-reaction",
|
|
20
|
+
type: "button",
|
|
21
|
+
"aria-label": `${emoji} ${count}`,
|
|
22
|
+
"aria-pressed": active,
|
|
23
|
+
onClick,
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ jsx("span", { className: "tlui-cmt-reaction__emoji", children: renderReaction(emoji) }),
|
|
26
|
+
/* @__PURE__ */ jsx("span", { className: "tlui-cmt-reaction__count", children: count })
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
if (!enableHoverList || reactors.length === 0) return pill;
|
|
31
|
+
return /* @__PURE__ */ jsx(ReactionTooltip, { reactors, children: pill });
|
|
32
|
+
}
|
|
33
|
+
function DefaultReactionTooltip({ reactors, children }) {
|
|
34
|
+
return /* @__PURE__ */ jsx(TldrawUiTooltip, { side: "bottom", content: /* @__PURE__ */ jsx(DefaultReactionTooltipContent, { reactors }), children });
|
|
35
|
+
}
|
|
36
|
+
function DefaultReactionTooltipContent({ reactors }) {
|
|
37
|
+
const msg = useTranslation();
|
|
38
|
+
const names = reactors.map(
|
|
39
|
+
(reactor) => reactor.you ? msg("comments.mention-you") : reactor.name
|
|
40
|
+
);
|
|
41
|
+
const [a, b, c] = names;
|
|
42
|
+
switch (names.length) {
|
|
43
|
+
case 0:
|
|
44
|
+
return null;
|
|
45
|
+
case 1:
|
|
46
|
+
return /* @__PURE__ */ jsx(Fragment, { children: msg("comments.reacted-1").replace("{a}", a) });
|
|
47
|
+
case 2:
|
|
48
|
+
return /* @__PURE__ */ jsx(Fragment, { children: msg("comments.reacted-2").replace("{a}", a).replace("{b}", b) });
|
|
49
|
+
case 3:
|
|
50
|
+
return /* @__PURE__ */ jsx(Fragment, { children: msg("comments.reacted-3").replace("{a}", a).replace("{b}", b).replace("{c}", c) });
|
|
51
|
+
default: {
|
|
52
|
+
const others = names.length - 3;
|
|
53
|
+
const template = others === 1 ? "comments.reacted-more-one" : "comments.reacted-more";
|
|
54
|
+
return /* @__PURE__ */ jsx(Fragment, { children: msg(template).replace("{a}", a).replace("{b}", b).replace("{c}", c).replace("{count}", String(others)) });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
7
57
|
}
|
|
8
58
|
export {
|
|
9
|
-
|
|
59
|
+
DefaultReactionTooltip,
|
|
60
|
+
DefaultReactionTooltipContent,
|
|
61
|
+
Reaction,
|
|
62
|
+
defaultRenderReaction
|
|
10
63
|
};
|
|
11
64
|
//# sourceMappingURL=reaction.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ui/reaction.tsx"],
|
|
4
|
-
"sourcesContent": ["/** @public */\nexport interface ReactionProps {\n\temoji: string\n\tcount: number\n\tactive: boolean\n
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { ComponentType, ReactNode } from 'react'\nimport { TldrawUiTooltip, useTranslation } from 'tldraw'\nimport { ReactionReactor } from './reactions'\n\n/** Render a reaction token to its visual \u2014 the emoji glyph by default. @public */\nexport type RenderReaction = (token: string) => ReactNode\n\n/** The default reaction renderer: emits the token string for the OS emoji font to draw. @public */\nexport function defaultRenderReaction(token: string): ReactNode {\n\treturn token\n}\n\n/** @public */\nexport interface ReactionProps {\n\temoji: string\n\tcount: number\n\tactive: boolean\n\t/** How to draw the emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n\t/** Who reacted with this emoji, in reaction order \u2014 shown when the pill is hovered. */\n\treactors: ReactionReactor[]\n\t/** Whether hovering shows the reactor list. Pass false to suppress it \u2014 e.g. while another\n\t * popup menu on the comment is open. Defaults to true. */\n\tenableHoverList?: boolean\n\t/** The hover affordance for the pill, naming who reacted. It receives the reactors and the pill\n\t * itself (as `children`) and returns the whole thing \u2014 so it owns the tooltip, its box, size,\n\t * shape, and position, not just the text inside. Defaults to `DefaultReactionTooltip`, which\n\t * hangs the built-in inline sentence below the pill. */\n\tReactionTooltip?: ComponentType<ReactionTooltipProps>\n\t/** Called when the pill is pressed \u2014 toggles the current user's reaction. */\n\tonClick?(): void\n}\n\n/**\n * A single emoji reaction pill with a count, highlighted when the user reacted. Hovering it shows a\n * tooltip naming who reacted (the current user included), when `enableHoverList` is set.\n * @public @react\n */\nexport function Reaction({\n\temoji,\n\tcount,\n\tactive,\n\treactors,\n\tenableHoverList = true,\n\trenderReaction = defaultRenderReaction,\n\tReactionTooltip = DefaultReactionTooltip,\n\tonClick,\n}: ReactionProps) {\n\tconst pill = (\n\t\t<button\n\t\t\tclassName={active ? 'tlui-cmt-reaction tlui-cmt-reaction--active' : 'tlui-cmt-reaction'}\n\t\t\ttype=\"button\"\n\t\t\t// The emoji alone announces as its unicode name (\"thumbs up\"); pairing it with the count\n\t\t\t// is what makes the pill's state legible to a screen reader.\n\t\t\taria-label={`${emoji} ${count}`}\n\t\t\taria-pressed={active}\n\t\t\tonClick={onClick}\n\t\t>\n\t\t\t<span className=\"tlui-cmt-reaction__emoji\">{renderReaction(emoji)}</span>\n\t\t\t<span className=\"tlui-cmt-reaction__count\">{count}</span>\n\t\t</button>\n\t)\n\n\t// A bare pill when hovering is suppressed, or when there's no one to name.\n\tif (!enableHoverList || reactors.length === 0) return pill\n\n\t// Hand the whole hover affordance to the tooltip component: it wraps the pill and owns how the\n\t// reactor list is presented.\n\treturn <ReactionTooltip reactors={reactors}>{pill}</ReactionTooltip>\n}\n\n/** @public */\nexport interface ReactionTooltipProps {\n\t/** Who reacted with this emoji, in reaction order. */\n\treactors: ReactionReactor[]\n\t/** The reaction pill to wrap. Anchor the hover affordance to this and render it. */\n\tchildren: ReactNode\n}\n\n/**\n * The default reactor tooltip: hangs the built-in inline sentence (`DefaultReactionTooltipContent`)\n * in a standard tooltip below the pill \u2014 the same tooltip the toolbar uses. Replace it via\n * `CommentingComponents.ReactionTooltip` to present the reactor list any other way (a different box,\n * avatars, a banner, anywhere on screen); it receives the pill as `children`.\n * @public @react\n */\nexport function DefaultReactionTooltip({ reactors, children }: ReactionTooltipProps) {\n\treturn (\n\t\t<TldrawUiTooltip side=\"bottom\" content={<DefaultReactionTooltipContent reactors={reactors} />}>\n\t\t\t{children}\n\t\t</TldrawUiTooltip>\n\t)\n}\n\n/**\n * The default reactor sentence naming who reacted \u2014 up to three names spelled out, then \"and N\n * others\" (e.g. \"You reacted\", \"You and Bo reacted\", \"You, Bo and Ada reacted\", \"You, Bo, Ada and 2\n * others reacted\"). The wording lives in the `comments.reacted-*` translation strings so each locale\n * controls the grammar. Exported so a custom `ReactionTooltip` can reuse it inside its own box.\n * @public @react\n */\nexport function DefaultReactionTooltipContent({ reactors }: { reactors: ReactionReactor[] }) {\n\tconst msg = useTranslation()\n\tconst names = reactors.map((reactor) =>\n\t\treactor.you ? msg('comments.mention-you') : reactor.name\n\t)\n\tconst [a, b, c] = names\n\tswitch (names.length) {\n\t\tcase 0:\n\t\t\treturn null\n\t\tcase 1:\n\t\t\treturn <>{msg('comments.reacted-1').replace('{a}', a)}</>\n\t\tcase 2:\n\t\t\treturn <>{msg('comments.reacted-2').replace('{a}', a).replace('{b}', b)}</>\n\t\tcase 3:\n\t\t\treturn <>{msg('comments.reacted-3').replace('{a}', a).replace('{b}', b).replace('{c}', c)}</>\n\t\tdefault: {\n\t\t\tconst others = names.length - 3\n\t\t\tconst template = others === 1 ? 'comments.reacted-more-one' : 'comments.reacted-more'\n\t\t\treturn (\n\t\t\t\t<>\n\t\t\t\t\t{msg(template)\n\t\t\t\t\t\t.replace('{a}', a)\n\t\t\t\t\t\t.replace('{b}', b)\n\t\t\t\t\t\t.replace('{c}', c)\n\t\t\t\t\t\t.replace('{count}', String(others))}\n\t\t\t\t</>\n\t\t\t)\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "AAiDE,SA8DQ,UArDP,KATD;AAhDF,SAAS,iBAAiB,sBAAsB;AAOzC,SAAS,sBAAsB,OAA0B;AAC/D,SAAO;AACR;AA4BO,SAAS,SAAS;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB;AACD,GAAkB;AACjB,QAAM,OACL;AAAA,IAAC;AAAA;AAAA,MACA,WAAW,SAAS,gDAAgD;AAAA,MACpE,MAAK;AAAA,MAGL,cAAY,GAAG,KAAK,IAAI,KAAK;AAAA,MAC7B,gBAAc;AAAA,MACd;AAAA,MAEA;AAAA,4BAAC,UAAK,WAAU,4BAA4B,yBAAe,KAAK,GAAE;AAAA,QAClE,oBAAC,UAAK,WAAU,4BAA4B,iBAAM;AAAA;AAAA;AAAA,EACnD;AAID,MAAI,CAAC,mBAAmB,SAAS,WAAW,EAAG,QAAO;AAItD,SAAO,oBAAC,mBAAgB,UAAqB,gBAAK;AACnD;AAiBO,SAAS,uBAAuB,EAAE,UAAU,SAAS,GAAyB;AACpF,SACC,oBAAC,mBAAgB,MAAK,UAAS,SAAS,oBAAC,iCAA8B,UAAoB,GACzF,UACF;AAEF;AASO,SAAS,8BAA8B,EAAE,SAAS,GAAoC;AAC5F,QAAM,MAAM,eAAe;AAC3B,QAAM,QAAQ,SAAS;AAAA,IAAI,CAAC,YAC3B,QAAQ,MAAM,IAAI,sBAAsB,IAAI,QAAQ;AAAA,EACrD;AACA,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI;AAClB,UAAQ,MAAM,QAAQ;AAAA,IACrB,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO,gCAAG,cAAI,oBAAoB,EAAE,QAAQ,OAAO,CAAC,GAAE;AAAA,IACvD,KAAK;AACJ,aAAO,gCAAG,cAAI,oBAAoB,EAAE,QAAQ,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,GAAE;AAAA,IACzE,KAAK;AACJ,aAAO,gCAAG,cAAI,oBAAoB,EAAE,QAAQ,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,GAAE;AAAA,IAC3F,SAAS;AACR,YAAM,SAAS,MAAM,SAAS;AAC9B,YAAM,WAAW,WAAW,IAAI,8BAA8B;AAC9D,aACC,gCACE,cAAI,QAAQ,EACX,QAAQ,OAAO,CAAC,EAChB,QAAQ,OAAO,CAAC,EAChB,QAAQ,OAAO,CAAC,EAChB,QAAQ,WAAW,OAAO,MAAM,CAAC,GACpC;AAAA,IAEF;AAAA,EACD;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
import { jsx
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Reaction } from "./reaction.mjs";
|
|
3
|
-
function Reactions(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
function Reactions({
|
|
4
|
+
reactions,
|
|
5
|
+
onToggle,
|
|
6
|
+
canReact = true,
|
|
7
|
+
enableHoverList = true,
|
|
8
|
+
renderReaction,
|
|
9
|
+
ReactionTooltip
|
|
10
|
+
}) {
|
|
11
|
+
if (reactions.length === 0) return null;
|
|
12
|
+
return /* @__PURE__ */ jsx("div", { className: "tlui-cmt-reactions", children: reactions.map((reaction) => /* @__PURE__ */ jsx(
|
|
13
|
+
Reaction,
|
|
14
|
+
{
|
|
15
|
+
...reaction,
|
|
16
|
+
enableHoverList,
|
|
17
|
+
renderReaction,
|
|
18
|
+
ReactionTooltip,
|
|
19
|
+
onClick: canReact ? () => onToggle?.(reaction.emoji) : void 0
|
|
20
|
+
},
|
|
21
|
+
reaction.emoji
|
|
22
|
+
)) });
|
|
10
23
|
}
|
|
11
24
|
export {
|
|
12
25
|
Reactions
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ui/reactions.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Reaction } from './reaction'\n\n/** The row of reactions under a comment
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { ComponentType } from 'react'\nimport { Reaction, ReactionTooltipProps, RenderReaction } from './reaction'\n\n/** One person who reacted with a given emoji, for the hover list. @public */\nexport interface ReactionReactor {\n\t/** Display name of the person who reacted. */\n\tname: string\n\t/** True for the current user. */\n\tyou: boolean\n}\n\n/** One emoji's reactions on a comment, already tallied. @public */\nexport interface ReactionSummary {\n\temoji: string\n\t/** How many people reacted with this emoji. */\n\tcount: number\n\t/** True when the current user is one of them \u2014 renders the pill highlighted. */\n\tactive: boolean\n\t/** Who reacted with this emoji, in reaction order \u2014 shown in the hover list. */\n\treactors: ReactionReactor[]\n}\n\n/** @public */\nexport interface ReactionsProps {\n\t/** The comment's reactions, grouped by emoji. Empty renders nothing. */\n\treactions: ReactionSummary[]\n\t/** Toggles the current user's reaction for an emoji. */\n\tonToggle?(emoji: string): void\n\t/** Whether the current user may react. False makes the pills inert (e.g. a signed-out\n\t * viewer, or a read-only thread) while still showing counts. */\n\tcanReact?: boolean\n\t/** Whether hovering a pill shows its reactor list. Pass false to suppress it \u2014 e.g. while\n\t * another popup menu on the comment is open. Defaults to true. */\n\tenableHoverList?: boolean\n\t/** How to draw each emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n\t/** The tooltip naming who reacted, shown when a pill is hovered. Defaults to an inline sentence\n\t * (`DefaultReactionTooltip`). Swap it to render the reactor list however you like. */\n\tReactionTooltip?: ComponentType<ReactionTooltipProps>\n}\n\n/**\n * The row of tallied reactions under a comment. Presentational \u2014 the host supplies the summaries\n * and owns what toggling does. Hovering a pill lists who reacted (see `Reaction`).\n *\n * The add-reaction affordance is a separate component (`ReactionPicker`) so it can live outside\n * this row \u2014 on a comment card it sits with the card's hover actions, which keeps its position\n * fixed as reactions are added here.\n * @public @react\n */\nexport function Reactions({\n\treactions,\n\tonToggle,\n\tcanReact = true,\n\tenableHoverList = true,\n\trenderReaction,\n\tReactionTooltip,\n}: ReactionsProps) {\n\tif (reactions.length === 0) return null\n\treturn (\n\t\t<div className=\"tlui-cmt-reactions\">\n\t\t\t{reactions.map((reaction) => (\n\t\t\t\t<Reaction\n\t\t\t\t\tkey={reaction.emoji}\n\t\t\t\t\t{...reaction}\n\t\t\t\t\tenableHoverList={enableHoverList}\n\t\t\t\t\trenderReaction={renderReaction}\n\t\t\t\t\tReactionTooltip={ReactionTooltip}\n\t\t\t\t\tonClick={canReact ? () => onToggle?.(reaction.emoji) : undefined}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</div>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "AA8DI;AA7DJ,SAAS,gBAAsD;AAiDxD,SAAS,UAAU;AAAA,EACzB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB;AAAA,EACA;AACD,GAAmB;AAClB,MAAI,UAAU,WAAW,EAAG,QAAO;AACnC,SACC,oBAAC,SAAI,WAAU,sBACb,oBAAU,IAAI,CAAC,aACf;AAAA,IAAC;AAAA;AAAA,MAEC,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,WAAW,MAAM,WAAW,SAAS,KAAK,IAAI;AAAA;AAAA,IALlD,SAAS;AAAA,EAMf,CACA,GACF;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
function replyCountLabel(msg, replyCount) {
|
|
2
|
+
if (replyCount <= 0) return null;
|
|
3
|
+
const key = replyCount === 1 ? "comments.replies-one" : "comments.replies";
|
|
4
|
+
return msg(key).replace("{count}", String(replyCount));
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
replyCountLabel
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=reply-count.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ui/reply-count.ts"],
|
|
4
|
+
"sourcesContent": ["import { useTranslation } from 'tldraw'\n\n/**\n * The localized \"N replies\" label (e.g. \"1 reply\", \"3 replies\") for a thread with `replyCount`\n * replies \u2014 the comments after the opening one. Returns null when there are none, so callers render\n * nothing rather than \"0 replies\". Shared by the sidebar row and the hover preview so both read the\n * same, with each site owning where it puts the text.\n */\nexport function replyCountLabel(\n\tmsg: ReturnType<typeof useTranslation>,\n\treplyCount: number\n): string | null {\n\tif (replyCount <= 0) return null\n\tconst key = replyCount === 1 ? 'comments.replies-one' : 'comments.replies'\n\treturn msg(key).replace('{count}', String(replyCount))\n}\n"],
|
|
5
|
+
"mappings": "AAQO,SAAS,gBACf,KACA,YACgB;AAChB,MAAI,cAAc,EAAG,QAAO;AAC5B,QAAM,MAAM,eAAe,IAAI,yBAAyB;AACxD,SAAO,IAAI,GAAG,EAAE,QAAQ,WAAW,OAAO,UAAU,CAAC;AACtD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
function SendButton({ label, disabled, onClick }) {
|
|
3
|
-
return /* @__PURE__ */ jsx(
|
|
3
|
+
return /* @__PURE__ */ jsx(
|
|
4
|
+
"button",
|
|
5
|
+
{
|
|
6
|
+
className: "tlui-cmt-send",
|
|
7
|
+
type: "button",
|
|
8
|
+
disabled,
|
|
9
|
+
onClick,
|
|
10
|
+
"aria-label": label,
|
|
11
|
+
title: label,
|
|
12
|
+
children: /* @__PURE__ */ jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
13
|
+
"path",
|
|
14
|
+
{
|
|
15
|
+
d: "M7.14645 2.14645C7.34171 1.95118 7.65829 1.95118 7.85355 2.14645L11.8536 6.14645C12.0488 6.34171 12.0488 6.65829 11.8536 6.85355C11.6583 7.04882 11.3417 7.04882 11.1464 6.85355L8 3.70711L8 12.5C8 12.7761 7.77614 13 7.5 13C7.22386 13 7 12.7761 7 12.5L7 3.70711L3.85355 6.85355C3.65829 7.04882 3.34171 7.04882 3.14645 6.85355C2.95118 6.65829 2.95118 6.34171 3.14645 6.14645L7.14645 2.14645Z",
|
|
16
|
+
fill: "currentColor",
|
|
17
|
+
fillRule: "evenodd",
|
|
18
|
+
clipRule: "evenodd"
|
|
19
|
+
}
|
|
20
|
+
) })
|
|
21
|
+
}
|
|
22
|
+
);
|
|
4
23
|
}
|
|
5
24
|
export {
|
|
6
25
|
SendButton
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ui/send-button.tsx"],
|
|
4
|
-
"sourcesContent": ["/** @public */\nexport interface SendButtonProps {\n\tlabel: string\n\tdisabled?: boolean\n\tonClick?(): void\n}\n\n/** The button that posts a comment. @public @react */\nexport function SendButton({ label, disabled, onClick }: SendButtonProps) {\n\treturn (\n\t\t<button
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/** @public */\nexport interface SendButtonProps {\n\t/** The button's accessible name (e.g. \"Send\"). Shown as an up-arrow icon, so this is its label. */\n\tlabel: string\n\tdisabled?: boolean\n\tonClick?(): void\n}\n\n/** The button that posts a comment \u2014 an up arrow. @public @react */\nexport function SendButton({ label, disabled, onClick }: SendButtonProps) {\n\treturn (\n\t\t<button\n\t\t\tclassName=\"tlui-cmt-send\"\n\t\t\ttype=\"button\"\n\t\t\tdisabled={disabled}\n\t\t\tonClick={onClick}\n\t\t\taria-label={label}\n\t\t\ttitle={label}\n\t\t>\n\t\t\t<svg width=\"15\" height=\"15\" viewBox=\"0 0 15 15\" fill=\"none\" aria-hidden=\"true\">\n\t\t\t\t<path\n\t\t\t\t\td=\"M7.14645 2.14645C7.34171 1.95118 7.65829 1.95118 7.85355 2.14645L11.8536 6.14645C12.0488 6.34171 12.0488 6.65829 11.8536 6.85355C11.6583 7.04882 11.3417 7.04882 11.1464 6.85355L8 3.70711L8 12.5C8 12.7761 7.77614 13 7.5 13C7.22386 13 7 12.7761 7 12.5L7 3.70711L3.85355 6.85355C3.65829 7.04882 3.34171 7.04882 3.14645 6.85355C2.95118 6.65829 2.95118 6.34171 3.14645 6.14645L7.14645 2.14645Z\"\n\t\t\t\t\tfill=\"currentColor\"\n\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t/>\n\t\t\t</svg>\n\t\t</button>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "AAoBI;AAXG,SAAS,WAAW,EAAE,OAAO,UAAU,QAAQ,GAAoB;AACzE,SACC;AAAA,IAAC;AAAA;AAAA,MACA,WAAU;AAAA,MACV,MAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,cAAY;AAAA,MACZ,OAAO;AAAA,MAEP,8BAAC,SAAI,OAAM,MAAK,QAAO,MAAK,SAAQ,aAAY,MAAK,QAAO,eAAY,QACvE;AAAA,QAAC;AAAA;AAAA,UACA,GAAE;AAAA,UACF,MAAK;AAAA,UACL,UAAS;AAAA,UACT,UAAS;AAAA;AAAA,MACV,GACD;AAAA;AAAA,EACD;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { TldrawUiTooltip } from "tldraw";
|
|
4
|
+
const TooltipButton = forwardRef(
|
|
5
|
+
function TooltipButton2({ tooltip, children, ...props }, ref) {
|
|
6
|
+
return /* @__PURE__ */ jsx(TldrawUiTooltip, { content: tooltip, children: /* @__PURE__ */ jsx("button", { ref, type: "button", "aria-label": tooltip, ...props, children }) });
|
|
7
|
+
}
|
|
8
|
+
);
|
|
9
|
+
export {
|
|
10
|
+
TooltipButton
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=tooltip-button.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ui/tooltip-button.tsx"],
|
|
4
|
+
"sourcesContent": ["import { type ButtonHTMLAttributes, forwardRef } from 'react'\nimport { TldrawUiTooltip } from 'tldraw'\n\ninterface TooltipButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n\ttooltip: string\n}\n\n/** An icon button labelled by a styled tooltip. Forwards ref and props to the underlying button\n * so `asChild` triggers (dropdown, tooltip) both compose onto the real element. */\nexport const TooltipButton = forwardRef<HTMLButtonElement, TooltipButtonProps>(\n\tfunction TooltipButton({ tooltip, children, ...props }, ref) {\n\t\treturn (\n\t\t\t<TldrawUiTooltip content={tooltip}>\n\t\t\t\t<button ref={ref} type=\"button\" aria-label={tooltip} {...props}>\n\t\t\t\t\t{children}\n\t\t\t\t</button>\n\t\t\t</TldrawUiTooltip>\n\t\t)\n\t}\n)\n"],
|
|
5
|
+
"mappings": "AAaI;AAbJ,SAAoC,kBAAkB;AACtD,SAAS,uBAAuB;AAQzB,MAAM,gBAAgB;AAAA,EAC5B,SAASA,eAAc,EAAE,SAAS,UAAU,GAAG,MAAM,GAAG,KAAK;AAC5D,WACC,oBAAC,mBAAgB,SAAS,SACzB,8BAAC,YAAO,KAAU,MAAK,UAAS,cAAY,SAAU,GAAG,OACvD,UACF,GACD;AAAA,EAEF;AACD;",
|
|
6
|
+
"names": ["TooltipButton"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/commenting",
|
|
3
3
|
"description": "tldraw commenting UI components.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.3.0-canary.31036673d3f8",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"test": "yarn run -T vitest --passWithNoTests",
|
|
29
29
|
"test-coverage": "yarn run -T vitest run --coverage --passWithNoTests",
|
|
30
30
|
"predev": "node ./scripts/copy-css-files.mjs",
|
|
31
|
-
"dev": "chokidar 'src/ui/comments.css' 'src/canvas/canvas.css' -c 'node ./scripts/copy-css-files.mjs'",
|
|
31
|
+
"dev": "chokidar '../mentions/mentions.css' 'src/ui/comments.css' 'src/canvas/canvas.css' -c 'node ./scripts/copy-css-files.mjs'",
|
|
32
32
|
"prebuild": "node ./scripts/copy-css-files.mjs",
|
|
33
33
|
"build": "yarn run -T tsx ../../internal/scripts/build-package.ts",
|
|
34
34
|
"build-api": "yarn run -T tsx ../../internal/scripts/build-api.ts",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
+
"@tldraw/driver": "5.3.0-canary.31036673d3f8",
|
|
41
42
|
"@types/react": "^19.2.7",
|
|
42
43
|
"@types/react-dom": "^19.2.3",
|
|
43
44
|
"react": "^19.2.1",
|
|
@@ -52,12 +53,11 @@
|
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
55
|
"@tiptap/core": "^3.12.1",
|
|
55
|
-
"@tiptap/extension-mention": "3.13.0",
|
|
56
56
|
"@tiptap/pm": "^3.12.1",
|
|
57
57
|
"@tiptap/react": "^3.12.1",
|
|
58
|
-
"@
|
|
59
|
-
"@tldraw/utils": "5.
|
|
60
|
-
"tldraw": "5.
|
|
58
|
+
"@tldraw/mentions": "5.3.0-canary.31036673d3f8",
|
|
59
|
+
"@tldraw/utils": "5.3.0-canary.31036673d3f8",
|
|
60
|
+
"tldraw": "5.3.0-canary.31036673d3f8"
|
|
61
61
|
},
|
|
62
62
|
"module": "dist-esm/index.mjs",
|
|
63
63
|
"source": "src/index.ts",
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commentSchemaRecords,
|
|
3
|
+
createCommentThread,
|
|
4
|
+
createShapeId,
|
|
5
|
+
createTLSchema,
|
|
6
|
+
createTLStore,
|
|
7
|
+
defaultBindingUtils,
|
|
8
|
+
defaultShapeUtils,
|
|
9
|
+
defaultTools,
|
|
10
|
+
Editor,
|
|
11
|
+
toRichText,
|
|
12
|
+
createComment as tlCreateComment,
|
|
13
|
+
TLPageId,
|
|
14
|
+
TLShapeId,
|
|
15
|
+
} from 'tldraw'
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
17
|
+
import { registerCommentAnchorLifecycle } from './anchor-lifecycle'
|
|
18
|
+
import { commitCommentMutation, putCommentRecords } from './comment-mutations'
|
|
19
|
+
import { getCommentRecord, getComments } from './comment-store'
|
|
20
|
+
import { CommentTool } from './comment-tool'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* These tests need a real editor: the behavior under test is the interplay between store
|
|
24
|
+
* side effects (the beforeDelete snapshot, operation-complete settlement) and real editor
|
|
25
|
+
* operations like `deleteShapes` and `moveShapesToPage` — exactly what a stub can't model.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
let editor: Editor
|
|
29
|
+
let dispose: () => void
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
editor = new Editor({
|
|
33
|
+
store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
|
|
34
|
+
shapeUtils: defaultShapeUtils,
|
|
35
|
+
bindingUtils: defaultBindingUtils,
|
|
36
|
+
tools: defaultTools,
|
|
37
|
+
getContainer: () => document.body,
|
|
38
|
+
})
|
|
39
|
+
dispose = registerCommentAnchorLifecycle(editor)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
dispose()
|
|
44
|
+
editor.dispose()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
function makeShape(x = 100, y = 100, w = 100, h = 50): TLShapeId {
|
|
48
|
+
const id = createShapeId()
|
|
49
|
+
editor.createShape({ id, type: 'geo', x, y, props: { w, h } })
|
|
50
|
+
return id
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeThread(shapeId: TLShapeId, anchorX = 0.5, anchorY = 0.5, isPrecise = true) {
|
|
54
|
+
const thread = createCommentThread({
|
|
55
|
+
pageId: editor.getCurrentPageId(),
|
|
56
|
+
anchor: { type: 'shape', shapeId, x: anchorX, y: anchorY, isPrecise },
|
|
57
|
+
createdBy: 'me',
|
|
58
|
+
})
|
|
59
|
+
const comment = tlCreateComment({
|
|
60
|
+
threadId: thread.id,
|
|
61
|
+
pageId: thread.pageId,
|
|
62
|
+
authorId: 'me',
|
|
63
|
+
body: toRichText('hello'),
|
|
64
|
+
})
|
|
65
|
+
putCommentRecords(editor, [thread, comment])
|
|
66
|
+
return { thread, comment }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeSecondPage(): TLPageId {
|
|
70
|
+
editor.createPage({ name: 'page 2' })
|
|
71
|
+
return editor.getPages()[1].id
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe('shape deletion', () => {
|
|
75
|
+
it('converts a precise shape anchor to a point anchor where the pin sat', () => {
|
|
76
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
77
|
+
const { thread } = makeThread(shapeId, 0.5, 0.5, true)
|
|
78
|
+
|
|
79
|
+
editor.deleteShape(shapeId)
|
|
80
|
+
|
|
81
|
+
const updated = getCommentRecord(editor, thread.id)!
|
|
82
|
+
expect(updated.typeName).toBe('comment-thread')
|
|
83
|
+
expect((updated as typeof thread).anchor).toEqual({ type: 'point', x: 150, y: 125 })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('converts an imprecise shape anchor where the inset pin is drawn, not at the raw corner', () => {
|
|
87
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
88
|
+
const { thread } = makeThread(shapeId, 0.2, 0.9, false)
|
|
89
|
+
|
|
90
|
+
editor.deleteShape(shapeId)
|
|
91
|
+
|
|
92
|
+
// Top-right corner (200, 100) plus the 20px screen inset toward the shape's centre.
|
|
93
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
94
|
+
expect(updated.anchor).toEqual({ type: 'point', x: 180, y: 120 })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('resolves the imprecise spot from the commenting options when no override is passed', () => {
|
|
98
|
+
const configured = new Editor({
|
|
99
|
+
store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
|
|
100
|
+
shapeUtils: defaultShapeUtils,
|
|
101
|
+
bindingUtils: defaultBindingUtils,
|
|
102
|
+
tools: [...defaultTools, CommentTool.configure({ impreciseShapeAnchor: { x: 0, y: 1 } })],
|
|
103
|
+
getContainer: () => document.body,
|
|
104
|
+
})
|
|
105
|
+
const disposeConfigured = registerCommentAnchorLifecycle(configured)
|
|
106
|
+
try {
|
|
107
|
+
const shapeId = createShapeId()
|
|
108
|
+
configured.createShape({ id: shapeId, type: 'geo', x: 100, y: 100, props: { w: 100, h: 50 } })
|
|
109
|
+
const thread = createCommentThread({
|
|
110
|
+
pageId: configured.getCurrentPageId(),
|
|
111
|
+
anchor: { type: 'shape', shapeId, x: 0.5, y: 0.5, isPrecise: false },
|
|
112
|
+
createdBy: 'me',
|
|
113
|
+
})
|
|
114
|
+
putCommentRecords(configured, [thread])
|
|
115
|
+
|
|
116
|
+
configured.deleteShape(shapeId)
|
|
117
|
+
|
|
118
|
+
// Bottom-left corner (100, 150) plus the inset toward the shape's centre.
|
|
119
|
+
const updated = getCommentRecord(configured, thread.id) as typeof thread
|
|
120
|
+
expect(updated.anchor).toEqual({ type: 'point', x: 120, y: 130 })
|
|
121
|
+
} finally {
|
|
122
|
+
disposeConfigured()
|
|
123
|
+
configured.dispose()
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('converts anchors on children when their frame is deleted with them', () => {
|
|
128
|
+
const frameId = createShapeId()
|
|
129
|
+
editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
|
|
130
|
+
const childId = createShapeId()
|
|
131
|
+
editor.createShape({
|
|
132
|
+
id: childId,
|
|
133
|
+
type: 'geo',
|
|
134
|
+
parentId: frameId,
|
|
135
|
+
x: 50,
|
|
136
|
+
y: 50,
|
|
137
|
+
props: { w: 100, h: 100 },
|
|
138
|
+
})
|
|
139
|
+
const { thread } = makeThread(childId, 0.5, 0.5, true)
|
|
140
|
+
|
|
141
|
+
editor.deleteShape(frameId)
|
|
142
|
+
|
|
143
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
144
|
+
expect(updated.anchor).toEqual({ type: 'point', x: 100, y: 100 })
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('re-attaches the thread when the delete is undone', () => {
|
|
148
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
149
|
+
const { thread } = makeThread(shapeId, 0.5, 0.5, true)
|
|
150
|
+
editor.markHistoryStoppingPoint()
|
|
151
|
+
|
|
152
|
+
editor.deleteShape(shapeId)
|
|
153
|
+
expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
|
|
154
|
+
|
|
155
|
+
editor.undo()
|
|
156
|
+
|
|
157
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
158
|
+
expect(updated.anchor).toEqual(thread.anchor)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('keeps the restore when a returning shape cannot resolve its page yet', () => {
|
|
162
|
+
const frameId = createShapeId()
|
|
163
|
+
editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
|
|
164
|
+
const childId = createShapeId()
|
|
165
|
+
editor.createShape({
|
|
166
|
+
id: childId,
|
|
167
|
+
type: 'geo',
|
|
168
|
+
parentId: frameId,
|
|
169
|
+
x: 50,
|
|
170
|
+
y: 50,
|
|
171
|
+
props: { w: 100, h: 100 },
|
|
172
|
+
})
|
|
173
|
+
const { thread } = makeThread(childId, 0.5, 0.5, true)
|
|
174
|
+
const childRecord = editor.getShape(childId)!
|
|
175
|
+
const frameRecord = editor.getShape(frameId)!
|
|
176
|
+
|
|
177
|
+
editor.deleteShape(frameId)
|
|
178
|
+
expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
|
|
179
|
+
|
|
180
|
+
// The child returns in an earlier operation than its parent, so its ancestor page can't
|
|
181
|
+
// resolve when it first reappears.
|
|
182
|
+
editor.store.put([childRecord])
|
|
183
|
+
editor.store.put([frameRecord])
|
|
184
|
+
|
|
185
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
186
|
+
expect(updated.anchor).toEqual(thread.anchor)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('re-attaches on undo even when the lifecycle was re-registered in between', () => {
|
|
190
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
191
|
+
const { thread } = makeThread(shapeId, 0.5, 0.5, true)
|
|
192
|
+
editor.markHistoryStoppingPoint()
|
|
193
|
+
|
|
194
|
+
editor.deleteShape(shapeId)
|
|
195
|
+
expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
|
|
196
|
+
|
|
197
|
+
// The registering effect re-runs whenever its inputs change identity; conversion memory
|
|
198
|
+
// must not live and die with any one registration.
|
|
199
|
+
dispose()
|
|
200
|
+
dispose = registerCommentAnchorLifecycle(editor)
|
|
201
|
+
|
|
202
|
+
editor.undo()
|
|
203
|
+
|
|
204
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
205
|
+
expect(updated.anchor).toEqual(thread.anchor)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('does not re-attach on undo when the pin was moved after the conversion', () => {
|
|
209
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
210
|
+
const { thread } = makeThread(shapeId, 0.5, 0.5, true)
|
|
211
|
+
editor.markHistoryStoppingPoint()
|
|
212
|
+
|
|
213
|
+
editor.deleteShape(shapeId)
|
|
214
|
+
const converted = getCommentRecord(editor, thread.id) as typeof thread
|
|
215
|
+
commitCommentMutation(editor, () =>
|
|
216
|
+
putCommentRecords(editor, [{ ...converted, anchor: { type: 'point', x: 900, y: 900 } }])
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
editor.undo()
|
|
220
|
+
|
|
221
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
222
|
+
expect(updated.anchor).toEqual({ type: 'point', x: 900, y: 900 })
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('leaves threads on other shapes untouched', () => {
|
|
226
|
+
const shapeId = makeShape()
|
|
227
|
+
const otherId = makeShape(300, 300)
|
|
228
|
+
const { thread } = makeThread(otherId)
|
|
229
|
+
|
|
230
|
+
editor.deleteShape(shapeId)
|
|
231
|
+
|
|
232
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
233
|
+
expect(updated.anchor.type).toBe('shape')
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
describe('moving a shape to another page', () => {
|
|
238
|
+
it('re-homes the thread and its comments to the new page, keeping the shape anchor', () => {
|
|
239
|
+
const shapeId = makeShape()
|
|
240
|
+
const { thread, comment } = makeThread(shapeId)
|
|
241
|
+
const page2 = makeSecondPage()
|
|
242
|
+
|
|
243
|
+
editor.moveShapesToPage([shapeId], page2)
|
|
244
|
+
|
|
245
|
+
const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
|
|
246
|
+
expect(updatedThread.pageId).toBe(page2)
|
|
247
|
+
expect(updatedThread.anchor).toEqual(thread.anchor)
|
|
248
|
+
const updatedComment = getComments(editor).find((c) => c.id === comment.id)!
|
|
249
|
+
expect(updatedComment.pageId).toBe(page2)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('re-homes threads anchored to children of a moved frame', () => {
|
|
253
|
+
const frameId = createShapeId()
|
|
254
|
+
editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
|
|
255
|
+
const childId = createShapeId()
|
|
256
|
+
editor.createShape({
|
|
257
|
+
id: childId,
|
|
258
|
+
type: 'geo',
|
|
259
|
+
parentId: frameId,
|
|
260
|
+
x: 50,
|
|
261
|
+
y: 50,
|
|
262
|
+
props: { w: 100, h: 100 },
|
|
263
|
+
})
|
|
264
|
+
const { thread } = makeThread(childId)
|
|
265
|
+
const page2 = makeSecondPage()
|
|
266
|
+
|
|
267
|
+
editor.moveShapesToPage([frameId], page2)
|
|
268
|
+
|
|
269
|
+
const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
|
|
270
|
+
expect(updatedThread.pageId).toBe(page2)
|
|
271
|
+
expect(updatedThread.anchor.type).toBe('shape')
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('re-homes the thread back when the move is undone', () => {
|
|
275
|
+
const shapeId = makeShape()
|
|
276
|
+
const { thread } = makeThread(shapeId)
|
|
277
|
+
const page1 = editor.getCurrentPageId()
|
|
278
|
+
const page2 = makeSecondPage()
|
|
279
|
+
editor.markHistoryStoppingPoint()
|
|
280
|
+
|
|
281
|
+
editor.moveShapesToPage([shapeId], page2)
|
|
282
|
+
expect((getCommentRecord(editor, thread.id) as typeof thread).pageId).toBe(page2)
|
|
283
|
+
|
|
284
|
+
editor.undo()
|
|
285
|
+
|
|
286
|
+
const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
|
|
287
|
+
expect(updatedThread.pageId).toBe(page1)
|
|
288
|
+
expect(updatedThread.anchor.type).toBe('shape')
|
|
289
|
+
})
|
|
290
|
+
})
|