@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,628 +0,0 @@
1
- import Module from '../core/module.js';
2
- import IconUtils from '../ui/icons.js';
3
-
4
- /**
5
- * Block Toolbar Module - Floating toolbar hiện lên khi select text hoặc ấn Enter
6
- */
7
- class BlockToolbar extends Module {
8
- static DEFAULTS = {
9
- showOnSelection: true,
10
- showOnEnter: true,
11
- buttons: ['bold', 'italic', 'underline', 'strike', 'code']
12
- };
13
-
14
- constructor(editor, options = {}) {
15
- super(editor, options);
16
- this.blockToolbar = null;
17
- this.isVisible = false;
18
- this.currentSelection = null; // Store current selection for scroll updates
19
- this.currentCursorPosition = null; // Store current cursor position for scroll updates
20
- this.originalTags = new Map(); // Store original tags before converting to code
21
- this.init();
22
- }
23
-
24
- init() {
25
- this.preloadIcons();
26
- this.createBlockToolbar();
27
- this.setupEventListeners();
28
- }
29
-
30
- async preloadIcons() {
31
- // Icons are now inline, no need to preload
32
- // This method is kept for backward compatibility
33
- }
34
-
35
- createBlockToolbar() {
36
- this.blockToolbar = document.createElement('div');
37
- this.blockToolbar.className = 'block-toolbar';
38
-
39
- // Create toolbar container
40
- const toolbarContainer = document.createElement('div');
41
- toolbarContainer.className = 'block-toolbar-container';
42
-
43
- const buttons = [
44
- { cmd: 'bold', icon: 'bold', title: 'Bold (Ctrl+B)' },
45
- { cmd: 'italic', icon: 'italic', title: 'Italic (Ctrl+I)' },
46
- { cmd: 'underline', icon: 'underline', title: 'Underline (Ctrl+U)' },
47
- { cmd: 'strike', icon: 'strike', title: 'Strikethrough' },
48
- { cmd: 'code', icon: 'code', title: 'Code' },
49
- { cmd: 'font-family', icon: 'heading', title: 'Font Family' }
50
- ];
51
- buttons.forEach(({ cmd, icon, title }) => {
52
- const button = document.createElement('button');
53
- button.className = 'block-toolbar-btn';
54
- button.title = title;
55
- button.dataset.command = cmd;
56
- const iconElement = IconUtils.createIconElement(icon, { width: '16px', height: '16px' });
57
- button.appendChild(iconElement);
58
- button.addEventListener('click', (e) => {
59
- e.preventDefault();
60
- e.stopPropagation();
61
- this.handleCommand(cmd, button);
62
- });
63
- toolbarContainer.appendChild(button);
64
- });
65
-
66
- // Create arrow element
67
- const arrow = document.createElement('div');
68
- arrow.className = 'block-toolbar-arrow';
69
-
70
- // Add container and arrow to toolbar
71
- this.blockToolbar.appendChild(toolbarContainer);
72
- this.blockToolbar.appendChild(arrow);
73
-
74
- this.editor.wrapper.appendChild(this.blockToolbar);
75
- }
76
-
77
- setupEventListeners() {
78
- if (this.options.showOnSelection) {
79
- this.editor.editor.addEventListener('mouseup', () => {
80
- setTimeout(() => this.handleSelectionChange(), 0);
81
- });
82
- }
83
- if (this.options.showOnEnter) {
84
- this.editor.editor.addEventListener('keydown', (e) => {
85
- if (e.key === 'Enter' && !e.shiftKey) {
86
- requestAnimationFrame(() => {
87
- setTimeout(() => this.showAtCursorAfterEnter(), 10);
88
- });
89
- }
90
- else{
91
- this.hide();
92
- }
93
- });
94
- }
95
-
96
- document.addEventListener('mousedown', (e) => {
97
- // Don't hide if clicking on font-family popup or its items
98
- if (e.target.closest('.font-family-select-popup') || e.target.closest('.custom-select-popup')) {
99
- return;
100
- }
101
-
102
- if (!e.target.closest('.block-toolbar') && !e.target.closest('.rich-editor-area')) {
103
- this.hide();
104
- }
105
- });
106
-
107
- // Update scroll event listeners to track position instead of hiding
108
- window.addEventListener('scroll', () => {
109
- if (this.isVisible) {
110
- this.updateToolbarPosition();
111
- }
112
- });
113
-
114
- // Add editor scroll listener
115
- this.editor.editor.addEventListener('scroll', () => {
116
- if (this.isVisible) {
117
- this.updateToolbarPosition();
118
- }
119
- });
120
-
121
- this.editor.editor.addEventListener('keyup', (e) => {
122
- // Nếu là Shift + Enter thì ẩn toolbar
123
- if (e.key === 'Enter' && e.shiftKey) {
124
- this.hide();
125
- return;
126
- }
127
-
128
- if (this.isVisible) {
129
- this.updateButtonStates();
130
- } else {
131
- this.handleSelectionChange();
132
- }
133
- });
134
- }
135
-
136
- handleSelectionChange() {
137
- const selection = window.getSelection();
138
- if (!selection || selection.rangeCount === 0) return this.hide();
139
- const range = selection.getRangeAt(0);
140
- const isInEditableArea = this.editor.isSelectionInEditableArea ?
141
- this.editor.isSelectionInEditableArea(selection) :
142
- this.editor.editor.contains(range.commonAncestorContainer);
143
- if (!isInEditableArea) return this.hide();
144
- if (!range.collapsed && selection.toString().trim().length > 0) {
145
- this.showAtSelection(selection);
146
- } else {
147
- this.hide();
148
- }
149
- }
150
-
151
- showAtSelection(selection) {
152
- if (!selection || selection.rangeCount === 0) return;
153
-
154
- // Store current selection for scroll updates
155
- this.currentSelection = selection;
156
- this.currentCursorPosition = null;
157
-
158
- const range = selection.getRangeAt(0);
159
- const rect = range.getBoundingClientRect();
160
- const editorRect = this.editor.wrapper.getBoundingClientRect();
161
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
162
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
163
- this.showAt(
164
- rect.left + rect.width / 2 - editorRect.left + scrollLeft,
165
- rect.top - editorRect.top + scrollTop - 10
166
- );
167
- }
168
-
169
- showAtCursorAfterEnter() {
170
- this.editor.focus();
171
- const selection = window.getSelection();
172
- if (!selection || selection.rangeCount === 0) return;
173
- const range = selection.getRangeAt(0);
174
- const isInEditableArea = this.editor.isSelectionInEditableArea ?
175
- this.editor.isSelectionInEditableArea(selection) :
176
- this.editor.editor.contains(range.commonAncestorContainer);
177
- if (!isInEditableArea) return;
178
-
179
- this.ensureCursorAtEndOfLine(range);
180
-
181
- // Store current cursor position for scroll updates
182
- this.currentSelection = selection;
183
- this.currentCursorPosition = this.getCursorPositionAfterEnter();
184
-
185
- const rect = this.currentCursorPosition;
186
- if (!rect) return;
187
- const editorRect = this.editor.wrapper.getBoundingClientRect();
188
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
189
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
190
- this.showAt(
191
- rect.left - editorRect.left + scrollLeft,
192
- rect.top - editorRect.top + scrollTop - 10
193
- );
194
- }
195
-
196
- ensureCursorAtEndOfLine(range) {
197
- if (!range.collapsed) return;
198
- const selection = window.getSelection();
199
- const currentNode = range.startContainer;
200
- if (currentNode.nodeType === Node.TEXT_NODE) {
201
- const textLength = currentNode.textContent.length;
202
- if (range.startOffset < textLength) {
203
- range.setStart(currentNode, textLength);
204
- range.setEnd(currentNode, textLength);
205
- selection.removeAllRanges();
206
- selection.addRange(range);
207
- }
208
- } else if (currentNode.nodeType === Node.ELEMENT_NODE) {
209
- const walker = document.createTreeWalker(currentNode, NodeFilter.SHOW_TEXT, null, false);
210
- let lastTextNode = null, node;
211
- while (node = walker.nextNode()) lastTextNode = node;
212
- if (lastTextNode) {
213
- const textLength = lastTextNode.textContent.length;
214
- range.setStart(lastTextNode, textLength);
215
- range.setEnd(lastTextNode, textLength);
216
- selection.removeAllRanges();
217
- selection.addRange(range);
218
- }
219
- }
220
- }
221
-
222
- getCursorPositionAfterEnter() {
223
- const selection = window.getSelection();
224
- if (!selection || selection.rangeCount === 0) return null;
225
- const range = selection.getRangeAt(0);
226
- const marker = document.createElement('span');
227
- marker.innerHTML = '&#8203;';
228
- marker.style.position = 'absolute';
229
- marker.style.visibility = 'hidden';
230
- marker.style.pointerEvents = 'none';
231
- range.insertNode(marker);
232
- const rect = marker.getBoundingClientRect();
233
- if (marker.parentNode) marker.parentNode.removeChild(marker);
234
- const newRange = document.createRange();
235
- newRange.setStart(range.startContainer, range.startOffset);
236
- newRange.collapse(true);
237
- selection.removeAllRanges();
238
- selection.addRange(newRange);
239
- return rect;
240
- }
241
-
242
- showAtCursor() {
243
- const selection = window.getSelection();
244
- if (!selection || selection.rangeCount === 0) return;
245
- const range = selection.getRangeAt(0);
246
- const isInEditableArea = this.editor.isSelectionInEditableArea ?
247
- this.editor.isSelectionInEditableArea(selection) :
248
- this.editor.editor.contains(range.commonAncestorContainer);
249
- if (!isInEditableArea) return;
250
- let rect;
251
- if (range.collapsed) {
252
- const span = document.createElement('span');
253
- span.innerHTML = '&#8203;';
254
- span.style.position = 'absolute';
255
- span.style.visibility = 'hidden';
256
- span.style.pointerEvents = 'none';
257
- range.insertNode(span);
258
- rect = span.getBoundingClientRect();
259
- if (span.parentNode) span.parentNode.removeChild(span);
260
- const newRange = document.createRange();
261
- newRange.setStart(range.startContainer, range.startOffset);
262
- newRange.collapse(true);
263
- selection.removeAllRanges();
264
- selection.addRange(newRange);
265
- } else {
266
- rect = range.getBoundingClientRect();
267
- }
268
- const editorRect = this.editor.wrapper.getBoundingClientRect();
269
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
270
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
271
- this.showAt(
272
- rect.left - editorRect.left + scrollLeft,
273
- rect.top - editorRect.top + scrollTop - 10
274
- );
275
- }
276
-
277
- showAt(x, y) {
278
- if (!this.blockToolbar) return;
279
- this.blockToolbar.classList.add('visible');
280
- this.isVisible = true;
281
- this.ensureToolbarInViewport(x,y);
282
- this.updateButtonStates();
283
- }
284
-
285
- ensureToolbarInViewport(x,y) {
286
- if (!this.blockToolbar) return;
287
-
288
- // Lấy thông tin về editor-area
289
- const editorArea = this.editor.editor;
290
- const editorRect = editorArea.getBoundingClientRect();
291
- const toolbarRect = this.blockToolbar.getBoundingClientRect();
292
- const toolbarContainer = this.editor.wrapper.querySelector('.rich-editor-toolbar-container');
293
- const toolbarRect2 = toolbarContainer ? toolbarContainer.getBoundingClientRect() : null;
294
-
295
-
296
- let left = x - this.blockToolbar.offsetWidth/2;
297
- let top = editorRect.y + y -(toolbarRect2.height) - editorArea.scrollTop - (editorRect.y + window.scrollY) +toolbarContainer.offsetHeight-49;
298
- console.log('toolbarContainer.offsetHeight', toolbarContainer.offsetHeight);
299
- console.log('editorRect.y', editorRect.y);
300
- console.log('y', y);
301
- console.log('toolbarRect2.height', toolbarRect2.height);
302
- console.log('editorArea.scrollTop', editorArea.scrollTop);
303
- console.log('top', top);
304
- console.log('window.scrollY', window.scrollY);
305
-
306
- let arrowLeft = '50%';
307
- let arrowDirection = 'down'; // mũi tên hướng xuống
308
-
309
- // Trường hợp 1: Vượt quá lề trái của editor
310
- if (left < 0) {
311
- left =(x - (this.blockToolbar.offsetWidth * (10/100)));
312
- if(left < 0) left = 0;
313
- arrowLeft = '10%'; // Mũi tên ở 10%
314
- }
315
- // Trường hợp 2: Vượt quá lề phải của editor
316
- if (left + this.blockToolbar.offsetWidth > (this.editor.wrapper.offsetWidth - 2)) {
317
- left = x - this.blockToolbar.offsetWidth*0.9;
318
- arrowLeft = '90%'; // Mũi tên ở 90%
319
- }
320
-
321
- // Trường hợp 3: Vượt quá lề trên của editor
322
-
323
- if (top < toolbarRect2.height) {
324
- top = editorRect.y + y -(toolbarRect2.height) - editorArea.scrollTop +100 - (editorRect.y + window.scrollY)+toolbarContainer.offsetHeight-49;
325
- arrowDirection = 'up'; // Mũi tên hướng lên
326
- if(top < toolbarRect2.height ){
327
- this.hide();
328
- return;
329
- }
330
- }
331
- if(top > editorRect.height){
332
- this.hide();
333
- return;
334
- }
335
- // Cập nhật vị trí mũi tên
336
- const arrow = this.blockToolbar.querySelector('.block-toolbar-arrow');
337
- if (arrow) {
338
- arrow.style.left = arrowLeft;
339
-
340
- if (arrowDirection === 'up') {
341
- // Mũi tên hướng lên
342
- arrow.style.bottom = 'auto';
343
- arrow.style.top = '-8px';
344
- arrow.style.borderTop = 'none';
345
- arrow.style.borderBottom = '8px solid #fff';
346
- arrow.style.borderLeft = '6px solid transparent';
347
- arrow.style.borderRight = '6px solid transparent';
348
- } else {
349
- // Mũi tên hướng xuống (mặc định)
350
- arrow.style.top = 'auto';
351
- arrow.style.bottom = '-8px';
352
- arrow.style.borderBottom = 'none';
353
- arrow.style.borderTop = '8px solid #fff';
354
- arrow.style.borderLeft = '6px solid transparent';
355
- arrow.style.borderRight = '6px solid transparent';
356
- }
357
- }
358
- // Áp dụng vị trí cuối cùng
359
- this.blockToolbar.style.left = left + 'px';
360
- this.blockToolbar.style.top = top + 'px';
361
- }
362
-
363
- /**
364
- * Update toolbar position based on current selection or cursor position
365
- */
366
- updateToolbarPosition() {
367
- if (!this.isVisible) return;
368
-
369
- const selection = window.getSelection();
370
- if (!selection || selection.rangeCount === 0) {
371
- this.hide();
372
- return;
373
- }
374
-
375
- const range = selection.getRangeAt(0);
376
- const isInEditableArea = this.editor.isSelectionInEditableArea ?
377
- this.editor.isSelectionInEditableArea(selection) :
378
- this.editor.editor.contains(range.commonAncestorContainer);
379
-
380
- if (!isInEditableArea) {
381
- this.hide();
382
- return;
383
- }
384
-
385
- let rect;
386
-
387
- if (range.collapsed) {
388
- // For cursor position, get current cursor rect
389
- const span = document.createElement('span');
390
- span.innerHTML = '&#8203;';
391
- span.style.position = 'absolute';
392
- span.style.visibility = 'hidden';
393
- span.style.pointerEvents = 'none';
394
-
395
- try {
396
- range.insertNode(span);
397
- rect = span.getBoundingClientRect();
398
- if (span.parentNode) span.parentNode.removeChild(span);
399
-
400
- // Restore range
401
- const newRange = document.createRange();
402
- newRange.setStart(range.startContainer, range.startOffset);
403
- newRange.collapse(true);
404
- selection.removeAllRanges();
405
- selection.addRange(newRange);
406
- } catch (e) {
407
- // If insertion fails, hide toolbar
408
- this.hide();
409
- return;
410
- }
411
- } else {
412
- // For selection, use selection rect
413
- rect = range.getBoundingClientRect();
414
- }
415
-
416
- const editorRect = this.editor.wrapper.getBoundingClientRect();
417
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
418
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
419
-
420
- let x, y;
421
- if (range.collapsed) {
422
- x = rect.left - editorRect.left + scrollLeft;
423
- y = rect.top - editorRect.top + scrollTop - 10;
424
- } else {
425
- x = rect.left + rect.width / 2 - editorRect.left + scrollLeft;
426
- y = rect.top - editorRect.top + scrollTop - 10;
427
- }
428
-
429
- this.updateToolbarAt(x, y);
430
-
431
- // Update font-family popup position if it's visible
432
- const fontFamilyFormat = this.editor.registry.get('formats/font-family');
433
- if (fontFamilyFormat && fontFamilyFormat.selectInstance && fontFamilyFormat.selectInstance.isVisible) {
434
- fontFamilyFormat.selectInstance.updatePosition();
435
- }
436
- }
437
-
438
- /**
439
- * Update toolbar position at specific coordinates
440
- */
441
- updateToolbarAt(x, y) {
442
- if (!this.blockToolbar) return;
443
-
444
- this.ensureToolbarInViewport(x, y);
445
- this.updateButtonStates();
446
- }
447
-
448
- hide() {
449
- if (!this.blockToolbar || !this.isVisible) return;
450
- this.blockToolbar.classList.remove('visible');
451
- this.isVisible = false;
452
- // Clear stored positions
453
- this.currentSelection = null;
454
- this.currentCursorPosition = null;
455
-
456
- // Hide any open font-family popup when block toolbar is hidden
457
- const fontFamilyFormat = this.editor.registry.get('formats/font-family');
458
- if (fontFamilyFormat && fontFamilyFormat.selectInstance) {
459
- fontFamilyFormat.selectInstance.hide();
460
- }
461
- }
462
-
463
- handleCommand(command, button) {
464
- const selection = window.getSelection();
465
- const isInEditableArea = this.editor.isSelectionInEditableArea ?
466
- this.editor.isSelectionInEditableArea(selection) : true;
467
- if (!isInEditableArea) {
468
- this.hide();
469
- return;
470
- }
471
-
472
- // Special handling for font-family command
473
- if (command === 'font-family') {
474
- const fontFamilyFormat = this.editor.registry.get('formats/font-family');
475
- if (fontFamilyFormat) {
476
- const format = new fontFamilyFormat();
477
- format.toggle(button); // Pass the button as anchor
478
- this.updateButtonState(command, button);
479
- this.editor.focus();
480
- return;
481
- }
482
- }
483
-
484
- // Special handling for code command to use PRE tag from heading format
485
- if (command === 'code') {
486
- const headingFormat = this.editor.registry.get('formats/heading');
487
- if (headingFormat) {
488
- const heading = new headingFormat();
489
- const currentTag = heading.getCurrentTag();
490
-
491
- // If current tag is PRE, convert back to original tag or P
492
- // If current tag is not PRE, convert to PRE (code format)
493
- if (currentTag === 'PRE') {
494
- // Get the selection to find the block element
495
- const selection = window.getSelection();
496
- if (selection && selection.rangeCount) {
497
- const range = selection.getRangeAt(0);
498
- const block = this.getBlockElement(range.startContainer);
499
-
500
- if (block) {
501
- // Get original tag for this block, default to P
502
- const originalTag = this.originalTags.get(block) || 'P';
503
- heading.apply(originalTag);
504
- this.originalTags.delete(block); // Clean up
505
- } else {
506
- heading.apply('P');
507
- }
508
- } else {
509
- heading.apply('P');
510
- }
511
- } else {
512
- // Store original tag before converting to PRE
513
- const selection = window.getSelection();
514
- if (selection && selection.rangeCount) {
515
- const range = selection.getRangeAt(0);
516
- const block = this.getBlockElement(range.startContainer);
517
-
518
- if (block) {
519
- this.originalTags.set(block, currentTag || 'P');
520
- }
521
- }
522
-
523
- heading.apply('PRE');
524
- }
525
-
526
- this.updateButtonState(command, button);
527
- this.editor.focus();
528
- return;
529
- }
530
- }
531
-
532
- const formatClass = this.editor.registry.get(`formats/${command}`);
533
- if (formatClass) {
534
- const format = new formatClass();
535
- if (typeof format.toggle === 'function') format.toggle();
536
- else if (typeof format.apply === 'function') format.apply();
537
- } else {
538
- document.execCommand(command, false, null);
539
- }
540
- this.updateButtonState(command, button);
541
- this.editor.focus();
542
- }
543
-
544
- updateButtonStates() {
545
- if (!this.blockToolbar) return;
546
- const buttons = this.blockToolbar.querySelectorAll('.block-toolbar-btn');
547
- buttons.forEach(button => {
548
- const command = button.dataset.command;
549
- this.updateButtonState(command, button);
550
- });
551
- }
552
-
553
- updateButtonState(command, button) {
554
- if (!button) return;
555
- let isActive = false;
556
- if (command === 'font-family') {
557
- const fontFamilyFormat = this.editor.registry.get('formats/font-family');
558
- if (fontFamilyFormat) {
559
- const format = new fontFamilyFormat();
560
- isActive = format.isActive();
561
- }
562
- } else if (command === 'code') {
563
- // Check if current block is PRE tag
564
- const headingFormat = this.editor.registry.get('formats/heading');
565
- if (headingFormat) {
566
- const heading = new headingFormat();
567
- const currentTag = heading.getCurrentTag();
568
- isActive = currentTag === 'PRE';
569
- }
570
- } else if (command === 'strike') {
571
- const formatClass = this.editor.registry.get(`formats/${command}`);
572
- if (formatClass) {
573
- const format = new formatClass();
574
- isActive = format.isActive();
575
- }
576
- } else {
577
- try {
578
- isActive = document.queryCommandState(command);
579
- } catch (e) {
580
- isActive = false;
581
- }
582
- }
583
- if (isActive) button.classList.add('active');
584
- else button.classList.remove('active');
585
- }
586
-
587
- /**
588
- * Get block element from a node
589
- * @param {Node} node - Node to find block element for
590
- * @returns {Element|null} Block element or null
591
- */
592
- getBlockElement(node) {
593
- if (!node) return null;
594
-
595
- // If node is an element and is a block, return it
596
- if (node.nodeType === Node.ELEMENT_NODE) {
597
- const blockTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'BLOCKQUOTE', 'DIV'];
598
- if (blockTags.includes(node.tagName)) {
599
- return node;
600
- }
601
- }
602
-
603
- // Walk up the DOM tree to find block element
604
- let current = node;
605
- while (current && current !== this.editor.editor) {
606
- if (current.nodeType === Node.ELEMENT_NODE) {
607
- const blockTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'BLOCKQUOTE', 'DIV'];
608
- if (blockTags.includes(current.tagName)) {
609
- return current;
610
- }
611
- }
612
- current = current.parentNode;
613
- }
614
-
615
- return null;
616
- }
617
-
618
- destroy() {
619
- if (this.blockToolbar && this.blockToolbar.parentNode) {
620
- this.blockToolbar.parentNode.removeChild(this.blockToolbar);
621
- }
622
- this.blockToolbar = null;
623
- this.isVisible = false;
624
- this.originalTags.clear(); // Clean up stored tags
625
- }
626
- }
627
-
628
- export default BlockToolbar;