@overlap/rte 0.1.2 → 0.1.4

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.
@@ -1,828 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useRef } from "react";
2
- import { defaultPlugins } from "../plugins";
3
- import { createBlockFormatPlugin } from "../plugins/blockFormat";
4
- import {
5
- createBackgroundColorPlugin,
6
- createTextColorPlugin,
7
- } from "../plugins/colors";
8
- import { createFontSizePlugin } from "../plugins/fontSize";
9
- import { createImagePlugin } from "../plugins/image";
10
- import { EditorAPI, EditorContent, EditorProps } from "../types";
11
- import {
12
- clearBackgroundColor,
13
- clearFontSize,
14
- clearFormatting,
15
- clearLinks,
16
- clearTextColor,
17
- } from "../utils/clearFormatting";
18
- import {
19
- contentToDOM,
20
- contentToHTML,
21
- createEmptyContent,
22
- domToContent,
23
- htmlToContent,
24
- } from "../utils/content";
25
- import { HistoryManager } from "../utils/history";
26
- import { indentListItem, outdentListItem } from "../utils/listIndent";
27
- import { Toolbar } from "./Toolbar";
28
-
29
- export const Editor: React.FC<EditorProps> = ({
30
- initialContent,
31
- onChange,
32
- plugins: providedPlugins,
33
- placeholder = "Text eingeben...",
34
- className,
35
- toolbarClassName,
36
- editorClassName,
37
- fontSizes,
38
- colors,
39
- headings,
40
- customLinkComponent,
41
- customHeadingRenderer,
42
- customRenderer,
43
- onEditorAPIReady,
44
- theme,
45
- onImageUpload,
46
- }) => {
47
- const plugins = useMemo(() => {
48
- const allPlugins = [...(providedPlugins || defaultPlugins)];
49
-
50
- if (fontSizes && fontSizes.length > 0) {
51
- allPlugins.push(createFontSizePlugin(fontSizes));
52
- }
53
-
54
- if (colors && colors.length > 0) {
55
- allPlugins.push(createTextColorPlugin(colors));
56
- allPlugins.push(createBackgroundColorPlugin(colors));
57
- }
58
-
59
- // BlockFormat Plugin ist bereits in defaultPlugins enthalten
60
- // Wenn custom headings angegeben sind, ersetze das Standard-Plugin
61
- if (headings && headings.length > 0) {
62
- // Entferne das Standard-BlockFormat-Plugin
63
- const blockFormatIndex = allPlugins.findIndex(
64
- (p) => p.name === "blockFormat"
65
- );
66
- if (blockFormatIndex !== -1) {
67
- allPlugins.splice(blockFormatIndex, 1);
68
- }
69
- // Füge das Plugin mit custom Headlines hinzu
70
- allPlugins.push(createBlockFormatPlugin(headings));
71
- }
72
-
73
- allPlugins.push(createImagePlugin(onImageUpload));
74
-
75
- return allPlugins;
76
- }, [providedPlugins, fontSizes, colors, headings, onImageUpload]);
77
- const editorRef = useRef<HTMLDivElement>(null);
78
- const historyRef = useRef<HistoryManager>(new HistoryManager());
79
- const isUpdatingRef = useRef(false);
80
-
81
- const notifyChange = useCallback(
82
- (content: EditorContent) => {
83
- if (onChange && !isUpdatingRef.current) {
84
- onChange(content);
85
- }
86
- },
87
- [onChange]
88
- );
89
-
90
- const restoreSelection = useCallback((editor: HTMLElement) => {
91
- if (typeof window === "undefined" || typeof document === "undefined")
92
- return;
93
- const range = document.createRange();
94
- const selection = window.getSelection();
95
-
96
- if (editor.firstChild) {
97
- range.setStart(editor.firstChild, 0);
98
- range.collapse(true);
99
- selection?.removeAllRanges();
100
- selection?.addRange(range);
101
- }
102
- }, []);
103
-
104
- const editorAPI = useMemo<EditorAPI>(() => {
105
- const executeCommand = (command: string, value?: string): boolean => {
106
- const editor = editorRef.current;
107
- if (!editor) return false;
108
-
109
- if (
110
- command !== "undo" &&
111
- command !== "redo" &&
112
- command !== "insertImage"
113
- ) {
114
- const currentContent = domToContent(editor);
115
- historyRef.current.push(currentContent);
116
- }
117
-
118
- if (command === "undo") {
119
- const content = historyRef.current.undo();
120
- if (content && editor) {
121
- isUpdatingRef.current = true;
122
- contentToDOM(
123
- content,
124
- editor,
125
- customLinkComponent,
126
- customHeadingRenderer
127
- );
128
- restoreSelection(editor);
129
- isUpdatingRef.current = false;
130
- notifyChange(content);
131
- }
132
- return true;
133
- }
134
-
135
- if (command === "redo") {
136
- const content = historyRef.current.redo();
137
- if (content && editor) {
138
- isUpdatingRef.current = true;
139
- contentToDOM(
140
- content,
141
- editor,
142
- customLinkComponent,
143
- customHeadingRenderer
144
- );
145
- restoreSelection(editor);
146
- isUpdatingRef.current = false;
147
- notifyChange(content);
148
- }
149
- return true;
150
- }
151
-
152
- if (command === "insertImage" && value) {
153
- let selection = window.getSelection();
154
- if (!selection) return false;
155
-
156
- if (document.activeElement !== editor) {
157
- editor.focus();
158
- }
159
-
160
- if (selection.rangeCount === 0) {
161
- const range = document.createRange();
162
- if (editor.childNodes.length > 0) {
163
- const lastChild =
164
- editor.childNodes[editor.childNodes.length - 1];
165
- range.setStartAfter(lastChild);
166
- range.collapse(true);
167
- } else {
168
- const img = document.createElement("img");
169
- img.setAttribute("src", value);
170
- img.setAttribute("alt", "");
171
- img.style.maxWidth = "100%";
172
- img.style.height = "auto";
173
- img.style.display = "block";
174
- img.style.margin = "16px 0";
175
- editor.appendChild(img);
176
-
177
- const newRange = document.createRange();
178
- newRange.setStartAfter(img);
179
- newRange.collapse(true);
180
- selection.removeAllRanges();
181
- selection.addRange(newRange);
182
-
183
- isUpdatingRef.current = true;
184
- setTimeout(() => {
185
- if (editor) {
186
- const currentContent = domToContent(editor);
187
- historyRef.current.push(currentContent);
188
- isUpdatingRef.current = false;
189
- notifyChange(currentContent);
190
- }
191
- }, 0);
192
- return true;
193
- }
194
- selection.removeAllRanges();
195
- selection.addRange(range);
196
- }
197
-
198
- if (selection.rangeCount === 0) return false;
199
- const range = selection.getRangeAt(0);
200
-
201
- const container = range.commonAncestorContainer;
202
- let parentElement: HTMLElement | null = null;
203
-
204
- if (container.nodeType === Node.TEXT_NODE) {
205
- parentElement = container.parentElement;
206
- } else if (container.nodeType === Node.ELEMENT_NODE) {
207
- parentElement = container as HTMLElement;
208
- }
209
-
210
- const img = document.createElement("img");
211
- img.setAttribute("src", value);
212
- img.setAttribute("alt", "");
213
- img.style.maxWidth = "100%";
214
- img.style.height = "auto";
215
- img.style.display = "block";
216
- img.style.margin = "16px 0";
217
-
218
- if (
219
- parentElement &&
220
- parentElement !== editor &&
221
- (parentElement.tagName === "P" ||
222
- parentElement.tagName === "DIV" ||
223
- parentElement.tagName === "H1" ||
224
- parentElement.tagName === "H2" ||
225
- parentElement.tagName === "H3" ||
226
- parentElement.tagName === "H4" ||
227
- parentElement.tagName === "H5" ||
228
- parentElement.tagName === "H6")
229
- ) {
230
- if (parentElement.nextSibling) {
231
- editor.insertBefore(img, parentElement.nextSibling);
232
- } else {
233
- editor.appendChild(img);
234
- }
235
- } else {
236
- try {
237
- range.insertNode(img);
238
- } catch (e) {
239
- editor.appendChild(img);
240
- }
241
- }
242
-
243
- const newRange = document.createRange();
244
- newRange.setStartAfter(img);
245
- newRange.collapse(true);
246
- selection.removeAllRanges();
247
- selection.addRange(newRange);
248
-
249
- isUpdatingRef.current = true;
250
- setTimeout(() => {
251
- if (editor) {
252
- const currentContent = domToContent(editor);
253
- historyRef.current.push(currentContent);
254
- isUpdatingRef.current = false;
255
- notifyChange(currentContent);
256
- }
257
- }, 0);
258
-
259
- return true;
260
- }
261
-
262
- const selection = window.getSelection();
263
- let savedRange: Range | null = null;
264
-
265
- if (selection && selection.rangeCount > 0) {
266
- savedRange = selection.getRangeAt(0).cloneRange();
267
- }
268
-
269
- if (document.activeElement !== editor) {
270
- editor.focus();
271
- }
272
-
273
- if (!selection || selection.rangeCount === 0) {
274
- const range = document.createRange();
275
-
276
- if (editor.childNodes.length > 0) {
277
- const lastChild =
278
- editor.childNodes[editor.childNodes.length - 1];
279
- if (lastChild.nodeType === Node.TEXT_NODE) {
280
- range.setStart(
281
- lastChild,
282
- lastChild.textContent?.length || 0
283
- );
284
- range.setEnd(
285
- lastChild,
286
- lastChild.textContent?.length || 0
287
- );
288
- } else {
289
- range.selectNodeContents(lastChild);
290
- range.collapse(false);
291
- }
292
- } else {
293
- const p = document.createElement("p");
294
- editor.appendChild(p);
295
- const textNode = document.createTextNode("");
296
- p.appendChild(textNode);
297
- range.setStart(textNode, 0);
298
- range.setEnd(textNode, 0);
299
- }
300
-
301
- selection?.removeAllRanges();
302
- selection?.addRange(range);
303
- } else if (savedRange) {
304
- selection.removeAllRanges();
305
- selection.addRange(savedRange);
306
- }
307
-
308
- document.execCommand(command, false, value);
309
-
310
- setTimeout(() => {
311
- if (editor && !isUpdatingRef.current) {
312
- const content = domToContent(editor);
313
- notifyChange(content);
314
- }
315
- }, 0);
316
-
317
- return true;
318
- };
319
-
320
- return {
321
- executeCommand,
322
-
323
- getSelection: (): Selection | null => {
324
- if (typeof window === "undefined") return null;
325
- return window.getSelection();
326
- },
327
-
328
- getContent: (): EditorContent => {
329
- const editor = editorRef.current;
330
- if (!editor) return createEmptyContent();
331
- return domToContent(editor);
332
- },
333
-
334
- setContent: (content: EditorContent): void => {
335
- const editor = editorRef.current;
336
- if (!editor) return;
337
-
338
- isUpdatingRef.current = true;
339
- contentToDOM(
340
- content,
341
- editor,
342
- customLinkComponent,
343
- customHeadingRenderer
344
- );
345
- historyRef.current.push(content);
346
- isUpdatingRef.current = false;
347
- notifyChange(content);
348
- },
349
-
350
- insertBlock: (
351
- type: string,
352
- attributes?: Record<string, string>
353
- ): void => {
354
- const selection = window.getSelection();
355
- if (!selection || selection.rangeCount === 0) return;
356
-
357
- const range = selection.getRangeAt(0);
358
- const block = document.createElement(type);
359
-
360
- if (attributes) {
361
- Object.entries(attributes).forEach(([key, value]) => {
362
- block.setAttribute(key, value);
363
- });
364
- }
365
-
366
- range.insertNode(block);
367
- const textNode = document.createTextNode("\u200B"); // Zero-width space
368
- block.appendChild(textNode);
369
-
370
- // Cursor setzen
371
- range.setStartAfter(textNode);
372
- range.collapse(true);
373
- selection.removeAllRanges();
374
- selection.addRange(range);
375
-
376
- const editor = editorRef.current;
377
- if (editor) {
378
- const content = domToContent(editor);
379
- notifyChange(content);
380
- }
381
- },
382
-
383
- insertInline: (
384
- type: string,
385
- attributes?: Record<string, string>
386
- ): void => {
387
- const selection = window.getSelection();
388
- if (!selection || selection.rangeCount === 0) return;
389
-
390
- const range = selection.getRangeAt(0);
391
- const inline = document.createElement(type);
392
-
393
- if (attributes) {
394
- Object.entries(attributes).forEach(([key, value]) => {
395
- inline.setAttribute(key, value);
396
- });
397
- }
398
-
399
- try {
400
- range.surroundContents(inline);
401
- } catch (e) {
402
- // Falls surroundContents fehlschlägt, versuche es anders
403
- const contents = range.extractContents();
404
- inline.appendChild(contents);
405
- range.insertNode(inline);
406
- }
407
-
408
- // Cursor setzen
409
- range.setStartAfter(inline);
410
- range.collapse(true);
411
- selection.removeAllRanges();
412
- selection.addRange(range);
413
-
414
- const editor = editorRef.current;
415
- if (editor) {
416
- const content = domToContent(editor);
417
- notifyChange(content);
418
- }
419
- },
420
-
421
- undo: (): void => {
422
- executeCommand("undo");
423
- },
424
-
425
- redo: (): void => {
426
- executeCommand("redo");
427
- },
428
-
429
- canUndo: (): boolean => {
430
- return historyRef.current.canUndo();
431
- },
432
-
433
- canRedo: (): boolean => {
434
- return historyRef.current.canRedo();
435
- },
436
-
437
- importHtml: (htmlString: string): EditorContent => {
438
- const content = htmlToContent(htmlString);
439
- const editor = editorRef.current;
440
- if (editor) {
441
- isUpdatingRef.current = true;
442
- contentToDOM(
443
- content,
444
- editor,
445
- customLinkComponent,
446
- customHeadingRenderer
447
- );
448
- historyRef.current.push(content);
449
- isUpdatingRef.current = false;
450
- notifyChange(content);
451
- }
452
- return content;
453
- },
454
-
455
- exportHtml: (): string => {
456
- const editor = editorRef.current;
457
- if (!editor) return "";
458
- const content = domToContent(editor);
459
- return contentToHTML(content);
460
- },
461
-
462
- clearFormatting: (): void => {
463
- const editor = editorRef.current;
464
- if (!editor) return;
465
-
466
- const selection = window.getSelection();
467
- if (selection && selection.rangeCount > 0) {
468
- const currentContent = domToContent(editor);
469
- historyRef.current.push(currentContent);
470
-
471
- clearFormatting(selection);
472
-
473
- setTimeout(() => {
474
- if (editor) {
475
- const content = domToContent(editor);
476
- notifyChange(content);
477
- }
478
- }, 0);
479
- }
480
- },
481
-
482
- clearTextColor: (): void => {
483
- const editor = editorRef.current;
484
- if (!editor) return;
485
-
486
- const selection = window.getSelection();
487
- if (selection && selection.rangeCount > 0) {
488
- const currentContent = domToContent(editor);
489
- historyRef.current.push(currentContent);
490
-
491
- clearTextColor(selection);
492
-
493
- setTimeout(() => {
494
- if (editor) {
495
- const content = domToContent(editor);
496
- notifyChange(content);
497
- }
498
- }, 0);
499
- }
500
- },
501
-
502
- clearBackgroundColor: (): void => {
503
- const editor = editorRef.current;
504
- if (!editor) return;
505
-
506
- const selection = window.getSelection();
507
- if (selection && selection.rangeCount > 0) {
508
- const currentContent = domToContent(editor);
509
- historyRef.current.push(currentContent);
510
-
511
- clearBackgroundColor(selection);
512
-
513
- setTimeout(() => {
514
- if (editor) {
515
- const content = domToContent(editor);
516
- notifyChange(content);
517
- }
518
- }, 0);
519
- }
520
- },
521
-
522
- clearFontSize: (): void => {
523
- const editor = editorRef.current;
524
- if (!editor) return;
525
-
526
- const selection = window.getSelection();
527
- if (selection && selection.rangeCount > 0) {
528
- const currentContent = domToContent(editor);
529
- historyRef.current.push(currentContent);
530
-
531
- clearFontSize(selection);
532
-
533
- setTimeout(() => {
534
- if (editor) {
535
- const content = domToContent(editor);
536
- notifyChange(content);
537
- }
538
- }, 0);
539
- }
540
- },
541
-
542
- clearLinks: (): void => {
543
- const editor = editorRef.current;
544
- if (!editor) return;
545
-
546
- const selection = window.getSelection();
547
- if (selection && selection.rangeCount > 0) {
548
- const currentContent = domToContent(editor);
549
- historyRef.current.push(currentContent);
550
-
551
- clearLinks(selection);
552
-
553
- setTimeout(() => {
554
- if (editor) {
555
- const content = domToContent(editor);
556
- notifyChange(content);
557
- }
558
- }, 0);
559
- }
560
- },
561
-
562
- indentListItem: (): void => {
563
- const editor = editorRef.current;
564
- if (!editor) return;
565
-
566
- const selection = window.getSelection();
567
- if (selection && selection.rangeCount > 0) {
568
- const currentContent = domToContent(editor);
569
- historyRef.current.push(currentContent);
570
-
571
- indentListItem(selection);
572
-
573
- setTimeout(() => {
574
- if (editor) {
575
- const content = domToContent(editor);
576
- notifyChange(content);
577
- }
578
- }, 0);
579
- }
580
- },
581
-
582
- outdentListItem: (): void => {
583
- const editor = editorRef.current;
584
- if (!editor) return;
585
-
586
- const selection = window.getSelection();
587
- if (selection && selection.rangeCount > 0) {
588
- const currentContent = domToContent(editor);
589
- historyRef.current.push(currentContent);
590
-
591
- outdentListItem(selection);
592
-
593
- setTimeout(() => {
594
- if (editor) {
595
- const content = domToContent(editor);
596
- notifyChange(content);
597
- }
598
- }, 0);
599
- }
600
- },
601
- };
602
- }, [
603
- notifyChange,
604
- restoreSelection,
605
- customLinkComponent,
606
- customHeadingRenderer,
607
- ]);
608
-
609
- useEffect(() => {
610
- if (onEditorAPIReady) {
611
- onEditorAPIReady(editorAPI);
612
- }
613
- }, [editorAPI, onEditorAPIReady]);
614
-
615
- const isInitializedRef = useRef(false);
616
-
617
- useEffect(() => {
618
- const editor = editorRef.current;
619
- if (!editor || isInitializedRef.current) return;
620
-
621
- const content = initialContent || createEmptyContent();
622
- isUpdatingRef.current = true;
623
- contentToDOM(
624
- content,
625
- editor,
626
- customLinkComponent,
627
- customHeadingRenderer
628
- );
629
- historyRef.current.push(content);
630
- isUpdatingRef.current = false;
631
- isInitializedRef.current = true;
632
-
633
- let inputTimeout: ReturnType<typeof setTimeout> | null = null;
634
- const handleInput = () => {
635
- if (isUpdatingRef.current) return;
636
-
637
- const content = domToContent(editor);
638
- notifyChange(content);
639
-
640
- if (inputTimeout) {
641
- clearTimeout(inputTimeout);
642
- }
643
- inputTimeout = setTimeout(() => {
644
- historyRef.current.push(content);
645
- inputTimeout = null;
646
- }, 300);
647
- };
648
-
649
- const handleKeyDown = (e: KeyboardEvent) => {
650
- const isModifierPressed = e.metaKey || e.ctrlKey;
651
-
652
- if (e.key === "Tab" && !isModifierPressed && !e.altKey) {
653
- // Immer preventDefault aufrufen (wie Lexical), damit Tab den Fokus nicht aus dem Editor entfernt
654
- e.preventDefault();
655
- e.stopPropagation();
656
- e.stopImmediatePropagation();
657
-
658
- const selection = window.getSelection();
659
-
660
- if (!selection || selection.rangeCount === 0) {
661
- // Keine Selection: Tab verhindern, Fokus bleibt im Editor
662
- return;
663
- }
664
-
665
- const range = selection.getRangeAt(0);
666
- const container = range.commonAncestorContainer;
667
-
668
- if (!editor.contains(container)) {
669
- // Container nicht im Editor: Tab verhindern
670
- return;
671
- }
672
-
673
- // Prüfe ob wir in einer Liste sind
674
- const listItem =
675
- container.nodeType === Node.TEXT_NODE
676
- ? container.parentElement?.closest("li")
677
- : (container as HTMLElement).closest("li");
678
-
679
- if (listItem && editor.contains(listItem)) {
680
- // In Liste: Indent/Outdent durchführen
681
- const currentContent = domToContent(editor);
682
- historyRef.current.push(currentContent);
683
-
684
- if (e.shiftKey) {
685
- outdentListItem(selection);
686
- } else {
687
- indentListItem(selection);
688
- }
689
-
690
- setTimeout(() => {
691
- if (editor) {
692
- const content = domToContent(editor);
693
- notifyChange(content);
694
- }
695
- }, 0);
696
- return;
697
- }
698
-
699
- // Nicht in Liste: Tab verhindern, aber kein Tab-Zeichen einfügen
700
- // Der Fokus bleibt im Editor (durch preventDefault)
701
- }
702
-
703
- if (isModifierPressed && e.key === "z" && !e.shiftKey) {
704
- e.preventDefault();
705
- e.stopPropagation();
706
- editorAPI.undo();
707
- } else if (
708
- isModifierPressed &&
709
- (e.key === "y" || (e.key === "z" && e.shiftKey))
710
- ) {
711
- e.preventDefault();
712
- e.stopPropagation();
713
- editorAPI.redo();
714
- }
715
- };
716
-
717
- editor.addEventListener("input", handleInput);
718
- editor.addEventListener("keydown", handleKeyDown, true);
719
-
720
- return () => {
721
- editor.removeEventListener("input", handleInput);
722
- editor.removeEventListener("keydown", handleKeyDown, true);
723
- if (inputTimeout) {
724
- clearTimeout(inputTimeout);
725
- }
726
- };
727
- }, [editorAPI, notifyChange]);
728
-
729
- const handlePaste = (e: React.ClipboardEvent) => {
730
- e.preventDefault();
731
-
732
- const html = e.clipboardData.getData("text/html");
733
- const text = e.clipboardData.getData("text/plain");
734
-
735
- if (html) {
736
- try {
737
- const pastedContent = htmlToContent(html);
738
- const editor = editorRef.current;
739
- if (!editor) return;
740
-
741
- const selection = window.getSelection();
742
- if (selection && selection.rangeCount > 0) {
743
- const range = selection.getRangeAt(0);
744
-
745
- range.deleteContents();
746
-
747
- const tempDiv = document.createElement("div");
748
- contentToDOM(
749
- pastedContent,
750
- tempDiv,
751
- customLinkComponent,
752
- customHeadingRenderer
753
- );
754
-
755
- const fragment = document.createDocumentFragment();
756
- while (tempDiv.firstChild) {
757
- fragment.appendChild(tempDiv.firstChild);
758
- }
759
-
760
- range.insertNode(fragment);
761
-
762
- if (fragment.lastChild) {
763
- range.setStartAfter(fragment.lastChild);
764
- range.collapse(true);
765
- }
766
- selection.removeAllRanges();
767
- selection.addRange(range);
768
-
769
- const content = domToContent(editor);
770
- notifyChange(content);
771
- }
772
- } catch (error) {
773
- document.execCommand("insertText", false, text);
774
- }
775
- } else if (text) {
776
- document.execCommand("insertText", false, text);
777
- }
778
- };
779
- const containerStyle: React.CSSProperties = theme
780
- ? {
781
- ...(theme.borderColor &&
782
- ({
783
- "--rte-border-color": theme.borderColor,
784
- } as React.CSSProperties)),
785
- ...(theme.borderRadius &&
786
- ({
787
- "--rte-border-radius": `${theme.borderRadius}px`,
788
- } as React.CSSProperties)),
789
- ...(theme.toolbarBg &&
790
- ({
791
- "--rte-toolbar-bg": theme.toolbarBg,
792
- } as React.CSSProperties)),
793
- ...(theme.buttonHoverBg &&
794
- ({
795
- "--rte-button-hover-bg": theme.buttonHoverBg,
796
- } as React.CSSProperties)),
797
- ...(theme.contentBg &&
798
- ({
799
- "--rte-content-bg": theme.contentBg,
800
- } as React.CSSProperties)),
801
- ...(theme.primaryColor &&
802
- ({
803
- "--rte-primary-color": theme.primaryColor,
804
- } as React.CSSProperties)),
805
- }
806
- : {};
807
-
808
- return (
809
- <div
810
- className={`rte-container ${className || ""}`}
811
- style={containerStyle}
812
- >
813
- <Toolbar
814
- plugins={plugins}
815
- editorAPI={editorAPI}
816
- className={toolbarClassName}
817
- />
818
- <div
819
- ref={editorRef}
820
- contentEditable
821
- className={`rte-editor ${editorClassName || ""}`}
822
- data-placeholder={placeholder}
823
- onPaste={handlePaste}
824
- suppressContentEditableWarning
825
- />
826
- </div>
827
- );
828
- };