@tldraw/commenting 0.0.0-bootstrap → 5.3.0-canary.31036673d3f8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/commenting.css +1139 -329
- package/dist-cjs/canvas/anchor-lifecycle.js +148 -0
- package/dist-cjs/canvas/anchor-lifecycle.js.map +7 -0
- package/dist-cjs/canvas/cluster-input.js +2 -2
- package/dist-cjs/canvas/cluster-input.js.map +2 -2
- package/dist-cjs/canvas/comment-body.js +1 -1
- package/dist-cjs/canvas/comment-body.js.map +2 -2
- package/dist-cjs/canvas/comment-drafts.js +54 -0
- package/dist-cjs/canvas/comment-drafts.js.map +7 -0
- package/dist-cjs/canvas/comment-mutations.js +123 -0
- package/dist-cjs/canvas/comment-mutations.js.map +7 -0
- package/dist-cjs/canvas/comment-reactions.js +163 -0
- package/dist-cjs/canvas/comment-reactions.js.map +7 -0
- package/dist-cjs/canvas/comment-render.js +5 -3
- package/dist-cjs/canvas/comment-render.js.map +2 -2
- package/dist-cjs/canvas/comment-store.js +19 -9
- package/dist-cjs/canvas/comment-store.js.map +2 -2
- package/dist-cjs/canvas/comment-tool.js +64 -14
- package/dist-cjs/canvas/comment-tool.js.map +2 -2
- package/dist-cjs/canvas/comments-filter-menu.js +20 -38
- package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overflow-menu.js +22 -27
- package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overlay.js +429 -474
- package/dist-cjs/canvas/comments-overlay.js.map +2 -2
- package/dist-cjs/canvas/comments-sidebar.js +35 -32
- package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
- package/dist-cjs/canvas/comments-visibility-toggle.js +65 -0
- package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
- package/dist-cjs/canvas/context.js +17 -0
- package/dist-cjs/canvas/context.js.map +7 -0
- package/dist-cjs/canvas/hooks.js +22 -3
- package/dist-cjs/canvas/hooks.js.map +2 -2
- package/dist-cjs/canvas/options.js +19 -2
- package/dist-cjs/canvas/options.js.map +2 -2
- package/dist-cjs/canvas/pin-stacking.js +56 -0
- package/dist-cjs/canvas/pin-stacking.js.map +7 -0
- package/dist-cjs/canvas/sidebar-filters.js +2 -2
- package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
- package/dist-cjs/canvas/state.js +28 -14
- package/dist-cjs/canvas/state.js.map +2 -2
- package/dist-cjs/canvas/thread-preview.js +185 -0
- package/dist-cjs/canvas/thread-preview.js.map +7 -0
- package/dist-cjs/canvas/thread-stack.js +199 -0
- package/dist-cjs/canvas/thread-stack.js.map +7 -0
- package/dist-cjs/canvas/thread-state.js +67 -21
- package/dist-cjs/canvas/thread-state.js.map +2 -2
- package/dist-cjs/canvas/thread-view.js +397 -0
- package/dist-cjs/canvas/thread-view.js.map +7 -0
- package/dist-cjs/clustering/computeClusterTable.js +4 -4
- package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
- package/dist-cjs/clustering/runtime.js +119 -7
- package/dist-cjs/clustering/runtime.js.map +2 -2
- package/dist-cjs/clustering/types.js.map +1 -1
- package/dist-cjs/index.d.ts +763 -333
- package/dist-cjs/index.js +46 -28
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/ui/byline.js +6 -4
- package/dist-cjs/ui/byline.js.map +2 -2
- package/dist-cjs/ui/comment-card.js +15 -8
- package/dist-cjs/ui/comment-card.js.map +2 -2
- package/dist-cjs/ui/comment-composer.js +137 -36
- package/dist-cjs/ui/comment-composer.js.map +2 -2
- package/dist-cjs/ui/comment-pin.js +24 -2
- package/dist-cjs/ui/comment-pin.js.map +2 -2
- package/dist-cjs/ui/comment-thread.js +9 -7
- package/dist-cjs/ui/comment-thread.js.map +2 -2
- package/dist-cjs/ui/comments-list.js +30 -18
- package/dist-cjs/ui/comments-list.js.map +2 -2
- package/dist-cjs/ui/count-badge.js +3 -2
- package/dist-cjs/ui/count-badge.js.map +2 -2
- package/dist-cjs/ui/emoji-picker.js +54 -0
- package/dist-cjs/ui/emoji-picker.js.map +7 -0
- package/dist-cjs/ui/empty-state.js +3 -3
- package/dist-cjs/ui/empty-state.js.map +2 -2
- package/dist-cjs/ui/format-time.js +4 -1
- package/dist-cjs/ui/format-time.js.map +2 -2
- package/dist-cjs/ui/reaction-picker.js +73 -0
- package/dist-cjs/ui/reaction-picker.js.map +7 -0
- package/dist-cjs/ui/reaction.js +59 -6
- package/dist-cjs/ui/reaction.js.map +2 -2
- package/dist-cjs/ui/reactions.js +20 -7
- package/dist-cjs/ui/reactions.js.map +2 -2
- package/dist-cjs/ui/{mention.js → reply-count.js} +9 -11
- package/dist-cjs/ui/reply-count.js.map +7 -0
- package/dist-cjs/ui/send-button.js +20 -1
- package/dist-cjs/ui/send-button.js.map +2 -2
- package/dist-cjs/ui/{comment-text.js → tooltip-button.js} +12 -9
- package/dist-cjs/ui/tooltip-button.js.map +7 -0
- package/dist-esm/canvas/anchor-lifecycle.mjs +128 -0
- package/dist-esm/canvas/anchor-lifecycle.mjs.map +7 -0
- package/dist-esm/canvas/cluster-input.mjs +2 -2
- package/dist-esm/canvas/cluster-input.mjs.map +2 -2
- package/dist-esm/canvas/comment-body.mjs +1 -1
- package/dist-esm/canvas/comment-body.mjs.map +2 -2
- package/dist-esm/canvas/comment-drafts.mjs +34 -0
- package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
- package/dist-esm/canvas/comment-mutations.mjs +103 -0
- package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
- package/dist-esm/canvas/comment-reactions.mjs +152 -0
- package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
- package/dist-esm/canvas/comment-render.mjs +5 -3
- package/dist-esm/canvas/comment-render.mjs.map +2 -2
- package/dist-esm/canvas/comment-store.mjs +19 -9
- package/dist-esm/canvas/comment-store.mjs.map +2 -2
- package/dist-esm/canvas/comment-tool.mjs +67 -17
- package/dist-esm/canvas/comment-tool.mjs.map +2 -2
- package/dist-esm/canvas/comments-filter-menu.mjs +20 -38
- package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overflow-menu.mjs +23 -27
- package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overlay.mjs +450 -479
- package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
- package/dist-esm/canvas/comments-sidebar.mjs +38 -35
- package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
- package/dist-esm/canvas/comments-visibility-toggle.mjs +45 -0
- package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
- package/dist-esm/canvas/context.mjs +1 -0
- package/dist-esm/canvas/context.mjs.map +7 -0
- package/dist-esm/canvas/hooks.mjs +28 -5
- package/dist-esm/canvas/hooks.mjs.map +2 -2
- package/dist-esm/canvas/options.mjs +21 -3
- package/dist-esm/canvas/options.mjs.map +2 -2
- package/dist-esm/canvas/pin-stacking.mjs +36 -0
- package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
- package/dist-esm/canvas/sidebar-filters.mjs +2 -2
- package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
- package/dist-esm/canvas/state.mjs +29 -19
- package/dist-esm/canvas/state.mjs.map +2 -2
- package/dist-esm/canvas/thread-preview.mjs +170 -0
- package/dist-esm/canvas/thread-preview.mjs.map +7 -0
- package/dist-esm/canvas/thread-stack.mjs +190 -0
- package/dist-esm/canvas/thread-stack.mjs.map +7 -0
- package/dist-esm/canvas/thread-state.mjs +71 -22
- package/dist-esm/canvas/thread-state.mjs.map +2 -2
- package/dist-esm/canvas/thread-view.mjs +402 -0
- package/dist-esm/canvas/thread-view.mjs.map +7 -0
- package/dist-esm/clustering/computeClusterTable.mjs +4 -4
- package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
- package/dist-esm/clustering/runtime.mjs +119 -7
- package/dist-esm/clustering/runtime.mjs.map +2 -2
- package/dist-esm/index.d.mts +763 -333
- package/dist-esm/index.mjs +87 -41
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/ui/byline.mjs +6 -4
- package/dist-esm/ui/byline.mjs.map +2 -2
- package/dist-esm/ui/comment-card.mjs +15 -8
- package/dist-esm/ui/comment-card.mjs.map +2 -2
- package/dist-esm/ui/comment-composer.mjs +148 -36
- package/dist-esm/ui/comment-composer.mjs.map +2 -2
- package/dist-esm/ui/comment-pin.mjs +24 -2
- package/dist-esm/ui/comment-pin.mjs.map +2 -2
- package/dist-esm/ui/comment-thread.mjs +9 -7
- package/dist-esm/ui/comment-thread.mjs.map +2 -2
- package/dist-esm/ui/comments-list.mjs +43 -29
- package/dist-esm/ui/comments-list.mjs.map +2 -2
- package/dist-esm/ui/count-badge.mjs +3 -2
- package/dist-esm/ui/count-badge.mjs.map +2 -2
- package/dist-esm/ui/emoji-picker.mjs +34 -0
- package/dist-esm/ui/emoji-picker.mjs.map +7 -0
- package/dist-esm/ui/empty-state.mjs +3 -3
- package/dist-esm/ui/empty-state.mjs.map +2 -2
- package/dist-esm/ui/format-time.mjs +4 -1
- package/dist-esm/ui/format-time.mjs.map +2 -2
- package/dist-esm/ui/reaction-picker.mjs +60 -0
- package/dist-esm/ui/reaction-picker.mjs.map +7 -0
- package/dist-esm/ui/reaction.mjs +60 -7
- package/dist-esm/ui/reaction.mjs.map +2 -2
- package/dist-esm/ui/reactions.mjs +21 -8
- package/dist-esm/ui/reactions.mjs.map +2 -2
- package/dist-esm/ui/reply-count.mjs +9 -0
- package/dist-esm/ui/reply-count.mjs.map +7 -0
- package/dist-esm/ui/send-button.mjs +20 -1
- package/dist-esm/ui/send-button.mjs.map +2 -2
- package/dist-esm/ui/tooltip-button.mjs +12 -0
- package/dist-esm/ui/tooltip-button.mjs.map +7 -0
- package/package.json +6 -6
- package/src/canvas/anchor-lifecycle.test.ts +290 -0
- package/src/canvas/anchor-lifecycle.ts +205 -0
- package/src/canvas/canvas.css +461 -46
- package/src/canvas/cluster-input.test.ts +31 -23
- package/src/canvas/cluster-input.ts +3 -4
- package/src/canvas/comment-body.tsx +1 -1
- package/src/canvas/comment-drafts.test.ts +55 -0
- package/src/canvas/comment-drafts.ts +46 -0
- package/src/canvas/comment-mutations.test.ts +410 -0
- package/src/canvas/comment-mutations.ts +288 -0
- package/src/canvas/comment-reactions.test.ts +132 -0
- package/src/canvas/comment-reactions.tsx +245 -0
- package/src/canvas/comment-render.test.ts +1 -1
- package/src/canvas/comment-render.ts +9 -5
- package/src/canvas/comment-store.ts +67 -27
- package/src/canvas/comment-tool.test.ts +162 -0
- package/src/canvas/comment-tool.tsx +105 -23
- package/src/canvas/comments-filter-menu.tsx +23 -32
- package/src/canvas/comments-overflow-menu.tsx +14 -21
- package/src/canvas/comments-overlay.tsx +695 -622
- package/src/canvas/comments-sidebar.test.ts +45 -0
- package/src/canvas/comments-sidebar.tsx +76 -58
- package/src/canvas/comments-visibility-toggle.tsx +49 -0
- package/src/canvas/context.ts +47 -0
- package/src/canvas/hooks.ts +52 -11
- package/src/canvas/options.test.ts +52 -2
- package/src/canvas/options.ts +133 -9
- package/src/canvas/pin-stacking.test.ts +114 -0
- package/src/canvas/pin-stacking.ts +57 -0
- package/src/canvas/sidebar-filters.ts +4 -3
- package/src/canvas/state.ts +92 -35
- package/src/canvas/thread-preview.test.ts +91 -0
- package/src/canvas/thread-preview.tsx +304 -0
- package/src/canvas/thread-stack.tsx +252 -0
- package/src/canvas/thread-state.test.ts +209 -0
- package/src/canvas/thread-state.ts +137 -36
- package/src/canvas/thread-view.tsx +516 -0
- package/src/clustering/computeClusterTable.test.ts +24 -23
- package/src/clustering/computeClusterTable.ts +5 -5
- package/src/clustering/runtime.test.ts +217 -7
- package/src/clustering/runtime.ts +171 -17
- package/src/clustering/types.ts +9 -9
- package/src/index.ts +76 -40
- package/src/ui/byline.tsx +7 -5
- package/src/ui/comment-card.tsx +20 -10
- package/src/ui/comment-composer.tsx +206 -40
- package/src/ui/comment-pin.tsx +26 -2
- package/src/ui/comment-thread.tsx +13 -6
- package/src/ui/comments-list.test.ts +31 -0
- package/src/ui/comments-list.tsx +45 -21
- package/src/ui/comments.css +566 -281
- package/src/ui/count-badge.tsx +9 -2
- package/src/ui/emoji-picker.test.ts +25 -0
- package/src/ui/emoji-picker.tsx +76 -0
- package/src/ui/empty-state.tsx +3 -3
- package/src/ui/format-time.ts +7 -2
- package/src/ui/reaction-picker.tsx +109 -0
- package/src/ui/reaction.tsx +121 -6
- package/src/ui/reactions.tsx +69 -10
- package/src/ui/reply-count.ts +16 -0
- package/src/ui/send-button.tsx +18 -3
- package/src/ui/tooltip-button.tsx +20 -0
- package/dist-cjs/canvas/region-options.js +0 -49
- package/dist-cjs/canvas/region-options.js.map +0 -7
- package/dist-cjs/ui/avatar.js +0 -43
- package/dist-cjs/ui/avatar.js.map +0 -7
- package/dist-cjs/ui/comment-mention.js +0 -42
- package/dist-cjs/ui/comment-mention.js.map +0 -7
- package/dist-cjs/ui/comment-text.js.map +0 -7
- package/dist-cjs/ui/mention-list.js +0 -70
- package/dist-cjs/ui/mention-list.js.map +0 -7
- package/dist-cjs/ui/mention-suggestion.js +0 -206
- package/dist-cjs/ui/mention-suggestion.js.map +0 -7
- package/dist-cjs/ui/mention.js.map +0 -7
- package/dist-cjs/ui/render-markdown.js +0 -63
- package/dist-cjs/ui/render-markdown.js.map +0 -7
- package/dist-esm/canvas/region-options.mjs +0 -29
- package/dist-esm/canvas/region-options.mjs.map +0 -7
- package/dist-esm/ui/avatar.mjs +0 -23
- package/dist-esm/ui/avatar.mjs.map +0 -7
- package/dist-esm/ui/comment-mention.mjs +0 -22
- package/dist-esm/ui/comment-mention.mjs.map +0 -7
- package/dist-esm/ui/comment-text.mjs +0 -9
- package/dist-esm/ui/comment-text.mjs.map +0 -7
- package/dist-esm/ui/mention-list.mjs +0 -50
- package/dist-esm/ui/mention-list.mjs.map +0 -7
- package/dist-esm/ui/mention-suggestion.mjs +0 -186
- package/dist-esm/ui/mention-suggestion.mjs.map +0 -7
- package/dist-esm/ui/mention.mjs +0 -11
- package/dist-esm/ui/mention.mjs.map +0 -7
- package/dist-esm/ui/render-markdown.mjs +0 -45
- package/dist-esm/ui/render-markdown.mjs.map +0 -7
- package/src/canvas/region-options.ts +0 -57
- package/src/ui/avatar.tsx +0 -31
- package/src/ui/comment-mention.ts +0 -47
- package/src/ui/comment-text.tsx +0 -12
- package/src/ui/mention-list.tsx +0 -106
- package/src/ui/mention-suggestion.test.ts +0 -18
- package/src/ui/mention-suggestion.tsx +0 -285
- package/src/ui/mention.tsx +0 -9
- package/src/ui/render-markdown.tsx +0 -72
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { mergeAttributes } from "@tiptap/core";
|
|
2
|
-
import { Mention } from "@tiptap/extension-mention";
|
|
3
|
-
function mentionName(attrs, resolveName) {
|
|
4
|
-
const resolved = attrs.id && resolveName ? resolveName(attrs.id) : void 0;
|
|
5
|
-
return resolved ?? attrs.label ?? attrs.id ?? "";
|
|
6
|
-
}
|
|
7
|
-
function commentMention({ resolveName, suggestion } = {}) {
|
|
8
|
-
return Mention.configure({
|
|
9
|
-
HTMLAttributes: { class: "cmt-mention" },
|
|
10
|
-
renderText: ({ node }) => `@${mentionName(node.attrs, resolveName)}`,
|
|
11
|
-
renderHTML: ({ node, options }) => [
|
|
12
|
-
"span",
|
|
13
|
-
mergeAttributes(options.HTMLAttributes, { "data-mention-id": node.attrs.id }),
|
|
14
|
-
`@${mentionName(node.attrs, resolveName)}`
|
|
15
|
-
],
|
|
16
|
-
...(suggestion ? { suggestion } : {})
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
export {
|
|
20
|
-
commentMention
|
|
21
|
-
};
|
|
22
|
-
//# sourceMappingURL=comment-mention.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/ui/comment-mention.ts"],
|
|
4
|
-
"sourcesContent": ["import { mergeAttributes } from '@tiptap/core'\nimport { Mention, type MentionNodeAttrs, type MentionOptions } from '@tiptap/extension-mention'\n\n/**\n * A mention node's display name: the member's current name, resolved live from its id when a\n * resolver is given (so a rename shows through), falling back to the label captured at insert time\n * when no source can name the id \u2014 e.g. a deleted account. The stored id is always the source of\n * truth; live resolution only freshens the displayed name over that captured label.\n */\nfunction mentionName(\n\tattrs: MentionNodeAttrs,\n\tresolveName?: (id: string) => string | undefined\n): string {\n\tconst resolved = attrs.id && resolveName ? resolveName(attrs.id) : undefined\n\treturn resolved ?? attrs.label ?? attrs.id ?? ''\n}\n\nexport interface CommentMentionOptions {\n\t/**\n\t * Resolve a member id to its current display name \u2014 for the read-only render paths. Returns\n\t * `undefined` when the id can't be resolved, so the render falls back to the mention's stored label.\n\t */\n\tresolveName?(id: string): string | undefined\n\t/** The `@`-picker suggestion config \u2014 for the composer (omit on the render paths). */\n\tsuggestion?: MentionOptions['suggestion']\n}\n\n/**\n * The comment @-mention node \u2014 TipTap's `Mention` configured to render as a `.cmt-mention` pill.\n *\n * A factory rather than a shared constant because it's configured differently per context: the\n * read-only render paths pass `resolveName` (so the stored `{ id }` node always shows the member's\n * current name, not a copy frozen at insert time), while the composer passes a `suggestion` (the\n * `@` picker). The node schema is identical either way \u2014 only these two levers differ.\n */\nexport function commentMention({ resolveName, suggestion }: CommentMentionOptions = {}) {\n\treturn Mention.configure({\n\t\tHTMLAttributes: { class: 'cmt-mention' },\n\t\trenderText: ({ node }) => `@${mentionName(node.attrs as MentionNodeAttrs, resolveName)}`,\n\t\trenderHTML: ({ node, options }) => [\n\t\t\t'span',\n\t\t\tmergeAttributes(options.HTMLAttributes, { 'data-mention-id': node.attrs.id }),\n\t\t\t`@${mentionName(node.attrs as MentionNodeAttrs, resolveName)}`,\n\t\t],\n\t\t...(suggestion ? { suggestion } : {}),\n\t})\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,uBAAuB;AAChC,SAAS,eAA2D;AAQpE,SAAS,YACR,OACA,aACS;AACT,QAAM,WAAW,MAAM,MAAM,cAAc,YAAY,MAAM,EAAE,IAAI;AACnE,SAAO,YAAY,MAAM,SAAS,MAAM,MAAM;AAC/C;AAoBO,SAAS,eAAe,EAAE,aAAa,WAAW,IAA2B,CAAC,GAAG;AACvF,SAAO,QAAQ,UAAU;AAAA,IACxB,gBAAgB,EAAE,OAAO,cAAc;AAAA,IACvC,YAAY,CAAC,EAAE,KAAK,MAAM,IAAI,YAAY,KAAK,OAA2B,WAAW,CAAC;AAAA,IACtF,YAAY,CAAC,EAAE,MAAM,QAAQ,MAAM;AAAA,MAClC;AAAA,MACA,gBAAgB,QAAQ,gBAAgB,EAAE,mBAAmB,KAAK,MAAM,GAAG,CAAC;AAAA,MAC5E,IAAI,YAAY,KAAK,OAA2B,WAAW,CAAC;AAAA,IAC7D;AAAA,IACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACpC,CAAC;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { renderMarkdown } from "./render-markdown.mjs";
|
|
3
|
-
function CommentText({ text }) {
|
|
4
|
-
return /* @__PURE__ */ jsx("div", { className: "cmt-text", children: renderMarkdown(text) });
|
|
5
|
-
}
|
|
6
|
-
export {
|
|
7
|
-
CommentText
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=comment-text.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/ui/comment-text.tsx"],
|
|
4
|
-
"sourcesContent": ["import { renderMarkdown } from './render-markdown'\n\n/** @public */\nexport interface CommentTextProps {\n\t/** Rich text body, authored as markdown. */\n\ttext: string\n}\n\n/** A comment's body, rendered as markdown (bold, italic, code, links, lists). @public @react */\nexport function CommentText({ text }: CommentTextProps) {\n\treturn <div className=\"cmt-text\">{renderMarkdown(text)}</div>\n}\n"],
|
|
5
|
-
"mappings": "AAUQ;AAVR,SAAS,sBAAsB;AASxB,SAAS,YAAY,EAAE,KAAK,GAAqB;AACvD,SAAO,oBAAC,SAAI,WAAU,YAAY,yBAAe,IAAI,GAAE;AACxD;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useTranslation } from "tldraw";
|
|
3
|
-
import { Avatar } from "./avatar.mjs";
|
|
4
|
-
function DefaultMemberRow({ member }) {
|
|
5
|
-
const msg = useTranslation();
|
|
6
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7
|
-
/* @__PURE__ */ jsx(Avatar, { name: member.name, color: member.color, image: member.avatar }),
|
|
8
|
-
/* @__PURE__ */ jsxs("span", { className: "cmt-mention-list__text", children: [
|
|
9
|
-
/* @__PURE__ */ jsxs("span", { className: "cmt-mention-list__name", children: [
|
|
10
|
-
member.name,
|
|
11
|
-
member.you && /* @__PURE__ */ jsx("span", { className: "cmt-mention-list__you", children: `(${msg("comments.mention-you")})` })
|
|
12
|
-
] }),
|
|
13
|
-
member.secondary && /* @__PURE__ */ jsx("span", { className: "cmt-mention-list__secondary", children: member.secondary })
|
|
14
|
-
] })
|
|
15
|
-
] });
|
|
16
|
-
}
|
|
17
|
-
function MentionList({
|
|
18
|
-
members,
|
|
19
|
-
activeIndex = 0,
|
|
20
|
-
onSelect,
|
|
21
|
-
emptyLabel,
|
|
22
|
-
renderMember
|
|
23
|
-
}) {
|
|
24
|
-
const msg = useTranslation();
|
|
25
|
-
if (members.length === 0) {
|
|
26
|
-
return /* @__PURE__ */ jsx("div", { className: "cmt-mention-list cmt-mention-list--empty", children: emptyLabel ?? msg("comments.mention-no-matches") });
|
|
27
|
-
}
|
|
28
|
-
return /* @__PURE__ */ jsx("div", { className: "cmt-mention-list", role: "listbox", children: members.map((member, i) => {
|
|
29
|
-
const active = i === activeIndex;
|
|
30
|
-
return /* @__PURE__ */ jsx(
|
|
31
|
-
"button",
|
|
32
|
-
{
|
|
33
|
-
type: "button",
|
|
34
|
-
role: "option",
|
|
35
|
-
"aria-selected": active,
|
|
36
|
-
className: active ? "cmt-mention-list__item cmt-mention-list__item--active" : "cmt-mention-list__item",
|
|
37
|
-
onMouseDown: (e) => e.preventDefault(),
|
|
38
|
-
onClick: () => {
|
|
39
|
-
if (onSelect) onSelect(member);
|
|
40
|
-
},
|
|
41
|
-
children: renderMember ? renderMember(member) : /* @__PURE__ */ jsx(DefaultMemberRow, { member })
|
|
42
|
-
},
|
|
43
|
-
member.id
|
|
44
|
-
);
|
|
45
|
-
}) });
|
|
46
|
-
}
|
|
47
|
-
export {
|
|
48
|
-
MentionList
|
|
49
|
-
};
|
|
50
|
-
//# sourceMappingURL=mention-list.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/ui/mention-list.tsx"],
|
|
4
|
-
"sourcesContent": ["import { ReactNode } from 'react'\nimport { useTranslation } from 'tldraw'\nimport { Avatar } from './avatar'\n\n/** A person the composer's \\@-mention picker can offer. @public */\nexport interface MentionMember {\n\tid: string\n\tname: string\n\t/** Avatar image URL. Falls back to a coloured initial when omitted. */\n\tavatar?: string\n\t/** Avatar background colour, used when there's no `avatar` image. */\n\tcolor?: string\n\t/** A secondary line under the name \u2014 e.g. an email or handle. Omit for a single-line row. */\n\tsecondary?: string\n\t/** Marks the current user; shown as a \"(You)\" suffix after the name. */\n\tyou?: boolean\n\t/** Hosts may carry extra fields; a custom `renderMember` can read them. */\n\t[key: string]: unknown\n}\n\n/** @public */\nexport interface MentionListProps {\n\t/** Members to choose from \u2014 already resolved for the current query by the host. */\n\tmembers: MentionMember[]\n\t/** Index of the highlighted row, driven by the composer's keyboard navigation. */\n\tactiveIndex?: number\n\t/** Called when a member is chosen (click, or Enter on the active row). */\n\tonSelect?(member: MentionMember): void\n\t/** Shown when no member matches the query. Defaults to a translated \"No matches\". */\n\temptyLabel?: string\n\t/** Override a row's content (inside the selectable button). Defaults to avatar + name (+ secondary). */\n\trenderMember?(member: MentionMember): ReactNode\n}\n\n/** The default row content: avatar, name (with a \"(You)\" marker), and an optional secondary line. */\nfunction DefaultMemberRow({ member }: { member: MentionMember }) {\n\tconst msg = useTranslation()\n\treturn (\n\t\t<>\n\t\t\t<Avatar name={member.name} color={member.color} image={member.avatar} />\n\t\t\t<span className=\"cmt-mention-list__text\">\n\t\t\t\t<span className=\"cmt-mention-list__name\">\n\t\t\t\t\t{member.name}\n\t\t\t\t\t{member.you && (\n\t\t\t\t\t\t<span className=\"cmt-mention-list__you\">{`(${msg('comments.mention-you')})`}</span>\n\t\t\t\t\t)}\n\t\t\t\t</span>\n\t\t\t\t{member.secondary && (\n\t\t\t\t\t<span className=\"cmt-mention-list__secondary\">{member.secondary}</span>\n\t\t\t\t)}\n\t\t\t</span>\n\t\t</>\n\t)\n}\n\n/**\n * The \\@-mention member picker: a popover list of people to mention, shown as the user types after\n * `@`. Presentational \u2014 the composer's suggestion plugin owns resolution, keyboard navigation, and\n * placement, and drives this with the host-resolved `members` and the highlighted `activeIndex`.\n * @public @react\n */\nexport function MentionList({\n\tmembers,\n\tactiveIndex = 0,\n\tonSelect,\n\temptyLabel,\n\trenderMember,\n}: MentionListProps) {\n\tconst msg = useTranslation()\n\tif (members.length === 0) {\n\t\treturn (\n\t\t\t<div className=\"cmt-mention-list cmt-mention-list--empty\">\n\t\t\t\t{emptyLabel ?? msg('comments.mention-no-matches')}\n\t\t\t</div>\n\t\t)\n\t}\n\treturn (\n\t\t<div className=\"cmt-mention-list\" role=\"listbox\">\n\t\t\t{members.map((member, i) => {\n\t\t\t\tconst active = i === activeIndex\n\t\t\t\treturn (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={member.id}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"option\"\n\t\t\t\t\t\taria-selected={active}\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\tactive\n\t\t\t\t\t\t\t\t? 'cmt-mention-list__item cmt-mention-list__item--active'\n\t\t\t\t\t\t\t\t: 'cmt-mention-list__item'\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Keep the composer focused on a row press: it's portaled outside the editor, so a\n\t\t\t\t\t\t// bare mousedown would blur the composer \u2014 which now dismisses the picker \u2014 before\n\t\t\t\t\t\t// the click could select. Selection runs on click; focus stays put.\n\t\t\t\t\t\tonMouseDown={(e) => e.preventDefault()}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tif (onSelect) onSelect(member)\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{renderMember ? renderMember(member) : <DefaultMemberRow member={member} />}\n\t\t\t\t\t</button>\n\t\t\t\t)\n\t\t\t})}\n\t\t</div>\n\t)\n}\n"],
|
|
5
|
-
"mappings": "AAsCE,mBACC,KAEC,YAHF;AArCF,SAAS,sBAAsB;AAC/B,SAAS,cAAc;AAiCvB,SAAS,iBAAiB,EAAE,OAAO,GAA8B;AAChE,QAAM,MAAM,eAAe;AAC3B,SACC,iCACC;AAAA,wBAAC,UAAO,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,OAAO,OAAO,QAAQ;AAAA,IACtE,qBAAC,UAAK,WAAU,0BACf;AAAA,2BAAC,UAAK,WAAU,0BACd;AAAA,eAAO;AAAA,QACP,OAAO,OACP,oBAAC,UAAK,WAAU,yBAAyB,cAAI,IAAI,sBAAsB,CAAC,KAAI;AAAA,SAE9E;AAAA,MACC,OAAO,aACP,oBAAC,UAAK,WAAU,+BAA+B,iBAAO,WAAU;AAAA,OAElE;AAAA,KACD;AAEF;AAQO,SAAS,YAAY;AAAA,EAC3B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACD,GAAqB;AACpB,QAAM,MAAM,eAAe;AAC3B,MAAI,QAAQ,WAAW,GAAG;AACzB,WACC,oBAAC,SAAI,WAAU,4CACb,wBAAc,IAAI,6BAA6B,GACjD;AAAA,EAEF;AACA,SACC,oBAAC,SAAI,WAAU,oBAAmB,MAAK,WACrC,kBAAQ,IAAI,CAAC,QAAQ,MAAM;AAC3B,UAAM,SAAS,MAAM;AACrB,WACC;AAAA,MAAC;AAAA;AAAA,QAEA,MAAK;AAAA,QACL,MAAK;AAAA,QACL,iBAAe;AAAA,QACf,WACC,SACG,0DACA;AAAA,QAKJ,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM;AACd,cAAI,SAAU,UAAS,MAAM;AAAA,QAC9B;AAAA,QAEC,yBAAe,aAAa,MAAM,IAAI,oBAAC,oBAAiB,QAAgB;AAAA;AAAA,MAjBpE,OAAO;AAAA,IAkBb;AAAA,EAEF,CAAC,GACF;AAEF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { ReactRenderer } from "@tiptap/react";
|
|
3
|
-
import { forwardRef, useImperativeHandle, useState } from "react";
|
|
4
|
-
import { atom, react } from "tldraw";
|
|
5
|
-
import { MentionList } from "./mention-list.mjs";
|
|
6
|
-
const MentionPopup = forwardRef(function MentionPopup2({ items, command, renderMember }, ref) {
|
|
7
|
-
const [activeIndex, setActiveIndex] = useState(0);
|
|
8
|
-
const [prevItems, setPrevItems] = useState(items);
|
|
9
|
-
if (items !== prevItems) {
|
|
10
|
-
setPrevItems(items);
|
|
11
|
-
setActiveIndex(0);
|
|
12
|
-
}
|
|
13
|
-
const select = (member) => {
|
|
14
|
-
if (member) command({ id: member.id, label: member.name });
|
|
15
|
-
};
|
|
16
|
-
useImperativeHandle(ref, () => ({
|
|
17
|
-
onKeyDown: ({ event }) => {
|
|
18
|
-
if (items.length === 0) return false;
|
|
19
|
-
if (event.key === "ArrowUp") {
|
|
20
|
-
setActiveIndex((i) => (i + items.length - 1) % items.length);
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
if (event.key === "ArrowDown") {
|
|
24
|
-
setActiveIndex((i) => (i + 1) % items.length);
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
if (event.key === "Enter" || event.key === "Tab") {
|
|
28
|
-
select(items[activeIndex] ?? items[0]);
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}));
|
|
34
|
-
return /* @__PURE__ */ jsx(
|
|
35
|
-
MentionList,
|
|
36
|
-
{
|
|
37
|
-
members: items,
|
|
38
|
-
activeIndex,
|
|
39
|
-
onSelect: select,
|
|
40
|
-
renderMember
|
|
41
|
-
}
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
const MAX_SUGGESTIONS = 8;
|
|
45
|
-
function filterMentionMembers(members, query) {
|
|
46
|
-
const q = query.toLowerCase();
|
|
47
|
-
return members.filter((m) => m.name.toLowerCase().includes(q)).slice(0, MAX_SUGGESTIONS);
|
|
48
|
-
}
|
|
49
|
-
const mentionPickerOpen = atom("isMentionPickerOpen", false);
|
|
50
|
-
function isMentionPickerOpen() {
|
|
51
|
-
return mentionPickerOpen.get();
|
|
52
|
-
}
|
|
53
|
-
function createMentionSuggestion(getSuggestions, options = {}) {
|
|
54
|
-
return {
|
|
55
|
-
char: "@",
|
|
56
|
-
items: ({ query }) => getSuggestions(query),
|
|
57
|
-
render: () => {
|
|
58
|
-
let renderer = null;
|
|
59
|
-
let container = null;
|
|
60
|
-
let editorEl = null;
|
|
61
|
-
let canvasEl = null;
|
|
62
|
-
let stopCameraReaction = null;
|
|
63
|
-
let anchorPage = null;
|
|
64
|
-
let popupWidth = 0;
|
|
65
|
-
const applyScreen = (left, top, width) => {
|
|
66
|
-
if (!container) return;
|
|
67
|
-
container.style.left = `${left}px`;
|
|
68
|
-
container.style.top = `${top + 4}px`;
|
|
69
|
-
container.style.width = `${width}px`;
|
|
70
|
-
};
|
|
71
|
-
const place = () => {
|
|
72
|
-
if (!container || !editorEl || container.style.display === "none") return;
|
|
73
|
-
const field = editorEl.closest(".cmt-composer__field") ?? editorEl;
|
|
74
|
-
const rect = field.getBoundingClientRect();
|
|
75
|
-
popupWidth = rect.width;
|
|
76
|
-
anchorPage = options.editor?.screenToPage({ x: rect.left, y: rect.bottom }) ?? null;
|
|
77
|
-
applyScreen(rect.left, rect.bottom, rect.width);
|
|
78
|
-
};
|
|
79
|
-
const reposition = () => {
|
|
80
|
-
if (!anchorPage || !options.editor) return;
|
|
81
|
-
const s = options.editor.pageToScreen(anchorPage);
|
|
82
|
-
applyScreen(s.x, s.y, popupWidth);
|
|
83
|
-
};
|
|
84
|
-
const startFollowing = () => {
|
|
85
|
-
window.addEventListener("scroll", place, true);
|
|
86
|
-
window.addEventListener("resize", place);
|
|
87
|
-
if (options.editor) {
|
|
88
|
-
stopCameraReaction = react("anchor mention popup to camera", () => {
|
|
89
|
-
options.editor.getCamera();
|
|
90
|
-
reposition();
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const stopFollowing = () => {
|
|
95
|
-
window.removeEventListener("scroll", place, true);
|
|
96
|
-
window.removeEventListener("resize", place);
|
|
97
|
-
stopCameraReaction?.();
|
|
98
|
-
stopCameraReaction = null;
|
|
99
|
-
anchorPage = null;
|
|
100
|
-
};
|
|
101
|
-
const hide = () => {
|
|
102
|
-
if (container) container.style.display = "none";
|
|
103
|
-
mentionPickerOpen.set(false);
|
|
104
|
-
};
|
|
105
|
-
const onWheel = (e) => {
|
|
106
|
-
if (e.isSpecialRedispatchedEvent || !canvasEl) return;
|
|
107
|
-
const list = container?.querySelector(".cmt-mention-list");
|
|
108
|
-
if (list && list.scrollHeight > list.clientHeight) return;
|
|
109
|
-
e.preventDefault();
|
|
110
|
-
const redispatched = new WheelEvent("wheel", e);
|
|
111
|
-
redispatched.isSpecialRedispatchedEvent = true;
|
|
112
|
-
canvasEl.dispatchEvent(redispatched);
|
|
113
|
-
};
|
|
114
|
-
return {
|
|
115
|
-
onStart: (props) => {
|
|
116
|
-
renderer = new ReactRenderer(MentionPopup, {
|
|
117
|
-
props: {
|
|
118
|
-
items: props.items,
|
|
119
|
-
command: props.command,
|
|
120
|
-
renderMember: options.renderMember
|
|
121
|
-
},
|
|
122
|
-
editor: props.editor
|
|
123
|
-
});
|
|
124
|
-
editorEl = props.editor.view.dom;
|
|
125
|
-
editorEl.addEventListener("blur", hide);
|
|
126
|
-
container = document.createElement("div");
|
|
127
|
-
container.className = "cmt-mention-popup";
|
|
128
|
-
container.appendChild(renderer.element);
|
|
129
|
-
const themed = editorEl.closest(".tl-container");
|
|
130
|
-
(themed ?? document.body).appendChild(container);
|
|
131
|
-
canvasEl = themed?.querySelector(".tl-canvas") ?? null;
|
|
132
|
-
container.addEventListener("wheel", onWheel, { passive: false });
|
|
133
|
-
place();
|
|
134
|
-
startFollowing();
|
|
135
|
-
mentionPickerOpen.set(true);
|
|
136
|
-
},
|
|
137
|
-
onUpdate: (props) => {
|
|
138
|
-
if (renderer)
|
|
139
|
-
renderer.updateProps({
|
|
140
|
-
items: props.items,
|
|
141
|
-
command: props.command,
|
|
142
|
-
renderMember: options.renderMember
|
|
143
|
-
});
|
|
144
|
-
if (container) container.style.display = "";
|
|
145
|
-
mentionPickerOpen.set(true);
|
|
146
|
-
place();
|
|
147
|
-
},
|
|
148
|
-
onKeyDown: (props) => {
|
|
149
|
-
if (!isMentionPickerOpen()) return false;
|
|
150
|
-
if (props.event.key === "Escape") {
|
|
151
|
-
hide();
|
|
152
|
-
props.event.stopPropagation();
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
if (props.event.key === "Enter" || props.event.key === "Tab") {
|
|
156
|
-
const completed = renderer?.ref?.onKeyDown(props) ?? false;
|
|
157
|
-
if (!completed) {
|
|
158
|
-
hide();
|
|
159
|
-
props.event.stopPropagation();
|
|
160
|
-
}
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
if (renderer && renderer.ref) return renderer.ref.onKeyDown(props);
|
|
164
|
-
return false;
|
|
165
|
-
},
|
|
166
|
-
onExit: () => {
|
|
167
|
-
stopFollowing();
|
|
168
|
-
if (editorEl) editorEl.removeEventListener("blur", hide);
|
|
169
|
-
if (container) container.removeEventListener("wheel", onWheel);
|
|
170
|
-
if (container) container.remove();
|
|
171
|
-
if (renderer) renderer.destroy();
|
|
172
|
-
renderer = null;
|
|
173
|
-
container = null;
|
|
174
|
-
canvasEl = null;
|
|
175
|
-
mentionPickerOpen.set(false);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
export {
|
|
182
|
-
createMentionSuggestion,
|
|
183
|
-
filterMentionMembers,
|
|
184
|
-
isMentionPickerOpen
|
|
185
|
-
};
|
|
186
|
-
//# sourceMappingURL=mention-suggestion.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/ui/mention-suggestion.tsx"],
|
|
4
|
-
"sourcesContent": ["import type { MentionNodeAttrs } from '@tiptap/extension-mention'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionKeyDownProps, SuggestionOptions } from '@tiptap/suggestion'\nimport { type ReactNode, forwardRef, useImperativeHandle, useState } from 'react'\nimport { type Editor as TldrawEditor, atom, react } from 'tldraw'\nimport { MentionList, MentionMember } from './mention-list'\n\n/** The handle the suggestion plugin drives \u2014 it forwards navigation keys into the popup. */\nexport interface MentionPopupHandle {\n\tonKeyDown(props: SuggestionKeyDownProps): boolean\n}\n\ninterface MentionPopupProps {\n\titems: MentionMember[]\n\tcommand(attrs: MentionNodeAttrs): void\n\trenderMember?(member: MentionMember): ReactNode\n}\n\n/** The live @-picker popup: owns the highlighted index and keyboard, renders the presentational list. */\nconst MentionPopup = forwardRef<MentionPopupHandle, MentionPopupProps>(function MentionPopup(\n\t{ items, command, renderMember },\n\tref\n) {\n\tconst [activeIndex, setActiveIndex] = useState(0)\n\t// A new query yields new items; reset the highlight to the top during render \u2014 not in an effect,\n\t// which would leave a frame where `activeIndex` still points past a shrunk list and Enter selects\n\t// its (now out-of-range, undefined) item, swallowing the key without inserting a mention.\n\tconst [prevItems, setPrevItems] = useState(items)\n\tif (items !== prevItems) {\n\t\tsetPrevItems(items)\n\t\tsetActiveIndex(0)\n\t}\n\n\tconst select = (member: MentionMember | undefined) => {\n\t\tif (member) command({ id: member.id, label: member.name })\n\t}\n\n\tuseImperativeHandle(ref, () => ({\n\t\tonKeyDown: ({ event }) => {\n\t\t\tif (items.length === 0) return false\n\t\t\tif (event.key === 'ArrowUp') {\n\t\t\t\tsetActiveIndex((i) => (i + items.length - 1) % items.length)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (event.key === 'ArrowDown') {\n\t\t\t\tsetActiveIndex((i) => (i + 1) % items.length)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// Enter and Tab both complete the highlighted member (falling back to the top match so a\n\t\t\t// stale index never selects `undefined`). The empty-roster case is handled a level up, in\n\t\t\t// the suggestion's onKeyDown, which cancels the picker.\n\t\t\tif (event.key === 'Enter' || event.key === 'Tab') {\n\t\t\t\tselect(items[activeIndex] ?? items[0])\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\t}))\n\n\treturn (\n\t\t<MentionList\n\t\t\tmembers={items}\n\t\t\tactiveIndex={activeIndex}\n\t\t\tonSelect={select}\n\t\t\trenderMember={renderMember}\n\t\t/>\n\t)\n})\n\nconst MAX_SUGGESTIONS = 8\n\n/** Members whose name contains the query (case-insensitive), capped to the popup's length.\n * @public */\nexport function filterMentionMembers(members: MentionMember[], query: string): MentionMember[] {\n\tconst q = query.toLowerCase()\n\treturn members.filter((m) => m.name.toLowerCase().includes(q)).slice(0, MAX_SUGGESTIONS)\n}\n\n// A reactive flag for whether the @-picker is showing. Deliberately NOT tldraw's open-menu registry:\n// registering there mounts MenuClickCapture, which covers the canvas with a click-capture overlay to\n// make it inert while a menu is open. But the picker is an inline autocomplete, not a modal \u2014 the\n// canvas must stay pannable/zoomable beneath it \u2014 so we track \"open\" ourselves instead.\nconst mentionPickerOpen = atom('isMentionPickerOpen', false)\n\n/**\n * Whether the @-mention picker is currently showing. Host dismissal (Escape, outside-click) checks\n * this so it can defer to the picker instead of tearing down the composer or thread beneath it.\n */\nexport function isMentionPickerOpen(): boolean {\n\treturn mentionPickerOpen.get()\n}\n\n/** @public */\nexport interface MentionSuggestionOptions {\n\t/** Override a member row's content in the picker. Defaults to avatar + name (+ secondary). */\n\trenderMember?(member: MentionMember): ReactNode\n\t/**\n\t * The tldraw editor the composer lives in. When provided, the popup re-anchors reactively as the\n\t * canvas camera moves (the composer rides it) instead of polling every frame. Omit off-canvas.\n\t */\n\teditor?: TldrawEditor | null\n}\n\n/**\n * Build the TipTap `suggestion` config for the comment \\@-picker. `getSuggestions(query)` is the\n * host's resolver \u2014 it returns the members matching the query (sync or async); the SDK owns neither\n * the roster nor the filtering. The plugin runs outside React, so `render` mounts `MentionPopup` via\n * a `ReactRenderer`, forwards navigation keys through the popup's imperative handle, and lets it call\n * `command` to insert.\n * @public\n */\nexport function createMentionSuggestion(\n\tgetSuggestions: (query: string) => MentionMember[] | Promise<MentionMember[]>,\n\toptions: MentionSuggestionOptions = {}\n): Omit<SuggestionOptions<MentionMember, MentionNodeAttrs>, 'editor'> {\n\treturn {\n\t\tchar: '@',\n\t\titems: ({ query }) => getSuggestions(query),\n\t\trender: () => {\n\t\t\tlet renderer: ReactRenderer<MentionPopupHandle, MentionPopupProps> | null = null\n\t\t\tlet container: HTMLElement | null = null\n\t\t\tlet editorEl: HTMLElement | null = null\n\t\t\tlet canvasEl: Element | null = null\n\t\t\tlet stopCameraReaction: (() => void) | null = null\n\t\t\t// The composer field's top-left in page space, plus the popup's screen width. Captured on a\n\t\t\t// fresh read so camera moves can re-derive the popup's screen position from the page anchor\n\t\t\t// (see reposition) rather than the field's DOM rect.\n\t\t\tlet anchorPage: { x: number; y: number } | null = null\n\t\t\tlet popupWidth = 0\n\n\t\t\tconst applyScreen = (left: number, top: number, width: number) => {\n\t\t\t\tif (!container) return\n\t\t\t\tcontainer.style.left = `${left}px`\n\t\t\t\tcontainer.style.top = `${top + 4}px`\n\t\t\t\tcontainer.style.width = `${width}px`\n\t\t\t}\n\n\t\t\t// Fresh placement: read the field's real screen rect, position the popup flush under it (not\n\t\t\t// the caret, matching its width), and remember the field's page-space anchor for reposition.\n\t\t\tconst place = () => {\n\t\t\t\tif (!container || !editorEl || container.style.display === 'none') return\n\t\t\t\tconst field = editorEl.closest('.cmt-composer__field') ?? editorEl\n\t\t\t\tconst rect = field.getBoundingClientRect()\n\t\t\t\tpopupWidth = rect.width\n\t\t\t\tanchorPage = options.editor?.screenToPage({ x: rect.left, y: rect.bottom }) ?? null\n\t\t\t\tapplyScreen(rect.left, rect.bottom, rect.width)\n\t\t\t}\n\n\t\t\t// Re-derive the popup's screen position from the remembered page anchor \u2014 pure camera math,\n\t\t\t// always current. Reading the field's DOM rect here instead would lag by a frame: the canvas\n\t\t\t// composer re-positions itself on a React commit (a `useValue(pageToViewport(...))`), which\n\t\t\t// lands after a camera reaction runs, so the rect is still last frame's during a pan.\n\t\t\tconst reposition = () => {\n\t\t\t\tif (!anchorPage || !options.editor) return\n\t\t\t\tconst s = options.editor.pageToScreen(anchorPage)\n\t\t\t\tapplyScreen(s.x, s.y, popupWidth)\n\t\t\t}\n\n\t\t\t// The popup is `position: fixed`, but its anchor \u2014 a canvas composer \u2014 rides the camera:\n\t\t\t// panning or zooming moves the composer with no scroll/resize event to hook. Re-anchor when\n\t\t\t// the camera actually changes (via a tldraw reaction) rather than polling every frame, plus\n\t\t\t// on window scroll/resize for the off-canvas case (which re-read the field directly).\n\t\t\tconst startFollowing = () => {\n\t\t\t\twindow.addEventListener('scroll', place, true)\n\t\t\t\twindow.addEventListener('resize', place)\n\t\t\t\tif (options.editor) {\n\t\t\t\t\tstopCameraReaction = react('anchor mention popup to camera', () => {\n\t\t\t\t\t\toptions.editor!.getCamera() // track the camera so this re-runs as it moves\n\t\t\t\t\t\treposition()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst stopFollowing = () => {\n\t\t\t\twindow.removeEventListener('scroll', place, true)\n\t\t\t\twindow.removeEventListener('resize', place)\n\t\t\t\tstopCameraReaction?.()\n\t\t\t\tstopCameraReaction = null\n\t\t\t\tanchorPage = null\n\t\t\t}\n\n\t\t\t// Dismiss the roster \u2014 on Escape, or when the composer loses focus \u2014 by hiding it and\n\t\t\t// clearing the open flag, so isMentionPickerOpen() stays accurate and the thread's own\n\t\t\t// Escape/outside-click dismissal can take over. The TipTap suggestion stays active, so typing\n\t\t\t// re-shows the roster via onUpdate.\n\t\t\tconst hide = () => {\n\t\t\t\tif (container) container.style.display = 'none'\n\t\t\t\tmentionPickerOpen.set(false)\n\t\t\t}\n\n\t\t\t// Wheel/panning over the popup drives the canvas beneath it, so scrolling to pan or zoom\n\t\t\t// isn't swallowed by the roster \u2014 the same passthrough the rest of the comments UI gets from\n\t\t\t// `usePassThroughWheelEvents`. The list still scrolls itself when the roster overflows (we\n\t\t\t// only redispatch when it can't). Done imperatively because the popup lives outside React.\n\t\t\tconst onWheel = (e: WheelEvent) => {\n\t\t\t\tif ((e as any).isSpecialRedispatchedEvent || !canvasEl) return\n\t\t\t\tconst list = container?.querySelector('.cmt-mention-list')\n\t\t\t\tif (list && list.scrollHeight > list.clientHeight) return\n\t\t\t\te.preventDefault()\n\t\t\t\tconst redispatched = new WheelEvent('wheel', e)\n\t\t\t\t;(redispatched as any).isSpecialRedispatchedEvent = true\n\t\t\t\tcanvasEl.dispatchEvent(redispatched)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tonStart: (props) => {\n\t\t\t\t\trenderer = new ReactRenderer(MentionPopup, {\n\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\titems: props.items,\n\t\t\t\t\t\t\tcommand: props.command,\n\t\t\t\t\t\t\trenderMember: options.renderMember,\n\t\t\t\t\t\t},\n\t\t\t\t\t\teditor: props.editor,\n\t\t\t\t\t})\n\t\t\t\t\teditorEl = props.editor.view.dom as HTMLElement\n\t\t\t\t\t// The TipTap suggestion has no blur handling, so without this the picker would stay\n\t\t\t\t\t// \"open\" (and the thread would defer its Escape to it) after focus moved away from the\n\t\t\t\t\t// composer \u2014 leaving Escape a no-op and the roster stuck on screen.\n\t\t\t\t\teditorEl.addEventListener('blur', hide)\n\t\t\t\t\tcontainer = document.createElement('div')\n\t\t\t\t\tcontainer.className = 'cmt-mention-popup'\n\t\t\t\t\tcontainer.appendChild(renderer.element)\n\t\t\t\t\t// Mount inside the tldraw container so the popup inherits the theme variables\n\t\t\t\t\t// (--tl-color-*); portaling to document.body would strip them and lose the panel.\n\t\t\t\t\tconst themed = editorEl.closest('.tl-container')\n\t\t\t\t\t;(themed ?? document.body).appendChild(container)\n\t\t\t\t\tcanvasEl = themed?.querySelector('.tl-canvas') ?? null\n\t\t\t\t\tcontainer.addEventListener('wheel', onWheel, { passive: false })\n\t\t\t\t\tplace()\n\t\t\t\t\tstartFollowing()\n\t\t\t\t\tmentionPickerOpen.set(true)\n\t\t\t\t},\n\t\t\t\tonUpdate: (props) => {\n\t\t\t\t\tif (renderer)\n\t\t\t\t\t\trenderer.updateProps({\n\t\t\t\t\t\t\titems: props.items,\n\t\t\t\t\t\t\tcommand: props.command,\n\t\t\t\t\t\t\trenderMember: options.renderMember,\n\t\t\t\t\t\t})\n\t\t\t\t\t// Typing after an Escape re-shows the roster.\n\t\t\t\t\tif (container) container.style.display = ''\n\t\t\t\t\tmentionPickerOpen.set(true)\n\t\t\t\t\tplace()\n\t\t\t\t},\n\t\t\t\tonKeyDown: (props) => {\n\t\t\t\t\t// Once the roster is hidden (a prior Escape), the TipTap suggestion is still active but\n\t\t\t\t\t// this handler goes inert \u2014 every key passes through to the composer/thread (so a second\n\t\t\t\t\t// Escape closes them, and Arrow/Enter don't select from an invisible list). Typing\n\t\t\t\t\t// re-shows the roster via onUpdate.\n\t\t\t\t\tif (!isMentionPickerOpen()) return false\n\t\t\t\t\tif (props.event.key === 'Escape') {\n\t\t\t\t\t\t// Dismiss only the roster: hide it and stop the key so the composer/thread beneath\n\t\t\t\t\t\t// doesn't also treat Escape as \"abandon\".\n\t\t\t\t\t\thide()\n\t\t\t\t\t\tprops.event.stopPropagation()\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\tif (props.event.key === 'Enter' || props.event.key === 'Tab') {\n\t\t\t\t\t\t// Complete the highlighted member if there is one to complete; if the roster is empty\n\t\t\t\t\t\t// there's nothing to pick, so cancel the picker and swallow the key \u2014 the composer\n\t\t\t\t\t\t// beneath neither submits (Enter) nor moves focus / indents (Tab).\n\t\t\t\t\t\tconst completed = renderer?.ref?.onKeyDown(props) ?? false\n\t\t\t\t\t\tif (!completed) {\n\t\t\t\t\t\t\thide()\n\t\t\t\t\t\t\tprops.event.stopPropagation()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\tif (renderer && renderer.ref) return renderer.ref.onKeyDown(props)\n\t\t\t\t\treturn false\n\t\t\t\t},\n\t\t\t\tonExit: () => {\n\t\t\t\t\tstopFollowing()\n\t\t\t\t\tif (editorEl) editorEl.removeEventListener('blur', hide)\n\t\t\t\t\tif (container) container.removeEventListener('wheel', onWheel)\n\t\t\t\t\tif (container) container.remove()\n\t\t\t\t\tif (renderer) renderer.destroy()\n\t\t\t\t\trenderer = null\n\t\t\t\t\tcontainer = null\n\t\t\t\t\tcanvasEl = null\n\t\t\t\t\tmentionPickerOpen.set(false)\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["MentionPopup"]
|
|
7
|
-
}
|
package/dist-esm/ui/mention.mjs
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/ui/mention.tsx"],
|
|
4
|
-
"sourcesContent": ["/** @public */\nexport interface MentionProps {\n\tname: string\n}\n\n/** An \\@-mention pill for referencing a person in a comment. @public @react */\nexport function Mention({ name }: MentionProps) {\n\treturn <span className=\"cmt-mention\">@{name}</span>\n}\n"],
|
|
5
|
-
"mappings": "AAOQ;AADD,SAAS,QAAQ,EAAE,KAAK,GAAiB;AAC/C,SAAO,qBAAC,UAAK,WAAU,eAAc;AAAA;AAAA,IAAE;AAAA,KAAK;AAC7C;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment } from "react";
|
|
3
|
-
function renderMarkdown(text) {
|
|
4
|
-
return text.trim().split(/\n{2,}/).map((block, i) => {
|
|
5
|
-
const lines = block.split("\n");
|
|
6
|
-
if (lines.every((line) => /^\s*[-*]\s+/.test(line))) {
|
|
7
|
-
return (
|
|
8
|
-
/* @__PURE__ */ jsx("ul", { className: "md-list", children: lines.map((line, j) => /* @__PURE__ */ jsx("li", { children: renderInline(line.replace(/^\s*[-*]\s+/, "")) }, j)) }, i)
|
|
9
|
-
);
|
|
10
|
-
}
|
|
11
|
-
return /* @__PURE__ */ jsx("p", { className: "md-p", children: lines.map((line, j) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12
|
-
j > 0 && /* @__PURE__ */ jsx("br", {}),
|
|
13
|
-
renderInline(line)
|
|
14
|
-
] }, j)) }, i);
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
const INLINE = /(\*\*[^*]+\*\*|\*[^*]+\*|_[^_]+_|`[^`]+`|\[[^\]]+\]\([^)]+\))/g;
|
|
18
|
-
function renderInline(text) {
|
|
19
|
-
const out = [];
|
|
20
|
-
let last = 0;
|
|
21
|
-
let key = 0;
|
|
22
|
-
for (const match of text.matchAll(INLINE)) {
|
|
23
|
-
const index = match.index ?? 0;
|
|
24
|
-
if (index > last) out.push(text.slice(last, index));
|
|
25
|
-
out.push(renderToken(match[0], key++));
|
|
26
|
-
last = index + match[0].length;
|
|
27
|
-
}
|
|
28
|
-
if (last < text.length) out.push(text.slice(last));
|
|
29
|
-
return out;
|
|
30
|
-
}
|
|
31
|
-
function renderToken(token, key) {
|
|
32
|
-
if (token.startsWith("**")) return /* @__PURE__ */ jsx("strong", { children: token.slice(2, -2) }, key);
|
|
33
|
-
if (token.startsWith("`"))
|
|
34
|
-
return /* @__PURE__ */ jsx("code", { className: "md-code", children: token.slice(1, -1) }, key);
|
|
35
|
-
if (token.startsWith("[")) {
|
|
36
|
-
const link = /\[([^\]]+)\]\(([^)]+)\)/.exec(token);
|
|
37
|
-
if (!link) return token;
|
|
38
|
-
return /* @__PURE__ */ jsx("a", { href: link[2], target: "_blank", rel: "noreferrer", children: link[1] }, key);
|
|
39
|
-
}
|
|
40
|
-
return /* @__PURE__ */ jsx("em", { children: token.slice(1, -1) }, key);
|
|
41
|
-
}
|
|
42
|
-
export {
|
|
43
|
-
renderMarkdown
|
|
44
|
-
};
|
|
45
|
-
//# sourceMappingURL=render-markdown.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/ui/render-markdown.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Fragment, ReactNode } from 'react'\n\n/**\n * Minimal markdown for comment bodies: paragraphs, line breaks, bullet lists, and\n * inline **bold**, *italic*, `code`, and [links](url). Not a full CommonMark parser \u2014\n * just the markdown people actually write in comments. Renders React elements only \u2014\n * never raw HTML \u2014 so there's no HTML-injection surface.\n * @public\n */\nexport function renderMarkdown(text: string): ReactNode {\n\treturn text\n\t\t.trim()\n\t\t.split(/\\n{2,}/)\n\t\t.map((block, i) => {\n\t\t\tconst lines = block.split('\\n')\n\t\t\tif (lines.every((line) => /^\\s*[-*]\\s+/.test(line))) {\n\t\t\t\treturn (\n\t\t\t\t\t<ul key={i} className=\"md-list\">\n\t\t\t\t\t\t{lines.map((line, j) => (\n\t\t\t\t\t\t\t<li key={j}>{renderInline(line.replace(/^\\s*[-*]\\s+/, ''))}</li>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</ul>\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn (\n\t\t\t\t<p key={i} className=\"md-p\">\n\t\t\t\t\t{lines.map((line, j) => (\n\t\t\t\t\t\t<Fragment key={j}>\n\t\t\t\t\t\t\t{j > 0 && <br />}\n\t\t\t\t\t\t\t{renderInline(line)}\n\t\t\t\t\t\t</Fragment>\n\t\t\t\t\t))}\n\t\t\t\t</p>\n\t\t\t)\n\t\t})\n}\n\nconst INLINE = /(\\*\\*[^*]+\\*\\*|\\*[^*]+\\*|_[^_]+_|`[^`]+`|\\[[^\\]]+\\]\\([^)]+\\))/g\n\nfunction renderInline(text: string): ReactNode {\n\tconst out: ReactNode[] = []\n\tlet last = 0\n\tlet key = 0\n\tfor (const match of text.matchAll(INLINE)) {\n\t\tconst index = match.index ?? 0\n\t\tif (index > last) out.push(text.slice(last, index))\n\t\tout.push(renderToken(match[0], key++))\n\t\tlast = index + match[0].length\n\t}\n\tif (last < text.length) out.push(text.slice(last))\n\treturn out\n}\n\nfunction renderToken(token: string, key: number): ReactNode {\n\tif (token.startsWith('**')) return <strong key={key}>{token.slice(2, -2)}</strong>\n\tif (token.startsWith('`'))\n\t\treturn (\n\t\t\t<code key={key} className=\"md-code\">\n\t\t\t\t{token.slice(1, -1)}\n\t\t\t</code>\n\t\t)\n\tif (token.startsWith('[')) {\n\t\tconst link = /\\[([^\\]]+)\\]\\(([^)]+)\\)/.exec(token)\n\t\tif (!link) return token\n\t\treturn (\n\t\t\t<a key={key} href={link[2]} target=\"_blank\" rel=\"noreferrer\">\n\t\t\t\t{link[1]}\n\t\t\t</a>\n\t\t)\n\t}\n\treturn <em key={key}>{token.slice(1, -1)}</em>\n}\n"],
|
|
5
|
-
"mappings": "AAmBO,cAQD,YARC;AAnBP,SAAS,gBAA2B;AAS7B,SAAS,eAAe,MAAyB;AACvD,SAAO,KACL,KAAK,EACL,MAAM,QAAQ,EACd,IAAI,CAAC,OAAO,MAAM;AAClB,UAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,QAAI,MAAM,MAAM,CAAC,SAAS,cAAc,KAAK,IAAI,CAAC,GAAG;AACpD,aACC,oBAAC,QAAW,WAAU,WACpB,gBAAM,IAAI,CAAC,MAAM,MACjB,oBAAC,QAAY,uBAAa,KAAK,QAAQ,eAAe,EAAE,CAAC,KAAhD,CAAkD,CAC3D,KAHO,CAIT;AAAA,IAEF;AACA,WACC,oBAAC,OAAU,WAAU,QACnB,gBAAM,IAAI,CAAC,MAAM,MACjB,qBAAC,YACC;AAAA,UAAI,KAAK,oBAAC,QAAG;AAAA,MACb,aAAa,IAAI;AAAA,SAFJ,CAGf,CACA,KANM,CAOR;AAAA,EAEF,CAAC;AACH;AAEA,MAAM,SAAS;AAEf,SAAS,aAAa,MAAyB;AAC9C,QAAM,MAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,MAAI,MAAM;AACV,aAAW,SAAS,KAAK,SAAS,MAAM,GAAG;AAC1C,UAAM,QAAQ,MAAM,SAAS;AAC7B,QAAI,QAAQ,KAAM,KAAI,KAAK,KAAK,MAAM,MAAM,KAAK,CAAC;AAClD,QAAI,KAAK,YAAY,MAAM,CAAC,GAAG,KAAK,CAAC;AACrC,WAAO,QAAQ,MAAM,CAAC,EAAE;AAAA,EACzB;AACA,MAAI,OAAO,KAAK,OAAQ,KAAI,KAAK,KAAK,MAAM,IAAI,CAAC;AACjD,SAAO;AACR;AAEA,SAAS,YAAY,OAAe,KAAwB;AAC3D,MAAI,MAAM,WAAW,IAAI,EAAG,QAAO,oBAAC,YAAkB,gBAAM,MAAM,GAAG,EAAE,KAAvB,GAAyB;AACzE,MAAI,MAAM,WAAW,GAAG;AACvB,WACC,oBAAC,UAAe,WAAU,WACxB,gBAAM,MAAM,GAAG,EAAE,KADR,GAEX;AAEF,MAAI,MAAM,WAAW,GAAG,GAAG;AAC1B,UAAM,OAAO,0BAA0B,KAAK,KAAK;AACjD,QAAI,CAAC,KAAM,QAAO;AAClB,WACC,oBAAC,OAAY,MAAM,KAAK,CAAC,GAAG,QAAO,UAAS,KAAI,cAC9C,eAAK,CAAC,KADA,GAER;AAAA,EAEF;AACA,SAAO,oBAAC,QAAc,gBAAM,MAAM,GAAG,EAAE,KAAvB,GAAyB;AAC1C;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { atom, Atom, Editor, VecLike } from 'tldraw'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The configurable dimensions of region comments — a design surface for prototyping the interaction
|
|
5
|
-
* before settling it. Region is **off by default**, so a consumer that leaves it unset keeps plain
|
|
6
|
-
* click-only point/shape commenting. Set it per editor through `<CanvasComments regionOptions>`.
|
|
7
|
-
* @public
|
|
8
|
-
*/
|
|
9
|
-
export interface RegionCommentOptions {
|
|
10
|
-
/** Whether dragging the comment tool out creates a region anchor. Off → click-only. */
|
|
11
|
-
enabled: boolean
|
|
12
|
-
/** Which corner the pin and composer sit on, as a normalized 0–1 offset. Default bottom-right. */
|
|
13
|
-
pinCorner: VecLike
|
|
14
|
-
/** When the dashed box and its handles reveal: while the pointer is within the region, while the
|
|
15
|
-
* pin is hovered, or only while the thread is open. */
|
|
16
|
-
reveal: 'pointer' | 'pin-hover' | 'open'
|
|
17
|
-
/** How a region is moved: dragging its pin, dragging its body, or either. */
|
|
18
|
-
move: 'pin' | 'body' | 'both'
|
|
19
|
-
/** The resize affordance: corner handles, edge handles, or none. */
|
|
20
|
-
resize: 'corners' | 'edges' | 'none'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** The out-of-the-box region config: disabled, and — when a consumer enables it — the current
|
|
24
|
-
* bottom-right / pointer-reveal / pin-move / corner-resize behaviour.
|
|
25
|
-
* @public */
|
|
26
|
-
export const DEFAULT_REGION_COMMENT_OPTIONS: RegionCommentOptions = {
|
|
27
|
-
enabled: false,
|
|
28
|
-
pinCorner: { x: 1, y: 1 },
|
|
29
|
-
reveal: 'pointer',
|
|
30
|
-
move: 'pin',
|
|
31
|
-
resize: 'corners',
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Per-editor region config. The overlay publishes its merged `regionOptions` here so the comment
|
|
35
|
-
// tool — which has no props of its own — can read the same per-instance config at interaction time.
|
|
36
|
-
// Held in an atom so reactive readers (e.g. `anchorPagePoint`, called inside `useValue`) recompute
|
|
37
|
-
// when the config changes; a plain map read wouldn't, leaving the pin at the stale default corner.
|
|
38
|
-
const byEditor = new WeakMap<Editor, Atom<RegionCommentOptions>>()
|
|
39
|
-
|
|
40
|
-
function getEditorAtom(editor: Editor): Atom<RegionCommentOptions> {
|
|
41
|
-
let a = byEditor.get(editor)
|
|
42
|
-
if (!a) {
|
|
43
|
-
a = atom('regionCommentOptions', DEFAULT_REGION_COMMENT_OPTIONS)
|
|
44
|
-
byEditor.set(editor, a)
|
|
45
|
-
}
|
|
46
|
-
return a
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Publish an editor's region config (called by the overlay from its `regionOptions` prop). */
|
|
50
|
-
export function setRegionCommentOptions(editor: Editor, options: RegionCommentOptions): void {
|
|
51
|
-
getEditorAtom(editor).set(options)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** The editor's region config, or the disabled default when none was set. */
|
|
55
|
-
export function getRegionCommentOptions(editor: Editor): RegionCommentOptions {
|
|
56
|
-
return getEditorAtom(editor).get()
|
|
57
|
-
}
|
package/src/ui/avatar.tsx
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { getFirstCharacter } from 'tldraw'
|
|
2
|
-
|
|
3
|
-
/** @public */
|
|
4
|
-
export interface AvatarProps {
|
|
5
|
-
name: string
|
|
6
|
-
/** Background colour, used when there's no `image`. Falls back to the default avatar tint. */
|
|
7
|
-
color?: string
|
|
8
|
-
/** Avatar image URL. When set, shows the image instead of the coloured initial. */
|
|
9
|
-
image?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function initial(name: string) {
|
|
13
|
-
return (getFirstCharacter(name.trim()) || '?').toUpperCase()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** A commenter's avatar — their image if provided, otherwise a single-initial coloured circle.
|
|
17
|
-
* @public @react */
|
|
18
|
-
export function Avatar({ name, color, image }: AvatarProps) {
|
|
19
|
-
if (image) {
|
|
20
|
-
return <img className="cmt-avatar cmt-avatar--image" src={image} alt="" aria-hidden="true" />
|
|
21
|
-
}
|
|
22
|
-
return (
|
|
23
|
-
<div
|
|
24
|
-
className="cmt-avatar"
|
|
25
|
-
aria-hidden="true"
|
|
26
|
-
style={color ? { backgroundColor: color } : undefined}
|
|
27
|
-
>
|
|
28
|
-
{initial(name)}
|
|
29
|
-
</div>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { mergeAttributes } from '@tiptap/core'
|
|
2
|
-
import { Mention, type MentionNodeAttrs, type MentionOptions } from '@tiptap/extension-mention'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* A mention node's display name: the member's current name, resolved live from its id when a
|
|
6
|
-
* resolver is given (so a rename shows through), falling back to the label captured at insert time
|
|
7
|
-
* when no source can name the id — e.g. a deleted account. The stored id is always the source of
|
|
8
|
-
* truth; live resolution only freshens the displayed name over that captured label.
|
|
9
|
-
*/
|
|
10
|
-
function mentionName(
|
|
11
|
-
attrs: MentionNodeAttrs,
|
|
12
|
-
resolveName?: (id: string) => string | undefined
|
|
13
|
-
): string {
|
|
14
|
-
const resolved = attrs.id && resolveName ? resolveName(attrs.id) : undefined
|
|
15
|
-
return resolved ?? attrs.label ?? attrs.id ?? ''
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface CommentMentionOptions {
|
|
19
|
-
/**
|
|
20
|
-
* Resolve a member id to its current display name — for the read-only render paths. Returns
|
|
21
|
-
* `undefined` when the id can't be resolved, so the render falls back to the mention's stored label.
|
|
22
|
-
*/
|
|
23
|
-
resolveName?(id: string): string | undefined
|
|
24
|
-
/** The `@`-picker suggestion config — for the composer (omit on the render paths). */
|
|
25
|
-
suggestion?: MentionOptions['suggestion']
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The comment @-mention node — TipTap's `Mention` configured to render as a `.cmt-mention` pill.
|
|
30
|
-
*
|
|
31
|
-
* A factory rather than a shared constant because it's configured differently per context: the
|
|
32
|
-
* read-only render paths pass `resolveName` (so the stored `{ id }` node always shows the member's
|
|
33
|
-
* current name, not a copy frozen at insert time), while the composer passes a `suggestion` (the
|
|
34
|
-
* `@` picker). The node schema is identical either way — only these two levers differ.
|
|
35
|
-
*/
|
|
36
|
-
export function commentMention({ resolveName, suggestion }: CommentMentionOptions = {}) {
|
|
37
|
-
return Mention.configure({
|
|
38
|
-
HTMLAttributes: { class: 'cmt-mention' },
|
|
39
|
-
renderText: ({ node }) => `@${mentionName(node.attrs as MentionNodeAttrs, resolveName)}`,
|
|
40
|
-
renderHTML: ({ node, options }) => [
|
|
41
|
-
'span',
|
|
42
|
-
mergeAttributes(options.HTMLAttributes, { 'data-mention-id': node.attrs.id }),
|
|
43
|
-
`@${mentionName(node.attrs as MentionNodeAttrs, resolveName)}`,
|
|
44
|
-
],
|
|
45
|
-
...(suggestion ? { suggestion } : {}),
|
|
46
|
-
})
|
|
47
|
-
}
|
package/src/ui/comment-text.tsx
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { renderMarkdown } from './render-markdown'
|
|
2
|
-
|
|
3
|
-
/** @public */
|
|
4
|
-
export interface CommentTextProps {
|
|
5
|
-
/** Rich text body, authored as markdown. */
|
|
6
|
-
text: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** A comment's body, rendered as markdown (bold, italic, code, links, lists). @public @react */
|
|
10
|
-
export function CommentText({ text }: CommentTextProps) {
|
|
11
|
-
return <div className="cmt-text">{renderMarkdown(text)}</div>
|
|
12
|
-
}
|