@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,497 +0,0 @@
1
- import { InlineFormat } from '../core/format.js';
2
- import CustomSelect from '../ui/customselect.js';
3
- import { saveBeforeFormat } from '../utils/history-helper.js';
4
- import Editor from '../core/editor.js';
5
-
6
- /**
7
- * Text Size Format - Handles font size formatting with 7 levels via execCommand
8
- * Now supports multiple editor instances with separate popup instances
9
- */
10
- class TextSize extends InlineFormat {
11
- static formatName = 'textSize';
12
- static tagName = 'SPAN';
13
-
14
- constructor() {
15
- super();
16
-
17
- // Get current editor instance
18
- const currentEditor = Editor.getCurrentInstance();
19
- if (!currentEditor) {
20
- console.warn('No editor instance found for TextSize format');
21
- return;
22
- }
23
-
24
- this.editorId = currentEditor.instanceId;
25
-
26
- // Check if this editor already has a text size select instance
27
- let customSelect = currentEditor.getPopupInstance('text-size');
28
-
29
- if (!customSelect) {
30
- // Create new custom select instance for this editor
31
- const sizeMap = TextSize.getSizeMap();
32
- const items = Object.values(sizeMap).map(sizeData => ({
33
- value: sizeData.size,
34
- label: sizeData.element,
35
- title: sizeData.title
36
- }));
37
-
38
- customSelect = new CustomSelect({
39
- items: items,
40
- displayProperty: 'label',
41
- valueProperty: 'value',
42
- className: 'text-size-select',
43
- onItemSelect: (value) => {
44
- TextSize.applyTextSizeToCurrentSelection(value, this.editorId);
45
- },
46
- editor: currentEditor,
47
- editorId: this.editorId
48
- });
49
-
50
- // Store popup instance in editor
51
- currentEditor.setPopupInstance('text-size', customSelect);
52
- }
53
-
54
- this.customSelect = customSelect;
55
- }
56
-
57
- /**
58
- * Create a new TextSize format instance for a specific editor
59
- * @param {string} editorId - Editor instance ID
60
- * @returns {TextSize} TextSize format instance
61
- */
62
- static createForEditor(editorId) {
63
- const editor = Editor.getInstanceById(editorId);
64
- if (!editor) {
65
- console.warn('No editor instance found for ID:', editorId);
66
- return null;
67
- }
68
-
69
- // Temporarily set as current instance
70
- const originalCurrent = Editor.currentInstance;
71
- Editor.currentInstance = editor;
72
-
73
- // Create format instance
74
- const format = new TextSize();
75
-
76
- // Restore original current instance
77
- Editor.currentInstance = originalCurrent;
78
-
79
- return format;
80
- }
81
-
82
- /**
83
- * 7-level text size map aligned with execCommand('fontSize', 1..7)
84
- */
85
- static getSizeMap() {
86
- return {
87
- '1': { size: '1', element: '<span >XX-Small</span>', title: 'XX-Small' },
88
- '2': { size: '2', element: '<span >X-Small</span>', title: 'X-Small' },
89
- '3': { size: '3', element: '<span >Small</span>', title: 'Small' },
90
- '4': { size: '4', element: '<span >Medium</span>', title: 'Medium' },
91
- '5': { size: '5', element: '<span >Large</span>', title: 'Large' },
92
- '6': { size: '6', element: '<span >X-Large</span>', title: 'X-Large' },
93
- '7': { size: '7', element: '<span >XX-Large</span>', title: 'XX-Large' },
94
- };
95
- }
96
-
97
- static getSizeDisplayName(size) {
98
- const sizeMap = this.getSizeMap();
99
- return sizeMap[size]?.title || 'Medium';
100
- }
101
-
102
- /**
103
- * Update button text based on current text size
104
- */
105
- updateButtonText() {
106
- const currentSize = this.getCurrentSize();
107
- const displayName = TextSize.getSizeDisplayName(currentSize || '4');
108
-
109
- // Find text-size button in the current editor's toolbar
110
- const editor = Editor.getInstanceById(this.editorId);
111
- if (!editor) return;
112
-
113
- const toolbar = editor.getModule('toolbar');
114
- let textSizeButton = null;
115
-
116
- if (toolbar) {
117
- textSizeButton = toolbar.getButton('text-size');
118
- }
119
-
120
- // Fallback: find button by class in the current editor's toolbar
121
- if (!textSizeButton) {
122
- const toolbarContainer = toolbar?.getContainer();
123
- if (toolbarContainer) {
124
- textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');
125
- }
126
- }
127
-
128
- // Final fallback: find any text-size button in the current editor's wrapper
129
- if (!textSizeButton) {
130
- textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');
131
- }
132
-
133
- if (textSizeButton && textSizeButton.updateText) {
134
- textSizeButton.updateText(displayName);
135
- } else if (textSizeButton) {
136
- textSizeButton.textContent = displayName;
137
- }
138
- }
139
-
140
- /**
141
- * Static method to update button text for any editor
142
- * @param {string} editorId - Editor instance ID
143
- */
144
- static updateButtonTextStatic(editorId = null) {
145
- // Get the correct editor instance
146
- let editor = null;
147
- if (editorId) {
148
- editor = Editor.getInstanceById(editorId);
149
- } else {
150
- editor = Editor.getCurrentInstance();
151
- }
152
-
153
- if (!editor) return;
154
-
155
- const currentSize = TextSize.getCurrentSizeStatic();
156
- const displayName = TextSize.getSizeDisplayName(currentSize || '4');
157
-
158
- const toolbar = editor.getModule('toolbar');
159
- let textSizeButton = null;
160
-
161
- if (toolbar) {
162
- textSizeButton = toolbar.getButton('text-size');
163
- }
164
-
165
- // Fallback: find button by class in the current editor's toolbar
166
- if (!textSizeButton) {
167
- const toolbarContainer = toolbar?.getContainer();
168
- if (toolbarContainer) {
169
- textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');
170
- }
171
- }
172
-
173
- // Final fallback: find any text-size button in the current editor's wrapper
174
- if (!textSizeButton) {
175
- textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');
176
- }
177
-
178
- if (textSizeButton && textSizeButton.updateText) {
179
- textSizeButton.updateText(displayName);
180
- } else if (textSizeButton) {
181
- textSizeButton.textContent = displayName;
182
- }
183
- }
184
-
185
- static create(size = '4') {
186
- const node = document.createElement('span');
187
- // Fallback creation with an approximate CSS size
188
- node.style.fontSize = TextSize.sizeToCss(size);
189
- return node;
190
- }
191
-
192
- /**
193
- * Apply text size to current selection
194
- * @param {string} size - Text size value
195
- * @param {string} editorId - Editor instance ID
196
- */
197
- static applyTextSizeToCurrentSelection(size, editorId = null) {
198
- // Get the correct editor instance
199
- let editor = null;
200
- if (editorId) {
201
- editor = Editor.getInstanceById(editorId);
202
- } else {
203
- editor = Editor.getCurrentInstance();
204
- }
205
-
206
- if (!editor) {
207
- console.warn('No editor instance found for text size application');
208
- return;
209
- }
210
-
211
- const selection = window.getSelection();
212
- if (!selection || !selection.rangeCount) return;
213
-
214
- // Save state before applying format
215
- saveBeforeFormat();
216
-
217
- const sizeFormat = TextSize.createForEditor(editorId);
218
- if (sizeFormat) {
219
- sizeFormat.apply(size);
220
- sizeFormat.updateButtonText();
221
- }
222
-
223
- // Trigger content change after applying format
224
- setTimeout(() => {
225
- if (editor && typeof editor.onContentChange === 'function') {
226
- editor.onContentChange();
227
- }
228
- }, 0);
229
- }
230
-
231
- /**
232
- * Map execCommand size (1..7) to CSS font-size for fallback/labels
233
- */
234
- static sizeToCss(size) {
235
- const map = {
236
- '1': '10px',
237
- '2': '12px',
238
- '3': '14px',
239
- '4': '16px',
240
- '5': '20px',
241
- '6': '28px',
242
- '7': '36px',
243
- };
244
- return map[String(size)] || '16px';
245
- }
246
-
247
- /**
248
- * Apply text size using execCommand; works with selection and collapsed caret
249
- */
250
- apply(size = '4') {
251
- const selection = window.getSelection();
252
- if (!selection || !selection.rangeCount) return;
253
-
254
- saveBeforeFormat();
255
-
256
- const range = selection.getRangeAt(0);
257
-
258
- if (!range.collapsed) {
259
- // Bạn chưa nói đến xử lý khi bôi đen, nên mình bỏ qua
260
- document.execCommand('fontSize', false, String(size));
261
-
262
- // Lấy node bao quanh selection hiện tại
263
- const sel = window.getSelection();
264
- if (sel.rangeCount > 0) {
265
- const container = sel.getRangeAt(0).commonAncestorContainer;
266
- // Nếu container là text node → normalize ở parent
267
- if (container.nodeType === Node.TEXT_NODE) {
268
- container.parentNode.normalize();
269
- } else {
270
- container.normalize();
271
- }
272
- }
273
-
274
- return;
275
- }
276
-
277
- let node = range.startContainer;
278
- let offset = range.startOffset;
279
-
280
- // Nếu caret đang trong text node → lấy cha
281
- if (node.nodeType === Node.TEXT_NODE) {
282
- node = node.parentNode;
283
- }
284
-
285
- // Kiểm tra nếu đang ở trong một <font>
286
- const currentFont = node.closest && node.closest('font');
287
-
288
- // ========================
289
- // Trường hợp 1: caret trong <font> rỗng (chỉ có \u200B)
290
- // ========================
291
- if (currentFont && currentFont.textContent === "\u200B") {
292
- currentFont.setAttribute('size', String(size));
293
- return;
294
- }
295
-
296
- // ========================
297
- // Trường hợp 2: caret trong <font> có ký tự thực
298
- // ========================
299
- if (currentFont && currentFont.firstChild && currentFont.firstChild.nodeType === Node.TEXT_NODE) {
300
- const textNode = currentFont.firstChild;
301
- const caretPos = range.startOffset; // vị trí caret trong text node
302
-
303
- // Loại bỏ ký tự ẩn trong tính toán
304
-
305
- const textBefore = textNode.data.slice(0, caretPos);
306
- const textAfter = textNode.data.slice(caretPos);
307
-
308
- const parent = currentFont.parentNode;
309
-
310
- if (caretPos === 0) {
311
- // Đang ở ĐẦU thẻ font → chèn font mới trước
312
- const newFont = document.createElement('font');
313
- newFont.setAttribute('size', String(size));
314
- newFont.appendChild(document.createTextNode("\u200B"));
315
- parent.insertBefore(newFont, currentFont);
316
-
317
- moveCaretInside(newFont);
318
-
319
- } else if (caretPos === textNode.data.length) {
320
- // Đang ở CUỐI thẻ font → chèn font mới sau
321
- const newFont = document.createElement('font');
322
- newFont.setAttribute('size', String(size));
323
- newFont.appendChild(document.createTextNode("\u200B"));
324
- parent.insertBefore(newFont, currentFont.nextSibling);
325
-
326
- moveCaretInside(newFont);
327
-
328
- } else {
329
-
330
-
331
- const font1 = document.createElement('font');
332
- font1.setAttribute('size', currentFont.getAttribute('size'));
333
- font1.appendChild(document.createTextNode(textBefore));
334
-
335
- const font2 = document.createElement('font');
336
- font2.setAttribute('size', String(size));
337
- font2.appendChild(document.createTextNode("\u200B"));
338
-
339
- const font3 = document.createElement('font');
340
- font3.setAttribute('size', currentFont.getAttribute('size'));
341
- font3.appendChild(document.createTextNode(textAfter));
342
-
343
- parent.insertBefore(font1, currentFont);
344
- parent.insertBefore(font2, currentFont);
345
- parent.insertBefore(font3, currentFont);
346
-
347
- parent.removeChild(currentFont);
348
-
349
- moveCaretInside(font2);
350
- }
351
- return;
352
- }
353
-
354
- // ========================
355
- // Trường hợp 3: không ở trong <font> nào → tạo mới
356
- // ========================
357
- const newFont = document.createElement('font');
358
- newFont.setAttribute('size', String(size));
359
- const zwsp = document.createTextNode("\u200B");
360
- newFont.appendChild(zwsp);
361
-
362
- range.insertNode(newFont);
363
- moveCaretInside(newFont);
364
-
365
- // Hàm phụ để đưa caret vào sau ký tự ẩn
366
- function moveCaretInside(fontEl) {
367
- const sel = window.getSelection();
368
- const range = document.createRange();
369
- const textNode = fontEl.firstChild;
370
- range.setStart(textNode, textNode.length);
371
- range.collapse(true);
372
- sel.removeAllRanges();
373
- sel.addRange(range);
374
- }
375
-
376
-
377
- }
378
-
379
- async toggle() {
380
- if (this.customSelect.isVisible) {
381
- this.customSelect.hide();
382
- } else {
383
- await this.showSizePicker();
384
- }
385
- }
386
-
387
- async showSizePicker() {
388
- // Find text-size button in the current editor's toolbar
389
- const editor = Editor.getInstanceById(this.editorId);
390
- if (!editor) return;
391
-
392
- const toolbar = editor.getModule('toolbar');
393
- let textSizeButton = null;
394
-
395
- if (toolbar) {
396
- textSizeButton = toolbar.getButton('text-size');
397
- }
398
-
399
- // Fallback: find button by class in the current editor's toolbar
400
- if (!textSizeButton) {
401
- const toolbarContainer = toolbar?.getContainer();
402
- if (toolbarContainer) {
403
- textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');
404
- }
405
- }
406
-
407
- // Final fallback: find any text-size button in the current editor's wrapper
408
- if (!textSizeButton) {
409
- textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');
410
- }
411
-
412
- if (!textSizeButton) {
413
- console.warn('Text-size button not found for editor:', this.editorId);
414
- return;
415
- }
416
-
417
- const currentSize = this.getCurrentSize();
418
- if (currentSize) {
419
- this.customSelect.setCurrentValue(currentSize);
420
- }
421
-
422
- await this.customSelect.show(textSizeButton);
423
- }
424
-
425
- isActive(size = null) {
426
- this.updateButtonText();
427
- return false;
428
- }
429
-
430
- /**
431
- * Get current text size near caret/selection, return one of '1'..'7'
432
- */
433
- getCurrentSize() {
434
- return TextSize.getCurrentSizeStatic();
435
- }
436
-
437
- /**
438
- * Static method to get current text size
439
- * @returns {string} Current text size
440
- */
441
- static getCurrentSizeStatic() {
442
- const selection = window.getSelection();
443
- if (!selection || !selection.rangeCount) return '4';
444
-
445
- try {
446
- // Try to use queryCommandValue when available (returns 1..7 in many browsers)
447
- const val = document.queryCommandValue('fontSize');
448
- const num = parseInt(val, 10);
449
- if (!isNaN(num) && num >= 1 && num <= 7) {
450
- return String(num);
451
- }
452
- } catch (_) {}
453
-
454
- const range = selection.getRangeAt(0);
455
- let currentNode = range.startContainer;
456
- if (currentNode.nodeType === Node.TEXT_NODE) {
457
- currentNode = currentNode.parentElement;
458
- }
459
-
460
- while (currentNode && currentNode !== document.body) {
461
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
462
- const element = currentNode;
463
- const inline = element.style?.fontSize;
464
- if (inline) return this.normalizeCssSizeToExecSize(inline);
465
-
466
- const computed = window.getComputedStyle(element).fontSize;
467
- if (computed) return this.normalizeCssSizeToExecSize(computed);
468
- }
469
- currentNode = currentNode.parentElement;
470
- }
471
-
472
- return '4';
473
- }
474
-
475
- /**
476
- * Normalize CSS px value to closest execCommand size 1..7
477
- */
478
- normalizeCssSizeToExecSize(cssSize) {
479
- const px = parseFloat(cssSize);
480
- if (isNaN(px)) return '4';
481
- const steps = [10, 12, 14, 16, 20, 28, 36];
482
- let closestIndex = 3; // default to '4' (16px)
483
- let minDiff = Infinity;
484
- for (let i = 0; i < steps.length; i++) {
485
- const diff = Math.abs(px - steps[i]);
486
- if (diff < minDiff) {
487
- minDiff = diff;
488
- closestIndex = i;
489
- }
490
- }
491
- return String(closestIndex + 1);
492
- }
493
- }
494
-
495
- export default TextSize;
496
-
497
-
@@ -1,30 +0,0 @@
1
- import { InlineFormat } from '../core/format.js';
2
- import { saveBeforeFormat } from '../utils/history-helper.js';
3
-
4
- /**
5
- * Underline Format - Handles underline text formatting
6
- * Extracted from FormatManager.js logic
7
- */
8
- class Underline extends InlineFormat {
9
- static formatName = 'underline';
10
- static tagName = 'U';
11
- static alternativeTagNames = ['SPAN'];
12
-
13
- /**
14
- * Toggle underline formatting
15
- */
16
-
17
- toggle() {
18
- // Save state before applying format
19
- saveBeforeFormat();
20
-
21
- if (this.isActive()) {
22
- this.remove();
23
- } else {
24
- this.apply();
25
- }
26
- }
27
-
28
- }
29
-
30
- export default Underline;