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
- if (activeTab === "library") {
16
+ }, [open]);
17
+ useEffect(() => {
18
+ if (!open || activeTab !== "library")
19
+ return;
20
+ const timer = window.setTimeout(() => {
17
21
  performSearch();
18
- }
19
- }, [open, activeTab]);
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.5",
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",