@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,563 +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
- * Capitalization Format - Handles text capitalization
8
- * Now supports multiple editor instances with separate popup instances
9
- */
10
- class Capitalization extends InlineFormat {
11
- static formatName = 'capitalization';
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 Capitalization format');
21
- return;
22
- }
23
-
24
- this.editorId = currentEditor.instanceId;
25
-
26
- // Check if this editor already has a capitalization select instance
27
- let customSelect = currentEditor.getPopupInstance('capitalization');
28
-
29
- if (!customSelect) {
30
- // Create new custom select instance for this editor
31
- const capMap = Capitalization.getCapitalizationMap();
32
- const items = Object.values(capMap).map(capData => ({
33
- value: capData.style,
34
- label: capData.element,
35
- title: capData.title
36
- }));
37
-
38
- customSelect = new CustomSelect({
39
- items: items,
40
- displayProperty: 'label',
41
- valueProperty: 'value',
42
- className: 'capitalization-select',
43
- onItemSelect: (value, item) => {
44
- Capitalization.applyCapitalizationToCurrentSelection(value, this.editorId);
45
- },
46
- editor: currentEditor,
47
- editorId: this.editorId
48
- });
49
-
50
- // Store popup instance in editor
51
- currentEditor.setPopupInstance('capitalization', customSelect);
52
- }
53
-
54
- this.customSelect = customSelect;
55
- }
56
-
57
- /**
58
- * Create a new Capitalization format instance for a specific editor
59
- * @param {string} editorId - Editor instance ID
60
- * @returns {Capitalization} Capitalization 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 Capitalization();
75
-
76
- // Restore original current instance
77
- Editor.currentInstance = originalCurrent;
78
-
79
- return format;
80
- }
81
-
82
- /**
83
- * Get capitalization map with different text transformations
84
- */
85
- static getCapitalizationMap() {
86
- return {
87
- 'capitalize': {
88
- style: 'capitalize',
89
- element: '<span>Capitalize</span>',
90
- title: 'Capitalize'
91
- },
92
- 'uppercase': {
93
- style: 'uppercase',
94
- element: '<span>UPPERCASE</span>',
95
- title: 'UPPERCASE'
96
- },
97
- 'lowercase': {
98
- style: 'lowercase',
99
- element: '<span>lowercase</span>',
100
- title: 'lowercase'
101
- },
102
- 'small-caps': {
103
- style: 'small-caps',
104
- element: '<span>Small Caps</span>',
105
- title: 'Small Caps'
106
- }
107
- };
108
- }
109
-
110
- /**
111
- * Get display name for capitalization
112
- * @param {string} style - Text transform value
113
- * @returns {string} Display name
114
- */
115
- static getCapitalizationDisplayName(style) {
116
- const capMap = this.getCapitalizationMap();
117
- return capMap[style]?.title || 'Capitalization';
118
- }
119
-
120
- /**
121
- * Update custom button text based on current capitalization
122
- */
123
- updateButtonText() {
124
- const currentCap = this.getCurrentCapitalization();
125
- const displayName = Capitalization.getCapitalizationDisplayName(currentCap || 'none');
126
-
127
- // Find capitalization button in the current editor's toolbar
128
- const editor = Editor.getInstanceById(this.editorId);
129
- if (!editor) return;
130
-
131
- const toolbar = editor.getModule('toolbar');
132
- let capitalizationButton = null;
133
-
134
- if (toolbar) {
135
- capitalizationButton = toolbar.getButton('capitalization');
136
- }
137
-
138
- // Fallback: find button by class in the current editor's toolbar
139
- if (!capitalizationButton) {
140
- const toolbarContainer = toolbar?.getContainer();
141
- if (toolbarContainer) {
142
- capitalizationButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.capitalization-btn');
143
- }
144
- }
145
-
146
- // Final fallback: find any capitalization button in the current editor's wrapper
147
- if (!capitalizationButton) {
148
- capitalizationButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.capitalization-btn');
149
- }
150
-
151
- if (capitalizationButton && capitalizationButton.updateText) {
152
- capitalizationButton.updateText(displayName);
153
- } else if (capitalizationButton) {
154
- capitalizationButton.textContent = displayName;
155
- }
156
- }
157
-
158
- /**
159
- * Create element with specific text transformation
160
- * @param {string} style - Text transform value
161
- * @returns {HTMLElement}
162
- */
163
- static create(style = 'none') {
164
- const node = document.createElement('span');
165
- if (style === 'small-caps') {
166
- node.style.fontVariant = 'small-caps';
167
- } else {
168
- node.style.textTransform = style;
169
- }
170
- return node;
171
- }
172
-
173
- /**
174
- * Static method to apply capitalization to current selection
175
- * @param {string} style - Text transform value
176
- * @param {string} editorId - Editor instance ID
177
- */
178
- static applyCapitalizationToCurrentSelection(style, editorId = null) {
179
- // Get the correct editor instance
180
- let editor = null;
181
- if (editorId) {
182
- editor = Editor.getInstanceById(editorId);
183
- } else {
184
- editor = Editor.getCurrentInstance();
185
- }
186
-
187
- if (!editor) {
188
- console.warn('No editor instance found for capitalization application');
189
- return;
190
- }
191
-
192
- const selection = window.getSelection();
193
- if (!selection || !selection.rangeCount) return;
194
-
195
- // Save state before applying format
196
- saveBeforeFormat();
197
-
198
- const range = selection.getRangeAt(0);
199
- const capFormat = Capitalization.createForEditor(editorId);
200
- if (capFormat) {
201
- capFormat.apply(style);
202
-
203
- // Update button text after applying
204
- capFormat.updateButtonText();
205
- }
206
-
207
- // Trigger content change after applying format
208
- setTimeout(() => {
209
- if (editor && typeof editor.onContentChange === 'function') {
210
- editor.onContentChange();
211
- }
212
- }, 0);
213
- }
214
-
215
- /**
216
- * Check if an element has capitalization-related inline or computed styles
217
- * @param {Element} element
218
- * @returns {boolean}
219
- */
220
- hasCapitalizationStyling(element) {
221
- if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;
222
- if (element.style.fontVariant === 'small-caps') return true;
223
- if (element.style.textTransform && element.style.textTransform !== 'none') return true;
224
- const computed = window.getComputedStyle(element);
225
- if (computed.fontVariant === 'small-caps') return true;
226
- if (computed.textTransform && computed.textTransform !== 'none') return true;
227
- return false;
228
- }
229
-
230
- /**
231
- * Determine whether an element is our capitalization wrapper (inline styles only)
232
- * @param {Element} element
233
- * @returns {boolean}
234
- */
235
- isCapitalizationElement(element) {
236
- if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;
237
- if (element.style.fontVariant === 'small-caps') return true;
238
- if (element.style.textTransform && element.style.textTransform !== 'none') return true;
239
- return false;
240
- }
241
-
242
- /**
243
- * Find nearest ancestor element that is a capitalization wrapper
244
- * @param {Node} node
245
- * @returns {Element|null}
246
- */
247
- findAncestorCapitalizationElement(node) {
248
- let current = node;
249
- if (!current) return null;
250
- if (current.nodeType === Node.TEXT_NODE) current = current.parentElement;
251
- while (current && current !== document.body) {
252
- if (this.isCapitalizationElement(current)) return current;
253
- current = current.parentElement;
254
- }
255
- return null;
256
- }
257
-
258
- /**
259
- * Apply capitalization style directly to an element
260
- * @param {Element} element
261
- * @param {string} style
262
- */
263
- setElementCapitalizationStyle(element, style) {
264
- if (!element) return;
265
- if (style === 'small-caps') {
266
- element.style.fontVariant = 'small-caps';
267
- element.style.textTransform = '';
268
- } else if (style === 'none') {
269
- element.style.fontVariant = '';
270
- element.style.textTransform = '';
271
- } else {
272
- element.style.fontVariant = '';
273
- element.style.textTransform = style;
274
- }
275
- }
276
-
277
- /**
278
- * Apply capitalization format with specified style
279
- * @param {string} style - Text transform value
280
- */
281
- apply(style = 'none') {
282
- const selection = window.getSelection();
283
- if (!selection || !selection.rangeCount) return;
284
-
285
- // Lưu trạng thái trước khi format
286
- saveBeforeFormat();
287
-
288
- const range = selection.getRangeAt(0);
289
-
290
- // Hàm đổi chữ theo style
291
- function transformText(text, style) {
292
- switch (style) {
293
- case 'uppercase':
294
- return text.toUpperCase();
295
- case 'lowercase':
296
- return text.toLowerCase();
297
- case 'capitalize':
298
- text =text.toLowerCase();
299
- return text.replace(/\b\w/g, char => char.toUpperCase());
300
- default:
301
- return text; // 'none' hoặc không đổi
302
- }
303
- }
304
- function removeEmptyElements(node) {
305
- if (!node) return;
306
-
307
- // Duyệt cây DOM từ node này xuống
308
- const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT, null);
309
- const toRemove = [];
310
-
311
- while (walker.nextNode()) {
312
- const el = walker.currentNode;
313
- // Nếu không có text hoặc chỉ toàn khoảng trắng & không có element con
314
- if (!el.textContent.trim() && el.childElementCount === 0) {
315
- toRemove.push(el);
316
- }
317
- }
318
-
319
- toRemove.forEach(el => el.remove());
320
- }
321
-
322
- // Nếu có selection: đổi text bên trong
323
- const contents = range.extractContents();
324
- const walker = document.createTreeWalker(contents, NodeFilter.SHOW_TEXT, null);
325
-
326
- while (walker.nextNode()) {
327
- const textNode = walker.currentNode;
328
- textNode.textContent = transformText(textNode.textContent, style);
329
- }
330
-
331
- range.deleteContents();
332
- range.insertNode(contents);
333
- removeEmptyElements(range.commonAncestorContainer);
334
-
335
- // Giữ nguyên selection
336
- selection.removeAllRanges();
337
- selection.addRange(range);
338
- }
339
-
340
- /**
341
- * Remove existing capitalization formatting from range
342
- * @param {Range} range - Selection range
343
- */
344
- removeExistingCapitalization(range) {
345
- const root = range.commonAncestorContainer;
346
- const elementsToProcess = new Set();
347
-
348
- // Helper to maybe add element
349
- const maybeAdd = (el) => {
350
- if (el && el.nodeType === Node.ELEMENT_NODE && this.hasCapitalizationStyling(el) && range.intersectsNode(el)) {
351
- elementsToProcess.add(el);
352
- }
353
- };
354
-
355
- // Include the root if applicable
356
- if (root && root.nodeType === Node.ELEMENT_NODE) {
357
- maybeAdd(root);
358
- }
359
-
360
- // Walk descendants
361
- const walker = document.createTreeWalker(
362
- root,
363
- NodeFilter.SHOW_ELEMENT,
364
- {
365
- acceptNode: (node) => (range.intersectsNode(node) && this.hasCapitalizationStyling(node)) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
366
- }
367
- );
368
- let node;
369
- while ((node = walker.nextNode())) {
370
- elementsToProcess.add(node);
371
- }
372
-
373
- // Include ancestors from start and end containers
374
- const addAncestors = (startNode) => {
375
- let current = startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode;
376
- while (current && current !== document.body) {
377
- maybeAdd(current);
378
- current = current.parentElement;
379
- }
380
- };
381
- addAncestors(range.startContainer);
382
- addAncestors(range.endContainer);
383
-
384
- Array.from(elementsToProcess).forEach(element => {
385
- // Clear text transform and font variant styles
386
- element.style.textTransform = '';
387
- element.style.fontVariant = '';
388
-
389
- // If element has no other styles, unwrap it
390
- if (!element.style.cssText.trim() && !element.className) {
391
- this.unwrapElement(element);
392
- }
393
- });
394
- }
395
-
396
- /**
397
- * Unwrap an element, moving its children to its parent
398
- * @param {Element} element - Element to unwrap
399
- */
400
- unwrapElement(element) {
401
- const parent = element.parentNode;
402
- if (!parent) return;
403
-
404
- while (element.firstChild) {
405
- parent.insertBefore(element.firstChild, element);
406
- }
407
- parent.removeChild(element);
408
- }
409
-
410
- /**
411
- * Toggle capitalization format - shows/hides capitalization picker
412
- */
413
- async toggle() {
414
- if (this.customSelect.isVisible) {
415
- this.customSelect.hide();
416
- } else {
417
- await this.showCapitalizationPicker();
418
- }
419
- }
420
-
421
- /**
422
- * Show custom select positioned relative to capitalization button on toolbar
423
- */
424
- async showCapitalizationPicker() {
425
- // Find capitalization button in the current editor's toolbar
426
- const editor = Editor.getInstanceById(this.editorId);
427
- if (!editor) return;
428
-
429
- const toolbar = editor.getModule('toolbar');
430
- let capitalizationButton = null;
431
-
432
- if (toolbar) {
433
- capitalizationButton = toolbar.getButton('capitalization');
434
- }
435
-
436
- // Fallback: find button by class in the current editor's toolbar
437
- if (!capitalizationButton) {
438
- const toolbarContainer = toolbar?.getContainer();
439
- if (toolbarContainer) {
440
- capitalizationButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.capitalization-btn');
441
- }
442
- }
443
-
444
- // Final fallback: find any capitalization button in the current editor's wrapper
445
- if (!capitalizationButton) {
446
- capitalizationButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.capitalization-btn');
447
- }
448
-
449
- if (!capitalizationButton) {
450
- console.warn('Capitalization button not found for editor:', this.editorId);
451
- return;
452
- }
453
-
454
- // Update current selection before showing
455
- const currentCap = this.getCurrentCapitalization();
456
- if (currentCap) {
457
- this.customSelect.setCurrentValue(currentCap);
458
- }
459
-
460
- await this.customSelect.show(capitalizationButton);
461
- }
462
-
463
- /**
464
- * Check if capitalization format is active - always return false (no active state)
465
- * Only update button text to show current capitalization
466
- * @param {string} style - Optional specific style to check
467
- * @returns {boolean}
468
- */
469
- isActive(style = null) {
470
- // Always update button text to show current capitalization
471
- this.updateButtonText();
472
-
473
- // Never show active state for capitalization button
474
- return false;
475
- }
476
-
477
- /**
478
- * Get current capitalization of the selection
479
- * @returns {string|null} Current text transform or null
480
- */
481
- getCurrentCapitalization() {
482
- const selection = window.getSelection();
483
- if (!selection || !selection.rangeCount) return null;
484
-
485
- const range = selection.getRangeAt(0);
486
- let currentNode = range.startContainer;
487
-
488
- // If text node, get parent element
489
- if (currentNode.nodeType === Node.TEXT_NODE) {
490
- currentNode = currentNode.parentElement;
491
- }
492
-
493
- // Find element with text-transform or font-variant style
494
- while (currentNode && currentNode !== document.body) {
495
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
496
- const element = currentNode;
497
-
498
- // Priority 1: Check if this element has explicit inline styles
499
- if (element.style.fontVariant === 'small-caps') {
500
- return 'small-caps';
501
- }
502
- if (element.style.textTransform && element.style.textTransform !== 'none') {
503
- return element.style.textTransform;
504
- }
505
-
506
- // Priority 2: Check computed styles
507
- const computedStyle = window.getComputedStyle(element);
508
- if (computedStyle.fontVariant === 'small-caps') {
509
- return 'small-caps';
510
- }
511
- if (computedStyle.textTransform && computedStyle.textTransform !== 'none') {
512
- return computedStyle.textTransform;
513
- }
514
- }
515
- currentNode = currentNode.parentElement;
516
- }
517
-
518
- // Default fallback
519
- return 'none';
520
- }
521
-
522
- /**
523
- * Set current capitalization for future typing
524
- * @param {string} style - Text transform value
525
- */
526
- setCurrentCapitalization(style) {
527
- // Store for future typing operations
528
- this.currentCapitalization = style;
529
- }
530
-
531
- /**
532
- * Quick toggle methods for common capitalizations
533
- */
534
- static toggleUppercase() {
535
- const cap = new Capitalization();
536
- const current = cap.getCurrentCapitalization();
537
- const newStyle = current === 'uppercase' ? 'none' : 'uppercase';
538
- Capitalization.applyCapitalizationToCurrentSelection(newStyle);
539
- }
540
-
541
- static toggleLowercase() {
542
- const cap = new Capitalization();
543
- const current = cap.getCurrentCapitalization();
544
- const newStyle = current === 'lowercase' ? 'none' : 'lowercase';
545
- Capitalization.applyCapitalizationToCurrentSelection(newStyle);
546
- }
547
-
548
- static toggleCapitalize() {
549
- const cap = new Capitalization();
550
- const current = cap.getCurrentCapitalization();
551
- const newStyle = current === 'capitalize' ? 'none' : 'capitalize';
552
- Capitalization.applyCapitalizationToCurrentSelection(newStyle);
553
- }
554
-
555
- static toggleSmallCaps() {
556
- const cap = new Capitalization();
557
- const current = cap.getCurrentCapitalization();
558
- const newStyle = current === 'small-caps' ? 'none' : 'small-caps';
559
- Capitalization.applyCapitalizationToCurrentSelection(newStyle);
560
- }
561
- }
562
-
563
- export default Capitalization;