@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,558 +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
- * Line Height Format - Handles line height formatting
8
- * Now supports multiple editor instances with separate popup instances
9
- */
10
- class LineHeight extends InlineFormat {
11
- static formatName = 'lineHeight';
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 LineHeight format');
21
- return;
22
- }
23
-
24
- this.editorId = currentEditor.instanceId;
25
-
26
- // Check if this editor already has a line height select instance
27
- let customSelect = currentEditor.getPopupInstance('line-height');
28
-
29
- if (!customSelect) {
30
- // Create new custom select instance for this editor
31
- const heightMap = LineHeight.getHeightMap();
32
- const items = Object.values(heightMap).map(heightData => ({
33
- value: heightData.height,
34
- label: heightData.element,
35
- title: heightData.title
36
- }));
37
-
38
- customSelect = new CustomSelect({
39
- items: items,
40
- displayProperty: 'label',
41
- valueProperty: 'value',
42
- className: 'line-height-select',
43
- onItemSelect: (value, item) => {
44
- LineHeight.applyLineHeightToCurrentSelection(value, this.editorId);
45
- },
46
- editor: currentEditor,
47
- editorId: this.editorId
48
- });
49
-
50
- // Store popup instance in editor
51
- currentEditor.setPopupInstance('line-height', 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 LineHeight format instance for a specific editor
62
- * @param {string} editorId - Editor instance ID
63
- * @returns {LineHeight} LineHeight 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 LineHeight();
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 height map with different line heights
119
- */
120
- static getHeightMap() {
121
- return {
122
- '1.0': {
123
- height: '1',
124
- element: '<span>1.0</span>',
125
- title: '1.0'
126
- },
127
- '1.2': {
128
- height: '1.2',
129
- element: '<span>1.2</span>',
130
- title: '1.2'
131
- },
132
- '1.5': {
133
- height: '1.5',
134
- element: '<span>1.5</span>',
135
- title: '1.5'
136
- },
137
- '1.8': {
138
- height: '1.8',
139
- element: '<span>1.8</span>',
140
- title: '1.8'
141
- },
142
- '2.0': {
143
- height: '2',
144
- element: '<span>2.0</span>',
145
- title: '2.0'
146
- },
147
- '2.5': {
148
- height: '2.5',
149
- element: '<span>2.5</span>',
150
- title: '2.5'
151
- },
152
- '3.0': {
153
- height: '3',
154
- element: '<span>3.0</span>',
155
- title: '3.0'
156
- }
157
- };
158
- }
159
-
160
- /**
161
- * Get display name for line height
162
- * @param {string} height - Line height value
163
- * @returns {string} Display name
164
- */
165
- static getHeightDisplayName(height) {
166
- const heightMap = this.getHeightMap();
167
- return heightMap[height]?.title || 'line height';
168
- }
169
-
170
- /**
171
- * Update custom button text based on current line height
172
- */
173
- updateButtonText() {
174
- const currentHeight = this.getCurrentHeight();
175
- const displayName = LineHeight.getHeightDisplayName(currentHeight || '1.15');
176
-
177
- // Find line-height button in the specific editor's toolbar using editorId
178
- const editor = Editor.getInstanceById(this.editorId);
179
- if (!editor) return;
180
-
181
- const toolbar = editor.getModule('toolbar');
182
- let lineHeightButton = null;
183
-
184
- if (toolbar) {
185
- lineHeightButton = toolbar.getButton('line-height');
186
- }
187
-
188
- // Fallback: find button by class in the specific editor's toolbar
189
- if (!lineHeightButton) {
190
- const toolbarContainer = toolbar?.getContainer();
191
- if (toolbarContainer) {
192
- lineHeightButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.line-height-btn');
193
- }
194
- }
195
-
196
- // Final fallback: find any line-height button in the specific editor's wrapper
197
- if (!lineHeightButton) {
198
- lineHeightButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.line-height-btn');
199
- }
200
-
201
- if (lineHeightButton && lineHeightButton.updateText) {
202
- lineHeightButton.updateText(displayName);
203
- } else if (lineHeightButton) {
204
- lineHeightButton.textContent = displayName;
205
- }
206
- }
207
-
208
- /**
209
- * Create element with specific line height
210
- * @param {string} height - Line height value
211
- * @returns {HTMLElement}
212
- */
213
- static create(height = '1.15') {
214
- const node = document.createElement('span');
215
- node.style.lineHeight = height;
216
- return node;
217
- }
218
-
219
- /**
220
- * Static method to apply line height to current selection
221
- * @param {string} height - Line height value
222
- * @param {string} editorId - Editor instance ID
223
- */
224
- static applyLineHeightToCurrentSelection(height, editorId = null) {
225
- // Get the correct editor instance
226
- let editor = null;
227
- if (editorId) {
228
- editor = Editor.getInstanceById(editorId);
229
- } else {
230
- editor = Editor.getCurrentInstance();
231
- }
232
-
233
- if (!editor) {
234
- console.warn('No editor instance found for line height application');
235
- return;
236
- }
237
-
238
- const selection = window.getSelection();
239
- if (!selection || !selection.rangeCount) return;
240
-
241
- // Save state before applying format
242
- saveBeforeFormat();
243
-
244
- const range = selection.getRangeAt(0);
245
- const lineHeightFormat = LineHeight.createForEditor(editorId);
246
- if (lineHeightFormat) {
247
- lineHeightFormat.apply(height);
248
-
249
- // Update button text after applying
250
- lineHeightFormat.updateButtonText();
251
- }
252
-
253
- // Trigger content change after applying format
254
- setTimeout(() => {
255
- if (editor && typeof editor.onContentChange === 'function') {
256
- editor.onContentChange();
257
- }
258
- }, 0);
259
- }
260
-
261
- /**
262
- * Apply line height format with specified height
263
- * @param {string} height - Line height value
264
- */
265
- apply(height = '1.15') {
266
- const selection = window.getSelection();
267
- if (!selection || !selection.rangeCount) return;
268
- // Hàm đặt caret vào bên trong span mới
269
- function moveCaretInside(el) {
270
- const sel = window.getSelection();
271
- const range = document.createRange();
272
- const textNode = el.firstChild;
273
- range.setStart(textNode, textNode.length);
274
- range.collapse(true);
275
- sel.removeAllRanges();
276
- sel.addRange(range);
277
- }
278
- // Save state before applying format
279
- saveBeforeFormat();
280
-
281
- const range = selection.getRangeAt(0);
282
-
283
- if (range.collapsed) {
284
- // No selection - set style for future typing
285
- let node = range.startContainer;
286
- if (node.nodeType === Node.TEXT_NODE) {
287
- node = node.parentNode;
288
- }
289
-
290
- // Tìm phần tử block cha gần nhất (div, p, li, ...)
291
- const blockParent = node.closest('div, p, li, section, article') || node;
292
- blockParent.style.lineHeight = height;
293
- moveCaretInside(blockParent);
294
-
295
- return;
296
- }
297
-
298
- // Apply to block elements if possible for better line height effect
299
- const blockElements = this.getBlockElementsInRange(range);
300
-
301
- if (blockElements.length > 0) {
302
- // Apply to block elements
303
- blockElements.forEach(block => {
304
- block.style.lineHeight = height;
305
- });
306
- } else {
307
- // Fallback: wrap in span with line-height
308
- const heightSpan = this.constructor.create(height);
309
-
310
- try {
311
- const contents = range.extractContents();
312
- heightSpan.appendChild(contents);
313
- range.insertNode(heightSpan);
314
-
315
- // Select the content in the span
316
- const newRange = document.createRange();
317
- newRange.selectNodeContents(heightSpan);
318
- selection.removeAllRanges();
319
- selection.addRange(newRange);
320
- } catch (error) {
321
- console.warn('Failed to apply line height manually:', error);
322
- }
323
- }
324
- }
325
-
326
- /**
327
- * Get block elements within the range
328
- * @param {Range} range - Selection range
329
- * @returns {Array} Array of block elements
330
- */
331
- getBlockElementsInRange(range) {
332
- const blockElements = [];
333
- const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI'];
334
-
335
- // Create a fragment of the selection
336
- const fragment = range.cloneContents();
337
-
338
- // Get all potential block elements in the fragment
339
- const walker = document.createTreeWalker(
340
- fragment,
341
- NodeFilter.SHOW_ELEMENT,
342
- {
343
- acceptNode: (node) => {
344
- return blockTags.includes(node.tagName) ?
345
- NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
346
- }
347
- }
348
- );
349
-
350
- // Get corresponding elements from the actual document
351
- let node = walker.nextNode();
352
- while (node) {
353
- // Find the actual element in the document that corresponds to this fragment node
354
- const actualNode = range.commonAncestorContainer.querySelector(
355
- `${node.tagName.toLowerCase()}:not([data-processed])`
356
- );
357
- if (actualNode && range.intersectsNode(actualNode)) {
358
- blockElements.push(actualNode);
359
- // Mark as processed to avoid duplicates
360
- actualNode.setAttribute('data-processed', 'true');
361
- }
362
- node = walker.nextNode();
363
- }
364
-
365
- // Clean up the temporary attribute
366
- blockElements.forEach(el => el.removeAttribute('data-processed'));
367
-
368
- // If no block elements found in selection, get the closest parent block element
369
- if (blockElements.length === 0) {
370
- let currentNode = range.startContainer;
371
-
372
- // If text node, get parent element
373
- if (currentNode.nodeType === Node.TEXT_NODE) {
374
- currentNode = currentNode.parentElement;
375
- }
376
-
377
- // Find parent block element
378
- while (currentNode && currentNode !== document.body) {
379
- if (currentNode.nodeType === Node.ELEMENT_NODE &&
380
- blockTags.includes(currentNode.tagName)) {
381
- blockElements.push(currentNode);
382
- break;
383
- }
384
- currentNode = currentNode.parentElement;
385
- }
386
- }
387
-
388
- return blockElements;
389
- }
390
-
391
- /**
392
- * Toggle line height format - shows/hides height picker
393
- */
394
- async toggle() {
395
- if (this.customSelect.isVisible) {
396
- this.customSelect.hide();
397
- } else {
398
- await this.showHeightPicker();
399
- }
400
- }
401
-
402
- /**
403
- * Show custom select positioned relative to line height button on toolbar
404
- */
405
- async showHeightPicker() {
406
- // Find line-height button in the current editor's toolbar
407
- const editor = Editor.getInstanceById(this.editorId);
408
- if (!editor) return;
409
-
410
- const toolbar = editor.getModule('toolbar');
411
- let lineHeightButton = null;
412
-
413
- if (toolbar) {
414
- lineHeightButton = toolbar.getButton('line-height');
415
- }
416
-
417
- // Fallback: find button by class in the current editor's toolbar
418
- if (!lineHeightButton) {
419
- const toolbarContainer = toolbar?.getContainer();
420
- if (toolbarContainer) {
421
- lineHeightButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.line-height-btn');
422
- }
423
- }
424
-
425
- // Final fallback: find any line-height button in the current editor's wrapper
426
- if (!lineHeightButton) {
427
- lineHeightButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.line-height-btn');
428
- }
429
-
430
- if (!lineHeightButton) {
431
- console.warn('Line-height button not found for editor:', this.editorId);
432
- return;
433
- }
434
-
435
- // Update current selection before showing
436
- const currentHeight = this.getCurrentHeight();
437
- if (currentHeight) {
438
- this.customSelect.setCurrentValue(currentHeight);
439
- }
440
-
441
- await this.customSelect.show(lineHeightButton);
442
- }
443
-
444
- /**
445
- * Check if line height format is active - always return false (no active state)
446
- * Only update button text to show current height
447
- * @param {string} height - Optional specific height to check
448
- * @returns {boolean}
449
- */
450
- isActive(height = null) {
451
- // Always update button text to show current height
452
- this.updateButtonText();
453
-
454
- // Never show active state for line height button
455
- return false;
456
- }
457
-
458
- /**
459
- * Get current line height of the selection
460
- * @returns {string|null} Current line height or null
461
- */
462
- getCurrentHeight() {
463
- const selection = window.getSelection();
464
- if (!selection || !selection.rangeCount) return null;
465
-
466
- const range = selection.getRangeAt(0);
467
- let currentNode = range.startContainer;
468
-
469
- // If text node, get parent element
470
- if (currentNode.nodeType === Node.TEXT_NODE) {
471
- currentNode = currentNode.parentElement;
472
- }
473
-
474
- // Get the specific editor instance
475
- const editor = Editor.getInstanceById(this.editorId);
476
- if (!editor) return '1.15';
477
-
478
- // Check if the selection is within this editor
479
- if (!editor.editor.contains(currentNode) && !editor.editor.isSameNode(currentNode)) {
480
- // Selection is not in this editor, return default
481
- return '1.15';
482
- }
483
-
484
- // Find element with line-height style
485
- while (currentNode && currentNode !== document.body) {
486
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
487
- const element = currentNode;
488
-
489
- // Priority 1: Check if this element has explicit inline line-height
490
- if (element.style.lineHeight) {
491
- const height = element.style.lineHeight;
492
- return this.normalizeHeightValue(height);
493
- }
494
-
495
- // Priority 2: Check computed line-height
496
- const computedStyle = window.getComputedStyle(element);
497
- const lineHeight = computedStyle.lineHeight;
498
-
499
- if (lineHeight && lineHeight !== 'normal' && lineHeight !== 'initial' && lineHeight !== 'inherit') {
500
- // Convert pixel values to relative values if possible
501
- if (lineHeight.endsWith('px')) {
502
- const fontSize = parseFloat(computedStyle.fontSize);
503
- const lineHeightPx = parseFloat(lineHeight);
504
- if (fontSize > 0) {
505
- const relative = (lineHeightPx / fontSize).toFixed(2);
506
- return this.normalizeHeightValue(relative);
507
- }
508
- }
509
- return this.normalizeHeightValue(lineHeight);
510
- }
511
- }
512
- currentNode = currentNode.parentElement;
513
- }
514
-
515
- // Default fallback
516
- return '1.15';
517
- }
518
-
519
- /**
520
- * Normalize height value to match heightMap keys
521
- * @param {string} height - Raw height value
522
- * @returns {string} Normalized height value
523
- */
524
- normalizeHeightValue(height) {
525
- if (!height) return '1.15';
526
-
527
- // Convert to number and back to string to normalize
528
- const numValue = parseFloat(height);
529
- if (isNaN(numValue)) return '1.15';
530
-
531
- // Round to 1 decimal place and convert back to string
532
- const normalized = numValue.toFixed(1);
533
-
534
- // Check if this normalized value exists in our heightMap
535
- const heightMap = this.constructor.getHeightMap();
536
- if (heightMap[normalized]) {
537
- return normalized;
538
- }
539
-
540
- // If not in map, return the original value
541
- return height;
542
- }
543
-
544
-
545
- /**
546
- * Clean up event listeners to prevent memory leaks
547
- */
548
- destroy() {
549
- if (this.selectionListener) {
550
- document.removeEventListener('selectionchange', this.selectionListener);
551
- document.removeEventListener('mouseup', this.selectionListener);
552
- document.removeEventListener('keyup', this.selectionListener);
553
- this.selectionListener = null;
554
- }
555
- }
556
- }
557
-
558
- export default LineHeight;