smartrte-react 0.1.7 → 0.1.8
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/ClassicEditor.js +207 -2
- package/dist/standalone/editor.js +64 -64
- package/package.json +14 -13
|
@@ -21,6 +21,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
21
21
|
const selectingRef = useRef(null);
|
|
22
22
|
const [imageMenu, setImageMenu] = useState(null);
|
|
23
23
|
const [showMediaManager, setShowMediaManager] = useState(false);
|
|
24
|
+
const [showColorPicker, setShowColorPicker] = useState(false);
|
|
25
|
+
const [colorPickerType, setColorPickerType] = useState('text');
|
|
26
|
+
const savedRangeRef = useRef(null);
|
|
27
|
+
const [currentFontSize, setCurrentFontSize] = useState("11");
|
|
24
28
|
useEffect(() => {
|
|
25
29
|
const el = editableRef.current;
|
|
26
30
|
if (!el)
|
|
@@ -42,6 +46,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
42
46
|
el.removeEventListener("contextmenu", onCtx, { capture: true });
|
|
43
47
|
};
|
|
44
48
|
}, [value]);
|
|
49
|
+
// Save selection whenever it changes
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const saveSelection = () => {
|
|
52
|
+
const sel = window.getSelection();
|
|
53
|
+
if (sel && sel.rangeCount > 0) {
|
|
54
|
+
const range = sel.getRangeAt(0);
|
|
55
|
+
const editor = editableRef.current;
|
|
56
|
+
if (editor && editor.contains(range.commonAncestorContainer)) {
|
|
57
|
+
savedRangeRef.current = range.cloneRange();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
document.addEventListener('selectionchange', saveSelection);
|
|
62
|
+
return () => {
|
|
63
|
+
document.removeEventListener('selectionchange', saveSelection);
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
45
66
|
const exec = (command, valueArg) => {
|
|
46
67
|
try {
|
|
47
68
|
document.execCommand(command, false, valueArg);
|
|
@@ -66,6 +87,76 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
66
87
|
return;
|
|
67
88
|
exec("createLink", url);
|
|
68
89
|
};
|
|
90
|
+
const applyFontSize = (size) => {
|
|
91
|
+
try {
|
|
92
|
+
// Update current font size state
|
|
93
|
+
setCurrentFontSize(size);
|
|
94
|
+
const editor = editableRef.current;
|
|
95
|
+
if (!editor)
|
|
96
|
+
return;
|
|
97
|
+
editor.focus();
|
|
98
|
+
// Try to get current selection, or use saved range
|
|
99
|
+
let range = null;
|
|
100
|
+
const sel = window.getSelection();
|
|
101
|
+
if (sel && sel.rangeCount > 0) {
|
|
102
|
+
const currentRange = sel.getRangeAt(0);
|
|
103
|
+
// Use current range if it's within our editor
|
|
104
|
+
if (editor.contains(currentRange.commonAncestorContainer)) {
|
|
105
|
+
range = currentRange;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Fallback to saved range if current range is not available
|
|
109
|
+
if (!range && savedRangeRef.current) {
|
|
110
|
+
range = savedRangeRef.current.cloneRange();
|
|
111
|
+
}
|
|
112
|
+
// If no range at all, just update state for future typing
|
|
113
|
+
if (!range)
|
|
114
|
+
return;
|
|
115
|
+
// If range is collapsed (cursor position, no selection), insert an invisible span
|
|
116
|
+
if (range.collapsed) {
|
|
117
|
+
// Create a span with zero-width space that will capture future typing
|
|
118
|
+
const span = document.createElement('span');
|
|
119
|
+
span.style.fontSize = size + 'pt';
|
|
120
|
+
span.textContent = '\u200B'; // Zero-width space
|
|
121
|
+
range.insertNode(span);
|
|
122
|
+
// Position cursor inside the span
|
|
123
|
+
const newRange = document.createRange();
|
|
124
|
+
newRange.setStart(span.firstChild, 1);
|
|
125
|
+
newRange.collapse(true);
|
|
126
|
+
if (sel) {
|
|
127
|
+
sel.removeAllRanges();
|
|
128
|
+
sel.addRange(newRange);
|
|
129
|
+
}
|
|
130
|
+
handleInput();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// If there's selected text, wrap it
|
|
134
|
+
const span = document.createElement('span');
|
|
135
|
+
span.style.fontSize = size + 'pt';
|
|
136
|
+
// Extract the selected content and wrap it in the span
|
|
137
|
+
const fragment = range.extractContents();
|
|
138
|
+
span.appendChild(fragment);
|
|
139
|
+
// Insert the span at the current position
|
|
140
|
+
range.insertNode(span);
|
|
141
|
+
// Update selection to show what was changed
|
|
142
|
+
if (sel) {
|
|
143
|
+
range.selectNodeContents(span);
|
|
144
|
+
sel.removeAllRanges();
|
|
145
|
+
sel.addRange(range);
|
|
146
|
+
}
|
|
147
|
+
// Trigger change event
|
|
148
|
+
handleInput();
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error('Error applying font size:', error);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const applyTextColor = (color) => {
|
|
155
|
+
exec("foreColor", color);
|
|
156
|
+
};
|
|
157
|
+
const applyBackgroundColor = (color) => {
|
|
158
|
+
exec("hiliteColor", color);
|
|
159
|
+
};
|
|
69
160
|
const insertImage = () => {
|
|
70
161
|
if (!media)
|
|
71
162
|
return;
|
|
@@ -739,7 +830,56 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
739
830
|
background: "#fff",
|
|
740
831
|
textDecoration: "line-through",
|
|
741
832
|
color: "#111",
|
|
742
|
-
}, children: "S" }),
|
|
833
|
+
}, children: "S" }), _jsxs("select", { value: currentFontSize, onMouseDown: () => {
|
|
834
|
+
// Save selection before dropdown interaction
|
|
835
|
+
const sel = window.getSelection();
|
|
836
|
+
if (sel && sel.rangeCount > 0) {
|
|
837
|
+
const range = sel.getRangeAt(0);
|
|
838
|
+
const editor = editableRef.current;
|
|
839
|
+
if (editor && editor.contains(range.commonAncestorContainer) && !range.collapsed) {
|
|
840
|
+
savedRangeRef.current = range.cloneRange();
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}, onChange: (e) => applyFontSize(e.target.value), title: "Font Size", style: {
|
|
844
|
+
height: 32,
|
|
845
|
+
padding: "0 8px",
|
|
846
|
+
border: "1px solid #e5e7eb",
|
|
847
|
+
borderRadius: 6,
|
|
848
|
+
background: "#fff",
|
|
849
|
+
color: "#111",
|
|
850
|
+
}, children: [_jsx("option", { value: "8", children: "8" }), _jsx("option", { value: "9", children: "9" }), _jsx("option", { value: "10", children: "10" }), _jsx("option", { value: "11", children: "11" }), _jsx("option", { value: "12", children: "12" }), _jsx("option", { value: "14", children: "14" }), _jsx("option", { value: "18", children: "18" }), _jsx("option", { value: "24", children: "24" }), _jsx("option", { value: "30", children: "30" }), _jsx("option", { value: "36", children: "36" }), _jsx("option", { value: "48", children: "48" }), _jsx("option", { value: "60", children: "60" }), _jsx("option", { value: "72", children: "72" }), _jsx("option", { value: "96", children: "96" })] }), _jsxs("button", { title: "Text Color", onClick: () => {
|
|
851
|
+
setColorPickerType('text');
|
|
852
|
+
setShowColorPicker(true);
|
|
853
|
+
}, style: {
|
|
854
|
+
height: 32,
|
|
855
|
+
minWidth: 32,
|
|
856
|
+
padding: "0 8px",
|
|
857
|
+
border: "1px solid #e5e7eb",
|
|
858
|
+
borderRadius: 6,
|
|
859
|
+
background: "#fff",
|
|
860
|
+
color: "#111",
|
|
861
|
+
position: "relative",
|
|
862
|
+
}, children: [_jsx("span", { style: { fontWeight: 700 }, children: "A" }), _jsx("div", { style: {
|
|
863
|
+
position: "absolute",
|
|
864
|
+
bottom: 4,
|
|
865
|
+
left: "50%",
|
|
866
|
+
transform: "translateX(-50%)",
|
|
867
|
+
width: 16,
|
|
868
|
+
height: 3,
|
|
869
|
+
background: "#000",
|
|
870
|
+
borderRadius: 1,
|
|
871
|
+
} })] }), _jsx("button", { title: "Background Color", onClick: () => {
|
|
872
|
+
setColorPickerType('background');
|
|
873
|
+
setShowColorPicker(true);
|
|
874
|
+
}, style: {
|
|
875
|
+
height: 32,
|
|
876
|
+
minWidth: 32,
|
|
877
|
+
padding: "0 8px",
|
|
878
|
+
border: "1px solid #e5e7eb",
|
|
879
|
+
borderRadius: 6,
|
|
880
|
+
background: "#fff",
|
|
881
|
+
color: "#111",
|
|
882
|
+
}, children: _jsx("span", { style: { fontWeight: 700, background: "#ffeb3b", padding: "2px 4px" }, children: "A" }) }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
|
|
743
883
|
height: 32,
|
|
744
884
|
padding: "0 10px",
|
|
745
885
|
border: "1px solid #e5e7eb",
|
|
@@ -929,7 +1069,72 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
929
1069
|
}, children: [_jsx("button", { onClick: () => setShowTableDialog(false), children: "Cancel" }), _jsx("button", { onClick: () => {
|
|
930
1070
|
insertTable();
|
|
931
1071
|
setShowTableDialog(false);
|
|
932
|
-
}, children: "Insert" })] })] }) })),
|
|
1072
|
+
}, children: "Insert" })] })] }) })), showColorPicker && (_jsx("div", { style: {
|
|
1073
|
+
position: "fixed",
|
|
1074
|
+
inset: 0,
|
|
1075
|
+
background: "rgba(0,0,0,0.35)",
|
|
1076
|
+
display: "flex",
|
|
1077
|
+
alignItems: "center",
|
|
1078
|
+
justifyContent: "center",
|
|
1079
|
+
zIndex: 50,
|
|
1080
|
+
}, onClick: () => setShowColorPicker(false), children: _jsxs("div", { style: {
|
|
1081
|
+
background: "#fff",
|
|
1082
|
+
padding: 16,
|
|
1083
|
+
borderRadius: 8,
|
|
1084
|
+
minWidth: 320,
|
|
1085
|
+
color: "#000",
|
|
1086
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 12 }, children: colorPickerType === 'text' ? 'Select Text Color' : 'Select Background Color' }), _jsx("div", { style: {
|
|
1087
|
+
display: "grid",
|
|
1088
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
1089
|
+
gap: 8,
|
|
1090
|
+
marginBottom: 12,
|
|
1091
|
+
}, children: [
|
|
1092
|
+
'#000000', '#434343', '#666666', '#999999',
|
|
1093
|
+
'#b7b7b7', '#cccccc', '#d9d9d9', '#efefef',
|
|
1094
|
+
'#f3f3f3', '#ffffff', '#980000', '#ff0000',
|
|
1095
|
+
'#ff9900', '#ffff00', '#00ff00', '#00ffff',
|
|
1096
|
+
'#4a86e8', '#0000ff', '#9900ff', '#ff00ff',
|
|
1097
|
+
'#e6b8af', '#f4cccc', '#fce5cd', '#fff2cc',
|
|
1098
|
+
'#d9ead3', '#d0e0e3', '#c9daf8', '#cfe2f3',
|
|
1099
|
+
'#d9d2e9', '#ead1dc', '#dd7e6b', '#ea9999',
|
|
1100
|
+
'#f9cb9c', '#ffe599', '#b6d7a8', '#a2c4c9',
|
|
1101
|
+
'#a4c2f4', '#9fc5e8', '#b4a7d6', '#d5a6bd',
|
|
1102
|
+
].map((color) => (_jsx("button", { onClick: () => {
|
|
1103
|
+
if (colorPickerType === 'text') {
|
|
1104
|
+
applyTextColor(color);
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
applyBackgroundColor(color);
|
|
1108
|
+
}
|
|
1109
|
+
setShowColorPicker(false);
|
|
1110
|
+
}, style: {
|
|
1111
|
+
width: 32,
|
|
1112
|
+
height: 32,
|
|
1113
|
+
border: color === '#ffffff' ? '1px solid #ddd' : 'none',
|
|
1114
|
+
borderRadius: 4,
|
|
1115
|
+
background: color,
|
|
1116
|
+
cursor: 'pointer',
|
|
1117
|
+
}, title: color }, color))) }), _jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("label", { style: { display: 'block', marginBottom: 6, fontSize: 12 }, children: "Custom color:" }), _jsx("input", { type: "color", onChange: (e) => {
|
|
1118
|
+
if (colorPickerType === 'text') {
|
|
1119
|
+
applyTextColor(e.target.value);
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
applyBackgroundColor(e.target.value);
|
|
1123
|
+
}
|
|
1124
|
+
}, style: {
|
|
1125
|
+
width: '100%',
|
|
1126
|
+
height: 40,
|
|
1127
|
+
border: '1px solid #ddd',
|
|
1128
|
+
borderRadius: 6,
|
|
1129
|
+
cursor: 'pointer',
|
|
1130
|
+
} })] }), _jsx("div", { style: { display: 'flex', justifyContent: 'end' }, children: _jsx("button", { onClick: () => setShowColorPicker(false), style: {
|
|
1131
|
+
padding: '6px 16px',
|
|
1132
|
+
border: '1px solid #e5e7eb',
|
|
1133
|
+
borderRadius: 6,
|
|
1134
|
+
background: '#fff',
|
|
1135
|
+
color: '#111',
|
|
1136
|
+
cursor: 'pointer',
|
|
1137
|
+
}, children: "Close" }) })] }) })), formula && showFormulaDialog && (_jsx("div", { style: {
|
|
933
1138
|
position: "fixed",
|
|
934
1139
|
inset: 0,
|
|
935
1140
|
background: "rgba(0,0,0,0.35)",
|