@tldraw/commenting 0.0.0-bootstrap → 5.3.0-canary.04044ed9e96d
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 +1152 -350
- package/dist-cjs/canvas/anchor-lifecycle.js +148 -0
- package/dist-cjs/canvas/anchor-lifecycle.js.map +7 -0
- package/dist-cjs/{ui/avatar.js → canvas/canvas-events.js} +14 -22
- package/dist-cjs/canvas/canvas-events.js.map +7 -0
- package/dist-cjs/canvas/cluster-badge.js +121 -0
- package/dist-cjs/canvas/cluster-badge.js.map +7 -0
- package/dist-cjs/canvas/cluster-fade.js +85 -0
- package/dist-cjs/canvas/cluster-fade.js.map +7 -0
- package/dist-cjs/canvas/cluster-input.js +53 -3
- package/dist-cjs/canvas/cluster-input.js.map +2 -2
- package/dist-cjs/canvas/cluster-model.js +252 -0
- package/dist-cjs/canvas/cluster-model.js.map +7 -0
- 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 +152 -0
- package/dist-cjs/canvas/comment-mutations.js.map +7 -0
- package/dist-cjs/canvas/comment-reactions.js +160 -0
- package/dist-cjs/canvas/comment-reactions.js.map +7 -0
- package/dist-cjs/canvas/comment-render.js +9 -6
- 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 +63 -14
- package/dist-cjs/canvas/comment-tool.js.map +2 -2
- package/dist-cjs/canvas/comments-filter-menu.js +17 -37
- package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overflow-menu.js +25 -22
- package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overlay.js +151 -989
- package/dist-cjs/canvas/comments-overlay.js.map +3 -3
- package/dist-cjs/canvas/comments-sidebar.js +49 -41
- package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
- package/dist-cjs/canvas/comments-visibility-toggle.js +45 -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 +28 -3
- package/dist-cjs/canvas/hooks.js.map +2 -2
- package/dist-cjs/canvas/license.js.map +1 -1
- package/dist-cjs/canvas/mobile-placement.js +91 -0
- package/dist-cjs/canvas/mobile-placement.js.map +7 -0
- package/dist-cjs/canvas/options.js +57 -2
- package/dist-cjs/canvas/options.js.map +2 -2
- package/dist-cjs/canvas/pending-composer.js +134 -0
- package/dist-cjs/canvas/pending-composer.js.map +7 -0
- package/dist-cjs/canvas/pin-stacking.js +81 -0
- package/dist-cjs/canvas/pin-stacking.js.map +7 -0
- package/dist-cjs/canvas/region-box.js +113 -0
- package/dist-cjs/canvas/region-box.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-pin.js +304 -0
- package/dist-cjs/canvas/thread-pin.js.map +7 -0
- package/dist-cjs/canvas/thread-preview.js +177 -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 +82 -21
- package/dist-cjs/canvas/thread-state.js.map +2 -2
- package/dist-cjs/canvas/thread-view.js +450 -0
- package/dist-cjs/canvas/thread-view.js.map +7 -0
- package/dist-cjs/clustering/computeClusterTable.js +6 -6
- package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
- package/dist-cjs/clustering/replay.js +89 -22
- package/dist-cjs/clustering/replay.js.map +3 -3
- package/dist-cjs/clustering/runtime.js +126 -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 +904 -353
- package/dist-cjs/index.js +52 -28
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/ui/byline.js +10 -6
- 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 +124 -39
- package/dist-cjs/ui/comment-composer.js.map +2 -2
- package/dist-cjs/ui/comment-pin.js +11 -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 +39 -40
- 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 +16 -1
- package/dist-cjs/ui/format-time.js.map +2 -2
- package/dist-cjs/ui/icons.js +115 -0
- package/dist-cjs/ui/icons.js.map +7 -0
- package/dist-cjs/ui/reaction-picker.js +71 -0
- package/dist-cjs/ui/reaction-picker.js.map +7 -0
- package/dist-cjs/ui/reaction.js +58 -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 +13 -1
- package/dist-cjs/ui/send-button.js.map +2 -2
- package/dist-cjs/ui/{comment-text.js → visual-viewport.js} +12 -9
- package/dist-cjs/ui/visual-viewport.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/canvas-events.mjs +15 -0
- package/dist-esm/canvas/canvas-events.mjs.map +7 -0
- package/dist-esm/canvas/cluster-badge.mjs +106 -0
- package/dist-esm/canvas/cluster-badge.mjs.map +7 -0
- package/dist-esm/canvas/cluster-fade.mjs +65 -0
- package/dist-esm/canvas/cluster-fade.mjs.map +7 -0
- package/dist-esm/canvas/cluster-input.mjs +54 -4
- package/dist-esm/canvas/cluster-input.mjs.map +2 -2
- package/dist-esm/canvas/cluster-model.mjs +236 -0
- package/dist-esm/canvas/cluster-model.mjs.map +7 -0
- 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 +132 -0
- package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
- package/dist-esm/canvas/comment-reactions.mjs +145 -0
- package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
- package/dist-esm/canvas/comment-render.mjs +10 -7
- 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 +66 -17
- package/dist-esm/canvas/comment-tool.mjs.map +2 -2
- package/dist-esm/canvas/comments-filter-menu.mjs +17 -36
- package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overflow-menu.mjs +27 -22
- package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overlay.mjs +157 -1010
- package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
- package/dist-esm/canvas/comments-sidebar.mjs +53 -46
- package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
- package/dist-esm/canvas/comments-visibility-toggle.mjs +25 -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 +35 -5
- package/dist-esm/canvas/hooks.mjs.map +2 -2
- package/dist-esm/canvas/license.mjs.map +1 -1
- package/dist-esm/canvas/mobile-placement.mjs +71 -0
- package/dist-esm/canvas/mobile-placement.mjs.map +7 -0
- package/dist-esm/canvas/options.mjs +59 -3
- package/dist-esm/canvas/options.mjs.map +2 -2
- package/dist-esm/canvas/pending-composer.mjs +126 -0
- package/dist-esm/canvas/pending-composer.mjs.map +7 -0
- package/dist-esm/canvas/pin-stacking.mjs +61 -0
- package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
- package/dist-esm/canvas/region-box.mjs +93 -0
- package/dist-esm/canvas/region-box.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-pin.mjs +310 -0
- package/dist-esm/canvas/thread-pin.mjs.map +7 -0
- package/dist-esm/canvas/thread-preview.mjs +163 -0
- package/dist-esm/canvas/thread-preview.mjs.map +7 -0
- package/dist-esm/canvas/thread-stack.mjs +189 -0
- package/dist-esm/canvas/thread-stack.mjs.map +7 -0
- package/dist-esm/canvas/thread-state.mjs +86 -22
- package/dist-esm/canvas/thread-state.mjs.map +2 -2
- package/dist-esm/canvas/thread-view.mjs +461 -0
- package/dist-esm/canvas/thread-view.mjs.map +7 -0
- package/dist-esm/clustering/computeClusterTable.mjs +6 -6
- package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
- package/dist-esm/clustering/replay.mjs +89 -22
- package/dist-esm/clustering/replay.mjs.map +3 -3
- package/dist-esm/clustering/runtime.mjs +126 -7
- package/dist-esm/clustering/runtime.mjs.map +2 -2
- package/dist-esm/index.d.mts +904 -353
- package/dist-esm/index.mjs +102 -43
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/ui/byline.mjs +11 -7
- 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 +135 -39
- package/dist-esm/ui/comment-composer.mjs.map +2 -2
- package/dist-esm/ui/comment-pin.mjs +11 -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 +51 -50
- 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 +16 -1
- package/dist-esm/ui/format-time.mjs.map +2 -2
- package/dist-esm/ui/icons.mjs +95 -0
- package/dist-esm/ui/icons.mjs.map +7 -0
- package/dist-esm/ui/reaction-picker.mjs +59 -0
- package/dist-esm/ui/reaction-picker.mjs.map +7 -0
- package/dist-esm/ui/reaction.mjs +59 -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 +13 -1
- package/dist-esm/ui/send-button.mjs.map +2 -2
- package/dist-esm/ui/visual-viewport.mjs +12 -0
- package/dist-esm/ui/visual-viewport.mjs.map +7 -0
- package/package.json +6 -6
- package/src/canvas/anchor-lifecycle.test.ts +290 -0
- package/src/canvas/anchor-lifecycle.ts +191 -0
- package/src/canvas/canvas-events.ts +18 -0
- package/src/canvas/canvas.css +464 -55
- package/src/canvas/cluster-badge.tsx +133 -0
- package/src/canvas/cluster-fade.ts +102 -0
- package/src/canvas/cluster-input.test.ts +178 -36
- package/src/canvas/cluster-input.ts +95 -9
- package/src/canvas/cluster-model.ts +378 -0
- 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 +452 -0
- package/src/canvas/comment-mutations.ts +299 -0
- package/src/canvas/comment-reactions.test.ts +154 -0
- package/src/canvas/comment-reactions.tsx +246 -0
- package/src/canvas/comment-render.test.ts +1 -1
- package/src/canvas/comment-render.ts +17 -13
- package/src/canvas/comment-store.ts +68 -33
- package/src/canvas/comment-tool.test.ts +162 -0
- package/src/canvas/comment-tool.tsx +103 -25
- package/src/canvas/comments-filter-menu.tsx +20 -36
- package/src/canvas/comments-overflow-menu.tsx +17 -19
- package/src/canvas/comments-overlay.tsx +252 -1369
- package/src/canvas/comments-sidebar.test.ts +45 -0
- package/src/canvas/comments-sidebar.tsx +112 -73
- package/src/canvas/comments-visibility-toggle.tsx +25 -0
- package/src/canvas/context.ts +48 -0
- package/src/canvas/hooks.test.ts +94 -0
- package/src/canvas/hooks.ts +66 -11
- package/src/canvas/license.ts +1 -1
- package/src/canvas/mobile-placement.ts +115 -0
- package/src/canvas/options.test.ts +176 -4
- package/src/canvas/options.ts +276 -18
- package/src/canvas/pending-composer.tsx +157 -0
- package/src/canvas/pin-stacking.test.ts +224 -0
- package/src/canvas/pin-stacking.ts +103 -0
- package/src/canvas/region-box.tsx +124 -0
- package/src/canvas/sidebar-filters.ts +4 -3
- package/src/canvas/state.ts +88 -39
- package/src/canvas/thread-pin.tsx +418 -0
- package/src/canvas/thread-preview.test.ts +91 -0
- package/src/canvas/thread-preview.tsx +294 -0
- package/src/canvas/thread-stack.tsx +242 -0
- package/src/canvas/thread-state.test.ts +260 -0
- package/src/canvas/thread-state.ts +171 -37
- package/src/canvas/thread-view.test.ts +72 -0
- package/src/canvas/thread-view.tsx +614 -0
- package/src/clustering/computeClusterTable.test.ts +24 -23
- package/src/clustering/computeClusterTable.ts +17 -8
- package/src/clustering/replay.test.ts +0 -7
- package/src/clustering/replay.ts +131 -32
- package/src/clustering/runtime.test.ts +267 -13
- package/src/clustering/runtime.ts +176 -19
- package/src/clustering/schedule.test.ts +0 -6
- package/src/clustering/screen-offsets.test.ts +171 -0
- package/src/clustering/types.ts +19 -9
- package/src/index.ts +91 -42
- package/src/ui/byline.tsx +21 -9
- package/src/ui/comment-card.tsx +20 -10
- package/src/ui/comment-composer.tsx +178 -56
- package/src/ui/comment-pin.tsx +13 -2
- package/src/ui/comment-thread.tsx +13 -6
- package/src/ui/comments-list.test.ts +31 -0
- package/src/ui/comments-list.tsx +82 -47
- package/src/ui/comments.css +577 -294
- 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.test.ts +69 -0
- package/src/ui/format-time.ts +27 -2
- package/src/ui/icons.tsx +116 -0
- package/src/ui/reaction-picker.tsx +102 -0
- package/src/ui/reaction.tsx +125 -6
- package/src/ui/reactions.tsx +69 -10
- package/src/ui/reply-count.ts +16 -0
- package/src/ui/send-button.tsx +13 -3
- package/src/ui/visual-viewport.test.ts +36 -0
- package/src/ui/visual-viewport.ts +27 -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.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/canvas/hooks.js
CHANGED
|
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var hooks_exports = {};
|
|
20
20
|
__export(hooks_exports, {
|
|
21
|
+
getCommentsByThread: () => getCommentsByThread,
|
|
21
22
|
useCommentThreads: () => useCommentThreads,
|
|
22
23
|
useComments: () => useComments,
|
|
23
24
|
useThreadComments: () => useThreadComments
|
|
@@ -26,19 +27,43 @@ module.exports = __toCommonJS(hooks_exports);
|
|
|
26
27
|
var import_tldraw = require("tldraw");
|
|
27
28
|
var import_comment_store = require("./comment-store");
|
|
28
29
|
function useCommentThreads(editor) {
|
|
29
|
-
return (0, import_tldraw.useValue)("comment threads", () => (0, import_comment_store.
|
|
30
|
+
return (0, import_tldraw.useValue)("comment threads", () => (0, import_comment_store.getLiveCommentThreads)(editor), [editor]);
|
|
31
|
+
}
|
|
32
|
+
const commentsByThread = new import_tldraw.WeakCache();
|
|
33
|
+
function getCommentsByThread(editor) {
|
|
34
|
+
return commentsByThread.get(
|
|
35
|
+
editor,
|
|
36
|
+
() => (0, import_tldraw.computed)("comments by thread", (prev) => {
|
|
37
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const comment of (0, import_comment_store.getLiveComments)(editor)) {
|
|
39
|
+
const existing = byThread.get(comment.threadId);
|
|
40
|
+
if (existing) existing.push(comment);
|
|
41
|
+
else byThread.set(comment.threadId, [comment]);
|
|
42
|
+
}
|
|
43
|
+
for (const [threadId, comments] of byThread) {
|
|
44
|
+
comments.sort((a, b) => a.createdAt - b.createdAt);
|
|
45
|
+
if ((0, import_tldraw.isUninitialized)(prev)) continue;
|
|
46
|
+
const prevComments = prev.get(threadId);
|
|
47
|
+
if (prevComments && prevComments.length === comments.length && comments.every((comment, i) => prevComments[i] === comment)) {
|
|
48
|
+
byThread.set(threadId, prevComments);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return byThread;
|
|
52
|
+
})
|
|
53
|
+
);
|
|
30
54
|
}
|
|
31
55
|
function useThreadComments(editor, threadId) {
|
|
32
56
|
return (0, import_tldraw.useValue)(
|
|
33
57
|
"thread comments",
|
|
34
|
-
() => (
|
|
58
|
+
() => getCommentsByThread(editor).get().get(threadId) ?? EMPTY_COMMENTS,
|
|
35
59
|
[editor, threadId]
|
|
36
60
|
);
|
|
37
61
|
}
|
|
62
|
+
const EMPTY_COMMENTS = [];
|
|
38
63
|
function useComments(editor) {
|
|
39
64
|
return (0, import_tldraw.useValue)(
|
|
40
65
|
"all comments",
|
|
41
|
-
() => (0, import_comment_store.
|
|
66
|
+
() => (0, import_comment_store.getLiveComments)(editor).sort((a, b) => a.createdAt - b.createdAt),
|
|
42
67
|
[editor]
|
|
43
68
|
);
|
|
44
69
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/hooks.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n\tcomputed,\n\tComputed,\n\tEditor,\n\tisUninitialized,\n\tTLComment,\n\tTLCommentThread,\n\tTLCommentThreadId,\n\tuseValue,\n\tWeakCache,\n} from 'tldraw'\nimport { getLiveComments, getLiveCommentThreads } from './comment-store'\n\n/**\n * The comment threads that should render (pins, sidebar), reactively \u2014 the live set described by\n * {@link getLiveCommentThreads}. Use `getCommentThreads` for the unfiltered set, including\n * soft-deleted threads awaiting the server's prune.\n *\n * @public\n */\nexport function useCommentThreads(editor: Editor): TLCommentThread[] {\n\treturn useValue('comment threads', () => getLiveCommentThreads(editor), [editor])\n}\n\n/**\n * Every live comment grouped by thread, oldest first within each thread. One computed shared by\n * every mounted thread: each `useThreadComments` would otherwise scan and sort the whole comment\n * set, so N open threads did N full passes on every comment mutation. Cached per editor so the\n * grouping survives re-renders and is rebuilt only when the comment set actually changes.\n */\nconst commentsByThread = new WeakCache<Editor, Computed<Map<TLCommentThreadId, TLComment[]>>>()\n\n/** Exported for tests; use {@link useThreadComments} to consume. @internal */\nexport function getCommentsByThread(editor: Editor) {\n\treturn commentsByThread.get(editor, () =>\n\t\tcomputed('comments by thread', (prev) => {\n\t\t\tconst byThread = new Map<TLCommentThreadId, TLComment[]>()\n\t\t\tfor (const comment of getLiveComments(editor)) {\n\t\t\t\tconst existing = byThread.get(comment.threadId)\n\t\t\t\tif (existing) existing.push(comment)\n\t\t\t\telse byThread.set(comment.threadId, [comment])\n\t\t\t}\n\t\t\tfor (const [threadId, comments] of byThread) {\n\t\t\t\tcomments.sort((a, b) => a.createdAt - b.createdAt)\n\t\t\t\t// Reuse the previous per-thread array when that thread's comment list is unchanged\n\t\t\t\t// (record identities are stable while unchanged), so `useThreadComments` subscribers\n\t\t\t\t// on other threads don't re-render when one thread gains a reply.\n\t\t\t\tif (isUninitialized(prev)) continue\n\t\t\t\tconst prevComments = prev.get(threadId)\n\t\t\t\tif (\n\t\t\t\t\tprevComments &&\n\t\t\t\t\tprevComments.length === comments.length &&\n\t\t\t\t\tcomments.every((comment, i) => prevComments[i] === comment)\n\t\t\t\t) {\n\t\t\t\t\tbyThread.set(threadId, prevComments)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn byThread\n\t\t})\n\t)\n}\n\n/** A thread's live comments, oldest first, reactively. @public */\nexport function useThreadComments(editor: Editor, threadId: TLCommentThreadId): TLComment[] {\n\treturn useValue(\n\t\t'thread comments',\n\t\t() => getCommentsByThread(editor).get().get(threadId) ?? EMPTY_COMMENTS,\n\t\t[editor, threadId]\n\t)\n}\n\n/** Shared empty result so a thread with no comments doesn't churn referential equality. */\nconst EMPTY_COMMENTS: TLComment[] = []\n\n/** Every live comment in the store ({@link getLiveComments}), oldest first, reactively. Group by\n * `threadId` for per-thread lists. @public */\nexport function useComments(editor: Editor): TLComment[] {\n\treturn useValue(\n\t\t'all comments',\n\t\t() => getLiveComments(editor).sort((a, b) => a.createdAt - b.createdAt),\n\t\t[editor]\n\t)\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAUO;AACP,2BAAuD;AAShD,SAAS,kBAAkB,QAAmC;AACpE,aAAO,wBAAS,mBAAmB,UAAM,4CAAsB,MAAM,GAAG,CAAC,MAAM,CAAC;AACjF;AAQA,MAAM,mBAAmB,IAAI,wBAAiE;AAGvF,SAAS,oBAAoB,QAAgB;AACnD,SAAO,iBAAiB;AAAA,IAAI;AAAA,IAAQ,UACnC,wBAAS,sBAAsB,CAAC,SAAS;AACxC,YAAM,WAAW,oBAAI,IAAoC;AACzD,iBAAW,eAAW,sCAAgB,MAAM,GAAG;AAC9C,cAAM,WAAW,SAAS,IAAI,QAAQ,QAAQ;AAC9C,YAAI,SAAU,UAAS,KAAK,OAAO;AAAA,YAC9B,UAAS,IAAI,QAAQ,UAAU,CAAC,OAAO,CAAC;AAAA,MAC9C;AACA,iBAAW,CAAC,UAAU,QAAQ,KAAK,UAAU;AAC5C,iBAAS,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAIjD,gBAAI,+BAAgB,IAAI,EAAG;AAC3B,cAAM,eAAe,KAAK,IAAI,QAAQ;AACtC,YACC,gBACA,aAAa,WAAW,SAAS,UACjC,SAAS,MAAM,CAAC,SAAS,MAAM,aAAa,CAAC,MAAM,OAAO,GACzD;AACD,mBAAS,IAAI,UAAU,YAAY;AAAA,QACpC;AAAA,MACD;AACA,aAAO;AAAA,IACR,CAAC;AAAA,EACF;AACD;AAGO,SAAS,kBAAkB,QAAgB,UAA0C;AAC3F,aAAO;AAAA,IACN;AAAA,IACA,MAAM,oBAAoB,MAAM,EAAE,IAAI,EAAE,IAAI,QAAQ,KAAK;AAAA,IACzD,CAAC,QAAQ,QAAQ;AAAA,EAClB;AACD;AAGA,MAAM,iBAA8B,CAAC;AAI9B,SAAS,YAAY,QAA6B;AACxD,aAAO;AAAA,IACN;AAAA,IACA,UAAM,sCAAgB,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,IACtE,CAAC,MAAM;AAAA,EACR;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/license.ts"],
|
|
4
|
-
"sourcesContent": ["import { useLicenseContext, useLicenseFeatureFlag } from 'tldraw'\n\n/**\n * Whether commenting is licensed for this editor. Enabled in development; in production it requires\n * a tldraw license that includes the commenting feature (or the collaboration umbrella that grants\n * it). Reactive: re-reads when license validation resolves, and returns `false` while validation is\n * pending, so gated UI stays hidden until the license is confirmed.\n *\n * The built-in commenting components (`CanvasComments`, `CanvasCommentsSidebar`, and the comment\n * tool's
|
|
4
|
+
"sourcesContent": ["import { useLicenseContext, useLicenseFeatureFlag } from 'tldraw'\n\n/**\n * Whether commenting is licensed for this editor. Enabled in development; in production it requires\n * a tldraw license that includes the commenting feature (or the collaboration umbrella that grants\n * it). Reactive: re-reads when license validation resolves, and returns `false` while validation is\n * pending, so gated UI stays hidden until the license is confirmed.\n *\n * The built-in commenting components (`CanvasComments`, `CanvasCommentsSidebar`, and the comment\n * tool's Quick Action) gate on this. Use it to gate any custom commenting UI the same way.\n * @public\n */\nexport function useCommentingEnabled(): boolean {\n\treturn useLicenseFeatureFlag(useLicenseContext(), 'commenting')\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAyD;AAYlD,SAAS,uBAAgC;AAC/C,aAAO,yCAAsB,iCAAkB,GAAG,YAAY;AAC/D;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var mobile_placement_exports = {};
|
|
20
|
+
__export(mobile_placement_exports, {
|
|
21
|
+
useIsMobileCommenting: () => useIsMobileCommenting,
|
|
22
|
+
useMobilePlacement: () => useMobilePlacement
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(mobile_placement_exports);
|
|
25
|
+
var import_react = require("react");
|
|
26
|
+
var import_tldraw = require("tldraw");
|
|
27
|
+
var import_visual_viewport = require("../ui/visual-viewport");
|
|
28
|
+
function useIsMobileCommenting() {
|
|
29
|
+
let breakpoint;
|
|
30
|
+
try {
|
|
31
|
+
breakpoint = (0, import_tldraw.useBreakpoint)();
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return breakpoint < import_tldraw.PORTRAIT_BREAKPOINT.TABLET_SM;
|
|
36
|
+
}
|
|
37
|
+
const VIEWPORT_MARGIN = 8;
|
|
38
|
+
function useMobilePlacement(ref, base, enabled) {
|
|
39
|
+
const container = (0, import_tldraw.useContainer)();
|
|
40
|
+
const { x: baseX, y: baseY } = base;
|
|
41
|
+
const [placed, setPlaced] = (0, import_react.useState)(() => ({
|
|
42
|
+
left: baseX,
|
|
43
|
+
top: baseY
|
|
44
|
+
}));
|
|
45
|
+
const baseRef = (0, import_react.useRef)({ x: baseX, y: baseY });
|
|
46
|
+
const update = (0, import_react.useCallback)(() => {
|
|
47
|
+
if (!enabled) return;
|
|
48
|
+
const el = ref.current;
|
|
49
|
+
if (!el) return;
|
|
50
|
+
const win = container.ownerDocument.defaultView ?? window;
|
|
51
|
+
const { x, y } = baseRef.current;
|
|
52
|
+
const cRect = container.getBoundingClientRect();
|
|
53
|
+
const vp = (0, import_visual_viewport.getVisibleViewport)(win);
|
|
54
|
+
const top = vp.top - cRect.top + VIEWPORT_MARGIN;
|
|
55
|
+
const bottom = vp.bottom - cRect.top - VIEWPORT_MARGIN;
|
|
56
|
+
const left = vp.left - cRect.left + VIEWPORT_MARGIN;
|
|
57
|
+
const right = vp.right - cRect.left - VIEWPORT_MARGIN;
|
|
58
|
+
const w = el.offsetWidth;
|
|
59
|
+
const h = el.offsetHeight;
|
|
60
|
+
const nextLeft = Math.max(left, Math.min(x, right - w));
|
|
61
|
+
const nextTop = Math.min(y, Math.max(top, bottom - h));
|
|
62
|
+
setPlaced(
|
|
63
|
+
(prev) => prev.left === nextLeft && prev.top === nextTop ? prev : { left: nextLeft, top: nextTop }
|
|
64
|
+
);
|
|
65
|
+
}, [container, ref, enabled]);
|
|
66
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
67
|
+
baseRef.current = { x: baseX, y: baseY };
|
|
68
|
+
update();
|
|
69
|
+
}, [baseX, baseY, update]);
|
|
70
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
71
|
+
if (!enabled) return;
|
|
72
|
+
const el = ref.current;
|
|
73
|
+
if (!el) return;
|
|
74
|
+
const win = container.ownerDocument.defaultView ?? window;
|
|
75
|
+
const ro = new ResizeObserver(update);
|
|
76
|
+
ro.observe(el);
|
|
77
|
+
const vv = win.visualViewport;
|
|
78
|
+
vv?.addEventListener("resize", update);
|
|
79
|
+
vv?.addEventListener("scroll", update);
|
|
80
|
+
win.addEventListener("resize", update);
|
|
81
|
+
return () => {
|
|
82
|
+
ro.disconnect();
|
|
83
|
+
vv?.removeEventListener("resize", update);
|
|
84
|
+
vv?.removeEventListener("scroll", update);
|
|
85
|
+
win.removeEventListener("resize", update);
|
|
86
|
+
};
|
|
87
|
+
}, [container, ref, enabled, update]);
|
|
88
|
+
if (!enabled) return { left: baseX, top: baseY };
|
|
89
|
+
return placed;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=mobile-placement.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/canvas/mobile-placement.ts"],
|
|
4
|
+
"sourcesContent": ["import { RefObject, useCallback, useLayoutEffect, useRef, useState } from 'react'\nimport { PORTRAIT_BREAKPOINT, useBreakpoint, useContainer } from 'tldraw'\nimport { getVisibleViewport } from '../ui/visual-viewport'\n\n/**\n * Mobile mode for the commenting surfaces \u2014 the same breakpoint gate as tldraw's mobile toolbar and\n * style panel (which also honors `forceMobile` on `<Tldraw>`). The commenting layer can mount\n * without tldraw's default UI (`hideUi`, custom UI), where the breakpoint provider is absent and\n * `useBreakpoint` throws; fall back to desktop there, matching the pre-mobile rendering those hosts\n * always had.\n */\nexport function useIsMobileCommenting(): boolean {\n\tlet breakpoint: number\n\ttry {\n\t\t// The call is unconditional \u2014 the try only guards the provider's absence, not hook order.\n\t\t// oxlint-disable-next-line react-hooks/rules-of-hooks\n\t\tbreakpoint = useBreakpoint()\n\t} catch {\n\t\treturn false\n\t}\n\treturn breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM\n}\n\nconst VIEWPORT_MARGIN = 8\n\n/**\n * Keep a canvas-anchored panel \u2014 the pending composer or an open thread popover \u2014 within the\n * visible viewport on mobile. The software keyboard shrinks the *visual* viewport while leaving the\n * layout viewport (and CSS `dvh`) untouched, so a panel placed at a fixed offset from its pin can\n * end up behind the keyboard. On mobile this clamps the panel's top-left into the visible box so it\n * stays on-screen above the keyboard. Desktop is untouched: when `enabled` is false the base point\n * is returned unchanged, tracking the camera exactly as before.\n *\n * `base` is the panel's desired top-left in container-relative viewport coordinates (what the call\n * site would otherwise write straight into `left`/`top`).\n */\nexport function useMobilePlacement(\n\tref: RefObject<HTMLElement | null>,\n\tbase: { x: number; y: number },\n\tenabled: boolean\n): { left: number; top: number } {\n\tconst container = useContainer()\n\t// Destructured so the effects below depend on the two numbers rather than the object, which the\n\t// call sites build fresh on every render.\n\tconst { x: baseX, y: baseY } = base\n\tconst [placed, setPlaced] = useState<{ left: number; top: number }>(() => ({\n\t\tleft: baseX,\n\t\ttop: baseY,\n\t}))\n\t// The camera moves `base` every frame while panning. Reading it from a ref keeps `update` stable\n\t// across those frames, so the observers below are set up once per panel rather than being torn\n\t// down and rebuilt each frame.\n\tconst baseRef = useRef({ x: baseX, y: baseY })\n\n\tconst update = useCallback(() => {\n\t\tif (!enabled) return\n\t\tconst el = ref.current\n\t\tif (!el) return\n\t\tconst win = container.ownerDocument.defaultView ?? window\n\t\tconst { x, y } = baseRef.current\n\n\t\t// Panel coordinates are container-relative; the visual viewport is window-relative.\n\t\tconst cRect = container.getBoundingClientRect()\n\t\tconst vp = getVisibleViewport(win)\n\t\tconst top = vp.top - cRect.top + VIEWPORT_MARGIN\n\t\tconst bottom = vp.bottom - cRect.top - VIEWPORT_MARGIN\n\t\tconst left = vp.left - cRect.left + VIEWPORT_MARGIN\n\t\tconst right = vp.right - cRect.left - VIEWPORT_MARGIN\n\n\t\tconst w = el.offsetWidth\n\t\tconst h = el.offsetHeight\n\n\t\tconst nextLeft = Math.max(left, Math.min(x, right - w))\n\t\t// The keyboard only ever covers space from the bottom, so it can pull the panel up (when its\n\t\t// bottom would be hidden) but must never push it down below its natural spot. `y` is the\n\t\t// floor: any spurious rise in `top` (e.g. iOS scrolling the page to reveal a focused input)\n\t\t// is capped here, so a panel that already clears the keyboard doesn't move at all.\n\t\tconst nextTop = Math.min(y, Math.max(top, bottom - h))\n\t\tsetPlaced((prev) =>\n\t\t\tprev.left === nextLeft && prev.top === nextTop ? prev : { left: nextLeft, top: nextTop }\n\t\t)\n\t}, [container, ref, enabled])\n\n\t// Re-place as the camera moves the panel's base point.\n\tuseLayoutEffect(() => {\n\t\tbaseRef.current = { x: baseX, y: baseY }\n\t\tupdate()\n\t}, [baseX, baseY, update])\n\n\t// Re-place when the panel grows (replies, edits), when the visual viewport changes (keyboard,\n\t// pinch-zoom), or when the window resizes.\n\tuseLayoutEffect(() => {\n\t\tif (!enabled) return\n\t\tconst el = ref.current\n\t\tif (!el) return\n\t\tconst win = container.ownerDocument.defaultView ?? window\n\t\tconst ro = new ResizeObserver(update)\n\t\tro.observe(el)\n\t\tconst vv = win.visualViewport\n\t\tvv?.addEventListener('resize', update)\n\t\tvv?.addEventListener('scroll', update)\n\t\twin.addEventListener('resize', update)\n\t\treturn () => {\n\t\t\tro.disconnect()\n\t\t\tvv?.removeEventListener('resize', update)\n\t\t\tvv?.removeEventListener('scroll', update)\n\t\t\twin.removeEventListener('resize', update)\n\t\t}\n\t}, [container, ref, enabled, update])\n\n\t// Desktop keeps its fixed placement, recomputed each render so it tracks the pin as the camera\n\t// moves.\n\tif (!enabled) return { left: baseX, top: baseY }\n\treturn placed\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA0E;AAC1E,oBAAiE;AACjE,6BAAmC;AAS5B,SAAS,wBAAiC;AAChD,MAAI;AACJ,MAAI;AAGH,qBAAa,6BAAc;AAAA,EAC5B,QAAQ;AACP,WAAO;AAAA,EACR;AACA,SAAO,aAAa,kCAAoB;AACzC;AAEA,MAAM,kBAAkB;AAajB,SAAS,mBACf,KACA,MACA,SACgC;AAChC,QAAM,gBAAY,4BAAa;AAG/B,QAAM,EAAE,GAAG,OAAO,GAAG,MAAM,IAAI;AAC/B,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAwC,OAAO;AAAA,IAC1E,MAAM;AAAA,IACN,KAAK;AAAA,EACN,EAAE;AAIF,QAAM,cAAU,qBAAO,EAAE,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,QAAM,aAAS,0BAAY,MAAM;AAChC,QAAI,CAAC,QAAS;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AACT,UAAM,MAAM,UAAU,cAAc,eAAe;AACnD,UAAM,EAAE,GAAG,EAAE,IAAI,QAAQ;AAGzB,UAAM,QAAQ,UAAU,sBAAsB;AAC9C,UAAM,SAAK,2CAAmB,GAAG;AACjC,UAAM,MAAM,GAAG,MAAM,MAAM,MAAM;AACjC,UAAM,SAAS,GAAG,SAAS,MAAM,MAAM;AACvC,UAAM,OAAO,GAAG,OAAO,MAAM,OAAO;AACpC,UAAM,QAAQ,GAAG,QAAQ,MAAM,OAAO;AAEtC,UAAM,IAAI,GAAG;AACb,UAAM,IAAI,GAAG;AAEb,UAAM,WAAW,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC;AAKtD,UAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC;AACrD;AAAA,MAAU,CAAC,SACV,KAAK,SAAS,YAAY,KAAK,QAAQ,UAAU,OAAO,EAAE,MAAM,UAAU,KAAK,QAAQ;AAAA,IACxF;AAAA,EACD,GAAG,CAAC,WAAW,KAAK,OAAO,CAAC;AAG5B,oCAAgB,MAAM;AACrB,YAAQ,UAAU,EAAE,GAAG,OAAO,GAAG,MAAM;AACvC,WAAO;AAAA,EACR,GAAG,CAAC,OAAO,OAAO,MAAM,CAAC;AAIzB,oCAAgB,MAAM;AACrB,QAAI,CAAC,QAAS;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AACT,UAAM,MAAM,UAAU,cAAc,eAAe;AACnD,UAAM,KAAK,IAAI,eAAe,MAAM;AACpC,OAAG,QAAQ,EAAE;AACb,UAAM,KAAK,IAAI;AACf,QAAI,iBAAiB,UAAU,MAAM;AACrC,QAAI,iBAAiB,UAAU,MAAM;AACrC,QAAI,iBAAiB,UAAU,MAAM;AACrC,WAAO,MAAM;AACZ,SAAG,WAAW;AACd,UAAI,oBAAoB,UAAU,MAAM;AACxC,UAAI,oBAAoB,UAAU,MAAM;AACxC,UAAI,oBAAoB,UAAU,MAAM;AAAA,IACzC;AAAA,EACD,GAAG,CAAC,WAAW,KAAK,SAAS,MAAM,CAAC;AAIpC,MAAI,CAAC,QAAS,QAAO,EAAE,MAAM,OAAO,KAAK,MAAM;AAC/C,SAAO;AACR;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -18,20 +18,30 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var options_exports = {};
|
|
20
20
|
__export(options_exports, {
|
|
21
|
+
defaultCanModifyComment: () => defaultCanModifyComment,
|
|
21
22
|
defaultCommentingOptions: () => defaultCommentingOptions,
|
|
23
|
+
getCanComment: () => getCanComment,
|
|
24
|
+
getCanModifyComment: () => getCanModifyComment,
|
|
22
25
|
getCommentingOptions: () => getCommentingOptions,
|
|
26
|
+
useCanComment: () => useCanComment,
|
|
27
|
+
useCanModifyComment: () => useCanModifyComment,
|
|
23
28
|
useCommentingOptions: () => useCommentingOptions
|
|
24
29
|
});
|
|
25
30
|
module.exports = __toCommonJS(options_exports);
|
|
26
31
|
var import_react = require("react");
|
|
27
32
|
var import_tldraw = require("tldraw");
|
|
33
|
+
var import_emoji_picker = require("../ui/emoji-picker");
|
|
28
34
|
const defaultCommentingOptions = {
|
|
29
35
|
history: "ignore",
|
|
30
36
|
dragHistory: void 0,
|
|
31
37
|
enableClustering: true,
|
|
38
|
+
allowMultipleReactions: true,
|
|
39
|
+
isAllowedReaction: import_emoji_picker.isAllowedReactionEmoji,
|
|
40
|
+
enableRegions: false,
|
|
41
|
+
canComment: void 0,
|
|
42
|
+
canModifyComment: void 0,
|
|
32
43
|
impreciseShapeAnchor: { x: 1, y: 0 },
|
|
33
|
-
|
|
34
|
-
clusterSplitZoomFactor: 1.05,
|
|
44
|
+
shouldBePrecise: () => true,
|
|
35
45
|
components: {}
|
|
36
46
|
};
|
|
37
47
|
function getCommentingOptions(editor) {
|
|
@@ -42,4 +52,49 @@ function useCommentingOptions() {
|
|
|
42
52
|
const editor = (0, import_tldraw.useEditor)();
|
|
43
53
|
return (0, import_react.useMemo)(() => getCommentingOptions(editor), [editor]);
|
|
44
54
|
}
|
|
55
|
+
function permits(option, check) {
|
|
56
|
+
try {
|
|
57
|
+
return check();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`[tldraw] \`${option}\` threw, so the write is denied:`, error);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function getCanComment(editor, currentUserId) {
|
|
64
|
+
const { canComment } = getCommentingOptions(editor);
|
|
65
|
+
if (!canComment) return currentUserId != null;
|
|
66
|
+
return permits("canComment", () => canComment({ editor, currentUserId: currentUserId ?? null }));
|
|
67
|
+
}
|
|
68
|
+
function useCanComment(currentUserId) {
|
|
69
|
+
const editor = (0, import_tldraw.useEditor)();
|
|
70
|
+
return (0, import_tldraw.useValue)("can comment", () => getCanComment(editor, currentUserId), [
|
|
71
|
+
editor,
|
|
72
|
+
currentUserId
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
function defaultCanModifyComment(ctx) {
|
|
76
|
+
const { currentUserId } = ctx;
|
|
77
|
+
if (!currentUserId) return false;
|
|
78
|
+
const owner = ctx.action === "delete-thread" ? ctx.thread.createdBy : ctx.comment.authorId;
|
|
79
|
+
return owner === currentUserId;
|
|
80
|
+
}
|
|
81
|
+
function getCanModifyComment(editor, currentUserId, modification) {
|
|
82
|
+
const { canModifyComment } = getCommentingOptions(editor);
|
|
83
|
+
const ctx = {
|
|
84
|
+
editor,
|
|
85
|
+
currentUserId: currentUserId ?? null,
|
|
86
|
+
...modification
|
|
87
|
+
};
|
|
88
|
+
if (!canModifyComment) return defaultCanModifyComment(ctx);
|
|
89
|
+
return permits("canModifyComment", () => canModifyComment(ctx));
|
|
90
|
+
}
|
|
91
|
+
function useCanModifyComment(currentUserId, modification) {
|
|
92
|
+
const editor = (0, import_tldraw.useEditor)();
|
|
93
|
+
const record = modification.action === "delete-thread" ? modification.thread : modification.comment;
|
|
94
|
+
return (0, import_tldraw.useValue)(
|
|
95
|
+
"can modify comment",
|
|
96
|
+
() => getCanModifyComment(editor, currentUserId, modification),
|
|
97
|
+
[editor, currentUserId, modification.action, record]
|
|
98
|
+
);
|
|
99
|
+
}
|
|
45
100
|
//# sourceMappingURL=options.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/options.ts"],
|
|
4
|
-
"sourcesContent": ["import { useMemo, type ComponentType } from 'react'\nimport {\n\ttype Editor,\n\ttype TLComment,\n\ttype TLCommentThread,\n\ttype TLHistoryBatchOptions,\n\tuseEditor,\n} from 'tldraw'\n\n/**\n * Component overrides for the batteries-included comments layer. Each slot replaces a built-in\n * piece; leave a slot unset to keep its default.\n *\n * @public\n */\nexport interface CommentingComponents {\n\t/** A comment's body. Replaces the default rich-text `<CommentBody>`. */\n\tCommentBody?: ComponentType<{ comment: TLComment }>\n\t/** A pin's inner content. Replaces the author-initial default. */\n\tPinContent?: ComponentType<{ thread: TLCommentThread; comments: TLComment[] }>\n\t/** A sidebar row's preview. Replaces the plaintext default. */\n\tThreadPreview?: ComponentType<{ comment: TLComment }>\n}\n\n/**\n * Configuration for the commenting layer. Static config only \u2014 pass it once via\n * `CommentTool.configure({ ... })`, mirroring `ShapeUtil.configure`. Live, reactive values\n * (`currentUserId`, `resolveName`, read-status callbacks) stay as props on `<CanvasComments>`.\n *\n * For defaults, see {@link defaultCommentingOptions}.\n *\n * @example\n * ```tsx\n * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />\n * ```\n *\n * @public\n */\nexport interface CommentingOptions {\n\t// \u2500\u2500 History / undo \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\t/**\n\t * How comment mutations (post, reply, edit, resolve, delete) interact with the editor undo\n\t * stack. Defaults to `'ignore'` \u2014 comments are deliberately not undoable (see `TLComment`).\n\t * `'record'` is a multiplayer footgun: undoing a delete resurrects a thread a collaborator\n\t * already removed, and undoing a resolve/edit reverts their newer state. Safe only single-player\n\t * or on a non-synced local comment store.\n\t */\n\treadonly history: TLHistoryBatchOptions['history']\n\t/**\n\t * History mode for the pin drag-to-move re-anchor specifically. Unlike posts/edits this is a\n\t * spatial edit that may reasonably be undoable alongside a shape move. Defaults to `history`.\n\t */\n\treadonly dragHistory: TLHistoryBatchOptions['history'] | undefined\n\n\t// \u2500\u2500 Feature toggles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\t/** Fold nearby pins into count badges as the camera zooms out. */\n\treadonly enableClustering: boolean\n\n\t// \u2500\u2500 Anchoring \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\t/** Normalized (0\u20131) spot within a shape where imprecise shape pins sit. Default top-right. */\n\treadonly impreciseShapeAnchor: { readonly x: number; readonly y: number }\n\n\t// \u2500\u2500 Clustering tuning \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\t/** Screen-pixel margin by which the viewport is inflated when culling cluster badges. */\n\treadonly clusterCullMargin: number\n\t/** How far past a cluster's split zoom to land when expanding it (1.05 = 5% overshoot). */\n\treadonly clusterSplitZoomFactor: number\n\n\t// \u2500\u2500 Components \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\t/** Component overrides. See {@link CommentingComponents}. */\n\treadonly components: CommentingComponents\n}\n\n/**\n * The default {@link CommentingOptions}. Override via `CommentTool.configure({ ... })`.\n *\n * @public\n */\nexport const defaultCommentingOptions = {\n\thistory: 'ignore',\n\tdragHistory: undefined,\n\tenableClustering: true,\n\timpreciseShapeAnchor: { x: 1, y: 0 },\n\tclusterCullMargin: 120,\n\tclusterSplitZoomFactor: 1.05,\n\tcomponents: {},\n} as const satisfies CommentingOptions\n\n/**\n * The merged {@link CommentingOptions} for an editor, read off the registered comment tool (which\n * carries them via `CommentTool.configure`). Falls back to {@link defaultCommentingOptions} when\n * the comment tool isn't registered. Usable from anywhere with an `Editor` \u2014 including the tool's\n * own state, which has no React context.\n *\n * @public\n */\nexport function getCommentingOptions(editor: Editor): CommentingOptions {\n\tconst tool = editor.getStateDescendant('comment') as { options?: CommentingOptions } | undefined\n\treturn tool?.options ?? defaultCommentingOptions\n}\n\n/**\n * React hook for {@link getCommentingOptions}. Options are fixed per editor (set at tool\n * registration), so this doesn't need to be reactive.\n *\n * @public\n */\nexport function useCommentingOptions(): CommentingOptions {\n\tconst editor = useEditor()\n\treturn useMemo(() => getCommentingOptions(editor), [editor])\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA4C;AAC5C,
|
|
4
|
+
"sourcesContent": ["import { useMemo, type ComponentType } from 'react'\nimport {\n\ttype Editor,\n\ttype TLComment,\n\ttype TLCommentThread,\n\ttype TLHistoryBatchOptions,\n\ttype TLShapeId,\n\ttype VecLike,\n\tuseEditor,\n\tuseValue,\n} from 'tldraw'\nimport { type CommentListItemRenderProps } from '../ui/comments-list'\nimport { isAllowedReactionEmoji, type EmojiPickerProps } from '../ui/emoji-picker'\nimport { type ReactionTooltipProps } from '../ui/reaction'\n\n/**\n * The gesture that's creating a shape anchor, passed to\n * {@link CommentingOptions.shouldBePrecise}: the target shape, the page point of the release, and\n * whether Alt was held.\n *\n * @public\n */\nexport interface ShapeCommentPrecisionContext {\n\treadonly shapeId: TLShapeId\n\treadonly point: VecLike\n\treadonly altKey: boolean\n}\n\n/**\n * A commenting write that belongs to someone in particular, and the record it targets \u2014 the\n * argument to {@link CommentingOptions.canModifyComment}.\n *\n * Resolving, reopening, reacting, and moving a pin aren't here: none of them is anyone's in\n * particular, so {@link CommentingOptions.canComment} is the only gate on them.\n *\n * @public\n */\nexport type CommentModification =\n\t| { readonly action: 'edit-comment'; readonly comment: TLComment }\n\t| { readonly action: 'delete-comment'; readonly comment: TLComment }\n\t| { readonly action: 'delete-thread'; readonly thread: TLCommentThread }\n\n/**\n * The argument to {@link CommentingOptions.canModifyComment}: which write, against which record,\n * and by whom.\n *\n * @public\n */\nexport type CommentModificationContext = {\n\treadonly editor: Editor\n\treadonly currentUserId: string | null\n} & CommentModification\n\n/**\n * Component overrides for the batteries-included comments layer. Each slot replaces a built-in\n * piece; leave a slot unset to keep its default.\n *\n * @public\n */\nexport interface CommentingComponents {\n\t/** A comment's body. Replaces the default rich-text `<CommentBody>`. */\n\tCommentBody?: ComponentType<{ comment: TLComment }>\n\t/** A pin's inner content. Replaces the author-initial default. */\n\tPinContent?: ComponentType<{ thread: TLCommentThread; comments: TLComment[] }>\n\t/** A sidebar row's preview. Replaces the plaintext default. */\n\tThreadPreview?: ComponentType<{ comment: TLComment }>\n\t/**\n\t * A whole sidebar row. Replaces the default `<CommentListItem>`, which is exported \u2014 so a row\n\t * that only adds an unread dot or a status chip can spread these props into it. Use\n\t * `ThreadPreview` instead when only the preview text is changing.\n\t */\n\tThreadRow?: ComponentType<CommentListItemRenderProps & { thread: TLCommentThread }>\n\t/**\n\t * Extra controls in an open thread's header, added ahead of the built-in resolve and dismiss\n\t * buttons rather than replacing them. \"Copy link\" is already built in whenever the host supplies\n\t * `getThreadHref`.\n\t */\n\tThreadActions?: ComponentType<{ thread: TLCommentThread; comments: TLComment[] }>\n\t/**\n\t * A reaction's visual, given its token. The default renders the token string for the OS emoji\n\t * font. Override to draw a custom palette \u2014 an `<img>`, an SVG, anything. The token is what gets\n\t * stored and synced; this only controls how it's drawn.\n\t */\n\tReactionContent?: ComponentType<{ token: string }>\n\t/**\n\t * What the add-reaction button opens. Replaces the default `<EmojiPicker>` grid. Pairs with\n\t * `ReactionContent` (which draws the tokens this emits) and `isAllowedReaction` (which must\n\t * accept them).\n\t */\n\tReactionPalette?: ComponentType<EmojiPickerProps>\n\t/**\n\t * The hover affordance naming who reacted with an emoji. Receives the reactors and the pill (as\n\t * `children`) and owns the whole thing \u2014 box, size, shape, position. For a wording change,\n\t * translate the `comments.reacted-*` strings instead.\n\t */\n\tReactionTooltip?: ComponentType<ReactionTooltipProps>\n\t/**\n\t * Shown where a composer would sit when the viewer can't compose (see\n\t * {@link CommentingOptions.canComment}). `context` is the surface rendering it: an open thread\n\t * popover (`'thread'`) or the comment tool's placement popover (`'pending'`). Unset, those\n\t * surfaces render nothing.\n\t */\n\tComposerFallback?: ComponentType<{ context: 'pending' | 'thread' }>\n}\n\n/**\n * Configuration for the commenting layer. Static config only \u2014 pass it once via\n * `CommentTool.configure({ ... })`, mirroring `ShapeUtil.configure`. Live, reactive values\n * (`currentUserId`, author resolution, read-status callbacks) are the `CommentingContext`, passed as\n * props to each commenting surface.\n *\n * For defaults, see {@link defaultCommentingOptions}.\n *\n * @example\n * ```tsx\n * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />\n * ```\n *\n * @public\n */\nexport interface CommentingOptions {\n\t// History / undo\n\t/**\n\t * How comment mutations interact with the editor undo stack. Defaults to `'ignore'` \u2014 comments\n\t * are deliberately not undoable (see `TLComment`). `'record'` is a multiplayer footgun: undoing\n\t * a delete resurrects a thread a collaborator already removed. Safe only single-player.\n\t */\n\treadonly history: TLHistoryBatchOptions['history']\n\t/**\n\t * History mode for the pin drag-to-move re-anchor specifically. Unlike posts/edits this is a\n\t * spatial edit that may reasonably be undoable alongside a shape move. Defaults to `history`.\n\t */\n\treadonly dragHistory: TLHistoryBatchOptions['history'] | undefined\n\n\t// Feature toggles\n\t/** Fold nearby pins into count badges as the camera zooms out. */\n\treadonly enableClustering: boolean\n\t/**\n\t * Whether a user may hold several emoji reactions on one comment. `true` (the default) is the\n\t * Slack model: each emoji toggles independently. `false` is single-select: picking a new emoji\n\t * replaces the user's existing reaction. Note this is enforced client-side; the server accepts\n\t * per-emoji records either way.\n\t */\n\treadonly allowMultipleReactions: boolean\n\t/**\n\t * Whether a token may be added as a reaction. Defaults to {@link isAllowedReactionEmoji}, which\n\t * keeps a scripted client from writing junk values the picker would never offer. Override\n\t * alongside a custom `ReactionPalette`. Removals aren't checked \u2014 an off-palette reaction must\n\t * still be clearable.\n\t */\n\tisAllowedReaction(token: string): boolean\n\t/**\n\t * Whether dragging the comment tool out creates a region anchor \u2014 a comment attached to a\n\t * rectangular area, drawn as a dashed box with the pin on the corner the drag released on. Off\n\t * by default, where comments attach to points and shapes only and a drag trails the composer.\n\t */\n\treadonly enableRegions: boolean\n\n\t// Permissions\n\t/**\n\t * Whether the viewer may participate in commenting: composing, editing, deleting, resolving, and\n\t * moving pins. When false, {@link CommentingComponents.ComposerFallback} renders in the\n\t * composer's place and action affordances are hidden. Unset, participation is allowed exactly\n\t * when `currentUserId` is set.\n\t *\n\t * Called during render via {@link useCanComment}, so signal reads are tracked. Posting still\n\t * needs a `currentUserId`, so returning true for a signed-out viewer yields a composer whose\n\t * send button stays disabled. A callback that throws is logged and read as false, rather than\n\t * taking the comments layer down with it.\n\t */\n\treadonly canComment:\n\t\t| ((ctx: { editor: Editor; currentUserId: string | null }) => boolean)\n\t\t| undefined\n\t/**\n\t * Whether the viewer may make a particular write against a particular record: editing or\n\t * deleting a comment, or deleting a thread. Unset, each is its record's owner's to make\n\t * ({@link defaultCanModifyComment}) \u2014 you edit and delete your own comments, and delete threads\n\t * you started. Override it to widen that (a workspace admin or moderator who may remove\n\t * anyone's comment) or to narrow it (no edits after an hour). Where it returns false the\n\t * affordance isn't rendered.\n\t *\n\t * Checked after {@link CommentingOptions.canComment}, which gates commenting as a whole: a\n\t * viewer who may not participate gets no action affordances at all, whatever this returns.\n\t *\n\t * Called during render via {@link useCanModifyComment}, so reactive reads (signals) are tracked.\n\t * A callback that throws is logged and read as false: an affordance is withheld rather than the\n\t * comments layer lost, and a denial is what a server enforcing the same rule would have said.\n\t *\n\t * @example\n\t * ```tsx\n\t * CommentTool.configure({\n\t * \tcanModifyComment: (ctx) =>\n\t * \t\t// Moderators may delete anything; everything else stays the owner's to do.\n\t * \t\t(ctx.action !== 'edit-comment' && isModerator(ctx.currentUserId)) ||\n\t * \t\tdefaultCanModifyComment(ctx),\n\t * })\n\t * ```\n\t */\n\treadonly canModifyComment: ((ctx: CommentModificationContext) => boolean) | undefined\n\n\t// Anchoring\n\t/** Normalized (0\u20131) spot within a shape where imprecise shape pins sit. Default top-right. */\n\treadonly impreciseShapeAnchor: { readonly x: number; readonly y: number }\n\t/**\n\t * Whether a comment landing on a shape pins to the exact clicked spot, or to the shape as a\n\t * whole (rendered at `impreciseShapeAnchor`). Always precise by default; return `false`, or\n\t * decide from the context. Governs new placements only \u2014 existing anchors render as stored.\n\t */\n\tshouldBePrecise(editor: Editor, context: ShapeCommentPrecisionContext): boolean\n\n\t// Components\n\t/** Component overrides. See {@link CommentingComponents}. */\n\treadonly components: CommentingComponents\n}\n\n/**\n * The default {@link CommentingOptions}. Override via `CommentTool.configure({ ... })`.\n *\n * @public\n */\nexport const defaultCommentingOptions = {\n\thistory: 'ignore',\n\tdragHistory: undefined,\n\tenableClustering: true,\n\tallowMultipleReactions: true,\n\tisAllowedReaction: isAllowedReactionEmoji,\n\tenableRegions: false,\n\tcanComment: undefined,\n\tcanModifyComment: undefined,\n\timpreciseShapeAnchor: { x: 1, y: 0 },\n\tshouldBePrecise: () => true,\n\tcomponents: {},\n} as const satisfies CommentingOptions\n\n/**\n * The merged {@link CommentingOptions} for an editor, read off the registered comment tool (which\n * carries them via `CommentTool.configure`). Falls back to {@link defaultCommentingOptions} when\n * the comment tool isn't registered. Usable from anywhere with an `Editor` \u2014 including the tool's\n * own state, which has no React context.\n *\n * @public\n */\nexport function getCommentingOptions(editor: Editor): CommentingOptions {\n\tconst tool = editor.getStateDescendant('comment') as { options?: CommentingOptions } | undefined\n\treturn tool?.options ?? defaultCommentingOptions\n}\n\n/**\n * React hook for {@link getCommentingOptions}. Options are fixed per editor (set at tool\n * registration), so this doesn't need to be reactive.\n *\n * @public\n */\nexport function useCommentingOptions(): CommentingOptions {\n\tconst editor = useEditor()\n\treturn useMemo(() => getCommentingOptions(editor), [editor])\n}\n\n/**\n * Ask a host's permission callback, denying the write if it throws.\n *\n * These are called during render, so an exception in one would take the comments layer down with\n * the answer. Denying costs an affordance, which is what a `false` would have cost anyway, and it\n * can't offer a write a server enforcing the same rule would then reject.\n */\nfunction permits(option: string, check: () => boolean): boolean {\n\ttry {\n\t\treturn check()\n\t} catch (error) {\n\t\tconsole.error(`[tldraw] \\`${option}\\` threw, so the write is denied:`, error)\n\t\treturn false\n\t}\n}\n\n/**\n * Whether the viewer may participate in commenting, per {@link CommentingOptions.canComment}\n * (defaulting to `currentUserId != null` when unset). A callback that throws denies.\n *\n * This is a plain, untracked read \u2014 in React, use {@link useCanComment} instead.\n *\n * @public\n */\nexport function getCanComment(editor: Editor, currentUserId: string | null | undefined): boolean {\n\tconst { canComment } = getCommentingOptions(editor)\n\tif (!canComment) return currentUserId != null\n\treturn permits('canComment', () => canComment({ editor, currentUserId: currentUserId ?? null }))\n}\n\n/**\n * Reactive React hook for {@link getCanComment}: a `canComment` callback that reads signals\n * re-evaluates when they change.\n *\n * @public\n */\nexport function useCanComment(currentUserId: string | null | undefined): boolean {\n\tconst editor = useEditor()\n\treturn useValue('can comment', () => getCanComment(editor, currentUserId), [\n\t\teditor,\n\t\tcurrentUserId,\n\t])\n}\n\n/**\n * The default {@link CommentingOptions.canModifyComment}: a write is its record's owner's to make \u2014\n * a comment's author edits and deletes it, a thread's creator deletes the thread \u2014 and a viewer\n * with no identity may make none of them.\n *\n * Exported so a callback can widen the default rather than restate it:\n * `(ctx) => isModerator(ctx.currentUserId) || defaultCanModifyComment(ctx)`.\n *\n * @public\n */\nexport function defaultCanModifyComment(ctx: CommentModificationContext): boolean {\n\tconst { currentUserId } = ctx\n\tif (!currentUserId) return false\n\tconst owner = ctx.action === 'delete-thread' ? ctx.thread.createdBy : ctx.comment.authorId\n\treturn owner === currentUserId\n}\n\n/**\n * Whether the viewer may make a given write against a given record, per\n * {@link CommentingOptions.canModifyComment} (defaulting to {@link defaultCanModifyComment} when\n * unset). Where this is false the affordance isn't rendered.\n *\n * This is the per-record rule alone: the built-in UI additionally requires\n * {@link CommentingOptions.canComment}, since a viewer who may not participate gets no action\n * affordances at all.\n *\n * A plain, untracked read \u2014 a `canModifyComment` callback that reads signals is not observed. In\n * React, use {@link useCanModifyComment} instead.\n *\n * @public\n */\nexport function getCanModifyComment(\n\teditor: Editor,\n\tcurrentUserId: string | null | undefined,\n\tmodification: CommentModification\n): boolean {\n\tconst { canModifyComment } = getCommentingOptions(editor)\n\tconst ctx: CommentModificationContext = {\n\t\teditor,\n\t\tcurrentUserId: currentUserId ?? null,\n\t\t...modification,\n\t}\n\tif (!canModifyComment) return defaultCanModifyComment(ctx)\n\treturn permits('canModifyComment', () => canModifyComment(ctx))\n}\n\n/**\n * Reactive React hook for {@link getCanModifyComment}: a `canModifyComment` callback that reads\n * signals re-evaluates when they change.\n *\n * @public\n */\nexport function useCanModifyComment(\n\tcurrentUserId: string | null | undefined,\n\tmodification: CommentModification\n): boolean {\n\tconst editor = useEditor()\n\t// Comment records are immutable, so the record itself is what changes when the thing being\n\t// checked changes \u2014 `modification` is a fresh object on every render and can't be a dep.\n\tconst record =\n\t\tmodification.action === 'delete-thread' ? modification.thread : modification.comment\n\treturn useValue(\n\t\t'can modify comment',\n\t\t() => getCanModifyComment(editor, currentUserId, modification),\n\t\t[editor, currentUserId, modification.action, record]\n\t)\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA4C;AAC5C,oBASO;AAEP,0BAA8D;AAgNvD,MAAM,2BAA2B;AAAA,EACvC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,wBAAwB;AAAA,EACxB,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,sBAAsB,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,EACnC,iBAAiB,MAAM;AAAA,EACvB,YAAY,CAAC;AACd;AAUO,SAAS,qBAAqB,QAAmC;AACvE,QAAM,OAAO,OAAO,mBAAmB,SAAS;AAChD,SAAO,MAAM,WAAW;AACzB;AAQO,SAAS,uBAA0C;AACzD,QAAM,aAAS,yBAAU;AACzB,aAAO,sBAAQ,MAAM,qBAAqB,MAAM,GAAG,CAAC,MAAM,CAAC;AAC5D;AASA,SAAS,QAAQ,QAAgB,OAA+B;AAC/D,MAAI;AACH,WAAO,MAAM;AAAA,EACd,SAAS,OAAO;AACf,YAAQ,MAAM,cAAc,MAAM,qCAAqC,KAAK;AAC5E,WAAO;AAAA,EACR;AACD;AAUO,SAAS,cAAc,QAAgB,eAAmD;AAChG,QAAM,EAAE,WAAW,IAAI,qBAAqB,MAAM;AAClD,MAAI,CAAC,WAAY,QAAO,iBAAiB;AACzC,SAAO,QAAQ,cAAc,MAAM,WAAW,EAAE,QAAQ,eAAe,iBAAiB,KAAK,CAAC,CAAC;AAChG;AAQO,SAAS,cAAc,eAAmD;AAChF,QAAM,aAAS,yBAAU;AACzB,aAAO,wBAAS,eAAe,MAAM,cAAc,QAAQ,aAAa,GAAG;AAAA,IAC1E;AAAA,IACA;AAAA,EACD,CAAC;AACF;AAYO,SAAS,wBAAwB,KAA0C;AACjF,QAAM,EAAE,cAAc,IAAI;AAC1B,MAAI,CAAC,cAAe,QAAO;AAC3B,QAAM,QAAQ,IAAI,WAAW,kBAAkB,IAAI,OAAO,YAAY,IAAI,QAAQ;AAClF,SAAO,UAAU;AAClB;AAgBO,SAAS,oBACf,QACA,eACA,cACU;AACV,QAAM,EAAE,iBAAiB,IAAI,qBAAqB,MAAM;AACxD,QAAM,MAAkC;AAAA,IACvC;AAAA,IACA,eAAe,iBAAiB;AAAA,IAChC,GAAG;AAAA,EACJ;AACA,MAAI,CAAC,iBAAkB,QAAO,wBAAwB,GAAG;AACzD,SAAO,QAAQ,oBAAoB,MAAM,iBAAiB,GAAG,CAAC;AAC/D;AAQO,SAAS,oBACf,eACA,cACU;AACV,QAAM,aAAS,yBAAU;AAGzB,QAAM,SACL,aAAa,WAAW,kBAAkB,aAAa,SAAS,aAAa;AAC9E,aAAO;AAAA,IACN;AAAA,IACA,MAAM,oBAAoB,QAAQ,eAAe,YAAY;AAAA,IAC7D,CAAC,QAAQ,eAAe,aAAa,QAAQ,MAAM;AAAA,EACpD;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var pending_composer_exports = {};
|
|
20
|
+
__export(pending_composer_exports, {
|
|
21
|
+
PendingComposer: () => PendingComposer
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(pending_composer_exports);
|
|
24
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
|
+
var import_mentions = require("@tldraw/mentions");
|
|
26
|
+
var import_react = require("react");
|
|
27
|
+
var import_tldraw = require("tldraw");
|
|
28
|
+
var import_comment_composer = require("../ui/comment-composer");
|
|
29
|
+
var import_comment_extensions = require("../ui/comment-extensions");
|
|
30
|
+
var import_comment_pin = require("../ui/comment-pin");
|
|
31
|
+
var import_comment_drafts = require("./comment-drafts");
|
|
32
|
+
var import_comment_mutations = require("./comment-mutations");
|
|
33
|
+
var import_comment_render = require("./comment-render");
|
|
34
|
+
var import_mobile_placement = require("./mobile-placement");
|
|
35
|
+
var import_options = require("./options");
|
|
36
|
+
var import_state = require("./state");
|
|
37
|
+
const stop = (e) => e.stopPropagation();
|
|
38
|
+
function PendingComposer({
|
|
39
|
+
editor,
|
|
40
|
+
pending,
|
|
41
|
+
currentUserId,
|
|
42
|
+
resolveAuthor,
|
|
43
|
+
onPostComment,
|
|
44
|
+
getMentionSuggestions,
|
|
45
|
+
renderMentionSuggestion
|
|
46
|
+
}) {
|
|
47
|
+
const ComposerFallback = (0, import_options.useCommentingOptions)().components.ComposerFallback;
|
|
48
|
+
const canComment = (0, import_options.useCanComment)(currentUserId);
|
|
49
|
+
const me = currentUserId ? resolveAuthor(currentUserId) : void 0;
|
|
50
|
+
const draftAvatar = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_pin.CommentPin, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_mentions.Avatar, { author: me ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR }) });
|
|
51
|
+
const [text, setText] = (0, import_react.useState)(
|
|
52
|
+
() => (0, import_comment_drafts.getCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT) ?? import_comment_extensions.EMPTY_COMMENT
|
|
53
|
+
);
|
|
54
|
+
const ref = (0, import_react.useRef)(null);
|
|
55
|
+
const msg = (0, import_tldraw.useTranslation)();
|
|
56
|
+
(0, import_tldraw.usePassThroughWheelEvents)(ref);
|
|
57
|
+
const point = (0, import_tldraw.useValue)("composer point", () => editor.pageToViewport(pending.point), [
|
|
58
|
+
editor,
|
|
59
|
+
pending.point
|
|
60
|
+
]);
|
|
61
|
+
const isMobile = (0, import_mobile_placement.useIsMobileCommenting)();
|
|
62
|
+
const placed = (0, import_mobile_placement.useMobilePlacement)(ref, point, isMobile);
|
|
63
|
+
(0, import_react.useEffect)(() => {
|
|
64
|
+
const onPointerDown = (e) => {
|
|
65
|
+
const el = ref.current;
|
|
66
|
+
const target = e.target;
|
|
67
|
+
if (!el || !target) return;
|
|
68
|
+
if (el.contains(target) || target.closest(".tlui-cmt-mention-popup")) return;
|
|
69
|
+
import_state.pendingComment.set(editor, null);
|
|
70
|
+
};
|
|
71
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
72
|
+
return () => document.removeEventListener("pointerdown", onPointerDown, true);
|
|
73
|
+
}, [editor]);
|
|
74
|
+
const submit = () => {
|
|
75
|
+
if ((0, import_comment_extensions.isCommentEmpty)(text) || !currentUserId) return;
|
|
76
|
+
const comment = (0, import_comment_mutations.commitCommentMutation)(editor, ({ put }) => {
|
|
77
|
+
const pageId = editor.getCurrentPageId();
|
|
78
|
+
const thread = (0, import_tldraw.createCommentThread)({
|
|
79
|
+
pageId,
|
|
80
|
+
anchor: pending.anchor,
|
|
81
|
+
createdBy: currentUserId
|
|
82
|
+
});
|
|
83
|
+
const comment2 = (0, import_tldraw.createComment)({
|
|
84
|
+
threadId: thread.id,
|
|
85
|
+
pageId,
|
|
86
|
+
authorId: currentUserId,
|
|
87
|
+
body: text
|
|
88
|
+
});
|
|
89
|
+
put([thread, comment2]);
|
|
90
|
+
return comment2;
|
|
91
|
+
});
|
|
92
|
+
setText(import_comment_extensions.EMPTY_COMMENT);
|
|
93
|
+
(0, import_comment_drafts.clearCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT);
|
|
94
|
+
import_state.pendingComment.set(editor, null);
|
|
95
|
+
onPostComment?.(comment);
|
|
96
|
+
};
|
|
97
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.EditorPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
98
|
+
"div",
|
|
99
|
+
{
|
|
100
|
+
ref,
|
|
101
|
+
className: [
|
|
102
|
+
"tlui-cmt-canvas-composer",
|
|
103
|
+
pending.anchor.type === "region" && "tlui-cmt-canvas-composer--region",
|
|
104
|
+
!canComment && "tlui-cmt-canvas-composer--fallback"
|
|
105
|
+
].filter(Boolean).join(" "),
|
|
106
|
+
style: { left: placed.left, top: placed.top },
|
|
107
|
+
onPointerDown: stop,
|
|
108
|
+
onContextMenu: stop,
|
|
109
|
+
onKeyDown: (e) => {
|
|
110
|
+
if (e.key === "Escape" && !(0, import_mentions.isMentionPickerOpen)()) import_state.pendingComment.set(editor, null);
|
|
111
|
+
},
|
|
112
|
+
children: canComment ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
113
|
+
import_comment_composer.CommentComposer,
|
|
114
|
+
{
|
|
115
|
+
author: me ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
|
|
116
|
+
placeholder: msg("comments.add-placeholder"),
|
|
117
|
+
sendLabel: msg("comments.send"),
|
|
118
|
+
value: text,
|
|
119
|
+
onChange: (value) => {
|
|
120
|
+
setText(value);
|
|
121
|
+
(0, import_comment_drafts.saveCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT, value);
|
|
122
|
+
},
|
|
123
|
+
onSubmit: submit,
|
|
124
|
+
disabled: (0, import_comment_extensions.isCommentEmpty)(text) || !currentUserId,
|
|
125
|
+
getMentionSuggestions,
|
|
126
|
+
renderMentionSuggestion,
|
|
127
|
+
autoFocus: true,
|
|
128
|
+
leading: draftAvatar
|
|
129
|
+
}
|
|
130
|
+
) : ComposerFallback && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComposerFallback, { context: "pending" })
|
|
131
|
+
}
|
|
132
|
+
) });
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=pending-composer.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/canvas/pending-composer.tsx"],
|
|
4
|
+
"sourcesContent": ["import { Avatar, isMentionPickerOpen } from '@tldraw/mentions'\nimport { useEffect, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tcreateCommentThread,\n\tEditor,\n\tEditorPortal,\n\tTLRichText,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\tNEW_COMMENT_DRAFT,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { PendingComment } from './comment-tool'\nimport { type CommentingContext } from './context'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport { useCanComment, useCommentingOptions } from './options'\nimport { pendingComment } from './state'\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/**\n * The composer for a thread that doesn't exist yet: the comment tool has placed a point (or\n * region) and is waiting on a first comment. Submitting creates the thread and its comment\n * together; clicking away keeps the draft for the next placement.\n */\nexport function PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveAuthor,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CommentingContext & { editor: Editor; pending: PendingComment }) {\n\tconst ComposerFallback = useCommentingOptions().components.ComposerFallback\n\tconst canComment = useCanComment(currentUserId)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// The leading pin previews the pin this draft becomes: a white pin holding the author's avatar.\n\tconst draftAvatar = (\n\t\t<CommentPin>\n\t\t\t<Avatar author={me ?? UNKNOWN_COMMENT_AUTHOR} />\n\t\t</CommentPin>\n\t)\n\t// Click-away keeps the draft (saved on every change) and the next placement composer\n\t// restores it \u2014 the flip side of dismissing without a discard warning.\n\tconst [text, setText] = useState<TLRichText>(\n\t\t() => getCommentDraft(NEW_COMMENT_DRAFT) ?? EMPTY_COMMENT\n\t)\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst msg = useTranslation()\n\t// Over this floating panel, a scroll reaches the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\n\t// On mobile the composer floats free of the pin so it can clear the software keyboard; desktop\n\t// keeps it pinned to the point.\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, point, isMobile)\n\n\t// Dismiss on a click anywhere outside the composer (capture-phase, ahead of stopPropagation).\n\tuseEffect(() => {\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst el = ref.current\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!el || !target) return\n\t\t\t// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is\n\t\t\t// not \"outside\" \u2014 keep the draft open so the pick can insert.\n\t\t\tif (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [editor])\n\n\tconst submit = () => {\n\t\tif (isCommentEmpty(text) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst pageId = editor.getCurrentPageId()\n\t\t\tconst thread = createCommentThread({\n\t\t\t\tpageId,\n\t\t\t\tanchor: pending.anchor,\n\t\t\t\tcreatedBy: currentUserId,\n\t\t\t})\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: text,\n\t\t\t})\n\t\t\tput([thread, comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tclearCommentDraft(NEW_COMMENT_DRAFT)\n\t\tpendingComment.set(editor, null)\n\t\t// The host's callback is its own operation, not part of the post's history scope. It runs\n\t\t// last so a throwing host can't strand the composer holding a draft of a posted comment.\n\t\tonPostComment?.(comment)\n\t}\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={[\n\t\t\t\t\t'tlui-cmt-canvas-composer',\n\t\t\t\t\tpending.anchor.type === 'region' && 'tlui-cmt-canvas-composer--region',\n\t\t\t\t\t!canComment && 'tlui-cmt-canvas-composer--fallback',\n\t\t\t\t]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\tstyle={{ left: placed.left, top: placed.top }}\n\t\t\t\tonPointerDown={stop}\n\t\t\t\tonContextMenu={stop}\n\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{canComment ? (\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={me ?? UNKNOWN_COMMENT_AUTHOR}\n\t\t\t\t\t\tplaceholder={msg('comments.add-placeholder')}\n\t\t\t\t\t\tsendLabel={msg('comments.send')}\n\t\t\t\t\t\tvalue={text}\n\t\t\t\t\t\tonChange={(value) => {\n\t\t\t\t\t\t\tsetText(value)\n\t\t\t\t\t\t\tsaveCommentDraft(NEW_COMMENT_DRAFT, value)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={submit}\n\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\tdisabled={isCommentEmpty(text) || !currentUserId}\n\t\t\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\tleading={draftAvatar}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\tComposerFallback && <ComposerFallback context=\"pending\" />\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmDG;AAnDH,sBAA4C;AAC5C,mBAA4C;AAC5C,oBASO;AACP,8BAAgC;AAChC,gCAA8C;AAC9C,yBAA2B;AAC3B,4BAKO;AACP,+BAAsC;AACtC,4BAAuC;AAGvC,8BAA0D;AAC1D,qBAAoD;AACpD,mBAA+B;AAE/B,MAAM,OAAO,CAAC,MAAmC,EAAE,gBAAgB;AAO5D,SAAS,gBAAgB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,GAAoE;AACnE,QAAM,uBAAmB,qCAAqB,EAAE,WAAW;AAC3D,QAAM,iBAAa,8BAAc,aAAa;AAC9C,QAAM,KAAK,gBAAgB,cAAc,aAAa,IAAI;AAE1D,QAAM,cACL,4CAAC,iCACA,sDAAC,0BAAO,QAAQ,MAAM,8CAAwB,GAC/C;AAID,QAAM,CAAC,MAAM,OAAO,QAAI;AAAA,IACvB,UAAM,uCAAgB,uCAAiB,KAAK;AAAA,EAC7C;AACA,QAAM,UAAM,qBAAuB,IAAI;AACvC,QAAM,UAAM,8BAAe;AAE3B,+CAA0B,GAAG;AAE7B,QAAM,YAAQ,wBAAS,kBAAkB,MAAM,OAAO,eAAe,QAAQ,KAAK,GAAG;AAAA,IACpF;AAAA,IACA,QAAQ;AAAA,EACT,CAAC;AAGD,QAAM,eAAW,+CAAsB;AACvC,QAAM,aAAS,4CAAmB,KAAK,OAAO,QAAQ;AAGtD,8BAAU,MAAM;AACf,UAAM,gBAAgB,CAAC,MAAoB;AAC1C,YAAM,KAAK,IAAI;AACf,YAAM,SAAS,EAAE;AACjB,UAAI,CAAC,MAAM,CAAC,OAAQ;AAGpB,UAAI,GAAG,SAAS,MAAM,KAAK,OAAO,QAAQ,yBAAyB,EAAG;AACtE,kCAAe,IAAI,QAAQ,IAAI;AAAA,IAChC;AACA,aAAS,iBAAiB,eAAe,eAAe,IAAI;AAC5D,WAAO,MAAM,SAAS,oBAAoB,eAAe,eAAe,IAAI;AAAA,EAC7E,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,SAAS,MAAM;AACpB,YAAI,0CAAe,IAAI,KAAK,CAAC,cAAe;AAC5C,UAAM,cAAU,gDAAsB,QAAQ,CAAC,EAAE,IAAI,MAAM;AAC1D,YAAM,SAAS,OAAO,iBAAiB;AACvC,YAAM,aAAS,mCAAoB;AAAA,QAClC;AAAA,QACA,QAAQ,QAAQ;AAAA,QAChB,WAAW;AAAA,MACZ,CAAC;AACD,YAAMA,eAAU,6BAAc;AAAA,QAC7B,UAAU,OAAO;AAAA,QACjB;AAAA,QACA,UAAU;AAAA,QACV,MAAM;AAAA,MACP,CAAC;AACD,UAAI,CAAC,QAAQA,QAAO,CAAC;AACrB,aAAOA;AAAA,IACR,CAAC;AACD,YAAQ,uCAAa;AACrB,iDAAkB,uCAAiB;AACnC,gCAAe,IAAI,QAAQ,IAAI;AAG/B,oBAAgB,OAAO;AAAA,EACxB;AAEA,SACC,4CAAC,8BACA;AAAA,IAAC;AAAA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACV;AAAA,QACA,QAAQ,OAAO,SAAS,YAAY;AAAA,QACpC,CAAC,cAAc;AAAA,MAChB,EACE,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MACV,OAAO,EAAE,MAAM,OAAO,MAAM,KAAK,OAAO,IAAI;AAAA,MAC5C,eAAe;AAAA,MACf,eAAe;AAAA,MACf,WAAW,CAAC,MAAM;AACjB,YAAI,EAAE,QAAQ,YAAY,KAAC,qCAAoB,EAAG,6BAAe,IAAI,QAAQ,IAAI;AAAA,MAClF;AAAA,MAEC,uBACA;AAAA,QAAC;AAAA;AAAA,UACA,QAAQ,MAAM;AAAA,UACd,aAAa,IAAI,0BAA0B;AAAA,UAC3C,WAAW,IAAI,eAAe;AAAA,UAC9B,OAAO;AAAA,UACP,UAAU,CAAC,UAAU;AACpB,oBAAQ,KAAK;AACb,wDAAiB,yCAAmB,KAAK;AAAA,UAC1C;AAAA,UACA,UAAU;AAAA,UAEV,cAAU,0CAAe,IAAI,KAAK,CAAC;AAAA,UACnC;AAAA,UACA;AAAA,UACA,WAAS;AAAA,UACT,SAAS;AAAA;AAAA,MACV,IAEA,oBAAoB,4CAAC,oBAAiB,SAAQ,WAAU;AAAA;AAAA,EAE1D,GACD;AAEF;",
|
|
6
|
+
"names": ["comment"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var pin_stacking_exports = {};
|
|
20
|
+
__export(pin_stacking_exports, {
|
|
21
|
+
computePinStacks: () => computePinStacks,
|
|
22
|
+
isOpenStackKeyLive: () => isOpenStackKeyLive,
|
|
23
|
+
pinStackKey: () => pinStackKey,
|
|
24
|
+
pinStacksEqual: () => pinStacksEqual
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(pin_stacking_exports);
|
|
27
|
+
var import_thread_state = require("./thread-state");
|
|
28
|
+
const PIN_STACK_QUANTUM = 0.1;
|
|
29
|
+
function pinStackKey(point) {
|
|
30
|
+
return `${Math.round(point.x / PIN_STACK_QUANTUM)}:${Math.round(point.y / PIN_STACK_QUANTUM)}`;
|
|
31
|
+
}
|
|
32
|
+
function computePinStacks(editor, threads) {
|
|
33
|
+
const pageId = editor.getCurrentPageId();
|
|
34
|
+
const groups = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const thread of threads) {
|
|
36
|
+
if (thread.pageId !== pageId) continue;
|
|
37
|
+
const point = (0, import_thread_state.anchorPagePoint)(editor, thread.anchor);
|
|
38
|
+
if (!point) continue;
|
|
39
|
+
const key = pinStackKey(point);
|
|
40
|
+
const group = groups.get(key);
|
|
41
|
+
if (group) {
|
|
42
|
+
group.push(thread);
|
|
43
|
+
} else {
|
|
44
|
+
groups.set(key, [thread]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const stacks = /* @__PURE__ */ new Map();
|
|
48
|
+
for (const group of groups.values()) {
|
|
49
|
+
if (group.length < 2) continue;
|
|
50
|
+
group.sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1));
|
|
51
|
+
const ids = group.map((thread) => thread.id);
|
|
52
|
+
for (const id of ids) {
|
|
53
|
+
stacks.set(id, ids);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return stacks;
|
|
57
|
+
}
|
|
58
|
+
function pinStacksEqual(a, b) {
|
|
59
|
+
if (a === b) return true;
|
|
60
|
+
if (a.size !== b.size) return false;
|
|
61
|
+
for (const [id, group] of b) {
|
|
62
|
+
const prevGroup = a.get(id);
|
|
63
|
+
if (!prevGroup) return false;
|
|
64
|
+
if (prevGroup === group) continue;
|
|
65
|
+
if (prevGroup.length !== group.length) return false;
|
|
66
|
+
for (let i = 0; i < group.length; i++) {
|
|
67
|
+
if (prevGroup[i] !== group[i]) return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
function isOpenStackKeyLive(editor, key, stacks, threadsById) {
|
|
73
|
+
for (const id of stacks.keys()) {
|
|
74
|
+
const thread = threadsById.get(id);
|
|
75
|
+
if (!thread) continue;
|
|
76
|
+
const point = (0, import_thread_state.anchorPagePoint)(editor, thread.anchor);
|
|
77
|
+
if (point && pinStackKey(point) === key) return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=pin-stacking.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/canvas/pin-stacking.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Editor, TLCommentThread } from 'tldraw'\nimport { anchorPagePoint } from './thread-state'\n\n/** Two anchors within this page-space distance (per axis) share a stack. Identical imprecise\n * anchors on one shape resolve to the same point exactly; the tolerance only absorbs float noise. */\nconst PIN_STACK_QUANTUM = 0.1\n\n/**\n * The key a page point stacks under. Threads sharing a key are coincident. Stable identity for a\n * stack itself: it survives losing any member (the survivors keep the same key), unlike keying by\n * a particular thread id, so open-stack state stays put when the stack's oldest thread is deleted.\n */\nexport function pinStackKey(point: { x: number; y: number }): string {\n\treturn `${Math.round(point.x / PIN_STACK_QUANTUM)}:${Math.round(point.y / PIN_STACK_QUANTUM)}`\n}\n\n/**\n * Group threads whose pins land on the same page point \u2014 coincident pins (typically several\n * imprecise comments on one shape) that zooming can never separate. The overlay renders each\n * group as a single count-badge pin that opens the threads as a list. Every member id maps to\n * its group's ordered member ids (oldest first); threads without an entry pin individually.\n *\n * Keyed by page-space anchor point, not screen position, so the result only changes when threads\n * or their anchors move \u2014 never on camera moves.\n * @internal\n */\nexport function computePinStacks(\n\teditor: Editor,\n\tthreads: readonly TLCommentThread[]\n): Map<string, readonly string[]> {\n\tconst pageId = editor.getCurrentPageId()\n\tconst groups = new Map<string, TLCommentThread[]>()\n\n\tfor (const thread of threads) {\n\t\tif (thread.pageId !== pageId) continue\n\t\tconst point = anchorPagePoint(editor, thread.anchor)\n\t\tif (!point) continue\n\t\tconst key = pinStackKey(point)\n\t\tconst group = groups.get(key)\n\t\tif (group) {\n\t\t\tgroup.push(thread)\n\t\t} else {\n\t\t\tgroups.set(key, [thread])\n\t\t}\n\t}\n\n\tconst stacks = new Map<string, readonly string[]>()\n\tfor (const group of groups.values()) {\n\t\tif (group.length < 2) continue\n\t\tgroup.sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1))\n\t\tconst ids = group.map((thread) => thread.id)\n\t\tfor (const id of ids) {\n\t\t\tstacks.set(id, ids)\n\t\t}\n\t}\n\treturn stacks\n}\n\n/**\n * Value equality for pin-stack maps, so the overlay can hold the map's identity across a recompute\n * that changed no grouping. Membership only: two equal maps can describe stacks at different page\n * points, so anything keyed on a stack's *point* must read the anchors itself, as\n * {@link isOpenStackKeyLive} does.\n * @internal\n */\nexport function pinStacksEqual(\n\ta: ReadonlyMap<string, readonly string[]>,\n\tb: ReadonlyMap<string, readonly string[]>\n): boolean {\n\tif (a === b) return true\n\tif (a.size !== b.size) return false\n\tfor (const [id, group] of b) {\n\t\tconst prevGroup = a.get(id)\n\t\tif (!prevGroup) return false\n\t\tif (prevGroup === group) continue\n\t\tif (prevGroup.length !== group.length) return false\n\t\tfor (let i = 0; i < group.length; i++) {\n\t\t\tif (prevGroup[i] !== group[i]) return false\n\t\t}\n\t}\n\treturn true\n}\n\n/**\n * Whether an open-stack key still names a stack on the canvas \u2014 false once that stack is emptied,\n * moved, or off the page, at which point the caller must clear the key. Reads the anchors, so a\n * reactive caller re-evaluates on a move that {@link pinStacksEqual} can't see.\n * @internal\n */\nexport function isOpenStackKeyLive(\n\teditor: Editor,\n\tkey: string,\n\tstacks: ReadonlyMap<string, readonly string[]>,\n\tthreadsById: ReadonlyMap<string, TLCommentThread>\n): boolean {\n\tfor (const id of stacks.keys()) {\n\t\tconst thread = threadsById.get(id)\n\t\tif (!thread) continue\n\t\tconst point = anchorPagePoint(editor, thread.anchor)\n\t\tif (point && pinStackKey(point) === key) return true\n\t}\n\treturn false\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,0BAAgC;AAIhC,MAAM,oBAAoB;AAOnB,SAAS,YAAY,OAAyC;AACpE,SAAO,GAAG,KAAK,MAAM,MAAM,IAAI,iBAAiB,CAAC,IAAI,KAAK,MAAM,MAAM,IAAI,iBAAiB,CAAC;AAC7F;AAYO,SAAS,iBACf,QACA,SACiC;AACjC,QAAM,SAAS,OAAO,iBAAiB;AACvC,QAAM,SAAS,oBAAI,IAA+B;AAElD,aAAW,UAAU,SAAS;AAC7B,QAAI,OAAO,WAAW,OAAQ;AAC9B,UAAM,YAAQ,qCAAgB,QAAQ,OAAO,MAAM;AACnD,QAAI,CAAC,MAAO;AACZ,UAAM,MAAM,YAAY,KAAK;AAC7B,UAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,QAAI,OAAO;AACV,YAAM,KAAK,MAAM;AAAA,IAClB,OAAO;AACN,aAAO,IAAI,KAAK,CAAC,MAAM,CAAC;AAAA,IACzB;AAAA,EACD;AAEA,QAAM,SAAS,oBAAI,IAA+B;AAClD,aAAW,SAAS,OAAO,OAAO,GAAG;AACpC,QAAI,MAAM,SAAS,EAAG;AACtB,UAAM,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE;AACxE,UAAM,MAAM,MAAM,IAAI,CAAC,WAAW,OAAO,EAAE;AAC3C,eAAW,MAAM,KAAK;AACrB,aAAO,IAAI,IAAI,GAAG;AAAA,IACnB;AAAA,EACD;AACA,SAAO;AACR;AASO,SAAS,eACf,GACA,GACU;AACV,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,EAAE,SAAS,EAAE,KAAM,QAAO;AAC9B,aAAW,CAAC,IAAI,KAAK,KAAK,GAAG;AAC5B,UAAM,YAAY,EAAE,IAAI,EAAE;AAC1B,QAAI,CAAC,UAAW,QAAO;AACvB,QAAI,cAAc,MAAO;AACzB,QAAI,UAAU,WAAW,MAAM,OAAQ,QAAO;AAC9C,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,UAAI,UAAU,CAAC,MAAM,MAAM,CAAC,EAAG,QAAO;AAAA,IACvC;AAAA,EACD;AACA,SAAO;AACR;AAQO,SAAS,mBACf,QACA,KACA,QACA,aACU;AACV,aAAW,MAAM,OAAO,KAAK,GAAG;AAC/B,UAAM,SAAS,YAAY,IAAI,EAAE;AACjC,QAAI,CAAC,OAAQ;AACb,UAAM,YAAQ,qCAAgB,QAAQ,OAAO,MAAM;AACnD,QAAI,SAAS,YAAY,KAAK,MAAM,IAAK,QAAO;AAAA,EACjD;AACA,SAAO;AACR;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|