leksy-editor 1.0.0
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/LICENSE +21 -0
- package/README.md +173 -0
- package/constant.js +605 -0
- package/gallery.js +107 -0
- package/index.js +696 -0
- package/package.json +21 -0
- package/plugin.js +1055 -0
- package/style.css +558 -0
- package/utilities.js +1798 -0
package/utilities.js
ADDED
|
@@ -0,0 +1,1798 @@
|
|
|
1
|
+
import { CLASSES, FONT_SIZES, FONTS, FORMATS, GIPHY_POWERED_IMAGE, REGEX, RESIZER_PLUGINS, SOCIAL_MEDIA_BASEURLS, SOCIAL_MEDIA_PATTERNS, SVG, TABLE_PLUGINS } from "./constant";
|
|
2
|
+
|
|
3
|
+
const traverseAndClean = (element) => {
|
|
4
|
+
if (!element?.childNodes) return;
|
|
5
|
+
|
|
6
|
+
// Iterate through child nodes
|
|
7
|
+
const childNodes = element.childNodes;
|
|
8
|
+
for (let i = childNodes.length - 1; i >= 0; i--) {
|
|
9
|
+
const node = childNodes[i];
|
|
10
|
+
|
|
11
|
+
// Recursively process child elements
|
|
12
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
13
|
+
// Check if element tag is allowed
|
|
14
|
+
if (!REGEX.WHILE_LISTED_TAG.test(node.tagName)) {
|
|
15
|
+
// Remove the element if not allowed
|
|
16
|
+
node.parentNode.removeChild(node);
|
|
17
|
+
} else {
|
|
18
|
+
// Remove attributes not in the whitelist
|
|
19
|
+
const attributesToRemove = [];
|
|
20
|
+
for (const attr of node.attributes) {
|
|
21
|
+
if (!REGEX.WHILE_LISTED_ATTRIBUTES.test(attr.name) || attr.value.includes('var(')) {
|
|
22
|
+
attributesToRemove.push(attr.name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
attributesToRemove.forEach(attr => node.removeAttribute(attr));
|
|
26
|
+
|
|
27
|
+
// Process child nodes
|
|
28
|
+
traverseAndClean(node);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cleanHTML = (html) => {
|
|
35
|
+
// Create a temporary div to manipulate the HTML
|
|
36
|
+
const tempDiv = document.createElement('div');
|
|
37
|
+
tempDiv.innerHTML = html;
|
|
38
|
+
|
|
39
|
+
// Traverse through all elements and clean attributes
|
|
40
|
+
traverseAndClean(tempDiv);
|
|
41
|
+
|
|
42
|
+
// Return manipulated HTML
|
|
43
|
+
return tempDiv.innerHTML;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const debounce = (fn, delay) => {
|
|
47
|
+
let timeout = null;
|
|
48
|
+
|
|
49
|
+
return (...args) => {
|
|
50
|
+
if (timeout) {
|
|
51
|
+
clearTimeout(timeout);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
timeout = setTimeout(async () => {
|
|
55
|
+
fn(...args);
|
|
56
|
+
timeout = null; // Clear the timeout reference after execution
|
|
57
|
+
}, delay);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function asyncDebounce(fn, delay) {
|
|
62
|
+
let timeout = null;
|
|
63
|
+
let isRunning = false;
|
|
64
|
+
let callNextFn = false;
|
|
65
|
+
let newArgs = [];
|
|
66
|
+
|
|
67
|
+
function call(...args) {
|
|
68
|
+
if (isRunning) {
|
|
69
|
+
newArgs = args;
|
|
70
|
+
callNextFn = true;
|
|
71
|
+
return;
|
|
72
|
+
} else {
|
|
73
|
+
clearTimeout(timeout);
|
|
74
|
+
newArgs = [];
|
|
75
|
+
callNextFn = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
timeout = setTimeout(async () => {
|
|
80
|
+
isRunning = true;
|
|
81
|
+
await fn(...args);
|
|
82
|
+
resolve();
|
|
83
|
+
isRunning = false;
|
|
84
|
+
if (callNextFn) call(...newArgs);
|
|
85
|
+
}, delay);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return call;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getDeviceName() {
|
|
93
|
+
const userAgent = navigator.userAgent;
|
|
94
|
+
|
|
95
|
+
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
|
|
96
|
+
return "IOS";
|
|
97
|
+
} else if (/Android/.test(userAgent)) {
|
|
98
|
+
return "ANDROID";
|
|
99
|
+
} else if (/Macintosh/.test(userAgent)) {
|
|
100
|
+
return "MAC";
|
|
101
|
+
} else if (/Windows/.test(userAgent)) {
|
|
102
|
+
return "WINDOWS";
|
|
103
|
+
} else if (/Linux/.test(userAgent)) {
|
|
104
|
+
return "LINUX";
|
|
105
|
+
} else {
|
|
106
|
+
return "Unknown";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const makeToolbarButton = (_plugin, options, core) => {
|
|
111
|
+
const pluginButton = document.createElement('button');
|
|
112
|
+
pluginButton.type = "button"
|
|
113
|
+
pluginButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM}`
|
|
114
|
+
|
|
115
|
+
pluginButton.setAttribute('data-title', _plugin.title)
|
|
116
|
+
pluginButton.setAttribute('data-type', 'button')
|
|
117
|
+
pluginButton.onclick = (e) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
_plugin.click(e, core, options);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const pluginIcon = document.createElement('div');
|
|
123
|
+
pluginIcon.innerHTML = _plugin.icon;
|
|
124
|
+
pluginButton.appendChild(pluginIcon)
|
|
125
|
+
return pluginButton
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const makeToolbarColor = (_plugin, options, core) => {
|
|
129
|
+
const pluginContainer = document.createElement('div');
|
|
130
|
+
pluginContainer.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_COLOR}`
|
|
131
|
+
pluginContainer.setAttribute('data-title', _plugin.title)
|
|
132
|
+
pluginContainer.setAttribute('data-type', 'color')
|
|
133
|
+
|
|
134
|
+
const pluginButton = document.createElement('input');
|
|
135
|
+
pluginButton.type = "color"
|
|
136
|
+
pluginButton.onclick = () => { if (getDeviceName() !== 'IOS') core.elements.editor.focus() }
|
|
137
|
+
|
|
138
|
+
pluginButton.onchange = (e) => {
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
_plugin.change(e, core, options);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const pluginIcon = document.createElement('div');
|
|
144
|
+
pluginIcon.innerHTML = _plugin.icon;
|
|
145
|
+
pluginContainer.append(pluginButton, pluginIcon)
|
|
146
|
+
return pluginContainer
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const makeToolbarDropdown = (_plugin, options, core) => {
|
|
150
|
+
const handleOutsideClick = (e, forceFully) => {
|
|
151
|
+
if (!forceFully && (e.target.getAttribute('data-toolbar-option') || dropdownPlugin.contains(e.target))) return
|
|
152
|
+
dropdownContent.style.display = 'none'
|
|
153
|
+
document.removeEventListener('mousedown', handleOutsideClick, false);
|
|
154
|
+
core.elements.editor.removeEventListener('click', handleOutsideClick, false);
|
|
155
|
+
}
|
|
156
|
+
const renderGalary = (images) => {
|
|
157
|
+
if ((core.state.page == 1 && !core.state.next) || (!core.state.page && !core.state.next)) container.innerHTML = ''
|
|
158
|
+
images.forEach(image => {
|
|
159
|
+
const imageElement = document.createElement('img');
|
|
160
|
+
imageElement.src = image.src;
|
|
161
|
+
imageElement.style.width = '120px';
|
|
162
|
+
imageElement.style.height = '120px';
|
|
163
|
+
imageElement.style.cursor = 'pointer';
|
|
164
|
+
imageElement.style.margin = '2px';
|
|
165
|
+
imageElement.onclick = (e) => {
|
|
166
|
+
const img = document.createElement('img');
|
|
167
|
+
img.src = image.src
|
|
168
|
+
core.insertNode(img)
|
|
169
|
+
core.updateCaretPosition()
|
|
170
|
+
options.closePluginOnClick && handleOutsideClick(e, true)
|
|
171
|
+
};
|
|
172
|
+
container.appendChild(imageElement);
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
const renderMentions = (value) => {
|
|
176
|
+
container.innerHTML = ''
|
|
177
|
+
const labels = JSON.parse(JSON.stringify(options.labels))
|
|
178
|
+
.map(label => {
|
|
179
|
+
label.fields = label.fields.filter(field => field.name.toLowerCase().includes(value.toLowerCase()))
|
|
180
|
+
return label
|
|
181
|
+
})
|
|
182
|
+
.filter(label => label.fields.length)
|
|
183
|
+
labels.forEach(category => {
|
|
184
|
+
const categoryElement = document.createElement('div')
|
|
185
|
+
categoryElement.textContent = category.name
|
|
186
|
+
container.append(categoryElement)
|
|
187
|
+
category.fields.forEach(field => {
|
|
188
|
+
const labelElement = document.createElement('button')
|
|
189
|
+
labelElement.type = "button"
|
|
190
|
+
labelElement.textContent = field.name
|
|
191
|
+
labelElement.onclick = (e) => {
|
|
192
|
+
core.elements.editor.focus();
|
|
193
|
+
const span = document.createElement('span');
|
|
194
|
+
span.spellcheck = false
|
|
195
|
+
span.contentEditable = false
|
|
196
|
+
span.innerText = field.name
|
|
197
|
+
core.insertNode(span)
|
|
198
|
+
core.updateCaretPosition()
|
|
199
|
+
options.closePluginOnClick && handleOutsideClick(e, true)
|
|
200
|
+
}
|
|
201
|
+
container.append(labelElement)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const dropdownPlugin = document.createElement('div');
|
|
207
|
+
dropdownPlugin.setAttribute('data-type', 'dropdown')
|
|
208
|
+
|
|
209
|
+
const dropdownButton = document.createElement('button');
|
|
210
|
+
dropdownButton.type = "button"
|
|
211
|
+
dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM}`
|
|
212
|
+
dropdownButton.setAttribute('data-title', _plugin.title)
|
|
213
|
+
|
|
214
|
+
const pluginIcon = document.createElement('div');
|
|
215
|
+
pluginIcon.innerHTML = _plugin.icon;
|
|
216
|
+
dropdownButton.appendChild(pluginIcon)
|
|
217
|
+
|
|
218
|
+
const dropdownContent = document.createElement('div');
|
|
219
|
+
dropdownContent.className = `${options.classPrefix}${CLASSES.DROPDOWN_CONTENT} ${_plugin.type}`
|
|
220
|
+
|
|
221
|
+
const container = document.createElement('div');
|
|
222
|
+
|
|
223
|
+
if (_plugin.type === 'dropdown') {
|
|
224
|
+
_plugin.options.forEach(option => {
|
|
225
|
+
const optionDiv = document.createElement('button');
|
|
226
|
+
optionDiv.type = "button"
|
|
227
|
+
optionDiv.textContent = option.label;
|
|
228
|
+
optionDiv.setAttribute('data-value', option.value); // Set custom attribute for value
|
|
229
|
+
optionDiv.setAttribute('data-toolbar-option', true); // Set custom attribute for value
|
|
230
|
+
|
|
231
|
+
// Event listener to handle selection
|
|
232
|
+
optionDiv.addEventListener('click', function (e) {
|
|
233
|
+
dropdownContent.style.display = 'none';
|
|
234
|
+
_plugin.click(e, core, options)
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Append option to the dropdown content div
|
|
238
|
+
dropdownContent.appendChild(optionDiv);
|
|
239
|
+
});
|
|
240
|
+
} else if (_plugin.type === 'gallery') {
|
|
241
|
+
const search = document.createElement('input');
|
|
242
|
+
search.placeholder = 'Search...'
|
|
243
|
+
const fetchAndRenderImages = async (searchQuery = '', page) => {
|
|
244
|
+
let res;
|
|
245
|
+
if (searchQuery) {
|
|
246
|
+
res = await _plugin.search(options, searchQuery, page);
|
|
247
|
+
} else {
|
|
248
|
+
res = await _plugin.suggestions(options, page);
|
|
249
|
+
}
|
|
250
|
+
if (res.totalPages) core.state.totalPages = res.totalPages; // Update total pages if available
|
|
251
|
+
if (res.next) core.state.next = res.next; // Update next page if available
|
|
252
|
+
renderGalary(res.images); // Pass search query and append flag
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const debounce = asyncDebounce(async (value) => {
|
|
256
|
+
core.state.page = 1; // Reset page on new search
|
|
257
|
+
core.state.next = "";
|
|
258
|
+
core.state.currentSearch = value; // Store the search term
|
|
259
|
+
if (_plugin.title == "Tenor") {
|
|
260
|
+
await fetchAndRenderImages(value, "");
|
|
261
|
+
} else {
|
|
262
|
+
await fetchAndRenderImages(value, 1);
|
|
263
|
+
}
|
|
264
|
+
}, 300);
|
|
265
|
+
|
|
266
|
+
search.oninput = (e) => debounce(e.target.value)
|
|
267
|
+
|
|
268
|
+
dropdownContent.addEventListener("scroll", async () => {
|
|
269
|
+
if (core.state.isFetching) return; // Prevent multiple calls while fetching
|
|
270
|
+
|
|
271
|
+
if (dropdownContent.scrollTop + dropdownContent.clientHeight >= dropdownContent.scrollHeight - 10) {
|
|
272
|
+
if (core.state.next) {
|
|
273
|
+
core.state.isFetching = true; // Set fetching state
|
|
274
|
+
await fetchAndRenderImages(core.state.currentSearch, core.state.next); // Fetch new images
|
|
275
|
+
core.state.isFetching = false; // Reset fetching state
|
|
276
|
+
}
|
|
277
|
+
if (core.state.totalPages && core.state.page < core.state.totalPages) {
|
|
278
|
+
core.state.isFetching = true; // Set fetching state
|
|
279
|
+
core.state.page += 1; // Increment page
|
|
280
|
+
await fetchAndRenderImages(core.state.currentSearch, core.state.page); // Fetch new images
|
|
281
|
+
core.state.isFetching = false; // Reset fetching state
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
const footer = document.createElement('div');
|
|
286
|
+
footer.className = 'footer';
|
|
287
|
+
if (_plugin.title === "GIPHY") {
|
|
288
|
+
const img = document.createElement('img');
|
|
289
|
+
|
|
290
|
+
img.src = `data:image/png;base64,${GIPHY_POWERED_IMAGE}`;
|
|
291
|
+
img.alt = "Powered By GIPHY";
|
|
292
|
+
footer.append(img)
|
|
293
|
+
} else {
|
|
294
|
+
footer.innerHTML = `<span>Powered By ${_plugin.title}</span>`;
|
|
295
|
+
}
|
|
296
|
+
footer.style.position = 'sticky';
|
|
297
|
+
footer.style.bottom = '0';
|
|
298
|
+
footer.style.width = '100%';
|
|
299
|
+
footer.style.background = '#f1f1f1';
|
|
300
|
+
footer.style.textAlign = 'center';
|
|
301
|
+
footer.style.height = '44px';
|
|
302
|
+
footer.style.display = 'flex';
|
|
303
|
+
footer.style.alignItems = 'center';
|
|
304
|
+
footer.style.justifyContent = 'center';
|
|
305
|
+
footer.style.overflow = 'hidden';
|
|
306
|
+
|
|
307
|
+
dropdownContent.append(search, container, footer)
|
|
308
|
+
} else if (_plugin.type === 'category') {
|
|
309
|
+
_plugin.categories.forEach(category => {
|
|
310
|
+
const categoryElement = document.createElement('div')
|
|
311
|
+
categoryElement.textContent = category.label
|
|
312
|
+
dropdownContent.append(categoryElement)
|
|
313
|
+
category.icons.forEach(emoji => {
|
|
314
|
+
const emojiElement = document.createElement('span')
|
|
315
|
+
emojiElement.textContent = emoji
|
|
316
|
+
emojiElement.onclick = (e) => {
|
|
317
|
+
core.elements.editor.focus();
|
|
318
|
+
core.elements.iframeWindow.execCommand('insertText', false, emoji)
|
|
319
|
+
options.closePluginOnClick && handleOutsideClick(e, true)
|
|
320
|
+
}
|
|
321
|
+
dropdownContent.append(emojiElement)
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
} else if (_plugin.type === 'mention') {
|
|
325
|
+
const search = document.createElement('input');
|
|
326
|
+
search.placeholder = 'Search...'
|
|
327
|
+
search.oninput = (e) => renderMentions(e.target.value)
|
|
328
|
+
dropdownContent.append(search, container)
|
|
329
|
+
renderMentions('')
|
|
330
|
+
} else if (_plugin.type === 'table') {
|
|
331
|
+
const totalRows = 10;
|
|
332
|
+
const totalCols = 10;
|
|
333
|
+
const table = document.createElement('div');
|
|
334
|
+
table.className = `${options.classPrefix}${CLASSES.TOOLBAR_TABLE}`
|
|
335
|
+
|
|
336
|
+
// Create table selector grid
|
|
337
|
+
for (let i = 0; i < totalRows; i++) {
|
|
338
|
+
for (let j = 0; j < totalCols; j++) {
|
|
339
|
+
|
|
340
|
+
const cell = document.createElement('div');
|
|
341
|
+
cell.className = 'cell'
|
|
342
|
+
|
|
343
|
+
cell.setAttribute('data-row', i);
|
|
344
|
+
cell.setAttribute('data-col', j);
|
|
345
|
+
if (j === 0) {
|
|
346
|
+
cell.setAttribute('tabIndex', '0');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
cell.addEventListener('mouseover', () => {
|
|
350
|
+
const cells = table.querySelectorAll('.cell');
|
|
351
|
+
cells.forEach((cell) => {
|
|
352
|
+
const cellRow = parseInt(cell.getAttribute('data-row'));
|
|
353
|
+
const cellCol = parseInt(cell.getAttribute('data-col'));
|
|
354
|
+
|
|
355
|
+
if (cellRow <= i && cellCol <= j) {
|
|
356
|
+
cell.classList.add('selected');
|
|
357
|
+
} else {
|
|
358
|
+
cell.classList.remove('selected');
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
cell.addEventListener('click', () => {
|
|
363
|
+
_plugin.create(core, options, { rows: i, cols: j })
|
|
364
|
+
dropdownContent.style.display = 'none'
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
table.appendChild(cell);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
dropdownContent.appendChild(table)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
let isSuggestionLoaded = false;
|
|
374
|
+
dropdownButton.onclick = () => {
|
|
375
|
+
if (dropdownContent.style.display === 'block') {
|
|
376
|
+
dropdownContent.style.display = 'none'
|
|
377
|
+
document.removeEventListener('mousedown', handleOutsideClick, false);
|
|
378
|
+
core.elements.editor.removeEventListener('click', handleOutsideClick, false);
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
dropdownContent.style.display = 'block'
|
|
382
|
+
if (dropdownContent.getBoundingClientRect().right > window.innerWidth) dropdownContent.style.right = '10px'
|
|
383
|
+
|
|
384
|
+
if (!isSuggestionLoaded && _plugin.type === 'gallery') {
|
|
385
|
+
_plugin.suggestions(options)
|
|
386
|
+
.then(res => {
|
|
387
|
+
isSuggestionLoaded = true
|
|
388
|
+
if (res.totalPages) {
|
|
389
|
+
core.state.totalPages = res.totalPages;
|
|
390
|
+
core.state.next = ""
|
|
391
|
+
} // Update total pages if available
|
|
392
|
+
if (res.next) {
|
|
393
|
+
core.state.next = res.next;
|
|
394
|
+
core.state.totalPages = null
|
|
395
|
+
} // Update next page if available
|
|
396
|
+
renderGalary(res.images)
|
|
397
|
+
})
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
document.addEventListener('mousedown', handleOutsideClick);
|
|
401
|
+
core.elements.editor.addEventListener('click', handleOutsideClick);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
dropdownPlugin.appendChild(dropdownButton)
|
|
405
|
+
dropdownPlugin.appendChild(dropdownContent)
|
|
406
|
+
return dropdownPlugin
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const makeToolbarSelect = (_plugin, options, core) => {
|
|
410
|
+
const handleOutsideClick = (e) => {
|
|
411
|
+
if (e.target.getAttribute('data-toolbar-option') || selectPlugin.contains(e.target)) return
|
|
412
|
+
dropdownContent.style.display = 'none'
|
|
413
|
+
document.removeEventListener('mousedown', handleOutsideClick, false);
|
|
414
|
+
core.elements.editor.removeEventListener('click', handleOutsideClick, false);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const selectPlugin = document.createElement('div');
|
|
418
|
+
selectPlugin.setAttribute('data-type', 'select')
|
|
419
|
+
|
|
420
|
+
const dropdownButton = document.createElement('button');
|
|
421
|
+
dropdownButton.type = "button"
|
|
422
|
+
dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_SELECT}`
|
|
423
|
+
dropdownButton.setAttribute('data-title', _plugin.title)
|
|
424
|
+
|
|
425
|
+
const pluginIcon = document.createElement('div');
|
|
426
|
+
pluginIcon.style.width = _plugin.width
|
|
427
|
+
pluginIcon.style.textAlign = 'left'
|
|
428
|
+
pluginIcon.style.display = 'flex'
|
|
429
|
+
pluginIcon.style.justifyContent = 'space-between'
|
|
430
|
+
const iconName = document.createElement('span');
|
|
431
|
+
iconName.innerHTML = _plugin.icon;
|
|
432
|
+
const arrow = document.createElement('span');
|
|
433
|
+
arrow.innerHTML = SVG.ARROW_DOWN;
|
|
434
|
+
arrow.style.paddingLeft = '8px';
|
|
435
|
+
pluginIcon.append(iconName, arrow)
|
|
436
|
+
|
|
437
|
+
dropdownButton.append(pluginIcon)
|
|
438
|
+
|
|
439
|
+
const dropdownContent = document.createElement('div');
|
|
440
|
+
dropdownContent.className = `${options.classPrefix}${CLASSES.DROPDOWN_CONTENT}`
|
|
441
|
+
_plugin.options.forEach(option => {
|
|
442
|
+
const optionDiv = document.createElement('button');
|
|
443
|
+
optionDiv.type = "button"
|
|
444
|
+
optionDiv.innerHTML = option.label;
|
|
445
|
+
optionDiv.setAttribute('data-value', option.value); // Set custom attribute for value
|
|
446
|
+
optionDiv.setAttribute('data-toolbar-option', true); // Set custom attribute for value
|
|
447
|
+
|
|
448
|
+
// Event listener to handle selection
|
|
449
|
+
optionDiv.addEventListener('click', function (e) {
|
|
450
|
+
dropdownContent.style.display = 'none';
|
|
451
|
+
_plugin.click(option.value, core, options)
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// Append option to the dropdown content div
|
|
455
|
+
dropdownContent.appendChild(optionDiv);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
dropdownButton.onclick = () => {
|
|
459
|
+
if (dropdownContent.style.display === 'block') {
|
|
460
|
+
dropdownContent.style.display = 'none'
|
|
461
|
+
document.removeEventListener('mousedown', handleOutsideClick, false);
|
|
462
|
+
core.elements.editor.removeEventListener('click', handleOutsideClick, false);
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
dropdownContent.style.display = 'block'
|
|
466
|
+
if (dropdownContent.getBoundingClientRect().right > window.innerWidth) dropdownContent.style.right = '10px'
|
|
467
|
+
document.addEventListener('mousedown', handleOutsideClick);
|
|
468
|
+
core.elements.editor.addEventListener('click', handleOutsideClick);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
selectPlugin.appendChild(dropdownButton)
|
|
472
|
+
selectPlugin.appendChild(dropdownContent)
|
|
473
|
+
return selectPlugin
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const makeEditToolbar = (options, core, { type, td, image, updateImage, updateTable }) => {
|
|
477
|
+
const handleOutsideClick = (e) => {
|
|
478
|
+
if (popoverPlugin.contains(e.target)) return
|
|
479
|
+
popoverContent.style.display = 'none'
|
|
480
|
+
document.removeEventListener('mousedown', handleOutsideClick, false);
|
|
481
|
+
core.elements.editor.removeEventListener('click', handleOutsideClick, false);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const popoverPlugin = document.createElement('div');
|
|
485
|
+
popoverPlugin.setAttribute('data-type', 'popover')
|
|
486
|
+
|
|
487
|
+
const popoverButton = document.createElement('button');
|
|
488
|
+
popoverButton.type = "button"
|
|
489
|
+
popoverButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM}`
|
|
490
|
+
popoverButton.setAttribute('data-title', `Edit ${type === 'td' ? 'Cell' : type === 'image' ? 'Image' : "Embed"}`)
|
|
491
|
+
|
|
492
|
+
const pluginIcon = document.createElement('div');
|
|
493
|
+
pluginIcon.innerHTML = SVG.EDIT;
|
|
494
|
+
popoverButton.appendChild(pluginIcon)
|
|
495
|
+
|
|
496
|
+
const popoverContent = document.createElement('div');
|
|
497
|
+
popoverContent.className = `${options.classPrefix}${CLASSES.POPOVER_CONTENT}`
|
|
498
|
+
|
|
499
|
+
if (type === 'image' || type === 'figure') {
|
|
500
|
+
const resizeByIds = {
|
|
501
|
+
byPercentage: Math.random(),
|
|
502
|
+
byPixels: Math.random(),
|
|
503
|
+
percentageWidth: Math.random(),
|
|
504
|
+
percentageHeight: Math.random(),
|
|
505
|
+
pixelsWidth: Math.random(),
|
|
506
|
+
pixelsHeight: Math.random(),
|
|
507
|
+
percentage: Math.random(),
|
|
508
|
+
pixels: Math.random(),
|
|
509
|
+
aspectRatio: Math.random(),
|
|
510
|
+
altText: Math.random(),
|
|
511
|
+
submit: Math.random(),
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
popoverContent.innerHTML = (`
|
|
515
|
+
<div style="border-bottom: 1px solid gray; padding-bottom: 4px;">
|
|
516
|
+
<div>
|
|
517
|
+
By:
|
|
518
|
+
<input type="radio" value="percentage" id="${resizeByIds.byPercentage}" checked/>
|
|
519
|
+
<label for="${resizeByIds.byPercentage}">Percentage</label>
|
|
520
|
+
<input type="radio" value="pixels" id="${resizeByIds.byPixels}" />
|
|
521
|
+
<label for="${resizeByIds.byPixels}">Pixels</label>
|
|
522
|
+
</div>
|
|
523
|
+
<div id="${resizeByIds.percentage}">
|
|
524
|
+
<div style="display: flex; justify-content: space-between; margin-top: 4px">
|
|
525
|
+
<label for="${resizeByIds.percentageWidth}">Width (%)</label>
|
|
526
|
+
<input style="width: 100px" type="number" name='width' placeholder="Width" id="${resizeByIds.percentageWidth}" />
|
|
527
|
+
</div>
|
|
528
|
+
<div style="display: flex; justify-content: space-between; margin-top: 4px">
|
|
529
|
+
<label for="${resizeByIds.percentageHeight}">Height (%)</label>
|
|
530
|
+
<input style="width: 100px" type="number" name='height' placeholder="Height" id="${resizeByIds.percentageHeight}" />
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
<div id="${resizeByIds.pixels}" style="display: none">
|
|
534
|
+
<div style="display: flex; justify-content: space-between; margin-top: 4px">
|
|
535
|
+
<label for="${resizeByIds.pixelsWidth}">Width (px)</label>
|
|
536
|
+
<input style="width: 100px" type="number" name='width' placeholder="Width" id="${resizeByIds.pixelsWidth}" />
|
|
537
|
+
</div>
|
|
538
|
+
<div style="display: flex; justify-content: space-between; margin-top: 4px">
|
|
539
|
+
<label for="${resizeByIds.pixelsHeight}">Height (px)</label>
|
|
540
|
+
<input style="width: 100px" type="number" name='height' placeholder="Height" id="${resizeByIds.pixelsHeight}" />
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
<div style="margin-top: 4px;">
|
|
544
|
+
<input type="checkbox" id="${resizeByIds.aspectRatio}" checked/>
|
|
545
|
+
<label for="${resizeByIds.aspectRatio}">Maintain Aspect Ratio</label>
|
|
546
|
+
</div>
|
|
547
|
+
${type === "image" ? `
|
|
548
|
+
<div style="display: flex; justify-content: space-between; margin-top: 4px">
|
|
549
|
+
<label for="${resizeByIds.altText}">Alt Text</label>
|
|
550
|
+
<input style="width: 100px" type="text" id="${resizeByIds.altText}" placeholder="Enter alt text" style="width: 100%;" />
|
|
551
|
+
</div>` : ""}
|
|
552
|
+
<div style="display: flex; justify-content: end; margin-top: 4px;">
|
|
553
|
+
<button style="border: 1px solid gray; padding: 2px 4px; border-radius: 4px; color: white; background-color: hsl(160, 100%, 40%);" type="button" id="${resizeByIds.submit}">Update</button>
|
|
554
|
+
</div>
|
|
555
|
+
</div >
|
|
556
|
+
`)
|
|
557
|
+
|
|
558
|
+
const pluginSet = document.createElement('div')
|
|
559
|
+
pluginSet.className = `${options.classPrefix}${CLASSES.RESIZE_ITEMS} `
|
|
560
|
+
RESIZER_PLUGINS.forEach(plugin => {
|
|
561
|
+
const pluginButton = document.createElement('button');
|
|
562
|
+
pluginButton.type = "button"
|
|
563
|
+
pluginButton.className = `${options.classPrefix}${CLASSES.RESIZE_ITEM} `
|
|
564
|
+
|
|
565
|
+
pluginButton.setAttribute('data-title', plugin.title)
|
|
566
|
+
pluginButton.setAttribute('data-type', 'button')
|
|
567
|
+
pluginButton.onclick = (e) => {
|
|
568
|
+
e.preventDefault();
|
|
569
|
+
updateImage(plugin.event);
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const pluginIcon = document.createElement('div');
|
|
573
|
+
pluginIcon.innerHTML = plugin.icon;
|
|
574
|
+
pluginButton.appendChild(pluginIcon)
|
|
575
|
+
pluginSet.appendChild(pluginButton)
|
|
576
|
+
})
|
|
577
|
+
popoverContent.appendChild(pluginSet)
|
|
578
|
+
popoverButton.onclick = () => {
|
|
579
|
+
const onInputKeydown = (event) => {
|
|
580
|
+
if (event.key === 'Enter') {
|
|
581
|
+
event.preventDefault()
|
|
582
|
+
resizeBy.submit.click();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (popoverContent.style.display === 'block') {
|
|
587
|
+
popoverContent.style.display = 'none'
|
|
588
|
+
document.removeEventListener('mousedown', handleOutsideClick, false);
|
|
589
|
+
core.elements.editor.removeEventListener('click', handleOutsideClick, false);
|
|
590
|
+
return
|
|
591
|
+
}
|
|
592
|
+
popoverContent.style.display = 'block'
|
|
593
|
+
if (popoverContent.getBoundingClientRect().right > window.innerWidth) popoverContent.style.right = '10px'
|
|
594
|
+
const resizeBy = {
|
|
595
|
+
byPercentage: document.getElementById(resizeByIds.byPercentage),
|
|
596
|
+
byPixels: document.getElementById(resizeByIds.byPixels),
|
|
597
|
+
percentageWidth: document.getElementById(resizeByIds.percentageWidth),
|
|
598
|
+
percentageHeight: document.getElementById(resizeByIds.percentageHeight),
|
|
599
|
+
pixelsWidth: document.getElementById(resizeByIds.pixelsWidth),
|
|
600
|
+
pixelsHeight: document.getElementById(resizeByIds.pixelsHeight),
|
|
601
|
+
percentage: document.getElementById(resizeByIds.percentage),
|
|
602
|
+
pixels: document.getElementById(resizeByIds.pixels),
|
|
603
|
+
aspectRatio: document.getElementById(resizeByIds.aspectRatio),
|
|
604
|
+
submit: document.getElementById(resizeByIds.submit),
|
|
605
|
+
}
|
|
606
|
+
if (type == "image") resizeBy.altText = document.getElementById(resizeByIds.altText)
|
|
607
|
+
|
|
608
|
+
const imageClientRect = image.getBoundingClientRect()
|
|
609
|
+
resizeBy.percentageWidth.value = (imageClientRect.width / image.naturalWidth) * 100
|
|
610
|
+
resizeBy.percentageHeight.value = (imageClientRect.height / image.naturalHeight) * 100
|
|
611
|
+
resizeBy.pixelsWidth.value = imageClientRect.width
|
|
612
|
+
resizeBy.pixelsHeight.value = imageClientRect.height
|
|
613
|
+
if (type == "image") resizeBy.altText.value = image.alt || "";
|
|
614
|
+
|
|
615
|
+
resizeBy.byPercentage.onchange = (e) => {
|
|
616
|
+
if (e.target.checked) {
|
|
617
|
+
resizeBy.pixels.style.display = 'none'
|
|
618
|
+
resizeBy.percentage.style.display = 'block'
|
|
619
|
+
resizeBy.byPixels.checked = false
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
resizeBy.byPixels.onchange = (e) => {
|
|
623
|
+
if (e.target.checked) {
|
|
624
|
+
resizeBy.percentage.style.display = 'none'
|
|
625
|
+
resizeBy.pixels.style.display = 'block'
|
|
626
|
+
resizeBy.byPercentage.checked = false
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const handlePercentage = (e) => {
|
|
631
|
+
let value = parseInt(e.target.value)
|
|
632
|
+
if (value) {
|
|
633
|
+
if (value > 100) value = 100;
|
|
634
|
+
if (resizeBy.aspectRatio.checked) {
|
|
635
|
+
resizeBy.percentageWidth.value = value
|
|
636
|
+
resizeBy.percentageHeight.value = value
|
|
637
|
+
} else {
|
|
638
|
+
e.target.value = value
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
resizeBy.percentageWidth.oninput = handlePercentage
|
|
643
|
+
resizeBy.percentageHeight.oninput = handlePercentage
|
|
644
|
+
|
|
645
|
+
const handlePixel = (e) => {
|
|
646
|
+
let value = parseInt(e.target.value)
|
|
647
|
+
if (value) {
|
|
648
|
+
if (resizeBy.aspectRatio.checked) {
|
|
649
|
+
let width, height;
|
|
650
|
+
if (e.target.name === 'width') {
|
|
651
|
+
width = value
|
|
652
|
+
const delta = image.naturalWidth / value
|
|
653
|
+
height = image.naturalHeight / delta
|
|
654
|
+
} else {
|
|
655
|
+
height = value
|
|
656
|
+
const delta = image.naturalHeight / value
|
|
657
|
+
width = image.naturalWidth / delta
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
resizeBy.pixelsWidth.value = width
|
|
661
|
+
resizeBy.pixelsHeight.value = height
|
|
662
|
+
} else {
|
|
663
|
+
e.target.value = value
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
resizeBy.pixelsWidth.oninput = handlePixel
|
|
668
|
+
resizeBy.pixelsHeight.oninput = handlePixel
|
|
669
|
+
|
|
670
|
+
resizeBy.percentageWidth.onkeydown = onInputKeydown
|
|
671
|
+
resizeBy.percentageHeight.onkeydown = onInputKeydown
|
|
672
|
+
resizeBy.pixelsWidth.onkeydown = onInputKeydown
|
|
673
|
+
resizeBy.pixelsHeight.onkeydown = onInputKeydown
|
|
674
|
+
|
|
675
|
+
resizeBy.aspectRatio.onchange = (e) => {
|
|
676
|
+
if (e.target.checked) {
|
|
677
|
+
if (resizeBy.byPixels.checked) {
|
|
678
|
+
const delta = image.naturalWidth / parseInt(resizeBy.pixelsWidth.value)
|
|
679
|
+
const height = image.naturalHeight / delta
|
|
680
|
+
resizeBy.pixelsHeight.value = height
|
|
681
|
+
} else {
|
|
682
|
+
resizeBy.percentageHeight.value = resizeBy.percentageWidth.value
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
resizeBy.submit.onclick = (e) => {
|
|
688
|
+
if (resizeBy.byPixels.checked) {
|
|
689
|
+
if (parseInt(resizeBy.pixelsWidth.value) && parseInt(resizeBy.pixelsHeight.value))
|
|
690
|
+
updateImage('update', { width: resizeBy.pixelsWidth.value + 'px', height: resizeBy.pixelsHeight.value + 'px', })
|
|
691
|
+
} else if (resizeBy.byPercentage.checked) {
|
|
692
|
+
if (parseInt(resizeBy.percentageWidth.value) && parseInt(resizeBy.percentageHeight.value))
|
|
693
|
+
updateImage('update', { width: resizeBy.percentageWidth.value + '%', height: resizeBy.percentageHeight.value + '%', })
|
|
694
|
+
}
|
|
695
|
+
if (type == "image") image.alt = resizeBy.altText.value;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
document.addEventListener('mousedown', handleOutsideClick);
|
|
699
|
+
core.elements.editor.addEventListener('click', handleOutsideClick);
|
|
700
|
+
}
|
|
701
|
+
} else if (type === 'td') {
|
|
702
|
+
const pluginSet = document.createElement('div')
|
|
703
|
+
pluginSet.className = `${options.classPrefix}${CLASSES.RESIZE_ITEMS} `
|
|
704
|
+
TABLE_PLUGINS.forEach(plugin => {
|
|
705
|
+
const pluginButton = plugin.event === 'border-color'
|
|
706
|
+
? document.createElement('input')
|
|
707
|
+
: document.createElement('button');
|
|
708
|
+
if (plugin.event === 'border-color') {
|
|
709
|
+
pluginButton.type = "color"
|
|
710
|
+
pluginButton.style.width = '30px'
|
|
711
|
+
pluginButton.style.padding = '4px'
|
|
712
|
+
pluginButton.oninput = (e) => {
|
|
713
|
+
updateTable(plugin.event, e.target.value);
|
|
714
|
+
}
|
|
715
|
+
} else
|
|
716
|
+
pluginButton.type = "button"
|
|
717
|
+
pluginButton.className = `${options.classPrefix}${CLASSES.RESIZE_ITEM} `
|
|
718
|
+
pluginButton.setAttribute('data-title', plugin.title)
|
|
719
|
+
pluginButton.setAttribute('data-type', 'button')
|
|
720
|
+
|
|
721
|
+
if (plugin.event !== 'border-color') {
|
|
722
|
+
pluginButton.onclick = (e) => {
|
|
723
|
+
e.preventDefault();
|
|
724
|
+
updateTable(plugin.event);
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const pluginIcon = document.createElement('div');
|
|
728
|
+
pluginIcon.innerHTML = plugin.icon;
|
|
729
|
+
pluginButton.appendChild(pluginIcon)
|
|
730
|
+
}
|
|
731
|
+
pluginSet.appendChild(pluginButton)
|
|
732
|
+
})
|
|
733
|
+
popoverContent.appendChild(pluginSet)
|
|
734
|
+
|
|
735
|
+
popoverButton.onclick = () => {
|
|
736
|
+
if (popoverContent.style.display === 'block') {
|
|
737
|
+
popoverContent.style.display = 'none'
|
|
738
|
+
document.removeEventListener('mousedown', handleOutsideClick, false);
|
|
739
|
+
core.elements.editor.removeEventListener('click', handleOutsideClick, false);
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
popoverContent.style.display = 'block'
|
|
743
|
+
if (popoverContent.getBoundingClientRect().right > window.innerWidth) popoverContent.style.right = '10px'
|
|
744
|
+
document.addEventListener('mousedown', handleOutsideClick);
|
|
745
|
+
core.elements.editor.addEventListener('click', handleOutsideClick);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
popoverPlugin.appendChild(popoverButton)
|
|
750
|
+
popoverPlugin.appendChild(popoverContent)
|
|
751
|
+
core.elements.toolbarContainer.appendChild(popoverPlugin)
|
|
752
|
+
core.elements.resizerPlugin = popoverPlugin
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const openModal = ({ title, bodyNode, footerNode }, core, options) => {
|
|
756
|
+
const modalContainer = document.createElement('div');
|
|
757
|
+
modalContainer.className = `${options.classPrefix}${CLASSES.MODAL} `
|
|
758
|
+
|
|
759
|
+
const modalContent = document.createElement('div');
|
|
760
|
+
modalContent.className = `${options.classPrefix}${CLASSES.MODAL_CONTENT} `
|
|
761
|
+
|
|
762
|
+
const modalHeader = document.createElement('div');
|
|
763
|
+
modalHeader.className = `${options.classPrefix}${CLASSES.MODAL_HEADER} `
|
|
764
|
+
const modalTitle = document.createElement('span');
|
|
765
|
+
modalTitle.innerText = title ?? ''
|
|
766
|
+
const modalCloseButton = document.createElement('button');
|
|
767
|
+
modalCloseButton.className = 'modal-close-btn';
|
|
768
|
+
modalCloseButton.innerHTML = SVG.CLOSE;
|
|
769
|
+
modalCloseButton.type = 'button'
|
|
770
|
+
modalCloseButton.onclick = () => {
|
|
771
|
+
core.elements.base.removeChild(modalContainer)
|
|
772
|
+
}
|
|
773
|
+
modalHeader.append(modalTitle, modalCloseButton)
|
|
774
|
+
|
|
775
|
+
const modalBody = document.createElement('div');
|
|
776
|
+
modalBody.className = `${options.classPrefix}${CLASSES.MODAL_BODY} `
|
|
777
|
+
modalBody.appendChild(bodyNode)
|
|
778
|
+
|
|
779
|
+
const modalFooter = document.createElement('div');
|
|
780
|
+
modalFooter.className = `${options.classPrefix}${CLASSES.MODAL_FOOTER} `
|
|
781
|
+
modalFooter.appendChild(footerNode)
|
|
782
|
+
|
|
783
|
+
modalContent.appendChild(modalHeader);
|
|
784
|
+
modalContent.appendChild(modalBody);
|
|
785
|
+
modalContent.appendChild(modalFooter);
|
|
786
|
+
|
|
787
|
+
modalContainer.appendChild(modalContent)
|
|
788
|
+
core.elements.base.appendChild(modalContainer)
|
|
789
|
+
|
|
790
|
+
return {
|
|
791
|
+
close: () => { core.elements.base.removeChild(modalContainer) }
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const initImageResizer = (type, image, options, core) => {
|
|
796
|
+
destroyImageResizer(options, core)
|
|
797
|
+
changeAllToolbarState(core, 'disabled', ['link'])
|
|
798
|
+
core.elements.editor.blur() // to remove focus from the editor
|
|
799
|
+
|
|
800
|
+
const imageClientRect = image.getBoundingClientRect();
|
|
801
|
+
const resizer = document.createElement('div');
|
|
802
|
+
resizer.className = `resizer`
|
|
803
|
+
resizer.style.width = imageClientRect.width + 'px';
|
|
804
|
+
resizer.style.height = imageClientRect.height + 'px';
|
|
805
|
+
resizer.style.top = imageClientRect.top + 'px';
|
|
806
|
+
resizer.style.left = imageClientRect.left + 'px';
|
|
807
|
+
|
|
808
|
+
// Full-screen button (only for images)
|
|
809
|
+
if (type === 'image') {
|
|
810
|
+
const fullscreenButton = document.createElement('span');
|
|
811
|
+
fullscreenButton.innerHTML = SVG.FULLSCREEN_IMAGE
|
|
812
|
+
fullscreenButton.style.position = 'absolute';
|
|
813
|
+
fullscreenButton.style.top = '5px';
|
|
814
|
+
fullscreenButton.style.right = '5px';
|
|
815
|
+
fullscreenButton.style.background = 'rgba(0, 0, 0, 0.6)';
|
|
816
|
+
fullscreenButton.style.color = 'white';
|
|
817
|
+
fullscreenButton.style.border = 'none';
|
|
818
|
+
fullscreenButton.style.cursor = 'pointer';
|
|
819
|
+
fullscreenButton.style.borderRadius = '4px';
|
|
820
|
+
fullscreenButton.style.fontSize = '14px';
|
|
821
|
+
fullscreenButton.style.transition = '0.3s ease-in-out';
|
|
822
|
+
fullscreenButton.style.width = '28px';
|
|
823
|
+
fullscreenButton.style.height = '28px';
|
|
824
|
+
fullscreenButton.style.display = 'flex';
|
|
825
|
+
fullscreenButton.style.alignItems = 'center';
|
|
826
|
+
fullscreenButton.style.justifyContent = 'center';
|
|
827
|
+
|
|
828
|
+
fullscreenButton.addEventListener('click', () => {
|
|
829
|
+
createPreviewModal(image.src, core, options); // Open custom modal with image preview
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
resizer.appendChild(fullscreenButton);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
let firstX, firstY, direction, aspectRatio, resizerClientRect;
|
|
836
|
+
const handleMouseDown = (e) => {
|
|
837
|
+
resizerClientRect = resizer.getBoundingClientRect();
|
|
838
|
+
direction = e.target.getAttribute('data-resize')
|
|
839
|
+
firstX = e.clientX || e.touches[0].clientX;
|
|
840
|
+
firstY = e.clientY || e.touches[0].clientY;
|
|
841
|
+
aspectRatio = resizer.offsetWidth / resizer.offsetHeight;
|
|
842
|
+
core.elements.iframeWindow.addEventListener('mousemove', handleMouseMove)
|
|
843
|
+
core.elements.iframeWindow.addEventListener('mouseup', handleMouseUp)
|
|
844
|
+
core.elements.iframeWindow.addEventListener('touchmove', handleMouseMove)
|
|
845
|
+
core.elements.iframeWindow.addEventListener('touchend', handleMouseUp)
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const handleMouseMove = (e) => {
|
|
849
|
+
const position = {
|
|
850
|
+
clientX: e.clientX || e.touches[0].clientX,
|
|
851
|
+
clientY: e.clientY || e.touches[0].clientY,
|
|
852
|
+
}
|
|
853
|
+
// Calculate the new dimensions
|
|
854
|
+
if (direction === 'up') {
|
|
855
|
+
let top = resizerClientRect.top + (position.clientY - firstY);
|
|
856
|
+
let height = resizerClientRect.height - (position.clientY - firstY);
|
|
857
|
+
if (height < 0) {
|
|
858
|
+
top = resizerClientRect.bottom;
|
|
859
|
+
height = 0
|
|
860
|
+
}
|
|
861
|
+
resizer.style.top = top + 'px';
|
|
862
|
+
resizer.style.height = height + 'px';
|
|
863
|
+
} else if (direction === 'bottom') {
|
|
864
|
+
let height = resizerClientRect.height + (position.clientY - firstY);
|
|
865
|
+
if (height < 0) height = 0
|
|
866
|
+
resizer.style.height = height + 'px';
|
|
867
|
+
} else if (direction === 'left') {
|
|
868
|
+
let left = resizerClientRect.left + (position.clientX - firstX);
|
|
869
|
+
let width = resizerClientRect.width - (position.clientX - firstX);
|
|
870
|
+
if (width < 0) {
|
|
871
|
+
left = resizerClientRect.right;
|
|
872
|
+
width = 0
|
|
873
|
+
}
|
|
874
|
+
resizer.style.left = left + 'px';
|
|
875
|
+
resizer.style.width = width + 'px';
|
|
876
|
+
} else if (direction === 'right') {
|
|
877
|
+
let width = resizerClientRect.width + (position.clientX - firstX);
|
|
878
|
+
if (width < 0) width = 0
|
|
879
|
+
resizer.style.width = width + 'px';
|
|
880
|
+
} else if (direction === 'bottom-right') {
|
|
881
|
+
const distanceX = position.clientX - firstX
|
|
882
|
+
const distanceY = position.clientY - firstY
|
|
883
|
+
const delta = Math.sign(distanceX) === -1 || Math.sign(distanceY) === -1
|
|
884
|
+
? Math.min(distanceX, distanceY)
|
|
885
|
+
: Math.max(distanceX, distanceY)
|
|
886
|
+
const width = resizerClientRect.width + delta;
|
|
887
|
+
const height = width / aspectRatio;
|
|
888
|
+
resizer.style.width = width + 'px';
|
|
889
|
+
resizer.style.height = height + 'px';
|
|
890
|
+
} else if (direction === 'bottom-left') {
|
|
891
|
+
const distanceX = position.clientX - firstX
|
|
892
|
+
const distanceY = position.clientY - firstY
|
|
893
|
+
const delta = distanceX > 0 ? Math.max(distanceX, distanceY) : distanceX
|
|
894
|
+
let width = resizerClientRect.width - delta;
|
|
895
|
+
if (width < 0) width = 0
|
|
896
|
+
let left = resizerClientRect.left + resizerClientRect.width - width;
|
|
897
|
+
const height = width / aspectRatio;
|
|
898
|
+
resizer.style.width = width + 'px';
|
|
899
|
+
resizer.style.left = left + 'px';
|
|
900
|
+
resizer.style.height = height + 'px';
|
|
901
|
+
} else if (direction === 'up-right') {
|
|
902
|
+
const distanceX = position.clientX - firstX
|
|
903
|
+
const distanceY = position.clientY - firstY
|
|
904
|
+
const delta = distanceX > 0 ? Math.max(distanceX, distanceY) : distanceX
|
|
905
|
+
|
|
906
|
+
let width = resizerClientRect.width + delta;
|
|
907
|
+
if (width < 0) width = 0
|
|
908
|
+
const height = width / aspectRatio;
|
|
909
|
+
const top = resizerClientRect.top + (resizerClientRect.height - height)
|
|
910
|
+
resizer.style.top = top + 'px';
|
|
911
|
+
resizer.style.width = width + 'px';
|
|
912
|
+
resizer.style.height = height + 'px';
|
|
913
|
+
} else if (direction === 'up-left') {
|
|
914
|
+
const distanceX = position.clientX - firstX
|
|
915
|
+
const distanceY = position.clientY - firstY
|
|
916
|
+
const delta = distanceX > 0 ? Math.max(distanceX, distanceY) : distanceX
|
|
917
|
+
let width = resizerClientRect.width - delta;
|
|
918
|
+
if (width < 0) width = 0
|
|
919
|
+
const height = width / aspectRatio;
|
|
920
|
+
const top = resizerClientRect.top + (resizerClientRect.height - height)
|
|
921
|
+
const left = resizerClientRect.left + (resizerClientRect.width - width)
|
|
922
|
+
resizer.style.top = top + 'px';
|
|
923
|
+
resizer.style.width = width + 'px';
|
|
924
|
+
resizer.style.height = height + 'px';
|
|
925
|
+
resizer.style.left = left + 'px';
|
|
926
|
+
}
|
|
927
|
+
position.textContent = `${parseInt(resizer.style.width)}x${parseInt(resizer.style.height)} `;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const handleMouseUp = (e) => {
|
|
931
|
+
image.style.width = parseInt(resizer.style.width) + 'px'
|
|
932
|
+
image.style.height = parseInt(resizer.style.height) + 'px'
|
|
933
|
+
const imageClientRect = image.getBoundingClientRect();
|
|
934
|
+
resizer.style.width = imageClientRect.width + 'px';
|
|
935
|
+
resizer.style.height = imageClientRect.height + 'px';
|
|
936
|
+
resizer.style.top = imageClientRect.top + 'px';
|
|
937
|
+
resizer.style.left = imageClientRect.left + 'px';
|
|
938
|
+
core.elements.iframeWindow.removeEventListener('mousemove', handleMouseMove, false)
|
|
939
|
+
core.elements.iframeWindow.removeEventListener('mouseup', handleMouseUp, false)
|
|
940
|
+
core.elements.iframeWindow.removeEventListener('touchmove', handleMouseMove, false)
|
|
941
|
+
core.elements.iframeWindow.removeEventListener('touchend', handleMouseUp, false)
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const dot1 = document.createElement('button');
|
|
945
|
+
dot1.type = 'button'
|
|
946
|
+
dot1.className = 'dot up-left'
|
|
947
|
+
dot1.setAttribute('data-resize', 'up-left')
|
|
948
|
+
dot1.addEventListener('mousedown', handleMouseDown)
|
|
949
|
+
dot1.addEventListener('touchstart', handleMouseDown)
|
|
950
|
+
|
|
951
|
+
const dot2 = document.createElement('button');
|
|
952
|
+
dot2.type = 'button'
|
|
953
|
+
dot2.className = 'dot up'
|
|
954
|
+
dot2.setAttribute('data-resize', 'up')
|
|
955
|
+
dot2.addEventListener('mousedown', handleMouseDown)
|
|
956
|
+
dot2.addEventListener('touchstart', handleMouseDown)
|
|
957
|
+
|
|
958
|
+
const dot3 = document.createElement('button');
|
|
959
|
+
dot3.type = 'button'
|
|
960
|
+
dot3.className = 'dot up-right'
|
|
961
|
+
dot3.setAttribute('data-resize', 'up-right')
|
|
962
|
+
dot3.addEventListener('mousedown', handleMouseDown)
|
|
963
|
+
dot3.addEventListener('touchstart', handleMouseDown)
|
|
964
|
+
|
|
965
|
+
const dot4 = document.createElement('button');
|
|
966
|
+
dot4.type = 'button'
|
|
967
|
+
dot4.className = 'dot right'
|
|
968
|
+
dot4.setAttribute('data-resize', 'right')
|
|
969
|
+
dot4.addEventListener('mousedown', handleMouseDown)
|
|
970
|
+
dot4.addEventListener('touchstart', handleMouseDown)
|
|
971
|
+
|
|
972
|
+
const dot5 = document.createElement('button');
|
|
973
|
+
dot5.type = 'button'
|
|
974
|
+
dot5.className = 'dot bottom-right'
|
|
975
|
+
dot5.setAttribute('data-resize', 'bottom-right')
|
|
976
|
+
dot5.addEventListener('mousedown', handleMouseDown)
|
|
977
|
+
dot5.addEventListener('touchstart', handleMouseDown)
|
|
978
|
+
|
|
979
|
+
const dot6 = document.createElement('button');
|
|
980
|
+
dot6.type = 'button'
|
|
981
|
+
dot6.className = 'dot bottom'
|
|
982
|
+
dot6.setAttribute('data-resize', 'bottom')
|
|
983
|
+
dot6.addEventListener('mousedown', handleMouseDown)
|
|
984
|
+
dot6.addEventListener('touchstart', handleMouseDown)
|
|
985
|
+
|
|
986
|
+
const dot7 = document.createElement('button');
|
|
987
|
+
dot7.type = 'button'
|
|
988
|
+
dot7.className = 'dot bottom-left'
|
|
989
|
+
dot7.setAttribute('data-resize', 'bottom-left')
|
|
990
|
+
dot7.addEventListener('mousedown', handleMouseDown)
|
|
991
|
+
dot7.addEventListener('touchstart', handleMouseDown)
|
|
992
|
+
|
|
993
|
+
const dot8 = document.createElement('button');
|
|
994
|
+
dot8.type = 'button'
|
|
995
|
+
dot8.className = 'dot left'
|
|
996
|
+
dot8.setAttribute('data-resize', 'left')
|
|
997
|
+
dot8.addEventListener('mousedown', handleMouseDown)
|
|
998
|
+
dot8.addEventListener('touchstart', handleMouseDown)
|
|
999
|
+
|
|
1000
|
+
const position = document.createElement('div')
|
|
1001
|
+
position.className = 'position';
|
|
1002
|
+
position.textContent = `${parseInt(imageClientRect.width)}x${parseInt(imageClientRect.height)} `;
|
|
1003
|
+
|
|
1004
|
+
resizer.append(dot1, dot2, dot3, dot4, dot5, dot6, dot7, dot8, position)
|
|
1005
|
+
|
|
1006
|
+
const updateImage = (event, data) => {
|
|
1007
|
+
switch (event) {
|
|
1008
|
+
case 'update':
|
|
1009
|
+
image.style.width = data.width;
|
|
1010
|
+
image.style.height = data.height;
|
|
1011
|
+
break;
|
|
1012
|
+
case '100%':
|
|
1013
|
+
case '75%':
|
|
1014
|
+
case '50%':
|
|
1015
|
+
case 'auto':
|
|
1016
|
+
image.style.width = event;
|
|
1017
|
+
image.style.height = 'unset';
|
|
1018
|
+
break;
|
|
1019
|
+
case 'revert':
|
|
1020
|
+
image.style.width = 'unset';
|
|
1021
|
+
image.style.height = 'unset';
|
|
1022
|
+
break;
|
|
1023
|
+
case 'delete':
|
|
1024
|
+
image.remove()
|
|
1025
|
+
destroyImageResizer(options, core)
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
const imageClientRect = image.getBoundingClientRect();
|
|
1029
|
+
resizer.style.width = imageClientRect.width + 'px';
|
|
1030
|
+
resizer.style.height = imageClientRect.height + 'px';
|
|
1031
|
+
resizer.style.top = imageClientRect.top + 'px';
|
|
1032
|
+
resizer.style.left = imageClientRect.left + 'px';
|
|
1033
|
+
position.textContent = `${parseInt(resizer.style.width)}x${parseInt(resizer.style.height)} `;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
makeEditToolbar(options, core, { type, image, updateImage })
|
|
1037
|
+
|
|
1038
|
+
core.elements.resizerImage = image
|
|
1039
|
+
core.elements.resizer = resizer
|
|
1040
|
+
core.elements.iframeWindow.body.appendChild(resizer)
|
|
1041
|
+
core.resizerHandler = (e) => updateImageResizerPosition(e, core);
|
|
1042
|
+
core.deleteImageHandler = (e) => {
|
|
1043
|
+
if (e.key === 'Backspace' || e.key === 'Delete') updateImage('delete')
|
|
1044
|
+
}
|
|
1045
|
+
core.elements.iframeWindow.addEventListener('scroll', core.resizerHandler)
|
|
1046
|
+
core.elements.iframeWindow.addEventListener('keydown', core.deleteImageHandler)
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const createPreviewModal = (imageSrc, core, options) => {
|
|
1050
|
+
// Create overlay
|
|
1051
|
+
const overlay = document.createElement('div');
|
|
1052
|
+
overlay.className = `${options.classPrefix}${CLASSES.PREVIEW_MODAL_OVERLAY} `
|
|
1053
|
+
|
|
1054
|
+
// Create modal content
|
|
1055
|
+
const modalContent = document.createElement('div');
|
|
1056
|
+
modalContent.className = `${options.classPrefix}${CLASSES.PREVIEW_MODAL_CONTENT} `
|
|
1057
|
+
|
|
1058
|
+
// Close button
|
|
1059
|
+
const closeButton = document.createElement('span');
|
|
1060
|
+
closeButton.className = `${options.classPrefix}${CLASSES.PREVIEW_MODAL_CLOSE} `
|
|
1061
|
+
closeButton.innerHTML = SVG.CLOSE;
|
|
1062
|
+
closeButton.onclick = () => overlay.remove();
|
|
1063
|
+
|
|
1064
|
+
// Create modal body
|
|
1065
|
+
const modalBody = document.createElement('div');
|
|
1066
|
+
modalBody.className = `${options.classPrefix}${CLASSES.PREVIEW_MODAL_BODY} `
|
|
1067
|
+
|
|
1068
|
+
// Image element
|
|
1069
|
+
const image = document.createElement('img');
|
|
1070
|
+
image.src = imageSrc;
|
|
1071
|
+
image.alt = 'Preview Image';
|
|
1072
|
+
|
|
1073
|
+
modalBody.appendChild(image);
|
|
1074
|
+
modalContent.append(closeButton, modalBody);
|
|
1075
|
+
overlay.appendChild(modalContent);
|
|
1076
|
+
|
|
1077
|
+
// Append to body
|
|
1078
|
+
document.body.appendChild(overlay);
|
|
1079
|
+
|
|
1080
|
+
overlay.addEventListener('click', (event) => {
|
|
1081
|
+
if (event.target !== image) {
|
|
1082
|
+
overlay.remove();
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
// Show modal
|
|
1086
|
+
setTimeout(() => {
|
|
1087
|
+
overlay.classList.add('active');
|
|
1088
|
+
}, 10);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const updateImageResizerPosition = (e, core) => {
|
|
1092
|
+
const imageClientRect = core.elements.resizerImage.getBoundingClientRect();
|
|
1093
|
+
core.elements.resizer.style.width = imageClientRect.width + 'px';
|
|
1094
|
+
core.elements.resizer.style.height = imageClientRect.height + 'px';
|
|
1095
|
+
core.elements.resizer.style.top = imageClientRect.top + 'px';
|
|
1096
|
+
core.elements.resizer.style.left = imageClientRect.left + 'px';
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
const destroyImageResizer = (options, core) => {
|
|
1100
|
+
changeAllToolbarState(core, 'enabled')
|
|
1101
|
+
if (core.elements.resizerPlugin) core.elements.toolbarContainer.removeChild(core.elements.resizerPlugin)
|
|
1102
|
+
if (core.elements.resizer) core.elements.iframeWindow.body.removeChild(core.elements.resizer)
|
|
1103
|
+
core.elements.iframeWindow.removeEventListener('scroll', core.resizerHandler, false)
|
|
1104
|
+
core.elements.iframeWindow.removeEventListener('scroll', core.deleteImageHandler, false)
|
|
1105
|
+
core.elements.iframeWindow.removeEventListener('keydown', core.deleteImageHandler, false)
|
|
1106
|
+
delete core.elements.resizer
|
|
1107
|
+
delete core.resizerHandler
|
|
1108
|
+
delete core.deleteImageHandler
|
|
1109
|
+
delete core.elements.resizerImage
|
|
1110
|
+
delete core.elements.resizerPlugin
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const initTableEditPlugin = (td, options, core) => {
|
|
1114
|
+
destroyTableEditPlugin(options, core)
|
|
1115
|
+
const table = td.closest('table');
|
|
1116
|
+
if (!table) return
|
|
1117
|
+
|
|
1118
|
+
const tableClientRect = table.getBoundingClientRect();
|
|
1119
|
+
let firstX, firstY;
|
|
1120
|
+
|
|
1121
|
+
const handleMouseDown = (e) => {
|
|
1122
|
+
firstX = e.clientX || e.touches[0].clientX;
|
|
1123
|
+
firstY = e.clientY || e.touches[0].clientY;
|
|
1124
|
+
|
|
1125
|
+
core.elements.iframeWindow.addEventListener('mousemove', handleMouseMove)
|
|
1126
|
+
core.elements.iframeWindow.addEventListener('mouseup', handleMouseUp)
|
|
1127
|
+
core.elements.iframeWindow.addEventListener('touchmove', handleMouseMove)
|
|
1128
|
+
core.elements.iframeWindow.addEventListener('touchend', handleMouseUp)
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const handleMouseMove = (e) => {
|
|
1132
|
+
// Calculate the new dimensions
|
|
1133
|
+
const distanceX = (e.clientX || e.touches[0].clientX) - firstX
|
|
1134
|
+
const distanceY = (e.clientY || e.touches[0].clientY) - firstY
|
|
1135
|
+
const width = tableClientRect.width + distanceX;
|
|
1136
|
+
const height = tableClientRect.height + distanceY;
|
|
1137
|
+
|
|
1138
|
+
table.style.width = width + 'px';
|
|
1139
|
+
table.style.height = height + 'px';
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
const handleMouseUp = (e) => {
|
|
1143
|
+
core.elements.iframeWindow.removeEventListener('mousemove', handleMouseMove, false)
|
|
1144
|
+
core.elements.iframeWindow.removeEventListener('mouseup', handleMouseUp, false)
|
|
1145
|
+
core.elements.iframeWindow.removeEventListener('touchmove', handleMouseMove, false)
|
|
1146
|
+
core.elements.iframeWindow.removeEventListener('touchend', handleMouseUp, false)
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const updateTable = (event, value) => {
|
|
1150
|
+
const rowIndex = td.parentElement.rowIndex;
|
|
1151
|
+
const cellIndex = td.cellIndex;
|
|
1152
|
+
const colCount = table.rows[0].cells.length; // Number of columns in the table
|
|
1153
|
+
|
|
1154
|
+
const addCellWithBorder = (row, index) => {
|
|
1155
|
+
const newCell = row.insertCell(index);
|
|
1156
|
+
newCell.innerHTML = '\u00A0'; // Add content (empty for now)
|
|
1157
|
+
newCell.style.border = '1px solid black'; // Set the border
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Helper function to toggle border between 'none' and '1px solid black'
|
|
1161
|
+
const toggleBorder = (cell, side) => {
|
|
1162
|
+
if (cell) {
|
|
1163
|
+
const currentBorder = cell.style[side];
|
|
1164
|
+
cell.style[side] = ['none', 'medium', 'medium none', '0px none rgb(0, 0, 0)'].includes(currentBorder) ? '1px solid black' : 'none';
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
// Helper function to check if the table has no rows and remove the table if needed
|
|
1169
|
+
const checkAndRemoveTable = () => {
|
|
1170
|
+
if (table.rows.length === 0) {
|
|
1171
|
+
table.remove();
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
switch (event) {
|
|
1176
|
+
case 'insert-row-above': {
|
|
1177
|
+
const newRow = table.insertRow(rowIndex); // Insert row above
|
|
1178
|
+
for (let i = 0; i < colCount; i++) {
|
|
1179
|
+
addCellWithBorder(newRow, i); // Add proper <td> cells with border
|
|
1180
|
+
}
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
case 'insert-row-below': {
|
|
1184
|
+
const newRow = table.insertRow(rowIndex + 1); // Insert row below
|
|
1185
|
+
for (let i = 0; i < colCount; i++) {
|
|
1186
|
+
addCellWithBorder(newRow, i); // Add proper <td> cells with border
|
|
1187
|
+
}
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
case 'delete-row': {
|
|
1191
|
+
table.deleteRow(rowIndex); // Delete the row
|
|
1192
|
+
destroyTableEditPlugin(options, core)
|
|
1193
|
+
checkAndRemoveTable()
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
case 'merge-cells-horizontal': {
|
|
1197
|
+
const nextCell = td.nextElementSibling;
|
|
1198
|
+
if (nextCell) {
|
|
1199
|
+
// Check if next cell has data and merge accordingly
|
|
1200
|
+
if (nextCell.innerHTML.trim() !== '') {
|
|
1201
|
+
td.innerHTML += '<br/>' + nextCell.innerHTML; // Add content of next cell with line break
|
|
1202
|
+
}
|
|
1203
|
+
// Adjust colSpan to include the next cell
|
|
1204
|
+
td.colSpan = (td.colSpan || 1) + (nextCell.colSpan || 1); // Merge colSpan correctly
|
|
1205
|
+
nextCell.remove();
|
|
1206
|
+
}
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1209
|
+
case 'insert-column-left': {
|
|
1210
|
+
for (const element of table.rows) {
|
|
1211
|
+
const row = element;
|
|
1212
|
+
addCellWithBorder(row, cellIndex); // Add a new <td> with border to the left
|
|
1213
|
+
}
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
case 'insert-column-right': {
|
|
1217
|
+
for (const element of table.rows) {
|
|
1218
|
+
const row = element;
|
|
1219
|
+
addCellWithBorder(row, cellIndex + 1); // Add a new <td> with border to the right
|
|
1220
|
+
}
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
case 'delete-column': {
|
|
1224
|
+
for (const element of table.rows) {
|
|
1225
|
+
element.deleteCell(cellIndex); // Delete the cell at the current index
|
|
1226
|
+
}
|
|
1227
|
+
checkAndRemoveTable()
|
|
1228
|
+
destroyTableEditPlugin(options, core)
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
case 'merge-cells-vertical': {
|
|
1232
|
+
const nextRow = table.rows[rowIndex + 1];
|
|
1233
|
+
if (nextRow) {
|
|
1234
|
+
const nextCell = nextRow.cells[cellIndex];
|
|
1235
|
+
if (nextCell) {
|
|
1236
|
+
// Check if next cell has data and merge accordingly
|
|
1237
|
+
if (nextCell.innerHTML.trim() !== '') {
|
|
1238
|
+
td.innerHTML += '<br/>' + nextCell.innerHTML; // Add content of next cell with line break
|
|
1239
|
+
}
|
|
1240
|
+
// Adjust rowSpan to include the next row's cell
|
|
1241
|
+
td.rowSpan = (td.rowSpan || 1) + (nextCell.rowSpan || 1); // Merge rowSpan correctly
|
|
1242
|
+
nextCell.remove();
|
|
1243
|
+
}
|
|
1244
|
+
// Check if the row is now empty and remove it if it is
|
|
1245
|
+
const isRowEmpty = Array.from(nextRow.cells).every(cell => cell.innerHTML.trim() === '');
|
|
1246
|
+
if (isRowEmpty) {
|
|
1247
|
+
nextRow.remove();
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
case 'cell-border-top': {
|
|
1253
|
+
toggleBorder(td, 'borderTop');
|
|
1254
|
+
if (rowIndex > 0) {
|
|
1255
|
+
const aboveCell = table.rows[rowIndex - 1].cells[cellIndex];
|
|
1256
|
+
toggleBorder(aboveCell, 'borderBottom');
|
|
1257
|
+
}
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
case 'cell-border-right': {
|
|
1261
|
+
toggleBorder(td, 'borderRight');
|
|
1262
|
+
if (cellIndex < table.rows[rowIndex].cells.length - 1) {
|
|
1263
|
+
const rightCell = table.rows[rowIndex].cells[cellIndex + 1];
|
|
1264
|
+
toggleBorder(rightCell, 'borderLeft');
|
|
1265
|
+
}
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
case 'cell-border-bottom': {
|
|
1269
|
+
toggleBorder(td, 'borderBottom');
|
|
1270
|
+
if (rowIndex < table.rows.length - 1) {
|
|
1271
|
+
const belowCell = table.rows[rowIndex + 1].cells[cellIndex];
|
|
1272
|
+
toggleBorder(belowCell, 'borderTop');
|
|
1273
|
+
}
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
case 'cell-border-left': {
|
|
1277
|
+
toggleBorder(td, 'borderLeft');
|
|
1278
|
+
if (cellIndex > 0) {
|
|
1279
|
+
const leftCell = table.rows[rowIndex].cells[cellIndex - 1];
|
|
1280
|
+
toggleBorder(leftCell, 'borderRight');
|
|
1281
|
+
}
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
case 'cell-border-full': {
|
|
1285
|
+
td.style.border = '1px solid black';
|
|
1286
|
+
// Handle top neighbor
|
|
1287
|
+
if (rowIndex > 0) {
|
|
1288
|
+
if (table.rows[rowIndex - 1].cells[cellIndex])
|
|
1289
|
+
table.rows[rowIndex - 1].cells[cellIndex].style.borderBottom = '1px solid black';
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Handle bottom neighbor
|
|
1293
|
+
if (rowIndex < table.rows.length - 1) {
|
|
1294
|
+
if (table.rows[rowIndex + 1].cells[cellIndex])
|
|
1295
|
+
table.rows[rowIndex + 1].cells[cellIndex].style.borderTop = '1px solid black';
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Handle left neighbor
|
|
1299
|
+
if (cellIndex > 0) {
|
|
1300
|
+
if (table.rows[rowIndex].cells[cellIndex - 1])
|
|
1301
|
+
table.rows[rowIndex].cells[cellIndex - 1].style.borderRight = '1px solid black';
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Handle right neighbor
|
|
1305
|
+
if (cellIndex < table.rows[rowIndex].cells.length - 1) {
|
|
1306
|
+
if (table.rows[rowIndex].cells[cellIndex + 1])
|
|
1307
|
+
table.rows[rowIndex].cells[cellIndex + 1].style.borderLeft = '1px solid black';
|
|
1308
|
+
}
|
|
1309
|
+
break;
|
|
1310
|
+
}
|
|
1311
|
+
case 'cell-border-none': {
|
|
1312
|
+
// Remove all borders from the current cell
|
|
1313
|
+
td.style.border = 'none';
|
|
1314
|
+
|
|
1315
|
+
// Handle top neighbor
|
|
1316
|
+
if (rowIndex > 0) {
|
|
1317
|
+
if (table.rows[rowIndex - 1].cells[cellIndex])
|
|
1318
|
+
table.rows[rowIndex - 1].cells[cellIndex].style.borderBottom = 'none';
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// Handle bottom neighbor
|
|
1322
|
+
if (rowIndex < table.rows.length - 1) {
|
|
1323
|
+
if (table.rows[rowIndex + 1].cells[cellIndex])
|
|
1324
|
+
table.rows[rowIndex + 1].cells[cellIndex].style.borderTop = 'none';
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// Handle left neighbor
|
|
1328
|
+
if (cellIndex > 0) {
|
|
1329
|
+
if (table.rows[rowIndex].cells[cellIndex - 1])
|
|
1330
|
+
table.rows[rowIndex].cells[cellIndex - 1].style.borderRight = 'none';
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Handle right neighbor
|
|
1334
|
+
if (cellIndex < table.rows[rowIndex].cells.length - 1) {
|
|
1335
|
+
if (table.rows[rowIndex].cells[cellIndex + 1])
|
|
1336
|
+
table.rows[rowIndex].cells[cellIndex + 1].style.borderLeft = 'none';
|
|
1337
|
+
}
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
case 'fixed-column-width': {
|
|
1341
|
+
if (table.style.tableLayout === 'fixed') {
|
|
1342
|
+
table.style.tableLayout = 'auto';
|
|
1343
|
+
} else {
|
|
1344
|
+
table.style.tableLayout = 'fixed';
|
|
1345
|
+
table.style.wordBreak = 'break-all'
|
|
1346
|
+
}
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
case 'border-color': {
|
|
1350
|
+
td.style.borderColor = value;
|
|
1351
|
+
|
|
1352
|
+
// Handle top neighbor
|
|
1353
|
+
if (rowIndex > 0) {
|
|
1354
|
+
if (table.rows[rowIndex - 1].cells[cellIndex])
|
|
1355
|
+
table.rows[rowIndex - 1].cells[cellIndex].style.borderBottomColor = value;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Handle bottom neighbor
|
|
1359
|
+
if (rowIndex < table.rows.length - 1) {
|
|
1360
|
+
if (table.rows[rowIndex + 1].cells[cellIndex])
|
|
1361
|
+
table.rows[rowIndex + 1].cells[cellIndex].style.borderTopColor = value;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Handle left neighbor
|
|
1365
|
+
if (cellIndex > 0) {
|
|
1366
|
+
if (table.rows[rowIndex].cells[cellIndex - 1])
|
|
1367
|
+
table.rows[rowIndex].cells[cellIndex - 1].style.borderRightColor = value;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// Handle right neighbor
|
|
1371
|
+
if (cellIndex < table.rows[rowIndex].cells.length - 1) {
|
|
1372
|
+
if (table.rows[rowIndex].cells[cellIndex + 1])
|
|
1373
|
+
table.rows[rowIndex].cells[cellIndex + 1].style.borderLeftColor = value;
|
|
1374
|
+
}
|
|
1375
|
+
break;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
makeEditToolbar(options, core, { type: 'td', td, updateTable })
|
|
1381
|
+
|
|
1382
|
+
|
|
1383
|
+
const resizer = document.createElement('div');
|
|
1384
|
+
resizer.className = `table-resizer`
|
|
1385
|
+
resizer.style.width = tableClientRect.width + 'px';
|
|
1386
|
+
resizer.style.height = tableClientRect.height + 'px';
|
|
1387
|
+
resizer.style.top = tableClientRect.top + 'px';
|
|
1388
|
+
resizer.style.left = tableClientRect.left + 'px';
|
|
1389
|
+
|
|
1390
|
+
const resizerButton = document.createElement('button');
|
|
1391
|
+
resizerButton.className = `table-resizer-btn`
|
|
1392
|
+
resizerButton.style.top = tableClientRect.bottom - 6 + 'px';
|
|
1393
|
+
resizerButton.style.left = tableClientRect.right - 6 + 'px';
|
|
1394
|
+
resizerButton.addEventListener('mousedown', handleMouseDown)
|
|
1395
|
+
resizerButton.addEventListener('touchstart', handleMouseDown)
|
|
1396
|
+
|
|
1397
|
+
resizer.appendChild(resizerButton)
|
|
1398
|
+
|
|
1399
|
+
core.elements.table = table
|
|
1400
|
+
core.elements.tableResizer = resizer
|
|
1401
|
+
core.elements.tableResizerBtn = resizerButton
|
|
1402
|
+
core.resizerHandler = (e) => updateTableResizerPosition(options, core);
|
|
1403
|
+
core.elements.iframeWindow.addEventListener('scroll', core.resizerHandler)
|
|
1404
|
+
core.elements.iframeWindow.body.appendChild(resizer)
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
const updateTableResizerPosition = (options, core) => {
|
|
1408
|
+
if (core.elements.tableResizerBtn) {
|
|
1409
|
+
if (!core.elements.editor.contains(core.elements.table)) return destroyTableEditPlugin(options, core)
|
|
1410
|
+
const tableClientRect = core.elements.table.getBoundingClientRect();
|
|
1411
|
+
core.elements.tableResizer.style.width = tableClientRect.width + 'px';
|
|
1412
|
+
core.elements.tableResizer.style.height = tableClientRect.height + 'px';
|
|
1413
|
+
core.elements.tableResizer.style.top = tableClientRect.top + 'px';
|
|
1414
|
+
core.elements.tableResizer.style.left = tableClientRect.left + 'px';
|
|
1415
|
+
core.elements.tableResizerBtn.style.top = tableClientRect.bottom - 6 + 'px';
|
|
1416
|
+
core.elements.tableResizerBtn.style.left = tableClientRect.right - 6 + 'px';
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
const destroyTableEditPlugin = (options, core) => {
|
|
1421
|
+
if (core.elements.resizerPlugin) core.elements.toolbarContainer.removeChild(core.elements.resizerPlugin)
|
|
1422
|
+
if (core.elements.tableResizer) core.elements.iframeWindow.body.removeChild(core.elements.tableResizer)
|
|
1423
|
+
core.elements.iframeWindow.removeEventListener('scroll', core.resizerHandler, false)
|
|
1424
|
+
delete core.elements.resizerPlugin
|
|
1425
|
+
delete core.resizerHandler
|
|
1426
|
+
delete core.elements.table
|
|
1427
|
+
delete core.elements.tableResizer
|
|
1428
|
+
delete core.elements.tableResizerBtn
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
const changeToolbarStatByPlugin = (plugin, action, value) => {
|
|
1432
|
+
switch (action) {
|
|
1433
|
+
case 'disabled':
|
|
1434
|
+
if (plugin.getAttribute('data-type') === 'button') {
|
|
1435
|
+
plugin.disabled = true
|
|
1436
|
+
plugin.classList.add('disabled')
|
|
1437
|
+
} else if (plugin.getAttribute('data-type') === 'dropdown' || plugin.getAttribute('data-type') === 'select') {
|
|
1438
|
+
plugin.childNodes[0].disabled = true
|
|
1439
|
+
plugin.childNodes[0].classList.add('disabled')
|
|
1440
|
+
} else if (plugin.getAttribute('data-type') === 'color') {
|
|
1441
|
+
plugin.childNodes[0].disabled = true
|
|
1442
|
+
plugin.classList.add('disabled')
|
|
1443
|
+
}
|
|
1444
|
+
break;
|
|
1445
|
+
case 'enabled':
|
|
1446
|
+
if (plugin.getAttribute('data-type') === 'button') {
|
|
1447
|
+
plugin.disabled = false
|
|
1448
|
+
plugin.classList.remove('disabled')
|
|
1449
|
+
} else if (plugin.getAttribute('data-type') === 'dropdown' || plugin.getAttribute('data-type') === 'select') {
|
|
1450
|
+
plugin.childNodes[0].disabled = false
|
|
1451
|
+
plugin.childNodes[0].classList.remove('disabled')
|
|
1452
|
+
} else if (plugin.getAttribute('data-type') === 'color') {
|
|
1453
|
+
plugin.childNodes[0].disabled = false
|
|
1454
|
+
plugin.classList.remove('disabled')
|
|
1455
|
+
}
|
|
1456
|
+
break;
|
|
1457
|
+
case 'active':
|
|
1458
|
+
if (plugin.getAttribute('data-type') === 'button') {
|
|
1459
|
+
plugin.classList.add('active')
|
|
1460
|
+
} else if (plugin.getAttribute('data-type') === 'dropdown' || plugin.getAttribute('data-type') === 'select') {
|
|
1461
|
+
plugin.childNodes[0].classList.add('active')
|
|
1462
|
+
} else if (plugin.getAttribute('data-type') === 'color') {
|
|
1463
|
+
plugin.classList.add('active')
|
|
1464
|
+
}
|
|
1465
|
+
break;
|
|
1466
|
+
case 'inactive':
|
|
1467
|
+
if (plugin.getAttribute('data-type') === 'button') {
|
|
1468
|
+
plugin.classList.remove('active')
|
|
1469
|
+
} else if (plugin.getAttribute('data-type') === 'dropdown' || plugin.getAttribute('data-type') === 'select') {
|
|
1470
|
+
plugin.childNodes[0].classList.remove('active')
|
|
1471
|
+
} else if (plugin.getAttribute('data-type') === 'color') {
|
|
1472
|
+
plugin.classList.remove('active')
|
|
1473
|
+
}
|
|
1474
|
+
break;
|
|
1475
|
+
case 'font':
|
|
1476
|
+
plugin.childNodes[0].childNodes[0].childNodes[0].innerHTML = FONTS[value] ? FONTS[value] : value ? value : 'Font'
|
|
1477
|
+
break;
|
|
1478
|
+
case 'font-size':
|
|
1479
|
+
plugin.childNodes[0].childNodes[0].childNodes[0].innerHTML = FONT_SIZES[value] ? FONT_SIZES[value] : value ? value : 'Font-Size'
|
|
1480
|
+
break;
|
|
1481
|
+
case 'format-block':
|
|
1482
|
+
plugin.childNodes[0].childNodes[0].childNodes[0].innerHTML = FORMATS[value] ?? 'Format'
|
|
1483
|
+
break;
|
|
1484
|
+
case 'font_color':
|
|
1485
|
+
plugin.childNodes[0].value = value || '#000000'
|
|
1486
|
+
break;
|
|
1487
|
+
case 'highlight_color':
|
|
1488
|
+
plugin.childNodes[0].value = value || '#ffffff'
|
|
1489
|
+
break;
|
|
1490
|
+
case 'icon':
|
|
1491
|
+
plugin.innerHTML = value
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
const changeAllToolbarState = (core, action, preventPlugins = []) => {
|
|
1496
|
+
Object.keys(core.elements.toolbar).forEach(pluginName => {
|
|
1497
|
+
core.elements.toolbar[pluginName].forEach(plugin => {
|
|
1498
|
+
if (preventPlugins.includes(pluginName)) return
|
|
1499
|
+
changeToolbarStatByPlugin(plugin, action)
|
|
1500
|
+
})
|
|
1501
|
+
})
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const changeToolbarStateByName = (core, action, pluginNames = []) => {
|
|
1505
|
+
Object.keys(core.elements.toolbar).forEach(pluginName => {
|
|
1506
|
+
if (!pluginNames.includes(pluginName)) return
|
|
1507
|
+
core.elements.toolbar[pluginName].forEach(plugin => {
|
|
1508
|
+
changeToolbarStatByPlugin(plugin, action)
|
|
1509
|
+
})
|
|
1510
|
+
})
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
const changeToolbarValueByName = (core, pluginName, value) => {
|
|
1514
|
+
core.elements.toolbar[pluginName]?.forEach(plugin => {
|
|
1515
|
+
changeToolbarStatByPlugin(plugin, pluginName, value)
|
|
1516
|
+
})
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const changeToolbarHtmlByName = (core, pluginName, value) => {
|
|
1520
|
+
core.elements.toolbar[pluginName]?.forEach(plugin => {
|
|
1521
|
+
changeToolbarStatByPlugin(plugin, 'icon', value)
|
|
1522
|
+
})
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
const rgbToHex = (rgb) => {
|
|
1526
|
+
const rgbArray = rgb.match(/\d+/g); // Extracts the numeric RGB values
|
|
1527
|
+
return `#${rgbArray.map(x => {
|
|
1528
|
+
const hex = parseInt(x).toString(16);
|
|
1529
|
+
return hex.length === 1 ? '0' + hex : hex; // Pads with 0 if needed
|
|
1530
|
+
}).join('')
|
|
1531
|
+
} `.slice(0, 7);
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
const extractSocialMediaId = (url) => {
|
|
1535
|
+
for (const platform in SOCIAL_MEDIA_PATTERNS) {
|
|
1536
|
+
const match = url.match(SOCIAL_MEDIA_PATTERNS[platform]);
|
|
1537
|
+
if (match) {
|
|
1538
|
+
|
|
1539
|
+
return { platform, id: match[1] };
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
return { platform: null, id: null };
|
|
1544
|
+
};
|
|
1545
|
+
|
|
1546
|
+
const constructEmbedUrl = (platform, id) => {
|
|
1547
|
+
if (!SOCIAL_MEDIA_BASEURLS[platform] || !id) return null;
|
|
1548
|
+
|
|
1549
|
+
switch (platform) {
|
|
1550
|
+
case "INSTAGRAM":
|
|
1551
|
+
case "THREADS":
|
|
1552
|
+
return `${SOCIAL_MEDIA_BASEURLS[platform]}${id}/embed`;
|
|
1553
|
+
case "REDDIT":
|
|
1554
|
+
return `${SOCIAL_MEDIA_BASEURLS[platform]}${id}/?ref_source=embed&ref=share`;
|
|
1555
|
+
default:
|
|
1556
|
+
return `${SOCIAL_MEDIA_BASEURLS[platform]}${id}`;
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1560
|
+
const showAnchorPopover = (element, x, y, options, core) => {
|
|
1561
|
+
let href = element.getAttribute("href");
|
|
1562
|
+
|
|
1563
|
+
// Create popover container
|
|
1564
|
+
let popover = document.createElement("div");
|
|
1565
|
+
popover.id = "anchor-popover";
|
|
1566
|
+
popover.style.position = "absolute";
|
|
1567
|
+
popover.style.top = `${y + 5}px`;
|
|
1568
|
+
popover.style.left = `${x + 5}px`;
|
|
1569
|
+
popover.style.padding = "8px";
|
|
1570
|
+
popover.style.background = "white";
|
|
1571
|
+
popover.style.border = "1px solid #ccc";
|
|
1572
|
+
popover.style.borderRadius = "6px";
|
|
1573
|
+
popover.style.boxShadow = "0px 4px 8px rgba(0,0,0,0.2)";
|
|
1574
|
+
popover.style.zIndex = "1000";
|
|
1575
|
+
popover.style.display = "flex";
|
|
1576
|
+
popover.style.alignItems = "center";
|
|
1577
|
+
popover.style.gap = "5px";
|
|
1578
|
+
popover.style.whiteSpace = "nowrap";
|
|
1579
|
+
popover.style.maxWidth = "300px";
|
|
1580
|
+
|
|
1581
|
+
// Create clickable link
|
|
1582
|
+
let link = document.createElement("a");
|
|
1583
|
+
link.href = href;
|
|
1584
|
+
link.target = "_blank";
|
|
1585
|
+
link.textContent = href;
|
|
1586
|
+
link.style.textDecoration = "none";
|
|
1587
|
+
link.style.color = "#007bff";
|
|
1588
|
+
link.style.padding = "5px";
|
|
1589
|
+
link.style.overflow = "hidden";
|
|
1590
|
+
link.style.textOverflow = "ellipsis";
|
|
1591
|
+
link.style.webkitBoxOrient = "vertical";
|
|
1592
|
+
link.style.webkitLineClamp = "1"; // Limit to 2 lines
|
|
1593
|
+
link.style.maxWidth = "250px";
|
|
1594
|
+
link.style.wordBreak = "break-word"; // Ensure long URLs wrap properly
|
|
1595
|
+
|
|
1596
|
+
// Create Edit button
|
|
1597
|
+
let editButton = document.createElement("span");
|
|
1598
|
+
editButton.innerHTML = SVG.EDIT_LINK;
|
|
1599
|
+
editButton.style.padding = "5px";
|
|
1600
|
+
editButton.title = "Edit link";
|
|
1601
|
+
editButton.onclick = (event) => editAnchorTag(event, core, options);
|
|
1602
|
+
|
|
1603
|
+
let copyButton = document.createElement("span");
|
|
1604
|
+
copyButton.innerHTML = SVG.COPY_LINK
|
|
1605
|
+
copyButton.style.padding = "5px";
|
|
1606
|
+
copyButton.title = "Copy link";
|
|
1607
|
+
copyButton.onclick = (event) => navigator.clipboard.writeText(href);
|
|
1608
|
+
|
|
1609
|
+
// Create Delete button
|
|
1610
|
+
let deleteButton = document.createElement("span");
|
|
1611
|
+
deleteButton.innerHTML = SVG.DELETE_LINK
|
|
1612
|
+
deleteButton.style.padding = "5px";
|
|
1613
|
+
deleteButton.title = "Remove link"
|
|
1614
|
+
deleteButton.onclick = (event) => removeAnchorTag(event, element, core);
|
|
1615
|
+
|
|
1616
|
+
// Append elements to popover
|
|
1617
|
+
popover.append(link, copyButton, editButton, deleteButton);
|
|
1618
|
+
|
|
1619
|
+
let parent = core.elements.iframeWindow.body; // Change this if needed
|
|
1620
|
+
parent.appendChild(popover);
|
|
1621
|
+
|
|
1622
|
+
requestAnimationFrame(() => {
|
|
1623
|
+
let popoverRect = popover.getBoundingClientRect();
|
|
1624
|
+
let parentRect = parent.getBoundingClientRect(); // Get parent size
|
|
1625
|
+
|
|
1626
|
+
let finalX = x + 5;
|
|
1627
|
+
let finalY = y + 5;
|
|
1628
|
+
|
|
1629
|
+
let spaceRight = parentRect.right - (x + popoverRect.width);
|
|
1630
|
+
let spaceLeft = x - popoverRect.left - popoverRect.width;
|
|
1631
|
+
let spaceBottom = parentRect.bottom - (y + popoverRect.height);
|
|
1632
|
+
let spaceTop = y - parentRect.top - popoverRect.height;
|
|
1633
|
+
|
|
1634
|
+
// Prefer showing below if there's space
|
|
1635
|
+
if (spaceBottom > 0) {
|
|
1636
|
+
finalY = y + 5;
|
|
1637
|
+
}
|
|
1638
|
+
// Otherwise, show above
|
|
1639
|
+
else if (spaceTop > 0) {
|
|
1640
|
+
finalY = y - popoverRect.height - 5;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// Prefer showing on the right if there's space
|
|
1644
|
+
if (spaceRight > 0) {
|
|
1645
|
+
finalX = x + 5;
|
|
1646
|
+
}
|
|
1647
|
+
// Otherwise, show on the left
|
|
1648
|
+
else if (spaceLeft > 0) {
|
|
1649
|
+
finalX = x - popoverRect.width - 5;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// Ensure the popover stays within the parent’s boundaries
|
|
1653
|
+
finalX = Math.max(parentRect.left + 10, Math.min(finalX, parentRect.right - popoverRect.width - 10));
|
|
1654
|
+
finalY = Math.max(parentRect.top + 10, Math.min(finalY, parentRect.bottom - popoverRect.height - 10));
|
|
1655
|
+
|
|
1656
|
+
// Apply final position
|
|
1657
|
+
popover.style.left = `${finalX - parentRect.left}px`;
|
|
1658
|
+
popover.style.top = `${finalY - parentRect.top}px`;
|
|
1659
|
+
popover.style.visibility = "visible"; // Now show the popover
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
core.elements.anchorPopover = popover;
|
|
1663
|
+
|
|
1664
|
+
const closePopover = (event) => {
|
|
1665
|
+
if (!popover.contains(event.target) || event.key === "Escape") {
|
|
1666
|
+
destroyAnchorPopover(core);
|
|
1667
|
+
document.removeEventListener("click", closePopover);
|
|
1668
|
+
document.removeEventListener("keydown", closePopover);
|
|
1669
|
+
document.removeEventListener("scroll", closePopover, true);
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
document.addEventListener("click", closePopover);
|
|
1674
|
+
document.addEventListener("keydown", closePopover);
|
|
1675
|
+
document.addEventListener("scroll", closePopover, true);
|
|
1676
|
+
};
|
|
1677
|
+
|
|
1678
|
+
const editAnchorTag = (event, core, options) => {
|
|
1679
|
+
event.stopPropagation();
|
|
1680
|
+
|
|
1681
|
+
if (!core.elements.selectedElement || core.elements.selectedElement.tagName !== "A") {
|
|
1682
|
+
console.warn("No anchor tag selected for editing.");
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
const anchorElement = core.elements.selectedElement;
|
|
1687
|
+
let anchorLink = anchorElement.getAttribute("href") || "";
|
|
1688
|
+
let anchorText = anchorElement.innerText || "";
|
|
1689
|
+
|
|
1690
|
+
const body = document.createElement("div");
|
|
1691
|
+
|
|
1692
|
+
// Link Input
|
|
1693
|
+
const inputLink = document.createElement("input");
|
|
1694
|
+
inputLink.value = anchorLink;
|
|
1695
|
+
inputLink.type = "text";
|
|
1696
|
+
inputLink.placeholder = "Insert the link";
|
|
1697
|
+
|
|
1698
|
+
// Text Input
|
|
1699
|
+
const inputText = document.createElement("input");
|
|
1700
|
+
inputText.value = anchorText;
|
|
1701
|
+
inputText.type = "text";
|
|
1702
|
+
inputText.style.marginTop = "8px";
|
|
1703
|
+
inputText.placeholder = "Link text";
|
|
1704
|
+
|
|
1705
|
+
const span = document.createElement("span");
|
|
1706
|
+
span.className = "warning";
|
|
1707
|
+
|
|
1708
|
+
body.append(inputLink, inputText, span);
|
|
1709
|
+
|
|
1710
|
+
// Footer (Buttons)
|
|
1711
|
+
const footer = document.createElement("div");
|
|
1712
|
+
const button = document.createElement("button");
|
|
1713
|
+
button.type = "button";
|
|
1714
|
+
button.className = "submit";
|
|
1715
|
+
button.innerText = "Update";
|
|
1716
|
+
|
|
1717
|
+
footer.append(button);
|
|
1718
|
+
|
|
1719
|
+
// Open Modal
|
|
1720
|
+
const modal = openModal(
|
|
1721
|
+
{
|
|
1722
|
+
title: "Edit Link",
|
|
1723
|
+
bodyNode: body,
|
|
1724
|
+
footerNode: footer,
|
|
1725
|
+
},
|
|
1726
|
+
core,
|
|
1727
|
+
options
|
|
1728
|
+
);
|
|
1729
|
+
|
|
1730
|
+
inputLink.focus();
|
|
1731
|
+
|
|
1732
|
+
// Handle Enter Key Submission
|
|
1733
|
+
const onInputKeydown = (event) => {
|
|
1734
|
+
if (event.key === "Enter") {
|
|
1735
|
+
event.preventDefault();
|
|
1736
|
+
button.click();
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1740
|
+
inputLink.addEventListener("keydown", onInputKeydown);
|
|
1741
|
+
inputText.addEventListener("keydown", onInputKeydown);
|
|
1742
|
+
|
|
1743
|
+
// Button Click Logic
|
|
1744
|
+
button.onclick = () => {
|
|
1745
|
+
const newLink = inputLink.value.trim();
|
|
1746
|
+
const newText = inputText.value.trim();
|
|
1747
|
+
|
|
1748
|
+
if (REGEX.URL.test(newLink)) {
|
|
1749
|
+
anchorElement.setAttribute("href", newLink);
|
|
1750
|
+
anchorElement.innerText = newText || newLink;
|
|
1751
|
+
modal.close();
|
|
1752
|
+
} else {
|
|
1753
|
+
span.innerText = "Invalid URL";
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
destroyAnchorPopover(core);
|
|
1757
|
+
core.updateCaretPosition();
|
|
1758
|
+
};
|
|
1759
|
+
|
|
1760
|
+
const removeAnchorTag = (event, element, core) => {
|
|
1761
|
+
event.stopPropagation();
|
|
1762
|
+
const anchorContent = core.elements.selectedElement.innerHTML;
|
|
1763
|
+
core.elements.selectedElement.outerHTML = anchorContent;
|
|
1764
|
+
|
|
1765
|
+
destroyAnchorPopover(core);
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
const destroyAnchorPopover = (core) => {
|
|
1769
|
+
if (core.elements.anchorPopover) {
|
|
1770
|
+
core.elements.anchorPopover.style.display = "none"
|
|
1771
|
+
delete core.elements.anchorPopover;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
};
|
|
1775
|
+
|
|
1776
|
+
export {
|
|
1777
|
+
cleanHTML,
|
|
1778
|
+
debounce,
|
|
1779
|
+
makeToolbarButton,
|
|
1780
|
+
makeToolbarDropdown,
|
|
1781
|
+
openModal,
|
|
1782
|
+
initImageResizer,
|
|
1783
|
+
destroyImageResizer,
|
|
1784
|
+
changeAllToolbarState,
|
|
1785
|
+
changeToolbarValueByName,
|
|
1786
|
+
makeToolbarSelect,
|
|
1787
|
+
changeToolbarStateByName,
|
|
1788
|
+
changeToolbarHtmlByName,
|
|
1789
|
+
makeToolbarColor,
|
|
1790
|
+
initTableEditPlugin,
|
|
1791
|
+
destroyTableEditPlugin,
|
|
1792
|
+
updateTableResizerPosition,
|
|
1793
|
+
rgbToHex,
|
|
1794
|
+
extractSocialMediaId,
|
|
1795
|
+
constructEmbedUrl,
|
|
1796
|
+
showAnchorPopover,
|
|
1797
|
+
destroyAnchorPopover,
|
|
1798
|
+
}
|