@openim/im-composer 1.0.0 → 1.0.2

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