@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.
- package/README.md +73 -22
- package/dist/rich-editor.esm.js +2 -0
- package/dist/rich-editor.esm.js.map +1 -0
- package/dist/rich-editor.min.js +2 -0
- package/dist/rich-editor.min.js.map +1 -0
- package/package.json +12 -7
- package/index.js +0 -221
- package/lib/core/editor.js +0 -1175
- package/lib/core/format.js +0 -542
- package/lib/core/module.js +0 -81
- package/lib/core/registry.js +0 -152
- package/lib/formats/background.js +0 -212
- package/lib/formats/bold.js +0 -67
- package/lib/formats/capitalization.js +0 -563
- package/lib/formats/color.js +0 -165
- package/lib/formats/emoji.js +0 -282
- package/lib/formats/font-family.js +0 -547
- package/lib/formats/heading.js +0 -502
- package/lib/formats/image.js +0 -344
- package/lib/formats/import.js +0 -385
- package/lib/formats/indent.js +0 -297
- package/lib/formats/italic.js +0 -27
- package/lib/formats/line-height.js +0 -558
- package/lib/formats/link.js +0 -251
- package/lib/formats/list.js +0 -635
- package/lib/formats/strike.js +0 -31
- package/lib/formats/subscript.js +0 -36
- package/lib/formats/superscript.js +0 -35
- package/lib/formats/table.js +0 -288
- package/lib/formats/tag.js +0 -304
- package/lib/formats/text-align.js +0 -421
- package/lib/formats/text-size.js +0 -497
- package/lib/formats/underline.js +0 -30
- package/lib/formats/video.js +0 -372
- package/lib/modules/block-toolbar.js +0 -628
- package/lib/modules/code-view.js +0 -434
- package/lib/modules/history.js +0 -410
- package/lib/modules/resize-handles.js +0 -677
- package/lib/modules/table-toolbar.js +0 -618
- package/lib/modules/toolbar.js +0 -424
- package/lib/styles-loader.js +0 -144
- package/lib/styles.css +0 -2123
- package/lib/ui/color-picker.js +0 -296
- package/lib/ui/customselect.js +0 -319
- package/lib/ui/emoji-picker.js +0 -196
- package/lib/ui/icons.js +0 -413
- package/lib/ui/image-popup.js +0 -444
- package/lib/ui/import-popup.js +0 -288
- package/lib/ui/link-popup.js +0 -191
- package/lib/ui/list-picker.js +0 -307
- package/lib/ui/select-button.js +0 -61
- package/lib/ui/table-popup.js +0 -171
- package/lib/ui/tag-popup.js +0 -249
- package/lib/ui/text-align-picker.js +0 -281
- package/lib/ui/video-popup.js +0 -422
- package/lib/utils/history-helper.js +0 -50
- package/lib/utils/popup-helper.js +0 -219
- package/lib/utils/popup-positioning.js +0 -231
package/lib/ui/tag-popup.js
DELETED
|
@@ -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;
|