smartrte-react 0.2.4 → 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);
|
|
@@ -2528,11 +2708,14 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2528
2708
|
const applyBgToSelection = (hex, fallbackCell) => {
|
|
2529
2709
|
const sel = shouldUseTableSelection(fallbackCell) ? selectionRef.current : null;
|
|
2530
2710
|
if (sel) {
|
|
2531
|
-
getCellsInGridRect(sel.tbody, sel.sr, sel.sc, sel.er, sel.ec)
|
|
2711
|
+
const cells = getCellsInGridRect(sel.tbody, sel.sr, sel.sc, sel.er, sel.ec);
|
|
2712
|
+
clearSelectionDecor();
|
|
2713
|
+
cells.forEach((cell) => {
|
|
2532
2714
|
cell.style.background = hex;
|
|
2533
2715
|
});
|
|
2534
2716
|
}
|
|
2535
2717
|
else if (fallbackCell) {
|
|
2718
|
+
clearSelectionDecor();
|
|
2536
2719
|
fallbackCell.style.background = hex;
|
|
2537
2720
|
}
|
|
2538
2721
|
};
|
|
@@ -2551,6 +2734,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2551
2734
|
}
|
|
2552
2735
|
};
|
|
2553
2736
|
const runTableCellAction = (cell, action) => {
|
|
2737
|
+
pushEditorHistory();
|
|
2554
2738
|
action(cell);
|
|
2555
2739
|
handleInput();
|
|
2556
2740
|
setTableMenu(null);
|
|
@@ -2574,6 +2758,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2574
2758
|
const cells = getColumnCells(table, colIndex);
|
|
2575
2759
|
if (cells.length === 0)
|
|
2576
2760
|
return;
|
|
2761
|
+
pushEditorHistory();
|
|
2577
2762
|
const firstCell = cells[0];
|
|
2578
2763
|
const currentWidth = firstCell.offsetWidth;
|
|
2579
2764
|
// Unlock table width so it can grow
|
|
@@ -2600,6 +2785,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2600
2785
|
return;
|
|
2601
2786
|
const cells = cellsOfRow(row);
|
|
2602
2787
|
const currentHeight = row.offsetHeight;
|
|
2788
|
+
pushEditorHistory();
|
|
2603
2789
|
tableResizeRef.current = {
|
|
2604
2790
|
type: 'row',
|
|
2605
2791
|
table,
|
|
@@ -3503,6 +3689,20 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3503
3689
|
}
|
|
3504
3690
|
updateActiveState();
|
|
3505
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
|
+
}
|
|
3506
3706
|
if (formula &&
|
|
3507
3707
|
(e.metaKey || e.ctrlKey) &&
|
|
3508
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",
|