@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,547 +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
- * Font Family Format - Handles font family formatting
8
- * Now supports multiple editor instances with separate popup instances
9
- */
10
- class FontFamily extends InlineFormat {
11
- static formatName = 'fontFamily';
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 FontFamily format');
21
- return;
22
- }
23
-
24
- this.editorId = currentEditor.instanceId;
25
-
26
- // Check if this editor already has a font family select instance
27
- let customSelect = currentEditor.getPopupInstance('font-family');
28
-
29
- if (!customSelect) {
30
- // Create new custom select instance for this editor
31
- const fontMap = FontFamily.getFontMap();
32
- const items = Object.values(fontMap).map(fontData => ({
33
- value: fontData.font,
34
- label: fontData.element,
35
- title: fontData.title
36
- }));
37
-
38
- customSelect = new CustomSelect({
39
- items: items,
40
- displayProperty: 'label',
41
- valueProperty: 'value',
42
- className: 'font-family-select',
43
- onItemSelect: (value, item) => {
44
- FontFamily.applyFontFamilyToCurrentSelection(value, this.editorId);
45
- },
46
- editor: currentEditor,
47
- editorId: this.editorId
48
- });
49
-
50
- // Store popup instance in editor
51
- currentEditor.setPopupInstance('font-family', customSelect);
52
- }
53
-
54
- this.customSelect = customSelect;
55
-
56
- // Set up event listener for selection changes
57
- this.setupSelectionListener();
58
- }
59
-
60
- /**
61
- * Create a new FontFamily format instance for a specific editor
62
- * @param {string} editorId - Editor instance ID
63
- * @returns {FontFamily} FontFamily format instance
64
- */
65
- static createForEditor(editorId) {
66
- const editor = Editor.getInstanceById(editorId);
67
- if (!editor) {
68
- console.warn('No editor instance found for ID:', editorId);
69
- return null;
70
- }
71
-
72
- // Temporarily set as current instance
73
- const originalCurrent = Editor.currentInstance;
74
- Editor.currentInstance = editor;
75
-
76
- // Create format instance
77
- const format = new FontFamily();
78
-
79
- // Restore original current instance
80
- Editor.currentInstance = originalCurrent;
81
-
82
- return format;
83
- }
84
-
85
- /**
86
- * Set up event listener for selection changes to update button text
87
- */
88
- setupSelectionListener() {
89
- // Use a debounced function to avoid too many updates
90
- let updateTimeout;
91
- const debouncedUpdate = () => {
92
- clearTimeout(updateTimeout);
93
- updateTimeout = setTimeout(() => {
94
- // Only update if selection is in this editor
95
- const selection = window.getSelection();
96
- if (selection && selection.rangeCount > 0) {
97
- const range = selection.getRangeAt(0);
98
- const editor = Editor.getInstanceById(this.editorId);
99
- if (editor && (editor.editor.contains(range.startContainer) || editor.editor.isSameNode(range.startContainer))) {
100
- this.updateButtonText();
101
- }
102
- }
103
- }, 50); // 50ms delay
104
- };
105
-
106
- // Listen for selection changes
107
- document.addEventListener('selectionchange', debouncedUpdate);
108
-
109
- // Also listen for mouseup and keyup events for immediate feedback
110
- document.addEventListener('mouseup', debouncedUpdate);
111
- document.addEventListener('keyup', debouncedUpdate);
112
-
113
- // Store the listener for cleanup
114
- this.selectionListener = debouncedUpdate;
115
- }
116
-
117
- /**
118
- * Get font map with different font families
119
- */
120
- static getFontMap() {
121
- return {
122
- 'Arial': {
123
- font: 'Arial, sans-serif',
124
- element: '<span style="font-family: Arial, sans-serif">Arial</span>',
125
- title: 'Arial'
126
- },
127
- 'Helvetica': {
128
- font: 'Helvetica, Arial, sans-serif',
129
- element: '<span style="font-family: Helvetica, Arial, sans-serif">Helvetica</span>',
130
- title: 'Helvetica'
131
- },
132
- 'Times New Roman': {
133
- font: '"Times New Roman", Times, serif',
134
- element: '<span style="font-family: \'Times New Roman\', Times, serif">Times New Roman</span>',
135
- title: 'Times New Roman'
136
- },
137
- 'Georgia': {
138
- font: 'Georgia, serif',
139
- element: '<span style="font-family: Georgia, serif">Georgia</span>',
140
- title: 'Georgia'
141
- },
142
- 'Verdana': {
143
- font: 'Verdana, Geneva, sans-serif',
144
- element: '<span style="font-family: Verdana, Geneva, sans-serif">Verdana</span>',
145
- title: 'Verdana'
146
- },
147
- 'Courier New': {
148
- font: '"Courier New", Courier, monospace',
149
- element: '<span style="font-family: \'Courier New\', Courier, monospace">Courier New</span>',
150
- title: 'Courier New'
151
- },
152
- 'Trebuchet MS': {
153
- font: '"Trebuchet MS", Helvetica, sans-serif',
154
- element: '<span style="font-family: \'Trebuchet MS\', Helvetica, sans-serif">Trebuchet MS</span>',
155
- title: 'Trebuchet MS'
156
- },
157
- 'Comic Sans MS': {
158
- font: '"Comic Sans MS", cursive',
159
- element: '<span style="font-family: \'Comic Sans MS\', cursive">Comic Sans MS</span>',
160
- title: 'Comic Sans MS'
161
- },
162
- 'Impact': {
163
- font: 'Impact, Charcoal, sans-serif',
164
- element: '<span style="font-family: Impact, Charcoal, sans-serif">Impact</span>',
165
- title: 'Impact'
166
- },
167
- 'Lucida Console': {
168
- font: '"Lucida Console", Monaco, monospace',
169
- element: '<span style="font-family: \'Lucida Console\', Monaco, monospace">Lucida Console</span>',
170
- title: 'Lucida Console'
171
- }
172
- };
173
- }
174
-
175
-
176
- /**
177
- * Get display name for font
178
- * @param {string} font - Font family value
179
- * @returns {string} Display name
180
- */
181
- static getFontDisplayName(font) {
182
- const fontMap = this.getFontMap();
183
- // Find by font value
184
- for (const [key, value] of Object.entries(fontMap)) {
185
- if (value.font === font || key === font) {
186
- return value.title;
187
- }
188
- }
189
- return 'Arial';
190
- }
191
-
192
- /**
193
- * Update custom button text based on current font
194
- */
195
- updateButtonText() {
196
- const currentFont = this.getCurrentFont();
197
- const displayName = FontFamily.getFontDisplayName(currentFont || 'Arial, sans-serif');
198
-
199
- // Find font-family button in the specific editor's toolbar using editorId
200
- const editor = Editor.getInstanceById(this.editorId);
201
- if (!editor) return;
202
-
203
- const toolbar = editor.getModule('toolbar');
204
- let fontFamilyButton = null;
205
-
206
- if (toolbar) {
207
- fontFamilyButton = toolbar.getButton('font-family');
208
- }
209
-
210
- // Fallback: find button by class in the specific editor's toolbar
211
- if (!fontFamilyButton) {
212
- const toolbarContainer = toolbar?.getContainer();
213
- if (toolbarContainer) {
214
- fontFamilyButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.font-family-btn');
215
- }
216
- }
217
-
218
- // Final fallback: find any font-family button in the specific editor's wrapper
219
- if (!fontFamilyButton) {
220
- fontFamilyButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.font-family-btn');
221
- }
222
-
223
- if (fontFamilyButton && fontFamilyButton.updateText) {
224
- fontFamilyButton.updateText(displayName);
225
- } else if (fontFamilyButton) {
226
- fontFamilyButton.textContent = displayName;
227
- }
228
- }
229
-
230
- /**
231
- * Create element with specific font family
232
- * @param {string} font - Font family value
233
- * @returns {HTMLElement}
234
- */
235
- static create(font = 'Arial, sans-serif') {
236
- const node = document.createElement('span');
237
- node.style.fontFamily = font;
238
- return node;
239
- }
240
-
241
- /**
242
- * Static method to apply font family to current selection
243
- * @param {string} font - Font family value
244
- * @param {string} editorId - Editor instance ID
245
- */
246
- static applyFontFamilyToCurrentSelection(font, editorId = null) {
247
- // Get the correct editor instance
248
- let editor = null;
249
- if (editorId) {
250
- editor = Editor.getInstanceById(editorId);
251
- } else {
252
- editor = Editor.getCurrentInstance();
253
- }
254
-
255
- if (!editor) {
256
- console.warn('No editor instance found for font family application');
257
- return;
258
- }
259
-
260
- const selection = window.getSelection();
261
- if (!selection || !selection.rangeCount) return;
262
-
263
- // Save state before applying format
264
- saveBeforeFormat();
265
-
266
- const range = selection.getRangeAt(0);
267
- const fontFamilyFormat = FontFamily.createForEditor(editorId);
268
- if (fontFamilyFormat) {
269
- fontFamilyFormat.apply(font);
270
-
271
- // Update button text after applying
272
- fontFamilyFormat.updateButtonText();
273
- }
274
-
275
- // Trigger content change after applying format
276
- setTimeout(() => {
277
- if (editor && typeof editor.onContentChange === 'function') {
278
- editor.onContentChange();
279
- }
280
- }, 0);
281
- }
282
-
283
- /**
284
- * Apply font family format with specified font
285
- * @param {string} font - Font family value
286
- */
287
- apply(font = 'Arial, sans-serif') {
288
- const selection = window.getSelection();
289
- if (!selection || !selection.rangeCount) return;
290
- const range = selection.getRangeAt(0);
291
-
292
- function isCaretInsideFontSpan(selection, font) {
293
- if (!selection.rangeCount) return false;
294
- const range = selection.getRangeAt(0);
295
- let node = range.startContainer;
296
-
297
- if (node.nodeType === Node.TEXT_NODE) {
298
- node = node.parentNode;
299
- }
300
-
301
- const fontNormalized = font.split(',')[0].trim().toLowerCase();
302
-
303
- while (node && node.nodeType === Node.ELEMENT_NODE) {
304
- if (node.tagName === 'SPAN') {
305
- const styleFont = node.style.fontFamily;
306
- if (styleFont) {
307
- const styleFontNormalized = styleFont.split(',')[0].trim().toLowerCase();
308
- if (styleFontNormalized === fontNormalized) {
309
- if (
310
- node.childNodes.length === 1 &&
311
- node.firstChild.nodeType === Node.TEXT_NODE &&
312
- node.firstChild.textContent === '\u200B'
313
- ) {
314
- return true; // Đang trong span marker rồi
315
- }
316
- return true; // Đang trong span font-family đó
317
- }
318
- }
319
- }
320
- node = node.parentNode;
321
- }
322
- return false;
323
- }
324
-
325
- // Hàm đặt caret vào bên trong span mới
326
- function moveCaretInside(el) {
327
- const sel = window.getSelection();
328
- const range = document.createRange();
329
- const textNode = el.firstChild;
330
- range.setStart(textNode, textNode.length);
331
- range.collapse(true);
332
- sel.removeAllRanges();
333
- sel.addRange(range);
334
- }
335
-
336
- if (range.collapsed) {
337
- if (isCaretInsideFontSpan(selection, font)) {
338
- // Đã ở trong span font rồi, không cần làm gì thêm
339
- return;
340
- }
341
-
342
- let node = range.startContainer;
343
- let offset = range.startOffset;
344
-
345
- if (node.nodeType === Node.TEXT_NODE) {
346
- node = node.parentNode;
347
- }
348
-
349
- const currentSpan = node.closest && node.closest('span');
350
-
351
- // Trường hợp 1: caret trong span rỗng chứa \u200B
352
- if (currentSpan && currentSpan.textContent === "\u200B") {
353
- currentSpan.style.fontFamily = font;
354
- return;
355
- }
356
-
357
- // Trường hợp 2: caret trong span có text thật
358
- if (currentSpan && currentSpan.firstChild && currentSpan.firstChild.nodeType === Node.TEXT_NODE) {
359
- const textNode = currentSpan.firstChild;
360
- const caretPos = range.startOffset;
361
-
362
- const textBefore = textNode.data.slice(0, caretPos);
363
- const textAfter = textNode.data.slice(caretPos);
364
-
365
- const parent = currentSpan.parentNode;
366
-
367
- if (caretPos === 0) {
368
- // Chèn span mới trước currentSpan
369
- const newSpan = document.createElement('span');
370
- newSpan.style.fontFamily = font;
371
- newSpan.appendChild(document.createTextNode('\u200B'));
372
- parent.insertBefore(newSpan, currentSpan);
373
- moveCaretInside(newSpan);
374
- } else if (caretPos === textNode.data.length) {
375
- // Chèn span mới sau currentSpan
376
- const newSpan = document.createElement('span');
377
- newSpan.style.fontFamily = font;
378
- newSpan.appendChild(document.createTextNode('\u200B'));
379
- parent.insertBefore(newSpan, currentSpan.nextSibling);
380
- moveCaretInside(newSpan);
381
- } else {
382
- // Tách thành 3 span
383
- const span1 = document.createElement('span');
384
- span1.style.fontFamily = currentSpan.style.fontFamily;
385
- span1.appendChild(document.createTextNode(textBefore));
386
-
387
- const span2 = document.createElement('span');
388
- span2.style.fontFamily = font;
389
- span2.appendChild(document.createTextNode('\u200B'));
390
-
391
- const span3 = document.createElement('span');
392
- span3.style.fontFamily = currentSpan.style.fontFamily;
393
- span3.appendChild(document.createTextNode(textAfter));
394
-
395
- parent.insertBefore(span1, currentSpan);
396
- parent.insertBefore(span2, currentSpan);
397
- parent.insertBefore(span3, currentSpan);
398
- parent.removeChild(currentSpan);
399
-
400
- moveCaretInside(span2);
401
- }
402
- return;
403
- }
404
-
405
- // Trường hợp 3: không ở trong span nào → tạo mới
406
- const newSpan = document.createElement('span');
407
- newSpan.style.fontFamily = font;
408
- newSpan.appendChild(document.createTextNode('\u200B'));
409
- range.insertNode(newSpan);
410
- moveCaretInside(newSpan);
411
-
412
- } else {
413
- // Có selection → dùng execCommand áp dụng fontName
414
- document.execCommand('fontName', false, font);
415
- }
416
- }
417
-
418
-
419
- /**
420
- * Toggle font family format - shows/hides font picker
421
- */
422
- async toggle(anchorButton = null) {
423
- if (this.customSelect.isVisible) {
424
- this.customSelect.hide();
425
- } else {
426
- await this.showFontPicker(anchorButton);
427
- }
428
- }
429
-
430
- /**
431
- * Show custom select positioned relative to font family button on toolbar
432
- */
433
- async showFontPicker(anchorButton = null) {
434
- // Use provided anchor button or find the default toolbar button
435
- let fontFamilyButton = anchorButton;
436
-
437
- if (!fontFamilyButton) {
438
- // Find font-family button in the current editor's toolbar
439
- const editor = Editor.getInstanceById(this.editorId);
440
- if (!editor) return;
441
-
442
- const toolbar = editor.getModule('toolbar');
443
-
444
- if (toolbar) {
445
- fontFamilyButton = toolbar.getButton('font-family');
446
- }
447
-
448
- // Fallback: find button by class in the current editor's toolbar
449
- if (!fontFamilyButton) {
450
- const toolbarContainer = toolbar?.getContainer();
451
- if (toolbarContainer) {
452
- fontFamilyButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.font-family-btn');
453
- }
454
- }
455
-
456
- // Final fallback: find any font-family button in the current editor's wrapper
457
- if (!fontFamilyButton) {
458
- fontFamilyButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.font-family-btn');
459
- }
460
- }
461
-
462
- if (!fontFamilyButton) return;
463
-
464
- // Update current selection before showing
465
- const currentFont = this.getCurrentFont();
466
- if (currentFont) {
467
- this.customSelect.setCurrentValue(currentFont);
468
- }
469
-
470
- await this.customSelect.show(fontFamilyButton);
471
- }
472
-
473
- /**
474
- * Check if font family format is active - always return false (no active state)
475
- * Only update button text to show current font
476
- * @param {string} font - Optional specific font to check
477
- * @returns {boolean}
478
- */
479
- isActive(font = null) {
480
- // Always update button text to show current font
481
- this.updateButtonText();
482
-
483
- // Never show active state for font family button
484
- return false;
485
- }
486
-
487
- /**
488
- * Get current font family of the selection
489
- * @returns {string|null} Current font family or null
490
- */
491
- getCurrentFont() {
492
- const selection = window.getSelection();
493
- if (!selection || !selection.rangeCount) return null;
494
-
495
- const range = selection.getRangeAt(0);
496
- let currentNode = range.startContainer;
497
-
498
- // If text node, get parent element
499
- if (currentNode.nodeType === Node.TEXT_NODE) {
500
- currentNode = currentNode.parentElement;
501
- }
502
-
503
- // Get the specific editor instance
504
- const editor = Editor.getInstanceById(this.editorId);
505
- if (!editor) return 'Arial, sans-serif';
506
-
507
- // Check if the selection is within this editor
508
- if (!editor.editor.contains(currentNode) && !editor.editor.isSameNode(currentNode)) {
509
- // Selection is not in this editor, return default
510
- return 'Arial, sans-serif';
511
- }
512
-
513
- // Find element with font-family style
514
- while (currentNode && currentNode !== document.body) {
515
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
516
- const element = currentNode;
517
-
518
- // Priority 1: Check if this element has explicit inline font-family
519
- if (element.style.fontFamily) {
520
- return element.style.fontFamily;
521
- }
522
-
523
- // Priority 2: Check computed font-family
524
- const computedStyle = window.getComputedStyle(element);
525
- const fontFamily = computedStyle.fontFamily;
526
- if (fontFamily && fontFamily !== 'initial' && fontFamily !== 'inherit') {
527
- return fontFamily;
528
- }
529
- }
530
- currentNode = currentNode.parentElement;
531
- }
532
-
533
- // Default fallback
534
- return 'Arial, sans-serif';
535
- }
536
-
537
- /**
538
- * Set current font for future typing
539
- * @param {string} font - Font family value
540
- */
541
- setCurrentFont(font) {
542
- // Store for future typing operations
543
- this.currentFont = font;
544
- }
545
- }
546
-
547
- export default FontFamily;