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