@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
package/src/plugins/headings.tsx
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Dropdown } from "../components/Dropdown";
|
|
2
|
-
import { ButtonProps, EditorAPI, Plugin } from "../types";
|
|
3
|
-
import { getCurrentHeading } from "../utils/stateReflection";
|
|
4
|
-
|
|
5
|
-
const defaultHeadings = ["h1", "h2", "h3"];
|
|
6
|
-
|
|
7
|
-
const headingLabels: Record<string, string> = {
|
|
8
|
-
h1: "Überschrift 1",
|
|
9
|
-
h2: "Überschrift 2",
|
|
10
|
-
h3: "Überschrift 3",
|
|
11
|
-
h4: "Überschrift 4",
|
|
12
|
-
h5: "Überschrift 5",
|
|
13
|
-
h6: "Überschrift 6",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export function createHeadingsPlugin(
|
|
17
|
-
headings: string[] = defaultHeadings
|
|
18
|
-
): Plugin {
|
|
19
|
-
const options = [
|
|
20
|
-
{ value: "p", label: "Normal", headingPreview: "p" },
|
|
21
|
-
...headings.map((h) => ({
|
|
22
|
-
value: h,
|
|
23
|
-
label: headingLabels[h] || h.toUpperCase(),
|
|
24
|
-
headingPreview: h,
|
|
25
|
-
})),
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
name: "headings",
|
|
30
|
-
type: "block",
|
|
31
|
-
renderButton: (
|
|
32
|
-
props: ButtonProps & {
|
|
33
|
-
onSelect?: (value: string) => void;
|
|
34
|
-
editorAPI?: EditorAPI;
|
|
35
|
-
currentValue?: string;
|
|
36
|
-
}
|
|
37
|
-
) => {
|
|
38
|
-
// Aktuelles Heading aus State Reflection
|
|
39
|
-
const currentValue =
|
|
40
|
-
props.currentValue ||
|
|
41
|
-
(props.editorAPI
|
|
42
|
-
? getCurrentHeading(props.editorAPI, headings)
|
|
43
|
-
: undefined);
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<Dropdown
|
|
47
|
-
icon="mdi:format-header-1"
|
|
48
|
-
label="Überschrift"
|
|
49
|
-
options={options}
|
|
50
|
-
onSelect={(value) => {
|
|
51
|
-
if (props.onSelect) {
|
|
52
|
-
props.onSelect(value);
|
|
53
|
-
} else {
|
|
54
|
-
props.onClick();
|
|
55
|
-
}
|
|
56
|
-
}}
|
|
57
|
-
currentValue={currentValue}
|
|
58
|
-
disabled={props.disabled}
|
|
59
|
-
/>
|
|
60
|
-
);
|
|
61
|
-
},
|
|
62
|
-
getCurrentValue: (editor: EditorAPI) => {
|
|
63
|
-
return getCurrentHeading(editor, headings);
|
|
64
|
-
},
|
|
65
|
-
execute: (editor: EditorAPI, value?: string) => {
|
|
66
|
-
const tag = value || "p";
|
|
67
|
-
editor.executeCommand("formatBlock", `<${tag}>`);
|
|
68
|
-
},
|
|
69
|
-
isActive: (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 element =
|
|
76
|
-
container.nodeType === Node.TEXT_NODE
|
|
77
|
-
? container.parentElement
|
|
78
|
-
: (container as HTMLElement);
|
|
79
|
-
|
|
80
|
-
if (!element) return false;
|
|
81
|
-
|
|
82
|
-
const tagName = element.tagName.toLowerCase();
|
|
83
|
-
return headings.includes(tagName);
|
|
84
|
-
},
|
|
85
|
-
canExecute: () => true,
|
|
86
|
-
};
|
|
87
|
-
}
|
package/src/plugins/image.tsx
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import React, { useState, useRef } from 'react';
|
|
2
|
-
import { Plugin, EditorAPI, ButtonProps } from '../types';
|
|
3
|
-
import { IconWrapper } from '../components/IconWrapper';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Image-Plugin mit URL-Eingabe und File-Upload
|
|
7
|
-
*/
|
|
8
|
-
export function createImagePlugin(onImageUpload?: (file: File) => Promise<string>): Plugin {
|
|
9
|
-
return {
|
|
10
|
-
name: 'image',
|
|
11
|
-
type: 'block',
|
|
12
|
-
renderButton: (props: ButtonProps & { editorAPI?: EditorAPI }) => {
|
|
13
|
-
const [showModal, setShowModal] = useState(false);
|
|
14
|
-
const [imageUrl, setImageUrl] = useState('');
|
|
15
|
-
const [altText, setAltText] = useState('');
|
|
16
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
17
|
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
18
|
-
|
|
19
|
-
const handleInsertImage = () => {
|
|
20
|
-
if (!props.editorAPI) return;
|
|
21
|
-
|
|
22
|
-
const src = imageUrl.trim();
|
|
23
|
-
|
|
24
|
-
if (!src) {
|
|
25
|
-
alert('Bitte geben Sie eine Bild-URL ein');
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Verwende executeCommand, das alles korrekt handhabt
|
|
30
|
-
// Alt-Text wird später über das Datenmodell gespeichert
|
|
31
|
-
props.editorAPI.executeCommand('insertImage', src);
|
|
32
|
-
|
|
33
|
-
// Modal schließen
|
|
34
|
-
setShowModal(false);
|
|
35
|
-
setImageUrl('');
|
|
36
|
-
setAltText('');
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
40
|
-
const file = e.target.files?.[0];
|
|
41
|
-
if (!file || !onImageUpload) return;
|
|
42
|
-
|
|
43
|
-
if (!file.type.startsWith('image/')) {
|
|
44
|
-
alert('Bitte wählen Sie eine Bilddatei aus');
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
setIsUploading(true);
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const uploadedUrl = await onImageUpload(file);
|
|
52
|
-
setImageUrl(uploadedUrl);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('Image upload failed:', error);
|
|
55
|
-
alert('Fehler beim Hochladen des Bildes');
|
|
56
|
-
} finally {
|
|
57
|
-
setIsUploading(false);
|
|
58
|
-
if (fileInputRef.current) {
|
|
59
|
-
fileInputRef.current.value = '';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<>
|
|
66
|
-
<button
|
|
67
|
-
type="button"
|
|
68
|
-
onClick={() => setShowModal(true)}
|
|
69
|
-
disabled={props.disabled}
|
|
70
|
-
className="rte-toolbar-button"
|
|
71
|
-
title="Bild einfügen"
|
|
72
|
-
aria-label="Bild einfügen"
|
|
73
|
-
>
|
|
74
|
-
<IconWrapper icon="mdi:image" width={18} height={18} />
|
|
75
|
-
</button>
|
|
76
|
-
|
|
77
|
-
{showModal && (
|
|
78
|
-
<div
|
|
79
|
-
className="rte-image-modal-overlay"
|
|
80
|
-
onClick={(e) => {
|
|
81
|
-
if (e.target === e.currentTarget) {
|
|
82
|
-
setShowModal(false);
|
|
83
|
-
}
|
|
84
|
-
}}
|
|
85
|
-
>
|
|
86
|
-
<div className="rte-image-modal">
|
|
87
|
-
<div className="rte-image-modal-header">
|
|
88
|
-
<h3>Bild einfügen</h3>
|
|
89
|
-
<button
|
|
90
|
-
type="button"
|
|
91
|
-
onClick={() => setShowModal(false)}
|
|
92
|
-
className="rte-image-modal-close"
|
|
93
|
-
aria-label="Schließen"
|
|
94
|
-
>
|
|
95
|
-
<IconWrapper icon="mdi:close" width={20} height={20} />
|
|
96
|
-
</button>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
<div className="rte-image-modal-content">
|
|
100
|
-
{onImageUpload && (
|
|
101
|
-
<div className="rte-image-upload-section">
|
|
102
|
-
<label className="rte-image-upload-label">
|
|
103
|
-
<input
|
|
104
|
-
ref={fileInputRef}
|
|
105
|
-
type="file"
|
|
106
|
-
accept="image/*"
|
|
107
|
-
onChange={handleFileSelect}
|
|
108
|
-
style={{ display: 'none' }}
|
|
109
|
-
/>
|
|
110
|
-
<div className="rte-image-upload-button">
|
|
111
|
-
{isUploading ? (
|
|
112
|
-
<>
|
|
113
|
-
<IconWrapper icon="mdi:loading" width={24} height={24} className="rte-spin" />
|
|
114
|
-
<span>Wird hochgeladen...</span>
|
|
115
|
-
</>
|
|
116
|
-
) : (
|
|
117
|
-
<>
|
|
118
|
-
<IconWrapper icon="mdi:upload" width={24} height={24} />
|
|
119
|
-
<span>Datei auswählen</span>
|
|
120
|
-
</>
|
|
121
|
-
)}
|
|
122
|
-
</div>
|
|
123
|
-
</label>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
|
|
127
|
-
<div className="rte-image-url-section">
|
|
128
|
-
<label>
|
|
129
|
-
Bild-URL
|
|
130
|
-
<input
|
|
131
|
-
type="url"
|
|
132
|
-
value={imageUrl}
|
|
133
|
-
onChange={(e) => setImageUrl(e.target.value)}
|
|
134
|
-
placeholder="https://example.com/image.jpg"
|
|
135
|
-
className="rte-image-url-input"
|
|
136
|
-
/>
|
|
137
|
-
</label>
|
|
138
|
-
</div>
|
|
139
|
-
|
|
140
|
-
<div className="rte-image-alt-section">
|
|
141
|
-
<label>
|
|
142
|
-
Alt-Text (optional)
|
|
143
|
-
<input
|
|
144
|
-
type="text"
|
|
145
|
-
value={altText}
|
|
146
|
-
onChange={(e) => setAltText(e.target.value)}
|
|
147
|
-
placeholder="Bildbeschreibung"
|
|
148
|
-
className="rte-image-alt-input"
|
|
149
|
-
/>
|
|
150
|
-
</label>
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
{imageUrl && (
|
|
154
|
-
<div className="rte-image-preview">
|
|
155
|
-
<img src={imageUrl} alt={altText || 'Preview'} />
|
|
156
|
-
</div>
|
|
157
|
-
)}
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
<div className="rte-image-modal-footer">
|
|
161
|
-
<button
|
|
162
|
-
type="button"
|
|
163
|
-
onClick={() => setShowModal(false)}
|
|
164
|
-
className="rte-image-modal-cancel"
|
|
165
|
-
>
|
|
166
|
-
Abbrechen
|
|
167
|
-
</button>
|
|
168
|
-
<button
|
|
169
|
-
type="button"
|
|
170
|
-
onClick={handleInsertImage}
|
|
171
|
-
disabled={!imageUrl.trim() || isUploading}
|
|
172
|
-
className="rte-image-modal-insert"
|
|
173
|
-
>
|
|
174
|
-
Einfügen
|
|
175
|
-
</button>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
)}
|
|
180
|
-
</>
|
|
181
|
-
);
|
|
182
|
-
},
|
|
183
|
-
execute: (editor: EditorAPI) => {
|
|
184
|
-
// Wird über renderButton gehandhabt
|
|
185
|
-
},
|
|
186
|
-
canExecute: () => true,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
package/src/plugins/index.tsx
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
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
|
-
];
|
|
@@ -1,90 +0,0 @@
|
|
|
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
|
-
};
|