@openim/im-composer 1.0.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/dist/index.js ADDED
@@ -0,0 +1,2649 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/IMComposer.tsx
9
+ import {
10
+ forwardRef,
11
+ useImperativeHandle,
12
+ useCallback as useCallback8,
13
+ useEffect as useEffect12,
14
+ useMemo,
15
+ useRef as useRef9,
16
+ useState as useState6
17
+ } from "react";
18
+ import { LexicalComposer } from "@lexical/react/LexicalComposer";
19
+ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
20
+ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
21
+ import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
22
+ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
23
+ import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
24
+ import { ListPlugin } from "@lexical/react/LexicalListPlugin";
25
+ import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
26
+ import { useLexicalComposerContext as useLexicalComposerContext12 } from "@lexical/react/LexicalComposerContext";
27
+ import { TRANSFORMERS as TRANSFORMERS3 } from "@lexical/markdown";
28
+ import { HeadingNode, QuoteNode as MarkdownQuoteNode } from "@lexical/rich-text";
29
+ import { ListNode, ListItemNode } from "@lexical/list";
30
+ import { CodeNode } from "@lexical/code";
31
+ import { LinkNode as LinkNode2, AutoLinkNode } from "@lexical/link";
32
+ import { $getRoot as $getRoot5, $getSelection as $getSelection8, $isRangeSelection as $isRangeSelection7, $createTextNode as $createTextNode7, $createParagraphNode as $createParagraphNode5 } from "lexical";
33
+
34
+ // src/nodes/MentionNode.tsx
35
+ import {
36
+ DecoratorNode
37
+ } from "lexical";
38
+ var MentionNode = class _MentionNode extends DecoratorNode {
39
+ static getType() {
40
+ return "mention";
41
+ }
42
+ static clone(node) {
43
+ return new _MentionNode(node.__userId, node.__display, node.__key);
44
+ }
45
+ constructor(userId, display, key) {
46
+ super(key);
47
+ this.__userId = userId;
48
+ this.__display = display;
49
+ }
50
+ createDOM(config) {
51
+ const span = document.createElement("span");
52
+ span.className = "im-composer-mention";
53
+ span.setAttribute("data-user-id", this.__userId);
54
+ span.setAttribute("data-lexical-mention", "true");
55
+ span.textContent = `@${this.__display}`;
56
+ return span;
57
+ }
58
+ updateDOM() {
59
+ return false;
60
+ }
61
+ exportDOM() {
62
+ const span = document.createElement("span");
63
+ span.className = "im-composer-mention";
64
+ span.setAttribute("data-user-id", this.__userId);
65
+ span.textContent = `@${this.__display}`;
66
+ return { element: span };
67
+ }
68
+ static importDOM() {
69
+ return null;
70
+ }
71
+ static importJSON(serializedNode) {
72
+ return new _MentionNode(serializedNode.userId, serializedNode.display);
73
+ }
74
+ exportJSON() {
75
+ return {
76
+ type: "mention",
77
+ version: 1,
78
+ userId: this.__userId,
79
+ display: this.__display
80
+ };
81
+ }
82
+ getUserId() {
83
+ return this.__userId;
84
+ }
85
+ getDisplay() {
86
+ return this.__display;
87
+ }
88
+ getTextContent() {
89
+ return `@${this.__userId} `;
90
+ }
91
+ isIsolated() {
92
+ return true;
93
+ }
94
+ isInline() {
95
+ return true;
96
+ }
97
+ canInsertTextBefore() {
98
+ return false;
99
+ }
100
+ canInsertTextAfter() {
101
+ return false;
102
+ }
103
+ decorate() {
104
+ return null;
105
+ }
106
+ };
107
+ function $createMentionNode(userId, display) {
108
+ return new MentionNode(userId, display);
109
+ }
110
+ function $isMentionNode(node) {
111
+ return node instanceof MentionNode;
112
+ }
113
+
114
+ // src/nodes/ImageNode.tsx
115
+ import {
116
+ DecoratorNode as DecoratorNode2,
117
+ $getNodeByKey
118
+ } from "lexical";
119
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
120
+ import { useLexicalNodeSelection } from "@lexical/react/useLexicalNodeSelection";
121
+ import { mergeRegister } from "@lexical/utils";
122
+ import {
123
+ $getSelection,
124
+ $isNodeSelection,
125
+ CLICK_COMMAND,
126
+ COMMAND_PRIORITY_LOW,
127
+ KEY_BACKSPACE_COMMAND,
128
+ KEY_DELETE_COMMAND
129
+ } from "lexical";
130
+ import { useCallback, useEffect, useRef } from "react";
131
+ import { jsx } from "react/jsx-runtime";
132
+ function ImageComponent({
133
+ src,
134
+ alt,
135
+ width,
136
+ height,
137
+ nodeKey
138
+ }) {
139
+ const [editor] = useLexicalComposerContext();
140
+ const imageRef = useRef(null);
141
+ const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
142
+ const onDelete = useCallback(
143
+ (event) => {
144
+ if (isSelected && $isNodeSelection($getSelection())) {
145
+ event.preventDefault();
146
+ editor.update(() => {
147
+ const node = $getNodeByKey(nodeKey);
148
+ if (node) {
149
+ node.remove();
150
+ }
151
+ });
152
+ return true;
153
+ }
154
+ return false;
155
+ },
156
+ [editor, isSelected, nodeKey]
157
+ );
158
+ useEffect(() => {
159
+ return mergeRegister(
160
+ editor.registerCommand(
161
+ CLICK_COMMAND,
162
+ (event) => {
163
+ if (imageRef.current && imageRef.current.contains(event.target)) {
164
+ if (!event.shiftKey) {
165
+ clearSelection();
166
+ }
167
+ setSelected(true);
168
+ return true;
169
+ }
170
+ return false;
171
+ },
172
+ COMMAND_PRIORITY_LOW
173
+ ),
174
+ editor.registerCommand(
175
+ KEY_DELETE_COMMAND,
176
+ onDelete,
177
+ COMMAND_PRIORITY_LOW
178
+ ),
179
+ editor.registerCommand(
180
+ KEY_BACKSPACE_COMMAND,
181
+ onDelete,
182
+ COMMAND_PRIORITY_LOW
183
+ )
184
+ );
185
+ }, [editor, clearSelection, setSelected, onDelete]);
186
+ return /* @__PURE__ */ jsx("div", { className: `im-composer-image-wrapper ${isSelected ? "selected" : ""}`, children: /* @__PURE__ */ jsx(
187
+ "img",
188
+ {
189
+ ref: imageRef,
190
+ src,
191
+ alt: alt || "",
192
+ width,
193
+ height,
194
+ className: "im-composer-image",
195
+ draggable: false
196
+ }
197
+ ) });
198
+ }
199
+ var ImageNode = class _ImageNode extends DecoratorNode2 {
200
+ static getType() {
201
+ return "image";
202
+ }
203
+ static clone(node) {
204
+ return new _ImageNode(node.__src, node.__alt, node.__width, node.__height, node.__key);
205
+ }
206
+ constructor(src, alt, width, height, key) {
207
+ super(key);
208
+ this.__src = src;
209
+ this.__alt = alt;
210
+ this.__width = width;
211
+ this.__height = height;
212
+ }
213
+ createDOM(config) {
214
+ const div = document.createElement("div");
215
+ div.className = "im-composer-image-container";
216
+ return div;
217
+ }
218
+ updateDOM() {
219
+ return false;
220
+ }
221
+ exportDOM(editor) {
222
+ const img = document.createElement("img");
223
+ img.src = this.__src;
224
+ img.alt = this.__alt || "";
225
+ if (this.__width) img.width = this.__width;
226
+ if (this.__height) img.height = this.__height;
227
+ return { element: img };
228
+ }
229
+ static importDOM() {
230
+ return {
231
+ img: () => ({
232
+ conversion: (element) => {
233
+ const img = element;
234
+ const src = img.src;
235
+ const alt = img.alt || "";
236
+ return { node: new _ImageNode(src, alt, img.width, img.height) };
237
+ },
238
+ priority: 0
239
+ })
240
+ };
241
+ }
242
+ static importJSON(serializedNode) {
243
+ return new _ImageNode(
244
+ serializedNode.src,
245
+ serializedNode.alt,
246
+ serializedNode.width,
247
+ serializedNode.height
248
+ );
249
+ }
250
+ exportJSON() {
251
+ return {
252
+ type: "image",
253
+ version: 1,
254
+ src: this.__src,
255
+ alt: this.__alt,
256
+ width: this.__width,
257
+ height: this.__height
258
+ };
259
+ }
260
+ getSrc() {
261
+ return this.__src;
262
+ }
263
+ getAlt() {
264
+ return this.__alt;
265
+ }
266
+ getTextContent() {
267
+ return "";
268
+ }
269
+ isInline() {
270
+ return false;
271
+ }
272
+ decorate() {
273
+ return /* @__PURE__ */ jsx(
274
+ ImageComponent,
275
+ {
276
+ src: this.__src,
277
+ alt: this.__alt,
278
+ width: this.__width,
279
+ height: this.__height,
280
+ nodeKey: this.__key
281
+ }
282
+ );
283
+ }
284
+ };
285
+ function $createImageNode(src, alt = "", width, height) {
286
+ return new ImageNode(src, alt, width, height);
287
+ }
288
+ function $isImageNode(node) {
289
+ return node instanceof ImageNode;
290
+ }
291
+
292
+ // src/nodes/EmojiNode.ts
293
+ import {
294
+ DecoratorNode as DecoratorNode3,
295
+ $applyNodeReplacement
296
+ } from "lexical";
297
+ function getEmojiUrl(emoji) {
298
+ const codePoints = [...emoji].map((char) => {
299
+ const cp = char.codePointAt(0);
300
+ if (cp === 65039) return null;
301
+ return cp?.toString(16);
302
+ }).filter(Boolean).join("-");
303
+ return `https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/${codePoints}.png`;
304
+ }
305
+ var EmojiNode = class _EmojiNode extends DecoratorNode3 {
306
+ static getType() {
307
+ return "emoji";
308
+ }
309
+ static clone(node) {
310
+ return new _EmojiNode(node.__emoji, node.__key);
311
+ }
312
+ constructor(emoji, key) {
313
+ super(key);
314
+ this.__emoji = emoji;
315
+ }
316
+ createDOM(config) {
317
+ const span = document.createElement("span");
318
+ span.className = "im-composer-emoji";
319
+ const img = document.createElement("img");
320
+ img.src = getEmojiUrl(this.__emoji);
321
+ img.alt = this.__emoji;
322
+ img.className = "im-composer-emoji-img";
323
+ img.draggable = false;
324
+ img.setAttribute("data-emoji", this.__emoji);
325
+ span.appendChild(img);
326
+ return span;
327
+ }
328
+ updateDOM() {
329
+ return false;
330
+ }
331
+ exportDOM() {
332
+ const span = document.createElement("span");
333
+ span.textContent = this.__emoji;
334
+ return { element: span };
335
+ }
336
+ static importDOM() {
337
+ return {
338
+ img: (node) => {
339
+ const img = node;
340
+ const emoji = img.getAttribute("data-emoji");
341
+ if (emoji) {
342
+ return {
343
+ conversion: () => ({ node: new _EmojiNode(emoji) }),
344
+ priority: 1
345
+ };
346
+ }
347
+ return null;
348
+ }
349
+ };
350
+ }
351
+ static importJSON(serializedNode) {
352
+ return new _EmojiNode(serializedNode.emoji);
353
+ }
354
+ exportJSON() {
355
+ return {
356
+ type: "emoji",
357
+ version: 1,
358
+ emoji: this.__emoji
359
+ };
360
+ }
361
+ getEmoji() {
362
+ return this.__emoji;
363
+ }
364
+ getTextContent() {
365
+ return this.__emoji;
366
+ }
367
+ isIsolated() {
368
+ return true;
369
+ }
370
+ isInline() {
371
+ return true;
372
+ }
373
+ canInsertTextBefore() {
374
+ return true;
375
+ }
376
+ canInsertTextAfter() {
377
+ return true;
378
+ }
379
+ decorate() {
380
+ return null;
381
+ }
382
+ };
383
+ function $createEmojiNode(emoji) {
384
+ return $applyNodeReplacement(new EmojiNode(emoji));
385
+ }
386
+ function $isEmojiNode(node) {
387
+ return node instanceof EmojiNode;
388
+ }
389
+
390
+ // src/nodes/QuoteNode.tsx
391
+ import {
392
+ DecoratorNode as DecoratorNode4,
393
+ $applyNodeReplacement as $applyNodeReplacement2,
394
+ $getNodeByKey as $getNodeByKey2
395
+ } from "lexical";
396
+ import { useLexicalComposerContext as useLexicalComposerContext2 } from "@lexical/react/LexicalComposerContext";
397
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
398
+ function QuoteMessageComponent({ title, content, nodeKey }) {
399
+ const [editor] = useLexicalComposerContext2();
400
+ const handleDelete = (e) => {
401
+ e.preventDefault();
402
+ e.stopPropagation();
403
+ editor.update(() => {
404
+ const node = $getNodeByKey2(nodeKey);
405
+ node?.remove();
406
+ });
407
+ };
408
+ return /* @__PURE__ */ jsxs("div", { className: "im-composer-quote", children: [
409
+ /* @__PURE__ */ jsxs("span", { className: "im-composer-quote-text", children: [
410
+ /* @__PURE__ */ jsx2("span", { className: "im-composer-quote-title", children: title }),
411
+ /* @__PURE__ */ jsx2("span", { className: "im-composer-quote-content", children: content })
412
+ ] }),
413
+ /* @__PURE__ */ jsx2("button", { className: "im-composer-quote-close", onClick: handleDelete, children: "\xD7" })
414
+ ] });
415
+ }
416
+ var QuoteNode = class _QuoteNode extends DecoratorNode4 {
417
+ static getType() {
418
+ return "quote-message";
419
+ }
420
+ static clone(node) {
421
+ return new _QuoteNode(node.__title, node.__content, node.__key);
422
+ }
423
+ constructor(title, content, key) {
424
+ super(key);
425
+ this.__title = title;
426
+ this.__content = content;
427
+ }
428
+ createDOM(config) {
429
+ const div = document.createElement("div");
430
+ div.className = "im-composer-quote-wrapper";
431
+ div.contentEditable = "false";
432
+ return div;
433
+ }
434
+ updateDOM() {
435
+ return false;
436
+ }
437
+ exportDOM() {
438
+ const element = document.createElement("blockquote");
439
+ element.setAttribute("data-title", this.__title);
440
+ element.textContent = this.__content;
441
+ return { element };
442
+ }
443
+ static importDOM() {
444
+ return {
445
+ blockquote: () => ({
446
+ conversion: (element) => {
447
+ const title = element.getAttribute("data-title") || "";
448
+ const content = element.textContent || "";
449
+ return { node: new _QuoteNode(title, content) };
450
+ },
451
+ priority: 0
452
+ })
453
+ };
454
+ }
455
+ static importJSON(serializedNode) {
456
+ return new _QuoteNode(serializedNode.title, serializedNode.content);
457
+ }
458
+ exportJSON() {
459
+ return {
460
+ type: "quote-message",
461
+ version: 1,
462
+ title: this.__title,
463
+ content: this.__content
464
+ };
465
+ }
466
+ getTitle() {
467
+ return this.__title;
468
+ }
469
+ getContent() {
470
+ return this.__content;
471
+ }
472
+ getTextContent() {
473
+ return "";
474
+ }
475
+ isIsolated() {
476
+ return true;
477
+ }
478
+ isInline() {
479
+ return false;
480
+ }
481
+ decorate() {
482
+ return /* @__PURE__ */ jsx2(
483
+ QuoteMessageComponent,
484
+ {
485
+ title: this.__title,
486
+ content: this.__content,
487
+ nodeKey: this.__key
488
+ }
489
+ );
490
+ }
491
+ };
492
+ function $createQuoteNode(title, content) {
493
+ return $applyNodeReplacement2(new QuoteNode(title, content));
494
+ }
495
+ function $isQuoteNode(node) {
496
+ return node instanceof QuoteNode;
497
+ }
498
+
499
+ // src/plugins/MentionPlugin.tsx
500
+ import { useEffect as useEffect2, useCallback as useCallback2, useState, useRef as useRef2 } from "react";
501
+ import { useLexicalComposerContext as useLexicalComposerContext3 } from "@lexical/react/LexicalComposerContext";
502
+ import {
503
+ $getSelection as $getSelection2,
504
+ $isRangeSelection,
505
+ COMMAND_PRIORITY_LOW as COMMAND_PRIORITY_LOW2,
506
+ COMMAND_PRIORITY_CRITICAL,
507
+ KEY_ARROW_DOWN_COMMAND,
508
+ KEY_ARROW_UP_COMMAND,
509
+ KEY_ENTER_COMMAND,
510
+ KEY_ESCAPE_COMMAND,
511
+ TextNode,
512
+ $createTextNode,
513
+ $getRoot
514
+ } from "lexical";
515
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
516
+ function MentionPlugin({ mentionProvider, enabled = true, maxMentions, renderMentionItem }) {
517
+ const [editor] = useLexicalComposerContext3();
518
+ const [isOpen, setIsOpen] = useState(false);
519
+ const [members, setMembers] = useState([]);
520
+ const [selectedIndex, setSelectedIndex] = useState(0);
521
+ const [matchInfo, setMatchInfo] = useState(null);
522
+ const [cursorBottom, setCursorBottom] = useState(0);
523
+ const [menuLeft, setMenuLeft] = useState(0);
524
+ const isComposingRef = useRef2(false);
525
+ const menuRef = useRef2(null);
526
+ const itemRefs = useRef2(/* @__PURE__ */ new Map());
527
+ const checkForMention = useCallback2(() => {
528
+ if (!enabled || isComposingRef.current) {
529
+ setIsOpen(false);
530
+ return;
531
+ }
532
+ editor.getEditorState().read(() => {
533
+ const selection = $getSelection2();
534
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
535
+ setIsOpen(false);
536
+ return;
537
+ }
538
+ if (maxMentions !== void 0) {
539
+ const root = $getRoot();
540
+ let mentionCount = 0;
541
+ const countMentions = (node) => {
542
+ if ($isMentionNode(node)) {
543
+ mentionCount++;
544
+ }
545
+ if (node.getChildren) {
546
+ node.getChildren().forEach(countMentions);
547
+ }
548
+ };
549
+ countMentions(root);
550
+ if (mentionCount >= maxMentions) {
551
+ setIsOpen(false);
552
+ return;
553
+ }
554
+ }
555
+ const anchor = selection.anchor;
556
+ const anchorNode = anchor.getNode();
557
+ if (!(anchorNode instanceof TextNode)) {
558
+ setIsOpen(false);
559
+ return;
560
+ }
561
+ const text = anchorNode.getTextContent();
562
+ const offset = anchor.offset;
563
+ let atIndex = -1;
564
+ for (let i = offset - 1; i >= 0; i--) {
565
+ const char = text[i];
566
+ if (char === "@") {
567
+ atIndex = i;
568
+ break;
569
+ }
570
+ if (/\s/.test(char)) {
571
+ break;
572
+ }
573
+ }
574
+ if (atIndex === -1) {
575
+ setIsOpen(false);
576
+ return;
577
+ }
578
+ const query = text.slice(atIndex + 1, offset);
579
+ const replaceableString = text.slice(atIndex, offset);
580
+ setMatchInfo({
581
+ leadOffset: atIndex,
582
+ matchingString: query,
583
+ replaceableString
584
+ });
585
+ const domSelection = window.getSelection();
586
+ if (domSelection && domSelection.rangeCount > 0) {
587
+ const range = domSelection.getRangeAt(0);
588
+ const rect = range.getBoundingClientRect();
589
+ setCursorBottom(window.innerHeight - rect.top + 4);
590
+ setMenuLeft(rect.left);
591
+ }
592
+ if (mentionProvider) {
593
+ mentionProvider(query).then((results) => {
594
+ setMembers(results);
595
+ setSelectedIndex(0);
596
+ setIsOpen(results.length > 0);
597
+ });
598
+ }
599
+ });
600
+ }, [editor, enabled, mentionProvider, maxMentions]);
601
+ useEffect2(() => {
602
+ const root = editor.getRootElement();
603
+ if (!root) return;
604
+ const handleCompositionStart = () => {
605
+ isComposingRef.current = true;
606
+ };
607
+ const handleCompositionEnd = () => {
608
+ isComposingRef.current = false;
609
+ setTimeout(() => {
610
+ checkForMention();
611
+ }, 0);
612
+ };
613
+ root.addEventListener("compositionstart", handleCompositionStart);
614
+ root.addEventListener("compositionend", handleCompositionEnd);
615
+ return () => {
616
+ root.removeEventListener("compositionstart", handleCompositionStart);
617
+ root.removeEventListener("compositionend", handleCompositionEnd);
618
+ };
619
+ }, [editor, checkForMention]);
620
+ useEffect2(() => {
621
+ return editor.registerUpdateListener(({ dirtyLeaves }) => {
622
+ if (dirtyLeaves.size > 0) {
623
+ checkForMention();
624
+ }
625
+ });
626
+ }, [editor, checkForMention]);
627
+ const insertMention = useCallback2(
628
+ (member) => {
629
+ if (!matchInfo) return;
630
+ editor.update(() => {
631
+ const selection = $getSelection2();
632
+ if (!$isRangeSelection(selection)) return;
633
+ const anchor = selection.anchor;
634
+ const anchorNode = anchor.getNode();
635
+ if (!(anchorNode instanceof TextNode)) return;
636
+ const text = anchorNode.getTextContent();
637
+ const beforeAt = text.slice(0, matchInfo.leadOffset);
638
+ const afterQuery = text.slice(matchInfo.leadOffset + matchInfo.replaceableString.length);
639
+ const mentionNode = $createMentionNode(member.userId, member.display);
640
+ if (beforeAt) {
641
+ const beforeNode = $createTextNode(beforeAt);
642
+ anchorNode.replace(beforeNode);
643
+ beforeNode.insertAfter(mentionNode);
644
+ } else {
645
+ anchorNode.replace(mentionNode);
646
+ }
647
+ if (afterQuery) {
648
+ const afterNode = $createTextNode(afterQuery);
649
+ mentionNode.insertAfter(afterNode);
650
+ afterNode.selectStart();
651
+ } else {
652
+ mentionNode.selectNext();
653
+ }
654
+ });
655
+ setIsOpen(false);
656
+ setMatchInfo(null);
657
+ },
658
+ [editor, matchInfo]
659
+ );
660
+ useEffect2(() => {
661
+ if (!isOpen) return;
662
+ const removeArrowDown = editor.registerCommand(
663
+ KEY_ARROW_DOWN_COMMAND,
664
+ (event) => {
665
+ event.preventDefault();
666
+ setSelectedIndex((i) => (i + 1) % members.length);
667
+ return true;
668
+ },
669
+ COMMAND_PRIORITY_LOW2
670
+ );
671
+ const removeArrowUp = editor.registerCommand(
672
+ KEY_ARROW_UP_COMMAND,
673
+ (event) => {
674
+ event.preventDefault();
675
+ setSelectedIndex((i) => (i - 1 + members.length) % members.length);
676
+ return true;
677
+ },
678
+ COMMAND_PRIORITY_LOW2
679
+ );
680
+ const removeEnter = editor.registerCommand(
681
+ KEY_ENTER_COMMAND,
682
+ (event) => {
683
+ if (members[selectedIndex]) {
684
+ event?.preventDefault();
685
+ insertMention(members[selectedIndex]);
686
+ return true;
687
+ }
688
+ return false;
689
+ },
690
+ COMMAND_PRIORITY_CRITICAL
691
+ );
692
+ const removeEscape = editor.registerCommand(
693
+ KEY_ESCAPE_COMMAND,
694
+ () => {
695
+ setIsOpen(false);
696
+ return true;
697
+ },
698
+ COMMAND_PRIORITY_LOW2
699
+ );
700
+ return () => {
701
+ removeArrowDown();
702
+ removeArrowUp();
703
+ removeEnter();
704
+ removeEscape();
705
+ };
706
+ }, [editor, isOpen, members, selectedIndex, insertMention]);
707
+ useEffect2(() => {
708
+ if (isOpen && itemRefs.current.has(selectedIndex)) {
709
+ const item = itemRefs.current.get(selectedIndex);
710
+ item?.scrollIntoView({ block: "nearest", behavior: "smooth" });
711
+ }
712
+ }, [isOpen, selectedIndex]);
713
+ if (!isOpen || members.length === 0) {
714
+ return null;
715
+ }
716
+ return /* @__PURE__ */ jsx3(
717
+ "div",
718
+ {
719
+ ref: menuRef,
720
+ className: "im-composer-mention-menu",
721
+ style: {
722
+ position: "fixed",
723
+ bottom: cursorBottom,
724
+ left: menuLeft
725
+ },
726
+ children: members.map((member, index) => /* @__PURE__ */ jsx3(
727
+ "div",
728
+ {
729
+ ref: (el) => {
730
+ if (el) {
731
+ itemRefs.current.set(index, el);
732
+ } else {
733
+ itemRefs.current.delete(index);
734
+ }
735
+ },
736
+ className: `im-composer-mention-item ${index === selectedIndex ? "selected" : ""}`,
737
+ onClick: () => insertMention(member),
738
+ onMouseEnter: () => setSelectedIndex(index),
739
+ children: renderMentionItem ? renderMentionItem({ member, isSelected: index === selectedIndex }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
740
+ member.avatarUrl && /* @__PURE__ */ jsx3(
741
+ "img",
742
+ {
743
+ src: member.avatarUrl,
744
+ alt: "",
745
+ className: "im-composer-mention-avatar"
746
+ }
747
+ ),
748
+ /* @__PURE__ */ jsx3("span", { className: "im-composer-mention-name", children: member.display })
749
+ ] })
750
+ },
751
+ member.userId
752
+ ))
753
+ }
754
+ );
755
+ }
756
+
757
+ // src/plugins/PlainTextPastePlugin.tsx
758
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
759
+ import { useLexicalComposerContext as useLexicalComposerContext4 } from "@lexical/react/LexicalComposerContext";
760
+ import {
761
+ $getSelection as $getSelection3,
762
+ $isRangeSelection as $isRangeSelection2,
763
+ COMMAND_PRIORITY_HIGH,
764
+ PASTE_COMMAND,
765
+ $createParagraphNode,
766
+ $createTextNode as $createTextNode2
767
+ } from "lexical";
768
+ function PlainTextPastePlugin({ onFilePaste }) {
769
+ const [editor] = useLexicalComposerContext4();
770
+ const isComposingRef = useRef3(false);
771
+ useEffect3(() => {
772
+ const root = editor.getRootElement();
773
+ if (!root) return;
774
+ const handleCompositionStart = () => {
775
+ isComposingRef.current = true;
776
+ };
777
+ const handleCompositionEnd = () => {
778
+ isComposingRef.current = false;
779
+ };
780
+ root.addEventListener("compositionstart", handleCompositionStart);
781
+ root.addEventListener("compositionend", handleCompositionEnd);
782
+ return () => {
783
+ root.removeEventListener("compositionstart", handleCompositionStart);
784
+ root.removeEventListener("compositionend", handleCompositionEnd);
785
+ };
786
+ }, [editor]);
787
+ useEffect3(() => {
788
+ return editor.registerCommand(
789
+ PASTE_COMMAND,
790
+ (event) => {
791
+ if (isComposingRef.current) {
792
+ return false;
793
+ }
794
+ const clipboardData = event.clipboardData;
795
+ if (!clipboardData) return false;
796
+ if (clipboardData.files && clipboardData.files.length > 0) {
797
+ event.preventDefault();
798
+ const files = Array.from(clipboardData.files);
799
+ onFilePaste?.(files);
800
+ return true;
801
+ }
802
+ const text = clipboardData.getData("text/plain");
803
+ if (text) {
804
+ event.preventDefault();
805
+ editor.update(() => {
806
+ const selection = $getSelection3();
807
+ if (!$isRangeSelection2(selection)) return;
808
+ const lines = text.split(/\r?\n/);
809
+ if (lines.length === 1) {
810
+ selection.insertText(lines[0]);
811
+ } else {
812
+ lines.forEach((line, index) => {
813
+ if (index > 0) {
814
+ const paragraph = $createParagraphNode();
815
+ const textNode = $createTextNode2(line);
816
+ paragraph.append(textNode);
817
+ const currentSelection = $getSelection3();
818
+ if ($isRangeSelection2(currentSelection)) {
819
+ currentSelection.insertParagraph();
820
+ if (line) {
821
+ currentSelection.insertText(line);
822
+ }
823
+ }
824
+ } else {
825
+ selection.insertText(line);
826
+ }
827
+ });
828
+ }
829
+ });
830
+ return true;
831
+ }
832
+ return false;
833
+ },
834
+ COMMAND_PRIORITY_HIGH
835
+ );
836
+ }, [editor, onFilePaste]);
837
+ return null;
838
+ }
839
+
840
+ // src/plugins/RichTextPastePlugin.tsx
841
+ import { useEffect as useEffect4, useRef as useRef4 } from "react";
842
+ import { useLexicalComposerContext as useLexicalComposerContext5 } from "@lexical/react/LexicalComposerContext";
843
+ import { COMMAND_PRIORITY_HIGH as COMMAND_PRIORITY_HIGH2, PASTE_COMMAND as PASTE_COMMAND2 } from "lexical";
844
+
845
+ // src/utils/helpers.ts
846
+ function generateId() {
847
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
848
+ }
849
+ function formatFileSize(bytes) {
850
+ if (bytes === 0) return "0 B";
851
+ const units = ["B", "KB", "MB", "GB", "TB"];
852
+ const k = 1024;
853
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
854
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${units[i]}`;
855
+ }
856
+ function isMimeTypeAllowed(mime, allowedTypes) {
857
+ if (!allowedTypes || allowedTypes.length === 0) {
858
+ return true;
859
+ }
860
+ return allowedTypes.some((pattern) => {
861
+ if (pattern.endsWith("/*")) {
862
+ const prefix = pattern.slice(0, -1);
863
+ return mime.startsWith(prefix);
864
+ }
865
+ return mime === pattern;
866
+ });
867
+ }
868
+ function isImageFile(file) {
869
+ return file.type.startsWith("image/");
870
+ }
871
+ var ALLOWED_PROTOCOLS = ["http:", "https:", "mailto:"];
872
+ function isUrlProtocolAllowed(url) {
873
+ try {
874
+ const parsed = new URL(url);
875
+ return ALLOWED_PROTOCOLS.includes(parsed.protocol);
876
+ } catch {
877
+ return false;
878
+ }
879
+ }
880
+ function sanitizeUrl(url) {
881
+ if (!url) return null;
882
+ if (!url.includes("://") && !url.startsWith("mailto:")) {
883
+ url = "https://" + url;
884
+ }
885
+ if (isUrlProtocolAllowed(url)) {
886
+ return url;
887
+ }
888
+ return null;
889
+ }
890
+
891
+ // src/utils/exportPayload.ts
892
+ import { $getRoot as $getRoot2, $isElementNode } from "lexical";
893
+ import { $convertToMarkdownString, TRANSFORMERS } from "@lexical/markdown";
894
+ function exportPlainPayload(editor, attachments) {
895
+ let plainText = "";
896
+ const mentions = [];
897
+ let quote;
898
+ editor.getEditorState().read(() => {
899
+ const root = $getRoot2();
900
+ const children = root.getChildren();
901
+ children.forEach((child) => {
902
+ if ($isQuoteNode(child)) {
903
+ quote = {
904
+ title: child.getTitle(),
905
+ content: child.getContent()
906
+ };
907
+ return;
908
+ }
909
+ if (plainText.length > 0) {
910
+ plainText += "\n";
911
+ }
912
+ const descendants = $isElementNode(child) ? child.getChildren() : [];
913
+ descendants.forEach((node) => {
914
+ if ($isMentionNode(node)) {
915
+ const userId = node.getUserId();
916
+ const display = node.getDisplay();
917
+ const mentionText = `@${userId} `;
918
+ const start = plainText.length;
919
+ const end = start + mentionText.length - 1;
920
+ mentions.push({
921
+ userId,
922
+ display,
923
+ start,
924
+ end
925
+ });
926
+ plainText += mentionText;
927
+ } else {
928
+ plainText += node.getTextContent();
929
+ }
930
+ });
931
+ });
932
+ });
933
+ return {
934
+ type: "text",
935
+ plainText,
936
+ mentions,
937
+ attachments,
938
+ quote
939
+ };
940
+ }
941
+ var IMAGE_TRANSFORMER = {
942
+ dependencies: [ImageNode],
943
+ export: (node) => {
944
+ if ($isImageNode(node)) {
945
+ const src = node.getSrc();
946
+ const alt = node.getAlt();
947
+ return `![${alt}](${src})`;
948
+ }
949
+ return null;
950
+ },
951
+ type: "element",
952
+ regExp: /!\[([^\]]*)\]\(([^)]*)\)/,
953
+ // Dummy regex to satisfy type
954
+ replace: () => {
955
+ }
956
+ // Dummy replace to satisfy type
957
+ };
958
+ function exportMarkdownPayload(editor) {
959
+ let markdown = "";
960
+ editor.getEditorState().read(() => {
961
+ const allTransformers = [...TRANSFORMERS, IMAGE_TRANSFORMER];
962
+ markdown = $convertToMarkdownString(allTransformers);
963
+ });
964
+ return {
965
+ type: "markdown",
966
+ markdown
967
+ };
968
+ }
969
+
970
+ // src/utils/editorUtils.ts
971
+ import { $getRoot as $getRoot3, $createParagraphNode as $createParagraphNode2 } from "lexical";
972
+ import { $convertFromMarkdownString, TRANSFORMERS as TRANSFORMERS2 } from "@lexical/markdown";
973
+ function importMarkdownToEditor(editor, markdown) {
974
+ editor.update(() => {
975
+ const root = $getRoot3();
976
+ root.clear();
977
+ $convertFromMarkdownString(markdown, TRANSFORMERS2);
978
+ });
979
+ }
980
+ function clearEditor(editor) {
981
+ editor.update(() => {
982
+ const root = $getRoot3();
983
+ root.clear();
984
+ const paragraph = $createParagraphNode2();
985
+ root.append(paragraph);
986
+ paragraph.select();
987
+ });
988
+ }
989
+ function isEditorEmpty(editor) {
990
+ let isEmpty = true;
991
+ editor.getEditorState().read(() => {
992
+ const root = $getRoot3();
993
+ const textContent = root.getTextContent().trim();
994
+ isEmpty = textContent.length === 0;
995
+ });
996
+ return isEmpty;
997
+ }
998
+
999
+ // src/hooks/useImageUpload.ts
1000
+ import { useState as useState2, useCallback as useCallback3 } from "react";
1001
+ import { $createParagraphNode as $createParagraphNode3 } from "lexical";
1002
+ import { $insertNodes } from "lexical";
1003
+ function isSafeImageUrl(url) {
1004
+ try {
1005
+ const parsedUrl = new URL(url);
1006
+ return ["https:", "http:", "blob:", "data:"].includes(parsedUrl.protocol);
1007
+ } catch {
1008
+ return false;
1009
+ }
1010
+ }
1011
+ function useImageUpload(options = {}) {
1012
+ const { uploadImage, onUploadStart, onUploadEnd, onError } = options;
1013
+ const [uploadCount, setUploadCount] = useState2(0);
1014
+ const upload = useCallback3(
1015
+ async (file) => {
1016
+ if (!uploadImage) {
1017
+ return null;
1018
+ }
1019
+ setUploadCount((c) => c + 1);
1020
+ try {
1021
+ const result = await uploadImage(file);
1022
+ if (!isSafeImageUrl(result.url)) {
1023
+ throw new Error("Image URL must use HTTPS, HTTP, blob:, or data: protocol");
1024
+ }
1025
+ return result;
1026
+ } catch (error) {
1027
+ onError?.(error instanceof Error ? error : new Error("Upload failed"));
1028
+ return null;
1029
+ } finally {
1030
+ setUploadCount((c) => c - 1);
1031
+ }
1032
+ },
1033
+ [uploadImage, onError]
1034
+ );
1035
+ const uploadAndInsert = useCallback3(
1036
+ async (file, editor) => {
1037
+ if (!uploadImage) {
1038
+ return false;
1039
+ }
1040
+ onUploadStart?.();
1041
+ setUploadCount((c) => c + 1);
1042
+ try {
1043
+ const result = await uploadImage(file);
1044
+ if (!result || !result.url) {
1045
+ return false;
1046
+ }
1047
+ if (!isSafeImageUrl(result.url)) {
1048
+ throw new Error("Image URL must use HTTPS, HTTP, blob:, or data: protocol");
1049
+ }
1050
+ editor.update(() => {
1051
+ const imageNode = $createImageNode(result.url, result.alt || file.name);
1052
+ const paragraphNode = $createParagraphNode3();
1053
+ $insertNodes([imageNode, paragraphNode]);
1054
+ paragraphNode.select();
1055
+ });
1056
+ setTimeout(() => {
1057
+ editor.focus();
1058
+ }, 0);
1059
+ return true;
1060
+ } catch (error) {
1061
+ onError?.(error instanceof Error ? error : new Error("Upload failed"));
1062
+ return false;
1063
+ } finally {
1064
+ setUploadCount((c) => c - 1);
1065
+ onUploadEnd?.();
1066
+ }
1067
+ },
1068
+ [uploadImage, onUploadStart, onUploadEnd, onError]
1069
+ );
1070
+ return {
1071
+ isUploading: uploadCount > 0,
1072
+ uploadCount,
1073
+ upload,
1074
+ uploadAndInsert
1075
+ };
1076
+ }
1077
+
1078
+ // src/plugins/RichTextPastePlugin.tsx
1079
+ function RichTextPastePlugin({
1080
+ uploadImage,
1081
+ onUploadStart,
1082
+ onUploadEnd,
1083
+ onError
1084
+ }) {
1085
+ const [editor] = useLexicalComposerContext5();
1086
+ const isComposingRef = useRef4(false);
1087
+ const { uploadAndInsert } = useImageUpload({
1088
+ uploadImage,
1089
+ onUploadStart,
1090
+ onUploadEnd,
1091
+ onError
1092
+ });
1093
+ useEffect4(() => {
1094
+ const root = editor.getRootElement();
1095
+ if (!root) return;
1096
+ const handleCompositionStart = () => {
1097
+ isComposingRef.current = true;
1098
+ };
1099
+ const handleCompositionEnd = () => {
1100
+ isComposingRef.current = false;
1101
+ };
1102
+ root.addEventListener("compositionstart", handleCompositionStart);
1103
+ root.addEventListener("compositionend", handleCompositionEnd);
1104
+ return () => {
1105
+ root.removeEventListener("compositionstart", handleCompositionStart);
1106
+ root.removeEventListener("compositionend", handleCompositionEnd);
1107
+ };
1108
+ }, [editor]);
1109
+ useEffect4(() => {
1110
+ return editor.registerCommand(
1111
+ PASTE_COMMAND2,
1112
+ (event) => {
1113
+ if (isComposingRef.current) {
1114
+ return false;
1115
+ }
1116
+ const clipboardData = event.clipboardData;
1117
+ if (!clipboardData) return false;
1118
+ if (clipboardData.files && clipboardData.files.length > 0 && uploadImage) {
1119
+ const files = Array.from(clipboardData.files);
1120
+ const imageFiles = files.filter(isImageFile);
1121
+ if (imageFiles.length > 0) {
1122
+ event.preventDefault();
1123
+ imageFiles.forEach(async (file) => {
1124
+ await uploadAndInsert(file, editor);
1125
+ });
1126
+ return true;
1127
+ }
1128
+ }
1129
+ return false;
1130
+ },
1131
+ COMMAND_PRIORITY_HIGH2
1132
+ );
1133
+ }, [editor, uploadImage, uploadAndInsert]);
1134
+ return null;
1135
+ }
1136
+
1137
+ // src/plugins/ToolbarPlugin.tsx
1138
+ import { useCallback as useCallback4, useState as useState3, useRef as useRef5, useEffect as useEffect5 } from "react";
1139
+ import { useLexicalComposerContext as useLexicalComposerContext6 } from "@lexical/react/LexicalComposerContext";
1140
+ import {
1141
+ $getSelection as $getSelection4,
1142
+ $isRangeSelection as $isRangeSelection3,
1143
+ $setSelection,
1144
+ FORMAT_TEXT_COMMAND,
1145
+ $createTextNode as $createTextNode4
1146
+ } from "lexical";
1147
+ import {
1148
+ $setBlocksType
1149
+ } from "@lexical/selection";
1150
+ import { $createHeadingNode, $createQuoteNode as $createQuoteNode2 } from "@lexical/rich-text";
1151
+ import {
1152
+ INSERT_ORDERED_LIST_COMMAND,
1153
+ INSERT_UNORDERED_LIST_COMMAND
1154
+ } from "@lexical/list";
1155
+ import { $createCodeNode } from "@lexical/code";
1156
+ import { $createLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
1157
+
1158
+ // src/components/Icons.tsx
1159
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1160
+ var BoldIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1161
+ /* @__PURE__ */ jsx4("path", { d: "M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" }),
1162
+ /* @__PURE__ */ jsx4("path", { d: "M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" })
1163
+ ] });
1164
+ var ItalicIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1165
+ /* @__PURE__ */ jsx4("line", { x1: "19", y1: "4", x2: "10", y2: "4" }),
1166
+ /* @__PURE__ */ jsx4("line", { x1: "14", y1: "20", x2: "5", y2: "20" }),
1167
+ /* @__PURE__ */ jsx4("line", { x1: "15", y1: "4", x2: "9", y2: "20" })
1168
+ ] });
1169
+ var StrikeIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1170
+ /* @__PURE__ */ jsx4("path", { d: "M17.3 19c-1.4 1.3-3.2 2-5.3 2-4.7 0-8.5-4-8.5-9s3.8-9 8.5-9c2.1 0 3.9.7 5.3 2" }),
1171
+ /* @__PURE__ */ jsx4("line", { x1: "4", y1: "12", x2: "20", y2: "12" })
1172
+ ] });
1173
+ var H1Icon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1174
+ /* @__PURE__ */ jsx4("path", { d: "M4 12h8" }),
1175
+ /* @__PURE__ */ jsx4("path", { d: "M4 18V6" }),
1176
+ /* @__PURE__ */ jsx4("path", { d: "M12 18V6" }),
1177
+ /* @__PURE__ */ jsx4("path", { d: "m17 12 3-2v8" })
1178
+ ] });
1179
+ var H2Icon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1180
+ /* @__PURE__ */ jsx4("path", { d: "M4 12h8" }),
1181
+ /* @__PURE__ */ jsx4("path", { d: "M4 18V6" }),
1182
+ /* @__PURE__ */ jsx4("path", { d: "M12 18V6" }),
1183
+ /* @__PURE__ */ jsx4("path", { d: "M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1" })
1184
+ ] });
1185
+ var H3Icon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1186
+ /* @__PURE__ */ jsx4("path", { d: "M4 12h8" }),
1187
+ /* @__PURE__ */ jsx4("path", { d: "M4 18V6" }),
1188
+ /* @__PURE__ */ jsx4("path", { d: "M12 18V6" }),
1189
+ /* @__PURE__ */ jsx4("path", { d: "M17.5 11.5c.5-.5 2.5-.5 3 0s.5 2 0 2.5-2 .5-2 .5 1.5 0 2 .5.5 2.5 0 3-2.5.5-3 0" })
1190
+ ] });
1191
+ var BulletListIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1192
+ /* @__PURE__ */ jsx4("line", { x1: "8", y1: "6", x2: "21", y2: "6" }),
1193
+ /* @__PURE__ */ jsx4("line", { x1: "8", y1: "12", x2: "21", y2: "12" }),
1194
+ /* @__PURE__ */ jsx4("line", { x1: "8", y1: "18", x2: "21", y2: "18" }),
1195
+ /* @__PURE__ */ jsx4("line", { x1: "3", y1: "6", x2: "3.01", y2: "6" }),
1196
+ /* @__PURE__ */ jsx4("line", { x1: "3", y1: "12", x2: "3.01", y2: "12" }),
1197
+ /* @__PURE__ */ jsx4("line", { x1: "3", y1: "18", x2: "3.01", y2: "18" })
1198
+ ] });
1199
+ var OrderedListIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1200
+ /* @__PURE__ */ jsx4("line", { x1: "10", y1: "6", x2: "21", y2: "6" }),
1201
+ /* @__PURE__ */ jsx4("line", { x1: "10", y1: "12", x2: "21", y2: "12" }),
1202
+ /* @__PURE__ */ jsx4("line", { x1: "10", y1: "18", x2: "21", y2: "18" }),
1203
+ /* @__PURE__ */ jsx4("path", { d: "M4 6h1v4" }),
1204
+ /* @__PURE__ */ jsx4("path", { d: "M4 10h2" }),
1205
+ /* @__PURE__ */ jsx4("path", { d: "M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" })
1206
+ ] });
1207
+ var QuoteIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1208
+ /* @__PURE__ */ jsx4("path", { d: "M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z", transform: "scale(0.8) translate(3, 3)" }),
1209
+ /* @__PURE__ */ jsx4("path", { d: "M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z", transform: "scale(0.8) translate(3, 3)" })
1210
+ ] });
1211
+ var CodeIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1212
+ /* @__PURE__ */ jsx4("polyline", { points: "16 18 22 12 16 6" }),
1213
+ /* @__PURE__ */ jsx4("polyline", { points: "8 6 2 12 8 18" })
1214
+ ] });
1215
+ var LinkIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1216
+ /* @__PURE__ */ jsx4("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
1217
+ /* @__PURE__ */ jsx4("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
1218
+ ] });
1219
+ var ImageIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1220
+ /* @__PURE__ */ jsx4("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
1221
+ /* @__PURE__ */ jsx4("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
1222
+ /* @__PURE__ */ jsx4("polyline", { points: "21 15 16 10 5 21" })
1223
+ ] });
1224
+
1225
+ // src/LocaleContext.tsx
1226
+ import { createContext, useContext } from "react";
1227
+ var defaultLocale = {
1228
+ linkDialog: {
1229
+ textLabel: "Text",
1230
+ urlLabel: "URL",
1231
+ cancelButton: "Cancel",
1232
+ insertButton: "Insert",
1233
+ saveButton: "Save"
1234
+ },
1235
+ toolbar: {
1236
+ bold: "Bold",
1237
+ italic: "Italic",
1238
+ strikethrough: "Strikethrough",
1239
+ code: "Code",
1240
+ heading1: "Heading 1",
1241
+ heading2: "Heading 2",
1242
+ heading3: "Heading 3",
1243
+ bulletList: "Bullet List",
1244
+ orderedList: "Ordered List",
1245
+ quote: "Quote",
1246
+ codeBlock: "Code Block",
1247
+ link: "Insert Link",
1248
+ image: "Insert Image"
1249
+ }
1250
+ };
1251
+ var LocaleContext = createContext(defaultLocale);
1252
+ function useLocale() {
1253
+ return useContext(LocaleContext);
1254
+ }
1255
+ function mergeLocale(custom) {
1256
+ if (!custom) return defaultLocale;
1257
+ return {
1258
+ linkDialog: { ...defaultLocale.linkDialog, ...custom.linkDialog },
1259
+ toolbar: { ...defaultLocale.toolbar, ...custom.toolbar }
1260
+ };
1261
+ }
1262
+
1263
+ // src/plugins/ToolbarPlugin.tsx
1264
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1265
+ function ToolbarPlugin({
1266
+ uploadImage,
1267
+ enabledSyntax,
1268
+ onUploadStart,
1269
+ onUploadEnd,
1270
+ onError
1271
+ }) {
1272
+ const [editor] = useLexicalComposerContext6();
1273
+ const locale = useLocale();
1274
+ const [showLinkDialog, setShowLinkDialog] = useState3(false);
1275
+ const [linkUrl, setLinkUrl] = useState3("");
1276
+ const [linkText, setLinkText] = useState3("");
1277
+ const [savedSelection, setSavedSelection] = useState3(null);
1278
+ const fileInputRef = useRef5(null);
1279
+ const [isBold, setIsBold] = useState3(false);
1280
+ const [isItalic, setIsItalic] = useState3(false);
1281
+ const [isStrike, setIsStrike] = useState3(false);
1282
+ const [isCode, setIsCode] = useState3(false);
1283
+ const { uploadAndInsert } = useImageUpload({
1284
+ uploadImage,
1285
+ onUploadStart,
1286
+ onUploadEnd,
1287
+ onError
1288
+ });
1289
+ const updateToolbar = useCallback4(() => {
1290
+ const selection = $getSelection4();
1291
+ if ($isRangeSelection3(selection)) {
1292
+ setIsBold(selection.hasFormat("bold"));
1293
+ setIsItalic(selection.hasFormat("italic"));
1294
+ setIsStrike(selection.hasFormat("strikethrough"));
1295
+ setIsCode(selection.hasFormat("code"));
1296
+ }
1297
+ }, []);
1298
+ useEffect5(() => {
1299
+ return editor.registerUpdateListener(({ editorState }) => {
1300
+ editorState.read(() => {
1301
+ updateToolbar();
1302
+ });
1303
+ });
1304
+ }, [editor, updateToolbar]);
1305
+ const syntax = {
1306
+ heading: enabledSyntax?.heading ?? true,
1307
+ bold: enabledSyntax?.bold ?? true,
1308
+ italic: enabledSyntax?.italic ?? true,
1309
+ strike: enabledSyntax?.strike ?? true,
1310
+ codeInline: enabledSyntax?.codeInline ?? true,
1311
+ codeBlock: enabledSyntax?.codeBlock ?? true,
1312
+ quote: enabledSyntax?.quote ?? true,
1313
+ list: enabledSyntax?.list ?? true,
1314
+ link: enabledSyntax?.link ?? true,
1315
+ image: enabledSyntax?.image ?? true
1316
+ };
1317
+ const formatBold = useCallback4(() => {
1318
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
1319
+ }, [editor]);
1320
+ const formatItalic = useCallback4(() => {
1321
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
1322
+ }, [editor]);
1323
+ const formatStrike = useCallback4(() => {
1324
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough");
1325
+ }, [editor]);
1326
+ const formatHeading = useCallback4(
1327
+ (headingSize) => {
1328
+ editor.update(() => {
1329
+ const selection = $getSelection4();
1330
+ if ($isRangeSelection3(selection)) {
1331
+ $setBlocksType(selection, () => $createHeadingNode(headingSize));
1332
+ }
1333
+ });
1334
+ },
1335
+ [editor]
1336
+ );
1337
+ const formatBulletList = useCallback4(() => {
1338
+ editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, void 0);
1339
+ }, [editor]);
1340
+ const formatOrderedList = useCallback4(() => {
1341
+ editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, void 0);
1342
+ }, [editor]);
1343
+ const formatQuote = useCallback4(() => {
1344
+ editor.update(() => {
1345
+ const selection = $getSelection4();
1346
+ if ($isRangeSelection3(selection)) {
1347
+ $setBlocksType(selection, () => $createQuoteNode2());
1348
+ }
1349
+ });
1350
+ }, [editor]);
1351
+ const formatCodeBlock = useCallback4(() => {
1352
+ editor.update(() => {
1353
+ const selection = $getSelection4();
1354
+ if ($isRangeSelection3(selection)) {
1355
+ $setBlocksType(selection, () => $createCodeNode());
1356
+ }
1357
+ });
1358
+ }, [editor]);
1359
+ const handleLinkSubmit = useCallback4(() => {
1360
+ const url = sanitizeUrl(linkUrl);
1361
+ if (!url) {
1362
+ onError?.(new Error("Invalid URL"));
1363
+ return;
1364
+ }
1365
+ editor.update(() => {
1366
+ if (savedSelection) {
1367
+ $setSelection(savedSelection);
1368
+ }
1369
+ const selection = $getSelection4();
1370
+ if ($isRangeSelection3(selection)) {
1371
+ if (selection.isCollapsed()) {
1372
+ const linkNode = $createLinkNode(url);
1373
+ const textNode = $createTextNode4(linkText || url);
1374
+ linkNode.append(textNode);
1375
+ const { $insertNodes: $insertNodes2 } = __require("lexical");
1376
+ $insertNodes2([linkNode]);
1377
+ } else {
1378
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
1379
+ }
1380
+ }
1381
+ });
1382
+ setSavedSelection(null);
1383
+ setShowLinkDialog(false);
1384
+ setLinkUrl("");
1385
+ setLinkText("");
1386
+ }, [editor, linkUrl, linkText, savedSelection, onError]);
1387
+ const handleImageSelect = useCallback4(
1388
+ async (event) => {
1389
+ const file = event.target.files?.[0];
1390
+ if (!file) return;
1391
+ event.target.value = "";
1392
+ await uploadAndInsert(file, editor);
1393
+ },
1394
+ [editor, uploadAndInsert]
1395
+ );
1396
+ return /* @__PURE__ */ jsxs4("div", { className: "im-composer-toolbar", children: [
1397
+ syntax.bold && /* @__PURE__ */ jsx5(
1398
+ "button",
1399
+ {
1400
+ type: "button",
1401
+ className: `im-composer-toolbar-btn ${isBold ? "active" : ""}`,
1402
+ onClick: formatBold,
1403
+ title: "Bold (Ctrl+B)",
1404
+ children: /* @__PURE__ */ jsx5(BoldIcon, {})
1405
+ }
1406
+ ),
1407
+ syntax.italic && /* @__PURE__ */ jsx5(
1408
+ "button",
1409
+ {
1410
+ type: "button",
1411
+ className: `im-composer-toolbar-btn ${isItalic ? "active" : ""}`,
1412
+ onClick: formatItalic,
1413
+ title: "Italic (Ctrl+I)",
1414
+ children: /* @__PURE__ */ jsx5(ItalicIcon, {})
1415
+ }
1416
+ ),
1417
+ syntax.strike && /* @__PURE__ */ jsx5(
1418
+ "button",
1419
+ {
1420
+ type: "button",
1421
+ className: `im-composer-toolbar-btn ${isStrike ? "active" : ""}`,
1422
+ onClick: formatStrike,
1423
+ title: "Strikethrough",
1424
+ children: /* @__PURE__ */ jsx5(StrikeIcon, {})
1425
+ }
1426
+ ),
1427
+ /* @__PURE__ */ jsx5("span", { className: "im-composer-toolbar-divider" }),
1428
+ syntax.heading && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1429
+ /* @__PURE__ */ jsx5(
1430
+ "button",
1431
+ {
1432
+ type: "button",
1433
+ className: "im-composer-toolbar-btn",
1434
+ onClick: () => formatHeading("h1"),
1435
+ title: "Heading 1",
1436
+ children: /* @__PURE__ */ jsx5(H1Icon, {})
1437
+ }
1438
+ ),
1439
+ /* @__PURE__ */ jsx5(
1440
+ "button",
1441
+ {
1442
+ type: "button",
1443
+ className: "im-composer-toolbar-btn",
1444
+ onClick: () => formatHeading("h2"),
1445
+ title: "Heading 2",
1446
+ children: /* @__PURE__ */ jsx5(H2Icon, {})
1447
+ }
1448
+ ),
1449
+ /* @__PURE__ */ jsx5(
1450
+ "button",
1451
+ {
1452
+ type: "button",
1453
+ className: "im-composer-toolbar-btn",
1454
+ onClick: () => formatHeading("h3"),
1455
+ title: "Heading 3",
1456
+ children: /* @__PURE__ */ jsx5(H3Icon, {})
1457
+ }
1458
+ )
1459
+ ] }),
1460
+ syntax.list && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1461
+ /* @__PURE__ */ jsx5(
1462
+ "button",
1463
+ {
1464
+ type: "button",
1465
+ className: "im-composer-toolbar-btn",
1466
+ onClick: formatBulletList,
1467
+ title: "Bullet List",
1468
+ children: /* @__PURE__ */ jsx5(BulletListIcon, {})
1469
+ }
1470
+ ),
1471
+ /* @__PURE__ */ jsx5(
1472
+ "button",
1473
+ {
1474
+ type: "button",
1475
+ className: "im-composer-toolbar-btn",
1476
+ onClick: formatOrderedList,
1477
+ title: "Ordered List",
1478
+ children: /* @__PURE__ */ jsx5(OrderedListIcon, {})
1479
+ }
1480
+ )
1481
+ ] }),
1482
+ syntax.quote && /* @__PURE__ */ jsx5(
1483
+ "button",
1484
+ {
1485
+ type: "button",
1486
+ className: "im-composer-toolbar-btn",
1487
+ onClick: formatQuote,
1488
+ title: "Quote",
1489
+ children: /* @__PURE__ */ jsx5(QuoteIcon, {})
1490
+ }
1491
+ ),
1492
+ syntax.codeBlock && /* @__PURE__ */ jsx5(
1493
+ "button",
1494
+ {
1495
+ type: "button",
1496
+ className: "im-composer-toolbar-btn",
1497
+ onClick: formatCodeBlock,
1498
+ title: "Code Block",
1499
+ children: /* @__PURE__ */ jsx5(CodeIcon, {})
1500
+ }
1501
+ ),
1502
+ /* @__PURE__ */ jsx5("span", { className: "im-composer-toolbar-divider" }),
1503
+ syntax.link && /* @__PURE__ */ jsx5(
1504
+ "button",
1505
+ {
1506
+ type: "button",
1507
+ className: "im-composer-toolbar-btn",
1508
+ onClick: () => {
1509
+ editor.getEditorState().read(() => {
1510
+ const selection = $getSelection4();
1511
+ if ($isRangeSelection3(selection)) {
1512
+ setSavedSelection(selection.clone());
1513
+ const selectedText = selection.getTextContent();
1514
+ if (selectedText) {
1515
+ setLinkText(selectedText);
1516
+ }
1517
+ }
1518
+ });
1519
+ setShowLinkDialog(true);
1520
+ },
1521
+ title: "Insert Link",
1522
+ children: /* @__PURE__ */ jsx5(LinkIcon, {})
1523
+ }
1524
+ ),
1525
+ syntax.image && uploadImage && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1526
+ /* @__PURE__ */ jsx5(
1527
+ "button",
1528
+ {
1529
+ type: "button",
1530
+ className: "im-composer-toolbar-btn",
1531
+ onClick: () => fileInputRef.current?.click(),
1532
+ title: "Insert Image",
1533
+ children: /* @__PURE__ */ jsx5(ImageIcon, {})
1534
+ }
1535
+ ),
1536
+ /* @__PURE__ */ jsx5(
1537
+ "input",
1538
+ {
1539
+ ref: fileInputRef,
1540
+ type: "file",
1541
+ accept: "image/*",
1542
+ style: { display: "none" },
1543
+ onChange: handleImageSelect
1544
+ }
1545
+ )
1546
+ ] }),
1547
+ showLinkDialog && /* @__PURE__ */ jsx5("div", { className: "im-composer-link-edit-popup ignore-drag", style: { position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)", zIndex: 99999, width: 220 }, children: /* @__PURE__ */ jsxs4("div", { className: "im-composer-link-edit-content", children: [
1548
+ /* @__PURE__ */ jsxs4("div", { className: "im-composer-link-edit-field", children: [
1549
+ /* @__PURE__ */ jsx5("label", { children: locale.linkDialog.textLabel }),
1550
+ /* @__PURE__ */ jsx5(
1551
+ "input",
1552
+ {
1553
+ type: "text",
1554
+ value: linkText,
1555
+ onChange: (e) => setLinkText(e.target.value),
1556
+ autoFocus: true
1557
+ }
1558
+ )
1559
+ ] }),
1560
+ /* @__PURE__ */ jsxs4("div", { className: "im-composer-link-edit-field", children: [
1561
+ /* @__PURE__ */ jsx5("label", { children: locale.linkDialog.urlLabel }),
1562
+ /* @__PURE__ */ jsx5(
1563
+ "input",
1564
+ {
1565
+ type: "url",
1566
+ value: linkUrl,
1567
+ onChange: (e) => setLinkUrl(e.target.value),
1568
+ placeholder: "https://example.com"
1569
+ }
1570
+ )
1571
+ ] }),
1572
+ /* @__PURE__ */ jsxs4("div", { className: "im-composer-link-edit-buttons", children: [
1573
+ /* @__PURE__ */ jsx5(
1574
+ "button",
1575
+ {
1576
+ type: "button",
1577
+ className: "remove",
1578
+ onClick: () => {
1579
+ setShowLinkDialog(false);
1580
+ setLinkUrl("");
1581
+ setLinkText("");
1582
+ setSavedSelection(null);
1583
+ },
1584
+ children: locale.linkDialog.cancelButton
1585
+ }
1586
+ ),
1587
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "save", onClick: handleLinkSubmit, children: locale.linkDialog.insertButton })
1588
+ ] })
1589
+ ] }) })
1590
+ ] });
1591
+ }
1592
+
1593
+ // src/plugins/KeymapPlugin.tsx
1594
+ import { useEffect as useEffect6, useRef as useRef6, useCallback as useCallback5 } from "react";
1595
+ import { useLexicalComposerContext as useLexicalComposerContext7 } from "@lexical/react/LexicalComposerContext";
1596
+ import {
1597
+ COMMAND_PRIORITY_HIGH as COMMAND_PRIORITY_HIGH3,
1598
+ KEY_ENTER_COMMAND as KEY_ENTER_COMMAND2
1599
+ } from "lexical";
1600
+ function KeymapPlugin({ sendKey = "enter", onSend, canSend, disabled }) {
1601
+ const [editor] = useLexicalComposerContext7();
1602
+ const isComposingRef = useRef6(false);
1603
+ useEffect6(() => {
1604
+ const root = editor.getRootElement();
1605
+ if (!root) return;
1606
+ const handleCompositionStart = () => {
1607
+ isComposingRef.current = true;
1608
+ };
1609
+ const handleCompositionEnd = () => {
1610
+ isComposingRef.current = false;
1611
+ };
1612
+ root.addEventListener("compositionstart", handleCompositionStart);
1613
+ root.addEventListener("compositionend", handleCompositionEnd);
1614
+ return () => {
1615
+ root.removeEventListener("compositionstart", handleCompositionStart);
1616
+ root.removeEventListener("compositionend", handleCompositionEnd);
1617
+ };
1618
+ }, [editor]);
1619
+ const shouldSend = useCallback5(
1620
+ (event) => {
1621
+ if (!event || disabled || isComposingRef.current) return false;
1622
+ switch (sendKey) {
1623
+ case "enter":
1624
+ return !event.shiftKey && !event.ctrlKey && !event.metaKey;
1625
+ case "ctrlEnter":
1626
+ return event.ctrlKey && !event.metaKey;
1627
+ case "cmdEnter":
1628
+ return event.metaKey && !event.ctrlKey;
1629
+ default:
1630
+ return false;
1631
+ }
1632
+ },
1633
+ [sendKey, disabled]
1634
+ );
1635
+ useEffect6(() => {
1636
+ return editor.registerCommand(
1637
+ KEY_ENTER_COMMAND2,
1638
+ (event) => {
1639
+ if (shouldSend(event) && canSend()) {
1640
+ event?.preventDefault();
1641
+ onSend();
1642
+ return true;
1643
+ }
1644
+ return false;
1645
+ },
1646
+ COMMAND_PRIORITY_HIGH3
1647
+ );
1648
+ }, [editor, shouldSend, canSend, onSend]);
1649
+ return null;
1650
+ }
1651
+
1652
+ // src/plugins/DeletionPlugin.tsx
1653
+ import { useLexicalComposerContext as useLexicalComposerContext8 } from "@lexical/react/LexicalComposerContext";
1654
+ import {
1655
+ $getSelection as $getSelection5,
1656
+ $isRangeSelection as $isRangeSelection4,
1657
+ KEY_BACKSPACE_COMMAND as KEY_BACKSPACE_COMMAND2,
1658
+ COMMAND_PRIORITY_HIGH as COMMAND_PRIORITY_HIGH4,
1659
+ $isNodeSelection as $isNodeSelection2,
1660
+ $isElementNode as $isElementNode2,
1661
+ $isTextNode
1662
+ } from "lexical";
1663
+ import { useEffect as useEffect7 } from "react";
1664
+ function DeletionPlugin() {
1665
+ const [editor] = useLexicalComposerContext8();
1666
+ useEffect7(() => {
1667
+ return editor.registerCommand(
1668
+ KEY_BACKSPACE_COMMAND2,
1669
+ (event) => {
1670
+ const selection = $getSelection5();
1671
+ if ($isRangeSelection4(selection) && selection.isCollapsed()) {
1672
+ const anchor = selection.anchor;
1673
+ const anchorNode = anchor.getNode();
1674
+ if (anchor.offset === 0) {
1675
+ let prevNode = anchorNode.getPreviousSibling();
1676
+ if (!prevNode) {
1677
+ const parent = anchorNode.getParent();
1678
+ if (parent) {
1679
+ prevNode = parent.getPreviousSibling();
1680
+ }
1681
+ }
1682
+ if (!prevNode || !$isImageNode(prevNode)) {
1683
+ const topElement = anchorNode.getTopLevelElement();
1684
+ if (topElement) {
1685
+ const topPrevSibling = topElement.getPreviousSibling();
1686
+ if (topPrevSibling && $isImageNode(topPrevSibling)) {
1687
+ prevNode = topPrevSibling;
1688
+ }
1689
+ }
1690
+ }
1691
+ if (prevNode && $isImageNode(prevNode)) {
1692
+ event.preventDefault();
1693
+ prevNode.remove();
1694
+ return true;
1695
+ }
1696
+ const topLevelElement = anchorNode.getTopLevelElement();
1697
+ if (topLevelElement) {
1698
+ const isAtStartOfBlock = anchor.type === "text" && anchor.offset === 0 && anchorNode.getPreviousSibling() === null || anchor.type === "element" && anchor.offset === 0;
1699
+ if (isAtStartOfBlock) {
1700
+ const quotePrevSibling = topLevelElement.getPreviousSibling();
1701
+ if ($isQuoteNode(quotePrevSibling)) {
1702
+ quotePrevSibling.remove();
1703
+ return true;
1704
+ }
1705
+ }
1706
+ }
1707
+ }
1708
+ let rawPrevNode = null;
1709
+ if ($isTextNode(anchorNode)) {
1710
+ if (anchor.offset === 0) {
1711
+ rawPrevNode = anchorNode.getPreviousSibling();
1712
+ }
1713
+ } else if ($isElementNode2(anchorNode)) {
1714
+ if (anchor.offset > 0) {
1715
+ const children = anchorNode.getChildren();
1716
+ rawPrevNode = children[anchor.offset - 1];
1717
+ }
1718
+ }
1719
+ if ($isEmojiNode(rawPrevNode) || $isMentionNode(rawPrevNode)) {
1720
+ rawPrevNode.remove();
1721
+ return true;
1722
+ }
1723
+ } else if ($isNodeSelection2(selection)) {
1724
+ const nodes = selection.getNodes();
1725
+ let handled = false;
1726
+ for (const node of nodes) {
1727
+ if ($isQuoteNode(node) || $isEmojiNode(node) || $isMentionNode(node) || $isImageNode(node)) {
1728
+ node.remove();
1729
+ handled = true;
1730
+ }
1731
+ }
1732
+ if (handled) return true;
1733
+ }
1734
+ return false;
1735
+ },
1736
+ COMMAND_PRIORITY_HIGH4
1737
+ );
1738
+ }, [editor]);
1739
+ return null;
1740
+ }
1741
+
1742
+ // src/plugins/QuoteRemovalListenerPlugin.tsx
1743
+ import { useLexicalComposerContext as useLexicalComposerContext9 } from "@lexical/react/LexicalComposerContext";
1744
+ import { useEffect as useEffect8 } from "react";
1745
+ function QuoteRemovalListenerPlugin({ onQuoteRemoved }) {
1746
+ const [editor] = useLexicalComposerContext9();
1747
+ useEffect8(() => {
1748
+ if (!onQuoteRemoved) return;
1749
+ return editor.registerMutationListener(QuoteNode, (mutations) => {
1750
+ let hasDestroyed = false;
1751
+ let hasCreated = false;
1752
+ for (const [, mutation] of mutations) {
1753
+ if (mutation === "destroyed") {
1754
+ hasDestroyed = true;
1755
+ }
1756
+ if (mutation === "created") {
1757
+ hasCreated = true;
1758
+ }
1759
+ }
1760
+ if (hasDestroyed && !hasCreated) {
1761
+ onQuoteRemoved();
1762
+ }
1763
+ });
1764
+ }, [editor, onQuoteRemoved]);
1765
+ return null;
1766
+ }
1767
+
1768
+ // src/plugins/LinkShortcutPlugin.tsx
1769
+ import { useEffect as useEffect9 } from "react";
1770
+ import { useLexicalComposerContext as useLexicalComposerContext10 } from "@lexical/react/LexicalComposerContext";
1771
+ import {
1772
+ $getSelection as $getSelection6,
1773
+ $isRangeSelection as $isRangeSelection5,
1774
+ $createTextNode as $createTextNode5,
1775
+ $isTextNode as $isTextNode2
1776
+ } from "lexical";
1777
+ import { $createLinkNode as $createLinkNode2 } from "@lexical/link";
1778
+ var MARKDOWN_LINK_REGEX = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)\s$/;
1779
+ function LinkShortcutPlugin() {
1780
+ const [editor] = useLexicalComposerContext10();
1781
+ useEffect9(() => {
1782
+ const removeListener = editor.registerUpdateListener(({ editorState, prevEditorState, tags }) => {
1783
+ if (tags.has("history-merge") || tags.has("collaboration")) {
1784
+ return;
1785
+ }
1786
+ editorState.read(() => {
1787
+ const selection = $getSelection6();
1788
+ if (!$isRangeSelection5(selection) || !selection.isCollapsed()) {
1789
+ return;
1790
+ }
1791
+ const anchorNode = selection.anchor.getNode();
1792
+ if (!$isTextNode2(anchorNode)) {
1793
+ return;
1794
+ }
1795
+ const textContent = anchorNode.getTextContent();
1796
+ const match = textContent.match(MARKDOWN_LINK_REGEX);
1797
+ if (!match) {
1798
+ return;
1799
+ }
1800
+ const [fullMatch, linkText, url] = match;
1801
+ try {
1802
+ new URL(url);
1803
+ } catch {
1804
+ return;
1805
+ }
1806
+ editor.update(() => {
1807
+ const currentSelection = $getSelection6();
1808
+ if (!$isRangeSelection5(currentSelection) || !currentSelection.isCollapsed()) {
1809
+ return;
1810
+ }
1811
+ const currentNode = currentSelection.anchor.getNode();
1812
+ if (!$isTextNode2(currentNode)) {
1813
+ return;
1814
+ }
1815
+ const currentText = currentNode.getTextContent();
1816
+ const currentMatch = currentText.match(MARKDOWN_LINK_REGEX);
1817
+ if (!currentMatch) {
1818
+ return;
1819
+ }
1820
+ const matchStart = currentText.indexOf(currentMatch[0]);
1821
+ const beforeText = currentText.slice(0, matchStart);
1822
+ const afterText = "";
1823
+ const linkNode = $createLinkNode2(currentMatch[2]);
1824
+ const linkTextNode = $createTextNode5(currentMatch[1]);
1825
+ linkNode.append(linkTextNode);
1826
+ const spaceNode = $createTextNode5(" ");
1827
+ if (beforeText) {
1828
+ const beforeNode = $createTextNode5(beforeText);
1829
+ currentNode.replace(beforeNode);
1830
+ beforeNode.insertAfter(linkNode);
1831
+ linkNode.insertAfter(spaceNode);
1832
+ } else {
1833
+ currentNode.replace(linkNode);
1834
+ linkNode.insertAfter(spaceNode);
1835
+ }
1836
+ spaceNode.select(1, 1);
1837
+ });
1838
+ });
1839
+ });
1840
+ return removeListener;
1841
+ }, [editor]);
1842
+ return null;
1843
+ }
1844
+
1845
+ // src/plugins/LinkEditPlugin.tsx
1846
+ import { useCallback as useCallback6, useEffect as useEffect10, useState as useState4, useRef as useRef7 } from "react";
1847
+ import { createPortal } from "react-dom";
1848
+ import { useLexicalComposerContext as useLexicalComposerContext11 } from "@lexical/react/LexicalComposerContext";
1849
+ import {
1850
+ $getSelection as $getSelection7,
1851
+ $isRangeSelection as $isRangeSelection6,
1852
+ $createTextNode as $createTextNode6,
1853
+ COMMAND_PRIORITY_LOW as COMMAND_PRIORITY_LOW3,
1854
+ CLICK_COMMAND as CLICK_COMMAND2,
1855
+ $getNodeByKey as $getNodeByKey3
1856
+ } from "lexical";
1857
+ import { $isLinkNode } from "@lexical/link";
1858
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1859
+ var POPUP_WIDTH = 220;
1860
+ var POPUP_HEIGHT = 160;
1861
+ var POPUP_MARGIN = 6;
1862
+ function LinkEditPlugin() {
1863
+ const [editor] = useLexicalComposerContext11();
1864
+ const locale = useLocale();
1865
+ const [editState, setEditState] = useState4({
1866
+ isOpen: false,
1867
+ linkKey: null,
1868
+ text: "",
1869
+ url: "",
1870
+ position: { top: 0, left: 0 }
1871
+ });
1872
+ const popupRef = useRef7(null);
1873
+ const calculatePosition = useCallback6((linkRect, editorRect) => {
1874
+ let top = linkRect.bottom + POPUP_MARGIN;
1875
+ let left = linkRect.left;
1876
+ const maxRight = editorRect.right - POPUP_MARGIN;
1877
+ if (left + POPUP_WIDTH > maxRight) {
1878
+ left = Math.max(editorRect.left + POPUP_MARGIN, maxRight - POPUP_WIDTH);
1879
+ }
1880
+ const minLeft = editorRect.left + POPUP_MARGIN;
1881
+ if (left < minLeft) {
1882
+ left = minLeft;
1883
+ }
1884
+ const maxBottom = editorRect.bottom - POPUP_MARGIN;
1885
+ if (top + POPUP_HEIGHT > maxBottom) {
1886
+ top = linkRect.top - POPUP_HEIGHT - POPUP_MARGIN;
1887
+ }
1888
+ const minTop = editorRect.top + POPUP_MARGIN;
1889
+ if (top < minTop) {
1890
+ top = minTop;
1891
+ }
1892
+ return { top, left };
1893
+ }, []);
1894
+ useEffect10(() => {
1895
+ if (!editState.isOpen) return;
1896
+ const handleClickOutside = (event) => {
1897
+ if (popupRef.current && !popupRef.current.contains(event.target)) {
1898
+ setEditState((prev) => ({ ...prev, isOpen: false }));
1899
+ }
1900
+ };
1901
+ const timer = setTimeout(() => {
1902
+ document.addEventListener("mousedown", handleClickOutside, true);
1903
+ }, 50);
1904
+ return () => {
1905
+ clearTimeout(timer);
1906
+ document.removeEventListener("mousedown", handleClickOutside, true);
1907
+ };
1908
+ }, [editState.isOpen]);
1909
+ useEffect10(() => {
1910
+ return editor.registerCommand(
1911
+ CLICK_COMMAND2,
1912
+ (event) => {
1913
+ const target = event.target;
1914
+ const linkElement = target.closest("a");
1915
+ const editorElement = editor.getRootElement();
1916
+ if (linkElement && editorElement && editorElement.contains(linkElement)) {
1917
+ event.preventDefault();
1918
+ event.stopPropagation();
1919
+ const composerContainer = editorElement.closest(".im-composer") || editorElement;
1920
+ const containerRect = composerContainer.getBoundingClientRect();
1921
+ const linkRect = linkElement.getBoundingClientRect();
1922
+ const position = calculatePosition(linkRect, containerRect);
1923
+ editor.getEditorState().read(() => {
1924
+ const selection = $getSelection7();
1925
+ if (!$isRangeSelection6(selection)) return;
1926
+ const node = selection.anchor.getNode();
1927
+ const parent = node.getParent();
1928
+ const linkNode = $isLinkNode(node) ? node : $isLinkNode(parent) ? parent : null;
1929
+ if (linkNode && $isLinkNode(linkNode)) {
1930
+ setEditState({
1931
+ isOpen: true,
1932
+ linkKey: linkNode.getKey(),
1933
+ text: linkNode.getTextContent(),
1934
+ url: linkNode.getURL(),
1935
+ position
1936
+ });
1937
+ }
1938
+ });
1939
+ return true;
1940
+ }
1941
+ return false;
1942
+ },
1943
+ COMMAND_PRIORITY_LOW3
1944
+ );
1945
+ }, [editor, calculatePosition]);
1946
+ const handleSave = useCallback6(() => {
1947
+ if (!editState.linkKey) return;
1948
+ const url = sanitizeUrl(editState.url);
1949
+ if (!url) return;
1950
+ editor.update(() => {
1951
+ const linkNode = $getNodeByKey3(editState.linkKey);
1952
+ if (linkNode && $isLinkNode(linkNode)) {
1953
+ linkNode.setURL(url);
1954
+ const currentText = linkNode.getTextContent();
1955
+ if (currentText !== editState.text && editState.text.trim()) {
1956
+ const children = linkNode.getChildren();
1957
+ if (children.length > 0) {
1958
+ for (let i = children.length - 1; i > 0; i--) {
1959
+ children[i].remove();
1960
+ }
1961
+ const firstChild = children[0];
1962
+ if (firstChild.getType() === "text") {
1963
+ firstChild.setTextContent(editState.text);
1964
+ } else {
1965
+ const newTextNode = $createTextNode6(editState.text);
1966
+ firstChild.replace(newTextNode);
1967
+ }
1968
+ } else {
1969
+ linkNode.append($createTextNode6(editState.text));
1970
+ }
1971
+ }
1972
+ }
1973
+ });
1974
+ setEditState((prev) => ({ ...prev, isOpen: false }));
1975
+ }, [editor, editState]);
1976
+ const handleCancel = useCallback6(() => {
1977
+ setEditState((prev) => ({ ...prev, isOpen: false }));
1978
+ }, []);
1979
+ const handleKeyDown = useCallback6((e) => {
1980
+ if (e.key === "Enter") {
1981
+ e.preventDefault();
1982
+ handleSave();
1983
+ } else if (e.key === "Escape") {
1984
+ handleCancel();
1985
+ }
1986
+ }, [handleSave, handleCancel]);
1987
+ if (!editState.isOpen) {
1988
+ return null;
1989
+ }
1990
+ return createPortal(
1991
+ /* @__PURE__ */ jsx6(
1992
+ "div",
1993
+ {
1994
+ ref: popupRef,
1995
+ className: "im-composer-link-edit-popup ignore-drag",
1996
+ style: {
1997
+ position: "fixed",
1998
+ top: editState.position.top,
1999
+ left: editState.position.left,
2000
+ width: POPUP_WIDTH,
2001
+ zIndex: 99999
2002
+ },
2003
+ onKeyDown: handleKeyDown,
2004
+ children: /* @__PURE__ */ jsxs5("div", { className: "im-composer-link-edit-content", children: [
2005
+ /* @__PURE__ */ jsxs5("div", { className: "im-composer-link-edit-field", children: [
2006
+ /* @__PURE__ */ jsx6("label", { children: locale.linkDialog.textLabel }),
2007
+ /* @__PURE__ */ jsx6(
2008
+ "input",
2009
+ {
2010
+ type: "text",
2011
+ value: editState.text,
2012
+ onChange: (e) => setEditState((prev) => ({ ...prev, text: e.target.value })),
2013
+ autoFocus: true
2014
+ }
2015
+ )
2016
+ ] }),
2017
+ /* @__PURE__ */ jsxs5("div", { className: "im-composer-link-edit-field", children: [
2018
+ /* @__PURE__ */ jsx6("label", { children: locale.linkDialog.urlLabel }),
2019
+ /* @__PURE__ */ jsx6(
2020
+ "input",
2021
+ {
2022
+ type: "text",
2023
+ value: editState.url,
2024
+ onChange: (e) => setEditState((prev) => ({ ...prev, url: e.target.value }))
2025
+ }
2026
+ )
2027
+ ] }),
2028
+ /* @__PURE__ */ jsxs5("div", { className: "im-composer-link-edit-buttons", children: [
2029
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: handleCancel, className: "remove", children: locale.linkDialog.cancelButton }),
2030
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: handleSave, className: "save", children: locale.linkDialog.saveButton })
2031
+ ] })
2032
+ ] })
2033
+ }
2034
+ ),
2035
+ document.body
2036
+ );
2037
+ }
2038
+
2039
+ // src/components/AttachmentPreviewBar.tsx
2040
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2041
+ function AttachmentPreviewBar({
2042
+ attachments,
2043
+ onRemove,
2044
+ placement = "bottom"
2045
+ }) {
2046
+ if (attachments.length === 0) {
2047
+ return null;
2048
+ }
2049
+ return /* @__PURE__ */ jsx7("div", { className: `im-composer-attachments im-composer-attachments-${placement}`, children: attachments.map((attachment) => /* @__PURE__ */ jsxs6("div", { className: "im-composer-attachment-item", children: [
2050
+ attachment.previewUrl && isImageFile(attachment.file) ? /* @__PURE__ */ jsx7(
2051
+ "img",
2052
+ {
2053
+ src: attachment.previewUrl,
2054
+ alt: attachment.name,
2055
+ className: "im-composer-attachment-preview"
2056
+ }
2057
+ ) : /* @__PURE__ */ jsx7("div", { className: "im-composer-attachment-icon", children: "\u{1F4C4}" }),
2058
+ /* @__PURE__ */ jsxs6("div", { className: "im-composer-attachment-info", children: [
2059
+ /* @__PURE__ */ jsx7("span", { className: "im-composer-attachment-name", title: attachment.name, children: attachment.name }),
2060
+ /* @__PURE__ */ jsx7("span", { className: "im-composer-attachment-size", children: formatFileSize(attachment.size) })
2061
+ ] }),
2062
+ /* @__PURE__ */ jsx7(
2063
+ "button",
2064
+ {
2065
+ type: "button",
2066
+ className: "im-composer-attachment-remove",
2067
+ onClick: () => onRemove(attachment.id),
2068
+ title: "Remove attachment",
2069
+ children: "\xD7"
2070
+ }
2071
+ )
2072
+ ] }, attachment.id)) });
2073
+ }
2074
+
2075
+ // src/hooks/useAttachments.ts
2076
+ import { useState as useState5, useCallback as useCallback7, useEffect as useEffect11, useRef as useRef8 } from "react";
2077
+ function useAttachments(options = {}) {
2078
+ const {
2079
+ maxAttachments = 10,
2080
+ maxFileSize,
2081
+ allowedMimeTypes,
2082
+ onLimitExceeded
2083
+ } = options;
2084
+ const [attachments, setAttachments] = useState5([]);
2085
+ const previewUrlsRef = useRef8(/* @__PURE__ */ new Set());
2086
+ useEffect11(() => {
2087
+ return () => {
2088
+ previewUrlsRef.current.forEach((url) => {
2089
+ URL.revokeObjectURL(url);
2090
+ });
2091
+ previewUrlsRef.current.clear();
2092
+ };
2093
+ }, []);
2094
+ const createPreviewUrl = useCallback7((file) => {
2095
+ if (isImageFile(file)) {
2096
+ const url = URL.createObjectURL(file);
2097
+ previewUrlsRef.current.add(url);
2098
+ return url;
2099
+ }
2100
+ return void 0;
2101
+ }, []);
2102
+ const revokePreviewUrl = useCallback7((url) => {
2103
+ if (url && previewUrlsRef.current.has(url)) {
2104
+ URL.revokeObjectURL(url);
2105
+ previewUrlsRef.current.delete(url);
2106
+ }
2107
+ }, []);
2108
+ const addFiles = useCallback7(
2109
+ (files) => {
2110
+ const fileArray = Array.from(files);
2111
+ setAttachments((prev) => {
2112
+ const newAttachments = [];
2113
+ for (const file of fileArray) {
2114
+ if (prev.length + newAttachments.length >= maxAttachments) {
2115
+ onLimitExceeded?.("count", file);
2116
+ continue;
2117
+ }
2118
+ if (maxFileSize && file.size > maxFileSize) {
2119
+ onLimitExceeded?.("size", file);
2120
+ continue;
2121
+ }
2122
+ if (!isMimeTypeAllowed(file.type, allowedMimeTypes)) {
2123
+ onLimitExceeded?.("mime", file);
2124
+ continue;
2125
+ }
2126
+ const attachment = {
2127
+ id: generateId(),
2128
+ file,
2129
+ name: file.name,
2130
+ size: file.size,
2131
+ mime: file.type || "application/octet-stream",
2132
+ lastModified: file.lastModified,
2133
+ previewUrl: createPreviewUrl(file)
2134
+ };
2135
+ newAttachments.push(attachment);
2136
+ }
2137
+ return [...prev, ...newAttachments];
2138
+ });
2139
+ },
2140
+ [maxAttachments, maxFileSize, allowedMimeTypes, onLimitExceeded, createPreviewUrl]
2141
+ );
2142
+ const removeAttachment = useCallback7(
2143
+ (id) => {
2144
+ setAttachments((prev) => {
2145
+ const attachment = prev.find((a) => a.id === id);
2146
+ if (attachment) {
2147
+ revokePreviewUrl(attachment.previewUrl);
2148
+ }
2149
+ return prev.filter((a) => a.id !== id);
2150
+ });
2151
+ },
2152
+ [revokePreviewUrl]
2153
+ );
2154
+ const clearAttachments = useCallback7(() => {
2155
+ setAttachments((prev) => {
2156
+ prev.forEach((attachment) => {
2157
+ revokePreviewUrl(attachment.previewUrl);
2158
+ });
2159
+ return [];
2160
+ });
2161
+ }, [revokePreviewUrl]);
2162
+ const setAttachmentsExternal = useCallback7(
2163
+ (newAttachments) => {
2164
+ attachments.forEach((attachment) => {
2165
+ revokePreviewUrl(attachment.previewUrl);
2166
+ });
2167
+ setAttachments(newAttachments);
2168
+ },
2169
+ [attachments, revokePreviewUrl]
2170
+ );
2171
+ return {
2172
+ attachments,
2173
+ addFiles,
2174
+ removeAttachment,
2175
+ clearAttachments,
2176
+ setAttachments: setAttachmentsExternal
2177
+ };
2178
+ }
2179
+
2180
+ // src/themes/EditorTheme.ts
2181
+ var EditorTheme = {
2182
+ ltr: "ltr",
2183
+ rtl: "rtl",
2184
+ paragraph: "im-composer-paragraph",
2185
+ quote: "im-composer-quote",
2186
+ heading: {
2187
+ h1: "im-composer-heading-h1",
2188
+ h2: "im-composer-heading-h2",
2189
+ h3: "im-composer-heading-h3",
2190
+ h4: "im-composer-heading-h4",
2191
+ h5: "im-composer-heading-h5",
2192
+ h6: "im-composer-heading-h6"
2193
+ },
2194
+ list: {
2195
+ nested: {
2196
+ listitem: "im-composer-nested-listitem"
2197
+ },
2198
+ ol: "im-composer-list-ol",
2199
+ ul: "im-composer-list-ul",
2200
+ listitem: "im-composer-list-item"
2201
+ },
2202
+ image: "im-composer-image",
2203
+ link: "im-composer-link",
2204
+ text: {
2205
+ bold: "im-composer-text-bold",
2206
+ italic: "im-composer-text-italic",
2207
+ overflowed: "im-composer-text-overflowed",
2208
+ hashtag: "im-composer-text-hashtag",
2209
+ underline: "im-composer-text-underline",
2210
+ strikethrough: "im-composer-text-strikethrough",
2211
+ underlineStrikethrough: "im-composer-text-underline-strikethrough",
2212
+ code: "im-composer-text-code"
2213
+ },
2214
+ code: "im-composer-code"
2215
+ };
2216
+
2217
+ // src/IMComposer.tsx
2218
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2219
+ function EditorInner({
2220
+ mode,
2221
+ onSend,
2222
+ enableMention = true,
2223
+ mentionProvider,
2224
+ maxMentions,
2225
+ renderMentionItem,
2226
+ enableAttachments = true,
2227
+ showAttachmentPreview = true,
2228
+ attachmentPreviewPlacement = "bottom",
2229
+ attachments,
2230
+ onFilePaste,
2231
+ onRemoveAttachment,
2232
+ markdownOptions,
2233
+ uploadImage,
2234
+ keymap,
2235
+ placeholder,
2236
+ disabled,
2237
+ isUploading,
2238
+ onUploadStart,
2239
+ onUploadEnd,
2240
+ editorRef,
2241
+ onQuoteRemoved,
2242
+ onChange,
2243
+ className,
2244
+ ...restProps
2245
+ }) {
2246
+ const [editor] = useLexicalComposerContext12();
2247
+ editorRef.current = editor;
2248
+ useEffect12(() => {
2249
+ if (!onChange) return;
2250
+ return editor.registerUpdateListener(({ editorState, dirtyElements, dirtyLeaves }) => {
2251
+ if (dirtyElements.size > 0 || dirtyLeaves.size > 0) {
2252
+ onChange();
2253
+ }
2254
+ });
2255
+ }, [editor, onChange]);
2256
+ const placeholderText = useMemo(() => {
2257
+ if (typeof placeholder === "string") return placeholder;
2258
+ if (typeof placeholder === "object") {
2259
+ const text = mode === "plain" ? placeholder.plain : placeholder.rich;
2260
+ if (text) return text;
2261
+ }
2262
+ return mode === "plain" ? "Type a message..." : "Type with Markdown...";
2263
+ }, [placeholder, mode]);
2264
+ const canSend = useCallback8(() => {
2265
+ if (disabled || isUploading) return false;
2266
+ const isEmpty = isEditorEmpty(editor);
2267
+ if (mode === "plain" && attachments.length > 0) {
2268
+ return true;
2269
+ }
2270
+ return !isEmpty;
2271
+ }, [editor, disabled, isUploading, mode, attachments.length]);
2272
+ const handleSend = useCallback8(() => {
2273
+ if (!canSend()) return;
2274
+ if (mode === "plain") {
2275
+ const payload = exportPlainPayload(editor, attachments);
2276
+ onSend?.(payload);
2277
+ } else {
2278
+ const payload = exportMarkdownPayload(editor);
2279
+ onSend?.(payload);
2280
+ }
2281
+ }, [editor, mode, attachments, onSend, canSend]);
2282
+ const handleError = useCallback8((error) => {
2283
+ console.error("IMComposer error:", error);
2284
+ }, []);
2285
+ const mergedClassName = [
2286
+ "im-composer",
2287
+ `im-composer-${mode}`,
2288
+ disabled ? "im-composer-disabled" : "",
2289
+ className
2290
+ ].filter(Boolean).join(" ");
2291
+ return /* @__PURE__ */ jsxs7(
2292
+ "div",
2293
+ {
2294
+ className: mergedClassName,
2295
+ ...restProps,
2296
+ children: [
2297
+ mode === "rich" && /* @__PURE__ */ jsx8(
2298
+ ToolbarPlugin,
2299
+ {
2300
+ uploadImage,
2301
+ enabledSyntax: markdownOptions?.enabledSyntax,
2302
+ onUploadStart,
2303
+ onUploadEnd,
2304
+ onError: handleError
2305
+ }
2306
+ ),
2307
+ mode === "plain" && attachmentPreviewPlacement === "top" && enableAttachments && showAttachmentPreview && /* @__PURE__ */ jsx8(
2308
+ AttachmentPreviewBar,
2309
+ {
2310
+ attachments,
2311
+ onRemove: onRemoveAttachment,
2312
+ placement: "top"
2313
+ }
2314
+ ),
2315
+ /* @__PURE__ */ jsxs7("div", { className: "im-composer-editor-container", children: [
2316
+ /* @__PURE__ */ jsx8(
2317
+ RichTextPlugin,
2318
+ {
2319
+ contentEditable: /* @__PURE__ */ jsx8(
2320
+ ContentEditable,
2321
+ {
2322
+ className: "im-composer-editor",
2323
+ "aria-placeholder": placeholderText,
2324
+ placeholder: /* @__PURE__ */ jsx8("div", { className: "im-composer-placeholder", children: placeholderText })
2325
+ }
2326
+ ),
2327
+ ErrorBoundary: LexicalErrorBoundary
2328
+ }
2329
+ ),
2330
+ /* @__PURE__ */ jsx8(HistoryPlugin, {}),
2331
+ /* @__PURE__ */ jsx8(ListPlugin, {}),
2332
+ /* @__PURE__ */ jsx8(LinkPlugin, {}),
2333
+ mode === "plain" && /* @__PURE__ */ jsxs7(Fragment3, { children: [
2334
+ /* @__PURE__ */ jsx8(
2335
+ MentionPlugin,
2336
+ {
2337
+ mentionProvider,
2338
+ enabled: enableMention,
2339
+ maxMentions,
2340
+ renderMentionItem
2341
+ }
2342
+ ),
2343
+ /* @__PURE__ */ jsx8(
2344
+ PlainTextPastePlugin,
2345
+ {
2346
+ onFilePaste: enableAttachments ? onFilePaste : void 0
2347
+ }
2348
+ ),
2349
+ /* @__PURE__ */ jsx8(DeletionPlugin, {}),
2350
+ /* @__PURE__ */ jsx8(QuoteRemovalListenerPlugin, { onQuoteRemoved })
2351
+ ] }),
2352
+ mode === "rich" && /* @__PURE__ */ jsxs7(Fragment3, { children: [
2353
+ /* @__PURE__ */ jsx8(MarkdownShortcutPlugin, { transformers: TRANSFORMERS3 }),
2354
+ /* @__PURE__ */ jsx8(
2355
+ RichTextPastePlugin,
2356
+ {
2357
+ uploadImage,
2358
+ onUploadStart,
2359
+ onUploadEnd,
2360
+ onError: handleError
2361
+ }
2362
+ ),
2363
+ /* @__PURE__ */ jsx8(LinkShortcutPlugin, {}),
2364
+ /* @__PURE__ */ jsx8(LinkEditPlugin, {}),
2365
+ /* @__PURE__ */ jsx8(DeletionPlugin, {})
2366
+ ] }),
2367
+ /* @__PURE__ */ jsx8(
2368
+ KeymapPlugin,
2369
+ {
2370
+ sendKey: keymap?.send,
2371
+ onSend: handleSend,
2372
+ canSend,
2373
+ disabled
2374
+ }
2375
+ )
2376
+ ] }),
2377
+ mode === "plain" && attachmentPreviewPlacement === "bottom" && enableAttachments && showAttachmentPreview && /* @__PURE__ */ jsx8(
2378
+ AttachmentPreviewBar,
2379
+ {
2380
+ attachments,
2381
+ onRemove: onRemoveAttachment,
2382
+ placement: "bottom"
2383
+ }
2384
+ ),
2385
+ isUploading && /* @__PURE__ */ jsx8("div", { className: "im-composer-uploading", children: "Uploading..." })
2386
+ ]
2387
+ }
2388
+ );
2389
+ }
2390
+ var IMComposer = forwardRef(
2391
+ function IMComposer2(props, ref) {
2392
+ const {
2393
+ mode: controlledMode,
2394
+ defaultMode = "plain",
2395
+ maxAttachments,
2396
+ maxFileSize,
2397
+ allowedMimeTypes,
2398
+ onAttachmentLimitExceeded,
2399
+ onFilesChange,
2400
+ ...restProps
2401
+ } = props;
2402
+ const [uncontrolledMode, setUncontrolledMode] = useState6(defaultMode);
2403
+ const mode = controlledMode ?? uncontrolledMode;
2404
+ const editorRef = useRef9(null);
2405
+ const [uploadCount, setUploadCount] = useState6(0);
2406
+ const {
2407
+ attachments,
2408
+ addFiles,
2409
+ removeAttachment,
2410
+ clearAttachments,
2411
+ setAttachments
2412
+ } = useAttachments({
2413
+ maxAttachments,
2414
+ maxFileSize,
2415
+ allowedMimeTypes,
2416
+ onLimitExceeded: onAttachmentLimitExceeded
2417
+ });
2418
+ const handleUploadStart = useCallback8(() => {
2419
+ setUploadCount((c) => c + 1);
2420
+ }, []);
2421
+ const handleUploadEnd = useCallback8(() => {
2422
+ setUploadCount((c) => c - 1);
2423
+ }, []);
2424
+ useEffect12(() => {
2425
+ onFilesChange?.(attachments);
2426
+ }, [attachments, onFilesChange]);
2427
+ const initialConfig = useMemo(
2428
+ () => ({
2429
+ namespace: "IMComposer",
2430
+ theme: EditorTheme,
2431
+ nodes: [
2432
+ ImageNode,
2433
+ EmojiNode,
2434
+ MentionNode,
2435
+ HeadingNode,
2436
+ MarkdownQuoteNode,
2437
+ QuoteNode,
2438
+ ListNode,
2439
+ ListItemNode,
2440
+ CodeNode,
2441
+ LinkNode2,
2442
+ AutoLinkNode
2443
+ ],
2444
+ onError: (error) => {
2445
+ console.error("Lexical error:", error);
2446
+ },
2447
+ editable: !props.disabled
2448
+ }),
2449
+ [props.disabled]
2450
+ );
2451
+ useImperativeHandle(
2452
+ ref,
2453
+ () => ({
2454
+ focus: () => {
2455
+ editorRef.current?.focus();
2456
+ },
2457
+ clear: () => {
2458
+ if (editorRef.current) {
2459
+ clearEditor(editorRef.current);
2460
+ }
2461
+ clearAttachments();
2462
+ },
2463
+ exportPayload: () => {
2464
+ if (!editorRef.current) return null;
2465
+ if (mode === "plain") {
2466
+ return exportPlainPayload(editorRef.current, attachments);
2467
+ } else {
2468
+ return exportMarkdownPayload(editorRef.current);
2469
+ }
2470
+ },
2471
+ importMarkdown: (markdown) => {
2472
+ if (editorRef.current && mode === "rich") {
2473
+ importMarkdownToEditor(editorRef.current, markdown);
2474
+ }
2475
+ },
2476
+ getAttachments: () => attachments,
2477
+ setAttachments,
2478
+ insertQuote: (title, content) => {
2479
+ if (editorRef.current && mode === "plain") {
2480
+ editorRef.current.update(() => {
2481
+ const root = $getRoot5();
2482
+ const newQuoteNode = $createQuoteNode(title, content);
2483
+ const children = root.getChildren();
2484
+ let existingQuote = null;
2485
+ for (const child of children) {
2486
+ if ($isQuoteNode(child)) {
2487
+ existingQuote = child;
2488
+ break;
2489
+ }
2490
+ }
2491
+ if (existingQuote) {
2492
+ existingQuote.replace(newQuoteNode);
2493
+ } else {
2494
+ const firstChild = root.getFirstChild();
2495
+ if (firstChild) {
2496
+ firstChild.insertBefore(newQuoteNode);
2497
+ } else {
2498
+ root.append(newQuoteNode);
2499
+ }
2500
+ }
2501
+ });
2502
+ }
2503
+ },
2504
+ addFiles,
2505
+ removeAttachment,
2506
+ clearAttachments,
2507
+ insertMention: (userId, display) => {
2508
+ if (editorRef.current && mode === "plain") {
2509
+ editorRef.current.update(() => {
2510
+ const selection = $getSelection8();
2511
+ if ($isRangeSelection7(selection)) {
2512
+ const mentionNode = $createMentionNode(userId, display);
2513
+ selection.insertNodes([mentionNode]);
2514
+ }
2515
+ });
2516
+ }
2517
+ },
2518
+ getDraft: () => {
2519
+ let editorState = "";
2520
+ let text = "";
2521
+ if (editorRef.current) {
2522
+ editorRef.current.getEditorState().read(() => {
2523
+ text = $getRoot5().getTextContent().trim();
2524
+ });
2525
+ if (text) {
2526
+ editorState = JSON.stringify(editorRef.current.getEditorState().toJSON());
2527
+ }
2528
+ }
2529
+ return {
2530
+ editorState,
2531
+ text,
2532
+ attachments: attachments.map((a) => ({
2533
+ ...a,
2534
+ file: a.file
2535
+ // Note: File objects can't be serialized to JSON
2536
+ }))
2537
+ };
2538
+ },
2539
+ setDraft: (draft) => {
2540
+ if (editorRef.current && draft.editorState) {
2541
+ try {
2542
+ const parsedState = editorRef.current.parseEditorState(draft.editorState);
2543
+ editorRef.current.setEditorState(parsedState);
2544
+ } catch (e) {
2545
+ console.error("Failed to restore editor state:", e);
2546
+ }
2547
+ }
2548
+ if (draft.attachments) {
2549
+ setAttachments(draft.attachments);
2550
+ }
2551
+ },
2552
+ setText: (text, mentions) => {
2553
+ const editor = editorRef.current;
2554
+ if (!editor) return;
2555
+ editor.update(() => {
2556
+ const root = $getRoot5();
2557
+ root.clear();
2558
+ const paragraph = $createParagraphNode5();
2559
+ if (!text) {
2560
+ root.append(paragraph);
2561
+ return;
2562
+ }
2563
+ if (!mentions || mentions.length === 0) {
2564
+ paragraph.append($createTextNode7(text));
2565
+ root.append(paragraph);
2566
+ return;
2567
+ }
2568
+ const pattern = /@(\S+?)\b/g;
2569
+ let lastIndex = 0;
2570
+ let match;
2571
+ while ((match = pattern.exec(text)) !== null) {
2572
+ const [fullMatch, userId] = match;
2573
+ const matchIndex = match.index;
2574
+ if (matchIndex > lastIndex) {
2575
+ paragraph.append($createTextNode7(text.slice(lastIndex, matchIndex)));
2576
+ }
2577
+ const member = mentions.find((m) => m.userId === userId);
2578
+ if (member) {
2579
+ const mentionNode = $createMentionNode(member.userId, member.display);
2580
+ paragraph.append(mentionNode);
2581
+ } else {
2582
+ paragraph.append($createTextNode7(fullMatch));
2583
+ }
2584
+ lastIndex = matchIndex + fullMatch.length;
2585
+ }
2586
+ if (lastIndex < text.length) {
2587
+ paragraph.append($createTextNode7(text.slice(lastIndex)));
2588
+ }
2589
+ root.append(paragraph);
2590
+ });
2591
+ },
2592
+ insertText: (text) => {
2593
+ const editor = editorRef.current;
2594
+ if (!editor) return;
2595
+ editor.update(() => {
2596
+ const selection = $getSelection8();
2597
+ if ($isRangeSelection7(selection)) {
2598
+ selection.insertText(text);
2599
+ } else {
2600
+ const root = $getRoot5();
2601
+ const lastChild = root.getLastChild();
2602
+ if (lastChild) {
2603
+ lastChild.selectEnd();
2604
+ const newSelection = $getSelection8();
2605
+ if ($isRangeSelection7(newSelection)) {
2606
+ newSelection.insertText(text);
2607
+ }
2608
+ }
2609
+ }
2610
+ });
2611
+ }
2612
+ }),
2613
+ [mode, attachments, clearAttachments, setAttachments, addFiles, removeAttachment]
2614
+ );
2615
+ const mergedLocale = useMemo(() => mergeLocale(restProps.locale), [restProps.locale]);
2616
+ return /* @__PURE__ */ jsx8(LocaleContext.Provider, { value: mergedLocale, children: /* @__PURE__ */ jsx8(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx8(
2617
+ EditorInner,
2618
+ {
2619
+ ...restProps,
2620
+ mode,
2621
+ attachments,
2622
+ onFilePaste: addFiles,
2623
+ onRemoveAttachment: removeAttachment,
2624
+ isUploading: uploadCount > 0,
2625
+ onUploadStart: handleUploadStart,
2626
+ onUploadEnd: handleUploadEnd,
2627
+ editorRef
2628
+ }
2629
+ ) }) });
2630
+ }
2631
+ );
2632
+ export {
2633
+ $createEmojiNode,
2634
+ $createImageNode,
2635
+ $createMentionNode,
2636
+ $createQuoteNode,
2637
+ $isEmojiNode,
2638
+ $isImageNode,
2639
+ $isMentionNode,
2640
+ $isQuoteNode,
2641
+ EmojiNode,
2642
+ IMComposer,
2643
+ ImageNode,
2644
+ MentionNode,
2645
+ QuoteNode,
2646
+ useAttachments,
2647
+ useImageUpload
2648
+ };
2649
+ //# sourceMappingURL=index.js.map