@oix1987/yjd 1.0.3 → 2.1.0

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 (73) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +223 -142
  3. package/core.js +82 -0
  4. package/dist/core.esm.js +2 -0
  5. package/dist/core.esm.js.map +1 -0
  6. package/dist/rich-editor.esm.js +1 -1
  7. package/dist/rich-editor.esm.js.map +1 -1
  8. package/dist/rich-editor.min.js +1 -1
  9. package/dist/rich-editor.min.js.map +1 -1
  10. package/index.d.ts +230 -103
  11. package/index.js +297 -0
  12. package/lib/core/editor.js +1885 -0
  13. package/lib/core/format.js +540 -0
  14. package/lib/core/module.js +81 -0
  15. package/lib/core/registry.js +158 -0
  16. package/lib/formats/background.js +213 -0
  17. package/lib/formats/bold.js +49 -0
  18. package/lib/formats/capitalization.js +579 -0
  19. package/lib/formats/color.js +183 -0
  20. package/lib/formats/emoji.js +282 -0
  21. package/lib/formats/font-family.js +548 -0
  22. package/lib/formats/heading.js +502 -0
  23. package/lib/formats/image.js +341 -0
  24. package/lib/formats/import.js +385 -0
  25. package/lib/formats/indent.js +297 -0
  26. package/lib/formats/italic.js +27 -0
  27. package/lib/formats/line-height.js +562 -0
  28. package/lib/formats/link.js +251 -0
  29. package/lib/formats/list.js +635 -0
  30. package/lib/formats/strike.js +31 -0
  31. package/lib/formats/subscript.js +40 -0
  32. package/lib/formats/superscript.js +39 -0
  33. package/lib/formats/table.js +293 -0
  34. package/lib/formats/tag.js +304 -0
  35. package/lib/formats/text-align.js +422 -0
  36. package/lib/formats/text-size.js +498 -0
  37. package/lib/formats/underline.js +30 -0
  38. package/lib/formats/video.js +381 -0
  39. package/lib/modules/block-toolbar.js +639 -0
  40. package/lib/modules/code-view.js +447 -0
  41. package/lib/modules/find-replace.js +273 -0
  42. package/lib/modules/history.js +425 -0
  43. package/lib/modules/mention.js +200 -0
  44. package/lib/modules/resize-handles.js +701 -0
  45. package/lib/modules/slash-menu.js +183 -0
  46. package/lib/modules/table-toolbar.js +635 -0
  47. package/lib/modules/toolbar.js +607 -0
  48. package/lib/serialize.js +241 -0
  49. package/lib/static.js +28 -0
  50. package/lib/styles-loader.js +142 -0
  51. package/{dist → lib}/styles.css +1392 -35
  52. package/lib/styles.css.js +2 -0
  53. package/lib/styles.min.css +1 -0
  54. package/lib/ui/color-picker.js +296 -0
  55. package/lib/ui/customselect.js +351 -0
  56. package/lib/ui/emoji-picker.js +196 -0
  57. package/lib/ui/icons.js +145 -0
  58. package/lib/ui/image-popup.js +435 -0
  59. package/lib/ui/import-popup.js +288 -0
  60. package/lib/ui/link-popup.js +139 -0
  61. package/lib/ui/list-picker.js +307 -0
  62. package/lib/ui/select-button.js +68 -0
  63. package/lib/ui/table-popup.js +171 -0
  64. package/lib/ui/tag-popup.js +249 -0
  65. package/lib/ui/text-align-picker.js +278 -0
  66. package/lib/ui/video-popup.js +413 -0
  67. package/lib/utils/exec-command.js +72 -0
  68. package/lib/utils/history-helper.js +50 -0
  69. package/lib/utils/popup-helper.js +219 -0
  70. package/lib/utils/popup-positioning.js +234 -0
  71. package/lib/utils/sanitize.js +164 -0
  72. package/package.json +51 -32
  73. package/umd-entry.js +19 -0
@@ -0,0 +1,635 @@
1
+ import { BlockFormat } from '../core/format.js';
2
+ import ListPicker from '../ui/list-picker.js';
3
+ import IconUtils from '../ui/icons.js';
4
+ import { saveBeforeFormat } from '../utils/history-helper.js';
5
+ import Editor from '../core/editor.js';
6
+
7
+ /**
8
+ * List Format - Handles list formatting (bullet, ordered, checklist)
9
+ * Now supports multiple editor instances with separate popup instances
10
+ */
11
+ class List extends BlockFormat {
12
+ static formatName = 'list';
13
+ static tagName = 'UL';
14
+ static attribute = 'class';
15
+
16
+ constructor() {
17
+ super();
18
+
19
+ // Get current editor instance
20
+ const currentEditor = Editor.getCurrentInstance();
21
+ if (!currentEditor) {
22
+ console.warn('No editor instance found for List format');
23
+ return;
24
+ }
25
+
26
+ this.editorId = currentEditor.instanceId;
27
+
28
+ // Check if this editor already has a list picker instance
29
+ let listPicker = currentEditor.getPopupInstance('list');
30
+
31
+ if (!listPicker) {
32
+ // Create new list picker instance for this editor
33
+ listPicker = new ListPicker({
34
+ onListSelect: (listType) => {
35
+ List.applyListToCurrentSelection(listType, this.editorId);
36
+ },
37
+ editor: currentEditor,
38
+ editorId: this.editorId
39
+ });
40
+
41
+ // Store popup instance in editor
42
+ currentEditor.setPopupInstance('list', listPicker);
43
+ }
44
+
45
+ this.listPicker = listPicker;
46
+ }
47
+
48
+ /**
49
+ * Create a new List format instance for a specific editor
50
+ * @param {string} editorId - Editor instance ID
51
+ * @returns {List} List format instance
52
+ */
53
+ static createForEditor(editorId) {
54
+ const editor = Editor.getInstanceById(editorId);
55
+ if (!editor) {
56
+ console.warn('No editor instance found for ID:', editorId);
57
+ return null;
58
+ }
59
+
60
+ // Temporarily set as current instance
61
+ const originalCurrent = Editor.currentInstance;
62
+ Editor.currentInstance = editor;
63
+
64
+ // Create format instance
65
+ const format = new List();
66
+
67
+ // Restore original current instance
68
+ Editor.currentInstance = originalCurrent;
69
+
70
+ return format;
71
+ }
72
+
73
+ /**
74
+ * Create list element with specified type
75
+ * @param {string} value - List type (bullet, ordered, checklist)
76
+ * @returns {HTMLElement}
77
+ */
78
+ static create(value) {
79
+ let node;
80
+
81
+ switch(value) {
82
+ case 'ordered':
83
+ node = document.createElement('OL');
84
+ node.style.listStyleType = 'decimal'; // 1, 2, 3...
85
+ break;
86
+ case 'roman':
87
+ node = document.createElement('OL');
88
+ node.style.listStyleType = 'upper-roman'; // I, II, III...
89
+ break;
90
+ case 'alpha':
91
+ node = document.createElement('OL');
92
+ node.style.listStyleType = 'lower-alpha'; // a, b, c...
93
+ break;
94
+ case 'bullet':
95
+ default:
96
+ node = document.createElement('UL');
97
+ node.style.listStyleType = 'disc'; // bullet points
98
+ break;
99
+ }
100
+
101
+ return node;
102
+ }
103
+
104
+ /**
105
+ * Static method to apply list to current selection or cursor position
106
+ * @param {string} listType - List type
107
+ * @param {string} editorId - Editor instance ID
108
+ */
109
+ static applyListToCurrentSelection(listType, editorId = null) {
110
+ // Get the correct editor instance
111
+ let editor = null;
112
+ if (editorId) {
113
+ editor = Editor.getInstanceById(editorId);
114
+ } else {
115
+ editor = Editor.getCurrentInstance();
116
+ }
117
+
118
+ if (!editor) {
119
+ console.warn('No editor instance found for list application');
120
+ return;
121
+ }
122
+
123
+ const selection = window.getSelection();
124
+ if (!selection || !selection.rangeCount) return;
125
+
126
+ // Save state before applying format
127
+ saveBeforeFormat();
128
+
129
+ try {
130
+ const range = selection.getRangeAt(0);
131
+ const blockElements = List.getSelectedBlockElements(range);
132
+
133
+ if (blockElements.length === 0) {
134
+ // If no block elements found, create one
135
+ List.createListFromSelection(listType);
136
+ } else {
137
+ // Apply list to existing blocks
138
+ List.convertBlocksToList(blockElements, listType);
139
+
140
+ }
141
+
142
+ // Update toolbar button icon after applying list
143
+ List.updateToolbarButtonIcon(listType, editorId);
144
+
145
+ } catch (error) {
146
+ console.error('Error applying list:', error);
147
+ }
148
+
149
+ // Trigger content change after applying format
150
+ setTimeout(() => {
151
+ if (editor && typeof editor.onContentChange === 'function') {
152
+ editor.onContentChange();
153
+ }
154
+ }, 0);
155
+ }
156
+
157
+ /**
158
+ * Create list from current selection
159
+ */
160
+ static createListFromSelection(listType) {
161
+ const selection = window.getSelection();
162
+ if (!selection || !selection.rangeCount) return;
163
+
164
+ const range = selection.getRangeAt(0);
165
+ const selectedText = range.toString() || 'List item';
166
+
167
+ // Create list element
168
+ const listElement = List.create(listType);
169
+ const listItem = document.createElement('LI');
170
+
171
+ // Preserve HTML content if selection contains formatted text
172
+ if (range.toString() === range.cloneContents().textContent) {
173
+ // Plain text selection
174
+ listItem.textContent = selectedText;
175
+ } else {
176
+ // HTML selection - preserve formatting
177
+ const fragment = range.cloneContents();
178
+ listItem.appendChild(fragment);
179
+ }
180
+
181
+ // Try to preserve style from existing block if cursor is inside one
182
+ const existingBlock = List.getBlockElement(range.startContainer);
183
+ if (existingBlock && existingBlock.style && existingBlock.style.cssText) {
184
+ listItem.style.cssText = existingBlock.style.cssText;
185
+ }
186
+
187
+ listElement.appendChild(listItem);
188
+
189
+ // Replace selection with list
190
+ range.deleteContents();
191
+ range.insertNode(listElement);
192
+
193
+ // Position cursor in the list item
194
+ const newRange = document.createRange();
195
+ newRange.selectNodeContents(listItem);
196
+ newRange.collapse(false);
197
+ selection.removeAllRanges();
198
+ selection.addRange(newRange);
199
+ }
200
+
201
+ /**
202
+ * Convert existing block elements to list
203
+ */
204
+ static convertBlocksToList(blocks, listType) {
205
+ if (blocks.length === 0) return;
206
+
207
+ // Check if blocks are already in a list
208
+ const existingList = List.getParentList(blocks[0]);
209
+ if (existingList) {
210
+ // If already in a list, toggle or change list type
211
+ List.toggleOrChangeListType(existingList, listType);
212
+ return;
213
+ }
214
+
215
+ // Create new list
216
+ const listElement = List.create(listType);
217
+ const firstBlock = blocks[0];
218
+
219
+ // Insert list before first block
220
+ firstBlock.parentNode.insertBefore(listElement, firstBlock);
221
+
222
+ let firstListItem = null;
223
+
224
+ // Convert each block to list item
225
+ blocks.forEach((block, index) => {
226
+ const listItem = document.createElement('LI');
227
+
228
+ // Preserve all HTML content including formatting (bold, italic, etc.)
229
+ listItem.innerHTML = block.innerHTML || block.textContent || '';
230
+
231
+ // Copy style attributes to preserve formatting like text-align
232
+ if (block.style && block.style.cssText) {
233
+ listItem.style.cssText = block.style.cssText;
234
+ }
235
+
236
+ listElement.appendChild(listItem);
237
+ block.remove();
238
+
239
+ // Lưu lại list item đầu tiên để đặt con trỏ
240
+ if (index === 0) {
241
+ firstListItem = listItem;
242
+ }
243
+ });
244
+
245
+ // Đặt con trỏ vào list item đầu tiên
246
+ if (firstListItem) {
247
+ const range = document.createRange();
248
+ const sel = window.getSelection();
249
+ range.selectNodeContents(firstListItem);
250
+ range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ
251
+ sel.removeAllRanges();
252
+ sel.addRange(range);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Toggle or change list type
258
+ */
259
+ static toggleOrChangeListType(existingList, newListType) {
260
+ const currentType = List.getListType(existingList);
261
+
262
+ if (currentType === newListType) {
263
+ // Same type - remove list formatting
264
+ List.removeListFormatting(existingList);
265
+ } else {
266
+ // Different type - change list type
267
+ List.changeListType(existingList, newListType);
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Get list type from list element
273
+ */
274
+ static getListType(listElement) {
275
+ if (listElement.tagName === 'OL') {
276
+ const type = listElement.style.listStyleType;
277
+ if (type === 'upper-roman') return 'roman';
278
+ if (type === 'lower-alpha') return 'alpha';
279
+ return 'ordered';
280
+ }
281
+ return 'bullet';
282
+ }
283
+
284
+ /**
285
+ * Change list type
286
+ */
287
+ static changeListType(existingList, newListType) {
288
+ const newList = List.create(newListType);
289
+
290
+ // Copy all list items
291
+ Array.from(existingList.children).forEach(item => {
292
+ const newItem = document.createElement('LI');
293
+
294
+ // Preserve all HTML content including formatting
295
+ newItem.innerHTML = item.innerHTML || item.textContent || '';
296
+
297
+ // Copy style attributes to preserve formatting like text-align
298
+ if (item.style && item.style.cssText) {
299
+ newItem.style.cssText = item.style.cssText;
300
+ }
301
+
302
+ newList.appendChild(newItem);
303
+ });
304
+
305
+ // Replace existing list
306
+ existingList.parentNode.replaceChild(newList, existingList);
307
+
308
+ // Đặt con trỏ vào list item đầu tiên
309
+ if (newList.firstElementChild) {
310
+ const range = document.createRange();
311
+ const sel = window.getSelection();
312
+ range.selectNodeContents(newList.firstElementChild);
313
+ range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ
314
+ sel.removeAllRanges();
315
+ sel.addRange(range);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Remove list formatting
321
+ */
322
+ static removeListFormatting(listElement) {
323
+ const parent = listElement.parentNode;
324
+ let firstParagraph = null;
325
+
326
+ // Convert list items back to paragraphs
327
+ Array.from(listElement.children).forEach((item, index) => {
328
+ const p = document.createElement('P');
329
+
330
+ // Preserve all HTML content including formatting
331
+ p.innerHTML = item.innerHTML || item.textContent || '';
332
+
333
+ // Copy style attributes to preserve formatting like text-align
334
+ if (item.style && item.style.cssText) {
335
+ p.style.cssText = item.style.cssText;
336
+ }
337
+
338
+ parent.insertBefore(p, listElement);
339
+
340
+ // Lưu lại paragraph đầu tiên để đặt con trỏ
341
+ if (index === 0) {
342
+ firstParagraph = p;
343
+ }
344
+ });
345
+
346
+ // Remove the list element
347
+ listElement.remove();
348
+
349
+ // Đặt con trỏ vào paragraph đầu tiên
350
+ if (firstParagraph) {
351
+ const range = document.createRange();
352
+ const sel = window.getSelection();
353
+ range.selectNodeContents(firstParagraph);
354
+ range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ
355
+ sel.removeAllRanges();
356
+ sel.addRange(range);
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Get parent list of an element
362
+ */
363
+ static getParentList(element) {
364
+ let current = element;
365
+ while (current && current !== document.body) {
366
+ if (current.tagName === 'UL' || current.tagName === 'OL') {
367
+ return current;
368
+ }
369
+ current = current.parentElement;
370
+ }
371
+ return null;
372
+ }
373
+
374
+ /**
375
+ * Get icon name for list type
376
+ * @param {string} listType - List type
377
+ * @returns {string} Icon name
378
+ */
379
+ static getIconNameForListType(listType) {
380
+ const iconMap = {
381
+ 'bullet': 'list-bullet',
382
+ 'ordered': 'list-ordered',
383
+ 'roman': 'list-roman',
384
+ 'alpha': 'list-alpha'
385
+ };
386
+ return iconMap[listType] || 'list-bullet';
387
+ }
388
+
389
+ /**
390
+ * Update toolbar button icon based on list type
391
+ * @param {string} listType - Current list type
392
+ * @param {string} editorId - Editor instance ID
393
+ */
394
+ static updateToolbarButtonIcon(listType, editorId = null) {
395
+ // Get the correct editor instance
396
+ let editor = null;
397
+ if (editorId) {
398
+ editor = Editor.getInstanceById(editorId);
399
+ } else {
400
+ editor = Editor.getCurrentInstance();
401
+ }
402
+
403
+ if (!editor) return;
404
+
405
+ const toolbar = editor.getModule('toolbar');
406
+ let button = null;
407
+
408
+ if (toolbar) {
409
+ button = toolbar.getButton('list');
410
+ }
411
+
412
+ // Fallback: find button by class in the current editor's toolbar
413
+ if (!button) {
414
+ const toolbarContainer = toolbar?.getContainer();
415
+ if (toolbarContainer) {
416
+ button = toolbarContainer.querySelector('.rich-editor-toolbar-btn.list-btn');
417
+ }
418
+ }
419
+
420
+ // Final fallback: find any list button in the current editor's wrapper
421
+ if (!button) {
422
+ button = editor.wrapper.querySelector('.rich-editor-toolbar-btn.list-btn');
423
+ }
424
+
425
+ if (!button) return;
426
+
427
+ const iconName = List.getIconNameForListType(listType);
428
+ const titleMap = {
429
+ 'bullet': 'Bullet List',
430
+ 'ordered': 'Numbered List',
431
+ 'roman': 'Roman Numerals List',
432
+ 'alpha': 'Alphabetical List'
433
+ };
434
+
435
+ // Update button title
436
+ button.title = titleMap[listType] || 'List';
437
+
438
+ // Update icon
439
+ const svgContent = IconUtils.getIcon(iconName);
440
+ if (svgContent) {
441
+ const iconSpan = button.querySelector('.icon');
442
+ if (iconSpan) {
443
+ iconSpan.innerHTML = svgContent;
444
+ } else {
445
+ button.innerHTML = `<span class="icon">${svgContent}</span>`;
446
+ }
447
+ }
448
+ }
449
+
450
+ /**
451
+ * Get all selected block elements
452
+ */
453
+ static getSelectedBlockElements(range) {
454
+ const blocks = [];
455
+ const startContainer = range.startContainer;
456
+ const endContainer = range.endContainer;
457
+
458
+ // Get start block
459
+ const startBlock = List.getBlockElement(startContainer);
460
+ if (startBlock) blocks.push(startBlock);
461
+
462
+ // If selection spans multiple blocks, get all blocks in between
463
+ if (startContainer !== endContainer) {
464
+ let currentNode = startBlock;
465
+ while (currentNode && currentNode !== endContainer) {
466
+ const nextBlock = List.getNextBlockElement(currentNode);
467
+ if (nextBlock && !blocks.includes(nextBlock)) {
468
+ blocks.push(nextBlock);
469
+ currentNode = nextBlock;
470
+ } else {
471
+ break;
472
+ }
473
+ }
474
+
475
+ // Get end block
476
+ const endBlock = List.getBlockElement(endContainer);
477
+ if (endBlock && !blocks.includes(endBlock)) {
478
+ blocks.push(endBlock);
479
+ }
480
+ }
481
+
482
+ return blocks;
483
+ }
484
+
485
+ /**
486
+ * Get the block element containing the given node
487
+ */
488
+ static getBlockElement(node) {
489
+ if (!node) return null;
490
+
491
+ let currentNode = node;
492
+ while (currentNode && currentNode !== document.body) {
493
+ if (currentNode.nodeType === Node.ELEMENT_NODE) {
494
+ const tagName = currentNode.tagName;
495
+ if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {
496
+ return currentNode;
497
+ }
498
+ }
499
+ currentNode = currentNode.parentNode;
500
+ }
501
+ return null;
502
+ }
503
+
504
+ /**
505
+ * Get next block element in document order
506
+ */
507
+ static getNextBlockElement(element) {
508
+ let currentNode = element.nextSibling;
509
+
510
+ while (currentNode) {
511
+ if (currentNode.nodeType === Node.ELEMENT_NODE) {
512
+ const tagName = currentNode.tagName;
513
+ if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {
514
+ return currentNode;
515
+ }
516
+ }
517
+ currentNode = currentNode.nextSibling;
518
+ }
519
+
520
+ return null;
521
+ }
522
+
523
+ /**
524
+ * Apply list formatting
525
+ * @param {string} value - List type
526
+ */
527
+ apply(value = 'bullet') {
528
+ List.applyListToCurrentSelection(value, this.editorId);
529
+ }
530
+
531
+ /**
532
+ * Remove list formatting
533
+ */
534
+ remove() {
535
+ const selection = window.getSelection();
536
+ if (!selection || !selection.rangeCount) return;
537
+
538
+ const range = selection.getRangeAt(0);
539
+ const listElement = List.getParentList(range.commonAncestorContainer);
540
+
541
+ if (listElement) {
542
+ List.removeListFormatting(listElement);
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Toggle list formatting - shows/hides list picker
548
+ */
549
+ toggle() {
550
+ if (this.listPicker.isVisible) {
551
+ this.listPicker.hide();
552
+ } else {
553
+ this.showListPicker();
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Show list picker popup positioned relative to list button on toolbar
559
+ */
560
+ showListPicker() {
561
+ // Find list button in the current editor's toolbar
562
+ const editor = Editor.getInstanceById(this.editorId);
563
+ if (!editor) return;
564
+
565
+ const toolbar = editor.getModule('toolbar');
566
+ let listButton = null;
567
+
568
+ if (toolbar) {
569
+ listButton = toolbar.getButton('list');
570
+ }
571
+
572
+ // Fallback: find button by class in the current editor's toolbar
573
+ if (!listButton) {
574
+ const toolbarContainer = toolbar?.getContainer();
575
+ if (toolbarContainer) {
576
+ listButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.list-btn');
577
+ }
578
+ }
579
+
580
+ // Final fallback: find any list button in the current editor's wrapper
581
+ if (!listButton) {
582
+ listButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.list-btn');
583
+ }
584
+
585
+ if (!listButton) {
586
+ console.warn('List button not found for editor:', this.editorId);
587
+ return;
588
+ }
589
+
590
+ this.listPicker.show(listButton);
591
+ }
592
+
593
+ /**
594
+ * Check if list formatting is active
595
+ * Always returns false because list button should not have active state
596
+ * Instead, the button icon changes to reflect current list type
597
+ */
598
+ isActive(listType = null) {
599
+ // Update button icon based on current list type
600
+ const currentListType = List.getCurrentListType();
601
+ if (currentListType) {
602
+ List.updateToolbarButtonIcon(currentListType, this.editorId);
603
+ } else {
604
+ // Reset to default bullet list icon
605
+ List.updateToolbarButtonIcon('bullet', this.editorId);
606
+ }
607
+
608
+ // Highlight the button when the caret is inside a list.
609
+ return !!currentListType;
610
+ }
611
+
612
+ /**
613
+ * Get current list type
614
+ * @returns {string|null}
615
+ */
616
+ static getCurrentListType() {
617
+ const selection = window.getSelection();
618
+ if (!selection || !selection.rangeCount) return null;
619
+
620
+ const range = selection.getRangeAt(0);
621
+ const listElement = List.getParentList(range.commonAncestorContainer);
622
+
623
+ return listElement ? List.getListType(listElement) : null;
624
+ }
625
+
626
+ /**
627
+ * Get current list type (instance method)
628
+ * @returns {string|null}
629
+ */
630
+ getCurrentListType() {
631
+ return List.getCurrentListType();
632
+ }
633
+ }
634
+
635
+ export default List;
@@ -0,0 +1,31 @@
1
+ import { InlineFormat } from '../core/format.js';
2
+ import { saveBeforeFormat } from '../utils/history-helper.js';
3
+
4
+ /**
5
+ * Strike Format - Handles strikethrough text formatting
6
+ * Extracted from FormatManager.js logic
7
+ */
8
+ class Strike extends InlineFormat {
9
+ static formatName = 'strike';
10
+ static tagName = 'S';
11
+ static alternativeTagNames = ['STRIKE', 'DEL'];
12
+
13
+ /**
14
+ * Toggle strikethrough formatting
15
+ */
16
+ toggle() {
17
+ // Save state before applying format
18
+ saveBeforeFormat();
19
+
20
+ if (this.isActive()) {
21
+ this.remove();
22
+ } else {
23
+ this.apply();
24
+ }
25
+ }
26
+
27
+
28
+
29
+ }
30
+
31
+ export default Strike;
@@ -0,0 +1,40 @@
1
+ import { InlineFormat } from '../core/format.js';
2
+ import { saveBeforeFormat } from '../utils/history-helper.js';
3
+ import registry from '../core/registry.js';
4
+ /**
5
+ * Subscript Format - Handles subscript text formatting
6
+ * Creates <sub> elements for subscript text
7
+ */
8
+ class Subscript extends InlineFormat {
9
+ static formatName = 'subscript';
10
+ static tagName = 'SUB';
11
+
12
+ removeSuperscriptBeforeApply() {
13
+ // Resolved via registry (not a static import) to avoid a circular
14
+ // dependency between subscript.js and superscript.js.
15
+ const Superscript = registry.get('formats/superscript');
16
+ if (!Superscript) return;
17
+ const superscript = new Superscript();
18
+ if (superscript.isActive()) {
19
+ superscript.remove();
20
+ }
21
+ }
22
+ /**
23
+ * Toggle subscript formatting
24
+ */
25
+ toggle() {
26
+ // Save state before applying format
27
+ saveBeforeFormat();
28
+
29
+ if (this.isActive()) {
30
+ this.remove();
31
+ } else {
32
+ this.removeSuperscriptBeforeApply();
33
+ this.apply();
34
+ }
35
+ }
36
+
37
+
38
+ }
39
+
40
+ export default Subscript;