smartrte-react 0.1.10 → 0.1.13
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/README.md +20 -32
- package/dist/components/ClassicEditor.js +250 -0
- package/package.json +12 -13
package/README.md
CHANGED
|
@@ -330,15 +330,13 @@ The editor comes with built-in styles. You can customize the appearance by wrapp
|
|
|
330
330
|
|
|
331
331
|
- Node.js 18+
|
|
332
332
|
- pnpm 9.10.0+
|
|
333
|
-
- Rust (for WASM compilation)
|
|
334
|
-
- wasm-pack
|
|
335
333
|
|
|
336
334
|
### Setting Up Development Environment
|
|
337
335
|
|
|
338
336
|
1. **Clone the repository**
|
|
339
337
|
|
|
340
338
|
```bash
|
|
341
|
-
git clone https://github.com/
|
|
339
|
+
git clone https://github.com/ayush1852017/smart-rte.git
|
|
342
340
|
cd smart-rte
|
|
343
341
|
```
|
|
344
342
|
|
|
@@ -351,13 +349,7 @@ pnpm install
|
|
|
351
349
|
3. **Build the project**
|
|
352
350
|
|
|
353
351
|
```bash
|
|
354
|
-
# Build WASM core
|
|
355
|
-
pnpm build:wasm
|
|
356
|
-
|
|
357
352
|
# Build TypeScript packages
|
|
358
|
-
pnpm build:ts
|
|
359
|
-
|
|
360
|
-
# Or build everything
|
|
361
353
|
pnpm build
|
|
362
354
|
```
|
|
363
355
|
|
|
@@ -376,19 +368,17 @@ The playground will be available at `http://localhost:5173`
|
|
|
376
368
|
```
|
|
377
369
|
smart-rte/
|
|
378
370
|
├── packages/
|
|
379
|
-
│
|
|
380
|
-
│
|
|
381
|
-
│
|
|
382
|
-
│
|
|
383
|
-
│
|
|
384
|
-
│
|
|
385
|
-
│
|
|
386
|
-
│
|
|
387
|
-
|
|
388
|
-
│
|
|
389
|
-
|
|
390
|
-
│ └── smart_rte_core/
|
|
391
|
-
├── apps/ # Example applications
|
|
371
|
+
│ └── react/ # Main React package (smartrte-react)
|
|
372
|
+
│ ├── src/
|
|
373
|
+
│ │ ├── components/
|
|
374
|
+
│ │ │ ├── ClassicEditor.tsx # Main editor component
|
|
375
|
+
│ │ │ └── MediaManager.tsx # Media management component
|
|
376
|
+
│ │ └── index.ts
|
|
377
|
+
│ ├── playground/ # Development playground
|
|
378
|
+
│ └── package.json
|
|
379
|
+
├── dart/ # Flutter/Dart packages
|
|
380
|
+
│ ├── smartrte_flutter/ # Flutter WebView integration
|
|
381
|
+
│ └── example_app/ # Flutter example
|
|
392
382
|
└── package.json
|
|
393
383
|
```
|
|
394
384
|
|
|
@@ -455,7 +445,7 @@ We welcome contributions! Here's how you can help:
|
|
|
455
445
|
|
|
456
446
|
### Reporting Bugs
|
|
457
447
|
|
|
458
|
-
1. Check if the bug has already been reported in [Issues](https://github.com/
|
|
448
|
+
1. Check if the bug has already been reported in [Issues](https://github.com/ayush1852017/smart-rte/issues)
|
|
459
449
|
2. If not, create a new issue with:
|
|
460
450
|
- Clear title and description
|
|
461
451
|
- Steps to reproduce
|
|
@@ -465,7 +455,7 @@ We welcome contributions! Here's how you can help:
|
|
|
465
455
|
|
|
466
456
|
### Suggesting Features
|
|
467
457
|
|
|
468
|
-
1. Check [existing feature requests](https://github.com/
|
|
458
|
+
1. Check [existing feature requests](https://github.com/ayush1852017/smart-rte/issues?q=is%3Aissue+label%3Aenhancement)
|
|
469
459
|
2. Create a new issue with:
|
|
470
460
|
- Clear description of the feature
|
|
471
461
|
- Use cases
|
|
@@ -611,20 +601,20 @@ SOFTWARE.
|
|
|
611
601
|
|
|
612
602
|
- **Smart RTE Team** - Initial work and maintenance
|
|
613
603
|
|
|
614
|
-
See the list of [contributors](https://github.com/
|
|
604
|
+
See the list of [contributors](https://github.com/ayush1852017/smart-rte/contributors) who participated in this project.
|
|
615
605
|
|
|
616
606
|
## 🙏 Acknowledgments
|
|
617
607
|
|
|
618
608
|
- [KaTeX](https://katex.org/) - For mathematical formula rendering
|
|
619
609
|
- [React](https://reactjs.org/) - The UI library
|
|
620
610
|
- [Vite](https://vitejs.dev/) - Build tool
|
|
621
|
-
- All our amazing [contributors](https://github.com/
|
|
611
|
+
- All our amazing [contributors](https://github.com/ayush1852017/smart-rte/contributors)
|
|
622
612
|
|
|
623
613
|
## 📞 Support
|
|
624
614
|
|
|
625
615
|
- **Documentation:** You're reading it! 📖
|
|
626
|
-
- **Issues:** [GitHub Issues](https://github.com/
|
|
627
|
-
- **Discussions:** [GitHub Discussions](https://github.com/
|
|
616
|
+
- **Issues:** [GitHub Issues](https://github.com/ayush1852017/smart-rte/issues)
|
|
617
|
+
- **Discussions:** [GitHub Discussions](https://github.com/ayush1852017/smart-rte/discussions)
|
|
628
618
|
- **Twitter:** [@smartrte](https://twitter.com/smartrte) (if applicable)
|
|
629
619
|
|
|
630
620
|
## 🗺️ Roadmap
|
|
@@ -658,9 +648,7 @@ See the list of [contributors](https://github.com/yourusername/smart-rte/contrib
|
|
|
658
648
|
|
|
659
649
|
## 🔗 Related Packages
|
|
660
650
|
|
|
661
|
-
-
|
|
662
|
-
- **smartrte-flutter** - Flutter/Dart implementation
|
|
663
|
-
- **smart-rte-core** - Rust core library
|
|
651
|
+
- **smartrte-flutter** - Flutter/Dart WebView implementation
|
|
664
652
|
|
|
665
653
|
## 💡 Tips & Best Practices
|
|
666
654
|
|
|
@@ -696,4 +684,4 @@ See the list of [contributors](https://github.com/yourusername/smart-rte/contrib
|
|
|
696
684
|
|
|
697
685
|
**Happy Editing! 🎉**
|
|
698
686
|
|
|
699
|
-
If you find this package useful, please consider giving it a ⭐ on [GitHub](https://github.com/
|
|
687
|
+
If you find this package useful, please consider giving it a ⭐ on [GitHub](https://github.com/ayush1852017/smart-rte)!
|
|
@@ -11,6 +11,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
11
11
|
const [imageOverlay, setImageOverlay] = useState(null);
|
|
12
12
|
const resizingRef = useRef(null);
|
|
13
13
|
const draggedImageRef = useRef(null);
|
|
14
|
+
const tableResizeRef = useRef(null);
|
|
14
15
|
const [showTableDialog, setShowTableDialog] = useState(false);
|
|
15
16
|
const [tableRows, setTableRows] = useState(3);
|
|
16
17
|
const [tableCols, setTableCols] = useState(3);
|
|
@@ -194,6 +195,124 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
194
195
|
window.removeEventListener("resize", onResize);
|
|
195
196
|
};
|
|
196
197
|
}, []);
|
|
198
|
+
// Table resize event listeners
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
const el = editableRef.current;
|
|
201
|
+
if (!el)
|
|
202
|
+
return;
|
|
203
|
+
addTableResizeHandles();
|
|
204
|
+
const onMouseDown = (e) => {
|
|
205
|
+
if (!table)
|
|
206
|
+
return;
|
|
207
|
+
const target = e.target;
|
|
208
|
+
if (target.tagName === 'TD' || target.tagName === 'TH') {
|
|
209
|
+
const rect = target.getBoundingClientRect();
|
|
210
|
+
const rightEdge = rect.right;
|
|
211
|
+
const clickX = e.clientX;
|
|
212
|
+
if (Math.abs(clickX - rightEdge) < 5) {
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
const tableElem = target.closest('table');
|
|
215
|
+
const colIndex = parseInt(target.getAttribute('data-col-index') || '0', 10);
|
|
216
|
+
if (tableElem) {
|
|
217
|
+
startColumnResize(tableElem, colIndex, e.clientX);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const bottomEdge = rect.bottom;
|
|
222
|
+
const clickY = e.clientY;
|
|
223
|
+
if (Math.abs(clickY - bottomEdge) < 5) {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
const tableElem = target.closest('table');
|
|
226
|
+
const row = target.closest('tr');
|
|
227
|
+
if (tableElem && row) {
|
|
228
|
+
const rowIndex = parseInt(row.getAttribute('data-row-index') || '0', 10);
|
|
229
|
+
startRowResize(tableElem, rowIndex, e.clientY);
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const onMouseMove = (e) => {
|
|
236
|
+
if (tableResizeRef.current) {
|
|
237
|
+
handleTableResizeMove(e);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!table)
|
|
241
|
+
return;
|
|
242
|
+
const target = e.target;
|
|
243
|
+
if (target.tagName === 'TD' || target.tagName === 'TH') {
|
|
244
|
+
const rect = target.getBoundingClientRect();
|
|
245
|
+
const clickX = e.clientX;
|
|
246
|
+
const clickY = e.clientY;
|
|
247
|
+
if (Math.abs(clickX - rect.right) < 5) {
|
|
248
|
+
el.style.cursor = 'col-resize';
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (Math.abs(clickY - rect.bottom) < 5) {
|
|
252
|
+
el.style.cursor = 'row-resize';
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (el.style.cursor === 'col-resize' || el.style.cursor === 'row-resize') {
|
|
256
|
+
el.style.cursor = '';
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
const onMouseUp = () => {
|
|
261
|
+
handleTableResizeEnd();
|
|
262
|
+
};
|
|
263
|
+
const onTouchStart = (e) => {
|
|
264
|
+
if (!table)
|
|
265
|
+
return;
|
|
266
|
+
const target = e.target;
|
|
267
|
+
if (target.tagName === 'TD' || target.tagName === 'TH') {
|
|
268
|
+
const rect = target.getBoundingClientRect();
|
|
269
|
+
const touch = e.touches[0];
|
|
270
|
+
const clickX = touch.clientX;
|
|
271
|
+
const clickY = touch.clientY;
|
|
272
|
+
if (Math.abs(clickX - rect.right) < 15) {
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
const tableElem = target.closest('table');
|
|
275
|
+
const colIndex = parseInt(target.getAttribute('data-col-index') || '0', 10);
|
|
276
|
+
if (tableElem) {
|
|
277
|
+
startColumnResize(tableElem, colIndex, clickX);
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (Math.abs(clickY - rect.bottom) < 15) {
|
|
282
|
+
e.preventDefault();
|
|
283
|
+
const tableElem = target.closest('table');
|
|
284
|
+
const row = target.closest('tr');
|
|
285
|
+
if (tableElem && row) {
|
|
286
|
+
const rowIndex = parseInt(row.getAttribute('data-row-index') || '0', 10);
|
|
287
|
+
startRowResize(tableElem, rowIndex, clickY);
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const onTouchMove = (e) => {
|
|
294
|
+
if (tableResizeRef.current) {
|
|
295
|
+
handleTableResizeMove(e);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const onTouchEnd = () => {
|
|
299
|
+
handleTableResizeEnd();
|
|
300
|
+
};
|
|
301
|
+
el.addEventListener('mousedown', onMouseDown);
|
|
302
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
303
|
+
window.addEventListener('mouseup', onMouseUp);
|
|
304
|
+
el.addEventListener('touchstart', onTouchStart, { passive: false });
|
|
305
|
+
window.addEventListener('touchmove', onTouchMove, { passive: false });
|
|
306
|
+
window.addEventListener('touchend', onTouchEnd);
|
|
307
|
+
return () => {
|
|
308
|
+
el.removeEventListener('mousedown', onMouseDown);
|
|
309
|
+
window.removeEventListener('mousemove', onMouseMove);
|
|
310
|
+
window.removeEventListener('mouseup', onMouseUp);
|
|
311
|
+
el.removeEventListener('touchstart', onTouchStart);
|
|
312
|
+
window.removeEventListener('touchmove', onTouchMove);
|
|
313
|
+
window.removeEventListener('touchend', onTouchEnd);
|
|
314
|
+
};
|
|
315
|
+
}, [table]);
|
|
197
316
|
const insertImageAtSelection = (src) => {
|
|
198
317
|
try {
|
|
199
318
|
const host = editableRef.current;
|
|
@@ -400,6 +519,20 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
400
519
|
if (!node || !range)
|
|
401
520
|
return;
|
|
402
521
|
range.insertNode(node);
|
|
522
|
+
// Add resize handles to the new table
|
|
523
|
+
if (node instanceof HTMLTableElement) {
|
|
524
|
+
const tbody = node.querySelector('tbody');
|
|
525
|
+
if (tbody) {
|
|
526
|
+
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
527
|
+
rows.forEach((row, index) => {
|
|
528
|
+
row.setAttribute('data-row-index', String(index));
|
|
529
|
+
const cells = cellsOfRow(row);
|
|
530
|
+
cells.forEach((cell, cellIndex) => {
|
|
531
|
+
cell.setAttribute('data-col-index', String(cellIndex));
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
403
536
|
// Move caret into first cell
|
|
404
537
|
const firstCell = node.querySelector("td,th");
|
|
405
538
|
if (firstCell)
|
|
@@ -741,6 +874,123 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
741
874
|
applyToggle(fallbackCell);
|
|
742
875
|
}
|
|
743
876
|
};
|
|
877
|
+
// Table column and row resizing functions
|
|
878
|
+
const getColumnCells = (table, colIndex) => {
|
|
879
|
+
const tbody = table.querySelector('tbody');
|
|
880
|
+
if (!tbody)
|
|
881
|
+
return [];
|
|
882
|
+
const cells = [];
|
|
883
|
+
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
884
|
+
rows.forEach(row => {
|
|
885
|
+
const rowCells = cellsOfRow(row);
|
|
886
|
+
if (rowCells[colIndex]) {
|
|
887
|
+
cells.push(rowCells[colIndex]);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
return cells;
|
|
891
|
+
};
|
|
892
|
+
const startColumnResize = (table, colIndex, clientX) => {
|
|
893
|
+
const cells = getColumnCells(table, colIndex);
|
|
894
|
+
if (cells.length === 0)
|
|
895
|
+
return;
|
|
896
|
+
const firstCell = cells[0];
|
|
897
|
+
const currentWidth = firstCell.offsetWidth;
|
|
898
|
+
tableResizeRef.current = {
|
|
899
|
+
type: 'column',
|
|
900
|
+
table,
|
|
901
|
+
index: colIndex,
|
|
902
|
+
startPos: clientX,
|
|
903
|
+
startSize: currentWidth,
|
|
904
|
+
cells,
|
|
905
|
+
};
|
|
906
|
+
document.body.style.cursor = 'col-resize';
|
|
907
|
+
document.body.style.userSelect = 'none';
|
|
908
|
+
};
|
|
909
|
+
const startRowResize = (table, rowIndex, clientY) => {
|
|
910
|
+
const tbody = table.querySelector('tbody');
|
|
911
|
+
if (!tbody)
|
|
912
|
+
return;
|
|
913
|
+
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
914
|
+
const row = rows[rowIndex];
|
|
915
|
+
if (!row)
|
|
916
|
+
return;
|
|
917
|
+
const cells = cellsOfRow(row);
|
|
918
|
+
const currentHeight = row.offsetHeight;
|
|
919
|
+
tableResizeRef.current = {
|
|
920
|
+
type: 'row',
|
|
921
|
+
table,
|
|
922
|
+
index: rowIndex,
|
|
923
|
+
startPos: clientY,
|
|
924
|
+
startSize: currentHeight,
|
|
925
|
+
cells,
|
|
926
|
+
};
|
|
927
|
+
document.body.style.cursor = 'row-resize';
|
|
928
|
+
document.body.style.userSelect = 'none';
|
|
929
|
+
};
|
|
930
|
+
const handleTableResizeMove = (e) => {
|
|
931
|
+
const resize = tableResizeRef.current;
|
|
932
|
+
if (!resize)
|
|
933
|
+
return;
|
|
934
|
+
e.preventDefault();
|
|
935
|
+
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
936
|
+
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
937
|
+
if (resize.type === 'column') {
|
|
938
|
+
const delta = clientX - resize.startPos;
|
|
939
|
+
const newWidth = Math.max(60, resize.startSize + delta);
|
|
940
|
+
resize.cells.forEach(cell => {
|
|
941
|
+
cell.style.width = `${newWidth}px`;
|
|
942
|
+
cell.style.minWidth = `${newWidth}px`;
|
|
943
|
+
cell.style.maxWidth = `${newWidth}px`;
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
else if (resize.type === 'row') {
|
|
947
|
+
const delta = clientY - resize.startPos;
|
|
948
|
+
const newHeight = Math.max(30, resize.startSize + delta);
|
|
949
|
+
const tbody = resize.table.querySelector('tbody');
|
|
950
|
+
if (tbody) {
|
|
951
|
+
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
952
|
+
const row = rows[resize.index];
|
|
953
|
+
if (row) {
|
|
954
|
+
row.style.height = `${newHeight}px`;
|
|
955
|
+
resize.cells.forEach(cell => {
|
|
956
|
+
cell.style.height = `${newHeight}px`;
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
const handleTableResizeEnd = () => {
|
|
963
|
+
if (tableResizeRef.current) {
|
|
964
|
+
tableResizeRef.current = null;
|
|
965
|
+
document.body.style.cursor = '';
|
|
966
|
+
document.body.style.userSelect = '';
|
|
967
|
+
handleInput();
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
const addTableResizeHandles = () => {
|
|
971
|
+
if (!table)
|
|
972
|
+
return;
|
|
973
|
+
const el = editableRef.current;
|
|
974
|
+
if (!el)
|
|
975
|
+
return;
|
|
976
|
+
const tables = el.querySelectorAll('table');
|
|
977
|
+
tables.forEach(tableElem => {
|
|
978
|
+
const tbody = tableElem.querySelector('tbody');
|
|
979
|
+
if (!tbody)
|
|
980
|
+
return;
|
|
981
|
+
const firstRow = tbody.querySelector('tr');
|
|
982
|
+
if (firstRow) {
|
|
983
|
+
const cells = cellsOfRow(firstRow);
|
|
984
|
+
cells.forEach((cell, index) => {
|
|
985
|
+
cell.setAttribute('data-col-index', String(index));
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
989
|
+
rows.forEach((row, index) => {
|
|
990
|
+
row.setAttribute('data-row-index', String(index));
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
};
|
|
744
994
|
return (_jsxs("div", { style: { border: "1px solid #ddd", borderRadius: 6 }, children: [_jsxs("div", { style: {
|
|
745
995
|
display: "flex",
|
|
746
996
|
flexWrap: "wrap",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smartrte-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
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",
|
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
},
|
|
39
39
|
"author": "Smart RTE Contributors",
|
|
40
40
|
"license": "MIT",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.json",
|
|
43
|
+
"prepublishOnly": "pnpm run build",
|
|
44
|
+
"dev": "pnpm build",
|
|
45
|
+
"lint": "eslint . || true",
|
|
46
|
+
"test": "vitest run || true",
|
|
47
|
+
"storybook": "storybook dev -p 6006",
|
|
48
|
+
"build-storybook": "storybook build",
|
|
49
|
+
"e2e": "playwright test || true"
|
|
50
|
+
},
|
|
41
51
|
"publishConfig": {
|
|
42
52
|
"access": "public"
|
|
43
53
|
},
|
|
@@ -59,16 +69,5 @@
|
|
|
59
69
|
"@playwright/test": "^1.48.2",
|
|
60
70
|
"react": "18.3.1",
|
|
61
71
|
"react-dom": "18.3.1"
|
|
62
|
-
},
|
|
63
|
-
"scripts": {
|
|
64
|
-
"build": "tsc -p tsconfig.json",
|
|
65
|
-
"build:embed": "vite build",
|
|
66
|
-
"build:all": "pnpm run build && pnpm run build:embed",
|
|
67
|
-
"dev": "pnpm build",
|
|
68
|
-
"lint": "eslint . || true",
|
|
69
|
-
"test": "vitest run || true",
|
|
70
|
-
"storybook": "storybook dev -p 6006",
|
|
71
|
-
"build-storybook": "storybook build",
|
|
72
|
-
"e2e": "playwright test || true"
|
|
73
72
|
}
|
|
74
|
-
}
|
|
73
|
+
}
|