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/plugin.js ADDED
@@ -0,0 +1,1055 @@
1
+ import { EMOJI_CATEGORIES, FONT_SIZE_OPTIONS, FONTS, FORMAT_OPTIONS, MIMETYPE, REGEX, SPECIAL_CHARACTERS, SVG } from "./constant"
2
+ import { giphy, pexels, tenor } from "./gallery";
3
+ import { changeAllToolbarState, changeToolbarHtmlByName, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, constructEmbedUrl, extractSocialMediaId, openModal } from "./utilities";
4
+
5
+ const applyTextFormat = (core, command) => {
6
+ core.elements.editor.focus();
7
+ core.elements.iframeWindow.execCommand(command);
8
+ core.updateCaretPosition()
9
+
10
+ const isActive = core.elements.iframeWindow.queryCommandState(command);
11
+ if (isActive) changeToolbarStateByName(core, 'active', [command])
12
+ else changeToolbarStateByName(core, 'inactive', [command])
13
+ }
14
+
15
+ const applyOrderList = (core) => {
16
+ core.elements.iframeWindow.execCommand('insertOrderedList');
17
+ core.elements.editor.focus();
18
+ core.updateCaretPosition()
19
+
20
+ const isActive = core.elements.iframeWindow.queryCommandState('insertOrderedList');
21
+ if (isActive) changeToolbarStateByName(core, 'active', ['ordered_list'])
22
+ else changeToolbarStateByName(core, 'inactive', ['ordered_list'])
23
+ changeToolbarStateByName(core, 'inactive', ['unordered_list'])
24
+ }
25
+
26
+ const PLUGINS = {
27
+ 'undo': {
28
+ title: 'Undo',
29
+ icon: SVG.UNDO,
30
+ type: 'button',
31
+ click: (event, core) => {
32
+ core.history.undo()
33
+ core.updateCaretPosition()
34
+ }
35
+ },
36
+ 'redo': {
37
+ title: 'Redo',
38
+ icon: SVG.REDO,
39
+ type: 'button',
40
+ click: (event, core) => {
41
+ core.history.redo()
42
+ core.updateCaretPosition()
43
+ }
44
+ },
45
+ 'bold': {
46
+ title: 'Bold',
47
+ icon: SVG.BOLD,
48
+ type: 'button',
49
+ click: (event, core) => {
50
+ applyTextFormat(core, 'bold')
51
+ }
52
+ },
53
+ 'underline': {
54
+ title: 'Underline',
55
+ icon: SVG.UNDERLINE,
56
+ type: 'button',
57
+ click: (event, core) => {
58
+ applyTextFormat(core, 'underline')
59
+ }
60
+ },
61
+ 'italic': {
62
+ title: 'Italic',
63
+ icon: SVG.ITALIC,
64
+ type: 'button',
65
+ click: (event, core) => {
66
+ applyTextFormat(core, 'italic')
67
+ }
68
+ },
69
+ 'strike': {
70
+ title: 'Strike',
71
+ icon: SVG.STRIKE,
72
+ type: 'button',
73
+ click: (event, core) => {
74
+ core.elements.editor.focus();
75
+ core.elements.iframeWindow.execCommand('strikeThrough');
76
+ core.updateCaretPosition()
77
+
78
+ const isActive = core.elements.iframeWindow.queryCommandState('strikeThrough');
79
+ if (isActive) changeToolbarStateByName(core, 'active', ['strike'])
80
+ else changeToolbarStateByName(core, 'inactive', ['strike'])
81
+ }
82
+ },
83
+ 'align_justify': {
84
+ title: 'Align Justify',
85
+ icon: SVG.ALIGN_JUSTIFY,
86
+ type: 'button',
87
+ click: (event, core) => {
88
+ core.elements.editor.focus();
89
+ core.elements.iframeWindow.execCommand('justifyFull');
90
+ core.updateCaretPosition()
91
+
92
+ changeToolbarStateByName(core, 'active', ['align_justify'])
93
+ changeToolbarStateByName(core, 'inactive', ['align_left', 'align_right', 'align_center'])
94
+ }
95
+ },
96
+ 'align_left': {
97
+ title: 'Align Left',
98
+ icon: SVG.ALIGN_LEFT,
99
+ type: 'button',
100
+ click: (event, core) => {
101
+ core.elements.editor.focus();
102
+ core.elements.iframeWindow.execCommand('justifyLeft');
103
+ core.updateCaretPosition()
104
+
105
+ changeToolbarStateByName(core, 'active', ['align_left'])
106
+ changeToolbarStateByName(core, 'inactive', ['align_justify', 'align_right', 'align_center'])
107
+ }
108
+ },
109
+ 'align_right': {
110
+ title: 'Align Right',
111
+ icon: SVG.ALIGN_RIGHT,
112
+ type: 'button',
113
+ click: (event, core) => {
114
+ core.elements.editor.focus();
115
+ core.elements.iframeWindow.execCommand('justifyRight');
116
+ core.updateCaretPosition()
117
+
118
+ changeToolbarStateByName(core, 'active', ['align_right'])
119
+ changeToolbarStateByName(core, 'inactive', ['align_justify', 'align_left', 'align_center'])
120
+ }
121
+ },
122
+ 'align_center': {
123
+ title: 'Align Center',
124
+ icon: SVG.ALIGN_CENTER,
125
+ type: 'button',
126
+ click: (event, core) => {
127
+ core.elements.editor.focus();
128
+ core.elements.iframeWindow.execCommand('justifyCenter');
129
+ core.updateCaretPosition()
130
+
131
+ changeToolbarStateByName(core, 'active', ['align_center'])
132
+ changeToolbarStateByName(core, 'inactive', ['align_justify', 'align_left', 'align_right'])
133
+ }
134
+ },
135
+ 'code_view': {
136
+ title: 'Code View',
137
+ icon: SVG.CODE_VIEW,
138
+ type: 'button',
139
+ click: (event, core) => {
140
+ if (core.state.isCodeViewOpen) {
141
+ core.elements.codeview.style.display = 'none'
142
+ core.elements.iframeContainer.style.display = 'block'
143
+ if (core.elements.stepper) core.elements.stepper.style.display = 'flex'
144
+ const _cleanHTML = cleanHTML(core.elements.codeview.value)
145
+ core.onChange(_cleanHTML);
146
+ core.elements.editor.innerHTML = _cleanHTML
147
+ core.elements.editor.focus()
148
+ changeAllToolbarState(core, 'enabled')
149
+ } else {
150
+ core.hidePlaceholder()
151
+ core.elements.iframeContainer.style.display = 'none'
152
+ if (core.elements.stepper) core.elements.stepper.style.display = 'none'
153
+ core.elements.codeview.value = core.html
154
+ core.elements.codeview.style.display = 'block'
155
+ core.elements.codeview.focus()
156
+ changeAllToolbarState(core, 'disabled', ['code_view'])
157
+ }
158
+ core.state.isCodeViewOpen = !core.state.isCodeViewOpen
159
+ core.updateCaretPosition()
160
+ }
161
+ },
162
+ 'remove_format': {
163
+ title: "Remove Format",
164
+ icon: SVG.REMOVE_FORMAT,
165
+ type: 'button',
166
+ click: (event, core) => {
167
+ core.elements.iframeWindow.execCommand('removeFormat');
168
+ core.elements.editor.focus();
169
+ core.updateCaretPosition()
170
+ }
171
+ },
172
+ 'subscript': {
173
+ title: 'Sub Script',
174
+ icon: SVG.SUBSCRIPT,
175
+ type: 'button',
176
+ click: (event, core) => {
177
+ core.elements.editor.focus();
178
+ core.elements.iframeWindow.execCommand('subscript');
179
+ core.updateCaretPosition()
180
+
181
+ const isActive = core.elements.iframeWindow.queryCommandState('subscript');
182
+ if (isActive) changeToolbarStateByName(core, 'active', ['subscript'])
183
+ else changeToolbarStateByName(core, 'inactive', ['subscript'])
184
+ changeToolbarStateByName(core, 'inactive', ['superscript'])
185
+ }
186
+ },
187
+ 'superscript': {
188
+ title: 'Super Script',
189
+ icon: SVG.SUPERSCRIPT,
190
+ type: 'button',
191
+ click: (event, core) => {
192
+ core.elements.editor.focus();
193
+ core.elements.iframeWindow.execCommand('superscript');
194
+ core.updateCaretPosition()
195
+
196
+ const isActive = core.elements.iframeWindow.queryCommandState('superscript');
197
+ if (isActive) changeToolbarStateByName(core, 'active', ['superscript'])
198
+ else changeToolbarStateByName(core, 'inactive', ['superscript'])
199
+ changeToolbarStateByName(core, 'inactive', ['subscript'])
200
+ }
201
+ },
202
+ 'font': {
203
+ title: 'Font',
204
+ icon: 'Font',
205
+ type: 'select',
206
+ options: Object.keys(FONTS).map(font => ({
207
+ label: FONTS[font], value: font
208
+ })),
209
+ width: '140px',
210
+ click: (name, core, options) => {
211
+ core.elements.editor.focus();
212
+ core.elements.iframeWindow.execCommand('fontName', false, name);
213
+
214
+ core.updateCaretPosition()
215
+ changeToolbarValueByName(core, 'font', name)
216
+ }
217
+ },
218
+ 'font-size': {
219
+ title: 'Font',
220
+ icon: 'Font Size',
221
+ type: 'select',
222
+ options: Object.keys(FONT_SIZE_OPTIONS).map(size => ({
223
+ label: FONT_SIZE_OPTIONS[size], value: size
224
+ })),
225
+ width: '100px',
226
+ click: (size, core, options) => {
227
+ core.elements.editor.focus();
228
+ core.elements.iframeWindow.execCommand('fontSize', false, parseInt(size));
229
+
230
+ core.updateCaretPosition()
231
+ changeToolbarValueByName(core, 'font-size', size)
232
+ }
233
+ },
234
+ 'format-block': {
235
+ title: 'Format Block',
236
+ icon: 'Format',
237
+ type: 'select',
238
+ options: Object.keys(FORMAT_OPTIONS).map(format => ({
239
+ label: FORMAT_OPTIONS[format], value: format
240
+ })),
241
+ width: '110px',
242
+ click: (tag, core, options) => {
243
+ core.elements.editor.focus();
244
+ core.elements.iframeWindow.execCommand('formatBlock', false, tag);
245
+
246
+ core.updateCaretPosition()
247
+ changeToolbarValueByName(core, 'format-block', tag)
248
+ }
249
+ },
250
+ 'ordered_list': {
251
+ title: "Ordered List",
252
+ icon: SVG.LIST_NUMBER,
253
+ type: 'button',
254
+ click: (event, core, options) => {
255
+ core.elements.editor.focus();
256
+ core.elements.iframeWindow.execCommand('insertOrderedList');
257
+ core.updateCaretPosition()
258
+
259
+ const isActive = core.elements.iframeWindow.queryCommandState('insertOrderedList');
260
+ if (isActive) changeToolbarStateByName(core, 'active', ['ordered_list'])
261
+ else changeToolbarStateByName(core, 'inactive', ['ordered_list'])
262
+ changeToolbarStateByName(core, 'inactive', ['unordered_list'])
263
+ }
264
+ },
265
+ 'unordered_list': {
266
+ title: "Unordered List",
267
+ icon: SVG.LIST_BULLETS,
268
+ type: 'button',
269
+ click: (event, core, options) => {
270
+ core.elements.editor.focus();
271
+ core.elements.iframeWindow.execCommand('insertUnorderedList');
272
+ core.updateCaretPosition()
273
+
274
+ const isActive = core.elements.iframeWindow.queryCommandState('insertUnorderedList');
275
+ if (isActive) changeToolbarStateByName(core, 'active', ['unordered_list'])
276
+ else changeToolbarStateByName(core, 'inactive', ['unordered_list'])
277
+ changeToolbarStateByName(core, 'inactive', ['ordered_list'])
278
+ }
279
+ },
280
+ 'line_height': {
281
+ title: 'Line Height',
282
+ icon: SVG.LINE_HEIGHT,
283
+ type: 'dropdown',
284
+ options: [
285
+ { label: "Normal", value: "normal" },
286
+ { label: "1.0", value: "1.0" },
287
+ { label: "1.5", value: "1.5" },
288
+ { label: "2.0", value: "2.0" },
289
+ { label: "2.5", value: "2.5" },
290
+ { label: "3.0", value: "3.0" },
291
+ ],
292
+ click: (event, core, options) => {
293
+ core.elements.editor.focus();
294
+ const selection = core.elements.iframeWindow.getSelection();
295
+ const range = selection.getRangeAt(0);
296
+ const selectedNode = range.commonAncestorContainer;
297
+ // Traverse up to the block element
298
+ let blockElement = selectedNode;
299
+ while (blockElement && blockElement.nodeType === Node.TEXT_NODE) {
300
+ blockElement = blockElement.parentNode;
301
+ }
302
+ if (blockElement && blockElement !== core.elements.editor) blockElement.style.lineHeight = event.target.getAttribute('data-value');
303
+
304
+ core.updateCaretPosition()
305
+ },
306
+ },
307
+ 'font_color': {
308
+ title: 'Font color',
309
+ icon: SVG.FONT_COLOR, // Assuming SVG icon for color picker
310
+ type: "color",
311
+ change: (event, core, options) => {
312
+ core.elements.editor.focus();
313
+ core.elements.iframeWindow.execCommand('foreColor', false, event.target.value); // Apply color to selected text
314
+ core.updateCaretPosition()
315
+ }
316
+ },
317
+ 'highlight_color': {
318
+ title: 'Highlight Color',
319
+ icon: SVG.HIGHLIGHT_COLOR,
320
+ type: "color",
321
+ change: (event, core, options) => {
322
+ core.elements.editor.focus();
323
+ core.elements.iframeWindow.execCommand('hiliteColor', false, event.target.value); // Apply color to selected text
324
+ core.updateCaretPosition()
325
+ }
326
+ },
327
+ 'text_style': {
328
+ title: 'Text Style',
329
+ icon: SVG.TEXT_STYLE,
330
+ type: 'dropdown',
331
+ options: [
332
+ { label: 'Block Quote', value: 'blockquote' },
333
+ { label: 'Code', value: 'code' },
334
+ { label: 'Translucent', value: 'translucent' },
335
+ { label: 'Shadow', value: 'shadow' }
336
+ ],
337
+ click: (event, core, options) => {
338
+ if (!core.state.range) return
339
+ switch (event.target.getAttribute('data-value')) {
340
+ case 'code': {
341
+ const code = document.createElement('code');
342
+ code.textContent = core.state.range.toString();
343
+ core.state.range.deleteContents();
344
+ core.state.range.insertNode(code);
345
+ break;
346
+ }
347
+ case 'translucent': {
348
+ const translucent = document.createElement('span');
349
+ translucent.textContent = core.state.range.toString();
350
+ translucent.style.backgroundColor = 'rgba(200, 151, 231, 0.3)'; // Adjust opacity as needed
351
+ core.state.range.deleteContents();
352
+ core.state.range.insertNode(translucent);
353
+ break;
354
+ }
355
+ case 'shadow': {
356
+ const shadow = document.createElement('span');
357
+ shadow.textContent = core.state.range.toString();
358
+ shadow.style.textShadow = '2px 2px 4px rgba(0, 0, 0, 0.3)'; // Adjust opacity as needed
359
+ core.state.range.deleteContents();
360
+ core.state.range.insertNode(shadow);
361
+ break;
362
+ }
363
+ case 'blockquote': {
364
+ const code = document.createElement('blockquote');
365
+ code.textContent = core.state.range.toString();
366
+ core.state.range.deleteContents();
367
+ core.state.range.insertNode(code);
368
+ break;
369
+ }
370
+ }
371
+ core.elements.editor.focus();
372
+ core.updateCaretPosition()
373
+ }
374
+ },
375
+ 'paragraph_style': {
376
+ title: 'Paragraph Style',
377
+ icon: SVG.PARAGRAPH_STYLE, // Assuming SVG icon for paragraph style
378
+ type: 'dropdown',
379
+ options: [
380
+ { label: 'Spaced', value: 'letter-spacing: 2px;' },
381
+ { label: 'Neon', value: `color: #FD7500; text-shadow: 0 0 5px #02B59F,0 0 10px #022422,0 0 15px #FF9F03,0 0 20px #02B59F,0 0 25px #017562;font-weight: bold;` },
382
+ { label: 'Bordered', value: 'border: 2px solid black; padding: 5px;' }
383
+ ],
384
+ click: (event, core, option) => {
385
+ core.elements.editor.focus();
386
+ const selection = core.elements.iframeWindow.getSelection();
387
+ const range = selection.getRangeAt(0);
388
+ const selectedNode = range.commonAncestorContainer;
389
+ // Traverse up to the block element
390
+ let blockElement = selectedNode;
391
+ while (blockElement && blockElement.nodeType === Node.TEXT_NODE) {
392
+ blockElement = blockElement.parentNode;
393
+ }
394
+ if (blockElement && blockElement !== core.elements.editor) blockElement.style.cssText = event.target.getAttribute('data-value');
395
+
396
+ core.updateCaretPosition()
397
+ }
398
+ },
399
+ 'outdent': {
400
+ title: 'Outdent',
401
+ icon: SVG.OUTDENT,
402
+ type: 'button',
403
+ click: (event, core, options) => {
404
+ if (!core.state.range) return
405
+
406
+ const selectedNode = core.state.range.commonAncestorContainer;
407
+
408
+ // Traverse up to the block element
409
+ let blockElement = selectedNode;
410
+ while (blockElement && blockElement.nodeType === Node.TEXT_NODE) {
411
+ blockElement = blockElement.parentNode;
412
+ }
413
+
414
+ if (blockElement) {
415
+ // Decrease the margin-left (outdent)
416
+ const currentIndent = parseInt(blockElement.style.marginLeft || '0', 10);
417
+ blockElement.style.marginLeft = Math.max(0, currentIndent - 40) + 'px';
418
+ }
419
+ core.updateCaretPosition()
420
+ }
421
+ },
422
+ 'indent': {
423
+ title: 'Indent',
424
+ icon: SVG.INDENT,
425
+ type: 'button',
426
+ click: (event, core, options) => {
427
+ if (!core.state.range) return
428
+
429
+ const selectedNode = core.state.range.commonAncestorContainer;
430
+
431
+ // Traverse up to the block element
432
+ let blockElement = selectedNode;
433
+ while (blockElement && blockElement.nodeType === Node.TEXT_NODE) {
434
+ blockElement = blockElement.parentNode;
435
+ }
436
+
437
+ if (blockElement) {
438
+ // Increase the margin-left (indent)
439
+ const currentIndent = parseInt(blockElement.style.marginLeft || '0', 10);
440
+ blockElement.style.marginLeft = (currentIndent + 40) + 'px';
441
+ }
442
+ core.updateCaretPosition()
443
+ }
444
+ },
445
+ 'link': {
446
+ title: 'Link',
447
+ icon: SVG.LINK,
448
+ type: 'button',
449
+ click: (event, core, options) => {
450
+ const onInputKeydown = (event) => {
451
+ if (event.key === 'Enter') {
452
+ event.preventDefault();
453
+ button.click();
454
+ }
455
+ }
456
+
457
+
458
+ /* --------------- If image or text is already have link then show that link and text in respective input fields ----------------- */
459
+ let anchorLink = ''
460
+ let anchorText = '';
461
+ let underAnchor = false;
462
+ if (core.elements.selectedElement && core.elements.selectedElement.tagName === 'IMG') {
463
+ const imgElement = core.elements.selectedElement;
464
+ if (imgElement) {
465
+ const imgParentNode = imgElement.parentNode
466
+ if (imgParentNode.nodeName === 'A') {
467
+ anchorLink = imgParentNode.href
468
+ anchorText = imgElement.alt || ''
469
+ underAnchor = true
470
+ }
471
+ }
472
+ } else if (core.state.range) {
473
+ const selection = core.state.selection;
474
+ if (selection.rangeCount > 0) {
475
+ const range = core.state.range;
476
+ let parentElement = range.commonAncestorContainer;
477
+
478
+ // If the commonAncestorContainer is a text node, use its parent element
479
+ if (parentElement.nodeType === Node.TEXT_NODE) {
480
+ parentElement = parentElement.parentNode;
481
+ }
482
+
483
+ if (parentElement.nodeName === 'A') {
484
+ anchorLink = parentElement.getAttribute('href')
485
+ anchorText = parentElement.innerText || ''
486
+ underAnchor = true
487
+ }
488
+ }
489
+ }
490
+
491
+ const body = document.createElement('div')
492
+ const inputLink = document.createElement('input');
493
+ inputLink.value = anchorLink
494
+ inputLink.type = 'text'
495
+ inputLink.placeholder = 'Insert the link'
496
+ inputLink.addEventListener('keydown', onInputKeydown);
497
+
498
+ const inputText = document.createElement('input');
499
+ inputText.value = anchorText
500
+ inputText.type = 'text'
501
+ inputText.style.marginTop = '8px'
502
+ inputText.placeholder = 'Link text'
503
+ inputText.addEventListener('keydown', onInputKeydown);
504
+
505
+ const span = document.createElement('span');
506
+ span.className = 'warning'
507
+ body.append(inputLink, inputText, span);
508
+
509
+ const footer = document.createElement('div')
510
+ const button = document.createElement('button')
511
+ button.type = 'button';
512
+ button.className = 'submit';
513
+ button.innerText = 'Add';
514
+ footer.append(button)
515
+
516
+ const modal = openModal({
517
+ title: "Link",
518
+ bodyNode: body,
519
+ footerNode: footer,
520
+
521
+ }, core, options)
522
+ inputLink.focus()
523
+
524
+ button.onclick = () => {
525
+ const link = inputLink.value
526
+ const value = inputText.value
527
+ if (REGEX.URL.test(link)) {
528
+ if (underAnchor) {
529
+ if (core.elements.selectedElement && core.elements.selectedElement.tagName === 'IMG') {
530
+ const imgElement = core.elements.selectedElement;
531
+ if (imgElement) {
532
+ const imgParentNode = imgElement.parentNode
533
+ if (imgParentNode.nodeName === 'A') {
534
+ imgParentNode.href = link
535
+ imgElement.alt = value
536
+ }
537
+ }
538
+ } else if (core.state.range) {
539
+ const selection = core.state.selection;
540
+ if (selection.rangeCount > 0) {
541
+ const range = core.state.range;
542
+ let parentElement = range.commonAncestorContainer;
543
+
544
+ // If the commonAncestorContainer is a text node, use its parent element
545
+ if (parentElement.nodeType === Node.TEXT_NODE) {
546
+ parentElement = parentElement.parentNode;
547
+ }
548
+
549
+ if (parentElement.nodeName === 'A') {
550
+ parentElement.setAttribute('href', link)
551
+ parentElement.innerText = value
552
+ }
553
+ }
554
+ }
555
+ } else if (core.elements.selectedElement && core.elements.selectedElement.tagName === 'IMG') {
556
+ const a = document.createElement('a');
557
+ a.href = link
558
+ a.target = '_blank'
559
+ core.elements.selectedElement.parentNode.insertBefore(a, core.elements.selectedElement);
560
+ a.appendChild(core.elements.selectedElement);
561
+ core.elements.selectedElement.alt = value
562
+ } else {
563
+ const a = document.createElement('a');
564
+ a.href = link
565
+ a.target = '_blank'
566
+ a.textContent = value || link
567
+ core.insertNode(a);
568
+ }
569
+ modal.close()
570
+ } else {
571
+ span.innerText = 'Invalid url'
572
+ }
573
+ };
574
+ core.updateCaretPosition()
575
+ }
576
+ },
577
+ 'unlink': {
578
+ title: 'Unlink',
579
+ icon: SVG.UNLINK,
580
+ type: 'button',
581
+ click: (event, core, options) => {
582
+ core.elements.iframeWindow.execCommand('unlink');
583
+ core.elements.editor.focus();
584
+ core.updateCaretPosition()
585
+ }
586
+ },
587
+ 'horizontal_rule': {
588
+ title: 'Horizontal Rule',
589
+ icon: SVG.HORIZONTAL_RULE,
590
+ type: 'button',
591
+ click: (event, core, options) => {
592
+ core.elements.iframeWindow.execCommand('insertHorizontalRule');
593
+ core.elements.editor.focus();
594
+ core.updateCaretPosition()
595
+ }
596
+ },
597
+ 'table': {
598
+ title: 'Table',
599
+ icon: SVG.TABLE,
600
+ type: 'table',
601
+ create: (core, options, { rows, cols }) => {
602
+ const table = document.createElement('table');
603
+ table.style.borderCollapse = 'collapse';
604
+ table.style.width = core.elements.editor.offsetWidth - 50 + 'px';
605
+
606
+ for (let i = 0; i <= rows; i++) {
607
+ const tr = document.createElement('tr');
608
+ for (let j = 0; j <= cols; j++) {
609
+ const td = document.createElement('td');
610
+ td.style.border = '1px solid black';
611
+ td.appendChild(document.createTextNode('\u00A0')); // Non-breaking space
612
+ tr.appendChild(td);
613
+ }
614
+ table.appendChild(tr);
615
+ }
616
+
617
+ core.insertNode(table);
618
+ core.elements.editor.focus();
619
+ core.updateCaretPosition()
620
+ }
621
+ },
622
+ 'mention': {
623
+ title: 'Mention',
624
+ icon: SVG.MENTION,
625
+ type: 'mention',
626
+ },
627
+ 'emoji': {
628
+ title: 'Emoji',
629
+ icon: SVG.EMOJI,
630
+ type: 'category',
631
+ categories: EMOJI_CATEGORIES
632
+ },
633
+ 'special_character': {
634
+ title: 'Special Character',
635
+ icon: SVG.SPECIAL_CHARACTER,
636
+ type: 'category',
637
+ categories: SPECIAL_CHARACTERS
638
+ },
639
+ 'pexels': {
640
+ title: 'Pexels',
641
+ icon: SVG.PEXELS,
642
+ type: 'gallery',
643
+ suggestions: async (options, page) => pexels.suggestions(options, page),
644
+ search: async (options, search, page) => pexels.search(options, search, page),
645
+ },
646
+ 'giphy': {
647
+ title: 'GIPHY',
648
+ icon: SVG.GIPHY_SVG,
649
+ type: 'gallery',
650
+ suggestions: async (options, page) => giphy.suggestions(options, page),
651
+ search: async (options, search, page) => giphy.search(options, search, page),
652
+ },
653
+ 'tenor': {
654
+ title: 'Tenor',
655
+ icon: SVG.TENOR,
656
+ type: 'gallery',
657
+ suggestions: async (options, next) => tenor.suggestions(options, next),
658
+ search: async (options, search) => tenor.search(options, search),
659
+ },
660
+ 'image': {
661
+ title: "Image",
662
+ icon: SVG.IMAGE,
663
+ type: 'dropdown',
664
+ options: [
665
+ { label: 'Upload from device', value: 'upload' },
666
+ { label: 'Using Link', value: 'link' },
667
+ ],
668
+ click: (event, core, options) => {
669
+ if (event.target.getAttribute('data-value') === 'upload') {
670
+
671
+ const onInputKeydown = (event) => {
672
+ if (event.key === 'Enter') {
673
+ event.preventDefault();
674
+ button.click();
675
+ }
676
+ };
677
+
678
+ const body = document.createElement('div');
679
+
680
+ const fileInput = document.createElement('input');
681
+ fileInput.type = 'file';
682
+ fileInput.accept = MIMETYPE.IMAGE;
683
+
684
+ const altInput = document.createElement('input');
685
+ altInput.type = 'text';
686
+ altInput.placeholder = 'Alternative Text';
687
+ altInput.style.marginTop = '10px';
688
+ altInput.addEventListener('keydown', onInputKeydown);
689
+
690
+ const span = document.createElement('span');
691
+ span.className = 'warning';
692
+
693
+ body.append(fileInput, altInput, span);
694
+
695
+ const footer = document.createElement('div');
696
+ const button = document.createElement('button');
697
+ button.type = 'button';
698
+ button.className = 'submit';
699
+ button.innerText = 'Add';
700
+ footer.append(button);
701
+
702
+ const modal = openModal(
703
+ {
704
+ title: "Upload Image",
705
+ bodyNode: body,
706
+ footerNode: footer,
707
+ },
708
+ core,
709
+ options
710
+ );
711
+ let base64String = ""
712
+
713
+ fileInput.addEventListener('change', (event) => {
714
+ const file = event.target.files[0];
715
+
716
+ if (file && MIMETYPE.IMAGE.includes(file.type)) {
717
+ const reader = new FileReader();
718
+
719
+ reader.onload = async function (e) {
720
+ base64String = e.target.result;
721
+ };
722
+
723
+ reader.readAsDataURL(file);
724
+ span.innerText = ""; // Clear warning if file is selected
725
+ }
726
+ });
727
+ button.onclick = async () => {
728
+ if (!base64String) {
729
+ span.innerText = 'Image not selected';
730
+ return;
731
+ }
732
+
733
+ const img = document.createElement('img');
734
+ img.src = await core.manuplateImage('base64', base64String);
735
+ img.alt = altInput.value || "";
736
+
737
+ core.insertNode(img);
738
+ modal.close();
739
+ };
740
+ }
741
+ else if (event.target.getAttribute('data-value') === 'link') {
742
+ const onInputKeydown = (event) => {
743
+ if (event.key === 'Enter') {
744
+ event.preventDefault();
745
+ button.click();
746
+ }
747
+ }
748
+
749
+ const body = document.createElement('div')
750
+ const input = document.createElement('input');
751
+ input.value = ''
752
+ input.type = 'text'
753
+ input.placeholder = 'Insert the link'
754
+ input.addEventListener('keydown', onInputKeydown);
755
+
756
+ const altInput = document.createElement('input');
757
+ altInput.value = ''
758
+ altInput.style.marginTop = '10px'
759
+ altInput.type = 'text'
760
+ altInput.placeholder = 'Alternative Text'
761
+ altInput.addEventListener('keydown', onInputKeydown);
762
+
763
+ const span = document.createElement('span');
764
+ span.className = 'warning'
765
+ body.append(input, altInput, span);
766
+
767
+ const footer = document.createElement('div')
768
+ const button = document.createElement('button')
769
+ button.type = 'button';
770
+ button.className = 'submit';
771
+ button.innerText = 'Add';
772
+ footer.append(button)
773
+
774
+ const modal = openModal({
775
+ title: "Image",
776
+ bodyNode: body,
777
+ footerNode: footer,
778
+
779
+ }, core, options)
780
+
781
+ button.onclick = () => {
782
+ const value = input.value
783
+ if (REGEX.URL.test(value)) {
784
+ button.disabled = true;
785
+ const img = document.createElement('img');
786
+ img.src = value
787
+ img.alt = altInput.value
788
+ core.insertNode(img);
789
+ modal.close()
790
+ } else {
791
+ span.innerText = 'Invalid url'
792
+ }
793
+ };
794
+ }
795
+ core.elements.editor.focus();
796
+ core.updateCaretPosition()
797
+ }
798
+ },
799
+ 'video': {
800
+ title: "Video",
801
+ icon: SVG.VIDEO,
802
+ type: 'button',
803
+ click: (event, core) => {
804
+ const fileInput = document.createElement('input');
805
+ fileInput.type = 'file';
806
+ fileInput.accept = MIMETYPE.VIDEO
807
+ fileInput.style.display = 'none';
808
+ fileInput.addEventListener('change', async (event) => {
809
+ const file = event.target.files[0]
810
+ if (MIMETYPE.VIDEO.includes(file.type)) {
811
+ const url = await core.uploadVideo(file);
812
+ if (url) {
813
+ const a = document.createElement('a');
814
+ a.href = url.videoUrl
815
+ a.target = '_blank'
816
+ const img = document.createElement('img');
817
+ img.src = url.thumbnailUrl
818
+ a.appendChild(img)
819
+ core.insertNode(a);
820
+ }
821
+ }
822
+ });
823
+ fileInput.click();
824
+ core.updateCaretPosition()
825
+ }
826
+ },
827
+ 'attachment': {
828
+ title: 'Attachment',
829
+ icon: SVG.ATTACHMENT,
830
+ type: 'button',
831
+ click: (event, core) => {
832
+ const fileInput = document.createElement('input');
833
+ fileInput.type = 'file';
834
+ fileInput.style.display = 'none';
835
+ fileInput.multiple = true;
836
+ fileInput.addEventListener('change', (event) => {
837
+ core.onAttachment(event.target.files);
838
+ });
839
+ fileInput.click();
840
+ core.updateCaretPosition()
841
+ }
842
+ },
843
+ 'cut': {
844
+ title: 'Cut',
845
+ icon: SVG.CUT,
846
+ type: 'button',
847
+ click: (event, core) => {
848
+ if (!core.state.range) return
849
+ core.elements.editor.focus();
850
+
851
+ const container = document.createElement('div');
852
+ container.appendChild(core.state.range.cloneContents());
853
+ const htmlContent = container.innerHTML;
854
+
855
+ const clipboardItem = new ClipboardItem({
856
+ 'text/html': new Blob([htmlContent], { type: 'text/html' })
857
+ });
858
+ navigator.clipboard.write([clipboardItem]).then(() => {
859
+ core.state.range.deleteContents();
860
+ core.updateCaretPosition();
861
+ })
862
+ }
863
+ },
864
+ 'copy': {
865
+ title: 'Copy',
866
+ icon: SVG.COPY,
867
+ type: 'button',
868
+ click: (event, core) => {
869
+ if (!core.state.range) return
870
+ core.elements.editor.focus();
871
+
872
+ const container = document.createElement('div');
873
+ container.appendChild(core.state.range.cloneContents());
874
+ const htmlContent = container.innerHTML;
875
+
876
+ const clipboardItem = new ClipboardItem({
877
+ 'text/html': new Blob([htmlContent], { type: 'text/html' })
878
+ });
879
+ navigator.clipboard.write([clipboardItem]).then(() => {
880
+ core.updateCaretPosition();
881
+ })
882
+ }
883
+ },
884
+ 'paste': {
885
+ title: 'Paste',
886
+ icon: SVG.PASTE,
887
+ type: 'button',
888
+ click: async (event, core) => {
889
+ core.elements.editor.focus();
890
+
891
+ const clipboardItems = await navigator.clipboard.read();
892
+ let pastedContentNode;
893
+
894
+ for (const item of clipboardItems) {
895
+ if (item.types.includes('text/html')) {
896
+ const blob = await item.getType('text/html');
897
+ const container = document.createElement('div');
898
+ if (blob) {
899
+ container.innerHTML = await blob.text();
900
+ pastedContentNode = container;
901
+ }
902
+ break;
903
+ } else if (item.types.includes('text/plain')) {
904
+ const blob = await item.getType('text/plain');
905
+ const plainTextContent = await blob.text();
906
+ pastedContentNode = document.createTextNode(plainTextContent);
907
+ break;
908
+ }
909
+ else if (item.types.find(type => type.startsWith('image/'))) {
910
+ const blob = await item.getType(item.types.find(type => type.startsWith('image/')));
911
+ const reader = new FileReader();
912
+
913
+ reader.onload = async function (event) {
914
+ const img = document.createElement('img');
915
+ const base64String = event.target.result;
916
+ img.src = await core.manuplateImage('base64', base64String);
917
+ pastedContentNode = img;
918
+ };
919
+
920
+ reader.readAsDataURL(blob);
921
+ break;
922
+ }
923
+ }
924
+ if (pastedContentNode) {
925
+ core.state.range.deleteContents();
926
+ core.insertNode(pastedContentNode)
927
+ }
928
+ core.updateCaretPosition();
929
+ }
930
+ },
931
+ 'select_all': {
932
+ title: 'Select All',
933
+ icon: SVG.SELECT_ALL,
934
+ type: 'button',
935
+ click: (event, core) => {
936
+ core.elements.editor.focus();
937
+ core.elements.iframeWindow.execCommand('selectAll');
938
+ core.updateCaretPosition()
939
+ }
940
+ },
941
+ 'fullscreen': {
942
+ title: 'Full screen',
943
+ icon: SVG.FULLSCREEN,
944
+ type: 'button',
945
+ click: (event, core) => {
946
+ const isExists = core.elements.base.classList.toggle('fullscreen')
947
+ changeToolbarHtmlByName(core, 'fullscreen', isExists ? SVG.FULLSCREEN_EXIT : SVG.FULLSCREEN)
948
+ }
949
+ },
950
+ 'print': {
951
+ title: 'Print',
952
+ icon: SVG.PRINT,
953
+ type: 'button',
954
+ click: (event, core) => {
955
+ core.elements.iframeContainer.contentWindow.print()
956
+ }
957
+ },
958
+ 'embed': {
959
+ title: 'Embed Link',
960
+ icon: SVG.EMBED,
961
+ type: 'button',
962
+ click: (event, core, options) => {
963
+ const onInputKeydown = (event) => {
964
+ if (event.key === 'Enter') {
965
+ event.preventDefault();
966
+ button.click();
967
+ }
968
+ };
969
+
970
+ const body = document.createElement('div');
971
+ body.className = "link"
972
+
973
+ // Input for Embed URL
974
+ const urlInput = document.createElement('input');
975
+ urlInput.type = 'text';
976
+ urlInput.placeholder = 'Enter Embed URL';
977
+ urlInput.style.marginBottom = '10px';
978
+ urlInput.addEventListener('keydown', onInputKeydown);
979
+
980
+ // Input for Width (%)
981
+ const widthInput = document.createElement('input');
982
+ widthInput.type = 'text';
983
+ widthInput.placeholder = 'Width (px)';
984
+ widthInput.style.marginBottom = '10px';
985
+ widthInput.value = "400"; // Default to 400px
986
+
987
+ // Input for Height (%)
988
+ const heightInput = document.createElement('input');
989
+ heightInput.type = 'text';
990
+ heightInput.placeholder = 'Height (px)';
991
+ heightInput.style.marginBottom = '10px';
992
+ heightInput.value = "400"; // Default to 400px
993
+
994
+ // Warning span
995
+ const span = document.createElement('span');
996
+ span.className = 'warning';
997
+
998
+ body.append(urlInput, widthInput, heightInput, span);
999
+
1000
+ const footer = document.createElement('div');
1001
+ const button = document.createElement('button');
1002
+ button.type = 'button';
1003
+ button.className = 'submit';
1004
+ button.innerText = 'Add';
1005
+ footer.append(button);
1006
+
1007
+ const modal = openModal(
1008
+ {
1009
+ title: "Embed URL",
1010
+ bodyNode: body,
1011
+ footerNode: footer,
1012
+ },
1013
+ core,
1014
+ options
1015
+ );
1016
+
1017
+ button.onclick = () => {
1018
+ const enteredUrl = urlInput.value.trim();
1019
+
1020
+ const { platform, id } = extractSocialMediaId(enteredUrl);
1021
+ if (!platform || !id) {
1022
+ span.innerText = 'Invalid URL';
1023
+ return;
1024
+ }
1025
+
1026
+ const url = constructEmbedUrl(platform, id);
1027
+ if (!url) {
1028
+ span.innerText = 'Unsupported platform';
1029
+ return;
1030
+ }
1031
+ const wrapper = document.createElement('figure');
1032
+ wrapper.className = 'iframe-wrapper';
1033
+ wrapper.style.height = `${heightInput.value}px`
1034
+ wrapper.style.width = `${widthInput.value}px`
1035
+ const embed = document.createElement('iframe');
1036
+ embed.src = url;
1037
+ embed.style.display = "inline-block"
1038
+ embed.style.height = "100%"
1039
+ embed.style.width = "100%"
1040
+ embed.style.border = "none";
1041
+ wrapper.append(embed)
1042
+ core.insertNode(wrapper);
1043
+ modal.close();
1044
+ };
1045
+ core.elements.editor.focus();
1046
+ core.updateCaretPosition()
1047
+ }
1048
+ }
1049
+ }
1050
+
1051
+ export default PLUGINS
1052
+ export {
1053
+ applyTextFormat,
1054
+ applyOrderList,
1055
+ }