@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
|
@@ -38,7 +38,7 @@ describe('renderCommentHtml', () => {
|
|
|
38
38
|
it('renders a mention as a pill and resolves its id to the current name', () => {
|
|
39
39
|
const body = doc(para(text('hey '), mention('u1', 'Ada')))
|
|
40
40
|
const html = renderCommentHtml(body, (id) => (id === 'u1' ? 'Ada Lovelace' : '?'))
|
|
41
|
-
expect(html).toContain('cmt-mention')
|
|
41
|
+
expect(html).toContain('tlui-cmt-mention')
|
|
42
42
|
// the live name from the resolver, not the label stored at insert time
|
|
43
43
|
expect(html).toContain('@Ada Lovelace')
|
|
44
44
|
expect(html).not.toContain('@Ada<')
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { generateText, JSONContent } from '@tiptap/core'
|
|
2
|
+
import { type CommentAuthor, createMentionExtension } from '@tldraw/mentions'
|
|
3
|
+
import { renderHtmlFromRichTextWithExtensions, TLRichText } from 'tldraw'
|
|
3
4
|
import { commentTipTapExtensions, isCommentEmpty } from '../ui/comment-extensions'
|
|
4
|
-
import { commentMention } from '../ui/comment-mention'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The author name shown in a byline when no source can name an id (e.g. a deleted account, or a
|
|
8
8
|
* member with no comment and no live presence). The toolkit's one generic default, applied where a
|
|
9
|
-
* byline needs a name; hosts pre-empt it by
|
|
10
|
-
* key — a single English literal
|
|
9
|
+
* byline needs a name; hosts pre-empt it by resolving the id from `resolveAuthor`. Not a
|
|
10
|
+
* translation key — a single English literal.
|
|
11
11
|
*/
|
|
12
12
|
export const UNKNOWN_AUTHOR = 'Someone'
|
|
13
13
|
|
|
14
|
+
/** The `CommentAuthor` fallback for an unresolvable id — just the name, no color or image.
|
|
15
|
+
* Frozen: the one shared instance is handed to host render slots. */
|
|
16
|
+
export const UNKNOWN_COMMENT_AUTHOR: CommentAuthor = Object.freeze({ name: UNKNOWN_AUTHOR })
|
|
17
|
+
|
|
14
18
|
/**
|
|
15
19
|
* The comment extension set has no heading node, so a body that nonetheless contains one (e.g. a
|
|
16
20
|
* record created programmatically or synced from a client with a fuller set) would make TipTap
|
|
@@ -40,9 +44,9 @@ const htmlCache = new WeakMap<TLRichText, string>()
|
|
|
40
44
|
|
|
41
45
|
/**
|
|
42
46
|
* Render a comment body to HTML through the limited comment extension set (no headings), so a body
|
|
43
|
-
* always renders with comment formatting regardless of the host editor's rich-text config.
|
|
44
|
-
* tldraw's
|
|
45
|
-
* collapsing. `resolveName` maps a member id to its current name for any @mentions.
|
|
47
|
+
* always renders with comment formatting regardless of the host editor's rich-text config. Renders
|
|
48
|
+
* through tldraw's shared helper, so it gets the same empty-paragraph fix that keeps blank lines
|
|
49
|
+
* from collapsing. `resolveName` maps a member id to its current name for any @mentions.
|
|
46
50
|
*/
|
|
47
51
|
export function renderCommentHtml(
|
|
48
52
|
richText: TLRichText,
|
|
@@ -54,11 +58,11 @@ export function renderCommentHtml(
|
|
|
54
58
|
if (cached !== undefined) return cached
|
|
55
59
|
}
|
|
56
60
|
const extensions = mentions
|
|
57
|
-
? [...commentTipTapExtensions,
|
|
61
|
+
? [...commentTipTapExtensions, createMentionExtension({ resolveName })]
|
|
58
62
|
: commentTipTapExtensions
|
|
59
|
-
const html =
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
const html = renderHtmlFromRichTextWithExtensions(
|
|
64
|
+
demoteHeadings(richText as JSONContent) as TLRichText,
|
|
65
|
+
extensions
|
|
62
66
|
)
|
|
63
67
|
if (!mentions) htmlCache.set(richText, html)
|
|
64
68
|
return html
|
|
@@ -82,7 +86,7 @@ export function renderCommentPlaintext(
|
|
|
82
86
|
if (cached !== undefined) return cached
|
|
83
87
|
}
|
|
84
88
|
const extensions = mentions
|
|
85
|
-
? [...commentTipTapExtensions,
|
|
89
|
+
? [...commentTipTapExtensions, createMentionExtension({ resolveName })]
|
|
86
90
|
: commentTipTapExtensions
|
|
87
91
|
const text = generateText(demoteHeadings(richText as JSONContent), extensions, {
|
|
88
92
|
blockSeparator: '\n',
|
|
@@ -1,55 +1,90 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Editor,
|
|
3
|
-
TLComment,
|
|
4
|
-
TLCommentId,
|
|
5
|
-
TLCommentThread,
|
|
6
|
-
TLCommentThreadId,
|
|
7
|
-
TLRecord,
|
|
8
|
-
} from 'tldraw'
|
|
1
|
+
import { Editor, TLComment, TLCommentReaction, TLCommentThread, TLRecord } from 'tldraw'
|
|
9
2
|
|
|
10
3
|
/**
|
|
11
|
-
* Typed
|
|
4
|
+
* Typed reads of comment records on the editor store.
|
|
12
5
|
*
|
|
13
|
-
* Comment
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
6
|
+
* Comment records live on the editor's local store so the canvas can render them reactively, but
|
|
7
|
+
* they're opt-in and aren't part of the `TLRecord` union — so `editor.store` is statically typed
|
|
8
|
+
* `Store<TLRecord>` and every access has to reinterpret the type. These helpers own that
|
|
9
|
+
* reinterpretation behind one boundary and keep call sites typed.
|
|
10
|
+
*
|
|
11
|
+
* Writes do the same, but also answer to the undo/redo policy, so they live in
|
|
12
|
+
* `comment-mutations.ts`.
|
|
19
13
|
*/
|
|
20
14
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Remove comment records from the store by id. @public */
|
|
30
|
-
export function removeCommentRecords(
|
|
31
|
-
editor: Editor,
|
|
32
|
-
ids: (TLCommentId | TLCommentThreadId)[]
|
|
33
|
-
): void {
|
|
34
|
-
editor.store.remove(ids as unknown as TLRecord['id'][])
|
|
35
|
-
}
|
|
15
|
+
/**
|
|
16
|
+
* A record that lives in a comment thread: the thread itself, one of its messages, or a reaction
|
|
17
|
+
* to one of those messages.
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export type TLCommentRecord = TLComment | TLCommentThread | TLCommentReaction
|
|
36
21
|
|
|
37
22
|
/** Read one comment record by id, or `undefined` if the id isn't a present comment record. @public */
|
|
38
23
|
export function getCommentRecord(editor: Editor, id: string): TLCommentRecord | undefined {
|
|
39
24
|
const record = editor.store.get(id as TLRecord['id']) as unknown as TLCommentRecord | undefined
|
|
40
25
|
if (!record) return undefined
|
|
41
|
-
if (
|
|
26
|
+
if (
|
|
27
|
+
record.typeName === 'comment' ||
|
|
28
|
+
record.typeName === 'comment-thread' ||
|
|
29
|
+
record.typeName === 'comment-reaction'
|
|
30
|
+
) {
|
|
31
|
+
return record
|
|
32
|
+
}
|
|
42
33
|
return undefined
|
|
43
34
|
}
|
|
44
35
|
|
|
45
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Every comment thread in the store, **including soft-deleted and emptied ones** awaiting the
|
|
38
|
+
* server's prune, which nothing renders. For the set the UI shows, use {@link getLiveCommentThreads}.
|
|
39
|
+
*
|
|
40
|
+
* Non-reactive; wrap in `useValue`, or use `useCommentThreads`, to react.
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
46
43
|
export function getCommentThreads(editor: Editor): TLCommentThread[] {
|
|
47
44
|
const typeName = 'comment-thread' as TLRecord['typeName']
|
|
48
45
|
return editor.store.query.records(typeName).get() as unknown as TLCommentThread[]
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Every comment in the store, **including soft-deleted ones** awaiting the server's prune. For the
|
|
50
|
+
* set the UI shows, use {@link getLiveComments}.
|
|
51
|
+
*
|
|
52
|
+
* Non-reactive; wrap in `useValue`, or use `useComments`, to react.
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
52
55
|
export function getComments(editor: Editor): TLComment[] {
|
|
53
56
|
const typeName = 'comment' as TLRecord['typeName']
|
|
54
57
|
return editor.store.query.records(typeName).get() as unknown as TLComment[]
|
|
55
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The comments that should render: not soft-deleted. A deleted record lingers in the store until
|
|
62
|
+
* the server prunes it, so build counts and lists from this rather than {@link getComments}.
|
|
63
|
+
*
|
|
64
|
+
* Non-reactive; the reactive equivalent is `useComments` (which also sorts oldest first).
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
export function getLiveComments(editor: Editor): TLComment[] {
|
|
68
|
+
return getComments(editor).filter((comment) => !comment.isDeleted)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The comment threads that should render (pins, sidebar): not soft-deleted, and still holding at
|
|
73
|
+
* least one live comment. A thread emptied by its last comment's delete lingers with no surface
|
|
74
|
+
* until the server's prune lands.
|
|
75
|
+
*
|
|
76
|
+
* Non-reactive; the reactive equivalent is `useCommentThreads`.
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
export function getLiveCommentThreads(editor: Editor): TLCommentThread[] {
|
|
80
|
+
const threadIdsWithComments = new Set(getLiveComments(editor).map((comment) => comment.threadId))
|
|
81
|
+
return getCommentThreads(editor).filter(
|
|
82
|
+
(thread) => !thread.isDeleted && threadIdsWithComments.has(thread.id)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** All comment reactions currently in the store (non-reactive; wrap in `useValue` to react). @public */
|
|
87
|
+
export function getCommentReactions(editor: Editor): TLCommentReaction[] {
|
|
88
|
+
const typeName = 'comment-reaction' as TLRecord['typeName']
|
|
89
|
+
return editor.store.query.records(typeName).get() as unknown as TLCommentReaction[]
|
|
90
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Driver } from '@tldraw/driver'
|
|
2
|
+
import {
|
|
3
|
+
commentSchemaRecords,
|
|
4
|
+
createShapeId,
|
|
5
|
+
createTLSchema,
|
|
6
|
+
createTLStore,
|
|
7
|
+
defaultBindingUtils,
|
|
8
|
+
defaultShapeUtils,
|
|
9
|
+
defaultTools,
|
|
10
|
+
Editor,
|
|
11
|
+
TLShapeId,
|
|
12
|
+
TLStateNodeConstructor,
|
|
13
|
+
} from 'tldraw'
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
15
|
+
import { CommentTool } from './comment-tool'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pointer-driven tests for the comment tool's placement affordances. These need a real editor
|
|
19
|
+
* and real pointer dispatch: the behavior under test is how the tool's state transitions
|
|
20
|
+
* (idle → pointing → dragging) maintain the editor's hinting state.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
let editor: Editor
|
|
24
|
+
|
|
25
|
+
function makeEditor(tool: TLStateNodeConstructor = CommentTool) {
|
|
26
|
+
editor = new Editor({
|
|
27
|
+
store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
|
|
28
|
+
shapeUtils: defaultShapeUtils,
|
|
29
|
+
bindingUtils: defaultBindingUtils,
|
|
30
|
+
tools: [...defaultTools, tool],
|
|
31
|
+
getContainer: () => document.body,
|
|
32
|
+
})
|
|
33
|
+
return new Driver(editor)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
editor.dispose()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
function makeShape(x = 100, y = 100, w = 100, h = 50): TLShapeId {
|
|
41
|
+
const id = createShapeId()
|
|
42
|
+
editor.createShape({ id, type: 'geo', x, y, props: { w, h } })
|
|
43
|
+
return id
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeFrame(x = 300, y = 100, w = 200, h = 200): TLShapeId {
|
|
47
|
+
const id = createShapeId()
|
|
48
|
+
editor.createShape({ id, type: 'frame', x, y, props: { w, h } })
|
|
49
|
+
return id
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('comment tool placement hints', () => {
|
|
53
|
+
let driver: Driver
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
driver = makeEditor()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('hints the shape under the pointer while hovering', () => {
|
|
60
|
+
const id = makeShape()
|
|
61
|
+
editor.setCurrentTool('comment')
|
|
62
|
+
driver.pointerMove(150, 125)
|
|
63
|
+
expect(editor.getHintingShapeIds()).toEqual([id])
|
|
64
|
+
|
|
65
|
+
driver.pointerMove(400, 400)
|
|
66
|
+
expect(editor.getHintingShapeIds()).toEqual([])
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('keeps hinting the anchor target while the composer follows the pointer', () => {
|
|
70
|
+
const id = makeShape()
|
|
71
|
+
editor.setCurrentTool('comment')
|
|
72
|
+
driver.pointerMove(400, 400)
|
|
73
|
+
expect(editor.getHintingShapeIds()).toEqual([])
|
|
74
|
+
|
|
75
|
+
// Press away from the shape, then drag the follow composer over it: the shape a release
|
|
76
|
+
// would anchor to stays hinted, exactly like the idle hover.
|
|
77
|
+
driver.pointerDown(400, 400)
|
|
78
|
+
driver.pointerMove(150, 125)
|
|
79
|
+
expect(editor.isIn('comment.pointing')).toBe(true)
|
|
80
|
+
expect(editor.getHintingShapeIds()).toEqual([id])
|
|
81
|
+
|
|
82
|
+
// ...and dragging back off the shape drops the hint.
|
|
83
|
+
driver.pointerMove(400, 400)
|
|
84
|
+
expect(editor.getHintingShapeIds()).toEqual([])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('clears the hint when the tool exits', () => {
|
|
88
|
+
const id = makeShape()
|
|
89
|
+
editor.setCurrentTool('comment')
|
|
90
|
+
driver.pointerMove(150, 125)
|
|
91
|
+
expect(editor.getHintingShapeIds()).toEqual([id])
|
|
92
|
+
|
|
93
|
+
// Releasing places the comment and hands back to select, which owns its own hover state.
|
|
94
|
+
driver.pointerDown(150, 125)
|
|
95
|
+
driver.pointerUp(150, 125)
|
|
96
|
+
expect(editor.isIn('select')).toBe(true)
|
|
97
|
+
expect(editor.getHintingShapeIds()).toEqual([])
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe('comment tool placement hints with regions enabled', () => {
|
|
102
|
+
it('clears the hint when a drag becomes a region', () => {
|
|
103
|
+
const driver = makeEditor(CommentTool.configure({ enableRegions: true }))
|
|
104
|
+
const id = makeShape()
|
|
105
|
+
editor.setCurrentTool('comment')
|
|
106
|
+
driver.pointerMove(150, 125)
|
|
107
|
+
expect(editor.getHintingShapeIds()).toEqual([id])
|
|
108
|
+
|
|
109
|
+
// Past the drag threshold this is a region draw — a single-shape outline under the dashed
|
|
110
|
+
// box would be stale, so the hint clears even with the pointer still over the shape.
|
|
111
|
+
driver.pointerDown(150, 125)
|
|
112
|
+
driver.pointerMove(180, 140)
|
|
113
|
+
expect(editor.isIn('comment.dragging')).toBe(true)
|
|
114
|
+
expect(editor.getHintingShapeIds()).toEqual([])
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('comment tool anchoring to frames', () => {
|
|
119
|
+
let driver: Driver
|
|
120
|
+
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
driver = makeEditor()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('hints a frame when hovering its empty interior', () => {
|
|
126
|
+
// A frame is hit only on its edge/label by default; the comment tool passes hitFrameInside
|
|
127
|
+
// so a click in its body anchors the frame. (300,100)–(500,300): (400,200) is well inside,
|
|
128
|
+
// clear of the edges and the (above-frame) label.
|
|
129
|
+
const frame = makeFrame(300, 100, 200, 200)
|
|
130
|
+
editor.setCurrentTool('comment')
|
|
131
|
+
driver.pointerMove(400, 200)
|
|
132
|
+
expect(editor.getHintingShapeIds()).toEqual([frame])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('a shape over the frame still wins the hit', () => {
|
|
136
|
+
const frame = makeFrame(300, 100, 200, 200)
|
|
137
|
+
const child = makeShape(340, 140, 60, 40) // sits inside the frame, drawn above it
|
|
138
|
+
editor.setCurrentTool('comment')
|
|
139
|
+
driver.pointerMove(370, 160)
|
|
140
|
+
expect(editor.getHintingShapeIds()).toEqual([child])
|
|
141
|
+
// ...and the frame's empty interior beside it still anchors the frame.
|
|
142
|
+
driver.pointerMove(470, 260)
|
|
143
|
+
expect(editor.getHintingShapeIds()).toEqual([frame])
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('keeps hinting a frame while the composer follows the pointer into it', () => {
|
|
147
|
+
// The click-drag path, not just idle hover: press away from the frame, then drag the follow
|
|
148
|
+
// composer into its body. The pointing state hints the anchor target too, so the frame stays
|
|
149
|
+
// hinted while dragging over it (regions are off by default, so this doesn't become a region).
|
|
150
|
+
const frame = makeFrame(300, 100, 200, 200)
|
|
151
|
+
editor.setCurrentTool('comment')
|
|
152
|
+
driver.pointerMove(50, 50)
|
|
153
|
+
driver.pointerDown(50, 50)
|
|
154
|
+
driver.pointerMove(400, 200)
|
|
155
|
+
expect(editor.isIn('comment.pointing')).toBe(true)
|
|
156
|
+
expect(editor.getHintingShapeIds()).toEqual([frame])
|
|
157
|
+
|
|
158
|
+
// ...and dragging back off the frame clears it.
|
|
159
|
+
driver.pointerMove(50, 400)
|
|
160
|
+
expect(editor.getHintingShapeIds()).toEqual([])
|
|
161
|
+
})
|
|
162
|
+
})
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BoxModel,
|
|
3
|
+
Editor,
|
|
3
4
|
StateNode,
|
|
4
5
|
TLCommentAnchor,
|
|
5
6
|
TLStateNodeConstructor,
|
|
6
7
|
TLUiOverrides,
|
|
7
8
|
VecLike,
|
|
8
9
|
} from 'tldraw'
|
|
9
|
-
import { type CommentingOptions, defaultCommentingOptions } from './options'
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { regionPinPoint, shapeAnchorAt } from './thread-state'
|
|
10
|
+
import { type CommentingOptions, defaultCommentingOptions, getCommentingOptions } from './options'
|
|
11
|
+
import { commentsSidebarOpen, pendingComment, regionDraft } from './state'
|
|
12
|
+
import { commentTargetShapeAt, regionPinPoint, shapeAnchorAt } from './thread-state'
|
|
13
13
|
|
|
14
14
|
/** A comment being placed but not yet posted: where its composer sits and what it will anchor
|
|
15
15
|
* to. Shared between the tool (which sets it on click) and the overlay (which renders the
|
|
16
|
-
* composer)
|
|
17
|
-
*
|
|
18
|
-
* @public */
|
|
16
|
+
* composer) through the internal `pendingComment` atom.
|
|
17
|
+
* @internal */
|
|
19
18
|
export interface PendingComment {
|
|
20
19
|
anchor: TLCommentAnchor
|
|
21
20
|
/** Page point where the composer opens (the click location, or a region's pin corner). */
|
|
@@ -50,9 +49,10 @@ export function regionBetween(a: VecLike, b: VecLike): BoxModel {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
/**
|
|
53
|
-
* The comment tool.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
52
|
+
* The comment tool. Pressing down opens the comment composer at the pointer and it follows until
|
|
53
|
+
* release — like placing a sticky note — settling on a point, or on a shape when released over one.
|
|
54
|
+
* With region comments enabled, dragging past the threshold draws a region rectangle instead.
|
|
55
|
+
* Placement only opens a composer; the records are created when the comment is posted.
|
|
56
56
|
* @public
|
|
57
57
|
*/
|
|
58
58
|
export class CommentTool extends StateNode {
|
|
@@ -90,7 +90,16 @@ export class CommentTool extends StateNode {
|
|
|
90
90
|
options: CommentingOptions = defaultCommentingOptions
|
|
91
91
|
|
|
92
92
|
override onEnter() {
|
|
93
|
-
this.editor.setCursor({ type: '
|
|
93
|
+
this.editor.setCursor({ type: 'comment', rotation: 0 })
|
|
94
|
+
// Placing comments is canvas-focused — the thread list gets out of the way while the tool is
|
|
95
|
+
// active. Reopened via its own control (a button), never by leaving the tool.
|
|
96
|
+
commentsSidebarOpen.set(this.editor, false)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override onExit() {
|
|
100
|
+
// Drop the hover hint painted while pointing at shapes (see CommentIdle). The cursor resets
|
|
101
|
+
// when the next tool takes over.
|
|
102
|
+
this.editor.setHintingShapes([])
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
// Escape leaves the tool, like the built-in tools (the editor dispatches `cancel` on Escape).
|
|
@@ -99,9 +108,27 @@ export class CommentTool extends StateNode {
|
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
110
|
|
|
111
|
+
/** Hint the shape a comment placed at the pointer would anchor to, using the same hit-test as the
|
|
112
|
+
* anchor resolution on release. Hinting shapes render an indicator ungated by the active tool, so
|
|
113
|
+
* this shows the select-style outline while the comment tool — not select — is active. */
|
|
114
|
+
function updateAnchorHint(editor: Editor) {
|
|
115
|
+
const hit = commentTargetShapeAt(editor, editor.inputs.getCurrentPagePoint())
|
|
116
|
+
editor.setHintingShapes(hit ? [hit.id] : [])
|
|
117
|
+
}
|
|
118
|
+
|
|
102
119
|
class CommentIdle extends StateNode {
|
|
103
120
|
static override id = 'idle'
|
|
104
121
|
|
|
122
|
+
override onEnter() {
|
|
123
|
+
// Back to hovering: restore the pin cursor (a prior placing state may have hidden it).
|
|
124
|
+
this.editor.setCursor({ type: 'comment', rotation: 0 })
|
|
125
|
+
updateAnchorHint(this.editor)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override onPointerMove() {
|
|
129
|
+
updateAnchorHint(this.editor)
|
|
130
|
+
}
|
|
131
|
+
|
|
105
132
|
override onPointerDown() {
|
|
106
133
|
this.parent.transition('pointing')
|
|
107
134
|
}
|
|
@@ -110,32 +137,82 @@ class CommentIdle extends StateNode {
|
|
|
110
137
|
class CommentPointing extends StateNode {
|
|
111
138
|
static override id = 'pointing'
|
|
112
139
|
|
|
113
|
-
|
|
114
|
-
|
|
140
|
+
override onEnter() {
|
|
141
|
+
// Open the composer immediately at the press point so it's visible from pointer-down (not just
|
|
142
|
+
// on release), and let it trail the pointer while dragging — like placing a sticky note. The
|
|
143
|
+
// anchor is a bare point for now; it's resolved (shape or point) on pointer up.
|
|
144
|
+
const { editor } = this
|
|
145
|
+
// Hide the cursor while placing: the draft composer is the pointer's stand-in now, so the pin
|
|
146
|
+
// cursor sitting over it just reads as clutter.
|
|
147
|
+
editor.setCursor({ type: 'none', rotation: 0 })
|
|
148
|
+
const point = editor.inputs.getCurrentPagePoint()
|
|
149
|
+
pendingComment.set(editor, {
|
|
150
|
+
anchor: { type: 'point', x: point.x, y: point.y },
|
|
151
|
+
point: { x: point.x, y: point.y },
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
115
155
|
override onPointerMove() {
|
|
116
|
-
|
|
156
|
+
const { editor } = this
|
|
157
|
+
// Once the pointer passes the drag threshold with region comments enabled, this is a region,
|
|
158
|
+
// not a follow — hand off to the region drag (which clears this composer and draws the box).
|
|
159
|
+
if (getCommentingOptions(editor).enableRegions && editor.inputs.getIsDragging()) {
|
|
117
160
|
this.parent.transition('dragging')
|
|
161
|
+
return
|
|
118
162
|
}
|
|
163
|
+
// Otherwise the composer trails the pointer — keep hinting the shape a release here would
|
|
164
|
+
// anchor to, like the idle hover does.
|
|
165
|
+
updateAnchorHint(editor)
|
|
166
|
+
const point = editor.inputs.getCurrentPagePoint()
|
|
167
|
+
pendingComment.update(editor, (p) => (p ? { ...p, point: { x: point.x, y: point.y } } : p))
|
|
119
168
|
}
|
|
120
169
|
|
|
121
|
-
//
|
|
170
|
+
// Settle where the pointer is released: anchor to the shape under it, or drop a point.
|
|
122
171
|
override onPointerUp() {
|
|
123
172
|
const { editor } = this
|
|
124
173
|
const point = editor.inputs.getCurrentPagePoint()
|
|
125
|
-
const hit = editor
|
|
174
|
+
const hit = commentTargetShapeAt(editor, point)
|
|
126
175
|
const anchor: TLCommentAnchor = hit
|
|
127
|
-
? shapeAnchorAt(
|
|
176
|
+
? shapeAnchorAt(
|
|
177
|
+
editor,
|
|
178
|
+
hit.id,
|
|
179
|
+
point,
|
|
180
|
+
getCommentingOptions(editor).shouldBePrecise(editor, {
|
|
181
|
+
shapeId: hit.id,
|
|
182
|
+
point,
|
|
183
|
+
altKey: editor.inputs.getAltKey(),
|
|
184
|
+
})
|
|
185
|
+
)
|
|
128
186
|
: { type: 'point', x: point.x, y: point.y }
|
|
129
187
|
pendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } })
|
|
130
|
-
// Hand back to select; the open composer is now the focus.
|
|
131
188
|
editor.setCurrentTool('select')
|
|
132
189
|
}
|
|
190
|
+
|
|
191
|
+
override onCancel() {
|
|
192
|
+
this.cancel()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
override onInterrupt() {
|
|
196
|
+
this.cancel()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Abandon the follow composer if placement is interrupted (Escape, focus loss, etc.).
|
|
200
|
+
private cancel() {
|
|
201
|
+
pendingComment.set(this.editor, null)
|
|
202
|
+
this.editor.setCurrentTool('select')
|
|
203
|
+
}
|
|
133
204
|
}
|
|
134
205
|
|
|
135
206
|
class CommentDragging extends StateNode {
|
|
136
207
|
static override id = 'dragging'
|
|
137
208
|
|
|
138
209
|
override onEnter() {
|
|
210
|
+
// A region drag supersedes the point-follow composer opened in `pointing`. Show a crosshair again,
|
|
211
|
+
// since the composer no longer stands in for the pointer, and drop the placement hint — a region
|
|
212
|
+
// anchors to its rectangle, so a single-shape outline would be stale.
|
|
213
|
+
pendingComment.set(this.editor, null)
|
|
214
|
+
this.editor.setHintingShapes([])
|
|
215
|
+
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
|
139
216
|
this.updateDraft()
|
|
140
217
|
}
|
|
141
218
|
|
|
@@ -146,14 +223,15 @@ class CommentDragging extends StateNode {
|
|
|
146
223
|
// Commit the dragged rectangle as a region anchor; the composer opens at its pin corner.
|
|
147
224
|
override onPointerUp() {
|
|
148
225
|
const { editor } = this
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
226
|
+
const origin = editor.inputs.getOriginPagePoint()
|
|
227
|
+
const current = editor.inputs.getCurrentPagePoint()
|
|
228
|
+
const region = regionBetween(origin, current)
|
|
229
|
+
// The pin lives on the corner the drag released on — drag up-left, pin top-left.
|
|
230
|
+
const pin = { x: current.x >= origin.x ? 1 : 0, y: current.y >= origin.y ? 1 : 0 }
|
|
153
231
|
regionDraft.set(editor, null)
|
|
154
232
|
pendingComment.set(editor, {
|
|
155
|
-
anchor: { type: 'region', ...region },
|
|
156
|
-
point: regionPinPoint(region,
|
|
233
|
+
anchor: { type: 'region', ...region, pinX: pin.x, pinY: pin.y },
|
|
234
|
+
point: regionPinPoint(region, pin),
|
|
157
235
|
})
|
|
158
236
|
editor.setCurrentTool('select')
|
|
159
237
|
}
|
|
@@ -184,7 +262,7 @@ class CommentDragging extends StateNode {
|
|
|
184
262
|
export const commentTools = [CommentTool]
|
|
185
263
|
|
|
186
264
|
/** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.
|
|
187
|
-
* Once registered, tldraw's `
|
|
265
|
+
* Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button.
|
|
188
266
|
* @public */
|
|
189
267
|
export const commentToolOverrides: TLUiOverrides = {
|
|
190
268
|
tools(editor, tools) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
TldrawUiButton,
|
|
2
3
|
TldrawUiDropdownMenuContent,
|
|
3
4
|
TldrawUiDropdownMenuRoot,
|
|
4
5
|
TldrawUiDropdownMenuTrigger,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
useTranslation,
|
|
10
11
|
useValue,
|
|
11
12
|
} from 'tldraw'
|
|
13
|
+
import { FilterIcon } from '../ui/icons'
|
|
12
14
|
import { SidebarFilters } from './sidebar-filters'
|
|
13
15
|
import { sidebarFilters } from './state'
|
|
14
16
|
|
|
@@ -36,28 +38,30 @@ export function CommentsFilterMenu({
|
|
|
36
38
|
return (
|
|
37
39
|
<TldrawUiDropdownMenuRoot id="comments-filter">
|
|
38
40
|
<TldrawUiDropdownMenuTrigger>
|
|
39
|
-
<
|
|
40
|
-
type="
|
|
41
|
-
|
|
41
|
+
<TldrawUiButton
|
|
42
|
+
type="icon"
|
|
43
|
+
tooltip={msg('comments.filter')}
|
|
42
44
|
title={msg('comments.filter')}
|
|
43
|
-
|
|
45
|
+
className="tlui-cmt-header-btn"
|
|
44
46
|
>
|
|
45
47
|
<FilterIcon />
|
|
46
|
-
</
|
|
48
|
+
</TldrawUiButton>
|
|
47
49
|
</TldrawUiDropdownMenuTrigger>
|
|
48
|
-
|
|
50
|
+
{/* No tlui-cmt-menu here: the rows are tldraw menu items, which carry their own insets —
|
|
51
|
+
the extra padding would set this menu apart from the canvas menus it should match. */}
|
|
52
|
+
<TldrawUiDropdownMenuContent side="bottom" align="end" alignOffset={0}>
|
|
49
53
|
<TldrawUiMenuContextProvider type="menu" sourceId="menu">
|
|
50
54
|
<TldrawUiMenuGroup id="comments-filter">
|
|
51
55
|
<TldrawUiMenuCheckboxItem
|
|
52
|
-
id="show-
|
|
53
|
-
label="comments.show-
|
|
54
|
-
checked={filters.
|
|
55
|
-
onSelect={() => toggle('
|
|
56
|
+
id="show-all-pages"
|
|
57
|
+
label="comments.show-all-pages"
|
|
58
|
+
checked={!filters.onlyCurrentPage}
|
|
59
|
+
onSelect={() => toggle('onlyCurrentPage')}
|
|
56
60
|
/>
|
|
57
61
|
{canFilterByAuthor && (
|
|
58
62
|
<TldrawUiMenuCheckboxItem
|
|
59
|
-
id="only-
|
|
60
|
-
label="comments.only-
|
|
63
|
+
id="only-my-comments"
|
|
64
|
+
label="comments.only-my-comments"
|
|
61
65
|
checked={filters.onlyMine}
|
|
62
66
|
onSelect={() => toggle('onlyMine')}
|
|
63
67
|
/>
|
|
@@ -71,10 +75,10 @@ export function CommentsFilterMenu({
|
|
|
71
75
|
/>
|
|
72
76
|
)}
|
|
73
77
|
<TldrawUiMenuCheckboxItem
|
|
74
|
-
id="
|
|
75
|
-
label="comments.
|
|
76
|
-
checked={filters.
|
|
77
|
-
onSelect={() => toggle('
|
|
78
|
+
id="show-resolved"
|
|
79
|
+
label="comments.show-resolved"
|
|
80
|
+
checked={filters.showResolved}
|
|
81
|
+
onSelect={() => toggle('showResolved')}
|
|
78
82
|
/>
|
|
79
83
|
</TldrawUiMenuGroup>
|
|
80
84
|
</TldrawUiMenuContextProvider>
|
|
@@ -82,23 +86,3 @@ export function CommentsFilterMenu({
|
|
|
82
86
|
</TldrawUiDropdownMenuRoot>
|
|
83
87
|
)
|
|
84
88
|
}
|
|
85
|
-
|
|
86
|
-
function FilterIcon() {
|
|
87
|
-
return (
|
|
88
|
-
<svg
|
|
89
|
-
width="16"
|
|
90
|
-
height="16"
|
|
91
|
-
viewBox="0 0 16 16"
|
|
92
|
-
fill="none"
|
|
93
|
-
stroke="currentColor"
|
|
94
|
-
strokeWidth="1.5"
|
|
95
|
-
strokeLinecap="round"
|
|
96
|
-
aria-hidden="true"
|
|
97
|
-
>
|
|
98
|
-
<line x1="2.5" y1="5" x2="13.5" y2="5" />
|
|
99
|
-
<line x1="2.5" y1="11" x2="13.5" y2="11" />
|
|
100
|
-
<circle cx="6" cy="5" r="1.9" fill="currentColor" stroke="none" />
|
|
101
|
-
<circle cx="10.5" cy="11" r="1.9" fill="currentColor" stroke="none" />
|
|
102
|
-
</svg>
|
|
103
|
-
)
|
|
104
|
-
}
|