@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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commentSchemaRecords,
|
|
3
|
+
createCommentThread,
|
|
4
|
+
createShapeId,
|
|
5
|
+
createTLSchema,
|
|
6
|
+
createTLStore,
|
|
7
|
+
defaultBindingUtils,
|
|
8
|
+
defaultShapeUtils,
|
|
9
|
+
defaultTools,
|
|
10
|
+
Editor,
|
|
11
|
+
toRichText,
|
|
12
|
+
createComment as tlCreateComment,
|
|
13
|
+
TLPageId,
|
|
14
|
+
TLShapeId,
|
|
15
|
+
} from 'tldraw'
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
17
|
+
import { registerCommentAnchorLifecycle } from './anchor-lifecycle'
|
|
18
|
+
import { commitCommentMutation, putCommentRecords } from './comment-mutations'
|
|
19
|
+
import { getCommentRecord, getComments } from './comment-store'
|
|
20
|
+
import { CommentTool } from './comment-tool'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* These tests need a real editor: the behavior under test is the interplay between store
|
|
24
|
+
* side effects (the beforeDelete snapshot, operation-complete settlement) and real editor
|
|
25
|
+
* operations like `deleteShapes` and `moveShapesToPage` — exactly what a stub can't model.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
let editor: Editor
|
|
29
|
+
let dispose: () => void
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
editor = new Editor({
|
|
33
|
+
store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
|
|
34
|
+
shapeUtils: defaultShapeUtils,
|
|
35
|
+
bindingUtils: defaultBindingUtils,
|
|
36
|
+
tools: defaultTools,
|
|
37
|
+
getContainer: () => document.body,
|
|
38
|
+
})
|
|
39
|
+
dispose = registerCommentAnchorLifecycle(editor)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
dispose()
|
|
44
|
+
editor.dispose()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
function makeShape(x = 100, y = 100, w = 100, h = 50): TLShapeId {
|
|
48
|
+
const id = createShapeId()
|
|
49
|
+
editor.createShape({ id, type: 'geo', x, y, props: { w, h } })
|
|
50
|
+
return id
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeThread(shapeId: TLShapeId, anchorX = 0.5, anchorY = 0.5, isPrecise = true) {
|
|
54
|
+
const thread = createCommentThread({
|
|
55
|
+
pageId: editor.getCurrentPageId(),
|
|
56
|
+
anchor: { type: 'shape', shapeId, x: anchorX, y: anchorY, isPrecise },
|
|
57
|
+
createdBy: 'me',
|
|
58
|
+
})
|
|
59
|
+
const comment = tlCreateComment({
|
|
60
|
+
threadId: thread.id,
|
|
61
|
+
pageId: thread.pageId,
|
|
62
|
+
authorId: 'me',
|
|
63
|
+
body: toRichText('hello'),
|
|
64
|
+
})
|
|
65
|
+
putCommentRecords(editor, [thread, comment])
|
|
66
|
+
return { thread, comment }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeSecondPage(): TLPageId {
|
|
70
|
+
editor.createPage({ name: 'page 2' })
|
|
71
|
+
return editor.getPages()[1].id
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe('shape deletion', () => {
|
|
75
|
+
it('converts a precise shape anchor to a point anchor where the pin sat', () => {
|
|
76
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
77
|
+
const { thread } = makeThread(shapeId, 0.5, 0.5, true)
|
|
78
|
+
|
|
79
|
+
editor.deleteShape(shapeId)
|
|
80
|
+
|
|
81
|
+
const updated = getCommentRecord(editor, thread.id)!
|
|
82
|
+
expect(updated.typeName).toBe('comment-thread')
|
|
83
|
+
expect((updated as typeof thread).anchor).toEqual({ type: 'point', x: 150, y: 125 })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('converts an imprecise shape anchor where the inset pin is drawn, not at the raw corner', () => {
|
|
87
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
88
|
+
const { thread } = makeThread(shapeId, 0.2, 0.9, false)
|
|
89
|
+
|
|
90
|
+
editor.deleteShape(shapeId)
|
|
91
|
+
|
|
92
|
+
// Top-right corner (200, 100) plus the 20px screen inset toward the shape's centre.
|
|
93
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
94
|
+
expect(updated.anchor).toEqual({ type: 'point', x: 180, y: 120 })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('resolves the imprecise spot from the commenting options when no override is passed', () => {
|
|
98
|
+
const configured = new Editor({
|
|
99
|
+
store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
|
|
100
|
+
shapeUtils: defaultShapeUtils,
|
|
101
|
+
bindingUtils: defaultBindingUtils,
|
|
102
|
+
tools: [...defaultTools, CommentTool.configure({ impreciseShapeAnchor: { x: 0, y: 1 } })],
|
|
103
|
+
getContainer: () => document.body,
|
|
104
|
+
})
|
|
105
|
+
const disposeConfigured = registerCommentAnchorLifecycle(configured)
|
|
106
|
+
try {
|
|
107
|
+
const shapeId = createShapeId()
|
|
108
|
+
configured.createShape({ id: shapeId, type: 'geo', x: 100, y: 100, props: { w: 100, h: 50 } })
|
|
109
|
+
const thread = createCommentThread({
|
|
110
|
+
pageId: configured.getCurrentPageId(),
|
|
111
|
+
anchor: { type: 'shape', shapeId, x: 0.5, y: 0.5, isPrecise: false },
|
|
112
|
+
createdBy: 'me',
|
|
113
|
+
})
|
|
114
|
+
putCommentRecords(configured, [thread])
|
|
115
|
+
|
|
116
|
+
configured.deleteShape(shapeId)
|
|
117
|
+
|
|
118
|
+
// Bottom-left corner (100, 150) plus the inset toward the shape's centre.
|
|
119
|
+
const updated = getCommentRecord(configured, thread.id) as typeof thread
|
|
120
|
+
expect(updated.anchor).toEqual({ type: 'point', x: 120, y: 130 })
|
|
121
|
+
} finally {
|
|
122
|
+
disposeConfigured()
|
|
123
|
+
configured.dispose()
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('converts anchors on children when their frame is deleted with them', () => {
|
|
128
|
+
const frameId = createShapeId()
|
|
129
|
+
editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
|
|
130
|
+
const childId = createShapeId()
|
|
131
|
+
editor.createShape({
|
|
132
|
+
id: childId,
|
|
133
|
+
type: 'geo',
|
|
134
|
+
parentId: frameId,
|
|
135
|
+
x: 50,
|
|
136
|
+
y: 50,
|
|
137
|
+
props: { w: 100, h: 100 },
|
|
138
|
+
})
|
|
139
|
+
const { thread } = makeThread(childId, 0.5, 0.5, true)
|
|
140
|
+
|
|
141
|
+
editor.deleteShape(frameId)
|
|
142
|
+
|
|
143
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
144
|
+
expect(updated.anchor).toEqual({ type: 'point', x: 100, y: 100 })
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('re-attaches the thread when the delete is undone', () => {
|
|
148
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
149
|
+
const { thread } = makeThread(shapeId, 0.5, 0.5, true)
|
|
150
|
+
editor.markHistoryStoppingPoint()
|
|
151
|
+
|
|
152
|
+
editor.deleteShape(shapeId)
|
|
153
|
+
expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
|
|
154
|
+
|
|
155
|
+
editor.undo()
|
|
156
|
+
|
|
157
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
158
|
+
expect(updated.anchor).toEqual(thread.anchor)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('keeps the restore when a returning shape cannot resolve its page yet', () => {
|
|
162
|
+
const frameId = createShapeId()
|
|
163
|
+
editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
|
|
164
|
+
const childId = createShapeId()
|
|
165
|
+
editor.createShape({
|
|
166
|
+
id: childId,
|
|
167
|
+
type: 'geo',
|
|
168
|
+
parentId: frameId,
|
|
169
|
+
x: 50,
|
|
170
|
+
y: 50,
|
|
171
|
+
props: { w: 100, h: 100 },
|
|
172
|
+
})
|
|
173
|
+
const { thread } = makeThread(childId, 0.5, 0.5, true)
|
|
174
|
+
const childRecord = editor.getShape(childId)!
|
|
175
|
+
const frameRecord = editor.getShape(frameId)!
|
|
176
|
+
|
|
177
|
+
editor.deleteShape(frameId)
|
|
178
|
+
expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
|
|
179
|
+
|
|
180
|
+
// The child returns in an earlier operation than its parent, so its ancestor page can't
|
|
181
|
+
// resolve when it first reappears.
|
|
182
|
+
editor.store.put([childRecord])
|
|
183
|
+
editor.store.put([frameRecord])
|
|
184
|
+
|
|
185
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
186
|
+
expect(updated.anchor).toEqual(thread.anchor)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('re-attaches on undo even when the lifecycle was re-registered in between', () => {
|
|
190
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
191
|
+
const { thread } = makeThread(shapeId, 0.5, 0.5, true)
|
|
192
|
+
editor.markHistoryStoppingPoint()
|
|
193
|
+
|
|
194
|
+
editor.deleteShape(shapeId)
|
|
195
|
+
expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
|
|
196
|
+
|
|
197
|
+
// The registering effect re-runs whenever its inputs change identity; conversion memory
|
|
198
|
+
// must not live and die with any one registration.
|
|
199
|
+
dispose()
|
|
200
|
+
dispose = registerCommentAnchorLifecycle(editor)
|
|
201
|
+
|
|
202
|
+
editor.undo()
|
|
203
|
+
|
|
204
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
205
|
+
expect(updated.anchor).toEqual(thread.anchor)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('does not re-attach on undo when the pin was moved after the conversion', () => {
|
|
209
|
+
const shapeId = makeShape(100, 100, 100, 50)
|
|
210
|
+
const { thread } = makeThread(shapeId, 0.5, 0.5, true)
|
|
211
|
+
editor.markHistoryStoppingPoint()
|
|
212
|
+
|
|
213
|
+
editor.deleteShape(shapeId)
|
|
214
|
+
const converted = getCommentRecord(editor, thread.id) as typeof thread
|
|
215
|
+
commitCommentMutation(editor, ({ put }) =>
|
|
216
|
+
put([{ ...converted, anchor: { type: 'point', x: 900, y: 900 } }])
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
editor.undo()
|
|
220
|
+
|
|
221
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
222
|
+
expect(updated.anchor).toEqual({ type: 'point', x: 900, y: 900 })
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('leaves threads on other shapes untouched', () => {
|
|
226
|
+
const shapeId = makeShape()
|
|
227
|
+
const otherId = makeShape(300, 300)
|
|
228
|
+
const { thread } = makeThread(otherId)
|
|
229
|
+
|
|
230
|
+
editor.deleteShape(shapeId)
|
|
231
|
+
|
|
232
|
+
const updated = getCommentRecord(editor, thread.id) as typeof thread
|
|
233
|
+
expect(updated.anchor.type).toBe('shape')
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
describe('moving a shape to another page', () => {
|
|
238
|
+
it('re-homes the thread and its comments to the new page, keeping the shape anchor', () => {
|
|
239
|
+
const shapeId = makeShape()
|
|
240
|
+
const { thread, comment } = makeThread(shapeId)
|
|
241
|
+
const page2 = makeSecondPage()
|
|
242
|
+
|
|
243
|
+
editor.moveShapesToPage([shapeId], page2)
|
|
244
|
+
|
|
245
|
+
const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
|
|
246
|
+
expect(updatedThread.pageId).toBe(page2)
|
|
247
|
+
expect(updatedThread.anchor).toEqual(thread.anchor)
|
|
248
|
+
const updatedComment = getComments(editor).find((c) => c.id === comment.id)!
|
|
249
|
+
expect(updatedComment.pageId).toBe(page2)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('re-homes threads anchored to children of a moved frame', () => {
|
|
253
|
+
const frameId = createShapeId()
|
|
254
|
+
editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
|
|
255
|
+
const childId = createShapeId()
|
|
256
|
+
editor.createShape({
|
|
257
|
+
id: childId,
|
|
258
|
+
type: 'geo',
|
|
259
|
+
parentId: frameId,
|
|
260
|
+
x: 50,
|
|
261
|
+
y: 50,
|
|
262
|
+
props: { w: 100, h: 100 },
|
|
263
|
+
})
|
|
264
|
+
const { thread } = makeThread(childId)
|
|
265
|
+
const page2 = makeSecondPage()
|
|
266
|
+
|
|
267
|
+
editor.moveShapesToPage([frameId], page2)
|
|
268
|
+
|
|
269
|
+
const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
|
|
270
|
+
expect(updatedThread.pageId).toBe(page2)
|
|
271
|
+
expect(updatedThread.anchor.type).toBe('shape')
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('re-homes the thread back when the move is undone', () => {
|
|
275
|
+
const shapeId = makeShape()
|
|
276
|
+
const { thread } = makeThread(shapeId)
|
|
277
|
+
const page1 = editor.getCurrentPageId()
|
|
278
|
+
const page2 = makeSecondPage()
|
|
279
|
+
editor.markHistoryStoppingPoint()
|
|
280
|
+
|
|
281
|
+
editor.moveShapesToPage([shapeId], page2)
|
|
282
|
+
expect((getCommentRecord(editor, thread.id) as typeof thread).pageId).toBe(page2)
|
|
283
|
+
|
|
284
|
+
editor.undo()
|
|
285
|
+
|
|
286
|
+
const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
|
|
287
|
+
expect(updatedThread.pageId).toBe(page1)
|
|
288
|
+
expect(updatedThread.anchor.type).toBe('shape')
|
|
289
|
+
})
|
|
290
|
+
})
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { WeakCache } from '@tldraw/utils'
|
|
2
|
+
import { Editor, TLCommentAnchor, TLCommentThread, TLPageId, TLShapeId, VecLike } from 'tldraw'
|
|
3
|
+
import { commitCommentMutation } from './comment-mutations'
|
|
4
|
+
import { getCommentRecord, getComments, getCommentThreads, TLCommentRecord } from './comment-store'
|
|
5
|
+
import { anchorPagePoint, impreciseShapePinInset } from './thread-state'
|
|
6
|
+
|
|
7
|
+
type ShapeAnchor = Extract<TLCommentAnchor, { type: 'shape' }>
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Threads converted to point anchors because their shape was deleted, kept so the anchor can be
|
|
11
|
+
* restored if the shape comes back. Owned by the editor, not the registration closure: the
|
|
12
|
+
* registering effect can re-run, and an undo can arrive long after the delete.
|
|
13
|
+
*/
|
|
14
|
+
const convertedByShapeCache = new WeakCache<
|
|
15
|
+
Editor,
|
|
16
|
+
Map<TLShapeId, { threadId: string; anchor: ShapeAnchor; point: VecLike }[]>
|
|
17
|
+
>()
|
|
18
|
+
|
|
19
|
+
function isAnchoredToShape(
|
|
20
|
+
thread: TLCommentThread,
|
|
21
|
+
shapeId: TLShapeId
|
|
22
|
+
): thread is TLCommentThread & { anchor: ShapeAnchor } {
|
|
23
|
+
const anchor = thread.anchor
|
|
24
|
+
return anchor.type === 'shape' && anchor.shapeId === shapeId
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Keep shape-anchored threads alive across their shape's lifecycle:
|
|
29
|
+
*
|
|
30
|
+
* - When the shape is deleted, the thread converts to a `point` anchor where its pin last sat, so
|
|
31
|
+
* the conversation outlives the shape instead of becoming invisible.
|
|
32
|
+
* - When the shape moves to another page, the thread follows: its `pageId` and each comment's
|
|
33
|
+
* denormalized `pageId` update, and the anchor keeps riding the shape.
|
|
34
|
+
* - When a deleted shape comes back, the thread re-attaches — unless its pin was manually moved in
|
|
35
|
+
* the meantime, in which case the manual placement wins.
|
|
36
|
+
*
|
|
37
|
+
* A page move is `deleteShapes` + re-create with preserved ids inside one `editor.run`, but each
|
|
38
|
+
* store write is its own operation, so "this shape is being moved" is never observable as a single
|
|
39
|
+
* event. The handlers therefore cooperate across operations: `beforeDelete` snapshots where each
|
|
40
|
+
* affected pin sits, the operation-complete pass converts threads whose shape is really gone, and
|
|
41
|
+
* `afterCreate` plus that same pass restore the anchor once the store has settled. Undo/redo of a
|
|
42
|
+
* move replays as a `parentId` update, so `afterChange` re-homes threads on cross-page reparents —
|
|
43
|
+
* including threads anchored to descendants, which move without change events of their own.
|
|
44
|
+
*
|
|
45
|
+
* Remote changes are ignored: the client that performed the operation runs this same maintenance
|
|
46
|
+
* and syncs the result. Writes honour the {@link CommentingOptions.history} option.
|
|
47
|
+
*
|
|
48
|
+
* Registered by `CanvasComments` on mount; parts-built consumers can call this directly. Returns a
|
|
49
|
+
* cleanup function that unregisters all handlers.
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
export function registerCommentAnchorLifecycle(editor: Editor): () => void {
|
|
54
|
+
// Pin positions snapshotted during the current operation: shape id -> (thread id -> pin page
|
|
55
|
+
// point).
|
|
56
|
+
const pendingByShape = new Map<TLShapeId, Map<string, VecLike | null>>()
|
|
57
|
+
const convertedByShape = convertedByShapeCache.get(editor, () => new Map())
|
|
58
|
+
|
|
59
|
+
function rehomeThread(thread: TLCommentThread, pageId: TLPageId, updates: TLCommentRecord[]) {
|
|
60
|
+
updates.push({ ...thread, pageId })
|
|
61
|
+
for (const comment of getComments(editor)) {
|
|
62
|
+
if (comment.threadId === thread.id) updates.push({ ...comment, pageId })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const disposeBeforeDelete = editor.sideEffects.registerBeforeDeleteHandler(
|
|
67
|
+
'shape',
|
|
68
|
+
(shape, source) => {
|
|
69
|
+
if (source === 'remote') return
|
|
70
|
+
for (const thread of getCommentThreads(editor)) {
|
|
71
|
+
if (!isAnchoredToShape(thread, shape.id)) continue
|
|
72
|
+
let snapshot = pendingByShape.get(shape.id)
|
|
73
|
+
if (!snapshot) {
|
|
74
|
+
snapshot = new Map()
|
|
75
|
+
pendingByShape.set(shape.id, snapshot)
|
|
76
|
+
}
|
|
77
|
+
// Snapshot where the pin is drawn, not just the anchor point: imprecise shape pins render inset
|
|
78
|
+
// toward the shape's centre, so without baking the inset in at the current zoom the pin would jump.
|
|
79
|
+
let point = anchorPagePoint(editor, thread.anchor)
|
|
80
|
+
const inset = impreciseShapePinInset(editor, thread.anchor)
|
|
81
|
+
if (point && inset) {
|
|
82
|
+
const zoom = editor.getZoomLevel()
|
|
83
|
+
point = { x: point.x + inset.x / zoom, y: point.y + inset.y / zoom }
|
|
84
|
+
}
|
|
85
|
+
snapshot.set(thread.id, point)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// Shape ids from `convertedByShape` re-created during the current operation. Settled at operation
|
|
91
|
+
// complete rather than in `afterCreate`: creation order within an operation is arbitrary, so a shape
|
|
92
|
+
// can appear before its parent and not resolve an ancestor page yet.
|
|
93
|
+
const returnedShapeIds = new Set<TLShapeId>()
|
|
94
|
+
|
|
95
|
+
const disposeOperationComplete = editor.sideEffects.registerOperationCompleteHandler((source) => {
|
|
96
|
+
// Only user-sourced handlers populate the maps and only local operations should settle
|
|
97
|
+
// them; a remote operation completing leaves any (impossible-in-practice) leftovers for
|
|
98
|
+
// the next local settle rather than judging them against remote state.
|
|
99
|
+
if (source === 'remote') return
|
|
100
|
+
if (pendingByShape.size === 0 && returnedShapeIds.size === 0) return
|
|
101
|
+
const settled = [...pendingByShape.entries()]
|
|
102
|
+
pendingByShape.clear()
|
|
103
|
+
const returned = [...returnedShapeIds]
|
|
104
|
+
returnedShapeIds.clear()
|
|
105
|
+
|
|
106
|
+
const updates: TLCommentRecord[] = []
|
|
107
|
+
for (const [shapeId, threadPoints] of settled) {
|
|
108
|
+
if (editor.getShape(shapeId)) continue
|
|
109
|
+
for (const [threadId, point] of threadPoints) {
|
|
110
|
+
if (!point) continue
|
|
111
|
+
const thread = getCommentRecord(editor, threadId)
|
|
112
|
+
if (!thread || thread.typeName !== 'comment-thread') continue
|
|
113
|
+
if (!isAnchoredToShape(thread, shapeId)) continue
|
|
114
|
+
let converted = convertedByShape.get(shapeId)
|
|
115
|
+
if (!converted) {
|
|
116
|
+
converted = []
|
|
117
|
+
convertedByShape.set(shapeId, converted)
|
|
118
|
+
}
|
|
119
|
+
converted.push({ threadId, anchor: thread.anchor, point })
|
|
120
|
+
updates.push({ ...thread, anchor: { type: 'point', x: point.x, y: point.y } })
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const shapeId of returned) {
|
|
125
|
+
const shape = editor.getShape(shapeId)
|
|
126
|
+
if (!shape) continue
|
|
127
|
+
const converted = convertedByShape.get(shapeId)
|
|
128
|
+
if (!converted) continue
|
|
129
|
+
convertedByShape.delete(shapeId)
|
|
130
|
+
|
|
131
|
+
const pageId = editor.getAncestorPageId(shape)
|
|
132
|
+
for (const { threadId, anchor, point } of converted) {
|
|
133
|
+
const thread = getCommentRecord(editor, threadId)
|
|
134
|
+
if (!thread || thread.typeName !== 'comment-thread') continue
|
|
135
|
+
// Re-attach only threads still sitting exactly where the conversion left them — a
|
|
136
|
+
// pin moved since then was placed deliberately, and that placement wins.
|
|
137
|
+
const current = thread.anchor
|
|
138
|
+
if (current.type !== 'point' || current.x !== point.x || current.y !== point.y) {
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
if (!pageId || thread.pageId === pageId) {
|
|
142
|
+
updates.push({ ...thread, anchor })
|
|
143
|
+
} else {
|
|
144
|
+
rehomeThread({ ...thread, anchor }, pageId, updates)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (updates.length > 0) {
|
|
150
|
+
commitCommentMutation(editor, ({ put }) => put(updates))
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const disposeAfterCreate = editor.sideEffects.registerAfterCreateHandler(
|
|
155
|
+
'shape',
|
|
156
|
+
(shape, source) => {
|
|
157
|
+
if (source === 'remote') return
|
|
158
|
+
if (convertedByShape.has(shape.id)) returnedShapeIds.add(shape.id)
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const disposeAfterChange = editor.sideEffects.registerAfterChangeHandler(
|
|
163
|
+
'shape',
|
|
164
|
+
(prev, next, source) => {
|
|
165
|
+
if (source === 'remote') return
|
|
166
|
+
if (prev.parentId === next.parentId) return
|
|
167
|
+
const pageId = editor.getAncestorPageId(next)
|
|
168
|
+
if (!pageId) return
|
|
169
|
+
// Descendants move with their parent without change events of their own.
|
|
170
|
+
const movedIds = editor.getShapeAndDescendantIds([next.id])
|
|
171
|
+
const updates: TLCommentRecord[] = []
|
|
172
|
+
for (const thread of getCommentThreads(editor)) {
|
|
173
|
+
const anchor = thread.anchor
|
|
174
|
+
if (anchor.type !== 'shape') continue
|
|
175
|
+
if (!movedIds.has(anchor.shapeId)) continue
|
|
176
|
+
if (thread.pageId === pageId) continue
|
|
177
|
+
rehomeThread(thread, pageId, updates)
|
|
178
|
+
}
|
|
179
|
+
if (updates.length > 0) {
|
|
180
|
+
commitCommentMutation(editor, ({ put }) => put(updates))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return () => {
|
|
186
|
+
disposeBeforeDelete()
|
|
187
|
+
disposeOperationComplete()
|
|
188
|
+
disposeAfterCreate()
|
|
189
|
+
disposeAfterChange()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type PointerEvent as ReactPointerEvent } from 'react'
|
|
2
|
+
import { Editor } from 'tldraw'
|
|
3
|
+
|
|
4
|
+
/** A pointer-down that belongs to the camera, not the comment UI: any non-primary button
|
|
5
|
+
* (middle/right-button pans), or a primary press with the spacebar pan key held. */
|
|
6
|
+
export function isCanvasPanGesture(editor: Editor, e: ReactPointerEvent) {
|
|
7
|
+
return e.button !== 0 || editor.inputs.keys.has('Space')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Hand a pointer event to the canvas beneath the comments layer, marked the same way the
|
|
11
|
+
* pass-through wheel/hover hooks mark their re-dispatched events. */
|
|
12
|
+
export function forwardPointerEventToCanvas(container: HTMLElement, e: ReactPointerEvent) {
|
|
13
|
+
const cvs = container.querySelector('.tl-canvas')
|
|
14
|
+
if (!cvs) return
|
|
15
|
+
const newEvent = new PointerEvent(e.type, e.nativeEvent as any)
|
|
16
|
+
;(newEvent as any).isSpecialRedispatchedEvent = true
|
|
17
|
+
cvs.dispatchEvent(newEvent)
|
|
18
|
+
}
|