@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,421 +0,0 @@
1
- import { BlockFormat } from '../core/format.js';
2
- import TextAlignPicker from '../ui/text-align-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
- * Text Align Format - Handles text alignment formatting
9
- * Now supports multiple editor instances with separate popup instances
10
- */
11
- class TextAlign extends BlockFormat {
12
- static formatName = 'text-align';
13
- static tagName = 'P';
14
- static attribute = 'style';
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 TextAlign format');
23
- return;
24
- }
25
-
26
- this.editorId = currentEditor.instanceId;
27
-
28
- // Check if this editor already has a text align picker instance
29
- let alignPicker = currentEditor.getPopupInstance('text-align');
30
-
31
- if (!alignPicker) {
32
- // Create new text align picker instance for this editor
33
- alignPicker = new TextAlignPicker({
34
- onAlignSelect: (alignment) => {
35
- TextAlign.applyAlignToCurrentSelection(alignment, this.editorId);
36
- },
37
- editor: currentEditor,
38
- editorId: this.editorId
39
- });
40
-
41
- // Store popup instance in editor
42
- currentEditor.setPopupInstance('text-align', alignPicker);
43
- }
44
-
45
- this.alignPicker = alignPicker;
46
- }
47
-
48
- /**
49
- * Create a new TextAlign format instance for a specific editor
50
- * @param {string} editorId - Editor instance ID
51
- * @returns {TextAlign} TextAlign 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 TextAlign();
66
-
67
- // Restore original current instance
68
- Editor.currentInstance = originalCurrent;
69
-
70
- return format;
71
- }
72
-
73
- /**
74
- * Create block element with text alignment
75
- * @param {string} value - Alignment value (left, center, right, justify)
76
- * @returns {HTMLElement}
77
- */
78
- static create(value) {
79
- const node = document.createElement(this.tagName);
80
- if (value && value !== 'left') {
81
- node.style.textAlign = value;
82
- }
83
- return node;
84
- }
85
-
86
- /**
87
- * Static method to apply alignment to current selection or cursor position
88
- * @param {string} alignment - Alignment value
89
- * @param {string} editorId - Editor instance ID
90
- */
91
- static applyAlignToCurrentSelection(alignment, editorId = null) {
92
- // Get the correct editor instance
93
- let editor = null;
94
- if (editorId) {
95
- editor = Editor.getInstanceById(editorId);
96
- } else {
97
- editor = Editor.getCurrentInstance();
98
- }
99
-
100
- if (!editor) {
101
- console.warn('No editor instance found for text alignment application');
102
- return;
103
- }
104
-
105
- const selection = window.getSelection();
106
- if (!selection || !selection.rangeCount) return;
107
-
108
- // Save state before applying format
109
- saveBeforeFormat();
110
- // Lưu vị trí caret trước khi thay đổi
111
- const originalRange = selection.getRangeAt(0);
112
- const caretContainer = originalRange.endContainer;
113
- const caretOffset = originalRange.endOffset;
114
- try {
115
- const range = selection.getRangeAt(0);
116
- const blockElements = TextAlign.getSelectedBlockElements(range);
117
-
118
- if (blockElements.length === 0) {
119
- // If no block elements found, create one
120
- document.execCommand('formatBlock', false, 'p');
121
- const newRange = selection.getRangeAt(0);
122
- const newBlocks = TextAlign.getSelectedBlockElements(newRange);
123
- newBlocks.forEach(block => {
124
- TextAlign.applyAlignmentToBlock(block, alignment);
125
- });
126
- } else {
127
- // Apply alignment to existing blocks
128
- blockElements.forEach(block => {
129
- TextAlign.applyAlignmentToBlock(block, alignment);
130
- });
131
- }
132
-
133
- // Update toolbar button icon after applying alignment
134
- TextAlign.updateToolbarButtonIcon(alignment, editorId);
135
- // Khôi phục caret
136
- selection.removeAllRanges();
137
- const newCaretRange = document.createRange();
138
- newCaretRange.setStart(caretContainer, caretOffset);
139
- newCaretRange.collapse(true);
140
- selection.addRange(newCaretRange);
141
-
142
- } catch (error) {
143
- console.error('Error applying text alignment:', error);
144
- }
145
-
146
- // Trigger content change after applying format
147
- setTimeout(() => {
148
- if (editor && typeof editor.onContentChange === 'function') {
149
- editor.onContentChange();
150
- }
151
- }, 0);
152
- }
153
-
154
- /**
155
- * Apply alignment to a specific block element
156
- */
157
- static applyAlignmentToBlock(block, alignment) {
158
- if (alignment === 'left') {
159
- block.style.textAlign = '';
160
- } else {
161
- block.style.textAlign = alignment;
162
- }
163
- }
164
-
165
- /**
166
- * Get icon name for alignment value
167
- * @param {string} alignment - Alignment value
168
- * @returns {string} Icon name
169
- */
170
- static getIconNameForAlignment(alignment) {
171
- const iconMap = {
172
- 'left': 'align-left',
173
- 'center': 'align-center',
174
- 'right': 'align-right',
175
- 'justify': 'align-justify'
176
- };
177
- return iconMap[alignment] || 'align-center';
178
- }
179
-
180
- /**
181
- * Update toolbar button icon based on alignment
182
- * @param {string} alignment - Current alignment
183
- * @param {string} editorId - Editor instance ID
184
- */
185
- static updateToolbarButtonIcon(alignment, editorId = null) {
186
- // Get the correct editor instance
187
- let editor = null;
188
- if (editorId) {
189
- editor = Editor.getInstanceById(editorId);
190
- } else {
191
- editor = Editor.getCurrentInstance();
192
- }
193
-
194
- if (!editor) return;
195
-
196
- const toolbar = editor.getModule('toolbar');
197
- let button = null;
198
-
199
- if (toolbar) {
200
- button = toolbar.getButton('text-align');
201
- }
202
-
203
- // Fallback: find button by class in the current editor's toolbar
204
- if (!button) {
205
- const toolbarContainer = toolbar?.getContainer();
206
- if (toolbarContainer) {
207
- button = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-align-btn');
208
- }
209
- }
210
-
211
- // Final fallback: find any text-align button in the current editor's wrapper
212
- if (!button) {
213
- button = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-align-btn');
214
- }
215
-
216
- if (!button) return;
217
-
218
- const iconName = TextAlign.getIconNameForAlignment(alignment);
219
- const titleMap = {
220
- 'left': 'Align Left',
221
- 'center': 'Align Center',
222
- 'right': 'Align Right',
223
- 'justify': 'Justify'
224
- };
225
-
226
- // Update button title
227
- button.title = titleMap[alignment] || 'Text Alignment';
228
-
229
- // Update icon
230
- const svgContent = IconUtils.getIcon(iconName);
231
- if (svgContent) {
232
- const iconSpan = button.querySelector('.icon');
233
- if (iconSpan) {
234
- iconSpan.innerHTML = svgContent;
235
- } else {
236
- button.innerHTML = `<span class="icon">${svgContent}</span>`;
237
- }
238
- }
239
- }
240
-
241
- /**
242
- * Get all selected block elements
243
- */
244
- static getSelectedBlockElements(range) {
245
- const blocks = [];
246
-
247
- // Xác định block chứa điểm bắt đầu và kết thúc
248
- const startBlock = TextAlign.getBlockElement(range.startContainer);
249
- const endBlock = TextAlign.getBlockElement(range.endContainer);
250
-
251
- if (!startBlock || !endBlock) return blocks;
252
-
253
- // Nếu chỉ trong 1 block
254
- if (startBlock === endBlock) {
255
- blocks.push(startBlock);
256
- return blocks;
257
- }
258
-
259
- // Duyệt từ startBlock tới endBlock
260
- let currentBlock = startBlock;
261
- while (currentBlock) {
262
- // Chỉ thêm block nếu nó giao với range
263
- const blockRange = document.createRange();
264
- blockRange.selectNodeContents(currentBlock);
265
-
266
- if (range.compareBoundaryPoints(Range.END_TO_START, blockRange) < 0 &&
267
- range.compareBoundaryPoints(Range.START_TO_END, blockRange) > 0) {
268
- blocks.push(currentBlock);
269
- }
270
-
271
- if (currentBlock === endBlock) break;
272
- currentBlock = TextAlign.getNextBlockElement(currentBlock);
273
- }
274
-
275
- return blocks;
276
- }
277
-
278
- /**
279
- * Get the block element containing the given node
280
- */
281
- static getBlockElement(node) {
282
- if (!node) return null;
283
-
284
- let currentNode = node;
285
- while (currentNode && currentNode !== document.body) {
286
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
287
- const tagName = currentNode.tagName;
288
- if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {
289
- return currentNode;
290
- }
291
- }
292
- currentNode = currentNode.parentNode;
293
- }
294
- return null;
295
- }
296
-
297
- /**
298
- * Get next block element in document order
299
- */
300
- static getNextBlockElement(element) {
301
- let currentNode = element.nextSibling;
302
-
303
- while (currentNode) {
304
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
305
- const tagName = currentNode.tagName;
306
- if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {
307
- return currentNode;
308
- }
309
- }
310
- currentNode = currentNode.nextSibling;
311
- }
312
-
313
- return null;
314
- }
315
-
316
- /**
317
- * Apply alignment formatting with specified value
318
- * @param {string} value - Alignment value (left, center, right, justify)
319
- */
320
- apply(value = 'left') {
321
- TextAlign.applyAlignToCurrentSelection(value, this.editorId);
322
- }
323
-
324
- /**
325
- * Remove alignment formatting (reset to left)
326
- */
327
- remove() {
328
- TextAlign.applyAlignToCurrentSelection('left', this.editorId);
329
- }
330
-
331
- /**
332
- * Toggle alignment formatting - shows/hides alignment picker
333
- */
334
- toggle() {
335
- if (this.alignPicker.isVisible) {
336
- this.alignPicker.hide();
337
- } else {
338
- this.showAlignPicker();
339
- }
340
- }
341
-
342
- /**
343
- * Show alignment picker positioned relative to align button on toolbar
344
- */
345
- showAlignPicker() {
346
- // Find text-align button in the current editor's toolbar
347
- const editor = Editor.getInstanceById(this.editorId);
348
- if (!editor) return;
349
-
350
- const toolbar = editor.getModule('toolbar');
351
- let alignButton = null;
352
-
353
- if (toolbar) {
354
- alignButton = toolbar.getButton('text-align');
355
- }
356
-
357
- // Fallback: find button by class in the current editor's toolbar
358
- if (!alignButton) {
359
- const toolbarContainer = toolbar?.getContainer();
360
- if (toolbarContainer) {
361
- alignButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-align-btn');
362
- }
363
- }
364
-
365
- // Final fallback: find any text-align button in the current editor's wrapper
366
- if (!alignButton) {
367
- alignButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-align-btn');
368
- }
369
-
370
- if (!alignButton) {
371
- console.warn('Text-align button not found for editor:', this.editorId);
372
- return;
373
- }
374
-
375
- this.alignPicker.show(alignButton);
376
- }
377
-
378
- /**
379
- * Check if specific alignment is active in current selection
380
- * Always returns false because text-align button should not have active state
381
- * Instead, the button icon changes to reflect current alignment
382
- */
383
- isActive(alignment = null) {
384
- // Update button icon based on current alignment
385
- const currentAlignment = TextAlign.getCurrentAlignment();
386
- TextAlign.updateToolbarButtonIcon(currentAlignment, this.editorId);
387
-
388
- // Always return false - no active state for this button
389
- return false;
390
- }
391
-
392
- /**
393
- * Get current alignment of selection
394
- */
395
- static getCurrentAlignment() {
396
- const selection = window.getSelection();
397
- if (!selection || !selection.rangeCount) return 'left';
398
-
399
- try {
400
- const range = selection.getRangeAt(0);
401
- // Lấy tất cả block trong vùng chọn
402
- const blocks = TextAlign.getSelectedBlockElements(range);
403
-
404
- // Nếu có nhiều block -> lấy block đầu tiên
405
- const firstBlock = blocks.length > 0
406
- ? blocks[0]
407
- : TextAlign.getBlockElement(range.commonAncestorContainer);
408
-
409
- if (!firstBlock) return 'left';
410
-
411
- const textAlign = window.getComputedStyle(firstBlock).textAlign;
412
- return textAlign === 'left' || textAlign === 'start' || !textAlign ? 'left' : textAlign;
413
- } catch (error) {
414
- console.error('Error getting current alignment:', error);
415
- return 'left';
416
- }
417
- }
418
-
419
- }
420
-
421
- export default TextAlign;