@oix1987/yjd 1.0.0 → 1.0.2

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.
Files changed (58) hide show
  1. package/README.md +73 -22
  2. package/dist/rich-editor.esm.js +2 -0
  3. package/dist/rich-editor.esm.js.map +1 -0
  4. package/dist/rich-editor.min.js +2 -0
  5. package/dist/rich-editor.min.js.map +1 -0
  6. package/package.json +12 -7
  7. package/index.js +0 -221
  8. package/lib/core/editor.js +0 -1175
  9. package/lib/core/format.js +0 -542
  10. package/lib/core/module.js +0 -81
  11. package/lib/core/registry.js +0 -152
  12. package/lib/formats/background.js +0 -212
  13. package/lib/formats/bold.js +0 -67
  14. package/lib/formats/capitalization.js +0 -563
  15. package/lib/formats/color.js +0 -165
  16. package/lib/formats/emoji.js +0 -282
  17. package/lib/formats/font-family.js +0 -547
  18. package/lib/formats/heading.js +0 -502
  19. package/lib/formats/image.js +0 -344
  20. package/lib/formats/import.js +0 -385
  21. package/lib/formats/indent.js +0 -297
  22. package/lib/formats/italic.js +0 -27
  23. package/lib/formats/line-height.js +0 -558
  24. package/lib/formats/link.js +0 -251
  25. package/lib/formats/list.js +0 -635
  26. package/lib/formats/strike.js +0 -31
  27. package/lib/formats/subscript.js +0 -36
  28. package/lib/formats/superscript.js +0 -35
  29. package/lib/formats/table.js +0 -288
  30. package/lib/formats/tag.js +0 -304
  31. package/lib/formats/text-align.js +0 -421
  32. package/lib/formats/text-size.js +0 -497
  33. package/lib/formats/underline.js +0 -30
  34. package/lib/formats/video.js +0 -372
  35. package/lib/modules/block-toolbar.js +0 -628
  36. package/lib/modules/code-view.js +0 -434
  37. package/lib/modules/history.js +0 -410
  38. package/lib/modules/resize-handles.js +0 -677
  39. package/lib/modules/table-toolbar.js +0 -618
  40. package/lib/modules/toolbar.js +0 -424
  41. package/lib/styles-loader.js +0 -144
  42. package/lib/styles.css +0 -2123
  43. package/lib/ui/color-picker.js +0 -296
  44. package/lib/ui/customselect.js +0 -319
  45. package/lib/ui/emoji-picker.js +0 -196
  46. package/lib/ui/icons.js +0 -413
  47. package/lib/ui/image-popup.js +0 -444
  48. package/lib/ui/import-popup.js +0 -288
  49. package/lib/ui/link-popup.js +0 -191
  50. package/lib/ui/list-picker.js +0 -307
  51. package/lib/ui/select-button.js +0 -61
  52. package/lib/ui/table-popup.js +0 -171
  53. package/lib/ui/tag-popup.js +0 -249
  54. package/lib/ui/text-align-picker.js +0 -281
  55. package/lib/ui/video-popup.js +0 -422
  56. package/lib/utils/history-helper.js +0 -50
  57. package/lib/utils/popup-helper.js +0 -219
  58. package/lib/utils/popup-positioning.js +0 -231
@@ -1,618 +0,0 @@
1
- import Module from '../core/module.js';
2
- import IconUtils from '../ui/icons.js';
3
-
4
- /**
5
- * Table Toolbar Module - Floating toolbar hiện lên khi click vào table
6
- */
7
- class TableToolbar extends Module {
8
- static DEFAULTS = {
9
- fadeDelay: 3000, // Auto hide after 3 seconds of inactivity
10
- buttons: ['tableProfile', 'deleteTable', 'insertRowAbove', 'insertRowBelow', 'deleteRow', 'insertColRight', 'insertColLeft', 'deleteCol']
11
- };
12
-
13
- constructor(editor, options = {}) {
14
- super(editor, options);
15
- this.tableToolbar = null;
16
- this.currentTable = null;
17
- this.currentCell = null;
18
- this.hideTimeout = null;
19
- this.isVisible = false;
20
-
21
- this.init();
22
- }
23
-
24
- async init() {
25
- await this.createTableToolbar();
26
- this.setupEventListeners();
27
- }
28
-
29
-
30
-
31
- /**
32
- * Tạo table toolbar element
33
- */
34
- async createTableToolbar() {
35
- this.tableToolbar = document.createElement('div');
36
- this.tableToolbar.className = 'table-toolbar';
37
-
38
- // Create toolbar container
39
- const toolbarContainer = document.createElement('div');
40
- toolbarContainer.className = 'table-toolbar-container';
41
-
42
- // Define button groups
43
- const buttonGroups = [
44
- {
45
- name: 'table-actions',
46
- buttons: [
47
- { cmd: 'tableProfile', icon: 'icon-table-profile', title: 'Table Profile' },
48
- { cmd: 'deleteTable', icon: 'icon-delete-table', title: 'Delete Table' }
49
- ]
50
- },
51
- {
52
- name: 'row-actions',
53
- buttons: [
54
- { cmd: 'insertRowAbove', icon: 'icon-add-row-above', title: 'Add Row Above' },
55
- { cmd: 'insertRowBelow', icon: 'icon-add-row-below', title: 'Add Row Below' },
56
- { cmd: 'deleteRow', icon: 'icon-delete-row', title: 'Delete Selected Row' }
57
- ]
58
- },
59
- {
60
- name: 'col-actions',
61
- buttons: [
62
- { cmd: 'insertColRight', icon: 'icon-add-col-right', title: 'Add Column Right' },
63
- { cmd: 'insertColLeft', icon: 'icon-add-col-left', title: 'Add Column Left' },
64
- { cmd: 'deleteCol', icon: 'icon-delete-col', title: 'Delete Selected Column' }
65
- ]
66
- }
67
- ];
68
-
69
- // Create groups
70
- for (const group of buttonGroups) {
71
- const groupDiv = document.createElement('div');
72
- groupDiv.className = `table-toolbar-group ${group.name}`;
73
-
74
- // Create buttons in this group
75
- for (const { cmd, icon, title } of group.buttons) {
76
- const button = document.createElement('button');
77
- button.className = 'table-toolbar-btn';
78
- button.title = title;
79
- button.dataset.command = cmd;
80
-
81
- // Load and set SVG icon using IconUtils
82
- const svgContent = IconUtils.getIcon(icon.replace('icon-', ''));
83
- if (svgContent) {
84
- button.innerHTML = svgContent;
85
- }
86
-
87
- button.addEventListener('click', (e) => {
88
- e.preventDefault();
89
- e.stopPropagation();
90
- this.handleCommand(cmd, button);
91
- });
92
-
93
- groupDiv.appendChild(button);
94
- }
95
-
96
- toolbarContainer.appendChild(groupDiv);
97
- }
98
-
99
- // Create arrow element
100
- const arrow = document.createElement('div');
101
- arrow.className = 'table-toolbar-arrow';
102
-
103
- // Add container and arrow to toolbar
104
- this.tableToolbar.appendChild(toolbarContainer);
105
- this.tableToolbar.appendChild(arrow);
106
-
107
- // Add to editor wrapper
108
- this.editor.wrapper.appendChild(this.tableToolbar);
109
- }
110
-
111
- /**
112
- * Setup event listeners
113
- */
114
- setupEventListeners() {
115
- // Listen for clicks on table cells
116
- this.editor.editor.addEventListener('click', (e) => {
117
- const clickedCell = e.target.closest('td, th');
118
- const clickedTable = e.target.closest('table');
119
-
120
- if (clickedTable && clickedCell) {
121
- // Check if the clicked table is within the editable area
122
- const isInEditableArea = this.editor.isNodeInEditableArea ?
123
- this.editor.isNodeInEditableArea(clickedTable) : true;
124
-
125
- if (!isInEditableArea) {
126
- this.hide();
127
- return;
128
- }
129
-
130
- this.currentTable = clickedTable;
131
- this.currentCell = clickedCell;
132
- this.showAtTable(clickedTable);
133
- } else {
134
- this.hide();
135
- }
136
- });
137
-
138
- // Hide on outside click
139
- document.addEventListener('mousedown', (e) => {
140
- if (!e.target.closest('.table-toolbar') && !e.target.closest('table')) {
141
- this.hide();
142
- }
143
- });
144
-
145
- // Update scroll event listeners to track position instead of hiding
146
- window.addEventListener('scroll', () => {
147
- if (this.isVisible && this.currentTable) {
148
- this.updateToolbarPosition();
149
- }
150
- });
151
-
152
- // Add editor scroll listener
153
- this.editor.editor.addEventListener('scroll', () => {
154
- if (this.isVisible && this.currentTable) {
155
- this.updateToolbarPosition();
156
- }
157
- });
158
-
159
- // Update when selection changes within table
160
- this.editor.editor.addEventListener('keyup', () => {
161
- if (this.isVisible && this.currentTable) {
162
- const selection = window.getSelection();
163
- if (selection && selection.rangeCount > 0) {
164
- const currentCell = selection.getRangeAt(0).startContainer;
165
- const tableCell = currentCell.nodeType === Node.TEXT_NODE
166
- ? currentCell.parentElement.closest('td, th')
167
- : currentCell.closest('td, th');
168
-
169
- if (tableCell && tableCell !== this.currentCell) {
170
- // Verify the table cell is still in editable area
171
- const isInEditableArea = this.editor.isNodeInEditableArea ?
172
- this.editor.isNodeInEditableArea(tableCell) : true;
173
-
174
- if (!isInEditableArea) {
175
- this.hide();
176
- return;
177
- }
178
-
179
- this.currentCell = tableCell;
180
- }
181
- }
182
- }
183
- });
184
- }
185
-
186
- /**
187
- * Update toolbar position based on current table
188
- */
189
- updateToolbarPosition() {
190
- if (!this.isVisible || !this.currentTable) return;
191
-
192
- // Check if table is still in DOM and in editable area
193
- if (!document.body.contains(this.currentTable)) {
194
- this.hide();
195
- return;
196
- }
197
-
198
- const isInEditableArea = this.editor.isNodeInEditableArea ?
199
- this.editor.isNodeInEditableArea(this.currentTable) : true;
200
-
201
- if (!isInEditableArea) {
202
- this.hide();
203
- return;
204
- }
205
-
206
- // Update position based on current table position
207
- const rect = this.currentTable.getBoundingClientRect();
208
- const editorRect = this.editor.wrapper.getBoundingClientRect();
209
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
210
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
211
-
212
- this.updateToolbarAt(
213
- rect.left - editorRect.left + scrollLeft,
214
- rect.top - editorRect.top + scrollTop,
215
- rect.width,
216
- rect.height
217
- );
218
- }
219
-
220
- /**
221
- * Update toolbar position at specific coordinates
222
- */
223
- updateToolbarAt(x, y, width, height) {
224
- if (!this.tableToolbar) return;
225
-
226
- this.ensureToolbarInViewport(x, y, width, height);
227
- }
228
-
229
- /**
230
- * Show toolbar at table position
231
- */
232
- showAtTable(table) {
233
- if (!table || !this.tableToolbar) return;
234
-
235
- const rect = table.getBoundingClientRect();
236
- const editorRect = this.editor.wrapper.getBoundingClientRect();
237
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
238
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
239
- this.showAt(
240
- rect.left - editorRect.left + scrollLeft,
241
- rect.top - editorRect.top + scrollTop,
242
- rect.width,
243
- rect.height
244
- );
245
- }
246
-
247
- /**
248
- * Show toolbar at specific position
249
- */
250
- showAt(x, y, width, height) {
251
- if (!this.tableToolbar) return;
252
-
253
- this.tableToolbar.classList.add('visible');
254
- this.isVisible = true;
255
-
256
- this.ensureToolbarInViewport(x, y, width, height);
257
- this.clearHideTimeout();
258
- }
259
-
260
- /**
261
- * Ensure toolbar stays within viewport and editor bounds
262
- */
263
- ensureToolbarInViewport(x, y, width, height) {
264
- if (!this.tableToolbar) return;
265
-
266
- // Lấy thông tin về editor-area
267
- const editorArea = this.editor.editor;
268
- const editorRect = editorArea.getBoundingClientRect();
269
- const toolbarRect = this.tableToolbar.getBoundingClientRect();
270
- const toolbarContainer = this.editor.wrapper.querySelector('.rich-editor-toolbar-container');
271
- const toolbarRect2 = toolbarContainer ? toolbarContainer.getBoundingClientRect() : null;
272
- let left = x+width/2 - this.tableToolbar.offsetWidth/2;
273
- let top = y - 60- document.documentElement.scrollTop;
274
- let arrowLeft = '50%';
275
- let arrowDirection = 'down'; // mũi tên hướng xuống
276
-
277
- // Trường hợp 1: Vượt quá lề trái của editor
278
- if (left < 0) {
279
- left = (x - (this.tableToolbar.offsetWidth * (10/100)));
280
- if(left < 0) left = 0;
281
- arrowLeft = '10%'; // Mũi tên ở 10%
282
- }
283
-
284
- // Trường hợp 2: Vượt quá lề phải của editor
285
- if (left + this.tableToolbar.offsetWidth > (this.editor.wrapper.offsetWidth - 2)) {
286
- left = x - this.tableToolbar.offsetWidth*0.9;
287
- arrowLeft = '90%'; // Mũi tên ở 90%
288
- }
289
-
290
- // Trường hợp 3: Vượt quá lề trên của editor
291
- if (top < (toolbarRect2 ? toolbarRect2.height : 48)) {
292
- top = y + height +10 - document.documentElement.scrollTop;
293
- arrowDirection = 'up'; // Mũi tên hướng lên
294
- if(top < (toolbarRect2 ? toolbarRect2.height : 48)){
295
- this.hide();
296
- return;
297
- }
298
- }
299
- if(top > editorRect.height){
300
- this.hide();
301
- return;
302
- }
303
- // Cập nhật vị trí mũi tên (nếu có)
304
- const arrow = this.tableToolbar.querySelector('.table-toolbar-arrow');
305
- if (arrow) {
306
- arrow.style.left = arrowLeft;
307
-
308
- if (arrowDirection === 'up') {
309
- // Mũi tên hướng lên
310
- arrow.style.bottom = 'auto';
311
- arrow.style.top = '-8px';
312
- arrow.style.borderTop = 'none';
313
- arrow.style.borderBottom = '8px solid #fff';
314
- arrow.style.borderLeft = '6px solid transparent';
315
- arrow.style.borderRight = '6px solid transparent';
316
- } else {
317
- // Mũi tên hướng xuống (mặc định)
318
- arrow.style.top = 'auto';
319
- arrow.style.bottom = '-8px';
320
- arrow.style.borderBottom = 'none';
321
- arrow.style.borderTop = '8px solid #fff';
322
- arrow.style.borderLeft = '6px solid transparent';
323
- arrow.style.borderRight = '6px solid transparent';
324
- }
325
- }
326
- // Áp dụng vị trí cuối cùng
327
- this.tableToolbar.style.left = left + 'px';
328
- this.tableToolbar.style.top = top + 'px';
329
- }
330
-
331
- /**
332
- * Hide toolbar
333
- */
334
- hide() {
335
- if (!this.tableToolbar || !this.isVisible) return;
336
-
337
- this.tableToolbar.classList.remove('visible');
338
- this.isVisible = false;
339
- this.currentTable = null;
340
- this.currentCell = null;
341
- this.clearHideTimeout();
342
-
343
- }
344
-
345
- /**
346
- * Clear hide timeout
347
- */
348
- clearHideTimeout() {
349
- if (this.hideTimeout) {
350
- clearTimeout(this.hideTimeout);
351
- this.hideTimeout = null;
352
- }
353
- }
354
-
355
- /**
356
- * Handle command execution - only if table is in editable area
357
- */
358
- handleCommand(command, button) {
359
-
360
- if (!this.currentTable || !this.currentCell) {
361
- return;
362
- }
363
-
364
- // Double check that the table is still in editable area before executing command
365
- const isInEditableArea = this.editor.isNodeInEditableArea ?
366
- this.editor.isNodeInEditableArea(this.currentTable) : true;
367
-
368
- if (!isInEditableArea) {
369
- this.hide();
370
- return;
371
- }
372
-
373
- switch (command) {
374
- case 'tableProfile':
375
- this.showTableProfile();
376
- break;
377
- case 'deleteTable':
378
- this.deleteTable();
379
- break;
380
- case 'insertRowAbove':
381
- this.insertRowAbove();
382
- break;
383
- case 'insertRowBelow':
384
- this.insertRowBelow();
385
- break;
386
- case 'deleteRow':
387
- this.deleteRow();
388
- break;
389
- case 'insertColRight':
390
- this.insertColumnRight();
391
- break;
392
- case 'insertColLeft':
393
- this.insertColumnLeft();
394
- break;
395
- case 'deleteCol':
396
- this.deleteColumn();
397
- break;
398
- }
399
-
400
- this.editor.focus();
401
- }
402
-
403
- /**
404
- * Insert row above current cell
405
- */
406
- insertRowAbove() {
407
- const currentRow = this.currentCell.parentElement;
408
- const newRow = this.createNewRow(currentRow.cells.length);
409
- currentRow.parentElement.insertBefore(newRow, currentRow);
410
-
411
- // Update resize handles if they exist
412
- if (this.editor.resizeHandles) {
413
- this.editor.resizeHandles.checkAndUpdateHandles();
414
- }
415
- }
416
-
417
- /**
418
- * Insert row below current cell
419
- */
420
- insertRowBelow() {
421
- const currentRow = this.currentCell.parentElement;
422
- const newRow = this.createNewRow(currentRow.cells.length);
423
-
424
- if (currentRow.nextElementSibling) {
425
- currentRow.parentElement.insertBefore(newRow, currentRow.nextElementSibling);
426
- } else {
427
- currentRow.parentElement.appendChild(newRow);
428
- }
429
-
430
- // Update resize handles if they exist
431
- if (this.editor.resizeHandles) {
432
- this.editor.resizeHandles.checkAndUpdateHandles();
433
- }
434
- }
435
-
436
- /**
437
- * Insert column left of current cell
438
- */
439
- insertColumnLeft() {
440
- const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);
441
- const tbody = this.currentTable.querySelector('tbody') || this.currentTable;
442
- const rows = tbody.querySelectorAll('tr');
443
-
444
- rows.forEach(row => {
445
- const newCell = this.createNewCell();
446
- const targetCell = row.children[cellIndex];
447
- if (targetCell) {
448
- row.insertBefore(newCell, targetCell);
449
- } else {
450
- row.appendChild(newCell);
451
- }
452
- });
453
-
454
- // Update resize handles if they exist
455
- if (this.editor.resizeHandles) {
456
- this.editor.resizeHandles.checkAndUpdateHandles();
457
- }
458
- }
459
-
460
- /**
461
- * Insert column right of current cell
462
- */
463
- insertColumnRight() {
464
- const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);
465
- const tbody = this.currentTable.querySelector('tbody') || this.currentTable;
466
- const rows = tbody.querySelectorAll('tr');
467
-
468
- rows.forEach(row => {
469
- const newCell = this.createNewCell();
470
- const targetCell = row.children[cellIndex + 1];
471
- if (targetCell) {
472
- row.insertBefore(newCell, targetCell);
473
- } else {
474
- row.appendChild(newCell);
475
- }
476
- });
477
-
478
- // Update resize handles if they exist
479
- if (this.editor.resizeHandles) {
480
- this.editor.resizeHandles.checkAndUpdateHandles();
481
- }
482
- }
483
-
484
- /**
485
- * Delete current row
486
- */
487
- deleteRow() {
488
- const currentRow = this.currentCell.parentElement;
489
- const tbody = currentRow.parentElement;
490
-
491
- // Don't delete if it's the only row
492
- if (tbody.children.length <= 1) {
493
- return;
494
- }
495
-
496
- currentRow.remove();
497
-
498
- // Update resize handles if they exist
499
- if (this.editor.resizeHandles) {
500
- this.editor.resizeHandles.checkAndUpdateHandles();
501
- }
502
-
503
- this.hide();
504
- }
505
-
506
- /**
507
- * Delete current column
508
- */
509
- deleteColumn() {
510
- const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);
511
- const tbody = this.currentTable.querySelector('tbody') || this.currentTable;
512
- const rows = tbody.querySelectorAll('tr');
513
-
514
- // Don't delete if it's the only column
515
- if (this.currentCell.parentElement.children.length <= 1) {
516
- console.warn('Cannot delete the only column in table');
517
- return;
518
- }
519
-
520
- rows.forEach(row => {
521
- if (row.children[cellIndex]) {
522
- row.children[cellIndex].remove();
523
- }
524
- });
525
-
526
- // Update resize handles if they exist
527
- if (this.editor.resizeHandles) {
528
- this.editor.resizeHandles.checkAndUpdateHandles();
529
- }
530
-
531
- this.hide();
532
- }
533
-
534
- /**
535
- * Show table profile/properties
536
- */
537
- showTableProfile() {
538
- if (!this.currentTable) return;
539
-
540
- const rows = this.currentTable.querySelectorAll('tr').length;
541
- const cols = this.currentTable.querySelector('tr') ?
542
- this.currentTable.querySelector('tr').querySelectorAll('td, th').length : 0;
543
-
544
- const tableInfo = {
545
- rows: rows,
546
- columns: cols,
547
- totalCells: rows * cols,
548
- tableWidth: this.currentTable.offsetWidth,
549
- tableHeight: this.currentTable.offsetHeight
550
- };
551
-
552
- // You can customize this to show a modal or popup with table information
553
- alert(`Table Profile:\n` +
554
- `Rows: ${tableInfo.rows}\n` +
555
- `Columns: ${tableInfo.columns}\n` +
556
- `Total Cells: ${tableInfo.totalCells}\n` +
557
- `Width: ${tableInfo.tableWidth}px\n` +
558
- `Height: ${tableInfo.tableHeight}px`);
559
-
560
- }
561
-
562
- /**
563
- * Delete entire table
564
- */
565
- deleteTable() {
566
- // Hide resize handles before removing table
567
- if (this.editor.resizeHandles) {
568
- this.editor.resizeHandles.hideHandles();
569
- }
570
-
571
- this.currentTable.remove();
572
- this.hide();
573
- }
574
-
575
- /**
576
- * Create new row with specified number of cells
577
- */
578
- createNewRow(cellCount) {
579
- const row = document.createElement('tr');
580
- for (let i = 0; i < cellCount; i++) {
581
- row.appendChild(this.createNewCell());
582
- }
583
- return row;
584
- }
585
-
586
- /**
587
- * Create new cell
588
- */
589
- createNewCell() {
590
- const cell = document.createElement('td');
591
- cell.innerHTML = '&nbsp;';
592
- cell.style.minWidth = '50px';
593
- cell.style.minHeight = '24px';
594
- cell.style.padding = '4px 8px';
595
- cell.style.border = '1px solid #ddd';
596
- cell.style.verticalAlign = 'top';
597
- cell.contentEditable = 'true';
598
- return cell;
599
- }
600
-
601
- /**
602
- * Destroy module
603
- */
604
- destroy() {
605
- if (this.tableToolbar && this.tableToolbar.parentNode) {
606
- this.tableToolbar.parentNode.removeChild(this.tableToolbar);
607
- }
608
-
609
- this.clearHideTimeout();
610
- this.tableToolbar = null;
611
- this.currentTable = null;
612
- this.currentCell = null;
613
- this.isVisible = false;
614
-
615
- }
616
- }
617
-
618
- export default TableToolbar;