@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.
- package/package.json +57 -58
- package/src/components/Dropdown.tsx +0 -103
- package/src/components/Editor.css +0 -2
- package/src/components/Editor.tsx +0 -828
- package/src/components/FloatingToolbar.tsx +0 -214
- package/src/components/IconWrapper.tsx +0 -14
- package/src/components/Icons.tsx +0 -374
- package/src/components/Toolbar.tsx +0 -137
- package/src/components/index.ts +0 -3
- package/src/index.ts +0 -19
- package/src/plugins/base.tsx +0 -91
- package/src/plugins/blockFormat.tsx +0 -194
- package/src/plugins/clearFormatting.tsx +0 -31
- package/src/plugins/colors.tsx +0 -122
- package/src/plugins/fontSize.tsx +0 -81
- package/src/plugins/headings.tsx +0 -87
- package/src/plugins/image.tsx +0 -189
- package/src/plugins/index.tsx +0 -161
- package/src/plugins/listIndent.tsx +0 -90
- package/src/plugins/optional.tsx +0 -243
- package/src/styles.css +0 -638
- package/src/types.ts +0 -95
- package/src/utils/clearFormatting.ts +0 -244
- package/src/utils/content.ts +0 -290
- package/src/utils/history.ts +0 -59
- package/src/utils/listIndent.ts +0 -171
- package/src/utils/stateReflection.ts +0 -175
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { ButtonProps, EditorAPI, Plugin } from "../types";
|
|
3
|
-
|
|
4
|
-
interface ToolbarProps {
|
|
5
|
-
plugins: Plugin[];
|
|
6
|
-
editorAPI: EditorAPI;
|
|
7
|
-
className?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const Toolbar: React.FC<ToolbarProps> = ({
|
|
11
|
-
plugins,
|
|
12
|
-
editorAPI,
|
|
13
|
-
className,
|
|
14
|
-
}) => {
|
|
15
|
-
const [updateTrigger, setUpdateTrigger] = useState(0);
|
|
16
|
-
const [isClient, setIsClient] = useState(false);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
setIsClient(true);
|
|
20
|
-
|
|
21
|
-
const handleSelectionChange = () => {
|
|
22
|
-
setUpdateTrigger((prev) => prev + 1);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const handleMouseUp = () => {
|
|
26
|
-
setTimeout(handleSelectionChange, 10);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const handleKeyUp = () => {
|
|
30
|
-
setTimeout(handleSelectionChange, 10);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
if (typeof document !== 'undefined') {
|
|
34
|
-
document.addEventListener("selectionchange", handleSelectionChange);
|
|
35
|
-
document.addEventListener("mouseup", handleMouseUp);
|
|
36
|
-
document.addEventListener("keyup", handleKeyUp);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return () => {
|
|
40
|
-
if (typeof document !== 'undefined') {
|
|
41
|
-
document.removeEventListener(
|
|
42
|
-
"selectionchange",
|
|
43
|
-
handleSelectionChange
|
|
44
|
-
);
|
|
45
|
-
document.removeEventListener("mouseup", handleMouseUp);
|
|
46
|
-
document.removeEventListener("keyup", handleKeyUp);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
const handlePluginClick = (plugin: Plugin, value?: string) => {
|
|
52
|
-
if (plugin.canExecute?.(editorAPI) !== false) {
|
|
53
|
-
if (plugin.execute) {
|
|
54
|
-
plugin.execute(editorAPI, value);
|
|
55
|
-
} else if (plugin.command && value !== undefined) {
|
|
56
|
-
editorAPI.executeCommand(plugin.command, value);
|
|
57
|
-
} else if (plugin.command) {
|
|
58
|
-
editorAPI.executeCommand(plugin.command);
|
|
59
|
-
}
|
|
60
|
-
setTimeout(() => setUpdateTrigger((prev) => prev + 1), 50);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const leftPlugins = plugins.filter((p) => p.name !== "clearFormatting");
|
|
65
|
-
const clearFormattingPlugin = plugins.find(
|
|
66
|
-
(p) => p.name === "clearFormatting"
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<div className={`rte-toolbar rte-toolbar-sticky ${className || ""}`}>
|
|
71
|
-
<div className="rte-toolbar-left">
|
|
72
|
-
{leftPlugins.map((plugin) => {
|
|
73
|
-
if (!plugin.renderButton) return null;
|
|
74
|
-
|
|
75
|
-
const isActive = isClient && plugin.isActive
|
|
76
|
-
? plugin.isActive(editorAPI)
|
|
77
|
-
: false;
|
|
78
|
-
const canExecute = isClient && plugin.canExecute
|
|
79
|
-
? plugin.canExecute(editorAPI)
|
|
80
|
-
: true;
|
|
81
|
-
|
|
82
|
-
const currentValue = isClient && plugin.getCurrentValue
|
|
83
|
-
? plugin.getCurrentValue(editorAPI)
|
|
84
|
-
: undefined;
|
|
85
|
-
|
|
86
|
-
const buttonProps: ButtonProps & { [key: string]: any } = {
|
|
87
|
-
isActive,
|
|
88
|
-
onClick: () => handlePluginClick(plugin),
|
|
89
|
-
disabled: !canExecute,
|
|
90
|
-
onSelect: (value: string) =>
|
|
91
|
-
handlePluginClick(plugin, value),
|
|
92
|
-
editorAPI,
|
|
93
|
-
currentValue,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<React.Fragment key={plugin.name}>
|
|
98
|
-
{plugin.renderButton(buttonProps)}
|
|
99
|
-
</React.Fragment>
|
|
100
|
-
);
|
|
101
|
-
})}
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
{clearFormattingPlugin && clearFormattingPlugin.renderButton && (
|
|
105
|
-
<div className="rte-toolbar-right">
|
|
106
|
-
<div className="rte-toolbar-divider" />
|
|
107
|
-
{(() => {
|
|
108
|
-
const isActive = isClient && clearFormattingPlugin.isActive
|
|
109
|
-
? clearFormattingPlugin.isActive(editorAPI)
|
|
110
|
-
: false;
|
|
111
|
-
const canExecute = isClient && clearFormattingPlugin.canExecute
|
|
112
|
-
? clearFormattingPlugin.canExecute(editorAPI)
|
|
113
|
-
: true;
|
|
114
|
-
|
|
115
|
-
const buttonProps: ButtonProps & {
|
|
116
|
-
[key: string]: any;
|
|
117
|
-
} = {
|
|
118
|
-
isActive,
|
|
119
|
-
onClick: () =>
|
|
120
|
-
handlePluginClick(clearFormattingPlugin),
|
|
121
|
-
disabled: !canExecute,
|
|
122
|
-
editorAPI,
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<React.Fragment key={clearFormattingPlugin.name}>
|
|
127
|
-
{clearFormattingPlugin.renderButton(
|
|
128
|
-
buttonProps
|
|
129
|
-
)}
|
|
130
|
-
</React.Fragment>
|
|
131
|
-
);
|
|
132
|
-
})()}
|
|
133
|
-
</div>
|
|
134
|
-
)}
|
|
135
|
-
</div>
|
|
136
|
-
);
|
|
137
|
-
};
|
package/src/components/index.ts
DELETED
package/src/index.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export { Dropdown } from "./components/Dropdown";
|
|
2
|
-
export { Editor } from "./components/Editor";
|
|
3
|
-
export { Toolbar } from "./components/Toolbar";
|
|
4
|
-
export * from "./plugins";
|
|
5
|
-
export * from "./plugins/blockFormat";
|
|
6
|
-
export * from "./plugins/clearFormatting";
|
|
7
|
-
export * from "./plugins/colors";
|
|
8
|
-
export * from "./plugins/fontSize";
|
|
9
|
-
export * from "./plugins/headings";
|
|
10
|
-
export * from "./plugins/image";
|
|
11
|
-
export * from "./plugins/optional";
|
|
12
|
-
export * from "./types";
|
|
13
|
-
export * from "./utils/content";
|
|
14
|
-
export { contentToHTML, htmlToContent } from "./utils/content";
|
|
15
|
-
export { HistoryManager } from "./utils/history";
|
|
16
|
-
export { indentListItem, outdentListItem } from "./utils/listIndent";
|
|
17
|
-
export * from "./utils/stateReflection";
|
|
18
|
-
|
|
19
|
-
export { Editor as default } from "./components/Editor";
|
package/src/plugins/base.tsx
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Plugin, EditorAPI, ButtonProps } from '../types';
|
|
3
|
-
import { IconWrapper } from '../components/IconWrapper';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Basis-Plugin für Inline-Formatierungen
|
|
7
|
-
*/
|
|
8
|
-
export function createInlinePlugin(
|
|
9
|
-
name: string,
|
|
10
|
-
command: string,
|
|
11
|
-
icon: string,
|
|
12
|
-
label: string
|
|
13
|
-
): Plugin {
|
|
14
|
-
return {
|
|
15
|
-
name,
|
|
16
|
-
type: 'inline',
|
|
17
|
-
command,
|
|
18
|
-
renderButton: (props: ButtonProps) => (
|
|
19
|
-
<button
|
|
20
|
-
type="button"
|
|
21
|
-
onClick={props.onClick}
|
|
22
|
-
disabled={props.disabled}
|
|
23
|
-
className={`rte-toolbar-button ${props.isActive ? 'rte-toolbar-button-active' : ''}`}
|
|
24
|
-
title={label}
|
|
25
|
-
aria-label={label}
|
|
26
|
-
>
|
|
27
|
-
<IconWrapper icon={icon} width={18} height={18} />
|
|
28
|
-
</button>
|
|
29
|
-
),
|
|
30
|
-
execute: (editor: EditorAPI) => {
|
|
31
|
-
editor.executeCommand(command);
|
|
32
|
-
},
|
|
33
|
-
isActive: (editor: EditorAPI) => {
|
|
34
|
-
if (typeof window === 'undefined' || typeof document === 'undefined') return false;
|
|
35
|
-
const selection = editor.getSelection();
|
|
36
|
-
if (!selection || selection.rangeCount === 0) return false;
|
|
37
|
-
|
|
38
|
-
const range = selection.getRangeAt(0);
|
|
39
|
-
const container = range.commonAncestorContainer;
|
|
40
|
-
const element = container.nodeType === Node.TEXT_NODE
|
|
41
|
-
? container.parentElement
|
|
42
|
-
: container as HTMLElement;
|
|
43
|
-
|
|
44
|
-
if (!element) return false;
|
|
45
|
-
|
|
46
|
-
return document.queryCommandState(command);
|
|
47
|
-
},
|
|
48
|
-
canExecute: (editor: EditorAPI) => {
|
|
49
|
-
// Formatierung sollte auch ohne Selection möglich sein
|
|
50
|
-
// (z.B. wenn Editor leer ist, wird beim Klick eine Selection erstellt)
|
|
51
|
-
return true;
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Basis-Plugin für Commands
|
|
58
|
-
*/
|
|
59
|
-
export function createCommandPlugin(
|
|
60
|
-
name: string,
|
|
61
|
-
command: string,
|
|
62
|
-
icon: string,
|
|
63
|
-
label: string
|
|
64
|
-
): Plugin {
|
|
65
|
-
return {
|
|
66
|
-
name,
|
|
67
|
-
type: 'command',
|
|
68
|
-
command,
|
|
69
|
-
renderButton: (props: ButtonProps) => (
|
|
70
|
-
<button
|
|
71
|
-
type="button"
|
|
72
|
-
onClick={props.onClick}
|
|
73
|
-
disabled={props.disabled}
|
|
74
|
-
className="rte-toolbar-button"
|
|
75
|
-
title={label}
|
|
76
|
-
aria-label={label}
|
|
77
|
-
>
|
|
78
|
-
<IconWrapper icon={icon} width={18} height={18} />
|
|
79
|
-
</button>
|
|
80
|
-
),
|
|
81
|
-
execute: (editor: EditorAPI) => {
|
|
82
|
-
editor.executeCommand(command);
|
|
83
|
-
},
|
|
84
|
-
canExecute: (editor: EditorAPI) => {
|
|
85
|
-
if (command === 'undo') return editor.canUndo();
|
|
86
|
-
if (command === 'redo') return editor.canRedo();
|
|
87
|
-
return true;
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { Dropdown } from "../components/Dropdown";
|
|
2
|
-
import { ButtonProps, EditorAPI, Plugin } from "../types";
|
|
3
|
-
|
|
4
|
-
const defaultHeadings = ["h1", "h2", "h3"];
|
|
5
|
-
|
|
6
|
-
const headingLabels: Record<string, string> = {
|
|
7
|
-
h1: "Überschrift 1",
|
|
8
|
-
h2: "Überschrift 2",
|
|
9
|
-
h3: "Überschrift 3",
|
|
10
|
-
h4: "Überschrift 4",
|
|
11
|
-
h5: "Überschrift 5",
|
|
12
|
-
h6: "Überschrift 6",
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Erstellt ein Block-Format-Plugin, das Headlines, Listen und Quote in einem Dropdown kombiniert
|
|
17
|
-
* @param headings - Array von Heading-Levels (z.B. ["h1", "h2", "h3"])
|
|
18
|
-
*/
|
|
19
|
-
export function createBlockFormatPlugin(
|
|
20
|
-
headings: string[] = defaultHeadings
|
|
21
|
-
): Plugin {
|
|
22
|
-
const options = [
|
|
23
|
-
{ value: "p", label: "Normal", headingPreview: "p" },
|
|
24
|
-
...headings.map((h) => ({
|
|
25
|
-
value: h,
|
|
26
|
-
label: headingLabels[h] || h.toUpperCase(),
|
|
27
|
-
headingPreview: h,
|
|
28
|
-
})),
|
|
29
|
-
{
|
|
30
|
-
value: "ul",
|
|
31
|
-
label: "Aufzählungsliste",
|
|
32
|
-
icon: "mdi:format-list-bulleted",
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
value: "ol",
|
|
36
|
-
label: "Nummerierte Liste",
|
|
37
|
-
icon: "mdi:format-list-numbered",
|
|
38
|
-
},
|
|
39
|
-
{ value: "blockquote", label: "Zitat", icon: "mdi:format-quote-close" },
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
name: "blockFormat",
|
|
44
|
-
type: "block",
|
|
45
|
-
renderButton: (
|
|
46
|
-
props: ButtonProps & {
|
|
47
|
-
onSelect?: (value: string) => void;
|
|
48
|
-
editorAPI?: EditorAPI;
|
|
49
|
-
currentValue?: string;
|
|
50
|
-
}
|
|
51
|
-
) => {
|
|
52
|
-
// Aktuelles Format bestimmen
|
|
53
|
-
const editor = props.editorAPI;
|
|
54
|
-
let currentValue = props.currentValue;
|
|
55
|
-
|
|
56
|
-
if (!currentValue && editor) {
|
|
57
|
-
const selection = editor.getSelection();
|
|
58
|
-
if (selection && selection.rangeCount > 0) {
|
|
59
|
-
const range = selection.getRangeAt(0);
|
|
60
|
-
const container = range.commonAncestorContainer;
|
|
61
|
-
const element =
|
|
62
|
-
container.nodeType === Node.TEXT_NODE
|
|
63
|
-
? container.parentElement
|
|
64
|
-
: (container as HTMLElement);
|
|
65
|
-
|
|
66
|
-
if (element) {
|
|
67
|
-
const tagName = element.tagName.toLowerCase();
|
|
68
|
-
|
|
69
|
-
// Prüfe auf Heading
|
|
70
|
-
if (headings.includes(tagName)) {
|
|
71
|
-
currentValue = tagName;
|
|
72
|
-
}
|
|
73
|
-
// Prüfe auf Blockquote
|
|
74
|
-
else if (element.closest("blockquote")) {
|
|
75
|
-
currentValue = "blockquote";
|
|
76
|
-
}
|
|
77
|
-
// Prüfe auf Liste
|
|
78
|
-
else if (element.closest("ul")) {
|
|
79
|
-
currentValue = "ul";
|
|
80
|
-
} else if (element.closest("ol")) {
|
|
81
|
-
currentValue = "ol";
|
|
82
|
-
}
|
|
83
|
-
// Prüfe auf Paragraph
|
|
84
|
-
else if (tagName === "p") {
|
|
85
|
-
currentValue = "p";
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<Dropdown
|
|
93
|
-
icon="mdi:format-header-1"
|
|
94
|
-
label="Format"
|
|
95
|
-
options={options}
|
|
96
|
-
onSelect={(value) => {
|
|
97
|
-
// onSelect wird von der Toolbar übergeben und ruft handlePluginClick auf
|
|
98
|
-
if (props.onSelect) {
|
|
99
|
-
props.onSelect(value);
|
|
100
|
-
}
|
|
101
|
-
}}
|
|
102
|
-
currentValue={currentValue}
|
|
103
|
-
disabled={props.disabled}
|
|
104
|
-
/>
|
|
105
|
-
);
|
|
106
|
-
},
|
|
107
|
-
getCurrentValue: (editor: EditorAPI) => {
|
|
108
|
-
const selection = editor.getSelection();
|
|
109
|
-
if (!selection || selection.rangeCount === 0) return undefined;
|
|
110
|
-
|
|
111
|
-
const range = selection.getRangeAt(0);
|
|
112
|
-
const container = range.commonAncestorContainer;
|
|
113
|
-
const element =
|
|
114
|
-
container.nodeType === Node.TEXT_NODE
|
|
115
|
-
? container.parentElement
|
|
116
|
-
: (container as HTMLElement);
|
|
117
|
-
|
|
118
|
-
if (!element) return undefined;
|
|
119
|
-
|
|
120
|
-
const tagName = element.tagName.toLowerCase();
|
|
121
|
-
|
|
122
|
-
// Prüfe auf Heading
|
|
123
|
-
if (headings.includes(tagName)) {
|
|
124
|
-
return tagName;
|
|
125
|
-
}
|
|
126
|
-
// Prüfe auf Blockquote
|
|
127
|
-
if (element.closest("blockquote")) {
|
|
128
|
-
return "blockquote";
|
|
129
|
-
}
|
|
130
|
-
// Prüfe auf Liste
|
|
131
|
-
if (element.closest("ul")) {
|
|
132
|
-
return "ul";
|
|
133
|
-
}
|
|
134
|
-
if (element.closest("ol")) {
|
|
135
|
-
return "ol";
|
|
136
|
-
}
|
|
137
|
-
// Prüfe auf Paragraph
|
|
138
|
-
if (tagName === "p") {
|
|
139
|
-
return "p";
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return undefined;
|
|
143
|
-
},
|
|
144
|
-
execute: (editor: EditorAPI, value?: string) => {
|
|
145
|
-
if (!value) return;
|
|
146
|
-
|
|
147
|
-
if (value === "ul") {
|
|
148
|
-
editor.executeCommand("insertUnorderedList");
|
|
149
|
-
} else if (value === "ol") {
|
|
150
|
-
editor.executeCommand("insertOrderedList");
|
|
151
|
-
} else if (value === "blockquote") {
|
|
152
|
-
const selection = editor.getSelection();
|
|
153
|
-
if (selection && selection.rangeCount > 0) {
|
|
154
|
-
const range = selection.getRangeAt(0);
|
|
155
|
-
const container = range.commonAncestorContainer;
|
|
156
|
-
const element =
|
|
157
|
-
container.nodeType === Node.TEXT_NODE
|
|
158
|
-
? container.parentElement
|
|
159
|
-
: (container as HTMLElement);
|
|
160
|
-
|
|
161
|
-
if (element?.closest("blockquote")) {
|
|
162
|
-
editor.executeCommand("formatBlock", "<p>");
|
|
163
|
-
} else {
|
|
164
|
-
editor.executeCommand("formatBlock", "<blockquote>");
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
editor.executeCommand("formatBlock", `<${value}>`);
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
isActive: (editor: EditorAPI) => {
|
|
172
|
-
const selection = editor.getSelection();
|
|
173
|
-
if (!selection || selection.rangeCount === 0) return false;
|
|
174
|
-
|
|
175
|
-
const range = selection.getRangeAt(0);
|
|
176
|
-
const container = range.commonAncestorContainer;
|
|
177
|
-
const element =
|
|
178
|
-
container.nodeType === Node.TEXT_NODE
|
|
179
|
-
? container.parentElement
|
|
180
|
-
: (container as HTMLElement);
|
|
181
|
-
|
|
182
|
-
if (!element) return false;
|
|
183
|
-
|
|
184
|
-
const tagName = element.tagName.toLowerCase();
|
|
185
|
-
return (
|
|
186
|
-
headings.includes(tagName) ||
|
|
187
|
-
element.closest("blockquote") !== null ||
|
|
188
|
-
element.closest("ul") !== null ||
|
|
189
|
-
element.closest("ol") !== null
|
|
190
|
-
);
|
|
191
|
-
},
|
|
192
|
-
canExecute: () => true,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Plugin, EditorAPI, ButtonProps } from '../types';
|
|
3
|
-
import { IconWrapper } from '../components/IconWrapper';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Clear Formatting Plugin - Entfernt alle Formatierungen
|
|
7
|
-
*/
|
|
8
|
-
export const clearFormattingPlugin: Plugin = {
|
|
9
|
-
name: 'clearFormatting',
|
|
10
|
-
type: 'command',
|
|
11
|
-
renderButton: (props: ButtonProps) => (
|
|
12
|
-
<button
|
|
13
|
-
type="button"
|
|
14
|
-
onClick={props.onClick}
|
|
15
|
-
disabled={props.disabled}
|
|
16
|
-
className="rte-toolbar-button"
|
|
17
|
-
title="Formatierung entfernen"
|
|
18
|
-
aria-label="Formatierung entfernen"
|
|
19
|
-
>
|
|
20
|
-
<IconWrapper icon="mdi:format-clear" width={18} height={18} />
|
|
21
|
-
</button>
|
|
22
|
-
),
|
|
23
|
-
execute: (editor: EditorAPI) => {
|
|
24
|
-
editor.clearFormatting();
|
|
25
|
-
},
|
|
26
|
-
canExecute: (editor: EditorAPI) => {
|
|
27
|
-
const selection = editor.getSelection();
|
|
28
|
-
return selection !== null && selection.rangeCount > 0 && !selection.isCollapsed;
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
|
package/src/plugins/colors.tsx
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Plugin, EditorAPI, ButtonProps } from '../types';
|
|
3
|
-
import { Dropdown } from '../components/Dropdown';
|
|
4
|
-
import { getCurrentTextColor, getCurrentBackgroundColor } from '../utils/stateReflection';
|
|
5
|
-
|
|
6
|
-
const defaultColors = [
|
|
7
|
-
{ value: '#000000', label: 'Schwarz', color: '#000000' },
|
|
8
|
-
{ value: '#333333', label: 'Dunkelgrau', color: '#333333' },
|
|
9
|
-
{ value: '#666666', label: 'Grau', color: '#666666' },
|
|
10
|
-
{ value: '#ff0000', label: 'Rot', color: '#ff0000' },
|
|
11
|
-
{ value: '#0000ff', label: 'Blau', color: '#0000ff' },
|
|
12
|
-
{ value: '#00aa00', label: 'Grün', color: '#00aa00' },
|
|
13
|
-
{ value: '#ffaa00', label: 'Orange', color: '#ffaa00' },
|
|
14
|
-
{ value: '#aa00ff', label: 'Lila', color: '#aa00ff' },
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
export function createTextColorPlugin(colors: string[] = defaultColors.map(c => c.value)): Plugin {
|
|
18
|
-
// Finde Labels für bekannte Farben
|
|
19
|
-
const getColorLabel = (color: string): string => {
|
|
20
|
-
const found = defaultColors.find(c => c.value.toLowerCase() === color.toLowerCase());
|
|
21
|
-
return found ? found.label : color;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const colorOptions = colors.map(color => ({
|
|
25
|
-
value: color,
|
|
26
|
-
label: getColorLabel(color),
|
|
27
|
-
color,
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
name: 'textColor',
|
|
32
|
-
type: 'inline',
|
|
33
|
-
renderButton: (props: ButtonProps & { onSelect?: (value: string) => void; editorAPI?: EditorAPI; currentValue?: string }) => {
|
|
34
|
-
// Aktuelle Textfarbe aus State Reflection
|
|
35
|
-
const currentValue = props.currentValue || (props.editorAPI ? getCurrentTextColor(props.editorAPI) : undefined);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<Dropdown
|
|
39
|
-
icon="mdi:format-color-text"
|
|
40
|
-
label="Textfarbe"
|
|
41
|
-
options={colorOptions.map(opt => ({
|
|
42
|
-
value: opt.value,
|
|
43
|
-
label: opt.label,
|
|
44
|
-
color: opt.color,
|
|
45
|
-
}))}
|
|
46
|
-
onSelect={(value) => {
|
|
47
|
-
if (props.onSelect) {
|
|
48
|
-
props.onSelect(value);
|
|
49
|
-
} else {
|
|
50
|
-
props.onClick();
|
|
51
|
-
}
|
|
52
|
-
}}
|
|
53
|
-
currentValue={currentValue}
|
|
54
|
-
disabled={props.disabled}
|
|
55
|
-
/>
|
|
56
|
-
);
|
|
57
|
-
},
|
|
58
|
-
execute: (editor: EditorAPI, value?: string) => {
|
|
59
|
-
if (value) {
|
|
60
|
-
editor.executeCommand('foreColor', value);
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
canExecute: () => true,
|
|
64
|
-
getCurrentValue: (editor: EditorAPI) => {
|
|
65
|
-
return getCurrentTextColor(editor);
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function createBackgroundColorPlugin(colors: string[] = defaultColors.map(c => c.value)): Plugin {
|
|
71
|
-
// Finde Labels für bekannte Farben
|
|
72
|
-
const getColorLabel = (color: string): string => {
|
|
73
|
-
const found = defaultColors.find(c => c.value.toLowerCase() === color.toLowerCase());
|
|
74
|
-
return found ? found.label : color;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const colorOptions = colors.map(color => ({
|
|
78
|
-
value: color,
|
|
79
|
-
label: getColorLabel(color),
|
|
80
|
-
color,
|
|
81
|
-
}));
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
name: 'backgroundColor',
|
|
85
|
-
type: 'inline',
|
|
86
|
-
renderButton: (props: ButtonProps & { onSelect?: (value: string) => void; editorAPI?: EditorAPI; currentValue?: string }) => {
|
|
87
|
-
// Aktuelle Hintergrundfarbe aus State Reflection
|
|
88
|
-
const currentValue = props.currentValue || (props.editorAPI ? getCurrentBackgroundColor(props.editorAPI) : undefined);
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<Dropdown
|
|
92
|
-
icon="mdi:format-color-fill"
|
|
93
|
-
label="Hintergrundfarbe"
|
|
94
|
-
options={colorOptions.map(opt => ({
|
|
95
|
-
value: opt.value,
|
|
96
|
-
label: opt.label,
|
|
97
|
-
color: opt.color,
|
|
98
|
-
}))}
|
|
99
|
-
onSelect={(value) => {
|
|
100
|
-
if (props.onSelect) {
|
|
101
|
-
props.onSelect(value);
|
|
102
|
-
} else {
|
|
103
|
-
props.onClick();
|
|
104
|
-
}
|
|
105
|
-
}}
|
|
106
|
-
currentValue={currentValue}
|
|
107
|
-
disabled={props.disabled}
|
|
108
|
-
/>
|
|
109
|
-
);
|
|
110
|
-
},
|
|
111
|
-
execute: (editor: EditorAPI, value?: string) => {
|
|
112
|
-
if (value) {
|
|
113
|
-
editor.executeCommand('backColor', value);
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
canExecute: () => true,
|
|
117
|
-
getCurrentValue: (editor: EditorAPI) => {
|
|
118
|
-
return getCurrentBackgroundColor(editor);
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
package/src/plugins/fontSize.tsx
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Plugin, EditorAPI, ButtonProps } from '../types';
|
|
3
|
-
import { Dropdown } from '../components/Dropdown';
|
|
4
|
-
import { getCurrentFontSize } from '../utils/stateReflection';
|
|
5
|
-
|
|
6
|
-
export function createFontSizePlugin(fontSizes: number[] = [12, 14, 16, 18, 20, 24]): Plugin {
|
|
7
|
-
return {
|
|
8
|
-
name: 'fontSize',
|
|
9
|
-
type: 'inline',
|
|
10
|
-
renderButton: (props: ButtonProps & { fontSizes?: number[]; onSelect?: (value: string) => void; editorAPI?: EditorAPI; currentValue?: string }) => {
|
|
11
|
-
const sizes = props.fontSizes || fontSizes;
|
|
12
|
-
const options = sizes.map(size => ({
|
|
13
|
-
value: size.toString(),
|
|
14
|
-
label: `${size}px`,
|
|
15
|
-
preview: size.toString(),
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
// Aktuelle Font-Size aus State Reflection
|
|
19
|
-
const currentValue = props.currentValue || (props.editorAPI ? getCurrentFontSize(props.editorAPI) : undefined);
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<Dropdown
|
|
23
|
-
icon="mdi:format-size"
|
|
24
|
-
label="Schriftgröße"
|
|
25
|
-
options={options}
|
|
26
|
-
onSelect={(value) => {
|
|
27
|
-
if (props.onSelect) {
|
|
28
|
-
props.onSelect(value);
|
|
29
|
-
} else {
|
|
30
|
-
props.onClick();
|
|
31
|
-
}
|
|
32
|
-
}}
|
|
33
|
-
currentValue={currentValue}
|
|
34
|
-
disabled={props.disabled}
|
|
35
|
-
/>
|
|
36
|
-
);
|
|
37
|
-
},
|
|
38
|
-
getCurrentValue: (editor: EditorAPI) => {
|
|
39
|
-
return getCurrentFontSize(editor);
|
|
40
|
-
},
|
|
41
|
-
execute: (editor: EditorAPI, value?: string) => {
|
|
42
|
-
if (value) {
|
|
43
|
-
// Setze inline style für präzise Größe
|
|
44
|
-
const selection = editor.getSelection();
|
|
45
|
-
if (selection && selection.rangeCount > 0) {
|
|
46
|
-
const range = selection.getRangeAt(0);
|
|
47
|
-
let element: HTMLElement | null = null;
|
|
48
|
-
|
|
49
|
-
if (range.commonAncestorContainer.nodeType === Node.TEXT_NODE) {
|
|
50
|
-
element = range.commonAncestorContainer.parentElement;
|
|
51
|
-
} else {
|
|
52
|
-
element = range.commonAncestorContainer as HTMLElement;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (element) {
|
|
56
|
-
// Erstelle oder aktualisiere span mit fontSize
|
|
57
|
-
const span = document.createElement('span');
|
|
58
|
-
span.style.fontSize = `${value}px`;
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
range.surroundContents(span);
|
|
62
|
-
} catch (e) {
|
|
63
|
-
// Falls surroundContents fehlschlägt
|
|
64
|
-
const contents = range.extractContents();
|
|
65
|
-
span.appendChild(contents);
|
|
66
|
-
range.insertNode(span);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Cursor setzen
|
|
70
|
-
range.setStartAfter(span);
|
|
71
|
-
range.collapse(true);
|
|
72
|
-
selection.removeAllRanges();
|
|
73
|
-
selection.addRange(range);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
canExecute: () => true,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|