@overlap/rte 1.0.7 → 1.0.9
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/dist/components/Dropdown.d.ts.map +1 -1
- package/dist/components/FloatingToolbar.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/hooks/useCheckbox.d.ts.map +1 -1
- package/dist/hooks/useEditorEvents.d.ts.map +1 -1
- package/dist/index.esm.js +195 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +194 -41
- package/dist/index.js.map +1 -1
- package/dist/plugins/table.d.ts.map +1 -1
- package/dist/styles.css +22 -18
- package/dist/utils/content.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAIxE,UAAU,aAAa;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3H,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAwM5C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FloatingToolbar.d.ts","sourceRoot":"","sources":["../../src/components/FloatingToolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"FloatingToolbar.d.ts","sourceRoot":"","sources":["../../src/components/FloatingToolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAExE,OAAO,EAAe,SAAS,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAE1D,UAAU,oBAAoB;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,aAAa,EAAE,WAAW,GAAG,IAAI,CAAC;CACrC;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA0L1D,CAAC"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/** Debounce time for pushing to history after input (ms) */
|
|
2
2
|
export declare const HISTORY_DEBOUNCE_MS = 300;
|
|
3
|
-
/** Width of the clickable checkbox area in pixels */
|
|
4
|
-
export declare const CHECKBOX_CLICK_ZONE_PX = 40;
|
|
5
3
|
/** Maximum nesting depth for lists */
|
|
6
4
|
export declare const MAX_LIST_DEPTH = 6;
|
|
7
5
|
/** Maximum number of history entries */
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,sCAAsC;AACtC,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,wCAAwC;AACxC,eAAO,MAAM,gBAAgB,KAAK,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckbox.d.ts","sourceRoot":"","sources":["../../src/hooks/useCheckbox.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useCheckbox.d.ts","sourceRoot":"","sources":["../../src/hooks/useCheckbox.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACH,mBAAmB,EAInB,qBAAqB,EACxB,MAAM,mBAAmB,CAAC;AAS3B,UAAU,kBAAkB;IACxB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,aAAa,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACpC,aAAa,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,aAAa,EAAE,MAAM,aAAa,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EACxB,SAAS,EACT,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,GAChB,EAAE,kBAAkB;;iCA4QJ,WAAW,KAAG,OAAO;+BApL1B,aAAa,KAAG,OAAO;6BAkFvB,aAAa,KAAG,OAAO;;EA0MlC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEditorEvents.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorEvents.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOlD,UAAU,sBAAsB;IAC5B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,UAAU,EAAE;QAAE,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC;IACxC,aAAa,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACpC,UAAU,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACjC,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,qBAAqB,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;IACrD,mBAAmB,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;IACnD,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC5B,SAAS,EACT,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,qBAAqB,EACrB,mBAAmB,EACnB,IAAI,EACJ,IAAI,GACP,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"useEditorEvents.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorEvents.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOlD,UAAU,sBAAsB;IAC5B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,UAAU,EAAE;QAAE,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC;IACxC,aAAa,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACpC,UAAU,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACjC,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,qBAAqB,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;IACrD,mBAAmB,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;IACnD,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC5B,SAAS,EACT,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,qBAAqB,EACrB,mBAAmB,EACnB,IAAI,EACJ,IAAI,GACP,EAAE,sBAAsB,QA0TxB"}
|
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import React, { useState, useRef,
|
|
2
|
+
import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
|
|
5
5
|
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" }) }));
|
|
@@ -83,10 +83,45 @@ const Icon = ({ icon, width = 18, height = 18, className }) => {
|
|
|
83
83
|
const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, showCustomColorInput, }) => {
|
|
84
84
|
const [isOpen, setIsOpen] = useState(false);
|
|
85
85
|
const [customColor, setCustomColor] = useState("#000000");
|
|
86
|
+
const [menuPos, setMenuPos] = useState({ top: 0, left: 0 });
|
|
86
87
|
const dropdownRef = useRef(null);
|
|
88
|
+
const menuRef = useRef(null);
|
|
89
|
+
const buttonRef = useRef(null);
|
|
90
|
+
const updateMenuPosition = useCallback(() => {
|
|
91
|
+
if (!buttonRef.current)
|
|
92
|
+
return;
|
|
93
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
94
|
+
const pad = 8;
|
|
95
|
+
let top = rect.bottom + 4;
|
|
96
|
+
let left = rect.left;
|
|
97
|
+
const menuEl = menuRef.current;
|
|
98
|
+
if (menuEl) {
|
|
99
|
+
const menuW = menuEl.offsetWidth;
|
|
100
|
+
const menuH = menuEl.offsetHeight;
|
|
101
|
+
if (left + menuW > window.innerWidth - pad) {
|
|
102
|
+
left = window.innerWidth - menuW - pad;
|
|
103
|
+
}
|
|
104
|
+
if (left < pad)
|
|
105
|
+
left = pad;
|
|
106
|
+
if (top + menuH > window.innerHeight - pad) {
|
|
107
|
+
top = rect.top - menuH - 4;
|
|
108
|
+
}
|
|
109
|
+
if (top < pad)
|
|
110
|
+
top = pad;
|
|
111
|
+
}
|
|
112
|
+
setMenuPos({ top, left });
|
|
113
|
+
}, []);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!isOpen)
|
|
116
|
+
return;
|
|
117
|
+
updateMenuPosition();
|
|
118
|
+
requestAnimationFrame(updateMenuPosition);
|
|
119
|
+
}, [isOpen, updateMenuPosition]);
|
|
87
120
|
useEffect(() => {
|
|
88
121
|
const handleClickOutside = (event) => {
|
|
89
|
-
|
|
122
|
+
const target = event.target;
|
|
123
|
+
if (dropdownRef.current && !dropdownRef.current.contains(target) &&
|
|
124
|
+
(!menuRef.current || !menuRef.current.contains(target))) {
|
|
90
125
|
setIsOpen(false);
|
|
91
126
|
}
|
|
92
127
|
};
|
|
@@ -102,7 +137,6 @@ const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, show
|
|
|
102
137
|
setIsOpen(false);
|
|
103
138
|
};
|
|
104
139
|
const currentOption = options.find(opt => opt.value === currentValue);
|
|
105
|
-
// Close on Escape key
|
|
106
140
|
useEffect(() => {
|
|
107
141
|
if (!isOpen)
|
|
108
142
|
return;
|
|
@@ -115,28 +149,28 @@ const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, show
|
|
|
115
149
|
document.addEventListener("keydown", handleKeyDown);
|
|
116
150
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
117
151
|
}, [isOpen]);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
152
|
+
const menuContent = isOpen ? (jsxs("div", { ref: menuRef, className: "rte-dropdown-menu", role: "listbox", "aria-label": label, style: {
|
|
153
|
+
position: 'fixed',
|
|
154
|
+
top: menuPos.top,
|
|
155
|
+
left: menuPos.left,
|
|
156
|
+
}, onMouseDown: (e) => e.preventDefault(), children: [options.map((option) => (jsxs("button", { type: "button", role: "option", "aria-selected": currentValue === option.value, 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))), showCustomColorInput && (jsxs("div", { className: "rte-color-custom-input", onMouseDown: (e) => e.stopPropagation(), children: [jsx("input", { type: "color", value: customColor, onChange: (e) => setCustomColor(e.target.value), title: "Pick a color" }), jsx("input", { type: "text", value: customColor, onChange: (e) => {
|
|
157
|
+
const v = e.target.value;
|
|
158
|
+
setCustomColor(v);
|
|
159
|
+
}, placeholder: "#000000", maxLength: 7, onKeyDown: (e) => {
|
|
160
|
+
if (e.key === "Enter") {
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
if (/^#[0-9a-fA-F]{3,6}$/.test(customColor)) {
|
|
163
|
+
handleSelect(customColor);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} }), jsx("button", { type: "button", className: "rte-color-custom-apply", onClick: () => {
|
|
167
|
+
if (/^#[0-9a-fA-F]{3,6}$/.test(customColor)) {
|
|
168
|
+
handleSelect(customColor);
|
|
169
|
+
}
|
|
170
|
+
}, children: "Apply" })] }))] })) : null;
|
|
171
|
+
return (jsxs("div", { className: "rte-dropdown", ref: dropdownRef, onMouseDown: (e) => e.preventDefault(), children: [jsxs("button", { ref: buttonRef, 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, "aria-expanded": isOpen, "aria-haspopup": "listbox", children: [jsx(Icon, { icon: icon, width: 18, height: 18 }), currentOption && (jsx("span", { className: "rte-dropdown-value", children: currentOption.label }))] }), menuContent && createPortal(menuContent, document.body)] }));
|
|
133
172
|
};
|
|
134
173
|
|
|
135
|
-
/** Debounce time for pushing to history after input (ms) */
|
|
136
|
-
const HISTORY_DEBOUNCE_MS = 300;
|
|
137
|
-
/** Width of the clickable checkbox area in pixels */
|
|
138
|
-
const CHECKBOX_CLICK_ZONE_PX = 40;
|
|
139
|
-
|
|
140
174
|
/**
|
|
141
175
|
* Pure DOM utility functions.
|
|
142
176
|
* No React dependencies - only native browser APIs.
|
|
@@ -389,8 +423,8 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
389
423
|
const rect = listItem.getBoundingClientRect();
|
|
390
424
|
const clientX = event.clientX;
|
|
391
425
|
const isInCheckboxArea = listItem.dir === "rtl"
|
|
392
|
-
? clientX >= rect.right
|
|
393
|
-
: clientX <= rect.left
|
|
426
|
+
? clientX >= rect.right
|
|
427
|
+
: clientX <= rect.left;
|
|
394
428
|
if (isInCheckboxArea) {
|
|
395
429
|
event.preventDefault();
|
|
396
430
|
event.stopPropagation();
|
|
@@ -417,8 +451,8 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
417
451
|
const rect = listItem.getBoundingClientRect();
|
|
418
452
|
const clientX = event.clientX;
|
|
419
453
|
const isInCheckboxArea = listItem.dir === "rtl"
|
|
420
|
-
? clientX >= rect.right
|
|
421
|
-
: clientX <= rect.left
|
|
454
|
+
? clientX >= rect.right
|
|
455
|
+
: clientX <= rect.left;
|
|
422
456
|
if (isInCheckboxArea) {
|
|
423
457
|
event.preventDefault();
|
|
424
458
|
}
|
|
@@ -676,6 +710,9 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
676
710
|
};
|
|
677
711
|
}
|
|
678
712
|
|
|
713
|
+
/** Debounce time for pushing to history after input (ms) */
|
|
714
|
+
const HISTORY_DEBOUNCE_MS = 300;
|
|
715
|
+
|
|
679
716
|
/**
|
|
680
717
|
* Lightweight HTML sanitizer (no external dependencies).
|
|
681
718
|
*
|
|
@@ -806,7 +843,7 @@ const ALLOWED_CONTENT_TAGS = new Set([
|
|
|
806
843
|
"ul", "ol", "li", "a", "strong", "em", "u", "s", "del",
|
|
807
844
|
"sub", "sup", "code", "pre", "blockquote", "br", "hr",
|
|
808
845
|
"img", "table", "thead", "tbody", "tr", "th", "td",
|
|
809
|
-
"b", "i", "strike",
|
|
846
|
+
"b", "i", "strike", "font",
|
|
810
847
|
]);
|
|
811
848
|
/** Checks if an attribute key is safe to set on a DOM element. */
|
|
812
849
|
function isSafeAttribute(key) {
|
|
@@ -1014,6 +1051,7 @@ function domToContent(element) {
|
|
|
1014
1051
|
"sub",
|
|
1015
1052
|
"sup",
|
|
1016
1053
|
"code",
|
|
1054
|
+
"font",
|
|
1017
1055
|
].includes(tagName)) {
|
|
1018
1056
|
const children = [];
|
|
1019
1057
|
Array.from(el.childNodes).forEach((child) => {
|
|
@@ -1103,6 +1141,46 @@ function domToContent(element) {
|
|
|
1103
1141
|
});
|
|
1104
1142
|
}
|
|
1105
1143
|
}
|
|
1144
|
+
// <font> from execCommand('foreColor') or pasted HTML
|
|
1145
|
+
if (tagName === "font") {
|
|
1146
|
+
const fontAttrs = {};
|
|
1147
|
+
const colorAttr = el.getAttribute("color");
|
|
1148
|
+
if (colorAttr)
|
|
1149
|
+
fontAttrs.color = colorAttr;
|
|
1150
|
+
const sizeAttr = el.getAttribute("size");
|
|
1151
|
+
if (sizeAttr) {
|
|
1152
|
+
const sizeMap = {
|
|
1153
|
+
"1": "10px", "2": "13px", "3": "16px", "4": "18px",
|
|
1154
|
+
"5": "24px", "6": "32px", "7": "48px",
|
|
1155
|
+
};
|
|
1156
|
+
const mapped = sizeMap[sizeAttr];
|
|
1157
|
+
if (mapped)
|
|
1158
|
+
fontAttrs.fontSize = mapped;
|
|
1159
|
+
}
|
|
1160
|
+
const style = el.getAttribute("style") || "";
|
|
1161
|
+
if (style) {
|
|
1162
|
+
style.split(";").forEach((rule) => {
|
|
1163
|
+
const [key, value] = rule
|
|
1164
|
+
.split(":")
|
|
1165
|
+
.map((s) => s.trim());
|
|
1166
|
+
if (key && value) {
|
|
1167
|
+
if (key === "font-size")
|
|
1168
|
+
fontAttrs.fontSize = value;
|
|
1169
|
+
else if (key === "color")
|
|
1170
|
+
fontAttrs.color = value;
|
|
1171
|
+
else if (key === "background-color")
|
|
1172
|
+
fontAttrs.backgroundColor = value;
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
return {
|
|
1177
|
+
type: "span",
|
|
1178
|
+
children: children.length > 0 ? children : undefined,
|
|
1179
|
+
attributes: Object.keys(fontAttrs).length > 0
|
|
1180
|
+
? fontAttrs
|
|
1181
|
+
: undefined,
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1106
1184
|
// Map tag names to semantic types
|
|
1107
1185
|
const type = tagName === "strong" || tagName === "b"
|
|
1108
1186
|
? "bold"
|
|
@@ -2121,6 +2199,61 @@ function useEditorEvents({ editorRef, historyRef, isUpdatingRef, mountedRef, not
|
|
|
2121
2199
|
}, 0);
|
|
2122
2200
|
return;
|
|
2123
2201
|
}
|
|
2202
|
+
// Exit code block: Enter on empty last line escapes <pre>
|
|
2203
|
+
if (e.key === "Enter" &&
|
|
2204
|
+
!e.shiftKey &&
|
|
2205
|
+
!isModifierPressed) {
|
|
2206
|
+
const sel = window.getSelection();
|
|
2207
|
+
if (sel && sel.rangeCount > 0 && sel.isCollapsed) {
|
|
2208
|
+
const range = sel.getRangeAt(0);
|
|
2209
|
+
const node = range.startContainer;
|
|
2210
|
+
const pre = (node instanceof HTMLElement
|
|
2211
|
+
? node
|
|
2212
|
+
: node.parentElement)?.closest("pre");
|
|
2213
|
+
if (pre && pre.lastChild) {
|
|
2214
|
+
const lastChild = pre.lastChild;
|
|
2215
|
+
const cursorInPre = node === pre &&
|
|
2216
|
+
range.startOffset === pre.childNodes.length;
|
|
2217
|
+
const cursorAtEndOfLastText = node.nodeType === Node.TEXT_NODE &&
|
|
2218
|
+
node === lastChild &&
|
|
2219
|
+
range.startOffset ===
|
|
2220
|
+
(node.textContent?.length ?? 0);
|
|
2221
|
+
const isAtEnd = cursorInPre || cursorAtEndOfLastText;
|
|
2222
|
+
const lastIsBr = lastChild instanceof HTMLElement &&
|
|
2223
|
+
lastChild.tagName === "BR";
|
|
2224
|
+
const endsWithNewline = node.nodeType === Node.TEXT_NODE &&
|
|
2225
|
+
(node.textContent || "").endsWith("\n");
|
|
2226
|
+
if (isAtEnd && (lastIsBr || endsWithNewline)) {
|
|
2227
|
+
e.preventDefault();
|
|
2228
|
+
if (lastIsBr) {
|
|
2229
|
+
pre.removeChild(lastChild);
|
|
2230
|
+
}
|
|
2231
|
+
else if (node.nodeType === Node.TEXT_NODE &&
|
|
2232
|
+
node.textContent) {
|
|
2233
|
+
node.textContent =
|
|
2234
|
+
node.textContent.replace(/\n$/, "");
|
|
2235
|
+
}
|
|
2236
|
+
if (!pre.textContent &&
|
|
2237
|
+
!pre.querySelector("br")) {
|
|
2238
|
+
pre.appendChild(document.createElement("br"));
|
|
2239
|
+
}
|
|
2240
|
+
const p = document.createElement("p");
|
|
2241
|
+
p.appendChild(document.createElement("br"));
|
|
2242
|
+
pre.parentNode?.insertBefore(p, pre.nextSibling);
|
|
2243
|
+
const newRange = document.createRange();
|
|
2244
|
+
newRange.setStart(p, 0);
|
|
2245
|
+
newRange.collapse(true);
|
|
2246
|
+
sel.removeAllRanges();
|
|
2247
|
+
sel.addRange(newRange);
|
|
2248
|
+
const content = domToContent(editor);
|
|
2249
|
+
const serializedSel = serializeSelection(editor);
|
|
2250
|
+
historyRef.current.push(content, serializedSel);
|
|
2251
|
+
notifyChange(content);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2124
2257
|
// Auto-link: convert URLs to <a> tags on space/enter
|
|
2125
2258
|
if (!isModifierPressed && (e.key === " " || e.key === "Enter")) {
|
|
2126
2259
|
handleAutoLink(editor, e);
|
|
@@ -4015,7 +4148,7 @@ function createAdvancedLinkPlugin(options = {}) {
|
|
|
4015
4148
|
/** Pre-built link plugin with just target enabled (no custom fields). */
|
|
4016
4149
|
const advancedLinkPlugin = createAdvancedLinkPlugin();
|
|
4017
4150
|
|
|
4018
|
-
const InsertTableDialog = ({ onInsert, onClose, }) => {
|
|
4151
|
+
const InsertTableDialog = ({ onInsert, onClose, anchorRect, }) => {
|
|
4019
4152
|
const [rows, setRows] = useState(3);
|
|
4020
4153
|
const [cols, setCols] = useState(3);
|
|
4021
4154
|
const dialogRef = useRef(null);
|
|
@@ -4029,7 +4162,25 @@ const InsertTableDialog = ({ onInsert, onClose, }) => {
|
|
|
4029
4162
|
document.addEventListener("mousedown", handler);
|
|
4030
4163
|
return () => document.removeEventListener("mousedown", handler);
|
|
4031
4164
|
}, [onClose]);
|
|
4032
|
-
|
|
4165
|
+
const style = { position: "fixed" };
|
|
4166
|
+
if (anchorRect) {
|
|
4167
|
+
const pad = 8;
|
|
4168
|
+
let top = anchorRect.bottom + 4;
|
|
4169
|
+
let left = anchorRect.left;
|
|
4170
|
+
if (left + 220 > window.innerWidth - pad) {
|
|
4171
|
+
left = window.innerWidth - 220 - pad;
|
|
4172
|
+
}
|
|
4173
|
+
if (left < pad)
|
|
4174
|
+
left = pad;
|
|
4175
|
+
if (top + 200 > window.innerHeight - pad) {
|
|
4176
|
+
top = anchorRect.top - 200 - 4;
|
|
4177
|
+
}
|
|
4178
|
+
if (top < pad)
|
|
4179
|
+
top = pad;
|
|
4180
|
+
style.top = top;
|
|
4181
|
+
style.left = left;
|
|
4182
|
+
}
|
|
4183
|
+
return createPortal(jsxs("div", { className: "rte-table-insert-dialog", ref: dialogRef, style: style, onMouseDown: (e) => e.preventDefault(), children: [jsx("div", { className: "rte-table-insert-title", children: "Insert Table" }), jsxs("div", { className: "rte-table-insert-fields", children: [jsxs("label", { className: "rte-table-insert-label", children: [jsx("span", { children: "Zeilen" }), jsx("input", { type: "number", min: 1, max: 20, value: rows, onChange: (e) => setRows(Math.max(1, Math.min(20, parseInt(e.target.value) || 1))), className: "rte-table-insert-input", onMouseDown: (e) => e.stopPropagation() })] }), jsxs("label", { className: "rte-table-insert-label", children: [jsx("span", { children: "Spalten" }), jsx("input", { type: "number", min: 1, max: 10, value: cols, onChange: (e) => setCols(Math.max(1, Math.min(10, parseInt(e.target.value) || 1))), className: "rte-table-insert-input", onMouseDown: (e) => e.stopPropagation() })] })] }), jsx("button", { type: "button", className: "rte-table-insert-btn", onClick: () => onInsert(rows, cols), children: "Insert" })] }), document.body);
|
|
4033
4184
|
};
|
|
4034
4185
|
const TableContextMenu = ({ x, y, onClose, }) => {
|
|
4035
4186
|
const ref = useRef(null);
|
|
@@ -4050,6 +4201,14 @@ const TableContextMenu = ({ x, y, onClose, }) => {
|
|
|
4050
4201
|
};
|
|
4051
4202
|
const TableToolbarButton = (props) => {
|
|
4052
4203
|
const [showDialog, setShowDialog] = useState(false);
|
|
4204
|
+
const btnRef = useRef(null);
|
|
4205
|
+
const [anchorRect, setAnchorRect] = useState(null);
|
|
4206
|
+
const handleToggle = useCallback(() => {
|
|
4207
|
+
if (!showDialog && btnRef.current) {
|
|
4208
|
+
setAnchorRect(btnRef.current.getBoundingClientRect());
|
|
4209
|
+
}
|
|
4210
|
+
setShowDialog((v) => !v);
|
|
4211
|
+
}, [showDialog]);
|
|
4053
4212
|
const handleInsert = useCallback((rows, cols) => {
|
|
4054
4213
|
setShowDialog(false);
|
|
4055
4214
|
if (!props.editorAPI)
|
|
@@ -4058,7 +4217,6 @@ const TableToolbarButton = (props) => {
|
|
|
4058
4217
|
if (!sel || sel.rangeCount === 0)
|
|
4059
4218
|
return;
|
|
4060
4219
|
const range = sel.getRangeAt(0);
|
|
4061
|
-
// Find the editor's contentEditable root
|
|
4062
4220
|
const container = range.commonAncestorContainer;
|
|
4063
4221
|
const editorEl = container.nodeType === Node.TEXT_NODE
|
|
4064
4222
|
? container.parentElement
|
|
@@ -4067,7 +4225,6 @@ const TableToolbarButton = (props) => {
|
|
|
4067
4225
|
if (!editorRoot)
|
|
4068
4226
|
return;
|
|
4069
4227
|
const table = createTable(rows, cols);
|
|
4070
|
-
// Insert after the current block element
|
|
4071
4228
|
let block = editorEl;
|
|
4072
4229
|
while (block &&
|
|
4073
4230
|
block !== editorRoot &&
|
|
@@ -4080,11 +4237,9 @@ const TableToolbarButton = (props) => {
|
|
|
4080
4237
|
else {
|
|
4081
4238
|
editorRoot.appendChild(table);
|
|
4082
4239
|
}
|
|
4083
|
-
// Add a paragraph after the table so the user can continue typing
|
|
4084
4240
|
const p = document.createElement("p");
|
|
4085
4241
|
p.innerHTML = "<br>";
|
|
4086
4242
|
table.parentNode?.insertBefore(p, table.nextSibling);
|
|
4087
|
-
// Focus the first cell
|
|
4088
4243
|
const firstCell = table.querySelector("td, th");
|
|
4089
4244
|
if (firstCell) {
|
|
4090
4245
|
const newRange = document.createRange();
|
|
@@ -4094,7 +4249,7 @@ const TableToolbarButton = (props) => {
|
|
|
4094
4249
|
sel.addRange(newRange);
|
|
4095
4250
|
}
|
|
4096
4251
|
}, [props.editorAPI]);
|
|
4097
|
-
return (jsxs(
|
|
4252
|
+
return (jsxs(Fragment, { children: [jsx("button", { ref: btnRef, type: "button", onClick: handleToggle, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? "rte-toolbar-button-active" : ""}`, title: "Table", "aria-label": "Table", children: jsx(IconWrapper, { icon: "mdi:table", width: 18, height: 18 }) }), showDialog && (jsx(InsertTableDialog, { onInsert: handleInsert, onClose: () => setShowDialog(false), anchorRect: anchorRect }))] }));
|
|
4098
4253
|
};
|
|
4099
4254
|
/* ══════════════════════════════════════════════════════════════════════════
|
|
4100
4255
|
Table Plugin export
|
|
@@ -4304,10 +4459,10 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
|
|
|
4304
4459
|
}
|
|
4305
4460
|
// Center horizontally on the selection, clamp to viewport
|
|
4306
4461
|
let left = rect.left + rect.width / 2 - toolbarW / 2;
|
|
4307
|
-
if (left < pad)
|
|
4308
|
-
left = pad;
|
|
4309
4462
|
if (left + toolbarW > vw - pad)
|
|
4310
4463
|
left = vw - toolbarW - pad;
|
|
4464
|
+
if (left < pad)
|
|
4465
|
+
left = pad;
|
|
4311
4466
|
setPos({ top, left, visible: true });
|
|
4312
4467
|
}, [editorElement]);
|
|
4313
4468
|
// Debounce via requestAnimationFrame for smooth repositioning
|
|
@@ -4352,16 +4507,14 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
|
|
|
4352
4507
|
requestAnimationFrame(() => scheduleUpdate());
|
|
4353
4508
|
}, [editorAPI, scheduleUpdate]);
|
|
4354
4509
|
const isHidden = !pos.visible || leftPlugins.length === 0;
|
|
4355
|
-
return (jsx("div", { ref: toolbarRef, className: "rte-floating-toolbar", style: {
|
|
4510
|
+
return createPortal(jsx("div", { ref: toolbarRef, className: "rte-floating-toolbar", style: {
|
|
4356
4511
|
position: "fixed",
|
|
4357
4512
|
top: pos.top,
|
|
4358
4513
|
left: pos.left,
|
|
4359
4514
|
visibility: isHidden ? "hidden" : "visible",
|
|
4360
4515
|
opacity: isHidden ? 0 : 1,
|
|
4361
4516
|
pointerEvents: isHidden ? "none" : "auto",
|
|
4362
|
-
},
|
|
4363
|
-
// Prevent selection loss when clicking toolbar buttons
|
|
4364
|
-
onMouseDown: (e) => e.preventDefault(), children: jsxs("div", { className: "rte-floating-toolbar-content", children: [jsx("div", { className: "rte-toolbar-left", children: leftPlugins.map((plugin) => {
|
|
4517
|
+
}, onMouseDown: (e) => e.preventDefault(), children: jsxs("div", { className: "rte-floating-toolbar-content", children: [jsx("div", { className: "rte-toolbar-left", children: leftPlugins.map((plugin) => {
|
|
4365
4518
|
if (!plugin.renderButton)
|
|
4366
4519
|
return null;
|
|
4367
4520
|
const isActive = plugin.isActive
|
|
@@ -4395,7 +4548,7 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
|
|
|
4395
4548
|
disabled: !canExecute,
|
|
4396
4549
|
editorAPI,
|
|
4397
4550
|
});
|
|
4398
|
-
})()] }))] }) }));
|
|
4551
|
+
})()] }))] }) }), document.body);
|
|
4399
4552
|
};
|
|
4400
4553
|
|
|
4401
4554
|
/**
|