@overlap/rte 0.1.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.
Files changed (75) hide show
  1. package/README.md +269 -0
  2. package/dist/components/Dropdown.d.ts +19 -0
  3. package/dist/components/Dropdown.d.ts.map +1 -0
  4. package/dist/components/Editor.d.ts +4 -0
  5. package/dist/components/Editor.d.ts.map +1 -0
  6. package/dist/components/FloatingToolbar.d.ts +10 -0
  7. package/dist/components/FloatingToolbar.d.ts.map +1 -0
  8. package/dist/components/IconWrapper.d.ts +10 -0
  9. package/dist/components/IconWrapper.d.ts.map +1 -0
  10. package/dist/components/Icons.d.ts +32 -0
  11. package/dist/components/Icons.d.ts.map +1 -0
  12. package/dist/components/Toolbar.d.ts +10 -0
  13. package/dist/components/Toolbar.d.ts.map +1 -0
  14. package/dist/components/index.d.ts +3 -0
  15. package/dist/components/index.d.ts.map +1 -0
  16. package/dist/index.d.ts +208 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.esm.js +2080 -0
  19. package/dist/index.esm.js.map +1 -0
  20. package/dist/index.js +2116 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/plugins/base.d.ts +10 -0
  23. package/dist/plugins/base.d.ts.map +1 -0
  24. package/dist/plugins/clearFormatting.d.ts +6 -0
  25. package/dist/plugins/clearFormatting.d.ts.map +1 -0
  26. package/dist/plugins/colors.d.ts +4 -0
  27. package/dist/plugins/colors.d.ts.map +1 -0
  28. package/dist/plugins/fontSize.d.ts +3 -0
  29. package/dist/plugins/fontSize.d.ts.map +1 -0
  30. package/dist/plugins/headings.d.ts +3 -0
  31. package/dist/plugins/headings.d.ts.map +1 -0
  32. package/dist/plugins/image.d.ts +6 -0
  33. package/dist/plugins/image.d.ts.map +1 -0
  34. package/dist/plugins/index.d.ts +14 -0
  35. package/dist/plugins/index.d.ts.map +1 -0
  36. package/dist/plugins/optional.d.ts +19 -0
  37. package/dist/plugins/optional.d.ts.map +1 -0
  38. package/dist/styles.css +638 -0
  39. package/dist/types.d.ts +81 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/utils/clearFormatting.d.ts +21 -0
  42. package/dist/utils/clearFormatting.d.ts.map +1 -0
  43. package/dist/utils/content.d.ts +12 -0
  44. package/dist/utils/content.d.ts.map +1 -0
  45. package/dist/utils/history.d.ts +14 -0
  46. package/dist/utils/history.d.ts.map +1 -0
  47. package/dist/utils/listIndent.d.ts +9 -0
  48. package/dist/utils/listIndent.d.ts.map +1 -0
  49. package/dist/utils/stateReflection.d.ts +18 -0
  50. package/dist/utils/stateReflection.d.ts.map +1 -0
  51. package/package.json +48 -0
  52. package/src/components/Dropdown.tsx +103 -0
  53. package/src/components/Editor.css +2 -0
  54. package/src/components/Editor.tsx +785 -0
  55. package/src/components/FloatingToolbar.tsx +214 -0
  56. package/src/components/IconWrapper.tsx +14 -0
  57. package/src/components/Icons.tsx +145 -0
  58. package/src/components/Toolbar.tsx +137 -0
  59. package/src/components/index.ts +3 -0
  60. package/src/index.ts +19 -0
  61. package/src/plugins/base.tsx +91 -0
  62. package/src/plugins/clearFormatting.tsx +31 -0
  63. package/src/plugins/colors.tsx +122 -0
  64. package/src/plugins/fontSize.tsx +81 -0
  65. package/src/plugins/headings.tsx +76 -0
  66. package/src/plugins/image.tsx +189 -0
  67. package/src/plugins/index.ts +54 -0
  68. package/src/plugins/optional.tsx +221 -0
  69. package/src/styles.css +638 -0
  70. package/src/types.ts +92 -0
  71. package/src/utils/clearFormatting.ts +244 -0
  72. package/src/utils/content.ts +290 -0
  73. package/src/utils/history.ts +59 -0
  74. package/src/utils/listIndent.ts +171 -0
  75. package/src/utils/stateReflection.ts +175 -0
@@ -0,0 +1,2080 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
3
+
4
+ const BoldIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" }) }));
5
+ const ItalicIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z" }) }));
6
+ const UnderlineIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" }) }));
7
+ const UndoIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z" }) }));
8
+ const RedoIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z" }) }));
9
+ const ClearFormattingIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M6 5v.18L8.82 8h2.4L7.73 4.36C7.26 3.85 6.61 3.5 6 3.5c-1.11 0-2 .89-2 2 0 .61.35 1.26.86 1.73L6 5zm14.27 2.5L18.73 9H21v1h-4.27L15 8.27l1.23-1.23c.5-.5 1.15-.73 1.77-.73 1.11 0 2 .89 2 2 0 .62-.23 1.27-.73 1.77L18.27 13H21v1h-5.27l-2-2H9.73l-2 2H3v-1h4.27l2-2H7v-1h2.73l2-2H12v-1h-2.27l2-2h4.54zM5 15h14v2H5v-2z" }) }));
10
+ const LinkIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" }) }));
11
+ const QuoteIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z" }) }));
12
+ const BulletListIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z" }) }));
13
+ const NumberedListIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 11.9V11H2zm6-5v2h14V6H8zm0 14h14v-2H8v2zm0-6h14v-2H8v2z" }) }));
14
+ const TextColorIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M2 20h20v4H2v-4zm3.49-3h2.42l1.27-3.58h5.64L16.09 17h2.42L13.25 3h-2.5L5.49 17zm4.22-5.61l2.03-5.79h.12l2.03 5.79H9.71z" }) }));
15
+ const BackgroundColorIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z" }) }));
16
+ const HeadingIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M5 4v3h5.5v12h3V7H19V4H5z" }) }));
17
+ const FontSizeIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M9 4v3h5v12h3V7h5V4H9zm-6 8h3v8h3v-8h3V10H3z" }) }));
18
+ const ImageIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" }) }));
19
+ const CloseIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) }));
20
+ const LoadingIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" }) }));
21
+ const UploadIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z" }) }));
22
+ const iconMap = {
23
+ 'mdi:format-bold': BoldIcon,
24
+ 'mdi:format-italic': ItalicIcon,
25
+ 'mdi:format-underline': UnderlineIcon,
26
+ 'mdi:undo': UndoIcon,
27
+ 'mdi:redo': RedoIcon,
28
+ 'mdi:format-clear': ClearFormattingIcon,
29
+ 'mdi:link': LinkIcon,
30
+ 'mdi:format-quote-close': QuoteIcon,
31
+ 'mdi:format-list-bulleted': BulletListIcon,
32
+ 'mdi:format-list-numbered': NumberedListIcon,
33
+ 'mdi:format-color-text': TextColorIcon,
34
+ 'mdi:format-color-fill': BackgroundColorIcon,
35
+ 'mdi:format-header-1': HeadingIcon,
36
+ 'mdi:format-size': FontSizeIcon,
37
+ 'mdi:image': ImageIcon,
38
+ 'mdi:close': CloseIcon,
39
+ 'mdi:loading': LoadingIcon,
40
+ 'mdi:upload': UploadIcon,
41
+ };
42
+ const Icon = ({ icon, width = 18, height = 18, className }) => {
43
+ const IconComponent = iconMap[icon];
44
+ if (!IconComponent) {
45
+ return jsx("span", { style: { width, height, display: 'inline-block' } });
46
+ }
47
+ return jsx(IconComponent, { width: width, height: height, className: className });
48
+ };
49
+
50
+ const IconWrapper = ({ icon, width = 18, height = 18, className }) => {
51
+ return jsx(Icon, { icon: icon, width: width, height: height, className: className });
52
+ };
53
+
54
+ /**
55
+ * Basis-Plugin für Inline-Formatierungen
56
+ */
57
+ function createInlinePlugin(name, command, icon, label) {
58
+ return {
59
+ name,
60
+ type: 'inline',
61
+ command,
62
+ renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? 'rte-toolbar-button-active' : ''}`, title: label, "aria-label": label, children: jsx(IconWrapper, { icon: icon, width: 18, height: 18 }) })),
63
+ execute: (editor) => {
64
+ editor.executeCommand(command);
65
+ },
66
+ isActive: (editor) => {
67
+ if (typeof window === 'undefined' || typeof document === 'undefined')
68
+ return false;
69
+ const selection = editor.getSelection();
70
+ if (!selection || selection.rangeCount === 0)
71
+ return false;
72
+ const range = selection.getRangeAt(0);
73
+ const container = range.commonAncestorContainer;
74
+ const element = container.nodeType === Node.TEXT_NODE
75
+ ? container.parentElement
76
+ : container;
77
+ if (!element)
78
+ return false;
79
+ return document.queryCommandState(command);
80
+ },
81
+ canExecute: (editor) => {
82
+ // Formatierung sollte auch ohne Selection möglich sein
83
+ // (z.B. wenn Editor leer ist, wird beim Klick eine Selection erstellt)
84
+ return true;
85
+ },
86
+ };
87
+ }
88
+ /**
89
+ * Basis-Plugin für Commands
90
+ */
91
+ function createCommandPlugin(name, command, icon, label) {
92
+ return {
93
+ name,
94
+ type: 'command',
95
+ command,
96
+ renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: "rte-toolbar-button", title: label, "aria-label": label, children: jsx(IconWrapper, { icon: icon, width: 18, height: 18 }) })),
97
+ execute: (editor) => {
98
+ editor.executeCommand(command);
99
+ },
100
+ canExecute: (editor) => {
101
+ if (command === 'undo')
102
+ return editor.canUndo();
103
+ if (command === 'redo')
104
+ return editor.canRedo();
105
+ return true;
106
+ },
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Clear Formatting Plugin - Entfernt alle Formatierungen
112
+ */
113
+ const clearFormattingPlugin = {
114
+ name: 'clearFormatting',
115
+ type: 'command',
116
+ renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: "rte-toolbar-button", title: "Formatierung entfernen", "aria-label": "Formatierung entfernen", children: jsx(IconWrapper, { icon: "mdi:format-clear", width: 18, height: 18 }) })),
117
+ execute: (editor) => {
118
+ editor.clearFormatting();
119
+ },
120
+ canExecute: (editor) => {
121
+ const selection = editor.getSelection();
122
+ return selection !== null && selection.rangeCount > 0 && !selection.isCollapsed;
123
+ },
124
+ };
125
+
126
+ /**
127
+ * Standard-Plugins
128
+ */
129
+ const boldPlugin = createInlinePlugin('bold', 'bold', 'mdi:format-bold', 'Fett');
130
+ const italicPlugin = createInlinePlugin('italic', 'italic', 'mdi:format-italic', 'Kursiv');
131
+ const underlinePlugin = createInlinePlugin('underline', 'underline', 'mdi:format-underline', 'Unterstrichen');
132
+ const undoPlugin = createCommandPlugin('undo', 'undo', 'mdi:undo', 'Rückgängig');
133
+ const redoPlugin = createCommandPlugin('redo', 'redo', 'mdi:redo', 'Wiederholen');
134
+ /**
135
+ * Standard-Plugin-Liste
136
+ */
137
+ const defaultPlugins = [
138
+ undoPlugin,
139
+ redoPlugin,
140
+ boldPlugin,
141
+ italicPlugin,
142
+ underlinePlugin,
143
+ clearFormattingPlugin,
144
+ ];
145
+
146
+ const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, }) => {
147
+ const [isOpen, setIsOpen] = useState(false);
148
+ const dropdownRef = useRef(null);
149
+ useEffect(() => {
150
+ const handleClickOutside = (event) => {
151
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
152
+ setIsOpen(false);
153
+ }
154
+ };
155
+ if (isOpen) {
156
+ document.addEventListener('mousedown', handleClickOutside);
157
+ }
158
+ return () => {
159
+ document.removeEventListener('mousedown', handleClickOutside);
160
+ };
161
+ }, [isOpen]);
162
+ const handleSelect = (value) => {
163
+ onSelect(value);
164
+ setIsOpen(false);
165
+ };
166
+ const currentOption = options.find(opt => opt.value === currentValue);
167
+ return (jsxs("div", { className: "rte-dropdown", ref: dropdownRef, children: [jsxs("button", { type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `rte-toolbar-button rte-dropdown-button ${currentOption ? 'rte-dropdown-button-has-value' : ''}`, title: label, "aria-label": label, children: [jsx(Icon, { icon: icon, width: 18, height: 18 }), currentOption && (jsx("span", { className: "rte-dropdown-value", children: currentOption.label }))] }), isOpen && (jsx("div", { className: "rte-dropdown-menu", children: options.map((option) => (jsxs("button", { type: "button", className: `rte-dropdown-item ${currentValue === option.value ? 'rte-dropdown-item-active' : ''}`, onClick: () => handleSelect(option.value), children: [option.color && (jsx("span", { className: `rte-dropdown-color-preview ${currentValue === option.value ? 'active' : ''}`, style: { backgroundColor: option.color } })), option.preview && !option.headingPreview && (jsx("span", { className: "rte-dropdown-fontsize-preview", style: { fontSize: `${option.preview}px` }, children: "Aa" })), option.headingPreview && (jsx("span", { className: `rte-dropdown-heading-preview ${option.headingPreview}`, children: option.headingPreview === 'p' ? 'Normal' : option.headingPreview.toUpperCase() })), option.icon && jsx(Icon, { icon: option.icon, width: 16, height: 16 }), jsx("span", { style: { flex: 1, fontWeight: currentValue === option.value ? 600 : 400 }, children: option.label })] }, option.value))) }))] }));
168
+ };
169
+
170
+ /**
171
+ * Liest die aktuelle Font-Size aus dem DOM an der Cursor-Position
172
+ */
173
+ function getCurrentFontSize(editor) {
174
+ const selection = editor.getSelection();
175
+ if (!selection || selection.rangeCount === 0)
176
+ return undefined;
177
+ const range = selection.getRangeAt(0);
178
+ const container = range.commonAncestorContainer;
179
+ const element = container.nodeType === Node.TEXT_NODE
180
+ ? container.parentElement
181
+ : container;
182
+ if (!element)
183
+ return undefined;
184
+ // Suche nach dem nächsten Element mit fontSize
185
+ let current = element;
186
+ while (current && current !== document.body) {
187
+ const fontSize = window.getComputedStyle(current).fontSize;
188
+ if (fontSize && fontSize !== 'inherit' && fontSize !== 'initial') {
189
+ // Konvertiere "16px" zu "16"
190
+ const size = parseInt(fontSize, 10);
191
+ if (!isNaN(size)) {
192
+ return size.toString();
193
+ }
194
+ }
195
+ current = current.parentElement;
196
+ }
197
+ return undefined;
198
+ }
199
+ /**
200
+ * Liest die aktuelle Textfarbe aus dem DOM an der Cursor-Position
201
+ */
202
+ function getCurrentTextColor(editor) {
203
+ const selection = editor.getSelection();
204
+ if (!selection || selection.rangeCount === 0)
205
+ return undefined;
206
+ const range = selection.getRangeAt(0);
207
+ const container = range.commonAncestorContainer;
208
+ const element = container.nodeType === Node.TEXT_NODE
209
+ ? container.parentElement
210
+ : container;
211
+ if (!element)
212
+ return undefined;
213
+ // Suche nach dem nächsten Element mit color
214
+ let current = element;
215
+ while (current && current !== document.body) {
216
+ // Prüfe zuerst inline style (hat Priorität)
217
+ const inlineColor = current.style.color;
218
+ if (inlineColor && inlineColor.trim()) {
219
+ // Wenn bereits Hex, direkt zurückgeben
220
+ if (inlineColor.startsWith('#')) {
221
+ return inlineColor;
222
+ }
223
+ // Wenn RGB/RGBA, konvertieren
224
+ const rgbMatch = inlineColor.match(/\d+/g);
225
+ if (rgbMatch && rgbMatch.length >= 3) {
226
+ const hex = '#' + rgbMatch.slice(0, 3).map(x => {
227
+ const hex = parseInt(x, 10).toString(16);
228
+ return hex.length === 1 ? '0' + hex : hex;
229
+ }).join('');
230
+ return hex;
231
+ }
232
+ }
233
+ // Dann computed style
234
+ const color = window.getComputedStyle(current).color;
235
+ if (color && color !== 'inherit' && color !== 'initial' && color !== 'rgb(0, 0, 0)' && color !== 'rgba(0, 0, 0, 0)') {
236
+ // Konvertiere RGB zu Hex
237
+ const rgb = color.match(/\d+/g);
238
+ if (rgb && rgb.length >= 3) {
239
+ const hex = '#' + rgb.slice(0, 3).map(x => {
240
+ const hex = parseInt(x, 10).toString(16);
241
+ return hex.length === 1 ? '0' + hex : hex;
242
+ }).join('');
243
+ return hex;
244
+ }
245
+ }
246
+ current = current.parentElement;
247
+ }
248
+ return undefined;
249
+ }
250
+ /**
251
+ * Liest die aktuelle Hintergrundfarbe aus dem DOM an der Cursor-Position
252
+ */
253
+ function getCurrentBackgroundColor(editor) {
254
+ const selection = editor.getSelection();
255
+ if (!selection || selection.rangeCount === 0)
256
+ return undefined;
257
+ const range = selection.getRangeAt(0);
258
+ const container = range.commonAncestorContainer;
259
+ const element = container.nodeType === Node.TEXT_NODE
260
+ ? container.parentElement
261
+ : container;
262
+ if (!element)
263
+ return undefined;
264
+ // Suche nach dem nächsten Element mit backgroundColor
265
+ let current = element;
266
+ while (current && current !== document.body) {
267
+ // Prüfe zuerst inline style (hat Priorität)
268
+ const inlineBgColor = current.style.backgroundColor;
269
+ if (inlineBgColor && inlineBgColor.trim()) {
270
+ // Wenn bereits Hex, direkt zurückgeben
271
+ if (inlineBgColor.startsWith('#')) {
272
+ return inlineBgColor;
273
+ }
274
+ // Wenn RGB/RGBA, konvertieren
275
+ const rgbMatch = inlineBgColor.match(/\d+/g);
276
+ if (rgbMatch && rgbMatch.length >= 3) {
277
+ const hex = '#' + rgbMatch.slice(0, 3).map(x => {
278
+ const hex = parseInt(x, 10).toString(16);
279
+ return hex.length === 1 ? '0' + hex : hex;
280
+ }).join('');
281
+ return hex;
282
+ }
283
+ }
284
+ // Dann computed style
285
+ const bgColor = window.getComputedStyle(current).backgroundColor;
286
+ if (bgColor && bgColor !== 'inherit' && bgColor !== 'initial' && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
287
+ // Konvertiere RGB zu Hex
288
+ const rgb = bgColor.match(/\d+/g);
289
+ if (rgb && rgb.length >= 3) {
290
+ const hex = '#' + rgb.slice(0, 3).map(x => {
291
+ const hex = parseInt(x, 10).toString(16);
292
+ return hex.length === 1 ? '0' + hex : hex;
293
+ }).join('');
294
+ return hex;
295
+ }
296
+ }
297
+ current = current.parentElement;
298
+ }
299
+ return undefined;
300
+ }
301
+ /**
302
+ * Liest das aktuelle Heading-Level aus dem DOM an der Cursor-Position
303
+ */
304
+ function getCurrentHeading(editor, availableHeadings) {
305
+ const selection = editor.getSelection();
306
+ if (!selection || selection.rangeCount === 0)
307
+ return undefined;
308
+ const range = selection.getRangeAt(0);
309
+ const container = range.commonAncestorContainer;
310
+ const element = container.nodeType === Node.TEXT_NODE
311
+ ? container.parentElement
312
+ : container;
313
+ if (!element)
314
+ return undefined;
315
+ // Suche nach dem nächsten Block-Element
316
+ let current = element;
317
+ while (current && current !== document.body) {
318
+ const tagName = current.tagName.toLowerCase();
319
+ if (availableHeadings.includes(tagName)) {
320
+ return tagName;
321
+ }
322
+ if (tagName === 'p') {
323
+ return 'p';
324
+ }
325
+ current = current.parentElement;
326
+ }
327
+ return undefined;
328
+ }
329
+
330
+ const defaultColors = [
331
+ { value: '#000000', label: 'Schwarz', color: '#000000' },
332
+ { value: '#333333', label: 'Dunkelgrau', color: '#333333' },
333
+ { value: '#666666', label: 'Grau', color: '#666666' },
334
+ { value: '#ff0000', label: 'Rot', color: '#ff0000' },
335
+ { value: '#0000ff', label: 'Blau', color: '#0000ff' },
336
+ { value: '#00aa00', label: 'Grün', color: '#00aa00' },
337
+ { value: '#ffaa00', label: 'Orange', color: '#ffaa00' },
338
+ { value: '#aa00ff', label: 'Lila', color: '#aa00ff' },
339
+ ];
340
+ function createTextColorPlugin(colors = defaultColors.map(c => c.value)) {
341
+ // Finde Labels für bekannte Farben
342
+ const getColorLabel = (color) => {
343
+ const found = defaultColors.find(c => c.value.toLowerCase() === color.toLowerCase());
344
+ return found ? found.label : color;
345
+ };
346
+ const colorOptions = colors.map(color => ({
347
+ value: color,
348
+ label: getColorLabel(color),
349
+ color,
350
+ }));
351
+ return {
352
+ name: 'textColor',
353
+ type: 'inline',
354
+ renderButton: (props) => {
355
+ // Aktuelle Textfarbe aus State Reflection
356
+ const currentValue = props.currentValue || (props.editorAPI ? getCurrentTextColor(props.editorAPI) : undefined);
357
+ return (jsx(Dropdown, { icon: "mdi:format-color-text", label: "Textfarbe", options: colorOptions.map(opt => ({
358
+ value: opt.value,
359
+ label: opt.label,
360
+ color: opt.color,
361
+ })), onSelect: (value) => {
362
+ if (props.onSelect) {
363
+ props.onSelect(value);
364
+ }
365
+ else {
366
+ props.onClick();
367
+ }
368
+ }, currentValue: currentValue, disabled: props.disabled }));
369
+ },
370
+ execute: (editor, value) => {
371
+ if (value) {
372
+ editor.executeCommand('foreColor', value);
373
+ }
374
+ },
375
+ canExecute: () => true,
376
+ getCurrentValue: (editor) => {
377
+ return getCurrentTextColor(editor);
378
+ },
379
+ };
380
+ }
381
+ function createBackgroundColorPlugin(colors = defaultColors.map(c => c.value)) {
382
+ // Finde Labels für bekannte Farben
383
+ const getColorLabel = (color) => {
384
+ const found = defaultColors.find(c => c.value.toLowerCase() === color.toLowerCase());
385
+ return found ? found.label : color;
386
+ };
387
+ const colorOptions = colors.map(color => ({
388
+ value: color,
389
+ label: getColorLabel(color),
390
+ color,
391
+ }));
392
+ return {
393
+ name: 'backgroundColor',
394
+ type: 'inline',
395
+ renderButton: (props) => {
396
+ // Aktuelle Hintergrundfarbe aus State Reflection
397
+ const currentValue = props.currentValue || (props.editorAPI ? getCurrentBackgroundColor(props.editorAPI) : undefined);
398
+ return (jsx(Dropdown, { icon: "mdi:format-color-fill", label: "Hintergrundfarbe", options: colorOptions.map(opt => ({
399
+ value: opt.value,
400
+ label: opt.label,
401
+ color: opt.color,
402
+ })), onSelect: (value) => {
403
+ if (props.onSelect) {
404
+ props.onSelect(value);
405
+ }
406
+ else {
407
+ props.onClick();
408
+ }
409
+ }, currentValue: currentValue, disabled: props.disabled }));
410
+ },
411
+ execute: (editor, value) => {
412
+ if (value) {
413
+ editor.executeCommand('backColor', value);
414
+ }
415
+ },
416
+ canExecute: () => true,
417
+ getCurrentValue: (editor) => {
418
+ return getCurrentBackgroundColor(editor);
419
+ },
420
+ };
421
+ }
422
+
423
+ function createFontSizePlugin(fontSizes = [12, 14, 16, 18, 20, 24]) {
424
+ return {
425
+ name: 'fontSize',
426
+ type: 'inline',
427
+ renderButton: (props) => {
428
+ const sizes = props.fontSizes || fontSizes;
429
+ const options = sizes.map(size => ({
430
+ value: size.toString(),
431
+ label: `${size}px`,
432
+ preview: size.toString(),
433
+ }));
434
+ // Aktuelle Font-Size aus State Reflection
435
+ const currentValue = props.currentValue || (props.editorAPI ? getCurrentFontSize(props.editorAPI) : undefined);
436
+ return (jsx(Dropdown, { icon: "mdi:format-size", label: "Schriftgr\u00F6\u00DFe", options: options, onSelect: (value) => {
437
+ if (props.onSelect) {
438
+ props.onSelect(value);
439
+ }
440
+ else {
441
+ props.onClick();
442
+ }
443
+ }, currentValue: currentValue, disabled: props.disabled }));
444
+ },
445
+ getCurrentValue: (editor) => {
446
+ return getCurrentFontSize(editor);
447
+ },
448
+ execute: (editor, value) => {
449
+ if (value) {
450
+ // Setze inline style für präzise Größe
451
+ const selection = editor.getSelection();
452
+ if (selection && selection.rangeCount > 0) {
453
+ const range = selection.getRangeAt(0);
454
+ let element = null;
455
+ if (range.commonAncestorContainer.nodeType === Node.TEXT_NODE) {
456
+ element = range.commonAncestorContainer.parentElement;
457
+ }
458
+ else {
459
+ element = range.commonAncestorContainer;
460
+ }
461
+ if (element) {
462
+ // Erstelle oder aktualisiere span mit fontSize
463
+ const span = document.createElement('span');
464
+ span.style.fontSize = `${value}px`;
465
+ try {
466
+ range.surroundContents(span);
467
+ }
468
+ catch (e) {
469
+ // Falls surroundContents fehlschlägt
470
+ const contents = range.extractContents();
471
+ span.appendChild(contents);
472
+ range.insertNode(span);
473
+ }
474
+ // Cursor setzen
475
+ range.setStartAfter(span);
476
+ range.collapse(true);
477
+ selection.removeAllRanges();
478
+ selection.addRange(range);
479
+ }
480
+ }
481
+ }
482
+ },
483
+ canExecute: () => true,
484
+ };
485
+ }
486
+
487
+ const defaultHeadings = ['h1', 'h2', 'h3'];
488
+ const headingLabels = {
489
+ h1: 'Überschrift 1',
490
+ h2: 'Überschrift 2',
491
+ h3: 'Überschrift 3',
492
+ h4: 'Überschrift 4',
493
+ h5: 'Überschrift 5',
494
+ h6: 'Überschrift 6',
495
+ };
496
+ function createHeadingsPlugin(headings = defaultHeadings) {
497
+ const options = [
498
+ { value: 'p', label: 'Normal', headingPreview: 'p' },
499
+ ...headings.map(h => ({
500
+ value: h,
501
+ label: headingLabels[h] || h.toUpperCase(),
502
+ headingPreview: h,
503
+ })),
504
+ ];
505
+ return {
506
+ name: 'headings',
507
+ type: 'block',
508
+ renderButton: (props) => {
509
+ // Aktuelles Heading aus State Reflection
510
+ const currentValue = props.currentValue || (props.editorAPI ? getCurrentHeading(props.editorAPI, headings) : undefined);
511
+ return (jsx(Dropdown, { icon: "mdi:format-header-1", label: "\u00DCberschrift", options: options, onSelect: (value) => {
512
+ if (props.onSelect) {
513
+ props.onSelect(value);
514
+ }
515
+ else {
516
+ props.onClick();
517
+ }
518
+ }, currentValue: currentValue, disabled: props.disabled }));
519
+ },
520
+ getCurrentValue: (editor) => {
521
+ return getCurrentHeading(editor, headings);
522
+ },
523
+ execute: (editor, value) => {
524
+ const tag = value || 'p';
525
+ editor.executeCommand('formatBlock', `<${tag}>`);
526
+ },
527
+ isActive: (editor) => {
528
+ const selection = editor.getSelection();
529
+ if (!selection || selection.rangeCount === 0)
530
+ return false;
531
+ const range = selection.getRangeAt(0);
532
+ const container = range.commonAncestorContainer;
533
+ const element = container.nodeType === Node.TEXT_NODE
534
+ ? container.parentElement
535
+ : container;
536
+ if (!element)
537
+ return false;
538
+ const tagName = element.tagName.toLowerCase();
539
+ return headings.includes(tagName);
540
+ },
541
+ canExecute: () => true,
542
+ };
543
+ }
544
+
545
+ /**
546
+ * Image-Plugin mit URL-Eingabe und File-Upload
547
+ */
548
+ function createImagePlugin(onImageUpload) {
549
+ return {
550
+ name: 'image',
551
+ type: 'block',
552
+ renderButton: (props) => {
553
+ const [showModal, setShowModal] = useState(false);
554
+ const [imageUrl, setImageUrl] = useState('');
555
+ const [altText, setAltText] = useState('');
556
+ const [isUploading, setIsUploading] = useState(false);
557
+ const fileInputRef = useRef(null);
558
+ const handleInsertImage = () => {
559
+ if (!props.editorAPI)
560
+ return;
561
+ const src = imageUrl.trim();
562
+ if (!src) {
563
+ alert('Bitte geben Sie eine Bild-URL ein');
564
+ return;
565
+ }
566
+ // Verwende executeCommand, das alles korrekt handhabt
567
+ // Alt-Text wird später über das Datenmodell gespeichert
568
+ props.editorAPI.executeCommand('insertImage', src);
569
+ // Modal schließen
570
+ setShowModal(false);
571
+ setImageUrl('');
572
+ setAltText('');
573
+ };
574
+ const handleFileSelect = async (e) => {
575
+ const file = e.target.files?.[0];
576
+ if (!file || !onImageUpload)
577
+ return;
578
+ if (!file.type.startsWith('image/')) {
579
+ alert('Bitte wählen Sie eine Bilddatei aus');
580
+ return;
581
+ }
582
+ setIsUploading(true);
583
+ try {
584
+ const uploadedUrl = await onImageUpload(file);
585
+ setImageUrl(uploadedUrl);
586
+ }
587
+ catch (error) {
588
+ console.error('Image upload failed:', error);
589
+ alert('Fehler beim Hochladen des Bildes');
590
+ }
591
+ finally {
592
+ setIsUploading(false);
593
+ if (fileInputRef.current) {
594
+ fileInputRef.current.value = '';
595
+ }
596
+ }
597
+ };
598
+ return (jsxs(Fragment, { children: [jsx("button", { type: "button", onClick: () => setShowModal(true), disabled: props.disabled, className: "rte-toolbar-button", title: "Bild einf\u00FCgen", "aria-label": "Bild einf\u00FCgen", children: jsx(IconWrapper, { icon: "mdi:image", width: 18, height: 18 }) }), showModal && (jsx("div", { className: "rte-image-modal-overlay", onClick: (e) => {
599
+ if (e.target === e.currentTarget) {
600
+ setShowModal(false);
601
+ }
602
+ }, children: jsxs("div", { className: "rte-image-modal", children: [jsxs("div", { className: "rte-image-modal-header", children: [jsx("h3", { children: "Bild einf\u00FCgen" }), jsx("button", { type: "button", onClick: () => setShowModal(false), className: "rte-image-modal-close", "aria-label": "Schlie\u00DFen", children: jsx(IconWrapper, { icon: "mdi:close", width: 20, height: 20 }) })] }), jsxs("div", { className: "rte-image-modal-content", children: [onImageUpload && (jsx("div", { className: "rte-image-upload-section", children: jsxs("label", { className: "rte-image-upload-label", children: [jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", onChange: handleFileSelect, style: { display: 'none' } }), jsx("div", { className: "rte-image-upload-button", children: isUploading ? (jsxs(Fragment, { children: [jsx(IconWrapper, { icon: "mdi:loading", width: 24, height: 24, className: "rte-spin" }), jsx("span", { children: "Wird hochgeladen..." })] })) : (jsxs(Fragment, { children: [jsx(IconWrapper, { icon: "mdi:upload", width: 24, height: 24 }), jsx("span", { children: "Datei ausw\u00E4hlen" })] })) })] }) })), jsx("div", { className: "rte-image-url-section", children: jsxs("label", { children: ["Bild-URL", jsx("input", { type: "url", value: imageUrl, onChange: (e) => setImageUrl(e.target.value), placeholder: "https://example.com/image.jpg", className: "rte-image-url-input" })] }) }), jsx("div", { className: "rte-image-alt-section", children: jsxs("label", { children: ["Alt-Text (optional)", jsx("input", { type: "text", value: altText, onChange: (e) => setAltText(e.target.value), placeholder: "Bildbeschreibung", className: "rte-image-alt-input" })] }) }), imageUrl && (jsx("div", { className: "rte-image-preview", children: jsx("img", { src: imageUrl, alt: altText || 'Preview' }) }))] }), jsxs("div", { className: "rte-image-modal-footer", children: [jsx("button", { type: "button", onClick: () => setShowModal(false), className: "rte-image-modal-cancel", children: "Abbrechen" }), jsx("button", { type: "button", onClick: handleInsertImage, disabled: !imageUrl.trim() || isUploading, className: "rte-image-modal-insert", children: "Einf\u00FCgen" })] })] }) }))] }));
603
+ },
604
+ execute: (editor) => {
605
+ // Wird über renderButton gehandhabt
606
+ },
607
+ canExecute: () => true,
608
+ };
609
+ }
610
+
611
+ /**
612
+ * Entfernt alle Formatierungen aus der aktuellen Selection
613
+ */
614
+ function clearFormatting(selection) {
615
+ if (!selection || selection.rangeCount === 0)
616
+ return;
617
+ const range = selection.getRangeAt(0);
618
+ // Wenn Selection leer ist, nichts zu tun
619
+ if (range.collapsed)
620
+ return;
621
+ try {
622
+ // Verwende document.execCommand für Standard-Formatierungen
623
+ document.execCommand('removeFormat', false);
624
+ document.execCommand('unlink', false);
625
+ // Entferne alle inline styles und Formatierungen manuell
626
+ const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT, null);
627
+ const elements = [];
628
+ let node = walker.currentNode;
629
+ while (node) {
630
+ if (node.nodeType === Node.ELEMENT_NODE) {
631
+ const el = node;
632
+ if (range.intersectsNode(el)) {
633
+ const tagName = el.tagName.toLowerCase();
634
+ // Entferne Inline-Formatierungen (strong, em, u, span, a, etc.)
635
+ if (['strong', 'b', 'em', 'i', 'u', 'span', 'a', 'font'].includes(tagName)) {
636
+ // Ersetze durch Text-Node
637
+ const text = el.textContent || '';
638
+ const textNode = document.createTextNode(text);
639
+ el.parentNode?.replaceChild(textNode, el);
640
+ }
641
+ // Entferne alle Styles
642
+ if (el.style.length > 0) {
643
+ el.removeAttribute('style');
644
+ }
645
+ // Konvertiere Headings zu Paragraphs
646
+ if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
647
+ const p = document.createElement('p');
648
+ while (el.firstChild) {
649
+ p.appendChild(el.firstChild);
650
+ }
651
+ el.parentNode?.replaceChild(p, el);
652
+ }
653
+ }
654
+ }
655
+ node = walker.nextNode();
656
+ }
657
+ // Normalisiere: Entferne leere Formatierungs-Tags
658
+ const normalizeWalker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT, null);
659
+ node = normalizeWalker.currentNode;
660
+ while (node) {
661
+ if (node.nodeType === Node.ELEMENT_NODE) {
662
+ const el = node;
663
+ const tagName = el.tagName.toLowerCase();
664
+ if (['strong', 'b', 'em', 'i', 'u', 'span'].includes(tagName) && !el.textContent?.trim()) {
665
+ el.parentNode?.removeChild(el);
666
+ }
667
+ }
668
+ node = normalizeWalker.nextNode();
669
+ }
670
+ }
671
+ catch (error) {
672
+ console.error('Error clearing formatting:', error);
673
+ // Fallback: Einfache Methode
674
+ document.execCommand('removeFormat', false);
675
+ document.execCommand('unlink', false);
676
+ }
677
+ }
678
+ /**
679
+ * Entfernt nur Textfarbe
680
+ */
681
+ function clearTextColor(selection) {
682
+ if (!selection || selection.rangeCount === 0)
683
+ return;
684
+ const range = selection.getRangeAt(0);
685
+ if (range.collapsed)
686
+ return;
687
+ // Finde alle Elemente mit color style
688
+ const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT, null);
689
+ const elements = [];
690
+ let node = walker.currentNode;
691
+ while (node) {
692
+ if (node.nodeType === Node.ELEMENT_NODE) {
693
+ const el = node;
694
+ if (range.intersectsNode(el) && el.style.color) {
695
+ elements.push(el);
696
+ }
697
+ }
698
+ node = walker.nextNode();
699
+ }
700
+ elements.forEach(el => {
701
+ el.style.color = '';
702
+ if (!el.style.length) {
703
+ el.removeAttribute('style');
704
+ }
705
+ });
706
+ }
707
+ /**
708
+ * Entfernt nur Hintergrundfarbe
709
+ */
710
+ function clearBackgroundColor(selection) {
711
+ if (!selection || selection.rangeCount === 0)
712
+ return;
713
+ const range = selection.getRangeAt(0);
714
+ if (range.collapsed)
715
+ return;
716
+ // Finde alle Elemente mit backgroundColor style
717
+ const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT, null);
718
+ const elements = [];
719
+ let node = walker.currentNode;
720
+ while (node) {
721
+ if (node.nodeType === Node.ELEMENT_NODE) {
722
+ const el = node;
723
+ if (range.intersectsNode(el) && el.style.backgroundColor) {
724
+ elements.push(el);
725
+ }
726
+ }
727
+ node = walker.nextNode();
728
+ }
729
+ elements.forEach(el => {
730
+ el.style.backgroundColor = '';
731
+ if (!el.style.length) {
732
+ el.removeAttribute('style');
733
+ }
734
+ });
735
+ }
736
+ /**
737
+ * Entfernt nur Font-Size
738
+ */
739
+ function clearFontSize(selection) {
740
+ if (!selection || selection.rangeCount === 0)
741
+ return;
742
+ const range = selection.getRangeAt(0);
743
+ if (range.collapsed)
744
+ return;
745
+ // Finde alle Elemente mit fontSize style
746
+ const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT, null);
747
+ const elements = [];
748
+ let node = walker.currentNode;
749
+ while (node) {
750
+ if (node.nodeType === Node.ELEMENT_NODE) {
751
+ const el = node;
752
+ if (range.intersectsNode(el) && el.style.fontSize) {
753
+ elements.push(el);
754
+ }
755
+ }
756
+ node = walker.nextNode();
757
+ }
758
+ elements.forEach(el => {
759
+ el.style.fontSize = '';
760
+ if (!el.style.length) {
761
+ el.removeAttribute('style');
762
+ }
763
+ });
764
+ }
765
+ /**
766
+ * Entfernt Links (behält Text)
767
+ */
768
+ function clearLinks(selection) {
769
+ if (!selection || selection.rangeCount === 0)
770
+ return;
771
+ const range = selection.getRangeAt(0);
772
+ // Finde alle Links in der Selection
773
+ const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT, {
774
+ acceptNode: (node) => {
775
+ if (node.nodeType === Node.ELEMENT_NODE) {
776
+ const el = node;
777
+ if (el.tagName.toLowerCase() === 'a' && range.intersectsNode(el)) {
778
+ return NodeFilter.FILTER_ACCEPT;
779
+ }
780
+ }
781
+ return NodeFilter.FILTER_SKIP;
782
+ }
783
+ });
784
+ const links = [];
785
+ let node = walker.currentNode;
786
+ while (node) {
787
+ if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'a') {
788
+ links.push(node);
789
+ }
790
+ node = walker.nextNode();
791
+ }
792
+ // Entferne Links (behält Text)
793
+ links.forEach(link => {
794
+ const parent = link.parentNode;
795
+ if (parent) {
796
+ while (link.firstChild) {
797
+ parent.insertBefore(link.firstChild, link);
798
+ }
799
+ parent.removeChild(link);
800
+ }
801
+ });
802
+ }
803
+
804
+ function domToContent(element) {
805
+ const blocks = [];
806
+ function processNode(node) {
807
+ if (node.nodeType === Node.TEXT_NODE) {
808
+ const text = node.textContent;
809
+ if (text === null || text === undefined)
810
+ return null;
811
+ return { type: "text", text };
812
+ }
813
+ if (node.nodeType === Node.ELEMENT_NODE) {
814
+ const el = node;
815
+ const tagName = el.tagName.toLowerCase();
816
+ if (tagName === "img") {
817
+ const attributes = {};
818
+ const src = el.getAttribute("src");
819
+ const alt = el.getAttribute("alt");
820
+ if (src)
821
+ attributes.src = src;
822
+ if (alt)
823
+ attributes.alt = alt;
824
+ return {
825
+ type: "image",
826
+ attributes: Object.keys(attributes).length > 0
827
+ ? attributes
828
+ : undefined,
829
+ };
830
+ }
831
+ if ([
832
+ "p",
833
+ "div",
834
+ "h1",
835
+ "h2",
836
+ "h3",
837
+ "h4",
838
+ "h5",
839
+ "h6",
840
+ "blockquote",
841
+ "ul",
842
+ "ol",
843
+ "li",
844
+ ].includes(tagName)) {
845
+ const children = [];
846
+ Array.from(el.childNodes).forEach((child) => {
847
+ const processed = processNode(child);
848
+ if (processed)
849
+ children.push(processed);
850
+ });
851
+ const attributes = {};
852
+ if (tagName === "a") {
853
+ const href = el.getAttribute("href");
854
+ if (href)
855
+ attributes.href = href;
856
+ }
857
+ return {
858
+ type: tagName,
859
+ children: children.length > 0 ? children : [],
860
+ attributes: Object.keys(attributes).length > 0
861
+ ? attributes
862
+ : undefined,
863
+ };
864
+ }
865
+ if (["strong", "b", "em", "i", "u", "a", "span"].includes(tagName)) {
866
+ const children = [];
867
+ Array.from(el.childNodes).forEach((child) => {
868
+ const processed = processNode(child);
869
+ if (processed)
870
+ children.push(processed);
871
+ });
872
+ const attributes = {};
873
+ Array.from(el.attributes).forEach((attr) => {
874
+ attributes[attr.name] = attr.value;
875
+ });
876
+ if (tagName === "a") {
877
+ const href = el.getAttribute("href");
878
+ if (href)
879
+ attributes.href = href;
880
+ return {
881
+ type: "link",
882
+ children: children.length > 0 ? children : undefined,
883
+ attributes: Object.keys(attributes).length > 0
884
+ ? attributes
885
+ : undefined,
886
+ };
887
+ }
888
+ if (tagName === "span") {
889
+ const style = el.getAttribute("style");
890
+ if (style) {
891
+ style.split(";").forEach((rule) => {
892
+ const [key, value] = rule
893
+ .split(":")
894
+ .map((s) => s.trim());
895
+ if (key && value) {
896
+ if (key === "font-size") {
897
+ attributes.fontSize = value;
898
+ }
899
+ else if (key === "color") {
900
+ attributes.color = value;
901
+ }
902
+ else if (key === "background-color") {
903
+ attributes.backgroundColor = value;
904
+ }
905
+ }
906
+ });
907
+ }
908
+ }
909
+ return {
910
+ type: tagName === "strong" || tagName === "b"
911
+ ? "bold"
912
+ : tagName === "em" || tagName === "i"
913
+ ? "italic"
914
+ : tagName === "u"
915
+ ? "underline"
916
+ : tagName,
917
+ children: children.length > 0 ? children : undefined,
918
+ attributes: Object.keys(attributes).length > 0
919
+ ? attributes
920
+ : undefined,
921
+ };
922
+ }
923
+ const children = [];
924
+ Array.from(el.childNodes).forEach((child) => {
925
+ const processed = processNode(child);
926
+ if (processed)
927
+ children.push(processed);
928
+ });
929
+ if (children.length > 0) {
930
+ return {
931
+ type: tagName,
932
+ children,
933
+ };
934
+ }
935
+ }
936
+ return null;
937
+ }
938
+ Array.from(element.childNodes).forEach((node) => {
939
+ const processed = processNode(node);
940
+ if (processed) {
941
+ if (processed.type === "text") {
942
+ blocks.push({ type: "p", children: [processed] });
943
+ }
944
+ else {
945
+ blocks.push(processed);
946
+ }
947
+ }
948
+ });
949
+ if (blocks.length === 0) {
950
+ blocks.push({ type: "p", children: [] });
951
+ }
952
+ return { blocks };
953
+ }
954
+ function contentToDOM(content, container, customLinkComponent, customHeadingRenderer) {
955
+ container.innerHTML = "";
956
+ function createNode(node) {
957
+ if (node.type === "text" && node.text !== undefined) {
958
+ return document.createTextNode(node.text);
959
+ }
960
+ if (node.type === "image") {
961
+ const img = document.createElement("img");
962
+ if (node.attributes) {
963
+ if (node.attributes.src)
964
+ img.setAttribute("src", node.attributes.src);
965
+ if (node.attributes.alt)
966
+ img.setAttribute("alt", node.attributes.alt);
967
+ if (node.attributes.uploading === "true") {
968
+ img.setAttribute("data-uploading", "true");
969
+ }
970
+ }
971
+ img.style.maxWidth = "100%";
972
+ img.style.height = "auto";
973
+ img.style.display = "block";
974
+ img.style.margin = "16px 0";
975
+ return img;
976
+ }
977
+ const tagMap = {
978
+ bold: "strong",
979
+ italic: "em",
980
+ underline: "u",
981
+ link: "a",
982
+ };
983
+ let tagName = tagMap[node.type] || node.type;
984
+ if (node.type === "link" && customLinkComponent) {
985
+ tagName = "a";
986
+ if (node.attributes) {
987
+ node.attributes["data-custom-link"] = "true";
988
+ }
989
+ }
990
+ const element = document.createElement(tagName);
991
+ if (node.attributes) {
992
+ Object.entries(node.attributes).forEach(([key, value]) => {
993
+ if (key === "fontSize" ||
994
+ key === "color" ||
995
+ key === "backgroundColor") {
996
+ const currentStyle = element.getAttribute("style") || "";
997
+ if (key === "fontSize") {
998
+ element.setAttribute("style", `${currentStyle}font-size: ${value};`.trim());
999
+ }
1000
+ else if (key === "color") {
1001
+ element.setAttribute("style", `${currentStyle}color: ${value};`.trim());
1002
+ }
1003
+ else if (key === "backgroundColor") {
1004
+ element.setAttribute("style", `${currentStyle}background-color: ${value};`.trim());
1005
+ }
1006
+ }
1007
+ else {
1008
+ element.setAttribute(key, value);
1009
+ }
1010
+ });
1011
+ }
1012
+ if (node.children) {
1013
+ node.children.forEach((child) => {
1014
+ const childNode = createNode(child);
1015
+ element.appendChild(childNode);
1016
+ });
1017
+ }
1018
+ return element;
1019
+ }
1020
+ content.blocks.forEach((block) => {
1021
+ const blockNode = createNode(block);
1022
+ container.appendChild(blockNode);
1023
+ });
1024
+ }
1025
+ function createEmptyContent() {
1026
+ return {
1027
+ blocks: [{ type: "p", children: [] }],
1028
+ };
1029
+ }
1030
+ function htmlToContent(htmlString) {
1031
+ const tempDiv = document.createElement("div");
1032
+ tempDiv.innerHTML = htmlString.trim();
1033
+ return domToContent(tempDiv);
1034
+ }
1035
+ function contentToHTML(content) {
1036
+ const tempDiv = document.createElement("div");
1037
+ contentToDOM(content, tempDiv);
1038
+ return tempDiv.innerHTML;
1039
+ }
1040
+
1041
+ class HistoryManager {
1042
+ constructor() {
1043
+ this.history = [];
1044
+ this.currentIndex = -1;
1045
+ this.maxHistorySize = 50;
1046
+ }
1047
+ push(content) {
1048
+ // Entferne alle Einträge nach currentIndex (wenn wir zurückgegangen sind)
1049
+ this.history = this.history.slice(0, this.currentIndex + 1);
1050
+ // Füge neuen Eintrag hinzu
1051
+ this.history.push(JSON.parse(JSON.stringify(content))); // Deep clone
1052
+ this.currentIndex++;
1053
+ // Begrenze die Historie
1054
+ if (this.history.length > this.maxHistorySize) {
1055
+ this.history.shift();
1056
+ this.currentIndex--;
1057
+ }
1058
+ }
1059
+ undo() {
1060
+ if (this.canUndo()) {
1061
+ this.currentIndex--;
1062
+ return JSON.parse(JSON.stringify(this.history[this.currentIndex]));
1063
+ }
1064
+ return null;
1065
+ }
1066
+ redo() {
1067
+ if (this.canRedo()) {
1068
+ this.currentIndex++;
1069
+ return JSON.parse(JSON.stringify(this.history[this.currentIndex]));
1070
+ }
1071
+ return null;
1072
+ }
1073
+ canUndo() {
1074
+ return this.currentIndex > 0;
1075
+ }
1076
+ canRedo() {
1077
+ return this.currentIndex < this.history.length - 1;
1078
+ }
1079
+ getCurrent() {
1080
+ if (this.currentIndex >= 0 && this.currentIndex < this.history.length) {
1081
+ return JSON.parse(JSON.stringify(this.history[this.currentIndex]));
1082
+ }
1083
+ return null;
1084
+ }
1085
+ reset() {
1086
+ this.history = [];
1087
+ this.currentIndex = -1;
1088
+ }
1089
+ }
1090
+
1091
+ /**
1092
+ * Erhöht den Einrückungs-Level eines List-Items (Tab)
1093
+ */
1094
+ function indentListItem(selection) {
1095
+ if (!selection || selection.rangeCount === 0)
1096
+ return false;
1097
+ const range = selection.getRangeAt(0);
1098
+ const container = range.commonAncestorContainer;
1099
+ const listItem = container.nodeType === Node.TEXT_NODE
1100
+ ? container.parentElement?.closest('li')
1101
+ : container.closest('li');
1102
+ if (!listItem)
1103
+ return false;
1104
+ const list = listItem.parentElement;
1105
+ if (!list || (list.tagName !== 'UL' && list.tagName !== 'OL'))
1106
+ return false;
1107
+ // Prüfe ob bereits verschachtelt (max depth check)
1108
+ let depth = 0;
1109
+ let current = listItem;
1110
+ while (current) {
1111
+ const parent = current.parentElement;
1112
+ if (parent && (parent.tagName === 'UL' || parent.tagName === 'OL')) {
1113
+ depth++;
1114
+ current = parent.closest('li');
1115
+ }
1116
+ else {
1117
+ break;
1118
+ }
1119
+ }
1120
+ // Max depth: 6 (wie in HTML Standard)
1121
+ if (depth >= 6)
1122
+ return false;
1123
+ // Finde vorheriges List-Item
1124
+ const previousItem = listItem.previousElementSibling;
1125
+ if (previousItem && previousItem.tagName === 'LI') {
1126
+ // Erstelle verschachtelte Liste im vorherigen Item
1127
+ let nestedList = previousItem.querySelector('ul, ol');
1128
+ if (!nestedList) {
1129
+ // Erstelle neue verschachtelte Liste
1130
+ nestedList = document.createElement(list.tagName.toLowerCase());
1131
+ previousItem.appendChild(nestedList);
1132
+ }
1133
+ // Verschiebe aktuelles Item in verschachtelte Liste
1134
+ nestedList.appendChild(listItem);
1135
+ // Cursor setzen
1136
+ const textNode = listItem.firstChild;
1137
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1138
+ range.setStart(textNode, 0);
1139
+ range.collapse(true);
1140
+ }
1141
+ else if (listItem.firstChild) {
1142
+ range.setStart(listItem.firstChild, 0);
1143
+ range.collapse(true);
1144
+ }
1145
+ else {
1146
+ const newText = document.createTextNode('');
1147
+ listItem.appendChild(newText);
1148
+ range.setStart(newText, 0);
1149
+ range.collapse(true);
1150
+ }
1151
+ selection.removeAllRanges();
1152
+ selection.addRange(range);
1153
+ return true;
1154
+ }
1155
+ else {
1156
+ // Kein vorheriges Item - erstelle neue verschachtelte Liste im aktuellen Item
1157
+ const nestedList = document.createElement(list.tagName.toLowerCase());
1158
+ // Verschiebe alle nachfolgenden Items in die verschachtelte Liste
1159
+ let nextSibling = listItem.nextElementSibling;
1160
+ while (nextSibling && nextSibling.tagName === 'LI') {
1161
+ const toMove = nextSibling;
1162
+ nextSibling = nextSibling.nextElementSibling;
1163
+ nestedList.appendChild(toMove);
1164
+ }
1165
+ if (nestedList.children.length > 0) {
1166
+ listItem.appendChild(nestedList);
1167
+ }
1168
+ else {
1169
+ // Wenn keine nachfolgenden Items, erstelle leeres Sub-Item
1170
+ const subItem = document.createElement('li');
1171
+ nestedList.appendChild(subItem);
1172
+ listItem.appendChild(nestedList);
1173
+ // Cursor ins Sub-Item setzen
1174
+ const newText = document.createTextNode('');
1175
+ subItem.appendChild(newText);
1176
+ range.setStart(newText, 0);
1177
+ range.collapse(true);
1178
+ selection.removeAllRanges();
1179
+ selection.addRange(range);
1180
+ }
1181
+ return true;
1182
+ }
1183
+ }
1184
+ /**
1185
+ * Reduziert den Einrückungs-Level eines List-Items (Shift+Tab)
1186
+ */
1187
+ function outdentListItem(selection) {
1188
+ if (!selection || selection.rangeCount === 0)
1189
+ return false;
1190
+ const range = selection.getRangeAt(0);
1191
+ const container = range.commonAncestorContainer;
1192
+ const listItem = container.nodeType === Node.TEXT_NODE
1193
+ ? container.parentElement?.closest('li')
1194
+ : container.closest('li');
1195
+ if (!listItem)
1196
+ return false;
1197
+ const list = listItem.parentElement;
1198
+ if (!list || (list.tagName !== 'UL' && list.tagName !== 'OL'))
1199
+ return false;
1200
+ // Prüfe ob in verschachtelter Liste
1201
+ const parentListItem = list.parentElement;
1202
+ if (!parentListItem || parentListItem.tagName !== 'LI') {
1203
+ // Bereits auf oberstem Level
1204
+ return false;
1205
+ }
1206
+ const parentList = parentListItem.parentElement;
1207
+ if (!parentList || (parentList.tagName !== 'UL' && parentList.tagName !== 'OL')) {
1208
+ return false;
1209
+ }
1210
+ // Verschiebe Item auf oberes Level
1211
+ // Finde Position nach dem Parent-Item
1212
+ const insertAfter = parentListItem;
1213
+ // Verschiebe Item und alle nachfolgenden Items aus der verschachtelten Liste
1214
+ const itemsToMove = [listItem];
1215
+ let nextSibling = listItem.nextElementSibling;
1216
+ while (nextSibling && nextSibling.tagName === 'LI') {
1217
+ itemsToMove.push(nextSibling);
1218
+ nextSibling = nextSibling.nextElementSibling;
1219
+ }
1220
+ // Füge Items nach dem Parent-Item ein
1221
+ itemsToMove.forEach(item => {
1222
+ parentList.insertBefore(item, insertAfter.nextSibling);
1223
+ });
1224
+ // Entferne leere verschachtelte Liste
1225
+ if (list.children.length === 0) {
1226
+ list.remove();
1227
+ }
1228
+ // Cursor setzen
1229
+ const textNode = listItem.firstChild;
1230
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1231
+ range.setStart(textNode, 0);
1232
+ range.collapse(true);
1233
+ }
1234
+ else if (listItem.firstChild) {
1235
+ range.setStart(listItem.firstChild, 0);
1236
+ range.collapse(true);
1237
+ }
1238
+ else {
1239
+ const newText = document.createTextNode('');
1240
+ listItem.appendChild(newText);
1241
+ range.setStart(newText, 0);
1242
+ range.collapse(true);
1243
+ }
1244
+ selection.removeAllRanges();
1245
+ selection.addRange(range);
1246
+ return true;
1247
+ }
1248
+
1249
+ const Toolbar = ({ plugins, editorAPI, className, }) => {
1250
+ const [updateTrigger, setUpdateTrigger] = useState(0);
1251
+ const [isClient, setIsClient] = useState(false);
1252
+ useEffect(() => {
1253
+ setIsClient(true);
1254
+ const handleSelectionChange = () => {
1255
+ setUpdateTrigger((prev) => prev + 1);
1256
+ };
1257
+ const handleMouseUp = () => {
1258
+ setTimeout(handleSelectionChange, 10);
1259
+ };
1260
+ const handleKeyUp = () => {
1261
+ setTimeout(handleSelectionChange, 10);
1262
+ };
1263
+ if (typeof document !== 'undefined') {
1264
+ document.addEventListener("selectionchange", handleSelectionChange);
1265
+ document.addEventListener("mouseup", handleMouseUp);
1266
+ document.addEventListener("keyup", handleKeyUp);
1267
+ }
1268
+ return () => {
1269
+ if (typeof document !== 'undefined') {
1270
+ document.removeEventListener("selectionchange", handleSelectionChange);
1271
+ document.removeEventListener("mouseup", handleMouseUp);
1272
+ document.removeEventListener("keyup", handleKeyUp);
1273
+ }
1274
+ };
1275
+ }, []);
1276
+ const handlePluginClick = (plugin, value) => {
1277
+ if (plugin.canExecute?.(editorAPI) !== false) {
1278
+ if (plugin.execute) {
1279
+ plugin.execute(editorAPI, value);
1280
+ }
1281
+ else if (plugin.command && value !== undefined) {
1282
+ editorAPI.executeCommand(plugin.command, value);
1283
+ }
1284
+ else if (plugin.command) {
1285
+ editorAPI.executeCommand(plugin.command);
1286
+ }
1287
+ setTimeout(() => setUpdateTrigger((prev) => prev + 1), 50);
1288
+ }
1289
+ };
1290
+ const leftPlugins = plugins.filter((p) => p.name !== "clearFormatting");
1291
+ const clearFormattingPlugin = plugins.find((p) => p.name === "clearFormatting");
1292
+ return (jsxs("div", { className: `rte-toolbar rte-toolbar-sticky ${className || ""}`, children: [jsx("div", { className: "rte-toolbar-left", children: leftPlugins.map((plugin) => {
1293
+ if (!plugin.renderButton)
1294
+ return null;
1295
+ const isActive = isClient && plugin.isActive
1296
+ ? plugin.isActive(editorAPI)
1297
+ : false;
1298
+ const canExecute = isClient && plugin.canExecute
1299
+ ? plugin.canExecute(editorAPI)
1300
+ : true;
1301
+ const currentValue = isClient && plugin.getCurrentValue
1302
+ ? plugin.getCurrentValue(editorAPI)
1303
+ : undefined;
1304
+ const buttonProps = {
1305
+ isActive,
1306
+ onClick: () => handlePluginClick(plugin),
1307
+ disabled: !canExecute,
1308
+ onSelect: (value) => handlePluginClick(plugin, value),
1309
+ editorAPI,
1310
+ currentValue,
1311
+ };
1312
+ return (jsx(React.Fragment, { children: plugin.renderButton(buttonProps) }, plugin.name));
1313
+ }) }), clearFormattingPlugin && clearFormattingPlugin.renderButton && (jsxs("div", { className: "rte-toolbar-right", children: [jsx("div", { className: "rte-toolbar-divider" }), (() => {
1314
+ const isActive = isClient && clearFormattingPlugin.isActive
1315
+ ? clearFormattingPlugin.isActive(editorAPI)
1316
+ : false;
1317
+ const canExecute = isClient && clearFormattingPlugin.canExecute
1318
+ ? clearFormattingPlugin.canExecute(editorAPI)
1319
+ : true;
1320
+ const buttonProps = {
1321
+ isActive,
1322
+ onClick: () => handlePluginClick(clearFormattingPlugin),
1323
+ disabled: !canExecute,
1324
+ editorAPI,
1325
+ };
1326
+ return (jsx(React.Fragment, { children: clearFormattingPlugin.renderButton(buttonProps) }, clearFormattingPlugin.name));
1327
+ })()] }))] }));
1328
+ };
1329
+
1330
+ const Editor = ({ initialContent, onChange, plugins: providedPlugins, placeholder = "Text eingeben...", className, toolbarClassName, editorClassName, fontSizes, colors, headings, customLinkComponent, customHeadingRenderer, customRenderer, onEditorAPIReady, theme, onImageUpload, }) => {
1331
+ const plugins = useMemo(() => {
1332
+ const allPlugins = [...(providedPlugins || defaultPlugins)];
1333
+ if (fontSizes && fontSizes.length > 0) {
1334
+ allPlugins.push(createFontSizePlugin(fontSizes));
1335
+ }
1336
+ if (colors && colors.length > 0) {
1337
+ allPlugins.push(createTextColorPlugin(colors));
1338
+ allPlugins.push(createBackgroundColorPlugin(colors));
1339
+ }
1340
+ if (headings && headings.length > 0) {
1341
+ allPlugins.push(createHeadingsPlugin(headings));
1342
+ }
1343
+ allPlugins.push(createImagePlugin(onImageUpload));
1344
+ return allPlugins;
1345
+ }, [providedPlugins, fontSizes, colors, headings, onImageUpload]);
1346
+ const editorRef = useRef(null);
1347
+ const historyRef = useRef(new HistoryManager());
1348
+ const isUpdatingRef = useRef(false);
1349
+ const notifyChange = useCallback((content) => {
1350
+ if (onChange && !isUpdatingRef.current) {
1351
+ onChange(content);
1352
+ }
1353
+ }, [onChange]);
1354
+ const restoreSelection = useCallback((editor) => {
1355
+ if (typeof window === 'undefined' || typeof document === 'undefined')
1356
+ return;
1357
+ const range = document.createRange();
1358
+ const selection = window.getSelection();
1359
+ if (editor.firstChild) {
1360
+ range.setStart(editor.firstChild, 0);
1361
+ range.collapse(true);
1362
+ selection?.removeAllRanges();
1363
+ selection?.addRange(range);
1364
+ }
1365
+ }, []);
1366
+ const editorAPI = useMemo(() => {
1367
+ const executeCommand = (command, value) => {
1368
+ const editor = editorRef.current;
1369
+ if (!editor)
1370
+ return false;
1371
+ if (command !== "undo" &&
1372
+ command !== "redo" &&
1373
+ command !== "insertImage") {
1374
+ const currentContent = domToContent(editor);
1375
+ historyRef.current.push(currentContent);
1376
+ }
1377
+ if (command === "undo") {
1378
+ const content = historyRef.current.undo();
1379
+ if (content && editor) {
1380
+ isUpdatingRef.current = true;
1381
+ contentToDOM(content, editor, customLinkComponent);
1382
+ restoreSelection(editor);
1383
+ isUpdatingRef.current = false;
1384
+ notifyChange(content);
1385
+ }
1386
+ return true;
1387
+ }
1388
+ if (command === "redo") {
1389
+ const content = historyRef.current.redo();
1390
+ if (content && editor) {
1391
+ isUpdatingRef.current = true;
1392
+ contentToDOM(content, editor, customLinkComponent);
1393
+ restoreSelection(editor);
1394
+ isUpdatingRef.current = false;
1395
+ notifyChange(content);
1396
+ }
1397
+ return true;
1398
+ }
1399
+ if (command === "insertImage" && value) {
1400
+ let selection = window.getSelection();
1401
+ if (!selection)
1402
+ return false;
1403
+ if (document.activeElement !== editor) {
1404
+ editor.focus();
1405
+ }
1406
+ if (selection.rangeCount === 0) {
1407
+ const range = document.createRange();
1408
+ if (editor.childNodes.length > 0) {
1409
+ const lastChild = editor.childNodes[editor.childNodes.length - 1];
1410
+ range.setStartAfter(lastChild);
1411
+ range.collapse(true);
1412
+ }
1413
+ else {
1414
+ const img = document.createElement("img");
1415
+ img.setAttribute("src", value);
1416
+ img.setAttribute("alt", "");
1417
+ img.style.maxWidth = "100%";
1418
+ img.style.height = "auto";
1419
+ img.style.display = "block";
1420
+ img.style.margin = "16px 0";
1421
+ editor.appendChild(img);
1422
+ const newRange = document.createRange();
1423
+ newRange.setStartAfter(img);
1424
+ newRange.collapse(true);
1425
+ selection.removeAllRanges();
1426
+ selection.addRange(newRange);
1427
+ isUpdatingRef.current = true;
1428
+ setTimeout(() => {
1429
+ if (editor) {
1430
+ const currentContent = domToContent(editor);
1431
+ historyRef.current.push(currentContent);
1432
+ isUpdatingRef.current = false;
1433
+ notifyChange(currentContent);
1434
+ }
1435
+ }, 0);
1436
+ return true;
1437
+ }
1438
+ selection.removeAllRanges();
1439
+ selection.addRange(range);
1440
+ }
1441
+ if (selection.rangeCount === 0)
1442
+ return false;
1443
+ const range = selection.getRangeAt(0);
1444
+ const container = range.commonAncestorContainer;
1445
+ let parentElement = null;
1446
+ if (container.nodeType === Node.TEXT_NODE) {
1447
+ parentElement = container.parentElement;
1448
+ }
1449
+ else if (container.nodeType === Node.ELEMENT_NODE) {
1450
+ parentElement = container;
1451
+ }
1452
+ const img = document.createElement("img");
1453
+ img.setAttribute("src", value);
1454
+ img.setAttribute("alt", "");
1455
+ img.style.maxWidth = "100%";
1456
+ img.style.height = "auto";
1457
+ img.style.display = "block";
1458
+ img.style.margin = "16px 0";
1459
+ if (parentElement &&
1460
+ parentElement !== editor &&
1461
+ (parentElement.tagName === "P" ||
1462
+ parentElement.tagName === "DIV" ||
1463
+ parentElement.tagName === "H1" ||
1464
+ parentElement.tagName === "H2" ||
1465
+ parentElement.tagName === "H3" ||
1466
+ parentElement.tagName === "H4" ||
1467
+ parentElement.tagName === "H5" ||
1468
+ parentElement.tagName === "H6")) {
1469
+ if (parentElement.nextSibling) {
1470
+ editor.insertBefore(img, parentElement.nextSibling);
1471
+ }
1472
+ else {
1473
+ editor.appendChild(img);
1474
+ }
1475
+ }
1476
+ else {
1477
+ try {
1478
+ range.insertNode(img);
1479
+ }
1480
+ catch (e) {
1481
+ editor.appendChild(img);
1482
+ }
1483
+ }
1484
+ const newRange = document.createRange();
1485
+ newRange.setStartAfter(img);
1486
+ newRange.collapse(true);
1487
+ selection.removeAllRanges();
1488
+ selection.addRange(newRange);
1489
+ isUpdatingRef.current = true;
1490
+ setTimeout(() => {
1491
+ if (editor) {
1492
+ const currentContent = domToContent(editor);
1493
+ historyRef.current.push(currentContent);
1494
+ isUpdatingRef.current = false;
1495
+ notifyChange(currentContent);
1496
+ }
1497
+ }, 0);
1498
+ return true;
1499
+ }
1500
+ const selection = window.getSelection();
1501
+ let savedRange = null;
1502
+ if (selection && selection.rangeCount > 0) {
1503
+ savedRange = selection.getRangeAt(0).cloneRange();
1504
+ }
1505
+ if (document.activeElement !== editor) {
1506
+ editor.focus();
1507
+ }
1508
+ if (!selection || selection.rangeCount === 0) {
1509
+ const range = document.createRange();
1510
+ if (editor.childNodes.length > 0) {
1511
+ const lastChild = editor.childNodes[editor.childNodes.length - 1];
1512
+ if (lastChild.nodeType === Node.TEXT_NODE) {
1513
+ range.setStart(lastChild, lastChild.textContent?.length || 0);
1514
+ range.setEnd(lastChild, lastChild.textContent?.length || 0);
1515
+ }
1516
+ else {
1517
+ range.selectNodeContents(lastChild);
1518
+ range.collapse(false);
1519
+ }
1520
+ }
1521
+ else {
1522
+ const p = document.createElement("p");
1523
+ editor.appendChild(p);
1524
+ const textNode = document.createTextNode("");
1525
+ p.appendChild(textNode);
1526
+ range.setStart(textNode, 0);
1527
+ range.setEnd(textNode, 0);
1528
+ }
1529
+ selection?.removeAllRanges();
1530
+ selection?.addRange(range);
1531
+ }
1532
+ else if (savedRange) {
1533
+ selection.removeAllRanges();
1534
+ selection.addRange(savedRange);
1535
+ }
1536
+ document.execCommand(command, false, value);
1537
+ setTimeout(() => {
1538
+ if (editor && !isUpdatingRef.current) {
1539
+ const content = domToContent(editor);
1540
+ notifyChange(content);
1541
+ }
1542
+ }, 0);
1543
+ return true;
1544
+ };
1545
+ return {
1546
+ executeCommand,
1547
+ getSelection: () => {
1548
+ if (typeof window === 'undefined')
1549
+ return null;
1550
+ return window.getSelection();
1551
+ },
1552
+ getContent: () => {
1553
+ const editor = editorRef.current;
1554
+ if (!editor)
1555
+ return createEmptyContent();
1556
+ return domToContent(editor);
1557
+ },
1558
+ setContent: (content) => {
1559
+ const editor = editorRef.current;
1560
+ if (!editor)
1561
+ return;
1562
+ isUpdatingRef.current = true;
1563
+ contentToDOM(content, editor, customLinkComponent);
1564
+ historyRef.current.push(content);
1565
+ isUpdatingRef.current = false;
1566
+ notifyChange(content);
1567
+ },
1568
+ insertBlock: (type, attributes) => {
1569
+ const selection = window.getSelection();
1570
+ if (!selection || selection.rangeCount === 0)
1571
+ return;
1572
+ const range = selection.getRangeAt(0);
1573
+ const block = document.createElement(type);
1574
+ if (attributes) {
1575
+ Object.entries(attributes).forEach(([key, value]) => {
1576
+ block.setAttribute(key, value);
1577
+ });
1578
+ }
1579
+ range.insertNode(block);
1580
+ const textNode = document.createTextNode("\u200B"); // Zero-width space
1581
+ block.appendChild(textNode);
1582
+ // Cursor setzen
1583
+ range.setStartAfter(textNode);
1584
+ range.collapse(true);
1585
+ selection.removeAllRanges();
1586
+ selection.addRange(range);
1587
+ const editor = editorRef.current;
1588
+ if (editor) {
1589
+ const content = domToContent(editor);
1590
+ notifyChange(content);
1591
+ }
1592
+ },
1593
+ insertInline: (type, attributes) => {
1594
+ const selection = window.getSelection();
1595
+ if (!selection || selection.rangeCount === 0)
1596
+ return;
1597
+ const range = selection.getRangeAt(0);
1598
+ const inline = document.createElement(type);
1599
+ if (attributes) {
1600
+ Object.entries(attributes).forEach(([key, value]) => {
1601
+ inline.setAttribute(key, value);
1602
+ });
1603
+ }
1604
+ try {
1605
+ range.surroundContents(inline);
1606
+ }
1607
+ catch (e) {
1608
+ // Falls surroundContents fehlschlägt, versuche es anders
1609
+ const contents = range.extractContents();
1610
+ inline.appendChild(contents);
1611
+ range.insertNode(inline);
1612
+ }
1613
+ // Cursor setzen
1614
+ range.setStartAfter(inline);
1615
+ range.collapse(true);
1616
+ selection.removeAllRanges();
1617
+ selection.addRange(range);
1618
+ const editor = editorRef.current;
1619
+ if (editor) {
1620
+ const content = domToContent(editor);
1621
+ notifyChange(content);
1622
+ }
1623
+ },
1624
+ undo: () => {
1625
+ executeCommand("undo");
1626
+ },
1627
+ redo: () => {
1628
+ executeCommand("redo");
1629
+ },
1630
+ canUndo: () => {
1631
+ return historyRef.current.canUndo();
1632
+ },
1633
+ canRedo: () => {
1634
+ return historyRef.current.canRedo();
1635
+ },
1636
+ importHtml: (htmlString) => {
1637
+ const content = htmlToContent(htmlString);
1638
+ const editor = editorRef.current;
1639
+ if (editor) {
1640
+ isUpdatingRef.current = true;
1641
+ contentToDOM(content, editor, customLinkComponent);
1642
+ historyRef.current.push(content);
1643
+ isUpdatingRef.current = false;
1644
+ notifyChange(content);
1645
+ }
1646
+ return content;
1647
+ },
1648
+ exportHtml: () => {
1649
+ const editor = editorRef.current;
1650
+ if (!editor)
1651
+ return "";
1652
+ const content = domToContent(editor);
1653
+ return contentToHTML(content);
1654
+ },
1655
+ clearFormatting: () => {
1656
+ const editor = editorRef.current;
1657
+ if (!editor)
1658
+ return;
1659
+ const selection = window.getSelection();
1660
+ if (selection && selection.rangeCount > 0) {
1661
+ const currentContent = domToContent(editor);
1662
+ historyRef.current.push(currentContent);
1663
+ clearFormatting(selection);
1664
+ setTimeout(() => {
1665
+ if (editor) {
1666
+ const content = domToContent(editor);
1667
+ notifyChange(content);
1668
+ }
1669
+ }, 0);
1670
+ }
1671
+ },
1672
+ clearTextColor: () => {
1673
+ const editor = editorRef.current;
1674
+ if (!editor)
1675
+ return;
1676
+ const selection = window.getSelection();
1677
+ if (selection && selection.rangeCount > 0) {
1678
+ const currentContent = domToContent(editor);
1679
+ historyRef.current.push(currentContent);
1680
+ clearTextColor(selection);
1681
+ setTimeout(() => {
1682
+ if (editor) {
1683
+ const content = domToContent(editor);
1684
+ notifyChange(content);
1685
+ }
1686
+ }, 0);
1687
+ }
1688
+ },
1689
+ clearBackgroundColor: () => {
1690
+ const editor = editorRef.current;
1691
+ if (!editor)
1692
+ return;
1693
+ const selection = window.getSelection();
1694
+ if (selection && selection.rangeCount > 0) {
1695
+ const currentContent = domToContent(editor);
1696
+ historyRef.current.push(currentContent);
1697
+ clearBackgroundColor(selection);
1698
+ setTimeout(() => {
1699
+ if (editor) {
1700
+ const content = domToContent(editor);
1701
+ notifyChange(content);
1702
+ }
1703
+ }, 0);
1704
+ }
1705
+ },
1706
+ clearFontSize: () => {
1707
+ const editor = editorRef.current;
1708
+ if (!editor)
1709
+ return;
1710
+ const selection = window.getSelection();
1711
+ if (selection && selection.rangeCount > 0) {
1712
+ const currentContent = domToContent(editor);
1713
+ historyRef.current.push(currentContent);
1714
+ clearFontSize(selection);
1715
+ setTimeout(() => {
1716
+ if (editor) {
1717
+ const content = domToContent(editor);
1718
+ notifyChange(content);
1719
+ }
1720
+ }, 0);
1721
+ }
1722
+ },
1723
+ clearLinks: () => {
1724
+ const editor = editorRef.current;
1725
+ if (!editor)
1726
+ return;
1727
+ const selection = window.getSelection();
1728
+ if (selection && selection.rangeCount > 0) {
1729
+ const currentContent = domToContent(editor);
1730
+ historyRef.current.push(currentContent);
1731
+ clearLinks(selection);
1732
+ setTimeout(() => {
1733
+ if (editor) {
1734
+ const content = domToContent(editor);
1735
+ notifyChange(content);
1736
+ }
1737
+ }, 0);
1738
+ }
1739
+ },
1740
+ };
1741
+ }, [
1742
+ notifyChange,
1743
+ restoreSelection,
1744
+ customLinkComponent,
1745
+ customHeadingRenderer,
1746
+ ]);
1747
+ useEffect(() => {
1748
+ if (onEditorAPIReady) {
1749
+ onEditorAPIReady(editorAPI);
1750
+ }
1751
+ }, [editorAPI, onEditorAPIReady]);
1752
+ const isInitializedRef = useRef(false);
1753
+ useEffect(() => {
1754
+ const editor = editorRef.current;
1755
+ if (!editor || isInitializedRef.current)
1756
+ return;
1757
+ const content = initialContent || createEmptyContent();
1758
+ isUpdatingRef.current = true;
1759
+ contentToDOM(content, editor, customLinkComponent);
1760
+ historyRef.current.push(content);
1761
+ isUpdatingRef.current = false;
1762
+ isInitializedRef.current = true;
1763
+ let inputTimeout = null;
1764
+ const handleInput = () => {
1765
+ if (isUpdatingRef.current)
1766
+ return;
1767
+ const content = domToContent(editor);
1768
+ notifyChange(content);
1769
+ if (inputTimeout) {
1770
+ clearTimeout(inputTimeout);
1771
+ }
1772
+ inputTimeout = setTimeout(() => {
1773
+ historyRef.current.push(content);
1774
+ inputTimeout = null;
1775
+ }, 300);
1776
+ };
1777
+ const handleKeyDown = (e) => {
1778
+ const isModifierPressed = e.metaKey || e.ctrlKey;
1779
+ if (e.key === "Tab" && !isModifierPressed && !e.altKey) {
1780
+ const selection = window.getSelection();
1781
+ const isSelectionInEditor = selection &&
1782
+ selection.rangeCount > 0 &&
1783
+ editor.contains(selection.getRangeAt(0).commonAncestorContainer);
1784
+ const isEditorFocused = document.activeElement === editor ||
1785
+ editor.contains(document.activeElement) ||
1786
+ isSelectionInEditor;
1787
+ if (!isEditorFocused) {
1788
+ return;
1789
+ }
1790
+ e.preventDefault();
1791
+ e.stopPropagation();
1792
+ if (isSelectionInEditor &&
1793
+ selection &&
1794
+ selection.rangeCount > 0) {
1795
+ const range = selection.getRangeAt(0);
1796
+ const container = range.commonAncestorContainer;
1797
+ const listItem = container.nodeType === Node.TEXT_NODE
1798
+ ? container.parentElement?.closest("li")
1799
+ : container.closest("li");
1800
+ if (listItem && editor.contains(listItem)) {
1801
+ e.stopImmediatePropagation();
1802
+ const currentContent = domToContent(editor);
1803
+ historyRef.current.push(currentContent);
1804
+ if (e.shiftKey) {
1805
+ outdentListItem(selection);
1806
+ }
1807
+ else {
1808
+ indentListItem(selection);
1809
+ }
1810
+ setTimeout(() => {
1811
+ if (editor) {
1812
+ const content = domToContent(editor);
1813
+ notifyChange(content);
1814
+ }
1815
+ }, 0);
1816
+ return;
1817
+ }
1818
+ }
1819
+ document.execCommand("insertText", false, "\t");
1820
+ }
1821
+ if (isModifierPressed && e.key === "z" && !e.shiftKey) {
1822
+ e.preventDefault();
1823
+ e.stopPropagation();
1824
+ editorAPI.undo();
1825
+ }
1826
+ else if (isModifierPressed &&
1827
+ (e.key === "y" || (e.key === "z" && e.shiftKey))) {
1828
+ e.preventDefault();
1829
+ e.stopPropagation();
1830
+ editorAPI.redo();
1831
+ }
1832
+ };
1833
+ editor.addEventListener("input", handleInput);
1834
+ editor.addEventListener("keydown", handleKeyDown);
1835
+ return () => {
1836
+ editor.removeEventListener("input", handleInput);
1837
+ editor.removeEventListener("keydown", handleKeyDown);
1838
+ if (inputTimeout) {
1839
+ clearTimeout(inputTimeout);
1840
+ }
1841
+ };
1842
+ }, [editorAPI, notifyChange]);
1843
+ const handlePaste = (e) => {
1844
+ e.preventDefault();
1845
+ const html = e.clipboardData.getData("text/html");
1846
+ const text = e.clipboardData.getData("text/plain");
1847
+ if (html) {
1848
+ try {
1849
+ const pastedContent = htmlToContent(html);
1850
+ const editor = editorRef.current;
1851
+ if (!editor)
1852
+ return;
1853
+ const selection = window.getSelection();
1854
+ if (selection && selection.rangeCount > 0) {
1855
+ const range = selection.getRangeAt(0);
1856
+ range.deleteContents();
1857
+ const tempDiv = document.createElement("div");
1858
+ contentToDOM(pastedContent, tempDiv, customLinkComponent, customHeadingRenderer);
1859
+ const fragment = document.createDocumentFragment();
1860
+ while (tempDiv.firstChild) {
1861
+ fragment.appendChild(tempDiv.firstChild);
1862
+ }
1863
+ range.insertNode(fragment);
1864
+ if (fragment.lastChild) {
1865
+ range.setStartAfter(fragment.lastChild);
1866
+ range.collapse(true);
1867
+ }
1868
+ selection.removeAllRanges();
1869
+ selection.addRange(range);
1870
+ const content = domToContent(editor);
1871
+ notifyChange(content);
1872
+ }
1873
+ }
1874
+ catch (error) {
1875
+ document.execCommand("insertText", false, text);
1876
+ }
1877
+ }
1878
+ else if (text) {
1879
+ document.execCommand("insertText", false, text);
1880
+ }
1881
+ };
1882
+ const containerStyle = theme
1883
+ ? {
1884
+ ...(theme.borderColor &&
1885
+ {
1886
+ "--rte-border-color": theme.borderColor,
1887
+ }),
1888
+ ...(theme.borderRadius &&
1889
+ {
1890
+ "--rte-border-radius": `${theme.borderRadius}px`,
1891
+ }),
1892
+ ...(theme.toolbarBg &&
1893
+ {
1894
+ "--rte-toolbar-bg": theme.toolbarBg,
1895
+ }),
1896
+ ...(theme.buttonHoverBg &&
1897
+ {
1898
+ "--rte-button-hover-bg": theme.buttonHoverBg,
1899
+ }),
1900
+ ...(theme.contentBg &&
1901
+ {
1902
+ "--rte-content-bg": theme.contentBg,
1903
+ }),
1904
+ ...(theme.primaryColor &&
1905
+ {
1906
+ "--rte-primary-color": theme.primaryColor,
1907
+ }),
1908
+ }
1909
+ : {};
1910
+ return (jsxs("div", { className: `rte-container ${className || ""}`, style: containerStyle, children: [jsx(Toolbar, { plugins: plugins, editorAPI: editorAPI, className: toolbarClassName }), jsx("div", { ref: editorRef, contentEditable: true, className: `rte-editor ${editorClassName || ""}`, "data-placeholder": placeholder, onPaste: handlePaste, suppressContentEditableWarning: true })] }));
1911
+ };
1912
+
1913
+ /**
1914
+ * Link-Plugin mit verbesserter Funktionalität
1915
+ */
1916
+ function createLinkPlugin() {
1917
+ return {
1918
+ name: 'link',
1919
+ type: 'inline',
1920
+ command: 'createLink',
1921
+ renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? 'rte-toolbar-button-active' : ''}`, title: "Link einf\u00FCgen", "aria-label": "Link einf\u00FCgen", children: jsx(IconWrapper, { icon: "mdi:link", width: 18, height: 18 }) })),
1922
+ execute: (editor) => {
1923
+ const selection = editor.getSelection();
1924
+ if (!selection || selection.rangeCount === 0)
1925
+ return;
1926
+ const range = selection.getRangeAt(0);
1927
+ const container = range.commonAncestorContainer;
1928
+ const element = container.nodeType === Node.TEXT_NODE
1929
+ ? container.parentElement
1930
+ : container;
1931
+ // Prüfe ob bereits ein Link vorhanden ist
1932
+ const existingLink = element?.closest('a');
1933
+ if (existingLink) {
1934
+ // Link entfernen
1935
+ const parent = existingLink.parentNode;
1936
+ if (parent) {
1937
+ while (existingLink.firstChild) {
1938
+ parent.insertBefore(existingLink.firstChild, existingLink);
1939
+ }
1940
+ parent.removeChild(existingLink);
1941
+ // Content aktualisieren
1942
+ const editorEl = editor.getSelection()?.anchorNode;
1943
+ if (editorEl) {
1944
+ const content = editor.getContent();
1945
+ editor.setContent(content);
1946
+ }
1947
+ }
1948
+ }
1949
+ else {
1950
+ // Neuen Link einfügen
1951
+ const url = prompt('URL eingeben:');
1952
+ if (url) {
1953
+ editor.executeCommand('createLink', url);
1954
+ }
1955
+ }
1956
+ },
1957
+ isActive: (editor) => {
1958
+ const selection = editor.getSelection();
1959
+ if (!selection || selection.rangeCount === 0)
1960
+ return false;
1961
+ const range = selection.getRangeAt(0);
1962
+ const container = range.commonAncestorContainer;
1963
+ const element = container.nodeType === Node.TEXT_NODE
1964
+ ? container.parentElement
1965
+ : container;
1966
+ if (!element)
1967
+ return false;
1968
+ return element.closest('a') !== null;
1969
+ },
1970
+ getCurrentValue: (editor) => {
1971
+ const selection = editor.getSelection();
1972
+ if (!selection || selection.rangeCount === 0)
1973
+ return undefined;
1974
+ const range = selection.getRangeAt(0);
1975
+ const container = range.commonAncestorContainer;
1976
+ const element = container.nodeType === Node.TEXT_NODE
1977
+ ? container.parentElement
1978
+ : container;
1979
+ if (!element)
1980
+ return undefined;
1981
+ const link = element.closest('a');
1982
+ return link ? link.href : undefined;
1983
+ },
1984
+ canExecute: (editor) => {
1985
+ const selection = editor.getSelection();
1986
+ return selection !== null && selection.rangeCount > 0;
1987
+ },
1988
+ };
1989
+ }
1990
+ const linkPlugin = createLinkPlugin();
1991
+ /**
1992
+ * Blockquote-Plugin
1993
+ */
1994
+ const blockquotePlugin = {
1995
+ name: 'blockquote',
1996
+ type: 'block',
1997
+ command: 'formatBlock',
1998
+ renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? 'rte-toolbar-button-active' : ''}`, title: "Zitat", "aria-label": "Zitat", children: jsx(IconWrapper, { icon: "mdi:format-quote-close", width: 18, height: 18 }) })),
1999
+ execute: (editor) => {
2000
+ const selection = editor.getSelection();
2001
+ if (!selection || selection.rangeCount === 0)
2002
+ return;
2003
+ const range = selection.getRangeAt(0);
2004
+ const container = range.commonAncestorContainer;
2005
+ const element = container.nodeType === Node.TEXT_NODE
2006
+ ? container.parentElement
2007
+ : container;
2008
+ if (!element)
2009
+ return;
2010
+ const isBlockquote = element.closest('blockquote') !== null;
2011
+ if (isBlockquote) {
2012
+ editor.executeCommand('formatBlock', '<p>');
2013
+ }
2014
+ else {
2015
+ editor.executeCommand('formatBlock', '<blockquote>');
2016
+ }
2017
+ },
2018
+ isActive: (editor) => {
2019
+ const selection = editor.getSelection();
2020
+ if (!selection || selection.rangeCount === 0)
2021
+ return false;
2022
+ const range = selection.getRangeAt(0);
2023
+ const container = range.commonAncestorContainer;
2024
+ const element = container.nodeType === Node.TEXT_NODE
2025
+ ? container.parentElement
2026
+ : container;
2027
+ if (!element)
2028
+ return false;
2029
+ return element.closest('blockquote') !== null;
2030
+ },
2031
+ canExecute: (editor) => {
2032
+ const selection = editor.getSelection();
2033
+ return selection !== null && selection.rangeCount > 0;
2034
+ },
2035
+ };
2036
+ /**
2037
+ * Unordered List Plugin
2038
+ */
2039
+ const unorderedListPlugin = {
2040
+ name: 'unorderedList',
2041
+ type: 'block',
2042
+ command: 'insertUnorderedList',
2043
+ renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? 'rte-toolbar-button-active' : ''}`, title: "Aufz\u00E4hlungsliste", "aria-label": "Aufz\u00E4hlungsliste", children: jsx(IconWrapper, { icon: "mdi:format-list-bulleted", width: 18, height: 18 }) })),
2044
+ execute: (editor) => {
2045
+ editor.executeCommand('insertUnorderedList');
2046
+ },
2047
+ isActive: (editor) => {
2048
+ if (typeof document === 'undefined')
2049
+ return false;
2050
+ return document.queryCommandState('insertUnorderedList');
2051
+ },
2052
+ canExecute: (editor) => {
2053
+ const selection = editor.getSelection();
2054
+ return selection !== null && selection.rangeCount > 0;
2055
+ },
2056
+ };
2057
+ /**
2058
+ * Ordered List Plugin
2059
+ */
2060
+ const orderedListPlugin = {
2061
+ name: 'orderedList',
2062
+ type: 'block',
2063
+ command: 'insertOrderedList',
2064
+ renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? 'rte-toolbar-button-active' : ''}`, title: "Nummerierte Liste", "aria-label": "Nummerierte Liste", children: jsx(IconWrapper, { icon: "mdi:format-list-numbered", width: 18, height: 18 }) })),
2065
+ execute: (editor) => {
2066
+ editor.executeCommand('insertOrderedList');
2067
+ },
2068
+ isActive: (editor) => {
2069
+ if (typeof document === 'undefined')
2070
+ return false;
2071
+ return document.queryCommandState('insertOrderedList');
2072
+ },
2073
+ canExecute: (editor) => {
2074
+ const selection = editor.getSelection();
2075
+ return selection !== null && selection.rangeCount > 0;
2076
+ },
2077
+ };
2078
+
2079
+ export { Dropdown, Editor, HistoryManager, Toolbar, blockquotePlugin, boldPlugin, clearFormattingPlugin, contentToDOM, contentToHTML, createBackgroundColorPlugin, createEmptyContent, createFontSizePlugin, createHeadingsPlugin, createImagePlugin, createLinkPlugin, createTextColorPlugin, Editor as default, defaultPlugins, domToContent, getCurrentBackgroundColor, getCurrentFontSize, getCurrentHeading, getCurrentTextColor, htmlToContent, indentListItem, italicPlugin, linkPlugin, orderedListPlugin, outdentListItem, redoPlugin, underlinePlugin, undoPlugin, unorderedListPlugin };
2080
+ //# sourceMappingURL=index.esm.js.map