@edux-design/comments 0.1.4 → 0.2.0
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/CHANGELOG.md +13 -0
- package/README.md +18 -0
- package/dist/index.d.mts +162 -48
- package/dist/index.d.ts +162 -48
- package/dist/index.js +348 -147
- package/dist/index.mjs +361 -152
- package/docs/composer.md +65 -0
- package/package.json +12 -23
- package/src/demos/Comment.stories.jsx +0 -593
- package/src/demos/CommentComposer.stories.jsx +0 -66
- package/src/elements/Comment.jsx +0 -240
- package/src/elements/Comment.test.jsx +0 -151
- package/src/elements/CommentComposer.jsx +0 -184
- package/src/elements/CommentComposer.test.jsx +0 -125
- package/src/elements/CommentMention.jsx +0 -232
- package/src/elements/CommentMention.test.jsx +0 -150
- package/src/elements/CommentMentionExtension.jsx +0 -56
- package/src/index.js +0 -3
- package/tsconfig.json +0 -10
package/dist/index.mjs
CHANGED
|
@@ -21,7 +21,10 @@ var CommentContext = createContext({
|
|
|
21
21
|
resolved: false
|
|
22
22
|
});
|
|
23
23
|
var useCommentContext = () => useContext(CommentContext);
|
|
24
|
-
var
|
|
24
|
+
var flattenReplies = (children) => React.Children.toArray(children).flatMap(
|
|
25
|
+
(child) => isValidElement(child) && child.type === React.Fragment ? flattenReplies(child.props.children) : [child]
|
|
26
|
+
);
|
|
27
|
+
function CommentComponent({
|
|
25
28
|
author = "Author",
|
|
26
29
|
timestamp,
|
|
27
30
|
avatar,
|
|
@@ -39,10 +42,7 @@ var Comment = forwardRef(function Comment2({
|
|
|
39
42
|
}, ref) {
|
|
40
43
|
const childArray = React.Children.toArray(children);
|
|
41
44
|
const actionsSlot = childArray.find(
|
|
42
|
-
(child) =>
|
|
43
|
-
var _a;
|
|
44
|
-
return isValidElement(child) && (child.type === CommentActions || ((_a = child.type) == null ? void 0 : _a.displayName) === "Comment.Actions");
|
|
45
|
-
}
|
|
45
|
+
(child) => isValidElement(child) && (child.type === CommentActions || child.type?.displayName === "Comment.Actions")
|
|
46
46
|
);
|
|
47
47
|
const bodyNodes = childArray.filter((child) => child !== actionsSlot);
|
|
48
48
|
const bodyContent = body ?? bodyNodes;
|
|
@@ -50,9 +50,11 @@ var Comment = forwardRef(function Comment2({
|
|
|
50
50
|
const isResolved = resolved || variant === "resolved";
|
|
51
51
|
const resolvedVariant = VARIANT_STYLES[isResolved ? "resolved" : variant] ?? VARIANT_STYLES.default;
|
|
52
52
|
const resolvedAvatarName = avatarName ?? (typeof author === "string" ? author : void 0);
|
|
53
|
-
const shouldRenderAvatar = Boolean(
|
|
53
|
+
const shouldRenderAvatar = Boolean(
|
|
54
|
+
avatar || avatarProps || resolvedAvatarName
|
|
55
|
+
);
|
|
54
56
|
const replyItems = useMemo(
|
|
55
|
-
() =>
|
|
57
|
+
() => flattenReplies(replies).filter(
|
|
56
58
|
(reply) => reply !== null && reply !== void 0 && reply !== false
|
|
57
59
|
),
|
|
58
60
|
[replies]
|
|
@@ -64,49 +66,55 @@ var Comment = forwardRef(function Comment2({
|
|
|
64
66
|
setShowAllReplies(!shouldCollapseReplies);
|
|
65
67
|
}, [shouldCollapseReplies]);
|
|
66
68
|
const visibleReplies = shouldCollapseReplies && !showAllReplies ? [replyItems[replyCount - 1]] : replyItems;
|
|
67
|
-
return /* @__PURE__ */ jsx(
|
|
68
|
-
|
|
69
|
+
return /* @__PURE__ */ jsx(
|
|
70
|
+
CommentContext.Provider,
|
|
69
71
|
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
72
|
+
value: { editable, onResolve, resolved: isResolved },
|
|
73
|
+
children: /* @__PURE__ */ jsxs(
|
|
74
|
+
"div",
|
|
75
|
+
{
|
|
76
|
+
ref,
|
|
77
|
+
className: cx(
|
|
78
|
+
"flex gap-12 rounded-default border p-12",
|
|
79
|
+
resolvedVariant,
|
|
80
|
+
className
|
|
81
|
+
),
|
|
82
|
+
"data-variant": variant,
|
|
83
|
+
...props,
|
|
84
|
+
children: [
|
|
85
|
+
shouldRenderAvatar && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar ?? /* @__PURE__ */ jsx(Avatar, { name: resolvedAvatarName, size: "small", ...avatarProps }) }),
|
|
86
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
87
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [
|
|
88
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-fg-base", children: author }),
|
|
89
|
+
timestamp && /* @__PURE__ */ jsx("span", { className: "text-xs text-fg-subtle", children: timestamp })
|
|
90
|
+
] }),
|
|
91
|
+
hasBodyContent && !isResolved ? /* @__PURE__ */ jsx("div", { className: "mt-6 text-sm leading-relaxed text-fg-base", children: bodyContent }) : null,
|
|
92
|
+
isResolved ? /* @__PURE__ */ jsx("div", { className: "mt-6 text-xs font-semibold uppercase tracking-wide text-fg-success-base", children: "Resolved" }) : null,
|
|
93
|
+
actionsSlot ? /* @__PURE__ */ jsx("div", { className: "mt-8", children: actionsSlot }) : null,
|
|
94
|
+
replies && !isResolved ? /* @__PURE__ */ jsxs("div", { className: "mt-[40px] border-l border-border-minimal pl-[20px]", children: [
|
|
95
|
+
shouldCollapseReplies && !showAllReplies && /* @__PURE__ */ jsxs(
|
|
96
|
+
"button",
|
|
97
|
+
{
|
|
98
|
+
type: "button",
|
|
99
|
+
className: "mb-8 text-xs font-semibold text-fg-info-base",
|
|
100
|
+
onClick: () => setShowAllReplies(true),
|
|
101
|
+
"aria-expanded": showAllReplies,
|
|
102
|
+
children: [
|
|
103
|
+
"Show all ",
|
|
104
|
+
replyCount,
|
|
105
|
+
" replies"
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
),
|
|
109
|
+
visibleReplies
|
|
110
|
+
] }) : null
|
|
111
|
+
] })
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
)
|
|
107
115
|
}
|
|
108
|
-
)
|
|
109
|
-
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
110
118
|
var CommentActions = ({ children, className, ...props }) => /* @__PURE__ */ jsx(
|
|
111
119
|
"div",
|
|
112
120
|
{
|
|
@@ -166,10 +174,13 @@ var CommentResolve = ({
|
|
|
166
174
|
}
|
|
167
175
|
);
|
|
168
176
|
};
|
|
169
|
-
Comment
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
177
|
+
var Comment = Object.assign(forwardRef(CommentComponent), {
|
|
178
|
+
Actions: CommentActions,
|
|
179
|
+
Reply: CommentReply,
|
|
180
|
+
Edit: CommentEdit,
|
|
181
|
+
Resolve: CommentResolve
|
|
182
|
+
});
|
|
183
|
+
Comment.displayName = "Comment";
|
|
173
184
|
CommentActions.displayName = "Comment.Actions";
|
|
174
185
|
CommentReply.displayName = "Comment.Reply";
|
|
175
186
|
CommentEdit.displayName = "Comment.Edit";
|
|
@@ -182,7 +193,9 @@ import React3, {
|
|
|
182
193
|
useRef,
|
|
183
194
|
createContext as createContext2,
|
|
184
195
|
useContext as useContext2,
|
|
185
|
-
useEffect as useEffect2
|
|
196
|
+
useEffect as useEffect2,
|
|
197
|
+
useImperativeHandle,
|
|
198
|
+
useState as useState2
|
|
186
199
|
} from "react";
|
|
187
200
|
import { cx as cx2 } from "@edux-design/utils";
|
|
188
201
|
import { Avatar as Avatar2 } from "@edux-design/avatars";
|
|
@@ -198,9 +211,8 @@ import { ReactNodeViewRenderer, NodeViewWrapper } from "@tiptap/react";
|
|
|
198
211
|
import { Mention } from "@edux-design/mentions";
|
|
199
212
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
200
213
|
var MentionNodeView = ({ node }) => {
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
const label = ((_b = node.attrs) == null ? void 0 : _b.label) ?? "";
|
|
214
|
+
const handle = node.attrs?.handle ?? "";
|
|
215
|
+
const label = node.attrs?.label ?? "";
|
|
204
216
|
return /* @__PURE__ */ jsx2(
|
|
205
217
|
NodeViewWrapper,
|
|
206
218
|
{
|
|
@@ -243,6 +255,11 @@ var CommentMentionExtension = Node.create({
|
|
|
243
255
|
label
|
|
244
256
|
];
|
|
245
257
|
},
|
|
258
|
+
renderText({ node }) {
|
|
259
|
+
const handle = node.attrs.handle || "";
|
|
260
|
+
const label = node.attrs.label || "";
|
|
261
|
+
return `@${handle || label || "mention"}`;
|
|
262
|
+
},
|
|
246
263
|
addNodeView() {
|
|
247
264
|
return ReactNodeViewRenderer(MentionNodeView);
|
|
248
265
|
}
|
|
@@ -260,87 +277,237 @@ var useCommentComposer = () => {
|
|
|
260
277
|
}
|
|
261
278
|
return context;
|
|
262
279
|
};
|
|
263
|
-
|
|
280
|
+
function readValue(editor) {
|
|
281
|
+
const mentions = [];
|
|
282
|
+
editor.state.doc.descendants((node) => {
|
|
283
|
+
if (node.type.name === "commentMention") {
|
|
284
|
+
mentions.push({
|
|
285
|
+
id: node.attrs.id ?? null,
|
|
286
|
+
handle: node.attrs.handle ?? "",
|
|
287
|
+
label: node.attrs.label ?? ""
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
const legacyText = editor.getText();
|
|
292
|
+
const text = editor.getText({
|
|
293
|
+
blockSeparator: "\n",
|
|
294
|
+
textSerializers: {
|
|
295
|
+
commentMention: ({ node }) => {
|
|
296
|
+
const { handle = "", label = "" } = node.attrs;
|
|
297
|
+
return label ? `@${handle} \xB7 ${label}` : `@${handle}`;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}).replace(/\u00a0/g, " ").replace(/[\u200b-\u200d\ufeff]/g, "").replace(/\r?\n/g, " ");
|
|
301
|
+
return {
|
|
302
|
+
text,
|
|
303
|
+
legacyText,
|
|
304
|
+
html: editor.getHTML(),
|
|
305
|
+
json: editor.getJSON(),
|
|
306
|
+
mentions,
|
|
307
|
+
isEmpty: !text.trim()
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function stringContent(value, format) {
|
|
311
|
+
if (format !== "text") return value;
|
|
312
|
+
return {
|
|
313
|
+
type: "doc",
|
|
314
|
+
content: value.split(/\r?\n/).map((line) => ({
|
|
315
|
+
type: "paragraph",
|
|
316
|
+
...line ? { content: [{ type: "text", text: line }] } : {}
|
|
317
|
+
}))
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function CommentComposerComponent({
|
|
264
321
|
value,
|
|
322
|
+
valueFormat = "html",
|
|
323
|
+
richValue,
|
|
265
324
|
onChange,
|
|
325
|
+
onValueChange,
|
|
266
326
|
onSubmit,
|
|
267
327
|
onCancel,
|
|
328
|
+
composerRef,
|
|
329
|
+
editorLabel = "Comment",
|
|
330
|
+
editorAttributes: suppliedEditorAttributes,
|
|
331
|
+
autoFocus = false,
|
|
332
|
+
externalSubmit = false,
|
|
333
|
+
submitOnModEnter = false,
|
|
268
334
|
placeholder = "Write a comment...",
|
|
269
335
|
rows = 4,
|
|
270
336
|
disabled = false,
|
|
271
337
|
isSubmitting = false,
|
|
272
338
|
submitLabel = "Post",
|
|
339
|
+
submittingLabel = "Posting...",
|
|
273
340
|
cancelLabel = "Cancel",
|
|
274
341
|
avatar,
|
|
275
342
|
avatarProps,
|
|
276
343
|
actions,
|
|
277
344
|
className,
|
|
278
345
|
children,
|
|
346
|
+
onKeyDown,
|
|
347
|
+
onKeyDownCapture,
|
|
279
348
|
...props
|
|
280
349
|
}, ref) {
|
|
281
|
-
const
|
|
282
|
-
const
|
|
350
|
+
const containerRef = useRef(null);
|
|
351
|
+
const formRef = useRef(null);
|
|
352
|
+
const pendingFocusRef = useRef(null);
|
|
353
|
+
const handledShortcutEvents = useRef(/* @__PURE__ */ new WeakSet());
|
|
354
|
+
const callbacks = useRef({ onChange, onValueChange });
|
|
355
|
+
callbacks.current = { onChange, onValueChange };
|
|
356
|
+
const pendingEchoesRef = useRef([]);
|
|
357
|
+
const controlledRef = useRef(false);
|
|
358
|
+
controlledRef.current = value !== void 0 || richValue !== void 0;
|
|
359
|
+
const placeholderRef = useRef(placeholder);
|
|
360
|
+
placeholderRef.current = placeholder;
|
|
361
|
+
const [hasValue, setHasValue] = useState2(Boolean(richValue || value?.trim()));
|
|
362
|
+
const locked = disabled || isSubmitting;
|
|
363
|
+
const editorAttributes = useMemo2(
|
|
364
|
+
() => ({
|
|
365
|
+
...suppliedEditorAttributes,
|
|
366
|
+
"data-comment-editor": "true",
|
|
367
|
+
role: "textbox",
|
|
368
|
+
"aria-label": editorLabel,
|
|
369
|
+
"aria-multiline": "true",
|
|
370
|
+
"aria-disabled": String(locked),
|
|
371
|
+
class: cx2(
|
|
372
|
+
"w-full bg-bg-base text-fg-base border-2 rounded-md border-border-base",
|
|
373
|
+
"outline-none transition-all duration-300 ease-in-out motion-reduce:transition-none",
|
|
374
|
+
"pl-[20px] py-12 font-normal text-base leading-relaxed overflow-y-auto",
|
|
375
|
+
"focus:shadow-focus focus-visible:shadow-focus",
|
|
376
|
+
locked && "bg-bg-subtle border-border-subtle text-fg-inactive"
|
|
377
|
+
),
|
|
378
|
+
style: `height: ${Math.max(1, rows) * 30}px`
|
|
379
|
+
}),
|
|
380
|
+
[editorLabel, locked, rows, suppliedEditorAttributes]
|
|
381
|
+
);
|
|
382
|
+
const isSubmitDisabled = locked || !hasValue;
|
|
283
383
|
const editor = useEditor({
|
|
384
|
+
immediatelyRender: false,
|
|
284
385
|
extensions: [
|
|
285
386
|
StarterKit,
|
|
286
387
|
CommentMentionExtension,
|
|
287
|
-
Placeholder.configure({
|
|
288
|
-
placeholder
|
|
289
|
-
})
|
|
388
|
+
Placeholder.configure({ placeholder: () => placeholderRef.current })
|
|
290
389
|
],
|
|
291
|
-
content: value ?? "",
|
|
292
|
-
editable: !
|
|
293
|
-
editorProps: {
|
|
294
|
-
attributes: {
|
|
295
|
-
class: cx2(
|
|
296
|
-
"w-full bg-white border-2 rounded-md border-border-base",
|
|
297
|
-
"outline-none transition-all duration-300 ease-in-out",
|
|
298
|
-
"pl-5 py-3 font-normal text-base leading-relaxed",
|
|
299
|
-
"h-[120px] overflow-y-auto",
|
|
300
|
-
"focus:shadow-focus focus-visible:shadow-focus",
|
|
301
|
-
disabled && "bg-bg-subtle border-border-subtle text-fg-inactive"
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
},
|
|
390
|
+
content: richValue ?? stringContent(value ?? "", valueFormat),
|
|
391
|
+
editable: !locked,
|
|
392
|
+
editorProps: { attributes: editorAttributes },
|
|
305
393
|
onUpdate: ({ editor: editor2 }) => {
|
|
306
|
-
|
|
394
|
+
const next = readValue(editor2);
|
|
395
|
+
if (controlledRef.current && (callbacks.current.onChange || callbacks.current.onValueChange)) {
|
|
396
|
+
pendingEchoesRef.current.push(next);
|
|
397
|
+
if (pendingEchoesRef.current.length > 100)
|
|
398
|
+
pendingEchoesRef.current.shift();
|
|
399
|
+
}
|
|
400
|
+
setHasValue(!next.isEmpty);
|
|
401
|
+
callbacks.current.onChange?.({ target: { value: next.legacyText } });
|
|
402
|
+
callbacks.current.onValueChange?.(next);
|
|
307
403
|
}
|
|
308
404
|
});
|
|
309
|
-
|
|
405
|
+
useImperativeHandle(ref, () => containerRef.current, []);
|
|
406
|
+
useImperativeHandle(
|
|
407
|
+
composerRef,
|
|
408
|
+
() => ({
|
|
409
|
+
focus: (position = "end") => {
|
|
410
|
+
if (locked) return false;
|
|
411
|
+
if (!editor) {
|
|
412
|
+
pendingFocusRef.current = position;
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
return editor.commands.focus(position);
|
|
416
|
+
},
|
|
417
|
+
clear: () => !locked && Boolean(editor?.commands.clearContent(true)),
|
|
418
|
+
getValue: () => editor ? readValue(editor) : null,
|
|
419
|
+
submit: () => {
|
|
420
|
+
if (!locked) formRef.current?.requestSubmit();
|
|
421
|
+
}
|
|
422
|
+
}),
|
|
423
|
+
[editor, locked]
|
|
424
|
+
);
|
|
310
425
|
useEffect2(() => {
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
426
|
+
if (!editor) return;
|
|
427
|
+
editor.setEditable(!locked, false);
|
|
428
|
+
editor.setOptions({
|
|
429
|
+
editorProps: {
|
|
430
|
+
...editor.options.editorProps,
|
|
431
|
+
attributes: editorAttributes
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}, [editor, locked, editorAttributes, placeholder]);
|
|
317
435
|
useEffect2(() => {
|
|
318
436
|
if (!editor) return;
|
|
319
|
-
|
|
320
|
-
|
|
437
|
+
const next = richValue ?? value;
|
|
438
|
+
if (next === void 0) return;
|
|
439
|
+
const current = readValue(editor);
|
|
440
|
+
const echoIndex = pendingEchoesRef.current.findIndex(
|
|
441
|
+
(snapshot) => richValue !== void 0 ? JSON.stringify(snapshot.json) === JSON.stringify(richValue) : snapshot.text === value || snapshot.legacyText === value
|
|
442
|
+
);
|
|
443
|
+
if (echoIndex !== -1) {
|
|
444
|
+
pendingEchoesRef.current.splice(0, echoIndex + 1);
|
|
445
|
+
setHasValue(!current.isEmpty);
|
|
446
|
+
return;
|
|
321
447
|
}
|
|
322
|
-
|
|
448
|
+
pendingEchoesRef.current = [];
|
|
449
|
+
const equal = richValue !== void 0 ? JSON.stringify(current.json) === JSON.stringify(richValue) : current.text === value || current.legacyText === value;
|
|
450
|
+
if (!equal)
|
|
451
|
+
editor.commands.setContent(
|
|
452
|
+
richValue ?? stringContent(value, valueFormat),
|
|
453
|
+
{ emitUpdate: false }
|
|
454
|
+
);
|
|
455
|
+
setHasValue(!readValue(editor).isEmpty);
|
|
456
|
+
}, [editor, value, richValue, valueFormat]);
|
|
457
|
+
useEffect2(() => {
|
|
458
|
+
if (editor) setHasValue(!readValue(editor).isEmpty);
|
|
459
|
+
}, [editor]);
|
|
460
|
+
useEffect2(() => {
|
|
461
|
+
if (!editor || locked) return;
|
|
462
|
+
if (!autoFocus && pendingFocusRef.current === null) return;
|
|
463
|
+
const position = pendingFocusRef.current ?? "end";
|
|
464
|
+
pendingFocusRef.current = null;
|
|
465
|
+
const frame = requestAnimationFrame(() => {
|
|
466
|
+
if (!editor.isDestroyed && editor.isEditable)
|
|
467
|
+
editor.commands.focus(position);
|
|
468
|
+
});
|
|
469
|
+
return () => cancelAnimationFrame(frame);
|
|
470
|
+
}, [editor, autoFocus, locked]);
|
|
323
471
|
const contextValue = useMemo2(
|
|
324
472
|
() => ({
|
|
325
473
|
editor,
|
|
326
474
|
placeholder,
|
|
475
|
+
editable: !locked,
|
|
327
476
|
editorContainerRef: containerRef
|
|
328
477
|
}),
|
|
329
|
-
[editor, placeholder]
|
|
478
|
+
[editor, placeholder, locked]
|
|
330
479
|
);
|
|
331
480
|
return /* @__PURE__ */ jsx3(CommentComposerContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs3(
|
|
332
481
|
"form",
|
|
333
482
|
{
|
|
483
|
+
...props,
|
|
484
|
+
ref: formRef,
|
|
485
|
+
"data-comment-surface": "composer",
|
|
486
|
+
"aria-busy": isSubmitting || void 0,
|
|
334
487
|
className: cx2(
|
|
335
488
|
"relative flex gap-12 rounded-default border border-border-minimal bg-bg-base p-12",
|
|
336
489
|
className
|
|
337
490
|
),
|
|
491
|
+
onKeyDown: (event) => {
|
|
492
|
+
if (!handledShortcutEvents.current.has(event.nativeEvent))
|
|
493
|
+
onKeyDown?.(event);
|
|
494
|
+
},
|
|
495
|
+
onKeyDownCapture: (event) => {
|
|
496
|
+
onKeyDownCapture?.(event);
|
|
497
|
+
if (!event.defaultPrevented && submitOnModEnter && !event.nativeEvent.isComposing && (event.ctrlKey || event.metaKey) && event.key === "Enter") {
|
|
498
|
+
handledShortcutEvents.current.add(event.nativeEvent);
|
|
499
|
+
onKeyDown?.(event);
|
|
500
|
+
if (event.defaultPrevented) return;
|
|
501
|
+
event.preventDefault();
|
|
502
|
+
if (!locked && editor && !readValue(editor).isEmpty)
|
|
503
|
+
formRef.current?.requestSubmit();
|
|
504
|
+
}
|
|
505
|
+
},
|
|
338
506
|
onSubmit: (event) => {
|
|
339
507
|
event.preventDefault();
|
|
340
|
-
if (
|
|
341
|
-
|
|
508
|
+
if (!locked && editor && !readValue(editor).isEmpty)
|
|
509
|
+
onSubmit?.(event);
|
|
342
510
|
},
|
|
343
|
-
...props,
|
|
344
511
|
children: [
|
|
345
512
|
(avatar || avatarProps) && /* @__PURE__ */ jsx3("div", { className: "shrink-0", children: avatar ?? /* @__PURE__ */ jsx3(Avatar2, { size: "small", ...avatarProps }) }),
|
|
346
513
|
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -355,11 +522,11 @@ var CommentComposer = forwardRef2(function CommentComposer2({
|
|
|
355
522
|
size: "small",
|
|
356
523
|
isSecondary: true,
|
|
357
524
|
onClick: onCancel,
|
|
358
|
-
disabled:
|
|
525
|
+
disabled: locked,
|
|
359
526
|
children: cancelLabel
|
|
360
527
|
}
|
|
361
528
|
),
|
|
362
|
-
/* @__PURE__ */ jsx3(Button, { type: "submit", size: "small", disabled: isSubmitDisabled, children: isSubmitting ?
|
|
529
|
+
!externalSubmit && /* @__PURE__ */ jsx3(Button, { type: "submit", size: "small", disabled: isSubmitDisabled, children: isSubmitting ? submittingLabel : submitLabel })
|
|
363
530
|
] })
|
|
364
531
|
] })
|
|
365
532
|
] }),
|
|
@@ -367,29 +534,38 @@ var CommentComposer = forwardRef2(function CommentComposer2({
|
|
|
367
534
|
]
|
|
368
535
|
}
|
|
369
536
|
) });
|
|
370
|
-
}
|
|
537
|
+
}
|
|
538
|
+
var CommentComposer = forwardRef2(CommentComposerComponent);
|
|
371
539
|
|
|
372
540
|
// src/elements/CommentMention.jsx
|
|
373
|
-
import React4, {
|
|
541
|
+
import React4, {
|
|
542
|
+
useMemo as useMemo3,
|
|
543
|
+
useState as useState3,
|
|
544
|
+
useCallback,
|
|
545
|
+
useEffect as useEffect3,
|
|
546
|
+
useRef as useRef2
|
|
547
|
+
} from "react";
|
|
374
548
|
import { Menu, MenuList, MenuItem } from "@edux-design/menus";
|
|
375
549
|
import { Avatar as Avatar3 } from "@edux-design/avatars";
|
|
376
550
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
377
|
-
var CommentMention = ({
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const {
|
|
382
|
-
const [menuOpen, setMenuOpen] = useState2(false);
|
|
383
|
-
const [query, setQuery] = useState2("");
|
|
384
|
-
const [range, setRange] = useState2(null);
|
|
385
|
-
const [anchorStyle, setAnchorStyle] = useState2({ left: 0, top: 0 });
|
|
551
|
+
var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
552
|
+
const { editor, editorContainerRef, editable = true } = useCommentComposer();
|
|
553
|
+
const [menuOpen, setMenuOpen] = useState3(false);
|
|
554
|
+
const [query, setQuery] = useState3("");
|
|
555
|
+
const [anchorStyle, setAnchorStyle] = useState3({ left: 0, top: 0 });
|
|
386
556
|
const menuListRef = useRef2(null);
|
|
387
557
|
const rangeRef = useRef2(null);
|
|
388
558
|
const lastQueryRef = useRef2("");
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
)
|
|
559
|
+
const dismissedRangeRef = useRef2(null);
|
|
560
|
+
const focusFrameRef = useRef2(null);
|
|
561
|
+
const focusTimeoutRef = useRef2(null);
|
|
562
|
+
const dismissMenu = useCallback(() => {
|
|
563
|
+
const activeRange = rangeRef.current;
|
|
564
|
+
dismissedRangeRef.current = activeRange ? `${activeRange.from}:${activeRange.to}` : null;
|
|
565
|
+
setMenuOpen(false);
|
|
566
|
+
if (editor && !editor.isDestroyed && editor.isEditable !== false)
|
|
567
|
+
editor.view.focus();
|
|
568
|
+
}, [editor]);
|
|
393
569
|
const filteredPeople = useMemo3(() => {
|
|
394
570
|
if (!query) return peopleList;
|
|
395
571
|
const lowered = query.toLowerCase();
|
|
@@ -398,14 +574,15 @@ var CommentMention = ({
|
|
|
398
574
|
);
|
|
399
575
|
}, [peopleList, query]);
|
|
400
576
|
const updateMenuState = useCallback(() => {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
577
|
+
if (!editor || !editable || editor.isEditable === false) {
|
|
578
|
+
setMenuOpen(false);
|
|
579
|
+
rangeRef.current = null;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
404
582
|
const { parent, parentOffset, pos } = editor.state.selection.$from;
|
|
405
583
|
if (!parent.isTextblock) {
|
|
406
584
|
setMenuOpen(false);
|
|
407
585
|
setQuery("");
|
|
408
|
-
setRange(null);
|
|
409
586
|
return;
|
|
410
587
|
}
|
|
411
588
|
const textBefore = parent.textBetween(0, parentOffset, "\n", "\0");
|
|
@@ -417,13 +594,12 @@ var CommentMention = ({
|
|
|
417
594
|
onQueryChange(handleQuery);
|
|
418
595
|
}
|
|
419
596
|
setQuery(handleQuery);
|
|
420
|
-
setMenuOpen(true);
|
|
421
597
|
const fromPos = pos - match[0].length;
|
|
422
598
|
const nextRange = { from: fromPos, to: pos };
|
|
423
|
-
|
|
599
|
+
setMenuOpen(dismissedRangeRef.current !== `${fromPos}:${pos}`);
|
|
424
600
|
rangeRef.current = nextRange;
|
|
425
601
|
const coords = editor.view.coordsAtPos(fromPos);
|
|
426
|
-
const containerRect =
|
|
602
|
+
const containerRect = editorContainerRef?.current?.getBoundingClientRect?.();
|
|
427
603
|
if (containerRect) {
|
|
428
604
|
setAnchorStyle({
|
|
429
605
|
left: coords.left - containerRect.left + 12,
|
|
@@ -434,16 +610,16 @@ var CommentMention = ({
|
|
|
434
610
|
}
|
|
435
611
|
setMenuOpen(false);
|
|
436
612
|
setQuery("");
|
|
437
|
-
setRange(null);
|
|
438
613
|
rangeRef.current = null;
|
|
439
614
|
if (onQueryChange && lastQueryRef.current !== "") {
|
|
440
615
|
lastQueryRef.current = "";
|
|
441
616
|
onQueryChange("");
|
|
442
617
|
}
|
|
443
|
-
}, [editor, editorContainerRef, onQueryChange]);
|
|
618
|
+
}, [editor, editorContainerRef, onQueryChange, editable]);
|
|
444
619
|
const handleSelectPerson = useCallback(
|
|
445
620
|
(person) => {
|
|
446
|
-
if (!editor || !rangeRef.current)
|
|
621
|
+
if (!editor || !editable || editor.isEditable === false || !rangeRef.current)
|
|
622
|
+
return;
|
|
447
623
|
const selectionRange = rangeRef.current;
|
|
448
624
|
editor.commands.focus();
|
|
449
625
|
editor.commands.setTextSelection(selectionRange);
|
|
@@ -463,21 +639,34 @@ var CommentMention = ({
|
|
|
463
639
|
{ updateSelection: false }
|
|
464
640
|
);
|
|
465
641
|
const nextPos = selectionRange.from + 2;
|
|
466
|
-
|
|
642
|
+
editor.view.focus();
|
|
643
|
+
cancelAnimationFrame(focusFrameRef.current);
|
|
644
|
+
clearTimeout(focusTimeoutRef.current);
|
|
645
|
+
focusFrameRef.current = requestAnimationFrame(() => {
|
|
646
|
+
if (editor.isDestroyed || editor.isEditable === false) return;
|
|
467
647
|
editor.commands.setTextSelection(nextPos);
|
|
468
648
|
editor.view.focus();
|
|
469
|
-
setTimeout(() =>
|
|
649
|
+
focusTimeoutRef.current = setTimeout(() => {
|
|
650
|
+
if (!editor.isDestroyed && editor.isEditable !== false)
|
|
651
|
+
editor.view.focus();
|
|
652
|
+
}, 0);
|
|
470
653
|
});
|
|
471
654
|
setMenuOpen(false);
|
|
472
655
|
setQuery("");
|
|
473
|
-
setRange(null);
|
|
474
656
|
rangeRef.current = null;
|
|
475
657
|
if (onQueryChange && lastQueryRef.current !== "") {
|
|
476
658
|
lastQueryRef.current = "";
|
|
477
659
|
onQueryChange("");
|
|
478
660
|
}
|
|
479
661
|
},
|
|
480
|
-
[editor, onQueryChange]
|
|
662
|
+
[editor, onQueryChange, editable]
|
|
663
|
+
);
|
|
664
|
+
useEffect3(
|
|
665
|
+
() => () => {
|
|
666
|
+
cancelAnimationFrame(focusFrameRef.current);
|
|
667
|
+
clearTimeout(focusTimeoutRef.current);
|
|
668
|
+
},
|
|
669
|
+
[editor]
|
|
481
670
|
);
|
|
482
671
|
useEffect3(() => {
|
|
483
672
|
if (!editor) return;
|
|
@@ -490,25 +679,30 @@ var CommentMention = ({
|
|
|
490
679
|
};
|
|
491
680
|
}, [editor, updateMenuState]);
|
|
492
681
|
useEffect3(() => {
|
|
493
|
-
var _a;
|
|
494
682
|
if (!editor) return;
|
|
495
683
|
const handleKeyDown = (event) => {
|
|
496
|
-
var _a2;
|
|
497
684
|
if (!menuOpen) return;
|
|
685
|
+
if (event.key === "Escape") {
|
|
686
|
+
event.preventDefault();
|
|
687
|
+
event.stopPropagation();
|
|
688
|
+
dismissMenu();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
498
691
|
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
|
|
499
692
|
event.preventDefault();
|
|
693
|
+
event.stopPropagation();
|
|
500
694
|
const list = menuListRef.current;
|
|
501
|
-
const items = list
|
|
502
|
-
if (!list || !
|
|
695
|
+
const items = list?.querySelectorAll("[data-menu-item]");
|
|
696
|
+
if (!list || !items?.length) return;
|
|
503
697
|
list.focus();
|
|
504
698
|
const direction = event.key === "ArrowDown" ? 1 : -1;
|
|
505
699
|
const targetIndex = direction === 1 ? 0 : items.length - 1;
|
|
506
|
-
|
|
700
|
+
items[targetIndex]?.focus();
|
|
507
701
|
};
|
|
508
|
-
const dom =
|
|
509
|
-
dom
|
|
510
|
-
return () => dom
|
|
511
|
-
}, [editor, menuOpen]);
|
|
702
|
+
const dom = editor.view?.dom;
|
|
703
|
+
dom?.addEventListener("keydown", handleKeyDown, true);
|
|
704
|
+
return () => dom?.removeEventListener("keydown", handleKeyDown, true);
|
|
705
|
+
}, [editor, menuOpen, dismissMenu]);
|
|
512
706
|
const menuContent = useMemo3(
|
|
513
707
|
() => /* @__PURE__ */ jsx4(
|
|
514
708
|
Menu,
|
|
@@ -520,6 +714,7 @@ var CommentMention = ({
|
|
|
520
714
|
{
|
|
521
715
|
type: "button",
|
|
522
716
|
"aria-hidden": true,
|
|
717
|
+
tabIndex: -1,
|
|
523
718
|
className: "absolute h-1 w-1 opacity-0",
|
|
524
719
|
style: {
|
|
525
720
|
left: `${anchorStyle.left}px`,
|
|
@@ -539,32 +734,46 @@ var CommentMention = ({
|
|
|
539
734
|
collisionPadding: 12,
|
|
540
735
|
onCloseAutoFocus: (event) => event.preventDefault()
|
|
541
736
|
},
|
|
542
|
-
children: /* @__PURE__ */ jsx4(
|
|
543
|
-
|
|
737
|
+
children: /* @__PURE__ */ jsx4(
|
|
738
|
+
MenuList,
|
|
544
739
|
{
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
740
|
+
ref: menuListRef,
|
|
741
|
+
className: "min-w-[220px]",
|
|
742
|
+
onKeyDownCapture: (event) => {
|
|
743
|
+
if (event.key === "Escape") {
|
|
744
|
+
event.preventDefault();
|
|
745
|
+
event.stopPropagation();
|
|
746
|
+
dismissMenu();
|
|
747
|
+
}
|
|
549
748
|
},
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
749
|
+
children: filteredPeople.map((person) => /* @__PURE__ */ jsx4(
|
|
750
|
+
MenuItem,
|
|
751
|
+
{
|
|
752
|
+
onMouseDown: (event) => {
|
|
753
|
+
event.preventDefault();
|
|
754
|
+
event.stopPropagation();
|
|
755
|
+
handleSelectPerson(person);
|
|
756
|
+
},
|
|
757
|
+
onClick: () => handleSelectPerson(person),
|
|
758
|
+
onPointerDown: (event) => event.preventDefault(),
|
|
759
|
+
children: /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-8", children: [
|
|
760
|
+
/* @__PURE__ */ jsx4(Avatar3, { size: "small", ...person.avatarProps }),
|
|
761
|
+
/* @__PURE__ */ jsxs4("span", { className: "flex flex-col", children: [
|
|
762
|
+
/* @__PURE__ */ jsx4("span", { className: "text-sm text-fg-base", children: person.name }),
|
|
763
|
+
/* @__PURE__ */ jsxs4("span", { className: "text-xs text-fg-subtle", children: [
|
|
764
|
+
"@",
|
|
765
|
+
person.handle
|
|
766
|
+
] })
|
|
767
|
+
] })
|
|
559
768
|
] })
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
)
|
|
769
|
+
},
|
|
770
|
+
person.id
|
|
771
|
+
))
|
|
772
|
+
}
|
|
773
|
+
)
|
|
565
774
|
}
|
|
566
775
|
),
|
|
567
|
-
[filteredPeople, handleSelectPerson, menuOpen]
|
|
776
|
+
[filteredPeople, handleSelectPerson, menuOpen, anchorStyle, dismissMenu]
|
|
568
777
|
);
|
|
569
778
|
if (!editor) return null;
|
|
570
779
|
return menuContent;
|