@overlap/rte 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -68
- package/dist/components/Editor.d.ts.map +1 -1
- package/dist/components/Icons.d.ts +3 -1
- package/dist/components/Icons.d.ts.map +1 -1
- package/dist/index.d.ts +59 -45
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +470 -197
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +472 -196
- package/dist/index.js.map +1 -1
- package/dist/plugins/blockFormat.d.ts +7 -0
- package/dist/plugins/blockFormat.d.ts.map +1 -0
- package/dist/plugins/headings.d.ts +1 -1
- package/dist/plugins/headings.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +8 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/listIndent.d.ts +10 -0
- package/dist/plugins/listIndent.d.ts.map +1 -0
- package/dist/plugins/optional.d.ts +1 -1
- package/dist/plugins/optional.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +17 -5
- package/src/components/Editor.tsx +92 -49
- package/src/components/Icons.tsx +306 -77
- package/src/index.ts +18 -18
- package/src/plugins/blockFormat.tsx +194 -0
- package/src/plugins/headings.tsx +39 -28
- package/src/plugins/index.tsx +161 -0
- package/src/plugins/listIndent.tsx +90 -0
- package/src/plugins/optional.tsx +216 -194
- package/src/types.ts +3 -0
- package/src/plugins/index.ts +0 -54
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
}
|
package/src/plugins/headings.tsx
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getCurrentHeading } from '../utils/stateReflection';
|
|
1
|
+
import { Dropdown } from "../components/Dropdown";
|
|
2
|
+
import { ButtonProps, EditorAPI, Plugin } from "../types";
|
|
3
|
+
import { getCurrentHeading } from "../utils/stateReflection";
|
|
5
4
|
|
|
6
|
-
const defaultHeadings = [
|
|
5
|
+
const defaultHeadings = ["h1", "h2", "h3"];
|
|
7
6
|
|
|
8
7
|
const headingLabels: Record<string, string> = {
|
|
9
|
-
h1:
|
|
10
|
-
h2:
|
|
11
|
-
h3:
|
|
12
|
-
h4:
|
|
13
|
-
h5:
|
|
14
|
-
h6:
|
|
8
|
+
h1: "Überschrift 1",
|
|
9
|
+
h2: "Überschrift 2",
|
|
10
|
+
h3: "Überschrift 3",
|
|
11
|
+
h4: "Überschrift 4",
|
|
12
|
+
h5: "Überschrift 5",
|
|
13
|
+
h6: "Überschrift 6",
|
|
15
14
|
};
|
|
16
15
|
|
|
17
|
-
export function createHeadingsPlugin(
|
|
16
|
+
export function createHeadingsPlugin(
|
|
17
|
+
headings: string[] = defaultHeadings
|
|
18
|
+
): Plugin {
|
|
18
19
|
const options = [
|
|
19
|
-
{ value:
|
|
20
|
-
...headings.map(h => ({
|
|
20
|
+
{ value: "p", label: "Normal", headingPreview: "p" },
|
|
21
|
+
...headings.map((h) => ({
|
|
21
22
|
value: h,
|
|
22
23
|
label: headingLabels[h] || h.toUpperCase(),
|
|
23
24
|
headingPreview: h,
|
|
@@ -25,12 +26,22 @@ export function createHeadingsPlugin(headings: string[] = defaultHeadings): Plug
|
|
|
25
26
|
];
|
|
26
27
|
|
|
27
28
|
return {
|
|
28
|
-
name:
|
|
29
|
-
type:
|
|
30
|
-
renderButton: (
|
|
29
|
+
name: "headings",
|
|
30
|
+
type: "block",
|
|
31
|
+
renderButton: (
|
|
32
|
+
props: ButtonProps & {
|
|
33
|
+
onSelect?: (value: string) => void;
|
|
34
|
+
editorAPI?: EditorAPI;
|
|
35
|
+
currentValue?: string;
|
|
36
|
+
}
|
|
37
|
+
) => {
|
|
31
38
|
// Aktuelles Heading aus State Reflection
|
|
32
|
-
const currentValue =
|
|
33
|
-
|
|
39
|
+
const currentValue =
|
|
40
|
+
props.currentValue ||
|
|
41
|
+
(props.editorAPI
|
|
42
|
+
? getCurrentHeading(props.editorAPI, headings)
|
|
43
|
+
: undefined);
|
|
44
|
+
|
|
34
45
|
return (
|
|
35
46
|
<Dropdown
|
|
36
47
|
icon="mdi:format-header-1"
|
|
@@ -52,25 +63,25 @@ export function createHeadingsPlugin(headings: string[] = defaultHeadings): Plug
|
|
|
52
63
|
return getCurrentHeading(editor, headings);
|
|
53
64
|
},
|
|
54
65
|
execute: (editor: EditorAPI, value?: string) => {
|
|
55
|
-
const tag = value ||
|
|
56
|
-
editor.executeCommand(
|
|
66
|
+
const tag = value || "p";
|
|
67
|
+
editor.executeCommand("formatBlock", `<${tag}>`);
|
|
57
68
|
},
|
|
58
69
|
isActive: (editor: EditorAPI) => {
|
|
59
70
|
const selection = editor.getSelection();
|
|
60
71
|
if (!selection || selection.rangeCount === 0) return false;
|
|
61
|
-
|
|
72
|
+
|
|
62
73
|
const range = selection.getRangeAt(0);
|
|
63
74
|
const container = range.commonAncestorContainer;
|
|
64
|
-
const element =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
const element =
|
|
76
|
+
container.nodeType === Node.TEXT_NODE
|
|
77
|
+
? container.parentElement
|
|
78
|
+
: (container as HTMLElement);
|
|
79
|
+
|
|
68
80
|
if (!element) return false;
|
|
69
|
-
|
|
81
|
+
|
|
70
82
|
const tagName = element.tagName.toLowerCase();
|
|
71
83
|
return headings.includes(tagName);
|
|
72
84
|
},
|
|
73
85
|
canExecute: () => true,
|
|
74
86
|
};
|
|
75
87
|
}
|
|
76
|
-
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { IconWrapper } from "../components/IconWrapper";
|
|
2
|
+
import { ButtonProps, EditorAPI, Plugin } from "../types";
|
|
3
|
+
import { createCommandPlugin, createInlinePlugin } from "./base";
|
|
4
|
+
import { createBlockFormatPlugin } from "./blockFormat";
|
|
5
|
+
import { clearFormattingPlugin } from "./clearFormatting";
|
|
6
|
+
|
|
7
|
+
const defaultHeadings = ["h1", "h2", "h3"];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Standard-Plugins
|
|
11
|
+
*/
|
|
12
|
+
export const boldPlugin: Plugin = createInlinePlugin(
|
|
13
|
+
"bold",
|
|
14
|
+
"bold",
|
|
15
|
+
"mdi:format-bold",
|
|
16
|
+
"Fett"
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const italicPlugin: Plugin = createInlinePlugin(
|
|
20
|
+
"italic",
|
|
21
|
+
"italic",
|
|
22
|
+
"mdi:format-italic",
|
|
23
|
+
"Kursiv"
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const underlinePlugin: Plugin = createInlinePlugin(
|
|
27
|
+
"underline",
|
|
28
|
+
"underline",
|
|
29
|
+
"mdi:format-underline",
|
|
30
|
+
"Unterstrichen"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export const undoPlugin: Plugin = createCommandPlugin(
|
|
34
|
+
"undo",
|
|
35
|
+
"undo",
|
|
36
|
+
"mdi:undo",
|
|
37
|
+
"Rückgängig"
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const redoPlugin: Plugin = createCommandPlugin(
|
|
41
|
+
"redo",
|
|
42
|
+
"redo",
|
|
43
|
+
"mdi:redo",
|
|
44
|
+
"Wiederholen"
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Indent List Item Plugin (Tab für Unterliste)
|
|
49
|
+
*/
|
|
50
|
+
const indentListItemPlugin: Plugin = {
|
|
51
|
+
name: "indentListItem",
|
|
52
|
+
type: "command",
|
|
53
|
+
renderButton: (props: ButtonProps) => (
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
onClick={props.onClick}
|
|
57
|
+
disabled={props.disabled}
|
|
58
|
+
className="rte-toolbar-button"
|
|
59
|
+
title="Einrücken (Unterliste)"
|
|
60
|
+
aria-label="Einrücken (Unterliste)"
|
|
61
|
+
>
|
|
62
|
+
<IconWrapper
|
|
63
|
+
icon="mdi:format-indent-increase"
|
|
64
|
+
width={18}
|
|
65
|
+
height={18}
|
|
66
|
+
/>
|
|
67
|
+
</button>
|
|
68
|
+
),
|
|
69
|
+
execute: (editor: EditorAPI) => {
|
|
70
|
+
editor.indentListItem();
|
|
71
|
+
},
|
|
72
|
+
canExecute: (editor: EditorAPI) => {
|
|
73
|
+
const selection = editor.getSelection();
|
|
74
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
75
|
+
|
|
76
|
+
const range = selection.getRangeAt(0);
|
|
77
|
+
const container = range.commonAncestorContainer;
|
|
78
|
+
const listItem =
|
|
79
|
+
container.nodeType === Node.TEXT_NODE
|
|
80
|
+
? container.parentElement?.closest("li")
|
|
81
|
+
: (container as HTMLElement).closest("li");
|
|
82
|
+
|
|
83
|
+
return listItem !== null;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Outdent List Item Plugin (Shift+Tab)
|
|
89
|
+
*/
|
|
90
|
+
const outdentListItemPlugin: Plugin = {
|
|
91
|
+
name: "outdentListItem",
|
|
92
|
+
type: "command",
|
|
93
|
+
renderButton: (props: ButtonProps) => (
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
onClick={props.onClick}
|
|
97
|
+
disabled={props.disabled}
|
|
98
|
+
className="rte-toolbar-button"
|
|
99
|
+
title="Ausrücken"
|
|
100
|
+
aria-label="Ausrücken"
|
|
101
|
+
>
|
|
102
|
+
<IconWrapper
|
|
103
|
+
icon="mdi:format-indent-decrease"
|
|
104
|
+
width={18}
|
|
105
|
+
height={18}
|
|
106
|
+
/>
|
|
107
|
+
</button>
|
|
108
|
+
),
|
|
109
|
+
execute: (editor: EditorAPI) => {
|
|
110
|
+
editor.outdentListItem();
|
|
111
|
+
},
|
|
112
|
+
canExecute: (editor: EditorAPI) => {
|
|
113
|
+
const selection = editor.getSelection();
|
|
114
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
115
|
+
|
|
116
|
+
const range = selection.getRangeAt(0);
|
|
117
|
+
const container = range.commonAncestorContainer;
|
|
118
|
+
const listItem =
|
|
119
|
+
container.nodeType === Node.TEXT_NODE
|
|
120
|
+
? container.parentElement?.closest("li")
|
|
121
|
+
: (container as HTMLElement).closest("li");
|
|
122
|
+
|
|
123
|
+
if (!listItem) return false;
|
|
124
|
+
|
|
125
|
+
// Prüfe ob in verschachtelter Liste
|
|
126
|
+
const list = listItem.parentElement;
|
|
127
|
+
if (!list || (list.tagName !== "UL" && list.tagName !== "OL"))
|
|
128
|
+
return false;
|
|
129
|
+
|
|
130
|
+
const parentListItem = list.parentElement;
|
|
131
|
+
return parentListItem !== null && parentListItem.tagName === "LI";
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Export plugins for direct use
|
|
136
|
+
export { indentListItemPlugin, outdentListItemPlugin };
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Standard-Plugin-Liste
|
|
140
|
+
* Die Plugins werden hier direkt referenziert, um sicherzustellen, dass sie in defaultPlugins enthalten sind
|
|
141
|
+
*/
|
|
142
|
+
const _indentPlugin = indentListItemPlugin;
|
|
143
|
+
const _outdentPlugin = outdentListItemPlugin;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Standard Block-Format Plugin (Headlines, Listen, Quote in einem Dropdown)
|
|
147
|
+
* Verwendet standardmäßig h1, h2, h3, kann aber über Editor-Props angepasst werden
|
|
148
|
+
*/
|
|
149
|
+
const defaultBlockFormatPlugin = createBlockFormatPlugin(defaultHeadings);
|
|
150
|
+
|
|
151
|
+
export const defaultPlugins: Plugin[] = [
|
|
152
|
+
undoPlugin,
|
|
153
|
+
redoPlugin,
|
|
154
|
+
boldPlugin,
|
|
155
|
+
italicPlugin,
|
|
156
|
+
underlinePlugin,
|
|
157
|
+
defaultBlockFormatPlugin,
|
|
158
|
+
clearFormattingPlugin,
|
|
159
|
+
_indentPlugin,
|
|
160
|
+
_outdentPlugin,
|
|
161
|
+
];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { IconWrapper } from "../components/IconWrapper";
|
|
2
|
+
import { ButtonProps, EditorAPI, Plugin } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Indent List Item Plugin (Tab für Unterliste)
|
|
6
|
+
*/
|
|
7
|
+
export const indentListItemPlugin: Plugin = {
|
|
8
|
+
name: "indentListItem",
|
|
9
|
+
type: "command",
|
|
10
|
+
renderButton: (props: ButtonProps) => (
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
onClick={props.onClick}
|
|
14
|
+
disabled={props.disabled}
|
|
15
|
+
className="rte-toolbar-button"
|
|
16
|
+
title="Einrücken (Unterliste)"
|
|
17
|
+
aria-label="Einrücken (Unterliste)"
|
|
18
|
+
>
|
|
19
|
+
<IconWrapper
|
|
20
|
+
icon="mdi:format-indent-increase"
|
|
21
|
+
width={18}
|
|
22
|
+
height={18}
|
|
23
|
+
/>
|
|
24
|
+
</button>
|
|
25
|
+
),
|
|
26
|
+
execute: (editor: EditorAPI) => {
|
|
27
|
+
editor.indentListItem();
|
|
28
|
+
},
|
|
29
|
+
canExecute: (editor: EditorAPI) => {
|
|
30
|
+
const selection = editor.getSelection();
|
|
31
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
32
|
+
|
|
33
|
+
const range = selection.getRangeAt(0);
|
|
34
|
+
const container = range.commonAncestorContainer;
|
|
35
|
+
const listItem =
|
|
36
|
+
container.nodeType === Node.TEXT_NODE
|
|
37
|
+
? container.parentElement?.closest("li")
|
|
38
|
+
: (container as HTMLElement).closest("li");
|
|
39
|
+
|
|
40
|
+
return listItem !== null;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Outdent List Item Plugin (Shift+Tab)
|
|
46
|
+
*/
|
|
47
|
+
export const outdentListItemPlugin: Plugin = {
|
|
48
|
+
name: "outdentListItem",
|
|
49
|
+
type: "command",
|
|
50
|
+
renderButton: (props: ButtonProps) => (
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
onClick={props.onClick}
|
|
54
|
+
disabled={props.disabled}
|
|
55
|
+
className="rte-toolbar-button"
|
|
56
|
+
title="Ausrücken"
|
|
57
|
+
aria-label="Ausrücken"
|
|
58
|
+
>
|
|
59
|
+
<IconWrapper
|
|
60
|
+
icon="mdi:format-indent-decrease"
|
|
61
|
+
width={18}
|
|
62
|
+
height={18}
|
|
63
|
+
/>
|
|
64
|
+
</button>
|
|
65
|
+
),
|
|
66
|
+
execute: (editor: EditorAPI) => {
|
|
67
|
+
editor.outdentListItem();
|
|
68
|
+
},
|
|
69
|
+
canExecute: (editor: EditorAPI) => {
|
|
70
|
+
const selection = editor.getSelection();
|
|
71
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
72
|
+
|
|
73
|
+
const range = selection.getRangeAt(0);
|
|
74
|
+
const container = range.commonAncestorContainer;
|
|
75
|
+
const listItem =
|
|
76
|
+
container.nodeType === Node.TEXT_NODE
|
|
77
|
+
? container.parentElement?.closest("li")
|
|
78
|
+
: (container as HTMLElement).closest("li");
|
|
79
|
+
|
|
80
|
+
if (!listItem) return false;
|
|
81
|
+
|
|
82
|
+
// Prüfe ob in verschachtelter Liste
|
|
83
|
+
const list = listItem.parentElement;
|
|
84
|
+
if (!list || (list.tagName !== "UL" && list.tagName !== "OL"))
|
|
85
|
+
return false;
|
|
86
|
+
|
|
87
|
+
const parentListItem = list.parentElement;
|
|
88
|
+
return parentListItem !== null && parentListItem.tagName === "LI";
|
|
89
|
+
},
|
|
90
|
+
};
|