@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-cjs/ui/reactions.js
CHANGED
|
@@ -23,12 +23,25 @@ __export(reactions_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(reactions_exports);
|
|
24
24
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
25
|
var import_reaction = require("./reaction");
|
|
26
|
-
function Reactions(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
function Reactions({
|
|
27
|
+
reactions,
|
|
28
|
+
onToggle,
|
|
29
|
+
canReact = true,
|
|
30
|
+
enableHoverList = true,
|
|
31
|
+
renderReaction,
|
|
32
|
+
ReactionTooltip
|
|
33
|
+
}) {
|
|
34
|
+
if (reactions.length === 0) return null;
|
|
35
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tlui-cmt-reactions", children: reactions.map((reaction) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
36
|
+
import_reaction.Reaction,
|
|
37
|
+
{
|
|
38
|
+
...reaction,
|
|
39
|
+
enableHoverList,
|
|
40
|
+
renderReaction,
|
|
41
|
+
ReactionTooltip,
|
|
42
|
+
onClick: canReact ? () => onToggle?.(reaction.emoji) : void 0
|
|
43
|
+
},
|
|
44
|
+
reaction.emoji
|
|
45
|
+
)) });
|
|
33
46
|
}
|
|
34
47
|
//# sourceMappingURL=reactions.js.map
|
|
@@ -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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8DI;AA7DJ,sBAA+D;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,4CAAC,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
|
}
|
|
@@ -16,16 +16,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var
|
|
20
|
-
__export(
|
|
21
|
-
|
|
19
|
+
var reply_count_exports = {};
|
|
20
|
+
__export(reply_count_exports, {
|
|
21
|
+
replyCountLabel: () => replyCountLabel
|
|
22
22
|
});
|
|
23
|
-
module.exports = __toCommonJS(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
name
|
|
29
|
-
] });
|
|
23
|
+
module.exports = __toCommonJS(reply_count_exports);
|
|
24
|
+
function replyCountLabel(msg, replyCount) {
|
|
25
|
+
if (replyCount <= 0) return null;
|
|
26
|
+
const key = replyCount === 1 ? "comments.replies-one" : "comments.replies";
|
|
27
|
+
return msg(key).replace("{count}", String(replyCount));
|
|
30
28
|
}
|
|
31
|
-
//# sourceMappingURL=
|
|
29
|
+
//# sourceMappingURL=reply-count.js.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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;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
|
+
}
|
|
@@ -23,6 +23,25 @@ __export(send_button_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(send_button_exports);
|
|
24
24
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
25
|
function SendButton({ label, disabled, onClick }) {
|
|
26
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
26
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
27
|
+
"button",
|
|
28
|
+
{
|
|
29
|
+
className: "tlui-cmt-send",
|
|
30
|
+
type: "button",
|
|
31
|
+
disabled,
|
|
32
|
+
onClick,
|
|
33
|
+
"aria-label": label,
|
|
34
|
+
title: label,
|
|
35
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
36
|
+
"path",
|
|
37
|
+
{
|
|
38
|
+
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",
|
|
39
|
+
fill: "currentColor",
|
|
40
|
+
fillRule: "evenodd",
|
|
41
|
+
clipRule: "evenodd"
|
|
42
|
+
}
|
|
43
|
+
) })
|
|
44
|
+
}
|
|
45
|
+
);
|
|
27
46
|
}
|
|
28
47
|
//# sourceMappingURL=send-button.js.map
|
|
@@ -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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;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,sDAAC,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
|
}
|
|
@@ -16,14 +16,17 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var
|
|
20
|
-
__export(
|
|
21
|
-
|
|
19
|
+
var tooltip_button_exports = {};
|
|
20
|
+
__export(tooltip_button_exports, {
|
|
21
|
+
TooltipButton: () => TooltipButton
|
|
22
22
|
});
|
|
23
|
-
module.exports = __toCommonJS(
|
|
23
|
+
module.exports = __toCommonJS(tooltip_button_exports);
|
|
24
24
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
25
|
+
var import_react = require("react");
|
|
26
|
+
var import_tldraw = require("tldraw");
|
|
27
|
+
const TooltipButton = (0, import_react.forwardRef)(
|
|
28
|
+
function TooltipButton2({ tooltip, children, ...props }, ref) {
|
|
29
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiTooltip, { content: tooltip, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ref, type: "button", "aria-label": tooltip, ...props, children }) });
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
//# sourceMappingURL=tooltip-button.js.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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAaI;AAbJ,mBAAsD;AACtD,oBAAgC;AAQzB,MAAM,oBAAgB;AAAA,EAC5B,SAASA,eAAc,EAAE,SAAS,UAAU,GAAG,MAAM,GAAG,KAAK;AAC5D,WACC,4CAAC,iCAAgB,SAAS,SACzB,sDAAC,YAAO,KAAU,MAAK,UAAS,cAAY,SAAU,GAAG,OACvD,UACF,GACD;AAAA,EAEF;AACD;",
|
|
6
|
+
"names": ["TooltipButton"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { WeakCache } from "@tldraw/utils";
|
|
2
|
+
import { commitCommentMutation, putRecordsInCommit } from "./comment-mutations.mjs";
|
|
3
|
+
import { getCommentRecord, getComments, getCommentThreads } from "./comment-store.mjs";
|
|
4
|
+
import { anchorPagePoint, impreciseShapePinInset } from "./thread-state.mjs";
|
|
5
|
+
const convertedByShapeCache = new WeakCache();
|
|
6
|
+
function isAnchoredToShape(thread, shapeId) {
|
|
7
|
+
const anchor = thread.anchor;
|
|
8
|
+
return anchor.type === "shape" && anchor.shapeId === shapeId;
|
|
9
|
+
}
|
|
10
|
+
function registerCommentAnchorLifecycle(editor) {
|
|
11
|
+
const pendingByShape = /* @__PURE__ */ new Map();
|
|
12
|
+
const convertedByShape = convertedByShapeCache.get(editor, () => /* @__PURE__ */ new Map());
|
|
13
|
+
function rehomeThread(thread, pageId, updates) {
|
|
14
|
+
updates.push({ ...thread, pageId });
|
|
15
|
+
for (const comment of getComments(editor)) {
|
|
16
|
+
if (comment.threadId === thread.id) updates.push({ ...comment, pageId });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const disposeBeforeDelete = editor.sideEffects.registerBeforeDeleteHandler(
|
|
20
|
+
"shape",
|
|
21
|
+
(shape, source) => {
|
|
22
|
+
if (source === "remote") return;
|
|
23
|
+
for (const thread of getCommentThreads(editor)) {
|
|
24
|
+
if (!isAnchoredToShape(thread, shape.id)) continue;
|
|
25
|
+
let snapshot = pendingByShape.get(shape.id);
|
|
26
|
+
if (!snapshot) {
|
|
27
|
+
snapshot = /* @__PURE__ */ new Map();
|
|
28
|
+
pendingByShape.set(shape.id, snapshot);
|
|
29
|
+
}
|
|
30
|
+
let point = anchorPagePoint(editor, thread.anchor);
|
|
31
|
+
const inset = impreciseShapePinInset(editor, thread.anchor);
|
|
32
|
+
if (point && inset) {
|
|
33
|
+
const zoom = editor.getZoomLevel();
|
|
34
|
+
point = { x: point.x + inset.x / zoom, y: point.y + inset.y / zoom };
|
|
35
|
+
}
|
|
36
|
+
snapshot.set(thread.id, point);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
const returnedShapeIds = /* @__PURE__ */ new Set();
|
|
41
|
+
const disposeOperationComplete = editor.sideEffects.registerOperationCompleteHandler((source) => {
|
|
42
|
+
if (source === "remote") return;
|
|
43
|
+
if (pendingByShape.size === 0 && returnedShapeIds.size === 0) return;
|
|
44
|
+
const settled = [...pendingByShape.entries()];
|
|
45
|
+
pendingByShape.clear();
|
|
46
|
+
const returned = [...returnedShapeIds];
|
|
47
|
+
returnedShapeIds.clear();
|
|
48
|
+
const updates = [];
|
|
49
|
+
for (const [shapeId, threadPoints] of settled) {
|
|
50
|
+
if (editor.getShape(shapeId)) continue;
|
|
51
|
+
for (const [threadId, point] of threadPoints) {
|
|
52
|
+
if (!point) continue;
|
|
53
|
+
const thread = getCommentRecord(editor, threadId);
|
|
54
|
+
if (!thread || thread.typeName !== "comment-thread") continue;
|
|
55
|
+
if (!isAnchoredToShape(thread, shapeId)) continue;
|
|
56
|
+
let converted = convertedByShape.get(shapeId);
|
|
57
|
+
if (!converted) {
|
|
58
|
+
converted = [];
|
|
59
|
+
convertedByShape.set(shapeId, converted);
|
|
60
|
+
}
|
|
61
|
+
converted.push({ threadId, anchor: thread.anchor, point });
|
|
62
|
+
updates.push({ ...thread, anchor: { type: "point", x: point.x, y: point.y } });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const shapeId of returned) {
|
|
66
|
+
const shape = editor.getShape(shapeId);
|
|
67
|
+
if (!shape) continue;
|
|
68
|
+
const converted = convertedByShape.get(shapeId);
|
|
69
|
+
if (!converted) continue;
|
|
70
|
+
convertedByShape.delete(shapeId);
|
|
71
|
+
const pageId = editor.getAncestorPageId(shape);
|
|
72
|
+
for (const { threadId, anchor, point } of converted) {
|
|
73
|
+
const thread = getCommentRecord(editor, threadId);
|
|
74
|
+
if (!thread || thread.typeName !== "comment-thread") continue;
|
|
75
|
+
const current = thread.anchor;
|
|
76
|
+
if (current.type !== "point" || current.x !== point.x || current.y !== point.y) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!pageId || thread.pageId === pageId) {
|
|
80
|
+
updates.push({ ...thread, anchor });
|
|
81
|
+
} else {
|
|
82
|
+
rehomeThread({ ...thread, anchor }, pageId, updates);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (updates.length > 0) {
|
|
87
|
+
commitCommentMutation(editor, () => putRecordsInCommit(editor, updates));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const disposeAfterCreate = editor.sideEffects.registerAfterCreateHandler(
|
|
91
|
+
"shape",
|
|
92
|
+
(shape, source) => {
|
|
93
|
+
if (source === "remote") return;
|
|
94
|
+
if (convertedByShape.has(shape.id)) returnedShapeIds.add(shape.id);
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
const disposeAfterChange = editor.sideEffects.registerAfterChangeHandler(
|
|
98
|
+
"shape",
|
|
99
|
+
(prev, next, source) => {
|
|
100
|
+
if (source === "remote") return;
|
|
101
|
+
if (prev.parentId === next.parentId) return;
|
|
102
|
+
const pageId = editor.getAncestorPageId(next);
|
|
103
|
+
if (!pageId) return;
|
|
104
|
+
const movedIds = editor.getShapeAndDescendantIds([next.id]);
|
|
105
|
+
const updates = [];
|
|
106
|
+
for (const thread of getCommentThreads(editor)) {
|
|
107
|
+
const anchor = thread.anchor;
|
|
108
|
+
if (anchor.type !== "shape") continue;
|
|
109
|
+
if (!movedIds.has(anchor.shapeId)) continue;
|
|
110
|
+
if (thread.pageId === pageId) continue;
|
|
111
|
+
rehomeThread(thread, pageId, updates);
|
|
112
|
+
}
|
|
113
|
+
if (updates.length > 0) {
|
|
114
|
+
commitCommentMutation(editor, () => putRecordsInCommit(editor, updates));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
return () => {
|
|
119
|
+
disposeBeforeDelete();
|
|
120
|
+
disposeOperationComplete();
|
|
121
|
+
disposeAfterCreate();
|
|
122
|
+
disposeAfterChange();
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
registerCommentAnchorLifecycle
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=anchor-lifecycle.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/canvas/anchor-lifecycle.ts"],
|
|
4
|
+
"sourcesContent": ["import { WeakCache } from '@tldraw/utils'\nimport { Editor, TLCommentAnchor, TLCommentThread, TLPageId, TLShapeId, VecLike } from 'tldraw'\nimport { commitCommentMutation, putRecordsInCommit } from './comment-mutations'\nimport { getCommentRecord, getComments, getCommentThreads, TLCommentRecord } from './comment-store'\nimport { anchorPagePoint, impreciseShapePinInset } from './thread-state'\n\ntype ShapeAnchor = Extract<TLCommentAnchor, { type: 'shape' }>\n\n/**\n * Threads converted to point anchors because their shape was deleted, kept so the anchor can be\n * restored if the shape comes back (page move re-create, undo of the delete). Owned by the\n * editor, not the registration closure: the registering effect can re-run (a prop identity\n * change, a remount), and an undo can arrive arbitrarily long after the delete \u2014 the memory has\n * to outlive any one registration.\n */\nconst convertedByShapeCache = new WeakCache<\n\tEditor,\n\tMap<TLShapeId, { threadId: string; anchor: ShapeAnchor; point: VecLike }[]>\n>()\n\nfunction isAnchoredToShape(\n\tthread: TLCommentThread,\n\tshapeId: TLShapeId\n): thread is TLCommentThread & { anchor: ShapeAnchor } {\n\tconst anchor = thread.anchor\n\treturn anchor.type === 'shape' && anchor.shapeId === shapeId\n}\n\n/**\n * Keep shape-anchored threads alive across their shape's lifecycle:\n *\n * - When the shape is deleted, the thread converts to a `point` anchor at the spot its pin last\n * occupied, so the conversation outlives the shape instead of becoming invisible (a missing\n * shape has no bounds, which hides the pin).\n * - When the shape moves to another page, the thread follows: its `pageId` and each comment's\n * denormalized `pageId` update to the shape's new page, and the anchor keeps riding the shape.\n * - When a deleted shape comes back (a page move re-creates it with its id preserved; undoing a\n * delete restores it), the thread re-attaches \u2014 unless its pin was manually moved in the\n * meantime, in which case the manual placement wins.\n *\n * A page move is `deleteShapes` + re-create with preserved ids inside one `editor.run`, but each\n * store write is its own operation \u2014 so at no single moment does \"this shape is being moved\"\n * exist as an observable event. The handlers therefore cooperate across operations:\n * `beforeDelete` snapshots where each affected pin sits (every record is still in the store at\n * that point, so page bounds resolve even for children of a deleted frame); the\n * operation-complete pass converts threads whose shape is really gone, remembering the original\n * anchor; `afterCreate` notes a reappeared shape id, and the operation-complete pass restores\n * the anchor once the store has settled (creation order within an operation is arbitrary, so a\n * fresh shape may not resolve an ancestor page mid-operation). Undo/redo of a move\n * replays as a `parentId` update (remove + add of one id squash to an update in the history\n * diff), so an `afterChange` handler re-homes threads on cross-page reparents \u2014 including\n * threads anchored to descendants, which move with their parent without change events of their\n * own.\n *\n * Remote changes are ignored: the client that performed the operation runs this same\n * maintenance and syncs the resulting thread updates. Writes honour the\n * {@link CommentingOptions.history} option, so the consumer decides whether they're undoable.\n *\n * Registered by `CanvasComments` on mount; parts-built consumers can call this directly.\n * Returns a cleanup function that unregisters all handlers.\n *\n * @public\n */\nexport function registerCommentAnchorLifecycle(editor: Editor): () => void {\n\t// Pin positions snapshotted during the current operation: shape id -> (thread id -> pin page\n\t// point).\n\tconst pendingByShape = new Map<TLShapeId, Map<string, VecLike | null>>()\n\tconst convertedByShape = convertedByShapeCache.get(editor, () => new Map())\n\n\tfunction rehomeThread(thread: TLCommentThread, pageId: TLPageId, updates: TLCommentRecord[]) {\n\t\tupdates.push({ ...thread, pageId })\n\t\tfor (const comment of getComments(editor)) {\n\t\t\tif (comment.threadId === thread.id) updates.push({ ...comment, pageId })\n\t\t}\n\t}\n\n\tconst disposeBeforeDelete = editor.sideEffects.registerBeforeDeleteHandler(\n\t\t'shape',\n\t\t(shape, source) => {\n\t\t\tif (source === 'remote') return\n\t\t\tfor (const thread of getCommentThreads(editor)) {\n\t\t\t\tif (!isAnchoredToShape(thread, shape.id)) continue\n\t\t\t\tlet snapshot = pendingByShape.get(shape.id)\n\t\t\t\tif (!snapshot) {\n\t\t\t\t\tsnapshot = new Map()\n\t\t\t\t\tpendingByShape.set(shape.id, snapshot)\n\t\t\t\t}\n\t\t\t\t// Snapshot where the pin is drawn, not just the anchor point: imprecise shape pins\n\t\t\t\t// render inset toward the shape's centre, and the converted point pin renders at\n\t\t\t\t// its point exactly \u2014 without the inset the pin would jump on deletion. The inset\n\t\t\t\t// is screen-fixed, so bake it in at the current zoom.\n\t\t\t\tlet point = anchorPagePoint(editor, thread.anchor)\n\t\t\t\tconst inset = impreciseShapePinInset(editor, thread.anchor)\n\t\t\t\tif (point && inset) {\n\t\t\t\t\tconst zoom = editor.getZoomLevel()\n\t\t\t\t\tpoint = { x: point.x + inset.x / zoom, y: point.y + inset.y / zoom }\n\t\t\t\t}\n\t\t\t\tsnapshot.set(thread.id, point)\n\t\t\t}\n\t\t}\n\t)\n\n\t// Shape ids from `convertedByShape` re-created during the current operation. Restores are\n\t// settled at operation complete rather than in `afterCreate` itself: creation order within an\n\t// operation is arbitrary, so a shape can appear before its parent and not resolve an ancestor\n\t// page yet \u2014 and the conversion memory must outlive any such partial state.\n\tconst returnedShapeIds = new Set<TLShapeId>()\n\n\tconst disposeOperationComplete = editor.sideEffects.registerOperationCompleteHandler((source) => {\n\t\t// Only user-sourced handlers populate the maps and only local operations should settle\n\t\t// them; a remote operation completing leaves any (impossible-in-practice) leftovers for\n\t\t// the next local settle rather than judging them against remote state.\n\t\tif (source === 'remote') return\n\t\tif (pendingByShape.size === 0 && returnedShapeIds.size === 0) return\n\t\tconst settled = [...pendingByShape.entries()]\n\t\tpendingByShape.clear()\n\t\tconst returned = [...returnedShapeIds]\n\t\treturnedShapeIds.clear()\n\n\t\tconst updates: TLCommentRecord[] = []\n\t\tfor (const [shapeId, threadPoints] of settled) {\n\t\t\tif (editor.getShape(shapeId)) continue\n\t\t\tfor (const [threadId, point] of threadPoints) {\n\t\t\t\tif (!point) continue\n\t\t\t\tconst thread = getCommentRecord(editor, threadId)\n\t\t\t\tif (!thread || thread.typeName !== 'comment-thread') continue\n\t\t\t\tif (!isAnchoredToShape(thread, shapeId)) continue\n\t\t\t\tlet converted = convertedByShape.get(shapeId)\n\t\t\t\tif (!converted) {\n\t\t\t\t\tconverted = []\n\t\t\t\t\tconvertedByShape.set(shapeId, converted)\n\t\t\t\t}\n\t\t\t\tconverted.push({ threadId, anchor: thread.anchor, point })\n\t\t\t\tupdates.push({ ...thread, anchor: { type: 'point', x: point.x, y: point.y } })\n\t\t\t}\n\t\t}\n\n\t\tfor (const shapeId of returned) {\n\t\t\tconst shape = editor.getShape(shapeId)\n\t\t\tif (!shape) continue\n\t\t\tconst converted = convertedByShape.get(shapeId)\n\t\t\tif (!converted) continue\n\t\t\tconvertedByShape.delete(shapeId)\n\n\t\t\tconst pageId = editor.getAncestorPageId(shape)\n\t\t\tfor (const { threadId, anchor, point } of converted) {\n\t\t\t\tconst thread = getCommentRecord(editor, threadId)\n\t\t\t\tif (!thread || thread.typeName !== 'comment-thread') continue\n\t\t\t\t// Re-attach only threads still sitting exactly where the conversion left them \u2014 a\n\t\t\t\t// pin moved since then was placed deliberately, and that placement wins.\n\t\t\t\tconst current = thread.anchor\n\t\t\t\tif (current.type !== 'point' || current.x !== point.x || current.y !== point.y) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (!pageId || thread.pageId === pageId) {\n\t\t\t\t\tupdates.push({ ...thread, anchor })\n\t\t\t\t} else {\n\t\t\t\t\trehomeThread({ ...thread, anchor }, pageId, updates)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (updates.length > 0) {\n\t\t\tcommitCommentMutation(editor, () => putRecordsInCommit(editor, updates))\n\t\t}\n\t})\n\n\tconst disposeAfterCreate = editor.sideEffects.registerAfterCreateHandler(\n\t\t'shape',\n\t\t(shape, source) => {\n\t\t\tif (source === 'remote') return\n\t\t\tif (convertedByShape.has(shape.id)) returnedShapeIds.add(shape.id)\n\t\t}\n\t)\n\n\tconst disposeAfterChange = editor.sideEffects.registerAfterChangeHandler(\n\t\t'shape',\n\t\t(prev, next, source) => {\n\t\t\tif (source === 'remote') return\n\t\t\tif (prev.parentId === next.parentId) return\n\t\t\tconst pageId = editor.getAncestorPageId(next)\n\t\t\tif (!pageId) return\n\t\t\t// Descendants move with their parent without change events of their own.\n\t\t\tconst movedIds = editor.getShapeAndDescendantIds([next.id])\n\t\t\tconst updates: TLCommentRecord[] = []\n\t\t\tfor (const thread of getCommentThreads(editor)) {\n\t\t\t\tconst anchor = thread.anchor\n\t\t\t\tif (anchor.type !== 'shape') continue\n\t\t\t\tif (!movedIds.has(anchor.shapeId)) continue\n\t\t\t\tif (thread.pageId === pageId) continue\n\t\t\t\trehomeThread(thread, pageId, updates)\n\t\t\t}\n\t\t\tif (updates.length > 0) {\n\t\t\t\tcommitCommentMutation(editor, () => putRecordsInCommit(editor, updates))\n\t\t\t}\n\t\t}\n\t)\n\n\treturn () => {\n\t\tdisposeBeforeDelete()\n\t\tdisposeOperationComplete()\n\t\tdisposeAfterCreate()\n\t\tdisposeAfterChange()\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { anchorPagePoint } from "./thread-state.mjs";
|
|
2
|
-
function collectClusterLeaves(editor, threads, openThreadId
|
|
2
|
+
function collectClusterLeaves(editor, threads, openThreadId) {
|
|
3
3
|
const pageId = editor.getCurrentPageId();
|
|
4
4
|
const leaves = [];
|
|
5
5
|
for (const thread of threads) {
|
|
6
6
|
if (thread.id === openThreadId) continue;
|
|
7
7
|
if (thread.pageId !== pageId) continue;
|
|
8
|
-
const point = anchorPagePoint(editor, thread.anchor
|
|
8
|
+
const point = anchorPagePoint(editor, thread.anchor);
|
|
9
9
|
if (!point) continue;
|
|
10
10
|
leaves.push({
|
|
11
11
|
id: thread.id,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/cluster-input.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Editor, TLCommentThread } from 'tldraw'\nimport type { LeafInput } from '../clustering/types'\nimport { anchorPagePoint } from './thread-state'\n\n/** @
|
|
5
|
-
"mappings": "AAEA,SAAS,uBAAuB;AAGzB,SAAS,qBACf,QACA,SACA,
|
|
4
|
+
"sourcesContent": ["import type { Editor, TLCommentThread } from 'tldraw'\nimport type { LeafInput } from '../clustering/types'\nimport { anchorPagePoint } from './thread-state'\n\n/** The cluster leaves for the threads pinned on the current page. @internal */\nexport function collectClusterLeaves(\n\teditor: Editor,\n\tthreads: readonly TLCommentThread[],\n\topenThreadId: string | null\n): LeafInput[] {\n\tconst pageId = editor.getCurrentPageId()\n\tconst leaves: LeafInput[] = []\n\n\tfor (const thread of threads) {\n\t\tif (thread.id === openThreadId) continue\n\t\tif (thread.pageId !== pageId) continue\n\n\t\tconst point = anchorPagePoint(editor, thread.anchor)\n\t\tif (!point) continue\n\n\t\tleaves.push({\n\t\t\tid: thread.id,\n\t\t\tpoint: { x: point.x, y: point.y },\n\t\t})\n\t}\n\n\treturn leaves\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,uBAAuB;AAGzB,SAAS,qBACf,QACA,SACA,cACc;AACd,QAAM,SAAS,OAAO,iBAAiB;AACvC,QAAM,SAAsB,CAAC;AAE7B,aAAW,UAAU,SAAS;AAC7B,QAAI,OAAO,OAAO,aAAc;AAChC,QAAI,OAAO,WAAW,OAAQ;AAE9B,UAAM,QAAQ,gBAAgB,QAAQ,OAAO,MAAM;AACnD,QAAI,CAAC,MAAO;AAEZ,WAAO,KAAK;AAAA,MACX,IAAI,OAAO;AAAA,MACX,OAAO,EAAE,GAAG,MAAM,GAAG,GAAG,MAAM,EAAE;AAAA,IACjC,CAAC;AAAA,EACF;AAEA,SAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@ import { useMemo } from "react";
|
|
|
3
3
|
import { renderCommentHtml } from "./comment-render.mjs";
|
|
4
4
|
function CommentBody({ richText, resolveName }) {
|
|
5
5
|
const html = useMemo(() => renderCommentHtml(richText, resolveName), [richText, resolveName]);
|
|
6
|
-
return /* @__PURE__ */ jsx("div", { className: "cmt-text", dangerouslySetInnerHTML: { __html: html } });
|
|
6
|
+
return /* @__PURE__ */ jsx("div", { className: "tlui-cmt-text", dangerouslySetInnerHTML: { __html: html } });
|
|
7
7
|
}
|
|
8
8
|
export {
|
|
9
9
|
CommentBody
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comment-body.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useMemo } from 'react'\nimport { TLRichText } from 'tldraw'\nimport { renderCommentHtml } from './comment-render'\n\n/** @public */\nexport interface CommentBodyProps {\n\trichText: TLRichText\n\t/** Maps a member id to its current display name, so \\@mentions show the live name. */\n\tresolveName?(id: string): string | undefined\n}\n\n/**\n * Renders a comment's rich-text body read-only through the limited comment extension set (no\n * headings), so formatting (bold, links, lists, highlight) is preserved rather than flattened, and\n * headings can never render. Use this as the `body` of a `CommentCard` on a canvas.\n * @public @react\n */\nexport function CommentBody({ richText, resolveName }: CommentBodyProps) {\n\tconst html = useMemo(() => renderCommentHtml(richText, resolveName), [richText, resolveName])\n\treturn <div className=\"cmt-text\" dangerouslySetInnerHTML={{ __html: html }} />\n}\n"],
|
|
5
|
-
"mappings": "AAmBQ;AAnBR,SAAS,eAAe;AAExB,SAAS,yBAAyB;AAe3B,SAAS,YAAY,EAAE,UAAU,YAAY,GAAqB;AACxE,QAAM,OAAO,QAAQ,MAAM,kBAAkB,UAAU,WAAW,GAAG,CAAC,UAAU,WAAW,CAAC;AAC5F,SAAO,oBAAC,SAAI,WAAU,
|
|
4
|
+
"sourcesContent": ["import { useMemo } from 'react'\nimport { TLRichText } from 'tldraw'\nimport { renderCommentHtml } from './comment-render'\n\n/** @public */\nexport interface CommentBodyProps {\n\trichText: TLRichText\n\t/** Maps a member id to its current display name, so \\@mentions show the live name. */\n\tresolveName?(id: string): string | undefined\n}\n\n/**\n * Renders a comment's rich-text body read-only through the limited comment extension set (no\n * headings), so formatting (bold, links, lists, highlight) is preserved rather than flattened, and\n * headings can never render. Use this as the `body` of a `CommentCard` on a canvas.\n * @public @react\n */\nexport function CommentBody({ richText, resolveName }: CommentBodyProps) {\n\tconst html = useMemo(() => renderCommentHtml(richText, resolveName), [richText, resolveName])\n\treturn <div className=\"tlui-cmt-text\" dangerouslySetInnerHTML={{ __html: html }} />\n}\n"],
|
|
5
|
+
"mappings": "AAmBQ;AAnBR,SAAS,eAAe;AAExB,SAAS,yBAAyB;AAe3B,SAAS,YAAY,EAAE,UAAU,YAAY,GAAqB;AACxE,QAAM,OAAO,QAAQ,MAAM,kBAAkB,UAAU,WAAW,GAAG,CAAC,UAAU,WAAW,CAAC;AAC5F,SAAO,oBAAC,SAAI,WAAU,iBAAgB,yBAAyB,EAAE,QAAQ,KAAK,GAAG;AAClF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { deleteFromLocalStorage, getFromLocalStorage, setInLocalStorage } from "@tldraw/utils";
|
|
2
|
+
import { isCommentEmpty } from "../ui/comment-extensions.mjs";
|
|
3
|
+
const DRAFT_KEY_PREFIX = "tldraw-comment-draft";
|
|
4
|
+
const NEW_COMMENT_DRAFT = "new";
|
|
5
|
+
function replyDraftSlot(threadId) {
|
|
6
|
+
return `reply:${threadId}`;
|
|
7
|
+
}
|
|
8
|
+
function getCommentDraft(slot) {
|
|
9
|
+
const raw = getFromLocalStorage(`${DRAFT_KEY_PREFIX}:${slot}`);
|
|
10
|
+
if (!raw) return void 0;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
} catch {
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function saveCommentDraft(slot, value) {
|
|
18
|
+
if (isCommentEmpty(value)) {
|
|
19
|
+
clearCommentDraft(slot);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
setInLocalStorage(`${DRAFT_KEY_PREFIX}:${slot}`, JSON.stringify(value));
|
|
23
|
+
}
|
|
24
|
+
function clearCommentDraft(slot) {
|
|
25
|
+
deleteFromLocalStorage(`${DRAFT_KEY_PREFIX}:${slot}`);
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
NEW_COMMENT_DRAFT,
|
|
29
|
+
clearCommentDraft,
|
|
30
|
+
getCommentDraft,
|
|
31
|
+
replyDraftSlot,
|
|
32
|
+
saveCommentDraft
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=comment-drafts.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/canvas/comment-drafts.ts"],
|
|
4
|
+
"sourcesContent": ["import { deleteFromLocalStorage, getFromLocalStorage, setInLocalStorage } from '@tldraw/utils'\nimport type { TLRichText } from 'tldraw'\nimport { isCommentEmpty } from '../ui/comment-extensions'\n\nconst DRAFT_KEY_PREFIX = 'tldraw-comment-draft'\n\n/** The single slot for an unsent new-comment (placement composer) draft. The anchor is re-picked\n * by clicking when the composer reopens, so only the text is worth keeping \u2014 and without a\n * universal document id in the SDK, one slot is the honest scope. */\nexport const NEW_COMMENT_DRAFT = 'new'\n\n/** The draft slot for an unsent reply on a thread. Thread record ids are globally unique and\n * stable across reloads, so replies never need a document key. @public */\nexport function replyDraftSlot(threadId: string): string {\n\treturn `reply:${threadId}`\n}\n\n/** Read a saved unsent draft, or undefined when there is none (or it fails to parse). @public */\nexport function getCommentDraft(slot: string): TLRichText | undefined {\n\tconst raw = getFromLocalStorage(`${DRAFT_KEY_PREFIX}:${slot}`)\n\tif (!raw) return undefined\n\ttry {\n\t\treturn JSON.parse(raw) as TLRichText\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * Save an unsent draft so click-away and reload don't lose it \u2014 the flip side of not warning\n * before discard. Saving empty content clears the slot instead, so deleting your text and\n * closing doesn't resurrect an empty draft later.\n * @public\n */\nexport function saveCommentDraft(slot: string, value: TLRichText) {\n\tif (isCommentEmpty(value)) {\n\t\tclearCommentDraft(slot)\n\t\treturn\n\t}\n\tsetInLocalStorage(`${DRAFT_KEY_PREFIX}:${slot}`, JSON.stringify(value))\n}\n\n/** Remove a saved draft (after posting, or when its content was emptied). @public */\nexport function clearCommentDraft(slot: string) {\n\tdeleteFromLocalStorage(`${DRAFT_KEY_PREFIX}:${slot}`)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,wBAAwB,qBAAqB,yBAAyB;AAE/E,SAAS,sBAAsB;AAE/B,MAAM,mBAAmB;AAKlB,MAAM,oBAAoB;AAI1B,SAAS,eAAe,UAA0B;AACxD,SAAO,SAAS,QAAQ;AACzB;AAGO,SAAS,gBAAgB,MAAsC;AACrE,QAAM,MAAM,oBAAoB,GAAG,gBAAgB,IAAI,IAAI,EAAE;AAC7D,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACH,WAAO,KAAK,MAAM,GAAG;AAAA,EACtB,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAQO,SAAS,iBAAiB,MAAc,OAAmB;AACjE,MAAI,eAAe,KAAK,GAAG;AAC1B,sBAAkB,IAAI;AACtB;AAAA,EACD;AACA,oBAAkB,GAAG,gBAAgB,IAAI,IAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AACvE;AAGO,SAAS,kBAAkB,MAAc;AAC/C,yBAAuB,GAAG,gBAAgB,IAAI,IAAI,EAAE;AACrD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { getCommentRecord, getLiveComments } from "./comment-store.mjs";
|
|
2
|
+
import { getCommentingOptions } from "./options.mjs";
|
|
3
|
+
import { openThreadId } from "./state.mjs";
|
|
4
|
+
function historyModeFor(options, kind) {
|
|
5
|
+
switch (kind) {
|
|
6
|
+
case "delete":
|
|
7
|
+
return "ignore";
|
|
8
|
+
case "drag":
|
|
9
|
+
return options.dragHistory ?? options.history;
|
|
10
|
+
case "mutation":
|
|
11
|
+
return options.history;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function commitCommentMutation(editor, fn, kind = "mutation") {
|
|
15
|
+
const history = historyModeFor(getCommentingOptions(editor), kind);
|
|
16
|
+
let result;
|
|
17
|
+
editor.run(
|
|
18
|
+
() => {
|
|
19
|
+
result = fn();
|
|
20
|
+
},
|
|
21
|
+
{ history }
|
|
22
|
+
);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
function putRecordsInCommit(editor, records) {
|
|
26
|
+
editor.store.put(records);
|
|
27
|
+
}
|
|
28
|
+
function removeRecordsInCommit(editor, ids) {
|
|
29
|
+
editor.store.remove(ids);
|
|
30
|
+
}
|
|
31
|
+
function putCommentRecords(editor, records) {
|
|
32
|
+
commitCommentMutation(editor, () => putRecordsInCommit(editor, records));
|
|
33
|
+
}
|
|
34
|
+
function removeCommentRecords(editor, ids) {
|
|
35
|
+
commitCommentMutation(editor, () => removeRecordsInCommit(editor, ids));
|
|
36
|
+
}
|
|
37
|
+
function readLatest(editor, record) {
|
|
38
|
+
const current = getCommentRecord(editor, record.id);
|
|
39
|
+
return current?.typeName === record.typeName ? current : void 0;
|
|
40
|
+
}
|
|
41
|
+
function editComment(editor, comment, body) {
|
|
42
|
+
commitCommentMutation(editor, () => {
|
|
43
|
+
const current = readLatest(editor, comment);
|
|
44
|
+
if (!current) return;
|
|
45
|
+
putRecordsInCommit(editor, [{ ...current, body, editedAt: Date.now() }]);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function resolveThread(editor, thread, userId) {
|
|
49
|
+
commitCommentMutation(editor, () => {
|
|
50
|
+
const current = readLatest(editor, thread);
|
|
51
|
+
if (!current) return;
|
|
52
|
+
putRecordsInCommit(editor, [{ ...current, resolved: { at: Date.now(), by: userId } }]);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function reopenThread(editor, thread) {
|
|
56
|
+
commitCommentMutation(editor, () => {
|
|
57
|
+
const current = readLatest(editor, thread);
|
|
58
|
+
if (!current) return;
|
|
59
|
+
putRecordsInCommit(editor, [{ ...current, resolved: null }]);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function deleteComment(editor, comment) {
|
|
63
|
+
commitCommentMutation(
|
|
64
|
+
editor,
|
|
65
|
+
() => {
|
|
66
|
+
const current = readLatest(editor, comment);
|
|
67
|
+
if (!current || current.isDeleted) return;
|
|
68
|
+
const isLastInThread = getLiveComments(editor).filter((c) => c.threadId === current.threadId).length <= 1;
|
|
69
|
+
if (isLastInThread && openThreadId.get(editor) === current.threadId) {
|
|
70
|
+
openThreadId.set(editor, null);
|
|
71
|
+
}
|
|
72
|
+
putRecordsInCommit(editor, [{ ...current, isDeleted: true }]);
|
|
73
|
+
},
|
|
74
|
+
"delete"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function deleteThread(editor, thread) {
|
|
78
|
+
commitCommentMutation(
|
|
79
|
+
editor,
|
|
80
|
+
() => {
|
|
81
|
+
const current = readLatest(editor, thread);
|
|
82
|
+
if (!current) return;
|
|
83
|
+
if (openThreadId.get(editor) === current.id) {
|
|
84
|
+
openThreadId.set(editor, null);
|
|
85
|
+
}
|
|
86
|
+
putRecordsInCommit(editor, [{ ...current, isDeleted: true }]);
|
|
87
|
+
},
|
|
88
|
+
"delete"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
commitCommentMutation,
|
|
93
|
+
deleteComment,
|
|
94
|
+
deleteThread,
|
|
95
|
+
editComment,
|
|
96
|
+
putCommentRecords,
|
|
97
|
+
putRecordsInCommit,
|
|
98
|
+
removeCommentRecords,
|
|
99
|
+
removeRecordsInCommit,
|
|
100
|
+
reopenThread,
|
|
101
|
+
resolveThread
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=comment-mutations.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/canvas/comment-mutations.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n\tEditor,\n\tTLComment,\n\tTLCommentId,\n\tTLCommentReactionId,\n\tTLCommentThread,\n\tTLCommentThreadId,\n\tTLHistoryBatchOptions,\n\tTLRecord,\n\tTLRichText,\n} from 'tldraw'\nimport { getCommentRecord, getLiveComments, type TLCommentRecord } from './comment-store'\nimport { getCommentingOptions, type CommentingOptions } from './options'\nimport { openThreadId } from './state'\n\n/**\n * Every write to a comment record, and the undo/redo policy governing them.\n *\n * This file layers bottom-up: {@link commitCommentMutation} resolves the history mode,\n * {@link putCommentRecords} and {@link removeCommentRecords} are the typed writes that run under\n * it, and the verbs below are those writes plus the one rule each carries \u2014 a timestamp to stamp, a\n * shape to put the `resolved` field in, or the soft-delete protocol. The built-in thread view calls\n * exactly these verbs, so a UI of your own behaves the same as the one in the box.\n *\n * Every verb takes the record it acts on, but treats it as the identity of what to change rather\n * than as the value to write back \u2014 see {@link readLatest}.\n *\n * Posting carries no such rule, so it isn't a verb here: build the records with\n * `createCommentThread`/`createComment` and write them with {@link putCommentRecords}.\n */\n\n/**\n * Which history policy a comment write follows:\n *\n * - `mutation` \u2014 {@link CommentingOptions.history}: posts, replies, edits, resolves.\n * - `drag` \u2014 {@link CommentingOptions.dragHistory}, falling back to `history`: pin and region\n * re-anchors, which are spatial edits a host may reasonably want undoable alongside a shape move.\n * - `delete` \u2014 always `'ignore'`, whatever the options say. A soft-delete flag is write-once\n * server-side, so an undo clearing it would be vetoed and rebased rather than restore anything.\n *\n * @internal\n */\nexport type CommentMutationKind = 'delete' | 'drag' | 'mutation'\n\n/** The undo/redo mode a write of the given kind runs under. See {@link CommentMutationKind}. */\nfunction historyModeFor(\n\toptions: CommentingOptions,\n\tkind: CommentMutationKind\n): TLHistoryBatchOptions['history'] {\n\tswitch (kind) {\n\t\tcase 'delete':\n\t\t\treturn 'ignore'\n\t\tcase 'drag':\n\t\t\treturn options.dragHistory ?? options.history\n\t\tcase 'mutation':\n\t\t\treturn options.history\n\t}\n}\n\n/**\n * Commit a comment mutation with the configured undo/redo behavior, so the\n * {@link CommentingOptions.history} option governs whether it lands on the undo stack. Defaults to\n * `'ignore'`. See {@link CommentMutationKind} for what each kind resolves to.\n * @internal\n */\nexport function commitCommentMutation<T>(\n\teditor: Editor,\n\tfn: () => T,\n\tkind: CommentMutationKind = 'mutation'\n): T {\n\tconst history = historyModeFor(getCommentingOptions(editor), kind)\n\tlet result: T\n\teditor.run(\n\t\t() => {\n\t\t\tresult = fn()\n\t\t},\n\t\t{ history }\n\t)\n\treturn result!\n}\n\n/**\n * Write records without opening a commit, for call sites that already sit inside one.\n *\n * `editor.run`'s history option isn't additive \u2014 a nested run overwrites the enclosing mode for its\n * own scope \u2014 so a {@link putCommentRecords} call inside a commit would discard the mode its caller\n * chose. A pin drag committed as `drag` under `dragHistory: 'record'` would land back on\n * `history: 'ignore'` and quietly stop being undoable. Inside a commit, write with this.\n *\n * @internal\n */\nexport function putRecordsInCommit(editor: Editor, records: TLCommentRecord[]): void {\n\teditor.store.put(records as unknown as TLRecord[])\n}\n\n/** {@link putRecordsInCommit}'s counterpart for removals. @internal */\nexport function removeRecordsInCommit(\n\teditor: Editor,\n\tids: (TLCommentId | TLCommentReactionId | TLCommentThreadId)[]\n): void {\n\teditor.store.remove(ids as unknown as TLRecord['id'][])\n}\n\n/**\n * Write comment records to the store, under the configured\n * {@link CommentingOptions.history} behavior \u2014 so a record you write lands on the undo stack (or\n * doesn't) exactly like one the built-in UI writes. Defaults to `'ignore'`.\n *\n * Use it to seed or import threads, and to save an edit. To delete, prefer\n * {@link deleteComment} and {@link deleteThread} over {@link removeCommentRecords}: comments are\n * soft-deleted, and a synced server rejects the hard delete.\n *\n * @public\n */\nexport function putCommentRecords(editor: Editor, records: TLCommentRecord[]): void {\n\tcommitCommentMutation(editor, () => putRecordsInCommit(editor, records))\n}\n\n/**\n * Remove comment records from the store by id, under the configured\n * {@link CommentingOptions.history} behavior.\n *\n * This is a hard delete, which is rarely what you want for a comment or a thread: the built-in UI\n * soft-deletes them ({@link deleteComment}, {@link deleteThread}) so the server can prune the\n * records \u2014 including reactions, which belong to whoever left them rather than to the deleter. A\n * server that enforces per-record permissions will veto a hard delete outright. Reach for this on\n * a local, unsynced comment store, or to drop a reaction (which is a hard delete \u2014 see\n * {@link toggleCommentReaction}).\n *\n * @public\n */\nexport function removeCommentRecords(\n\teditor: Editor,\n\tids: (TLCommentId | TLCommentReactionId | TLCommentThreadId)[]\n): void {\n\tcommitCommentMutation(editor, () => removeRecordsInCommit(editor, ids))\n}\n\n/**\n * The record as the store currently holds it, or `undefined` if it isn't there any more.\n *\n * A verb is handed a record, but that record is a snapshot of whenever its caller got hold of one,\n * and a comment record moves underneath it. A thread's `anchor` and `pageId` are rewritten without\n * anyone touching the thread: deleting a pinned shape converts the anchor to a point, reparenting\n * one rehomes the thread to another page, a pin drag re-anchors it. Writing the caller's snapshot\n * back would put those fields as they were and sync the revert out to everyone.\n *\n * `put` is also an upsert, so a record a remote delete has already removed would come back \u2014 with\n * `isDeleted: false` on an edit or a resolve. That's the multiplayer surprise the schema warns\n * about (see `TLComment`), reached by a route the history option doesn't cover.\n *\n * So a verb reads what it's changing rather than trusting what it was given, and a record that's\n * gone is a no-op: whatever the change was, there's nothing left for it to apply to.\n */\nfunction readLatest<T extends TLComment | TLCommentThread>(\n\teditor: Editor,\n\trecord: T\n): T | undefined {\n\tconst current = getCommentRecord(editor, record.id)\n\t// Record ids carry their type, so a matching `typeName` means a record of exactly T.\n\treturn current?.typeName === record.typeName ? (current as T) : undefined\n}\n\n/**\n * Replace a comment's body and stamp it as edited, which is what renders the \"(edited)\" marker on\n * its byline.\n *\n * Editing is the author's to do. The built-in UI only offers it on your own comments, and a server\n * that enforces per-record permissions rejects anyone else's edit.\n *\n * The `comment` you pass says which comment to edit; the body lands on the version the store\n * currently holds, so a copy you've held on to can't revert a change made since it, and can't\n * re-create a comment that's already been removed \u2014 editing one of those does nothing.\n *\n * @example\n * ```ts\n * editComment(editor, comment, toRichText('Actually, make it dashed'))\n * ```\n *\n * @public\n */\nexport function editComment(editor: Editor, comment: TLComment, body: TLRichText): void {\n\tcommitCommentMutation(editor, () => {\n\t\tconst current = readLatest(editor, comment)\n\t\tif (!current) return\n\t\tputRecordsInCommit(editor, [{ ...current, body, editedAt: Date.now() }])\n\t})\n}\n\n/**\n * Mark a thread resolved, stamping who resolved it and when. Resolved threads keep their pin (a\n * checked one) and are hidden from the sidebar until its \"show resolved\" filter is on.\n *\n * Only the resolution is written: the rest of the thread is read fresh, so resolving with a stale\n * copy in hand won't drag a pin back to where it used to be. A no-op on a thread that's gone.\n *\n * @public\n */\nexport function resolveThread(editor: Editor, thread: TLCommentThread, userId: string): void {\n\tcommitCommentMutation(editor, () => {\n\t\tconst current = readLatest(editor, thread)\n\t\tif (!current) return\n\t\tputRecordsInCommit(editor, [{ ...current, resolved: { at: Date.now(), by: userId } }])\n\t})\n}\n\n/**\n * Reopen a resolved thread, clearing the resolution. A no-op on a thread that isn't resolved, and\n * on one that's gone. Like {@link resolveThread}, it touches only the resolution.\n *\n * @public\n */\nexport function reopenThread(editor: Editor, thread: TLCommentThread): void {\n\tcommitCommentMutation(editor, () => {\n\t\tconst current = readLatest(editor, thread)\n\t\tif (!current) return\n\t\tputRecordsInCommit(editor, [{ ...current, resolved: null }])\n\t})\n}\n\n/**\n * Delete a comment.\n *\n * This is a soft delete: it sets the record's `isDeleted` flag rather than removing it, and the\n * server prunes the comment and its reactions once the flag is persisted. That way no client ever\n * removes records it doesn't own \u2014 a reaction belongs to whoever left it \u2014 and a server enforcing\n * per-record permissions has a write it can check rather than a deletion it can only refuse.\n * Deleting is the author's to do; the built-in UI only offers it on your own comments.\n *\n * The write is never undoable, whatever {@link CommentingOptions.history} says: the flag is\n * write-once server-side, so an undo clearing it would be vetoed and rebased rather than bring the\n * comment back.\n *\n * Deleting a thread's last comment leaves the thread with no surface \u2014 an empty thread renders\n * nothing \u2014 and closes it if it's open. The thread record is left for the server to prune, since\n * the deleter may not be its creator.\n *\n * A comment that's already deleted, or already pruned, is a no-op.\n *\n * @public\n */\nexport function deleteComment(editor: Editor, comment: TLComment): void {\n\tcommitCommentMutation(\n\t\teditor,\n\t\t() => {\n\t\t\tconst current = readLatest(editor, comment)\n\t\t\t// Deleting twice \u2014 a double activation, a handler firing on a copy taken before the first\n\t\t\t// delete \u2014 is nothing to do rather than something to redo. The check below counts the\n\t\t\t// comment being deleted among the live ones, so it only reads as \"the last one\" while\n\t\t\t// this delete is the one taking it away.\n\t\t\tif (!current || current.isDeleted) return\n\t\t\tconst isLastInThread =\n\t\t\t\tgetLiveComments(editor).filter((c) => c.threadId === current.threadId).length <= 1\n\t\t\tif (isLastInThread && openThreadId.get(editor) === current.threadId) {\n\t\t\t\topenThreadId.set(editor, null)\n\t\t\t}\n\t\t\tputRecordsInCommit(editor, [{ ...current, isDeleted: true }])\n\t\t},\n\t\t'delete'\n\t)\n}\n\n/**\n * Delete a thread and, with it, the whole conversation.\n *\n * A soft delete on the same model as {@link deleteComment}: the flag goes on the thread record and\n * the server prunes the thread, its comments, and their reactions once it's persisted. Deleting a\n * thread is its creator's to do \u2014 a server enforcing per-record permissions vetoes anyone else \u2014\n * and the write is never undoable.\n *\n * Closes the thread if it's the open one. A thread that's already pruned is a no-op.\n *\n * @public\n */\nexport function deleteThread(editor: Editor, thread: TLCommentThread): void {\n\tcommitCommentMutation(\n\t\teditor,\n\t\t() => {\n\t\t\tconst current = readLatest(editor, thread)\n\t\t\tif (!current) return\n\t\t\tif (openThreadId.get(editor) === current.id) {\n\t\t\t\topenThreadId.set(editor, null)\n\t\t\t}\n\t\t\tputRecordsInCommit(editor, [{ ...current, isDeleted: true }])\n\t\t},\n\t\t'delete'\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|