@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,249 +0,0 @@
1
- /**
2
- * Tag Popup Component - Popup for inserting custom tags
3
- */
4
- import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
5
-
6
- class TagPopup {
7
- constructor(options = {}) {
8
- this.options = {
9
- onTagInsert: null,
10
- editor: null,
11
- ...options
12
- };
13
-
14
- this.popup = null;
15
- this.isVisible = false;
16
- this.clickOutsideHandler = null;
17
- this.selectedTagType = 'mention';
18
-
19
- this.createTagPopup();
20
- }
21
-
22
- createTagPopup() {
23
- this.popup = document.createElement('div');
24
- this.popup.className = 'tag-popup';
25
-
26
- const content = document.createElement('div');
27
- content.className = 'tag-popup-content';
28
-
29
- // Title
30
- const title = document.createElement('h3');
31
- title.textContent = 'Insert tags';
32
- title.className = 'yjd-input-title';
33
- content.appendChild(title);
34
-
35
- // Tag type selector
36
- const group1 = document.createElement('div');
37
- group1.className = 'yjd-input-group';
38
-
39
- const typeLabel = document.createElement('label');
40
- typeLabel.textContent = 'Type';
41
- typeLabel.className = 'yjd-input-label';
42
-
43
- this.typeSelect = document.createElement('select');
44
- this.typeSelect.className = 'yjd-select-input';
45
- this.typeSelect.innerHTML = `
46
- <option value="mention">Mention</option>
47
- <option value="hashtag">Hashtag</option>
48
- <option value="custom">Custom</option>
49
- `;
50
- this.typeSelect.addEventListener('change', () => this.updateSuggestions());
51
-
52
- group1.appendChild(typeLabel);
53
- group1.appendChild(this.typeSelect);
54
- content.appendChild(group1);
55
-
56
- // Content input
57
- const group2 = document.createElement('div');
58
- group2.className = 'yjd-input-group';
59
-
60
- const contentLabel = document.createElement('label');
61
- contentLabel.textContent = 'Content';
62
- contentLabel.className = 'yjd-input-label';
63
-
64
- this.contentInput = document.createElement('input');
65
- this.contentInput.type = 'text';
66
- this.contentInput.className = 'yjd-input';
67
- this.contentInput.placeholder = 'Please enter tag content';
68
- this.contentInput.addEventListener('input', () => this.updateInsertButton());
69
- this.contentInput.addEventListener('keydown', (e) => {
70
- if (e.key === 'Enter') {
71
- e.preventDefault();
72
- this.insertTag();
73
- }
74
- });
75
-
76
- group2.appendChild(contentLabel);
77
- group2.appendChild(this.contentInput);
78
- content.appendChild(group2);
79
-
80
- // Suggestions
81
- const group3 = document.createElement('div');
82
- group3.className = 'yjd-input-group';
83
- this.suggestionsContainer = document.createElement('div');
84
- this.suggestionsContainer.className = 'tag-suggestions-container';
85
-
86
- const suggestionsLabel = document.createElement('label');
87
- suggestionsLabel.textContent = 'Suggestions';
88
- suggestionsLabel.className = 'yjd-input-label';
89
-
90
- this.suggestionsList = document.createElement('div');
91
- this.suggestionsList.className = 'yjd-suggestions-list';
92
-
93
- this.suggestionsContainer.appendChild(this.suggestionsList);
94
- group3.appendChild(suggestionsLabel);
95
- group3.appendChild(this.suggestionsContainer);
96
- content.appendChild(group3);
97
-
98
- // Buttons
99
- const buttonContainer = document.createElement('div');
100
- buttonContainer.className = 'yjd-button-container';
101
-
102
- const cancelButton = document.createElement('button');
103
- cancelButton.type = 'button';
104
- cancelButton.className = 'yjd-button-cancel';
105
- cancelButton.textContent = 'Cancel';
106
- cancelButton.addEventListener('click', () => this.hide());
107
-
108
- this.insertButton = document.createElement('button');
109
- this.insertButton.type = 'button';
110
- this.insertButton.className = 'yjd-button-confirm';
111
- this.insertButton.textContent = 'Insert Tag';
112
- this.insertButton.disabled = true;
113
- this.insertButton.addEventListener('click', () => this.insertTag());
114
-
115
- buttonContainer.appendChild(cancelButton);
116
- buttonContainer.appendChild(this.insertButton);
117
- content.appendChild(buttonContainer);
118
-
119
- this.popup.appendChild(content);
120
- appendPopup(this.popup);
121
-
122
- // Prevent focus loss when clicking on popup
123
- if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {
124
- this.options.editor.preventFocusLoss(this.popup);
125
- }
126
-
127
- this.updateSuggestions();
128
- }
129
-
130
- updateSuggestions() {
131
- this.selectedTagType = this.typeSelect.value;
132
- this.suggestionsList.innerHTML = '';
133
-
134
- const suggestions = this.getSuggestions(this.selectedTagType);
135
-
136
- suggestions.forEach(suggestion => {
137
- const suggestionButton = document.createElement('button');
138
- suggestionButton.type = 'button';
139
- suggestionButton.className = 'yjd-suggestion-button';
140
- suggestionButton.textContent = suggestion;
141
-
142
- suggestionButton.addEventListener('click', () => {
143
- this.contentInput.value = suggestion;
144
- this.updateInsertButton();
145
- this.contentInput.focus();
146
- });
147
-
148
- this.suggestionsList.appendChild(suggestionButton);
149
- });
150
- }
151
-
152
- getSuggestions(tagType) {
153
- const suggestions = {
154
- mention: ['john', 'admin', 'team', 'support'],
155
- hashtag: ['urgent', 'done', 'important'],
156
- custom: ['warning', 'info', 'success']
157
- };
158
-
159
- return suggestions[tagType] || [];
160
- }
161
-
162
- updateInsertButton() {
163
- const hasContent = this.contentInput.value.trim();
164
- this.insertButton.disabled = !hasContent;
165
- }
166
-
167
- insertTag() {
168
- const content = this.contentInput.value.trim();
169
-
170
- if (!content) return;
171
-
172
- if (this.options.onTagInsert) {
173
- this.options.onTagInsert(this.selectedTagType, content);
174
- }
175
-
176
- this.hide();
177
- this.reset();
178
- }
179
-
180
- reset() {
181
- this.contentInput.value = '';
182
- this.typeSelect.value = 'mention';
183
- this.selectedTagType = 'mention';
184
- this.updateInsertButton();
185
- this.updateSuggestions();
186
- }
187
-
188
- setupClickOutside() {
189
- if (this.clickOutsideHandler) {
190
- document.removeEventListener('click', this.clickOutsideHandler);
191
- }
192
-
193
- this.clickOutsideHandler = (e) => {
194
- if (!this.popup.contains(e.target)) {
195
- this.hide();
196
- }
197
- };
198
-
199
- setTimeout(() => {
200
- document.addEventListener('click', this.clickOutsideHandler);
201
- }, 100);
202
- }
203
-
204
- removeClickOutside() {
205
- if (this.clickOutsideHandler) {
206
- document.removeEventListener('click', this.clickOutsideHandler);
207
- this.clickOutsideHandler = null;
208
- }
209
- }
210
-
211
- show(anchor) {
212
- if (!anchor) return;
213
-
214
- // Calculate and set popup position
215
- const position = calculatePopupPosition(anchor, this.popup, {
216
- offsetY: 5,
217
- offsetX: 0
218
- });
219
- setPopupPosition(this.popup, position);
220
-
221
- this.popup.classList.add('visible');
222
- this.isVisible = true;
223
-
224
- this.setupClickOutside();
225
-
226
- setTimeout(() => {
227
- this.contentInput.focus();
228
- }, 100);
229
- }
230
-
231
- hide() {
232
- this.popup.classList.remove('visible');
233
- this.isVisible = false;
234
- this.removeClickOutside();
235
- }
236
-
237
- destroy() {
238
- this.removeClickOutside();
239
-
240
- if (this.popup && this.popup.parentNode) {
241
- this.popup.parentNode.removeChild(this.popup);
242
- }
243
-
244
- this.popup = null;
245
- this.isVisible = false;
246
- }
247
- }
248
-
249
- export default TagPopup;
@@ -1,281 +0,0 @@
1
- import IconUtils from './icons.js';
2
- import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
3
-
4
- /**
5
- * Text Align Picker Component - Popup for selecting text alignment
6
- */
7
- class TextAlignPicker {
8
- constructor(options = {}) {
9
- this.options = {
10
- alignments: [
11
- { value: 'left', label: 'Align Left', icon: 'align-left' },
12
- { value: 'center', label: 'Align Center', icon: 'align-center' },
13
- { value: 'right', label: 'Align Right', icon: 'align-right' },
14
- { value: 'justify', label: 'Justify', icon: 'align-justify' }
15
- ],
16
- onAlignSelect: null,
17
- ...options
18
- };
19
-
20
- this.popup = null;
21
- this.isVisible = false;
22
- this.currentAlignment = 'left';
23
- this.clickOutsideHandler = null;
24
-
25
- this.createAlignPicker();
26
- }
27
-
28
- /**
29
- * Create text align picker popup
30
- */
31
- createAlignPicker() {
32
- // Create popup
33
- this.popup = document.createElement('div');
34
- this.popup.className = 'text-align-picker-popup';
35
-
36
- // Create alignment buttons
37
- this.createAlignmentButtons();
38
-
39
- // Add popup to container
40
- appendPopup(this.popup);
41
- }
42
-
43
- /**
44
- * Create alignment buttons
45
- */
46
- async createAlignmentButtons() {
47
- const buttonContainer = document.createElement('div');
48
- buttonContainer.className = 'align-button-container';
49
-
50
- // Icons are now inline, no need to preload
51
-
52
- // Create buttons
53
- for (const alignment of this.options.alignments) {
54
- const alignButton = document.createElement('button');
55
- alignButton.type = 'button';
56
- alignButton.className = 'align-button';
57
- alignButton.dataset.alignment = alignment.value;
58
- alignButton.title = alignment.label;
59
-
60
- // Add icon
61
- const iconSvg = IconUtils.getIcon(alignment.icon);
62
- if (iconSvg) {
63
- alignButton.innerHTML = `
64
- <span class="icon-wrapper">${iconSvg}</span>
65
- <span class="label-text">${alignment.label}</span>
66
- `;
67
- } else {
68
- alignButton.textContent = alignment.label.charAt(0);
69
- }
70
-
71
- alignButton.addEventListener('click', (e) => {
72
- e.preventDefault();
73
- e.stopPropagation();
74
- this.selectAlignment(alignment.value);
75
- });
76
-
77
- buttonContainer.appendChild(alignButton);
78
- }
79
-
80
- this.popup.appendChild(buttonContainer);
81
- }
82
-
83
- /**
84
- * Setup click outside handler
85
- */
86
- setupClickOutside() {
87
- if (this.clickOutsideHandler) {
88
- document.removeEventListener('click', this.clickOutsideHandler);
89
- }
90
-
91
- this.clickOutsideHandler = (e) => {
92
- if (!this.popup.contains(e.target)) {
93
- this.hide();
94
- }
95
- };
96
-
97
- // Add slight delay to avoid immediate close
98
- setTimeout(() => {
99
- document.addEventListener('click', this.clickOutsideHandler);
100
- }, 100);
101
- }
102
-
103
- /**
104
- * Remove click outside handler
105
- */
106
- removeClickOutside() {
107
- if (this.clickOutsideHandler) {
108
- document.removeEventListener('click', this.clickOutsideHandler);
109
- this.clickOutsideHandler = null;
110
- }
111
- }
112
-
113
- /**
114
- * Show text align picker popup
115
- * @param {HTMLElement} anchor - Element to position popup relative to
116
- */
117
- show(anchor) {
118
- if (!anchor) return;
119
-
120
- // Ensure popup is in DOM
121
- if (!document.body.contains(this.popup)) {
122
- appendPopup(this.popup);
123
- }
124
-
125
- // Update current alignment state
126
- this.updateCurrentAlignment();
127
-
128
- // Calculate and set popup position
129
- const position = calculatePopupPosition(anchor, this.popup, {
130
- offsetY: 5,
131
- offsetX: 0
132
- });
133
- setPopupPosition(this.popup, position);
134
-
135
- // Show popup by adding visible class
136
- this.popup.classList.add('visible');
137
- this.isVisible = true;
138
-
139
- // Setup click outside handler
140
- this.setupClickOutside();
141
- }
142
-
143
- /**
144
- * Hide text align picker popup
145
- */
146
- hide() {
147
- this.popup.classList.remove('visible');
148
- this.isVisible = false;
149
- this.removeClickOutside();
150
- }
151
-
152
- /**
153
- * Select alignment and trigger callback
154
- * @param {string} alignment - Selected alignment
155
- */
156
- selectAlignment(alignment) {
157
- this.currentAlignment = alignment;
158
-
159
- if (this.options.onAlignSelect) {
160
- this.options.onAlignSelect(alignment);
161
- }
162
-
163
- this.hide();
164
- }
165
-
166
- /**
167
- * Update current alignment state based on selection
168
- */
169
- updateCurrentAlignment() {
170
- const selection = window.getSelection();
171
- if (!selection || !selection.rangeCount) return;
172
-
173
- try {
174
- const range = selection.getRangeAt(0);
175
-
176
- // Lấy tất cả block trong vùng chọn
177
- const blocks = this.getSelectedBlockElements(range);
178
-
179
- // Nếu có block → lấy block đầu tiên, nếu không thì fallback về block bao quanh
180
- const firstBlock = blocks.length > 0
181
- ? blocks[0]
182
- : this.getBlockElement(range.commonAncestorContainer);
183
-
184
- if (firstBlock) {
185
- const textAlign = window.getComputedStyle(firstBlock).textAlign;
186
- this.currentAlignment =
187
- textAlign === 'left' || textAlign === 'start' || !textAlign
188
- ? 'left'
189
- : textAlign;
190
- } else {
191
- this.currentAlignment = 'left';
192
- }
193
-
194
- // Cập nhật trạng thái nút trong popup
195
- const buttons = this.popup.querySelectorAll('.align-button');
196
- buttons.forEach(button => {
197
- if (button.dataset.alignment === this.currentAlignment) {
198
- button.classList.add('active');
199
- } else {
200
- button.classList.remove('active');
201
- }
202
- });
203
-
204
- // Cập nhật icon trên toolbar
205
- this.updateToolbarButtonIcon(this.currentAlignment);
206
- } catch (error) {
207
- console.error('Error updating current alignment:', error);
208
- }
209
- }
210
-
211
- /**
212
- * Lấy tất cả block element trong vùng chọn
213
- */
214
- getSelectedBlockElements(range) {
215
- const blocks = [];
216
- const startBlock = this.getBlockElement(range.startContainer);
217
- const endBlock = this.getBlockElement(range.endContainer);
218
-
219
- if (startBlock) blocks.push(startBlock);
220
-
221
- if (startBlock && endBlock && startBlock !== endBlock) {
222
- let current = startBlock;
223
- while (current && current !== endBlock) {
224
- current = current.nextElementSibling;
225
- if (current && this.getBlockElement(current) && !blocks.includes(current)) {
226
- blocks.push(current);
227
- }
228
- }
229
- if (endBlock && !blocks.includes(endBlock)) {
230
- blocks.push(endBlock);
231
- }
232
- }
233
-
234
- return blocks;
235
- }
236
-
237
- /**
238
- * Update toolbar button icon based on alignment
239
- * @param {string} alignment - Current alignment
240
- */
241
- updateToolbarButtonIcon(alignment) {
242
- // Import TextAlign class to use its static method
243
- import('../formats/text-align.js').then(module => {
244
- const TextAlign = module.default;
245
- TextAlign.updateToolbarButtonIcon(alignment);
246
- }).catch(error => {
247
- console.warn('Could not import TextAlign class:', error);
248
- });
249
- }
250
-
251
- /**
252
- * Get the block element containing the given node
253
- */
254
- getBlockElement(node) {
255
- if (!node) return null;
256
-
257
- let currentNode = node;
258
- while (currentNode && currentNode !== document.body) {
259
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
260
- const tagName = currentNode.tagName;
261
- if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {
262
- return currentNode;
263
- }
264
- }
265
- currentNode = currentNode.parentNode;
266
- }
267
- return null;
268
- }
269
-
270
- /**
271
- * Destroy text align picker
272
- */
273
- destroy() {
274
- this.removeClickOutside();
275
- if (this.popup && this.popup.parentNode) {
276
- this.popup.parentNode.removeChild(this.popup);
277
- }
278
- }
279
- }
280
-
281
- export default TextAlignPicker;