tulih-editor 0.1.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 +331 -0
- package/dist/tulih-editor.css +1 -0
- package/dist/tulih-editor.es.js +3051 -0
- package/dist/tulih-editor.es.js.map +1 -0
- package/dist/tulih-editor.umd.js +8 -0
- package/dist/tulih-editor.umd.js.map +1 -0
- package/dist/types/core/Editor.d.ts +20 -0
- package/dist/types/core/PluginManager.d.ts +22 -0
- package/dist/types/core/helpers.d.ts +22 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/plugins/align.d.ts +3 -0
- package/dist/types/plugins/autoLinkify.d.ts +3 -0
- package/dist/types/plugins/autosave.d.ts +3 -0
- package/dist/types/plugins/block.d.ts +3 -0
- package/dist/types/plugins/caseTransform.d.ts +3 -0
- package/dist/types/plugins/codeBlock.d.ts +3 -0
- package/dist/types/plugins/colors.d.ts +3 -0
- package/dist/types/plugins/darkMode.d.ts +3 -0
- package/dist/types/plugins/direction.d.ts +3 -0
- package/dist/types/plugins/dragDrop.d.ts +3 -0
- package/dist/types/plugins/emoji.d.ts +3 -0
- package/dist/types/plugins/emojiAutocomplete.d.ts +3 -0
- package/dist/types/plugins/findReplace.d.ts +3 -0
- package/dist/types/plugins/floatingToolbar.d.ts +3 -0
- package/dist/types/plugins/fontFamily.d.ts +3 -0
- package/dist/types/plugins/fontSize.d.ts +3 -0
- package/dist/types/plugins/fullscreen.d.ts +3 -0
- package/dist/types/plugins/history.d.ts +3 -0
- package/dist/types/plugins/hr.d.ts +3 -0
- package/dist/types/plugins/iframe.d.ts +3 -0
- package/dist/types/plugins/image.d.ts +3 -0
- package/dist/types/plugins/imageProps.d.ts +3 -0
- package/dist/types/plugins/imageTools.d.ts +3 -0
- package/dist/types/plugins/indent.d.ts +3 -0
- package/dist/types/plugins/index.d.ts +2 -0
- package/dist/types/plugins/inline.d.ts +3 -0
- package/dist/types/plugins/inlineCode.d.ts +3 -0
- package/dist/types/plugins/keyboardShortcuts.d.ts +3 -0
- package/dist/types/plugins/lineHeight.d.ts +3 -0
- package/dist/types/plugins/link.d.ts +3 -0
- package/dist/types/plugins/linkTooltip.d.ts +3 -0
- package/dist/types/plugins/list.d.ts +3 -0
- package/dist/types/plugins/markdown.d.ts +3 -0
- package/dist/types/plugins/mediaEmbed.d.ts +3 -0
- package/dist/types/plugins/pasteImage.d.ts +3 -0
- package/dist/types/plugins/pastePlain.d.ts +3 -0
- package/dist/types/plugins/pre.d.ts +3 -0
- package/dist/types/plugins/readOnly.d.ts +3 -0
- package/dist/types/plugins/shortcutCustomizer.d.ts +3 -0
- package/dist/types/plugins/shortcutsHelp.d.ts +3 -0
- package/dist/types/plugins/source.d.ts +3 -0
- package/dist/types/plugins/specialChars.d.ts +3 -0
- package/dist/types/plugins/statusBar.d.ts +3 -0
- package/dist/types/plugins/subSuper.d.ts +3 -0
- package/dist/types/plugins/table.d.ts +3 -0
- package/dist/types/plugins/tableBg.d.ts +3 -0
- package/dist/types/plugins/tableTools.d.ts +3 -0
- package/dist/types/plugins/toolbarCollapse.d.ts +3 -0
- package/dist/types/plugins/unlink.d.ts +3 -0
- package/dist/types/plugins/wordCount.d.ts +3 -0
- package/dist/types/types.d.ts +226 -0
- package/package.json +66 -0
- package/src/core/Editor.ts +460 -0
- package/src/core/PluginManager.ts +140 -0
- package/src/core/helpers.ts +209 -0
- package/src/css.d.ts +2 -0
- package/src/index.ts +87 -0
- package/src/plugins/align.ts +72 -0
- package/src/plugins/autoLinkify.ts +34 -0
- package/src/plugins/autosave.ts +69 -0
- package/src/plugins/block.ts +32 -0
- package/src/plugins/caseTransform.ts +54 -0
- package/src/plugins/codeBlock.ts +93 -0
- package/src/plugins/colors.ts +68 -0
- package/src/plugins/darkMode.ts +123 -0
- package/src/plugins/direction.ts +30 -0
- package/src/plugins/dragDrop.ts +68 -0
- package/src/plugins/emoji.ts +188 -0
- package/src/plugins/emojiAutocomplete.ts +183 -0
- package/src/plugins/findReplace.ts +229 -0
- package/src/plugins/floatingToolbar.ts +258 -0
- package/src/plugins/fontFamily.ts +41 -0
- package/src/plugins/fontSize.ts +32 -0
- package/src/plugins/fullscreen.ts +36 -0
- package/src/plugins/history.ts +14 -0
- package/src/plugins/hr.ts +118 -0
- package/src/plugins/iframe.ts +88 -0
- package/src/plugins/image.ts +107 -0
- package/src/plugins/imageProps.ts +119 -0
- package/src/plugins/imageTools.ts +344 -0
- package/src/plugins/indent.ts +29 -0
- package/src/plugins/index.ts +101 -0
- package/src/plugins/inline.ts +17 -0
- package/src/plugins/inlineCode.ts +21 -0
- package/src/plugins/keyboardShortcuts.ts +92 -0
- package/src/plugins/lineHeight.ts +40 -0
- package/src/plugins/link.ts +344 -0
- package/src/plugins/linkTooltip.ts +63 -0
- package/src/plugins/list.ts +141 -0
- package/src/plugins/markdown.ts +61 -0
- package/src/plugins/mediaEmbed.ts +44 -0
- package/src/plugins/pasteImage.ts +61 -0
- package/src/plugins/pastePlain.ts +43 -0
- package/src/plugins/pre.ts +11 -0
- package/src/plugins/readOnly.ts +46 -0
- package/src/plugins/shortcutCustomizer.ts +125 -0
- package/src/plugins/shortcutsHelp.ts +51 -0
- package/src/plugins/source.ts +77 -0
- package/src/plugins/specialChars.ts +64 -0
- package/src/plugins/statusBar.ts +85 -0
- package/src/plugins/subSuper.ts +20 -0
- package/src/plugins/table.ts +166 -0
- package/src/plugins/tableBg.ts +11 -0
- package/src/plugins/tableTools.ts +475 -0
- package/src/plugins/toolbarCollapse.ts +14 -0
- package/src/plugins/unlink.ts +29 -0
- package/src/plugins/wordCount.ts +34 -0
- package/src/styles/base.css +258 -0
- package/src/styles/editor.css +309 -0
- package/src/styles/index.css +6 -0
- package/src/types.ts +278 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const colors: Plugin = {
|
|
4
|
+
name: 'colors',
|
|
5
|
+
order: 30,
|
|
6
|
+
css: '' +
|
|
7
|
+
'.te-color-btn {' +
|
|
8
|
+
' position: relative; padding: .15rem .35rem !important;' +
|
|
9
|
+
' min-width: 28px; text-align: center;' +
|
|
10
|
+
'}' +
|
|
11
|
+
'.te-color-preview {' +
|
|
12
|
+
' display: block; font-size: 1rem; line-height: 1;' +
|
|
13
|
+
'}' +
|
|
14
|
+
'.te-color-preview::after {' +
|
|
15
|
+
' content: ""; display: block;' +
|
|
16
|
+
' height: 3px; border-radius: 1px;' +
|
|
17
|
+
' margin-top: 2px;' +
|
|
18
|
+
'}' +
|
|
19
|
+
'.te-fore-btn .te-color-preview::after {' +
|
|
20
|
+
' background: currentColor;' +
|
|
21
|
+
'}' +
|
|
22
|
+
'.te-back-btn .te-color-preview::after {' +
|
|
23
|
+
' background: var(--te-back-color, #ffff00);' +
|
|
24
|
+
'}' +
|
|
25
|
+
'.te-color-input {' +
|
|
26
|
+
' position: absolute; top: 0; left: 0;' +
|
|
27
|
+
' width: 100%; height: 100%;' +
|
|
28
|
+
' opacity: 0; cursor: pointer;' +
|
|
29
|
+
' border: none; padding: 0;' +
|
|
30
|
+
'}',
|
|
31
|
+
toolbarHTML: '' +
|
|
32
|
+
'<button type="button" class="btn btn-sm btn-light te-color-btn te-fore-btn" title="Text color">' +
|
|
33
|
+
'<span class="te-color-preview" style="color:#000000">A</span>' +
|
|
34
|
+
'<input type="color" class="te-color-input te-fore-color" value="#000000">' +
|
|
35
|
+
'</button>' +
|
|
36
|
+
'<button type="button" class="btn btn-sm btn-light te-color-btn te-back-btn" title="Background color">' +
|
|
37
|
+
'<span class="te-color-preview" style="--te-back-color:#ffff00;color:#000">A</span>' +
|
|
38
|
+
'<input type="color" class="te-color-input te-back-color" value="#ffff00">' +
|
|
39
|
+
'</button>',
|
|
40
|
+
init(ctx: PluginContext): void {
|
|
41
|
+
if(!ctx.features.colors) return;
|
|
42
|
+
|
|
43
|
+
var foreInput = ctx.wrapper.querySelector('.te-fore-color') as HTMLInputElement | null;
|
|
44
|
+
var foreBtn = ctx.wrapper.querySelector('.te-fore-btn') as HTMLElement | null;
|
|
45
|
+
if(foreBtn && foreInput){
|
|
46
|
+
foreInput.addEventListener('change', function(){
|
|
47
|
+
var preview = foreBtn!.querySelector('.te-color-preview') as HTMLElement;
|
|
48
|
+
preview.style.color = foreInput!.value;
|
|
49
|
+
document.execCommand('foreColor', false, foreInput!.value);
|
|
50
|
+
ctx.editor.focus();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
var backInput = ctx.wrapper.querySelector('.te-back-color') as HTMLInputElement | null;
|
|
55
|
+
var backBtn = ctx.wrapper.querySelector('.te-back-btn') as HTMLElement | null;
|
|
56
|
+
if(backBtn && backInput){
|
|
57
|
+
backInput.addEventListener('change', function(){
|
|
58
|
+
var preview = backBtn!.querySelector('.te-color-preview') as HTMLElement;
|
|
59
|
+
preview.style.setProperty('--te-back-color', backInput!.value);
|
|
60
|
+
try{ document.execCommand('hiliteColor', false, backInput!.value); }
|
|
61
|
+
catch(_){ document.execCommand('backColor', false, backInput!.value); }
|
|
62
|
+
ctx.editor.focus();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default colors;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const darkMode: Plugin = {
|
|
4
|
+
name: 'darkMode',
|
|
5
|
+
order: 59,
|
|
6
|
+
toolbarHTML: '<button type="button" class="btn btn-sm btn-light" title="Toggle dark mode" data-cmd="toggleDark"><i class="ti ti-moon"></i></button>',
|
|
7
|
+
css: '' +
|
|
8
|
+
'.te-container.te-dark {' +
|
|
9
|
+
' color-scheme: dark;' +
|
|
10
|
+
'}' +
|
|
11
|
+
'.te-container.te-dark .te-toolbar {' +
|
|
12
|
+
' background: #1e1e1e; border-color: #444;' +
|
|
13
|
+
'}' +
|
|
14
|
+
'.te-container.te-dark .te-toolbar .btn {' +
|
|
15
|
+
' color: #ccc; border-color: #555; background: #2a2a2a;' +
|
|
16
|
+
'}' +
|
|
17
|
+
'.te-container.te-dark .te-toolbar .btn:hover {' +
|
|
18
|
+
' background: #333; color: #fff;' +
|
|
19
|
+
'}' +
|
|
20
|
+
'.te-container.te-dark .te-toolbar .btn.active {' +
|
|
21
|
+
' background: #0d6efd; color: #fff; border-color: #0d6efd;' +
|
|
22
|
+
'}' +
|
|
23
|
+
'.te-container.te-dark .te-editor {' +
|
|
24
|
+
' background: #121212; border-color: #444; color: #e0e0e0;' +
|
|
25
|
+
'}' +
|
|
26
|
+
'.te-container.te-dark .te-editor:focus {' +
|
|
27
|
+
' box-shadow: inset 0 0 0 2px rgba(13,110,253,.5);' +
|
|
28
|
+
'}' +
|
|
29
|
+
'.te-container.te-dark .te-editor[placeholder]:empty:before {' +
|
|
30
|
+
' color: #666;' +
|
|
31
|
+
'}' +
|
|
32
|
+
'.te-container.te-dark .te-divider {' +
|
|
33
|
+
' background: #444;' +
|
|
34
|
+
'}' +
|
|
35
|
+
'.te-container.te-dark .te-status {' +
|
|
36
|
+
' color: #999;' +
|
|
37
|
+
'}' +
|
|
38
|
+
'.te-container.te-dark .te-modal-dialog {' +
|
|
39
|
+
' background: #1e1e1e; color: #e0e0e0; box-shadow: 0 20px 60px rgba(0,0,0,.5);' +
|
|
40
|
+
'}' +
|
|
41
|
+
'.te-container.te-dark .te-modal-header,' +
|
|
42
|
+
'.te-container.te-dark .te-modal-footer {' +
|
|
43
|
+
' background: #2a2a2a; border-color: #444;' +
|
|
44
|
+
'}' +
|
|
45
|
+
'.te-container.te-dark .te-modal-header .te-modal-title {' +
|
|
46
|
+
' color: #e0e0e0;' +
|
|
47
|
+
'}' +
|
|
48
|
+
'.te-container.te-dark .te-modal-body {' +
|
|
49
|
+
' color: #e0e0e0;' +
|
|
50
|
+
'}' +
|
|
51
|
+
'.te-container.te-dark .btn-close {' +
|
|
52
|
+
' filter: invert(1);' +
|
|
53
|
+
'}' +
|
|
54
|
+
'.te-container.te-dark .te-modal .btn-primary {' +
|
|
55
|
+
' background: #555; border-color: #555; color: #fff;' +
|
|
56
|
+
'}' +
|
|
57
|
+
'.te-container.te-dark .te-modal .btn-primary:hover {' +
|
|
58
|
+
' background: #777; border-color: #777;' +
|
|
59
|
+
'}' +
|
|
60
|
+
'.te-container.te-dark .te-modal .btn-outline-secondary {' +
|
|
61
|
+
' color: #ccc; border-color: #555;' +
|
|
62
|
+
'}' +
|
|
63
|
+
'.te-container.te-dark .te-modal .btn-outline-secondary:hover {' +
|
|
64
|
+
' background: #333; border-color: #777;' +
|
|
65
|
+
'}' +
|
|
66
|
+
'.te-container.te-dark .te-modal .btn-outline-danger {' +
|
|
67
|
+
' color: #e57373; border-color: #e57373;' +
|
|
68
|
+
'}' +
|
|
69
|
+
'.te-container.te-dark .te-modal .btn-outline-danger:hover {' +
|
|
70
|
+
' background: #C1272D; border-color: #C1272D; color: #fff;' +
|
|
71
|
+
'}' +
|
|
72
|
+
'.te-container.te-dark .form-control {' +
|
|
73
|
+
' background: #2a2a2a; border-color: #555; color: #e0e0e0;' +
|
|
74
|
+
'}' +
|
|
75
|
+
'.te-container.te-dark .form-select {' +
|
|
76
|
+
' background: #2a2a2a; border-color: #555; color: #e0e0e0;' +
|
|
77
|
+
'}' +
|
|
78
|
+
'.te-container.te-dark .te-table-tools {' +
|
|
79
|
+
' background: #1e1e1e; border-color: #444;' +
|
|
80
|
+
'}' +
|
|
81
|
+
'.te-container.te-dark .te-table-tools .btn {' +
|
|
82
|
+
' color: #ccc; border-color: #555; background: #2a2a2a;' +
|
|
83
|
+
'}' +
|
|
84
|
+
'.te-container.te-dark .te-table-tools .tt-label {' +
|
|
85
|
+
' color: #999;' +
|
|
86
|
+
'}' +
|
|
87
|
+
'.te-container.te-dark .te-image-tools {' +
|
|
88
|
+
' background: #1e1e1e; border-color: #444;' +
|
|
89
|
+
'}' +
|
|
90
|
+
'.te-container.te-dark .te-image-tools .btn {' +
|
|
91
|
+
' color: #ccc; border-color: #555; background: #2a2a2a;' +
|
|
92
|
+
'}' +
|
|
93
|
+
'.te-container.te-dark .te-float-toolbar {' +
|
|
94
|
+
' background: #1e1e1e; border-color: #444;' +
|
|
95
|
+
'}' +
|
|
96
|
+
'.te-container.te-dark .te-float-toolbar .btn {' +
|
|
97
|
+
' color: #ccc; border-color: #555; background: #2a2a2a;' +
|
|
98
|
+
'}' +
|
|
99
|
+
'.te-container.te-dark .te-hr-popup {' +
|
|
100
|
+
' background: #1e1e1e; border-color: #444;' +
|
|
101
|
+
'}' +
|
|
102
|
+
'.te-container.te-dark .te-hr-popup .btn {' +
|
|
103
|
+
' color: #ccc; border-color: #555; background: #2a2a2a;' +
|
|
104
|
+
'}',
|
|
105
|
+
init(ctx: PluginContext): void {
|
|
106
|
+
if(!ctx.features.darkMode) return;
|
|
107
|
+
var btn = ctx.wrapper.querySelector('[data-cmd="toggleDark"]');
|
|
108
|
+
if(!btn) return;
|
|
109
|
+
|
|
110
|
+
btn.addEventListener('click', function(){
|
|
111
|
+
var container = ctx.wrapper.querySelector('.te-container');
|
|
112
|
+
if(!container) return;
|
|
113
|
+
var isDark = container.classList.toggle('te-dark');
|
|
114
|
+
var icon = btn!.querySelector('.ti');
|
|
115
|
+
if(icon){
|
|
116
|
+
icon.className = 'ti ' + (isDark ? 'ti-sun' : 'ti-moon');
|
|
117
|
+
}
|
|
118
|
+
(btn as HTMLElement).classList.toggle('active', isDark);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export default darkMode;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const direction: Plugin = {
|
|
4
|
+
name: 'direction',
|
|
5
|
+
order: 61,
|
|
6
|
+
toolbarHTML: '' +
|
|
7
|
+
'<button type="button" class="btn btn-sm btn-light" title="Left to Right" data-cmd="ltr"><i class="ti ti-arrow-left"></i> LTR</button>' +
|
|
8
|
+
'<button type="button" class="btn btn-sm btn-light" title="Right to Left" data-cmd="rtl">RTL <i class="ti ti-arrow-right"></i></button>',
|
|
9
|
+
init(ctx: PluginContext): void {
|
|
10
|
+
if (!ctx.features.direction) return;
|
|
11
|
+
ctx.wrapper.querySelectorAll('[data-cmd="ltr"],[data-cmd="rtl"]').forEach(function (btn) {
|
|
12
|
+
btn.addEventListener('click', function () {
|
|
13
|
+
var dir = btn.getAttribute('data-cmd');
|
|
14
|
+
document.execCommand('formatBlock', false, 'p');
|
|
15
|
+
var sel = window.getSelection();
|
|
16
|
+
if (sel && sel.rangeCount > 0) {
|
|
17
|
+
var node: Node | null = sel.anchorNode;
|
|
18
|
+
if (node && node.nodeType === 3) node = node.parentNode;
|
|
19
|
+
var block = node && (node as Element).closest && (node as Element).closest('p,h1,h2,h3,h4,h5,h6,div');
|
|
20
|
+
if (block && ctx.wrapper.contains(block)) {
|
|
21
|
+
block.setAttribute('dir', dir!);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
ctx.editor.focus();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default direction;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const dragDrop: Plugin = {
|
|
4
|
+
name: 'dragDrop',
|
|
5
|
+
order: 3,
|
|
6
|
+
css: '' +
|
|
7
|
+
'.te-editor.te-dragover {' +
|
|
8
|
+
' outline: 3px dashed rgba(13,110,253,.5);' +
|
|
9
|
+
' outline-offset: -6px;' +
|
|
10
|
+
' background: rgba(13,110,253,.03);' +
|
|
11
|
+
'}',
|
|
12
|
+
init(ctx: PluginContext): void {
|
|
13
|
+
if(ctx.features.dragDrop === false) return;
|
|
14
|
+
var editor = ctx.editor;
|
|
15
|
+
|
|
16
|
+
function isImageFile(file: File): boolean {
|
|
17
|
+
return file && /^image\/(png|jpe?g|gif|webp|svg\+xml|bmp)$/i.test(file.type);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readFile(file: File | Blob, cb: (url: string) => void): void {
|
|
21
|
+
var reader = new FileReader();
|
|
22
|
+
reader.onload = function(){ cb(reader.result as string); };
|
|
23
|
+
reader.readAsDataURL(file);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
editor.addEventListener('dragenter', function(e: DragEvent){
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
e.stopPropagation();
|
|
29
|
+
editor.classList.add('te-dragover');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
editor.addEventListener('dragover', function(e: DragEvent){
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
e.stopPropagation();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
editor.addEventListener('dragleave', function(e: DragEvent){
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
if(!editor.contains(e.relatedTarget as Node)){
|
|
41
|
+
editor.classList.remove('te-dragover');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
editor.addEventListener('drop', function(e: DragEvent){
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
e.stopPropagation();
|
|
48
|
+
editor.classList.remove('te-dragover');
|
|
49
|
+
|
|
50
|
+
var files = e.dataTransfer && e.dataTransfer.files;
|
|
51
|
+
if(!files || files.length === 0) return;
|
|
52
|
+
|
|
53
|
+
for(var i = 0; i < files.length; i++){
|
|
54
|
+
var file = files[i];
|
|
55
|
+
if(!isImageFile(file)) continue;
|
|
56
|
+
|
|
57
|
+
var handler = (ctx.TulihEditor && ctx.TulihEditor.onUploadImage) || readFile;
|
|
58
|
+
handler(file, function(url: string){
|
|
59
|
+
if(!url) return;
|
|
60
|
+
var html = '<img src="' + url.replace(/"/g,'"') + '" alt="' + (file.name || '').replace(/"/g,'"') + '" style="max-width:100%;" />';
|
|
61
|
+
document.execCommand('insertHTML', false, html);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default dragDrop;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
var EMOJIS = '😀😃😄😁😆😅🤣😂🙂🙃😉😊😇🥰😍🤩😘😗😚😋😛😜🤪😝🤑🤗🤭🤫🤔🤐🤨😐😑😶😏😒🙄😬🤥😌😔😪🤤😴😷🤒🤕🤢🤮🤧🥵🥶🥴😵🤯🤠🥳🥸😎🤓🧐😕😟🙁😮😯😲😳🥺😦😧😨😰😥😢😭😱😖😣😞😓😩😫🥱😤😡😠🤬😈👿💀☠💩🤡👹👺👻👽👾🤖💋👋✋👌🤏🤞🤟🤘🤙👈👉👆🖕👍👎✊👊🤛🤜👏🙌👐🤲🤝🙏✍💅👂👃👣👀👅👄'.match(/.{1,2}/g) || [];
|
|
4
|
+
|
|
5
|
+
var EMOJI_NAMES: Record<string, string> = {
|
|
6
|
+
'😀':'grinning laugh happy','😃':'grinning smile happy','😄':'grinning smile happy','😁':'beaming grin happy',
|
|
7
|
+
'😆':'laughing grinning','🤣':'rolling floor laughing','😂':'tears joy laughing cry',
|
|
8
|
+
'🙂':'slight smile','🙃':'upside down','😉':'winking wink',
|
|
9
|
+
'😊':'blush smile happy','😇':'innocent halo angel',
|
|
10
|
+
'🥰':'love heart smile','😍':'heart eyes love','🤩':'star struck',
|
|
11
|
+
'😘':'kiss blowing heart','😗':'kissing kiss','😚':'kiss closed eyes',
|
|
12
|
+
'😋':'savor tongue yum','😛':'tongue playful','😜':'wink tongue silly','🤪':'crazy wild','😝':'squint tongue',
|
|
13
|
+
'🤑':'money tongue rich',
|
|
14
|
+
'🤗':'hug open hands','🤭':'hand over mouth secret','🤫':'shush quiet silent',
|
|
15
|
+
'🤔':'thinking think','🤐':'zipper mouth secret','🤨':'raised eyebrow suspicious',
|
|
16
|
+
'😐':'neutral straight face','😑':'expressionless blank','😶':'no mouth silent',
|
|
17
|
+
'😏':'smirk smug','😒':'unamified roll','🙄':'eye roll',
|
|
18
|
+
'😬':'grimace awkward','🤥':'lying liar nose',
|
|
19
|
+
'😌':'relieved relief','😔':'pensive sad','😪':'sleepy tired','🤤':'drooling drool','😴':'sleeping sleep zzz',
|
|
20
|
+
'😷':'medical mask sick','🤒':'thermometer sick fever','🤕':'head bandage hurt',
|
|
21
|
+
'🤢':'nauseous sick vomit','🤮':'vomiting sick','🤧':'sneeze sick',
|
|
22
|
+
'🥵':'hot fever sweat','🥶':'cold freeze ice','🥴':'dizzy tipsy',
|
|
23
|
+
'😵':'dizzy confused','🤯':'exploding head mind blown',
|
|
24
|
+
'🤠':'cowboy hat','🥳':'party celebration','🥸':'disguise mask',
|
|
25
|
+
'😎':'sunglasses cool','🤓':'nerd geek glasses','🧐':'monocle detective',
|
|
26
|
+
'😕':'confused unsure','😟':'worried concern','🙁':'slight frown sad',
|
|
27
|
+
'😮':'surprised shock','😯':'hushed surprise','😲':'astonished amazed','😳':'flushed embarrassed',
|
|
28
|
+
'🥺':'pleading puppy eyes',
|
|
29
|
+
'😦':'frown sad','😧':'anguish distressed','😨':'fearful afraid','😰':'anxious sweat worry',
|
|
30
|
+
'😥':'disappointed sad','😢':'cry sad tear','😭':'sobbing cry tears',
|
|
31
|
+
'😱':'scream fear horror','😖':'confounded frustrated','😣':'persevere struggle',
|
|
32
|
+
'😞':'disappointed sad','😓':'downcast sweat','😩':'weary tired','😫':'tired exhausted','🥱':'yawn sleepy',
|
|
33
|
+
'😤':'triumph frustrated steam','😡':'angry rage','😠':'angry mad','🤬':'cursing swear',
|
|
34
|
+
'😈':'devil evil grin','👿':'devil evil angry','💀':'skull death','☠':'skull crossbones danger',
|
|
35
|
+
'💩':'poop poo','🤡':'clown circus','👹':'ogre monster','👺':'goblin monster',
|
|
36
|
+
'👻':'ghost halloween','👽':'alien ufo','👾':'alien monster space','🤖':'robot ai',
|
|
37
|
+
'💋':'kiss lips',
|
|
38
|
+
'👋':'wave hand hello bye','✋':'raised hand stop high five','👌':'ok perfect',
|
|
39
|
+
'🤏':'pinching small little','🤞':'crossed fingers luck','🤟':'love gesture',
|
|
40
|
+
'🤘':'horns rock metal','🤙':'call me phone',
|
|
41
|
+
'👈':'point left','👉':'point right','👆':'point up','🖕':'middle finger fuck',
|
|
42
|
+
'👍':'thumbs up like','👎':'thumbs down dislike',
|
|
43
|
+
'✊':'fist raised power','👊':'fist punch','🤛':'fist left','🤜':'fist right',
|
|
44
|
+
'👏':'clap applause','🙌':'raised hands celebration','👐':'open hands',
|
|
45
|
+
'🤲':'palms together pray','🤝':'handshake deal agree','🙏':'folded hands pray please thank',
|
|
46
|
+
'✍':'writing hand','💅':'nail polish care',
|
|
47
|
+
'👂':'ear listen','👃':'nose smell','👣':'footsteps feet','👀':'eyes look','👅':'tongue taste','👄':'mouth lips'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const emoji: Plugin = {
|
|
51
|
+
name: 'emoji',
|
|
52
|
+
order: 60,
|
|
53
|
+
css: '' +
|
|
54
|
+
'.te-emoji-popup {' +
|
|
55
|
+
' position: absolute; z-index: 3000;' +
|
|
56
|
+
' background: #fff; border: 1px solid #dee2e6;' +
|
|
57
|
+
' border-radius: .5rem; box-shadow: 0 6px 20px rgba(0,0,0,.15);' +
|
|
58
|
+
' padding: .4rem; max-width: 290px;' +
|
|
59
|
+
'}' +
|
|
60
|
+
'.te-emoji-popup .te-emoji-search {' +
|
|
61
|
+
' width: 100%; box-sizing: border-box;' +
|
|
62
|
+
' padding: .3rem .5rem; margin-bottom: .4rem;' +
|
|
63
|
+
' border: 1px solid #dee2e6; border-radius: .375rem;' +
|
|
64
|
+
' font-size: .8rem; outline: none;' +
|
|
65
|
+
'}' +
|
|
66
|
+
'.te-emoji-popup .te-emoji-search:focus {' +
|
|
67
|
+
' border-color: #86b7fe; box-shadow: 0 0 0 2px rgba(13,110,253,.15);' +
|
|
68
|
+
'}' +
|
|
69
|
+
'.te-emoji-popup .te-emoji-no-result {' +
|
|
70
|
+
' padding: 1rem; text-align: center; color: #6c757d; font-size: .8rem;' +
|
|
71
|
+
'}' +
|
|
72
|
+
'.te-emoji-popup .te-emoji-grid {' +
|
|
73
|
+
' display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px;' +
|
|
74
|
+
' max-height: 220px; overflow-y: auto;' +
|
|
75
|
+
'}' +
|
|
76
|
+
'.te-emoji-popup .te-emoji-grid button {' +
|
|
77
|
+
' background: none; border: none; font-size: 1.3rem;' +
|
|
78
|
+
' padding: .25rem; cursor: pointer; border-radius: .25rem; text-align: center;' +
|
|
79
|
+
'}' +
|
|
80
|
+
'.te-emoji-popup .te-emoji-grid button:hover { background: #f0f0f0; }' +
|
|
81
|
+
'.te-emoji-popup .te-emoji-grid button.hidden { display: none; }',
|
|
82
|
+
toolbarHTML: '<button type="button" class="btn btn-sm btn-light" title="Emoji" data-cmd="emoji"><i class="ti ti-mood-smile"></i></button>',
|
|
83
|
+
init(ctx: PluginContext): void {
|
|
84
|
+
if(!ctx.features.emoji) return;
|
|
85
|
+
|
|
86
|
+
var popup = document.createElement('div');
|
|
87
|
+
popup.className = 'te-emoji-popup';
|
|
88
|
+
popup.style.display = 'none';
|
|
89
|
+
popup.innerHTML = '<input type="text" class="te-emoji-search" placeholder="Search emoji..." /><div class="te-emoji-grid"></div><div class="te-emoji-no-result" style="display:none">No emoji found</div>';
|
|
90
|
+
var grid = popup.querySelector('.te-emoji-grid') as HTMLElement;
|
|
91
|
+
var search = popup.querySelector('.te-emoji-search') as HTMLInputElement;
|
|
92
|
+
var noResult = popup.querySelector('.te-emoji-no-result') as HTMLElement;
|
|
93
|
+
|
|
94
|
+
EMOJIS.forEach(function(emoji){
|
|
95
|
+
var btn = document.createElement('button');
|
|
96
|
+
btn.type = 'button';
|
|
97
|
+
btn.textContent = emoji;
|
|
98
|
+
btn.setAttribute('data-emoji', emoji);
|
|
99
|
+
btn.title = EMOJI_NAMES[emoji] || '';
|
|
100
|
+
grid.appendChild(btn);
|
|
101
|
+
});
|
|
102
|
+
document.body.appendChild(popup);
|
|
103
|
+
|
|
104
|
+
var toolbarBtn = ctx.wrapper.querySelector('[data-cmd="emoji"]') as HTMLElement | null;
|
|
105
|
+
var savedRange: Range | null = null;
|
|
106
|
+
|
|
107
|
+
function saveEditorRange(){
|
|
108
|
+
var sel = window.getSelection();
|
|
109
|
+
if(sel && sel.rangeCount > 0) savedRange = sel.getRangeAt(0).cloneRange();
|
|
110
|
+
else savedRange = null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function restoreEditorRange(){
|
|
114
|
+
if(!savedRange) return;
|
|
115
|
+
var sel = window.getSelection();
|
|
116
|
+
sel!.removeAllRanges();
|
|
117
|
+
sel!.addRange(savedRange);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function showPopup(){
|
|
121
|
+
saveEditorRange();
|
|
122
|
+
var rect = toolbarBtn!.getBoundingClientRect();
|
|
123
|
+
popup.style.display = 'block';
|
|
124
|
+
popup.style.visibility = 'hidden';
|
|
125
|
+
var pw = popup.offsetWidth, ph = popup.offsetHeight;
|
|
126
|
+
var sx = window.scrollX, sy = window.scrollY;
|
|
127
|
+
var vw = window.innerWidth, vh = window.innerHeight;
|
|
128
|
+
|
|
129
|
+
var left = rect.left + sx;
|
|
130
|
+
if(left + pw > sx + vw - 8) left = sx + vw - pw - 8;
|
|
131
|
+
if(left < sx + 4) left = sx + 4;
|
|
132
|
+
popup.style.left = left + 'px';
|
|
133
|
+
|
|
134
|
+
var top = rect.bottom + 4 + sy;
|
|
135
|
+
if(top + ph > sy + vh - 8) top = rect.top - ph - 4 + sy;
|
|
136
|
+
if(top < sy + 4) top = sy + 4;
|
|
137
|
+
popup.style.top = top + 'px';
|
|
138
|
+
|
|
139
|
+
popup.style.visibility = '';
|
|
140
|
+
search.value = '';
|
|
141
|
+
grid.querySelectorAll('button').forEach(function(b){ b.classList.remove('hidden'); });
|
|
142
|
+
noResult.style.display = 'none';
|
|
143
|
+
search.focus();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if(toolbarBtn){
|
|
147
|
+
toolbarBtn.addEventListener('click', function(e: MouseEvent){
|
|
148
|
+
if(popup.style.display !== 'none'){ popup.style.display = 'none'; return; }
|
|
149
|
+
showPopup();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
search.addEventListener('input', function(){
|
|
154
|
+
var q = search.value.trim().toLowerCase();
|
|
155
|
+
var visible = 0;
|
|
156
|
+
grid.querySelectorAll('button').forEach(function(b){
|
|
157
|
+
var name = (EMOJI_NAMES[(b as HTMLButtonElement).textContent!] || '').toLowerCase();
|
|
158
|
+
var match = !q || name.indexOf(q) !== -1;
|
|
159
|
+
b.classList.toggle('hidden', !match);
|
|
160
|
+
if(match) visible++;
|
|
161
|
+
});
|
|
162
|
+
noResult.style.display = visible ? 'none' : 'block';
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
search.addEventListener('keydown', function(e: KeyboardEvent){
|
|
166
|
+
if(e.key === 'Escape'){ popup.style.display = 'none'; ctx.editor.focus(); }
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
popup.addEventListener('mousedown', function(e: MouseEvent){
|
|
170
|
+
var btn = (e.target as HTMLElement).closest && (e.target as HTMLElement).closest('[data-emoji]');
|
|
171
|
+
if(!btn) return;
|
|
172
|
+
e.preventDefault();
|
|
173
|
+
ctx.editor.focus();
|
|
174
|
+
restoreEditorRange();
|
|
175
|
+
document.execCommand('insertHTML', false, (btn as HTMLElement).textContent!);
|
|
176
|
+
popup.style.display = 'none';
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
document.addEventListener('click', function(e: MouseEvent){
|
|
180
|
+
if(popup.style.display === 'none') return;
|
|
181
|
+
if(!popup.contains(e.target as Node) && e.target !== toolbarBtn){
|
|
182
|
+
popup.style.display = 'none';
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export default emoji;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
import * as H from '../core/helpers';
|
|
3
|
+
|
|
4
|
+
var EMOJIS = '😀😃😄😁😆😅🤣😂🙂🙃😉😊😇🥰😍🤩😘😗😚😋😛😜🤪😝🤑🤗🤭🤫🤔🤐🤨😐😑😶😏😒🙄😬🤥😌😔😪🤤😴😷🤒🤕🤢🤮🤧🥵🥶🥴😵🤯🤠🥳🥸😎🤓🧐😕😟🙁😮😯😲😳🥺😦😧😨😰😥😢😭😱😖😣😞😓😩😫🥱😤😡😠🤬😈👿💀☠💩🤡👹👺👻👽👾🤖'.match(/.{1,2}/g) || [];
|
|
5
|
+
|
|
6
|
+
var EMOJI_NAMES: Record<string, string> = {
|
|
7
|
+
'😀':'grinning laugh happy','😃':'grinning smile','😄':'smile happy','😁':'beaming grin','😆':'laughing grinning',
|
|
8
|
+
'🤣':'rolling floor laugh','😂':'tears joy cry',
|
|
9
|
+
'🙂':'slight smile','🙃':'upside down','😉':'winking wink','😊':'blush smile','😇':'innocent angel',
|
|
10
|
+
'🥰':'love heart','😍':'heart eyes','🤩':'star struck','😘':'kiss blowing','😗':'kissing','😚':'kiss closed',
|
|
11
|
+
'😋':'savor yum','😛':'tongue','😜':'wink tongue silly','🤪':'crazy wild','😝':'squint tongue',
|
|
12
|
+
'🤑':'money tongue','🤗':'hug','🤭':'hand over mouth secret','🤫':'shush quiet silent',
|
|
13
|
+
'🤔':'thinking think','🤐':'zipper mouth secret','🤨':'raised eyebrow suspicious',
|
|
14
|
+
'😐':'neutral straight','😑':'expressionless blank','😶':'no mouth silent',
|
|
15
|
+
'😏':'smirk smug','😒':'unamified','🙄':'eye roll',
|
|
16
|
+
'😬':'grimace awkward','🤥':'lying liar nose',
|
|
17
|
+
'😌':'relieved','😔':'pensive sad','😪':'sleepy tired','🤤':'drooling','😴':'sleeping zzz',
|
|
18
|
+
'😷':'mask sick','🤒':'fever','🤕':'hurt bandage','🤢':'nauseous sick','🤮':'vomiting','🤧':'sneeze',
|
|
19
|
+
'🥵':'hot sweat','🥶':'cold freeze','🥴':'dizzy','😵':'dizzy confused','🤯':'mind blown exploding',
|
|
20
|
+
'🤠':'cowboy','🥳':'party celebration','🥸':'disguise','😎':'sunglasses cool','🤓':'nerd geek','🧐':'monocle',
|
|
21
|
+
'😕':'confused','😟':'worried','🙁':'frown sad','😮':'surprised shock','😯':'hushed','😲':'astonished',
|
|
22
|
+
'😳':'flushed embarrassed','🥺':'pleading puppy eyes',
|
|
23
|
+
'😦':'frown','😧':'anguish','😨':'fearful afraid','😰':'anxious worry','😥':'disappointed',
|
|
24
|
+
'😢':'cry sad tear','😭':'sobbing cry','😱':'scream horror','😖':'confounded','😣':'struggle',
|
|
25
|
+
'😞':'disappointed','😓':'sweat','😩':'weary','😫':'tired exhausted','🥱':'yawn sleepy',
|
|
26
|
+
'😤':'frustrated','😡':'angry rage','😠':'angry mad','🤬':'cursing swear',
|
|
27
|
+
'😈':'devil evil grin','👿':'devil evil angry','💀':'skull death','☠':'skull crossbones danger',
|
|
28
|
+
'💩':'poop','🤡':'clown','👹':'ogre monster','👺':'goblin',
|
|
29
|
+
'👻':'ghost halloween','👽':'alien ufo','👾':'alien monster','🤖':'robot',
|
|
30
|
+
'💋':'kiss lips',
|
|
31
|
+
'👋':'wave hello bye','✋':'raised hand stop','👌':'ok perfect',
|
|
32
|
+
'🤏':'pinching small','🤞':'crossed fingers luck','🤟':'love gesture','🤘':'horns rock','🤙':'call me',
|
|
33
|
+
'👈':'point left','👉':'point right','👆':'point up','🖕':'middle finger',
|
|
34
|
+
'👍':'thumbs up like','👎':'thumbs down dislike',
|
|
35
|
+
'✊':'fist raised power','👊':'fist punch','🤛':'fist left','🤜':'fist right',
|
|
36
|
+
'👏':'clap applause','🙌':'celebration','👐':'open hands','🤲':'palms together pray',
|
|
37
|
+
'🤝':'handshake deal','🙏':'pray please thank',
|
|
38
|
+
'✍':'writing','💅':'nail polish',
|
|
39
|
+
'👂':'ear listen','👃':'nose smell','👣':'footsteps','👀':'eyes','👅':'tongue','👄':'mouth lips'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const emojiAutocomplete: Plugin = {
|
|
43
|
+
name: 'emojiAutocomplete',
|
|
44
|
+
order: 6,
|
|
45
|
+
css: '' +
|
|
46
|
+
'.te-emoji-ac {' +
|
|
47
|
+
' position: fixed; z-index: 9999;' +
|
|
48
|
+
' background: #fff; border: 1px solid #dee2e6;' +
|
|
49
|
+
' border-radius: .375rem; box-shadow: 0 6px 20px rgba(0,0,0,.15);' +
|
|
50
|
+
' padding: .25rem; display: none;' +
|
|
51
|
+
' max-height: 160px; overflow-y: auto;' +
|
|
52
|
+
' min-width: 120px;' +
|
|
53
|
+
'}' +
|
|
54
|
+
'.te-emoji-ac-item {' +
|
|
55
|
+
' display: flex; align-items: center; gap: 6px;' +
|
|
56
|
+
' padding: 4px 8px; cursor: pointer;' +
|
|
57
|
+
' border-radius: .25rem; font-size: 1rem;' +
|
|
58
|
+
'}' +
|
|
59
|
+
'.te-emoji-ac-item:hover, .te-emoji-ac-item.active {' +
|
|
60
|
+
' background: #e7f1ff;' +
|
|
61
|
+
'}',
|
|
62
|
+
init(ctx: PluginContext): void {
|
|
63
|
+
if(ctx.features.emojiAutocomplete === false) return;
|
|
64
|
+
var editor = ctx.editor;
|
|
65
|
+
|
|
66
|
+
var popup = document.createElement('div');
|
|
67
|
+
popup.className = 'te-emoji-ac';
|
|
68
|
+
document.body.appendChild(popup);
|
|
69
|
+
|
|
70
|
+
var query = '';
|
|
71
|
+
var activeIdx = 0;
|
|
72
|
+
var items: Element[] = [];
|
|
73
|
+
|
|
74
|
+
function buildItems(filter: string){
|
|
75
|
+
filter = filter || '';
|
|
76
|
+
popup.innerHTML = '';
|
|
77
|
+
items = [];
|
|
78
|
+
activeIdx = 0;
|
|
79
|
+
var f = filter.toLowerCase();
|
|
80
|
+
EMOJIS.forEach(function(e){
|
|
81
|
+
var name = (EMOJI_NAMES[e] || '').toLowerCase();
|
|
82
|
+
if(f && name.indexOf(f) === -1) return;
|
|
83
|
+
var div = document.createElement('div');
|
|
84
|
+
div.className = 'te-emoji-ac-item';
|
|
85
|
+
div.textContent = e;
|
|
86
|
+
div.setAttribute('data-emoji', e);
|
|
87
|
+
popup.appendChild(div);
|
|
88
|
+
});
|
|
89
|
+
items = Array.prototype.slice.call(popup.children);
|
|
90
|
+
if(items.length === 0){ popup.style.display = 'none'; return; }
|
|
91
|
+
items[0].classList.add('active');
|
|
92
|
+
popup.style.display = 'block';
|
|
93
|
+
position();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function position(){
|
|
97
|
+
var sel = window.getSelection();
|
|
98
|
+
if(!sel || !sel.rangeCount) return;
|
|
99
|
+
var r = sel.getRangeAt(0);
|
|
100
|
+
var rect = r.getBoundingClientRect();
|
|
101
|
+
popup.style.left = Math.max(4, rect.left) + 'px';
|
|
102
|
+
popup.style.top = (rect.bottom + 4) + 'px';
|
|
103
|
+
var pr = popup.getBoundingClientRect();
|
|
104
|
+
if(pr.right > window.innerWidth - 4){
|
|
105
|
+
popup.style.left = (window.innerWidth - pr.width - 4) + 'px';
|
|
106
|
+
}
|
|
107
|
+
if(pr.bottom > window.innerHeight - 4){
|
|
108
|
+
popup.style.top = (rect.top - pr.height - 4) + 'px';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function insertEmoji(emoji: string){
|
|
113
|
+
editor.focus();
|
|
114
|
+
var sel = window.getSelection();
|
|
115
|
+
if(sel && sel.rangeCount > 0 && query){
|
|
116
|
+
var range = sel.getRangeAt(0);
|
|
117
|
+
var len = query.length + 1;
|
|
118
|
+
if(range.startOffset >= len && range.startContainer.nodeType === 3){
|
|
119
|
+
range.setStart(range.startContainer, range.startOffset - len);
|
|
120
|
+
range.deleteContents();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
document.execCommand('insertText', false, emoji);
|
|
124
|
+
popup.style.display = 'none';
|
|
125
|
+
query = '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function move(dir: number){
|
|
129
|
+
if(items.length === 0) return;
|
|
130
|
+
items[activeIdx].classList.remove('active');
|
|
131
|
+
activeIdx = (activeIdx + dir + items.length) % items.length;
|
|
132
|
+
items[activeIdx].classList.add('active');
|
|
133
|
+
(items[activeIdx] as HTMLElement).scrollIntoView({ block: 'nearest' });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
editor.addEventListener('keydown', function(e: KeyboardEvent){
|
|
137
|
+
if(popup.style.display === 'none') return;
|
|
138
|
+
if(e.key === 'ArrowDown'){ e.preventDefault(); move(1); return; }
|
|
139
|
+
if(e.key === 'ArrowUp'){ e.preventDefault(); move(-1); return; }
|
|
140
|
+
if(e.key === 'Enter' || e.key === 'Tab'){
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
var active = popup.querySelector('.active');
|
|
143
|
+
if(active){
|
|
144
|
+
var emoji = active.getAttribute('data-emoji');
|
|
145
|
+
if(emoji) insertEmoji(emoji);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if(e.key === 'Escape'){ popup.style.display = 'none'; query = ''; return; }
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
editor.addEventListener('input', function(){
|
|
153
|
+
var sel = window.getSelection();
|
|
154
|
+
if(!sel || !sel.rangeCount || !sel.anchorNode) return;
|
|
155
|
+
var node = sel.anchorNode;
|
|
156
|
+
var text = node.textContent || '';
|
|
157
|
+
var offset = sel.anchorOffset;
|
|
158
|
+
var before = text.slice(0, offset);
|
|
159
|
+
var colon = before.lastIndexOf(':');
|
|
160
|
+
if(colon !== -1 && before.indexOf(' ', colon) === -1 && colon !== offset - 1){
|
|
161
|
+
query = before.slice(colon + 1);
|
|
162
|
+
if(query.indexOf(':') !== -1){ popup.style.display = 'none'; return; }
|
|
163
|
+
buildItems(query);
|
|
164
|
+
} else if(colon !== -1 && colon === offset - 1){
|
|
165
|
+
query = '';
|
|
166
|
+
buildItems('');
|
|
167
|
+
} else {
|
|
168
|
+
popup.style.display = 'none';
|
|
169
|
+
query = '';
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
popup.addEventListener('mousedown', function(e: MouseEvent){
|
|
174
|
+
var item = (e.target as HTMLElement) && (e.target as HTMLElement).closest && (e.target as HTMLElement).closest('.te-emoji-ac-item');
|
|
175
|
+
if(!item) return;
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
var emoji = (item as HTMLElement).getAttribute('data-emoji');
|
|
178
|
+
if(emoji) insertEmoji(emoji);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default emojiAutocomplete;
|