@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.
- package/README.md +269 -0
- package/dist/components/Dropdown.d.ts +19 -0
- package/dist/components/Dropdown.d.ts.map +1 -0
- package/dist/components/Editor.d.ts +4 -0
- package/dist/components/Editor.d.ts.map +1 -0
- package/dist/components/FloatingToolbar.d.ts +10 -0
- package/dist/components/FloatingToolbar.d.ts.map +1 -0
- package/dist/components/IconWrapper.d.ts +10 -0
- package/dist/components/IconWrapper.d.ts.map +1 -0
- package/dist/components/Icons.d.ts +32 -0
- package/dist/components/Icons.d.ts.map +1 -0
- package/dist/components/Toolbar.d.ts +10 -0
- package/dist/components/Toolbar.d.ts.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +2080 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2116 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/base.d.ts +10 -0
- package/dist/plugins/base.d.ts.map +1 -0
- package/dist/plugins/clearFormatting.d.ts +6 -0
- package/dist/plugins/clearFormatting.d.ts.map +1 -0
- package/dist/plugins/colors.d.ts +4 -0
- package/dist/plugins/colors.d.ts.map +1 -0
- package/dist/plugins/fontSize.d.ts +3 -0
- package/dist/plugins/fontSize.d.ts.map +1 -0
- package/dist/plugins/headings.d.ts +3 -0
- package/dist/plugins/headings.d.ts.map +1 -0
- package/dist/plugins/image.d.ts +6 -0
- package/dist/plugins/image.d.ts.map +1 -0
- package/dist/plugins/index.d.ts +14 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/optional.d.ts +19 -0
- package/dist/plugins/optional.d.ts.map +1 -0
- package/dist/styles.css +638 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/clearFormatting.d.ts +21 -0
- package/dist/utils/clearFormatting.d.ts.map +1 -0
- package/dist/utils/content.d.ts +12 -0
- package/dist/utils/content.d.ts.map +1 -0
- package/dist/utils/history.d.ts +14 -0
- package/dist/utils/history.d.ts.map +1 -0
- package/dist/utils/listIndent.d.ts +9 -0
- package/dist/utils/listIndent.d.ts.map +1 -0
- package/dist/utils/stateReflection.d.ts +18 -0
- package/dist/utils/stateReflection.d.ts.map +1 -0
- package/package.json +48 -0
- package/src/components/Dropdown.tsx +103 -0
- package/src/components/Editor.css +2 -0
- package/src/components/Editor.tsx +785 -0
- package/src/components/FloatingToolbar.tsx +214 -0
- package/src/components/IconWrapper.tsx +14 -0
- package/src/components/Icons.tsx +145 -0
- package/src/components/Toolbar.tsx +137 -0
- package/src/components/index.ts +3 -0
- package/src/index.ts +19 -0
- package/src/plugins/base.tsx +91 -0
- package/src/plugins/clearFormatting.tsx +31 -0
- package/src/plugins/colors.tsx +122 -0
- package/src/plugins/fontSize.tsx +81 -0
- package/src/plugins/headings.tsx +76 -0
- package/src/plugins/image.tsx +189 -0
- package/src/plugins/index.ts +54 -0
- package/src/plugins/optional.tsx +221 -0
- package/src/styles.css +638 -0
- package/src/types.ts +92 -0
- package/src/utils/clearFormatting.ts +244 -0
- package/src/utils/content.ts +290 -0
- package/src/utils/history.ts +59 -0
- package/src/utils/listIndent.ts +171 -0
- package/src/utils/stateReflection.ts +175 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Erhöht den Einrückungs-Level eines List-Items (Tab)
|
|
3
|
+
*/
|
|
4
|
+
export function indentListItem(selection: Selection): boolean {
|
|
5
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
6
|
+
|
|
7
|
+
const range = selection.getRangeAt(0);
|
|
8
|
+
const container = range.commonAncestorContainer;
|
|
9
|
+
const listItem = container.nodeType === Node.TEXT_NODE
|
|
10
|
+
? container.parentElement?.closest('li')
|
|
11
|
+
: (container as HTMLElement).closest('li');
|
|
12
|
+
|
|
13
|
+
if (!listItem) return false;
|
|
14
|
+
|
|
15
|
+
const list = listItem.parentElement;
|
|
16
|
+
if (!list || (list.tagName !== 'UL' && list.tagName !== 'OL')) return false;
|
|
17
|
+
|
|
18
|
+
// Prüfe ob bereits verschachtelt (max depth check)
|
|
19
|
+
let depth = 0;
|
|
20
|
+
let current: HTMLElement | null = listItem;
|
|
21
|
+
while (current) {
|
|
22
|
+
const parent: HTMLElement | null = current.parentElement;
|
|
23
|
+
if (parent && (parent.tagName === 'UL' || parent.tagName === 'OL')) {
|
|
24
|
+
depth++;
|
|
25
|
+
current = parent.closest('li');
|
|
26
|
+
} else {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Max depth: 6 (wie in HTML Standard)
|
|
32
|
+
if (depth >= 6) return false;
|
|
33
|
+
|
|
34
|
+
// Finde vorheriges List-Item
|
|
35
|
+
const previousItem = listItem.previousElementSibling as HTMLElement | null;
|
|
36
|
+
|
|
37
|
+
if (previousItem && previousItem.tagName === 'LI') {
|
|
38
|
+
// Erstelle verschachtelte Liste im vorherigen Item
|
|
39
|
+
let nestedList = previousItem.querySelector('ul, ol');
|
|
40
|
+
|
|
41
|
+
if (!nestedList) {
|
|
42
|
+
// Erstelle neue verschachtelte Liste
|
|
43
|
+
nestedList = document.createElement(list.tagName.toLowerCase() as 'ul' | 'ol');
|
|
44
|
+
previousItem.appendChild(nestedList);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Verschiebe aktuelles Item in verschachtelte Liste
|
|
48
|
+
nestedList.appendChild(listItem);
|
|
49
|
+
|
|
50
|
+
// Cursor setzen
|
|
51
|
+
const textNode = listItem.firstChild;
|
|
52
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
53
|
+
range.setStart(textNode, 0);
|
|
54
|
+
range.collapse(true);
|
|
55
|
+
} else if (listItem.firstChild) {
|
|
56
|
+
range.setStart(listItem.firstChild, 0);
|
|
57
|
+
range.collapse(true);
|
|
58
|
+
} else {
|
|
59
|
+
const newText = document.createTextNode('');
|
|
60
|
+
listItem.appendChild(newText);
|
|
61
|
+
range.setStart(newText, 0);
|
|
62
|
+
range.collapse(true);
|
|
63
|
+
}
|
|
64
|
+
selection.removeAllRanges();
|
|
65
|
+
selection.addRange(range);
|
|
66
|
+
|
|
67
|
+
return true;
|
|
68
|
+
} else {
|
|
69
|
+
// Kein vorheriges Item - erstelle neue verschachtelte Liste im aktuellen Item
|
|
70
|
+
const nestedList = document.createElement(list.tagName.toLowerCase() as 'ul' | 'ol');
|
|
71
|
+
|
|
72
|
+
// Verschiebe alle nachfolgenden Items in die verschachtelte Liste
|
|
73
|
+
let nextSibling = listItem.nextElementSibling;
|
|
74
|
+
while (nextSibling && nextSibling.tagName === 'LI') {
|
|
75
|
+
const toMove = nextSibling;
|
|
76
|
+
nextSibling = nextSibling.nextElementSibling;
|
|
77
|
+
nestedList.appendChild(toMove);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (nestedList.children.length > 0) {
|
|
81
|
+
listItem.appendChild(nestedList);
|
|
82
|
+
} else {
|
|
83
|
+
// Wenn keine nachfolgenden Items, erstelle leeres Sub-Item
|
|
84
|
+
const subItem = document.createElement('li');
|
|
85
|
+
nestedList.appendChild(subItem);
|
|
86
|
+
listItem.appendChild(nestedList);
|
|
87
|
+
|
|
88
|
+
// Cursor ins Sub-Item setzen
|
|
89
|
+
const newText = document.createTextNode('');
|
|
90
|
+
subItem.appendChild(newText);
|
|
91
|
+
range.setStart(newText, 0);
|
|
92
|
+
range.collapse(true);
|
|
93
|
+
selection.removeAllRanges();
|
|
94
|
+
selection.addRange(range);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Reduziert den Einrückungs-Level eines List-Items (Shift+Tab)
|
|
103
|
+
*/
|
|
104
|
+
export function outdentListItem(selection: Selection): boolean {
|
|
105
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
106
|
+
|
|
107
|
+
const range = selection.getRangeAt(0);
|
|
108
|
+
const container = range.commonAncestorContainer;
|
|
109
|
+
const listItem = container.nodeType === Node.TEXT_NODE
|
|
110
|
+
? container.parentElement?.closest('li')
|
|
111
|
+
: (container as HTMLElement).closest('li');
|
|
112
|
+
|
|
113
|
+
if (!listItem) return false;
|
|
114
|
+
|
|
115
|
+
const list = listItem.parentElement;
|
|
116
|
+
if (!list || (list.tagName !== 'UL' && list.tagName !== 'OL')) return false;
|
|
117
|
+
|
|
118
|
+
// Prüfe ob in verschachtelter Liste
|
|
119
|
+
const parentListItem = list.parentElement;
|
|
120
|
+
if (!parentListItem || parentListItem.tagName !== 'LI') {
|
|
121
|
+
// Bereits auf oberstem Level
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const parentList = parentListItem.parentElement;
|
|
126
|
+
if (!parentList || (parentList.tagName !== 'UL' && parentList.tagName !== 'OL')) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Verschiebe Item auf oberes Level
|
|
131
|
+
// Finde Position nach dem Parent-Item
|
|
132
|
+
const insertAfter = parentListItem;
|
|
133
|
+
|
|
134
|
+
// Verschiebe Item und alle nachfolgenden Items aus der verschachtelten Liste
|
|
135
|
+
const itemsToMove: HTMLElement[] = [listItem];
|
|
136
|
+
let nextSibling = listItem.nextElementSibling;
|
|
137
|
+
while (nextSibling && nextSibling.tagName === 'LI') {
|
|
138
|
+
itemsToMove.push(nextSibling as HTMLElement);
|
|
139
|
+
nextSibling = nextSibling.nextElementSibling;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Füge Items nach dem Parent-Item ein
|
|
143
|
+
itemsToMove.forEach(item => {
|
|
144
|
+
parentList.insertBefore(item, insertAfter.nextSibling);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Entferne leere verschachtelte Liste
|
|
148
|
+
if (list.children.length === 0) {
|
|
149
|
+
list.remove();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Cursor setzen
|
|
153
|
+
const textNode = listItem.firstChild;
|
|
154
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
155
|
+
range.setStart(textNode, 0);
|
|
156
|
+
range.collapse(true);
|
|
157
|
+
} else if (listItem.firstChild) {
|
|
158
|
+
range.setStart(listItem.firstChild, 0);
|
|
159
|
+
range.collapse(true);
|
|
160
|
+
} else {
|
|
161
|
+
const newText = document.createTextNode('');
|
|
162
|
+
listItem.appendChild(newText);
|
|
163
|
+
range.setStart(newText, 0);
|
|
164
|
+
range.collapse(true);
|
|
165
|
+
}
|
|
166
|
+
selection.removeAllRanges();
|
|
167
|
+
selection.addRange(range);
|
|
168
|
+
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { EditorAPI } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Liest die aktuelle Font-Size aus dem DOM an der Cursor-Position
|
|
5
|
+
*/
|
|
6
|
+
export function getCurrentFontSize(editor: EditorAPI): string | undefined {
|
|
7
|
+
const selection = editor.getSelection();
|
|
8
|
+
if (!selection || selection.rangeCount === 0) return undefined;
|
|
9
|
+
|
|
10
|
+
const range = selection.getRangeAt(0);
|
|
11
|
+
const container = range.commonAncestorContainer;
|
|
12
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
13
|
+
? container.parentElement
|
|
14
|
+
: container as HTMLElement;
|
|
15
|
+
|
|
16
|
+
if (!element) return undefined;
|
|
17
|
+
|
|
18
|
+
// Suche nach dem nächsten Element mit fontSize
|
|
19
|
+
let current: HTMLElement | null = element;
|
|
20
|
+
while (current && current !== document.body) {
|
|
21
|
+
const fontSize = window.getComputedStyle(current).fontSize;
|
|
22
|
+
if (fontSize && fontSize !== 'inherit' && fontSize !== 'initial') {
|
|
23
|
+
// Konvertiere "16px" zu "16"
|
|
24
|
+
const size = parseInt(fontSize, 10);
|
|
25
|
+
if (!isNaN(size)) {
|
|
26
|
+
return size.toString();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
current = current.parentElement;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Liest die aktuelle Textfarbe aus dem DOM an der Cursor-Position
|
|
37
|
+
*/
|
|
38
|
+
export function getCurrentTextColor(editor: EditorAPI): string | undefined {
|
|
39
|
+
const selection = editor.getSelection();
|
|
40
|
+
if (!selection || selection.rangeCount === 0) return undefined;
|
|
41
|
+
|
|
42
|
+
const range = selection.getRangeAt(0);
|
|
43
|
+
const container = range.commonAncestorContainer;
|
|
44
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
45
|
+
? container.parentElement
|
|
46
|
+
: container as HTMLElement;
|
|
47
|
+
|
|
48
|
+
if (!element) return undefined;
|
|
49
|
+
|
|
50
|
+
// Suche nach dem nächsten Element mit color
|
|
51
|
+
let current: HTMLElement | null = element;
|
|
52
|
+
while (current && current !== document.body) {
|
|
53
|
+
// Prüfe zuerst inline style (hat Priorität)
|
|
54
|
+
const inlineColor = current.style.color;
|
|
55
|
+
if (inlineColor && inlineColor.trim()) {
|
|
56
|
+
// Wenn bereits Hex, direkt zurückgeben
|
|
57
|
+
if (inlineColor.startsWith('#')) {
|
|
58
|
+
return inlineColor;
|
|
59
|
+
}
|
|
60
|
+
// Wenn RGB/RGBA, konvertieren
|
|
61
|
+
const rgbMatch = inlineColor.match(/\d+/g);
|
|
62
|
+
if (rgbMatch && rgbMatch.length >= 3) {
|
|
63
|
+
const hex = '#' + rgbMatch.slice(0, 3).map(x => {
|
|
64
|
+
const hex = parseInt(x, 10).toString(16);
|
|
65
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
66
|
+
}).join('');
|
|
67
|
+
return hex;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Dann computed style
|
|
72
|
+
const color = window.getComputedStyle(current).color;
|
|
73
|
+
if (color && color !== 'inherit' && color !== 'initial' && color !== 'rgb(0, 0, 0)' && color !== 'rgba(0, 0, 0, 0)') {
|
|
74
|
+
// Konvertiere RGB zu Hex
|
|
75
|
+
const rgb = color.match(/\d+/g);
|
|
76
|
+
if (rgb && rgb.length >= 3) {
|
|
77
|
+
const hex = '#' + rgb.slice(0, 3).map(x => {
|
|
78
|
+
const hex = parseInt(x, 10).toString(16);
|
|
79
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
80
|
+
}).join('');
|
|
81
|
+
return hex;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
current = current.parentElement;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Liest die aktuelle Hintergrundfarbe aus dem DOM an der Cursor-Position
|
|
92
|
+
*/
|
|
93
|
+
export function getCurrentBackgroundColor(editor: EditorAPI): string | undefined {
|
|
94
|
+
const selection = editor.getSelection();
|
|
95
|
+
if (!selection || selection.rangeCount === 0) return undefined;
|
|
96
|
+
|
|
97
|
+
const range = selection.getRangeAt(0);
|
|
98
|
+
const container = range.commonAncestorContainer;
|
|
99
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
100
|
+
? container.parentElement
|
|
101
|
+
: container as HTMLElement;
|
|
102
|
+
|
|
103
|
+
if (!element) return undefined;
|
|
104
|
+
|
|
105
|
+
// Suche nach dem nächsten Element mit backgroundColor
|
|
106
|
+
let current: HTMLElement | null = element;
|
|
107
|
+
while (current && current !== document.body) {
|
|
108
|
+
// Prüfe zuerst inline style (hat Priorität)
|
|
109
|
+
const inlineBgColor = current.style.backgroundColor;
|
|
110
|
+
if (inlineBgColor && inlineBgColor.trim()) {
|
|
111
|
+
// Wenn bereits Hex, direkt zurückgeben
|
|
112
|
+
if (inlineBgColor.startsWith('#')) {
|
|
113
|
+
return inlineBgColor;
|
|
114
|
+
}
|
|
115
|
+
// Wenn RGB/RGBA, konvertieren
|
|
116
|
+
const rgbMatch = inlineBgColor.match(/\d+/g);
|
|
117
|
+
if (rgbMatch && rgbMatch.length >= 3) {
|
|
118
|
+
const hex = '#' + rgbMatch.slice(0, 3).map(x => {
|
|
119
|
+
const hex = parseInt(x, 10).toString(16);
|
|
120
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
121
|
+
}).join('');
|
|
122
|
+
return hex;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Dann computed style
|
|
127
|
+
const bgColor = window.getComputedStyle(current).backgroundColor;
|
|
128
|
+
if (bgColor && bgColor !== 'inherit' && bgColor !== 'initial' && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
|
|
129
|
+
// Konvertiere RGB zu Hex
|
|
130
|
+
const rgb = bgColor.match(/\d+/g);
|
|
131
|
+
if (rgb && rgb.length >= 3) {
|
|
132
|
+
const hex = '#' + rgb.slice(0, 3).map(x => {
|
|
133
|
+
const hex = parseInt(x, 10).toString(16);
|
|
134
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
135
|
+
}).join('');
|
|
136
|
+
return hex;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
current = current.parentElement;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Liest das aktuelle Heading-Level aus dem DOM an der Cursor-Position
|
|
147
|
+
*/
|
|
148
|
+
export function getCurrentHeading(editor: EditorAPI, availableHeadings: string[]): string | undefined {
|
|
149
|
+
const selection = editor.getSelection();
|
|
150
|
+
if (!selection || selection.rangeCount === 0) return undefined;
|
|
151
|
+
|
|
152
|
+
const range = selection.getRangeAt(0);
|
|
153
|
+
const container = range.commonAncestorContainer;
|
|
154
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
155
|
+
? container.parentElement
|
|
156
|
+
: container as HTMLElement;
|
|
157
|
+
|
|
158
|
+
if (!element) return undefined;
|
|
159
|
+
|
|
160
|
+
// Suche nach dem nächsten Block-Element
|
|
161
|
+
let current: HTMLElement | null = element;
|
|
162
|
+
while (current && current !== document.body) {
|
|
163
|
+
const tagName = current.tagName.toLowerCase();
|
|
164
|
+
if (availableHeadings.includes(tagName)) {
|
|
165
|
+
return tagName;
|
|
166
|
+
}
|
|
167
|
+
if (tagName === 'p') {
|
|
168
|
+
return 'p';
|
|
169
|
+
}
|
|
170
|
+
current = current.parentElement;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|