smartrte-react 0.2.5 → 0.2.6
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.
|
@@ -52,6 +52,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
52
52
|
const [showSpecialChars, setShowSpecialChars] = useState(false);
|
|
53
53
|
const [colorPickerType, setColorPickerType] = useState('text');
|
|
54
54
|
const savedRangeRef = useRef(null);
|
|
55
|
+
const historyRef = useRef({
|
|
56
|
+
undo: [],
|
|
57
|
+
redo: [],
|
|
58
|
+
});
|
|
55
59
|
const [currentFontSize, setCurrentFontSize] = useState("");
|
|
56
60
|
const [currentFont, setCurrentFont] = useState("");
|
|
57
61
|
const [activeState, setActiveState] = useState({
|
|
@@ -139,6 +143,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
139
143
|
}, []);
|
|
140
144
|
const exec = (command, valueArg) => {
|
|
141
145
|
try {
|
|
146
|
+
if (command === "undo" && restoreEditorHistory("undo"))
|
|
147
|
+
return;
|
|
148
|
+
if (command === "redo" && restoreEditorHistory("redo"))
|
|
149
|
+
return;
|
|
142
150
|
if (!restoreSavedSelection()) {
|
|
143
151
|
safeSelectRange(getSelectionRangeInEditor());
|
|
144
152
|
}
|
|
@@ -176,6 +184,63 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
176
184
|
onChange(html);
|
|
177
185
|
}
|
|
178
186
|
};
|
|
187
|
+
const pushEditorHistory = () => {
|
|
188
|
+
const editor = editableRef.current;
|
|
189
|
+
if (!editor)
|
|
190
|
+
return;
|
|
191
|
+
const selectionCells = selectionRef.current
|
|
192
|
+
? getCellsInGridRect(selectionRef.current.tbody, selectionRef.current.sr, selectionRef.current.sc, selectionRef.current.er, selectionRef.current.ec)
|
|
193
|
+
: [];
|
|
194
|
+
const decor = selectionCells.map((cell) => ({
|
|
195
|
+
cell,
|
|
196
|
+
background: cell.style.background,
|
|
197
|
+
outline: cell.style.outline,
|
|
198
|
+
outlineOffset: cell.style.outlineOffset,
|
|
199
|
+
previousBackground: cell.__rtePrevBg,
|
|
200
|
+
}));
|
|
201
|
+
decor.forEach(({ cell, previousBackground }) => {
|
|
202
|
+
if (previousBackground != null)
|
|
203
|
+
cell.style.background = previousBackground || "";
|
|
204
|
+
cell.style.outline = "";
|
|
205
|
+
cell.style.outlineOffset = "";
|
|
206
|
+
});
|
|
207
|
+
const html = editor.innerHTML;
|
|
208
|
+
decor.forEach(({ cell, background, outline, outlineOffset }) => {
|
|
209
|
+
cell.style.background = background;
|
|
210
|
+
cell.style.outline = outline;
|
|
211
|
+
cell.style.outlineOffset = outlineOffset;
|
|
212
|
+
});
|
|
213
|
+
const undo = historyRef.current.undo;
|
|
214
|
+
if (undo[undo.length - 1] !== html) {
|
|
215
|
+
undo.push(html);
|
|
216
|
+
if (undo.length > 100)
|
|
217
|
+
undo.shift();
|
|
218
|
+
}
|
|
219
|
+
historyRef.current.redo = [];
|
|
220
|
+
};
|
|
221
|
+
const restoreEditorHistory = (dir) => {
|
|
222
|
+
const editor = editableRef.current;
|
|
223
|
+
if (!editor)
|
|
224
|
+
return false;
|
|
225
|
+
const history = historyRef.current;
|
|
226
|
+
const from = dir === "undo" ? history.undo : history.redo;
|
|
227
|
+
const to = dir === "undo" ? history.redo : history.undo;
|
|
228
|
+
const html = from.pop();
|
|
229
|
+
if (html == null)
|
|
230
|
+
return false;
|
|
231
|
+
to.push(editor.innerHTML);
|
|
232
|
+
editor.innerHTML = html;
|
|
233
|
+
fixNegativeMargins(editor);
|
|
234
|
+
ensureTableWrappers(editor);
|
|
235
|
+
addTableResizeHandles();
|
|
236
|
+
clearSelectionDecor();
|
|
237
|
+
setTableMenu(null);
|
|
238
|
+
if (html !== lastEmittedRef.current) {
|
|
239
|
+
lastEmittedRef.current = html;
|
|
240
|
+
onChange?.(html);
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
};
|
|
179
244
|
const restoreSavedSelection = () => {
|
|
180
245
|
const editor = editableRef.current;
|
|
181
246
|
if (!editor)
|
|
@@ -1788,9 +1853,123 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1788
1853
|
link.remove();
|
|
1789
1854
|
URL.revokeObjectURL(url);
|
|
1790
1855
|
};
|
|
1856
|
+
const buildStandaloneHtmlDocument = (html) => `<!doctype html>
|
|
1857
|
+
<html lang="en">
|
|
1858
|
+
<head>
|
|
1859
|
+
<meta charset="utf-8">
|
|
1860
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1861
|
+
<title>Smart RTE Export</title>
|
|
1862
|
+
<style>
|
|
1863
|
+
:root {
|
|
1864
|
+
--srte-bg: #ffffff;
|
|
1865
|
+
--srte-text: #111111;
|
|
1866
|
+
--srte-text-muted: #4b5563;
|
|
1867
|
+
--srte-border: #d1d5db;
|
|
1868
|
+
--srte-border-light: #e5e7eb;
|
|
1869
|
+
--srte-accent: #1e90ff;
|
|
1870
|
+
--srte-accent-bg: rgba(30, 144, 255, 0.15);
|
|
1871
|
+
--srte-surface-subtle: #f3f4f6;
|
|
1872
|
+
}
|
|
1873
|
+
html, body {
|
|
1874
|
+
margin: 0;
|
|
1875
|
+
background: var(--srte-bg);
|
|
1876
|
+
color: var(--srte-text);
|
|
1877
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1878
|
+
line-height: 1.6;
|
|
1879
|
+
}
|
|
1880
|
+
body {
|
|
1881
|
+
padding: 32px;
|
|
1882
|
+
}
|
|
1883
|
+
.srte-export {
|
|
1884
|
+
max-width: 960px;
|
|
1885
|
+
margin: 0 auto;
|
|
1886
|
+
}
|
|
1887
|
+
.srte-export h1,
|
|
1888
|
+
.srte-export h2,
|
|
1889
|
+
.srte-export h3,
|
|
1890
|
+
.srte-export h4,
|
|
1891
|
+
.srte-export h5,
|
|
1892
|
+
.srte-export h6 {
|
|
1893
|
+
line-height: 1.3;
|
|
1894
|
+
margin: 1.25em 0 0.5em;
|
|
1895
|
+
font-weight: 700;
|
|
1896
|
+
color: var(--srte-text);
|
|
1897
|
+
}
|
|
1898
|
+
.srte-export p {
|
|
1899
|
+
margin: 0 0 0.85em;
|
|
1900
|
+
}
|
|
1901
|
+
.srte-export blockquote {
|
|
1902
|
+
border-left: 4px solid var(--srte-accent);
|
|
1903
|
+
margin: 0.75em 0;
|
|
1904
|
+
padding: 0.5em 1em;
|
|
1905
|
+
background: var(--srte-surface-subtle);
|
|
1906
|
+
color: var(--srte-text);
|
|
1907
|
+
}
|
|
1908
|
+
.srte-export ul,
|
|
1909
|
+
.srte-export ol {
|
|
1910
|
+
margin: 0.75em 0;
|
|
1911
|
+
padding-left: 1.75em;
|
|
1912
|
+
list-style-position: outside;
|
|
1913
|
+
}
|
|
1914
|
+
.srte-export ul {
|
|
1915
|
+
list-style-type: disc;
|
|
1916
|
+
}
|
|
1917
|
+
.srte-export ol {
|
|
1918
|
+
list-style-type: decimal;
|
|
1919
|
+
}
|
|
1920
|
+
.srte-export li {
|
|
1921
|
+
display: list-item;
|
|
1922
|
+
margin: 0.25em 0;
|
|
1923
|
+
padding-left: 0.25em;
|
|
1924
|
+
}
|
|
1925
|
+
.srte-export table {
|
|
1926
|
+
border-collapse: collapse;
|
|
1927
|
+
width: 100%;
|
|
1928
|
+
margin: 12px 0;
|
|
1929
|
+
}
|
|
1930
|
+
.srte-export td,
|
|
1931
|
+
.srte-export th {
|
|
1932
|
+
border: 1px solid var(--srte-border);
|
|
1933
|
+
padding: 8px;
|
|
1934
|
+
vertical-align: top;
|
|
1935
|
+
text-align: left;
|
|
1936
|
+
}
|
|
1937
|
+
.srte-export th {
|
|
1938
|
+
background: var(--srte-surface-subtle);
|
|
1939
|
+
font-weight: 700;
|
|
1940
|
+
}
|
|
1941
|
+
.srte-export img {
|
|
1942
|
+
max-width: 100%;
|
|
1943
|
+
height: auto;
|
|
1944
|
+
}
|
|
1945
|
+
.srte-export pre,
|
|
1946
|
+
.srte-export code {
|
|
1947
|
+
background: var(--srte-surface-subtle);
|
|
1948
|
+
color: var(--srte-text);
|
|
1949
|
+
border-radius: 4px;
|
|
1950
|
+
}
|
|
1951
|
+
.srte-export pre {
|
|
1952
|
+
padding: 12px;
|
|
1953
|
+
overflow-x: auto;
|
|
1954
|
+
white-space: pre-wrap;
|
|
1955
|
+
}
|
|
1956
|
+
@media print {
|
|
1957
|
+
body {
|
|
1958
|
+
padding: 0;
|
|
1959
|
+
}
|
|
1960
|
+
.srte-export {
|
|
1961
|
+
max-width: none;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
</style>
|
|
1965
|
+
</head>
|
|
1966
|
+
<body>
|
|
1967
|
+
<main class="srte-export">${html || "<p></p>"}</main>
|
|
1968
|
+
</body>
|
|
1969
|
+
</html>`;
|
|
1791
1970
|
const exportHtml = () => {
|
|
1792
1971
|
const html = editableRef.current?.innerHTML || "";
|
|
1793
|
-
downloadText("smart-rte-export.html", html, "text/html");
|
|
1972
|
+
downloadText("smart-rte-export.html", buildStandaloneHtmlDocument(html), "text/html;charset=utf-8");
|
|
1794
1973
|
};
|
|
1795
1974
|
const exportMarkdown = () => {
|
|
1796
1975
|
const html = editableRef.current?.innerHTML || "";
|
|
@@ -2339,6 +2518,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2339
2518
|
const anchor = grid[sr]?.[sc];
|
|
2340
2519
|
if (!anchor)
|
|
2341
2520
|
return;
|
|
2521
|
+
pushEditorHistory();
|
|
2342
2522
|
// Collect content and remove other cells
|
|
2343
2523
|
const contents = [];
|
|
2344
2524
|
const cellsToMerge = getCellsInGridRect(tbody, sr, sc, er, ec);
|
|
@@ -2554,6 +2734,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2554
2734
|
}
|
|
2555
2735
|
};
|
|
2556
2736
|
const runTableCellAction = (cell, action) => {
|
|
2737
|
+
pushEditorHistory();
|
|
2557
2738
|
action(cell);
|
|
2558
2739
|
handleInput();
|
|
2559
2740
|
setTableMenu(null);
|
|
@@ -2577,6 +2758,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2577
2758
|
const cells = getColumnCells(table, colIndex);
|
|
2578
2759
|
if (cells.length === 0)
|
|
2579
2760
|
return;
|
|
2761
|
+
pushEditorHistory();
|
|
2580
2762
|
const firstCell = cells[0];
|
|
2581
2763
|
const currentWidth = firstCell.offsetWidth;
|
|
2582
2764
|
// Unlock table width so it can grow
|
|
@@ -2603,6 +2785,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2603
2785
|
return;
|
|
2604
2786
|
const cells = cellsOfRow(row);
|
|
2605
2787
|
const currentHeight = row.offsetHeight;
|
|
2788
|
+
pushEditorHistory();
|
|
2606
2789
|
tableResizeRef.current = {
|
|
2607
2790
|
type: 'row',
|
|
2608
2791
|
table,
|
|
@@ -3506,6 +3689,20 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3506
3689
|
}
|
|
3507
3690
|
updateActiveState();
|
|
3508
3691
|
}, onKeyDown: (e) => {
|
|
3692
|
+
if ((e.metaKey || e.ctrlKey) && String(e.key).toLowerCase() === "z") {
|
|
3693
|
+
const restored = restoreEditorHistory(e.shiftKey ? "redo" : "undo");
|
|
3694
|
+
if (restored) {
|
|
3695
|
+
e.preventDefault();
|
|
3696
|
+
return;
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
if ((e.metaKey || e.ctrlKey) && String(e.key).toLowerCase() === "y") {
|
|
3700
|
+
const restored = restoreEditorHistory("redo");
|
|
3701
|
+
if (restored) {
|
|
3702
|
+
e.preventDefault();
|
|
3703
|
+
return;
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3509
3706
|
if (formula &&
|
|
3510
3707
|
(e.metaKey || e.ctrlKey) &&
|
|
3511
3708
|
String(e.key).toLowerCase() === "m") {
|
|
@@ -13,10 +13,15 @@ export function MediaManager(props) {
|
|
|
13
13
|
if (!open)
|
|
14
14
|
return;
|
|
15
15
|
setError(null);
|
|
16
|
-
|
|
16
|
+
}, [open]);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!open || activeTab !== "library")
|
|
19
|
+
return;
|
|
20
|
+
const timer = window.setTimeout(() => {
|
|
17
21
|
performSearch();
|
|
18
|
-
}
|
|
19
|
-
|
|
22
|
+
}, 300);
|
|
23
|
+
return () => window.clearTimeout(timer);
|
|
24
|
+
}, [open, activeTab, query]);
|
|
20
25
|
const performSearch = async () => {
|
|
21
26
|
try {
|
|
22
27
|
const items = await adapter.search({ q: query, mimePrefix: "image/" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smartrte-react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "A powerful, feature-rich Rich Text Editor for React with support for tables, mathematical formulas (LaTeX/KaTeX), and media management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|