@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.
- package/README.md +73 -22
- package/dist/rich-editor.esm.js +2 -0
- package/dist/rich-editor.esm.js.map +1 -0
- package/dist/rich-editor.min.js +2 -0
- package/dist/rich-editor.min.js.map +1 -0
- package/package.json +12 -7
- package/index.js +0 -221
- package/lib/core/editor.js +0 -1175
- package/lib/core/format.js +0 -542
- package/lib/core/module.js +0 -81
- package/lib/core/registry.js +0 -152
- package/lib/formats/background.js +0 -212
- package/lib/formats/bold.js +0 -67
- package/lib/formats/capitalization.js +0 -563
- package/lib/formats/color.js +0 -165
- package/lib/formats/emoji.js +0 -282
- package/lib/formats/font-family.js +0 -547
- package/lib/formats/heading.js +0 -502
- package/lib/formats/image.js +0 -344
- package/lib/formats/import.js +0 -385
- package/lib/formats/indent.js +0 -297
- package/lib/formats/italic.js +0 -27
- package/lib/formats/line-height.js +0 -558
- package/lib/formats/link.js +0 -251
- package/lib/formats/list.js +0 -635
- package/lib/formats/strike.js +0 -31
- package/lib/formats/subscript.js +0 -36
- package/lib/formats/superscript.js +0 -35
- package/lib/formats/table.js +0 -288
- package/lib/formats/tag.js +0 -304
- package/lib/formats/text-align.js +0 -421
- package/lib/formats/text-size.js +0 -497
- package/lib/formats/underline.js +0 -30
- package/lib/formats/video.js +0 -372
- package/lib/modules/block-toolbar.js +0 -628
- package/lib/modules/code-view.js +0 -434
- package/lib/modules/history.js +0 -410
- package/lib/modules/resize-handles.js +0 -677
- package/lib/modules/table-toolbar.js +0 -618
- package/lib/modules/toolbar.js +0 -424
- package/lib/styles-loader.js +0 -144
- package/lib/styles.css +0 -2123
- package/lib/ui/color-picker.js +0 -296
- package/lib/ui/customselect.js +0 -319
- package/lib/ui/emoji-picker.js +0 -196
- package/lib/ui/icons.js +0 -413
- package/lib/ui/image-popup.js +0 -444
- package/lib/ui/import-popup.js +0 -288
- package/lib/ui/link-popup.js +0 -191
- package/lib/ui/list-picker.js +0 -307
- package/lib/ui/select-button.js +0 -61
- package/lib/ui/table-popup.js +0 -171
- package/lib/ui/tag-popup.js +0 -249
- package/lib/ui/text-align-picker.js +0 -281
- package/lib/ui/video-popup.js +0 -422
- package/lib/utils/history-helper.js +0 -50
- package/lib/utils/popup-helper.js +0 -219
- package/lib/utils/popup-positioning.js +0 -231
package/lib/formats/list.js
DELETED
|
@@ -1,635 +0,0 @@
|
|
|
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
|
-
// Always return false - no active state for this button
|
|
609
|
-
return false;
|
|
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;
|
package/lib/formats/strike.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
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;
|
package/lib/formats/subscript.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { InlineFormat } from '../core/format.js';
|
|
2
|
-
import { saveBeforeFormat } from '../utils/history-helper.js';
|
|
3
|
-
import Superscript from './superscript.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
|
-
const superscript = new Superscript();
|
|
14
|
-
if (superscript.isActive()) {
|
|
15
|
-
superscript.remove();
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Toggle subscript formatting
|
|
20
|
-
*/
|
|
21
|
-
toggle() {
|
|
22
|
-
// Save state before applying format
|
|
23
|
-
saveBeforeFormat();
|
|
24
|
-
|
|
25
|
-
if (this.isActive()) {
|
|
26
|
-
this.remove();
|
|
27
|
-
} else {
|
|
28
|
-
this.removeSuperscriptBeforeApply();
|
|
29
|
-
this.apply();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default Subscript;
|