@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/import-popup.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Import Popup Component - Popup for importing various file types
|
|
3
|
-
*/
|
|
4
|
-
import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
|
|
5
|
-
|
|
6
|
-
class ImportPopup {
|
|
7
|
-
constructor(options = {}) {
|
|
8
|
-
this.options = {
|
|
9
|
-
onImport: null,
|
|
10
|
-
...options
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
this.popup = null;
|
|
14
|
-
this.isVisible = false;
|
|
15
|
-
this.clickOutsideHandler = null;
|
|
16
|
-
this.selectedFile = null;
|
|
17
|
-
this.fileType = null;
|
|
18
|
-
|
|
19
|
-
this.createImportPopup();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
createImportPopup() {
|
|
23
|
-
this.popup = document.createElement('div');
|
|
24
|
-
this.popup.className = 'import-popup';
|
|
25
|
-
|
|
26
|
-
const content = document.createElement('div');
|
|
27
|
-
content.className = 'import-popup-content';
|
|
28
|
-
|
|
29
|
-
// Title
|
|
30
|
-
const title = document.createElement('h3');
|
|
31
|
-
title.textContent = 'Import File';
|
|
32
|
-
title.className = 'import-popup-title';
|
|
33
|
-
content.appendChild(title);
|
|
34
|
-
|
|
35
|
-
// File type selector
|
|
36
|
-
const typeContainer = document.createElement('div');
|
|
37
|
-
typeContainer.className = 'import-type-container';
|
|
38
|
-
|
|
39
|
-
const typeLabel = document.createElement('label');
|
|
40
|
-
typeLabel.textContent = 'File Type:';
|
|
41
|
-
typeLabel.className = 'import-input-label';
|
|
42
|
-
|
|
43
|
-
this.typeSelect = document.createElement('select');
|
|
44
|
-
this.typeSelect.className = 'import-type-select';
|
|
45
|
-
this.typeSelect.innerHTML = `
|
|
46
|
-
<option value="">Select file type...</option>
|
|
47
|
-
<option value="html">HTML (.html, .htm)</option>
|
|
48
|
-
<option value="excel">Excel/CSV (.csv, .xlsx, .xls)</option>
|
|
49
|
-
<option value="pdf">PDF (.pdf)</option>
|
|
50
|
-
<option value="word">Word (.doc, .docx)</option>
|
|
51
|
-
`;
|
|
52
|
-
this.typeSelect.addEventListener('change', () => this.updateFileInput());
|
|
53
|
-
|
|
54
|
-
typeContainer.appendChild(typeLabel);
|
|
55
|
-
typeContainer.appendChild(this.typeSelect);
|
|
56
|
-
content.appendChild(typeContainer);
|
|
57
|
-
|
|
58
|
-
// File input
|
|
59
|
-
this.fileInput = document.createElement('input');
|
|
60
|
-
this.fileInput.type = 'file';
|
|
61
|
-
this.fileInput.className = 'import-file-input';
|
|
62
|
-
this.fileInput.disabled = true;
|
|
63
|
-
this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
|
|
64
|
-
|
|
65
|
-
content.appendChild(this.fileInput);
|
|
66
|
-
|
|
67
|
-
// File info
|
|
68
|
-
this.fileInfo = document.createElement('div');
|
|
69
|
-
this.fileInfo.className = 'import-file-info';
|
|
70
|
-
this.fileInfo.style.display = 'none';
|
|
71
|
-
content.appendChild(this.fileInfo);
|
|
72
|
-
|
|
73
|
-
// Buttons
|
|
74
|
-
const buttonContainer = document.createElement('div');
|
|
75
|
-
buttonContainer.className = 'import-button-container';
|
|
76
|
-
|
|
77
|
-
const cancelButton = document.createElement('button');
|
|
78
|
-
cancelButton.type = 'button';
|
|
79
|
-
cancelButton.className = 'import-button cancel-button';
|
|
80
|
-
cancelButton.textContent = 'Cancel';
|
|
81
|
-
cancelButton.addEventListener('click', () => this.hide());
|
|
82
|
-
|
|
83
|
-
this.importButton = document.createElement('button');
|
|
84
|
-
this.importButton.type = 'button';
|
|
85
|
-
this.importButton.className = 'import-button import-button-main';
|
|
86
|
-
this.importButton.textContent = 'Import';
|
|
87
|
-
this.importButton.disabled = true;
|
|
88
|
-
this.importButton.addEventListener('click', () => this.processImport());
|
|
89
|
-
|
|
90
|
-
buttonContainer.appendChild(cancelButton);
|
|
91
|
-
buttonContainer.appendChild(this.importButton);
|
|
92
|
-
content.appendChild(buttonContainer);
|
|
93
|
-
|
|
94
|
-
this.popup.appendChild(content);
|
|
95
|
-
appendPopup(this.popup);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
updateFileInput() {
|
|
99
|
-
const selectedType = this.typeSelect.value;
|
|
100
|
-
|
|
101
|
-
if (selectedType) {
|
|
102
|
-
this.fileType = selectedType;
|
|
103
|
-
this.fileInput.disabled = false;
|
|
104
|
-
|
|
105
|
-
const acceptTypes = this.getAcceptTypes(selectedType);
|
|
106
|
-
this.fileInput.accept = acceptTypes;
|
|
107
|
-
} else {
|
|
108
|
-
this.fileType = null;
|
|
109
|
-
this.fileInput.disabled = true;
|
|
110
|
-
this.fileInput.accept = '';
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
this.updateImportButton();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
getAcceptTypes(fileType) {
|
|
117
|
-
const types = {
|
|
118
|
-
html: '.html,.htm,text/html',
|
|
119
|
-
excel: '.csv,.xlsx,.xls,text/csv',
|
|
120
|
-
pdf: '.pdf,application/pdf',
|
|
121
|
-
word: '.doc,.docx'
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
return types[fileType] || '';
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
handleFileSelect(e) {
|
|
128
|
-
const file = e.target.files[0];
|
|
129
|
-
if (file) {
|
|
130
|
-
this.setSelectedFile(file);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
setSelectedFile(file) {
|
|
135
|
-
this.selectedFile = file;
|
|
136
|
-
|
|
137
|
-
this.fileInfo.style.display = 'block';
|
|
138
|
-
this.fileInfo.innerHTML = `
|
|
139
|
-
<div><strong>Name:</strong> ${file.name}</div>
|
|
140
|
-
<div><strong>Size:</strong> ${this.formatFileSize(file.size)}</div>
|
|
141
|
-
<div><strong>Type:</strong> ${file.type || 'Unknown'}</div>
|
|
142
|
-
`;
|
|
143
|
-
|
|
144
|
-
this.updateImportButton();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
formatFileSize(bytes) {
|
|
148
|
-
if (bytes === 0) return '0 Bytes';
|
|
149
|
-
|
|
150
|
-
const k = 1024;
|
|
151
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
152
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
153
|
-
|
|
154
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
updateImportButton() {
|
|
158
|
-
this.importButton.disabled = !this.selectedFile || !this.fileType;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async processImport() {
|
|
162
|
-
if (!this.selectedFile || !this.fileType) return;
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
let content;
|
|
166
|
-
|
|
167
|
-
if (this.fileType === 'html') {
|
|
168
|
-
content = await this.readAsText(this.selectedFile);
|
|
169
|
-
} else if (this.fileType === 'excel') {
|
|
170
|
-
if (this.selectedFile.name.toLowerCase().endsWith('.csv')) {
|
|
171
|
-
const csvContent = await this.readAsText(this.selectedFile);
|
|
172
|
-
content = this.parseCSV(csvContent);
|
|
173
|
-
} else {
|
|
174
|
-
alert('Excel files (.xlsx/.xls) require additional libraries. Please use CSV format.');
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
} else if (this.fileType === 'pdf') {
|
|
178
|
-
alert('PDF import requires additional libraries. Feature coming soon.');
|
|
179
|
-
return;
|
|
180
|
-
} else if (this.fileType === 'word') {
|
|
181
|
-
alert('Word document import requires additional libraries. Feature coming soon.');
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (this.options.onImport) {
|
|
186
|
-
this.options.onImport(content, this.fileType);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
this.hide();
|
|
190
|
-
this.reset();
|
|
191
|
-
|
|
192
|
-
} catch (error) {
|
|
193
|
-
console.error('Import error:', error);
|
|
194
|
-
alert('Error importing file: ' + error.message);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
parseCSV(csvContent) {
|
|
199
|
-
const lines = csvContent.split('\n');
|
|
200
|
-
const result = [];
|
|
201
|
-
|
|
202
|
-
lines.forEach(line => {
|
|
203
|
-
if (line.trim()) {
|
|
204
|
-
const cells = line.split(',').map(cell => cell.trim().replace(/^["']|["']$/g, ''));
|
|
205
|
-
result.push(cells);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
return result;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
readAsText(file) {
|
|
213
|
-
return new Promise((resolve, reject) => {
|
|
214
|
-
const reader = new FileReader();
|
|
215
|
-
reader.onload = (e) => resolve(e.target.result);
|
|
216
|
-
reader.onerror = () => reject(new Error('Failed to read file'));
|
|
217
|
-
reader.readAsText(file);
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
reset() {
|
|
222
|
-
this.selectedFile = null;
|
|
223
|
-
this.fileType = null;
|
|
224
|
-
this.typeSelect.value = '';
|
|
225
|
-
this.fileInput.value = '';
|
|
226
|
-
this.fileInput.disabled = true;
|
|
227
|
-
this.fileInfo.style.display = 'none';
|
|
228
|
-
this.updateImportButton();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
setupClickOutside() {
|
|
232
|
-
if (this.clickOutsideHandler) {
|
|
233
|
-
document.removeEventListener('click', this.clickOutsideHandler);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
this.clickOutsideHandler = (e) => {
|
|
237
|
-
if (!this.popup.contains(e.target)) {
|
|
238
|
-
this.hide();
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
setTimeout(() => {
|
|
243
|
-
document.addEventListener('click', this.clickOutsideHandler);
|
|
244
|
-
}, 100);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
removeClickOutside() {
|
|
248
|
-
if (this.clickOutsideHandler) {
|
|
249
|
-
document.removeEventListener('click', this.clickOutsideHandler);
|
|
250
|
-
this.clickOutsideHandler = null;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
show(anchor) {
|
|
255
|
-
if (!anchor) return;
|
|
256
|
-
|
|
257
|
-
// Calculate and set popup position
|
|
258
|
-
const position = calculatePopupPosition(anchor, this.popup, {
|
|
259
|
-
offsetY: 5,
|
|
260
|
-
offsetX: 0
|
|
261
|
-
});
|
|
262
|
-
setPopupPosition(this.popup, position);
|
|
263
|
-
|
|
264
|
-
this.popup.classList.add('visible');
|
|
265
|
-
this.isVisible = true;
|
|
266
|
-
|
|
267
|
-
this.setupClickOutside();
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
hide() {
|
|
271
|
-
this.popup.classList.remove('visible');
|
|
272
|
-
this.isVisible = false;
|
|
273
|
-
this.removeClickOutside();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
destroy() {
|
|
277
|
-
this.removeClickOutside();
|
|
278
|
-
|
|
279
|
-
if (this.popup && this.popup.parentNode) {
|
|
280
|
-
this.popup.parentNode.removeChild(this.popup);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
this.popup = null;
|
|
284
|
-
this.isVisible = false;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
export default ImportPopup;
|
package/lib/ui/link-popup.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Link Popup Component - Simple link popup
|
|
3
|
-
*/
|
|
4
|
-
import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
|
|
5
|
-
|
|
6
|
-
class LinkPopup {
|
|
7
|
-
constructor(options = {}) {
|
|
8
|
-
this.options = {
|
|
9
|
-
onLinkSelect: null,
|
|
10
|
-
editor: null,
|
|
11
|
-
...options
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
this.popup = null;
|
|
15
|
-
this.isVisible = false;
|
|
16
|
-
this.urlInput = null;
|
|
17
|
-
this.textInput = null;
|
|
18
|
-
|
|
19
|
-
this.createPopup();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
createPopup() {
|
|
23
|
-
this.popup = document.createElement('div');
|
|
24
|
-
this.popup.className = 'link-popup';
|
|
25
|
-
|
|
26
|
-
const content = document.createElement('div');
|
|
27
|
-
content.className = 'link-popup-content';
|
|
28
|
-
|
|
29
|
-
// title
|
|
30
|
-
const linkTitle = document.createElement('h2');
|
|
31
|
-
linkTitle.textContent = 'Upload link';
|
|
32
|
-
linkTitle.className = 'yjd-input-title';
|
|
33
|
-
//text label 1
|
|
34
|
-
const inputgroup1 = document.createElement('div');
|
|
35
|
-
inputgroup1.className = 'yjd-input-group';
|
|
36
|
-
const textLabel = document.createElement('p');
|
|
37
|
-
textLabel.textContent = 'Your URL';
|
|
38
|
-
textLabel.className = 'yjd-input-label';
|
|
39
|
-
|
|
40
|
-
this.urlInput = document.createElement('input');
|
|
41
|
-
this.urlInput.type = 'url';
|
|
42
|
-
this.urlInput.className = 'yjd-input';
|
|
43
|
-
this.urlInput.placeholder = 'Please enter your URL';
|
|
44
|
-
inputgroup1.appendChild(textLabel);
|
|
45
|
-
inputgroup1.appendChild(this.urlInput);
|
|
46
|
-
|
|
47
|
-
// Text label 2
|
|
48
|
-
const inputgroup2 = document.createElement('div');
|
|
49
|
-
inputgroup2.className = 'yjd-input-group';
|
|
50
|
-
const urlLabel = document.createElement('p');
|
|
51
|
-
urlLabel.textContent = 'Your display text';
|
|
52
|
-
urlLabel.className = 'yjd-input-label';
|
|
53
|
-
|
|
54
|
-
this.textInput = document.createElement('input');
|
|
55
|
-
this.textInput.type = 'text';
|
|
56
|
-
this.textInput.className = 'yjd-input';
|
|
57
|
-
this.textInput.placeholder = 'Please enter display text';
|
|
58
|
-
inputgroup2.appendChild(urlLabel);
|
|
59
|
-
inputgroup2.appendChild(this.textInput);
|
|
60
|
-
|
|
61
|
-
// Buttons
|
|
62
|
-
const buttonContainer = document.createElement('div');
|
|
63
|
-
buttonContainer.className = 'yjd-button-container';
|
|
64
|
-
|
|
65
|
-
const okButton = document.createElement('button');
|
|
66
|
-
okButton.type = 'button';
|
|
67
|
-
okButton.className = 'yjd-button-confirm';
|
|
68
|
-
okButton.textContent = 'Add link';
|
|
69
|
-
okButton.onclick = () => {
|
|
70
|
-
this.handleOk();
|
|
71
|
-
// Maintain editor focus after action
|
|
72
|
-
if (this.options.editor) {
|
|
73
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const cancelButton = document.createElement('button');
|
|
78
|
-
cancelButton.type = 'button';
|
|
79
|
-
cancelButton.className = 'yjd-button-cancel';
|
|
80
|
-
cancelButton.textContent = 'Cancel';
|
|
81
|
-
cancelButton.onclick = () => {
|
|
82
|
-
this.hide();
|
|
83
|
-
// Maintain editor focus after popup close
|
|
84
|
-
if (this.options.editor) {
|
|
85
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Enter key to submit
|
|
90
|
-
this.urlInput.onkeydown = (e) => {
|
|
91
|
-
if (e.key === 'Enter') this.handleOk();
|
|
92
|
-
if (e.key === 'Escape') this.hide();
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
this.textInput.onkeydown = (e) => {
|
|
96
|
-
if (e.key === 'Enter') this.handleOk();
|
|
97
|
-
if (e.key === 'Escape') this.hide();
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
buttonContainer.appendChild(cancelButton);
|
|
101
|
-
buttonContainer.appendChild(okButton);
|
|
102
|
-
|
|
103
|
-
content.appendChild(linkTitle);
|
|
104
|
-
content.appendChild(inputgroup1);
|
|
105
|
-
content.appendChild(inputgroup2);
|
|
106
|
-
content.appendChild(buttonContainer);
|
|
107
|
-
|
|
108
|
-
this.popup.appendChild(content);
|
|
109
|
-
appendPopup(this.popup);
|
|
110
|
-
|
|
111
|
-
// Prevent focus loss when clicking on popup
|
|
112
|
-
if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {
|
|
113
|
-
this.options.editor.preventFocusLoss(this.popup);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
handleOk() {
|
|
118
|
-
const url = this.urlInput.value.trim();
|
|
119
|
-
// Kiểm tra rỗng
|
|
120
|
-
if (!url) {
|
|
121
|
-
alert('Please enter a URL');
|
|
122
|
-
this.urlInput.focus();
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
// Kiểm tra định dạng URL
|
|
126
|
-
try {
|
|
127
|
-
new URL(url); // Nếu sai format, sẽ throw
|
|
128
|
-
} catch (e) {
|
|
129
|
-
alert('Please enter a valid URL');
|
|
130
|
-
this.urlInput.focus();
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const text = this.textInput.value.trim();
|
|
135
|
-
|
|
136
|
-
if (this.options.onLinkSelect) {
|
|
137
|
-
this.options.onLinkSelect({ url, text });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this.hide();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
show(anchor, existingLink = null, selectedText = '') {
|
|
144
|
-
if (!anchor) return;
|
|
145
|
-
|
|
146
|
-
// Populate fields
|
|
147
|
-
this.urlInput.value = existingLink ? existingLink.url : '';
|
|
148
|
-
// Use selected text if available, otherwise use existing link text or empty
|
|
149
|
-
this.textInput.value = selectedText || (existingLink ? existingLink.text : '');
|
|
150
|
-
|
|
151
|
-
// Calculate and set popup position
|
|
152
|
-
const position = calculatePopupPosition(anchor, this.popup, {
|
|
153
|
-
offsetY: 5,
|
|
154
|
-
offsetX: 0
|
|
155
|
-
});
|
|
156
|
-
setPopupPosition(this.popup, position);
|
|
157
|
-
|
|
158
|
-
// Show popup
|
|
159
|
-
this.popup.classList.add('visible');
|
|
160
|
-
this.isVisible = true;
|
|
161
|
-
|
|
162
|
-
// Focus URL input
|
|
163
|
-
setTimeout(() => this.urlInput.focus(), 100);
|
|
164
|
-
|
|
165
|
-
// Click outside to close
|
|
166
|
-
setTimeout(() => {
|
|
167
|
-
document.addEventListener('click', this.closeOnClickOutside);
|
|
168
|
-
}, 100);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
hide() {
|
|
172
|
-
this.popup.classList.remove('visible');
|
|
173
|
-
this.isVisible = false;
|
|
174
|
-
document.removeEventListener('click', this.closeOnClickOutside);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
closeOnClickOutside = (e) => {
|
|
178
|
-
if (!this.popup.contains(e.target)) {
|
|
179
|
-
this.hide();
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
destroy() {
|
|
184
|
-
document.removeEventListener('click', this.closeOnClickOutside);
|
|
185
|
-
if (this.popup && this.popup.parentNode) {
|
|
186
|
-
this.popup.parentNode.removeChild(this.popup);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export default LinkPopup;
|