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,344 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const imageTools: Plugin = {
|
|
4
|
+
name: 'imageTools',
|
|
5
|
+
order: 56,
|
|
6
|
+
deps: ['image', 'imageProps'],
|
|
7
|
+
css: '' +
|
|
8
|
+
'.te-image-tools {' +
|
|
9
|
+
' position: absolute; display: none; z-index: 2000;' +
|
|
10
|
+
' background: #fff; border: 1px solid #dee2e6;' +
|
|
11
|
+
' border-radius: .375rem; box-shadow: 0 6px 20px rgba(0,0,0,.15);' +
|
|
12
|
+
' padding: .375rem; gap: .25rem; align-items: center;' +
|
|
13
|
+
'}' +
|
|
14
|
+
'.te-image-tools .btn { padding: .2rem .4rem; font-size: .8rem; }' +
|
|
15
|
+
'.te-image-tools svg, .te-image-tools i { pointer-events: none; }' +
|
|
16
|
+
'.te-image-tools .te-image-tools-alt {' +
|
|
17
|
+
' width: 130px; height: 26px; font-size: .8rem;' +
|
|
18
|
+
' padding: 0 .35rem; border: 1px solid #dee2e6; border-radius: .25rem;' +
|
|
19
|
+
'}' +
|
|
20
|
+
'.te-image-tools .te-image-tools-alt:focus { outline: none; border-color: #86b7fe; box-shadow: 0 0 0 2px rgba(13,110,253,.25); }' +
|
|
21
|
+
'.te-image-tools .te-divider-v {' +
|
|
22
|
+
' width: 1px; height: 20px; background: #dee2e6;' +
|
|
23
|
+
'}' +
|
|
24
|
+
'.te-image-tools .active { background: #e7f1ff; border-color: #b6d4fe; }' +
|
|
25
|
+
'.te-editor img.te-focused {' +
|
|
26
|
+
' outline: 3px solid rgba(13,110,253,.4);' +
|
|
27
|
+
' outline-offset: 1px;' +
|
|
28
|
+
'}' +
|
|
29
|
+
'.te-image-resize {' +
|
|
30
|
+
' position: absolute; display: none; z-index: 2001;' +
|
|
31
|
+
' width: 10px; height: 10px;' +
|
|
32
|
+
' background: #0d6efd; border: 2px solid #fff;' +
|
|
33
|
+
' border-radius: 50%; cursor: nwse-resize;' +
|
|
34
|
+
' box-shadow: 0 1px 4px rgba(0,0,0,.3);' +
|
|
35
|
+
'}' +
|
|
36
|
+
'.te-image-resize:hover { transform: scale(1.2); }' +
|
|
37
|
+
'.te-figure {' +
|
|
38
|
+
' display: inline-block; margin: .5rem 0;' +
|
|
39
|
+
' text-align: center; max-width: 100%;' +
|
|
40
|
+
'}' +
|
|
41
|
+
'.te-figure figcaption {' +
|
|
42
|
+
' font-size: .85rem; color: #6c757d;' +
|
|
43
|
+
' padding: .25rem .5rem; font-style: italic;' +
|
|
44
|
+
'}',
|
|
45
|
+
init(ctx: PluginContext): void {
|
|
46
|
+
if(!ctx.features.image || !ctx.features.imageTools) return;
|
|
47
|
+
|
|
48
|
+
var wrapper = ctx.wrapper;
|
|
49
|
+
var editor = ctx.editor;
|
|
50
|
+
var currentImage: HTMLImageElement | null = null;
|
|
51
|
+
|
|
52
|
+
var tools = document.createElement('div');
|
|
53
|
+
tools.className = 'te-image-tools';
|
|
54
|
+
tools.innerHTML = '' +
|
|
55
|
+
'<input type="text" class="te-image-tools-alt" placeholder="Alt text" title="Alt text (Enter to apply)" />' +
|
|
56
|
+
'<div class="te-divider-v"></div>' +
|
|
57
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="align-default" title="Inline"><i class="ti ti-align-left"></i></button>' +
|
|
58
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="align-left" title="Float left"><i class="ti ti-align-left"></i></button>' +
|
|
59
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="align-center" title="Center"><i class="ti ti-align-center"></i></button>' +
|
|
60
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="align-right" title="Float right"><i class="ti ti-align-right"></i></button>' +
|
|
61
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="caption" title="Caption (toggle)"><i class="ti ti-typography"></i></button>' +
|
|
62
|
+
'<div class="te-divider-v"></div>' +
|
|
63
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="width-100" title="Width 100%">100%</button>' +
|
|
64
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="width-75" title="Width 75%">75%</button>' +
|
|
65
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="width-50" title="Width 50%">50%</button>' +
|
|
66
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="width-orig" title="Original size">Orig</button>' +
|
|
67
|
+
'<div class="te-divider-v"></div>' +
|
|
68
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="border" title="Toggle border">Brdr</button>' +
|
|
69
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-image="rounded" title="Toggle rounded corners">Rnd</button>' +
|
|
70
|
+
'<div class="te-divider-v"></div>' +
|
|
71
|
+
'<button type="button" class="btn btn-sm btn-outline-secondary" data-te-image="props" title="Image Properties"><i class="ti ti-settings"></i></button>' +
|
|
72
|
+
'<button type="button" class="btn btn-sm btn-outline-danger" data-te-image="delete" title="Delete image"><i class="ti ti-x"></i></button>';
|
|
73
|
+
document.body.appendChild(tools);
|
|
74
|
+
|
|
75
|
+
var resizeHandle = document.createElement('div');
|
|
76
|
+
resizeHandle.className = 'te-image-resize';
|
|
77
|
+
document.body.appendChild(resizeHandle);
|
|
78
|
+
|
|
79
|
+
function getAlign(img: HTMLImageElement): string {
|
|
80
|
+
var st = img.style;
|
|
81
|
+
if(st.float === 'left') return 'left';
|
|
82
|
+
if(st.float === 'right') return 'right';
|
|
83
|
+
if(st.display === 'block' && st.marginLeft === 'auto' && st.marginRight === 'auto') return 'center';
|
|
84
|
+
return 'default';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function setAlign(img: HTMLImageElement, align: string): void {
|
|
88
|
+
if(align === 'default'){
|
|
89
|
+
img.style.float = '';
|
|
90
|
+
img.style.display = '';
|
|
91
|
+
img.style.marginLeft = '';
|
|
92
|
+
img.style.marginRight = '';
|
|
93
|
+
} else if(align === 'left'){
|
|
94
|
+
img.style.float = 'left';
|
|
95
|
+
img.style.display = '';
|
|
96
|
+
img.style.marginLeft = '0';
|
|
97
|
+
img.style.marginRight = '1em';
|
|
98
|
+
} else if(align === 'right'){
|
|
99
|
+
img.style.float = 'right';
|
|
100
|
+
img.style.display = '';
|
|
101
|
+
img.style.marginLeft = '1em';
|
|
102
|
+
img.style.marginRight = '0';
|
|
103
|
+
} else if(align === 'center'){
|
|
104
|
+
img.style.float = '';
|
|
105
|
+
img.style.display = 'block';
|
|
106
|
+
img.style.marginLeft = 'auto';
|
|
107
|
+
img.style.marginRight = 'auto';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function clearImageFocus(): void {
|
|
112
|
+
if(!editor) return;
|
|
113
|
+
var prev = editor.querySelector('.te-focused') as HTMLElement | null;
|
|
114
|
+
if(prev) prev.classList.remove('te-focused');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function showResizeHandle(img: HTMLImageElement): void {
|
|
118
|
+
var rect = img.getBoundingClientRect();
|
|
119
|
+
resizeHandle.style.left = (window.scrollX + rect.right - 5) + 'px';
|
|
120
|
+
resizeHandle.style.top = (window.scrollY + rect.bottom - 5) + 'px';
|
|
121
|
+
resizeHandle.style.display = 'block';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function updateAlignBtns(img: HTMLImageElement): void {
|
|
125
|
+
var align = getAlign(img);
|
|
126
|
+
tools.querySelectorAll('[data-te-image^="align-"]').forEach(function(b){
|
|
127
|
+
b.classList.toggle('active', b.getAttribute('data-te-image') === 'align-' + align);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function posTools(img: HTMLImageElement): void {
|
|
132
|
+
var rect = img.getBoundingClientRect();
|
|
133
|
+
var altInp = tools.querySelector('.te-image-tools-alt') as HTMLInputElement | null;
|
|
134
|
+
if(altInp) altInp.value = img.getAttribute('alt') || '';
|
|
135
|
+
tools.style.display = 'flex';
|
|
136
|
+
tools.style.left = (window.scrollX + rect.left) + 'px';
|
|
137
|
+
tools.style.visibility = 'hidden';
|
|
138
|
+
var toolsH = tools.offsetHeight;
|
|
139
|
+
var editorRect = wrapper.getBoundingClientRect();
|
|
140
|
+
var below = rect.bottom + 6 + toolsH;
|
|
141
|
+
var above = rect.top - 6 - toolsH;
|
|
142
|
+
if(below > editorRect.bottom && above > editorRect.top){
|
|
143
|
+
tools.style.top = (window.scrollY + rect.top - 6 - toolsH) + 'px';
|
|
144
|
+
} else {
|
|
145
|
+
tools.style.top = (window.scrollY + rect.bottom + 6) + 'px';
|
|
146
|
+
}
|
|
147
|
+
tools.style.visibility = '';
|
|
148
|
+
showResizeHandle(img);
|
|
149
|
+
updateAlignBtns(img);
|
|
150
|
+
var capBtn = tools.querySelector('[data-te-image="caption"]');
|
|
151
|
+
if(capBtn) capBtn.classList.toggle('active', !!(img.parentNode && (img.parentNode as Element).tagName === 'FIGURE'));
|
|
152
|
+
var brdrBtn = tools.querySelector('[data-te-image="border"]');
|
|
153
|
+
if(brdrBtn){
|
|
154
|
+
var b = img.style.border || '';
|
|
155
|
+
brdrBtn.classList.toggle('active', !!(b && b !== 'none'));
|
|
156
|
+
}
|
|
157
|
+
var rndBtn = tools.querySelector('[data-te-image="rounded"]');
|
|
158
|
+
if(rndBtn){
|
|
159
|
+
var r = img.style.borderRadius || '';
|
|
160
|
+
rndBtn.classList.toggle('active', !!(r && r !== '0px' && r !== ''));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function hideTools(): void {
|
|
165
|
+
tools.style.display = 'none';
|
|
166
|
+
resizeHandle.style.display = 'none';
|
|
167
|
+
clearImageFocus();
|
|
168
|
+
currentImage = null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
editor.addEventListener('click', function(e: MouseEvent){
|
|
172
|
+
var img = e.target && ((e.target as Element).closest ? (e.target as Element).closest('img') : null) as HTMLImageElement | null;
|
|
173
|
+
if(img && wrapper.contains(img)){
|
|
174
|
+
e.stopPropagation();
|
|
175
|
+
currentImage = img;
|
|
176
|
+
clearImageFocus();
|
|
177
|
+
img.classList.add('te-focused');
|
|
178
|
+
img.setAttribute('tabindex', '-1');
|
|
179
|
+
try { img.focus({preventScroll: true}); } catch(_){ }
|
|
180
|
+
posTools(img);
|
|
181
|
+
} else {
|
|
182
|
+
if(!tools.contains(e.target as Node) && e.target !== resizeHandle){
|
|
183
|
+
hideTools();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
editor.addEventListener('dblclick', function(){
|
|
189
|
+
hideTools();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
document.addEventListener('selectionchange', function(){
|
|
193
|
+
if(!currentImage) return;
|
|
194
|
+
if(document.activeElement !== currentImage &&
|
|
195
|
+
!tools.contains(document.activeElement)){
|
|
196
|
+
var sel = window.getSelection();
|
|
197
|
+
if(!sel || sel.isCollapsed){
|
|
198
|
+
hideTools();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
tools.addEventListener('click', function(e: MouseEvent){
|
|
204
|
+
var actionBtn = e.target && (e.target as Element).closest('[data-te-image]') as HTMLElement | null;
|
|
205
|
+
var action = actionBtn && actionBtn.getAttribute('data-te-image');
|
|
206
|
+
if(!action || !currentImage) return;
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
e.stopPropagation();
|
|
209
|
+
|
|
210
|
+
if(action === 'width-100'){
|
|
211
|
+
currentImage.setAttribute('width', '100%');
|
|
212
|
+
if(currentImage.style) currentImage.style.maxWidth = '100%';
|
|
213
|
+
posTools(currentImage);
|
|
214
|
+
}
|
|
215
|
+
else if(action === 'width-75'){
|
|
216
|
+
currentImage.setAttribute('width', '75%');
|
|
217
|
+
posTools(currentImage);
|
|
218
|
+
}
|
|
219
|
+
else if(action === 'width-50'){
|
|
220
|
+
currentImage.setAttribute('width', '50%');
|
|
221
|
+
posTools(currentImage);
|
|
222
|
+
}
|
|
223
|
+
else if(action === 'width-orig'){
|
|
224
|
+
currentImage.removeAttribute('width');
|
|
225
|
+
currentImage.removeAttribute('height');
|
|
226
|
+
if(currentImage.style) currentImage.style.maxWidth = '';
|
|
227
|
+
posTools(currentImage);
|
|
228
|
+
}
|
|
229
|
+
else if(action === 'delete'){
|
|
230
|
+
var parent = currentImage.parentNode;
|
|
231
|
+
if(parent) parent.removeChild(currentImage);
|
|
232
|
+
hideTools();
|
|
233
|
+
}
|
|
234
|
+
else if(action === 'props'){
|
|
235
|
+
var dblClick = new MouseEvent('dblclick', { bubbles: true });
|
|
236
|
+
currentImage.dispatchEvent(dblClick);
|
|
237
|
+
hideTools();
|
|
238
|
+
}
|
|
239
|
+
else if(action.indexOf('align-') === 0){
|
|
240
|
+
var a = action.replace('align-', '');
|
|
241
|
+
setAlign(currentImage, a);
|
|
242
|
+
updateAlignBtns(currentImage);
|
|
243
|
+
posTools(currentImage);
|
|
244
|
+
}
|
|
245
|
+
else if(action === 'border'){
|
|
246
|
+
var curBorder = currentImage.style.border || '';
|
|
247
|
+
if(curBorder && curBorder !== 'none'){
|
|
248
|
+
currentImage.style.border = '';
|
|
249
|
+
currentImage.removeAttribute('border');
|
|
250
|
+
} else {
|
|
251
|
+
currentImage.style.border = '2px solid #dee2e6';
|
|
252
|
+
}
|
|
253
|
+
posTools(currentImage);
|
|
254
|
+
}
|
|
255
|
+
else if(action === 'rounded'){
|
|
256
|
+
var curRadius = currentImage.style.borderRadius || '';
|
|
257
|
+
if(curRadius && curRadius !== '0px'){
|
|
258
|
+
currentImage.style.borderRadius = '';
|
|
259
|
+
} else {
|
|
260
|
+
currentImage.style.borderRadius = '8px';
|
|
261
|
+
}
|
|
262
|
+
posTools(currentImage);
|
|
263
|
+
}
|
|
264
|
+
else if(action === 'caption'){
|
|
265
|
+
var fig = currentImage.parentNode as Element | null;
|
|
266
|
+
if(fig && fig.tagName === 'FIGURE' && fig.classList.contains('te-figure')){
|
|
267
|
+
var parent = fig.parentNode;
|
|
268
|
+
var img = fig.querySelector('img') || fig.querySelector('figcaption');
|
|
269
|
+
if(img){
|
|
270
|
+
parent!.insertBefore(img, fig);
|
|
271
|
+
parent!.removeChild(fig);
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
var figure = document.createElement('figure');
|
|
275
|
+
figure.className = 'te-figure';
|
|
276
|
+
var caption = document.createElement('figcaption');
|
|
277
|
+
caption.textContent = 'Caption';
|
|
278
|
+
currentImage.parentNode!.insertBefore(figure, currentImage);
|
|
279
|
+
figure.appendChild(currentImage);
|
|
280
|
+
figure.appendChild(caption);
|
|
281
|
+
caption.contentEditable = 'true';
|
|
282
|
+
}
|
|
283
|
+
posTools(currentImage);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
tools.addEventListener('keydown', function(e: KeyboardEvent){
|
|
288
|
+
if(e.key === 'Enter' && currentImage){
|
|
289
|
+
var altInp = tools.querySelector('.te-image-tools-alt') as HTMLInputElement | null;
|
|
290
|
+
if(altInp){
|
|
291
|
+
var val = altInp.value.trim();
|
|
292
|
+
if(val) currentImage.setAttribute('alt', val);
|
|
293
|
+
else currentImage.removeAttribute('alt');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
var resizeData: { startX: number; parentW: number; startPct: number; startH: number } | null = null;
|
|
299
|
+
resizeHandle.addEventListener('mousedown', function(e: MouseEvent){
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
e.stopPropagation();
|
|
302
|
+
if(!currentImage) return;
|
|
303
|
+
var parentW = currentImage.parentNode ? (currentImage.parentNode as HTMLElement).clientWidth : currentImage.clientWidth;
|
|
304
|
+
var currentPct = ((currentImage.clientWidth / parentW) * 100);
|
|
305
|
+
var rect = currentImage.getBoundingClientRect();
|
|
306
|
+
resizeData = {
|
|
307
|
+
startX: e.clientX,
|
|
308
|
+
parentW: parentW,
|
|
309
|
+
startPct: currentPct,
|
|
310
|
+
startH: rect.height
|
|
311
|
+
};
|
|
312
|
+
document.addEventListener('mousemove', onResize);
|
|
313
|
+
document.addEventListener('mouseup', onResizeEnd);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
function onResize(e: MouseEvent): void {
|
|
317
|
+
if(!resizeData || !currentImage) return;
|
|
318
|
+
var dx = e.clientX - resizeData.startX;
|
|
319
|
+
var pctDelta = (dx / resizeData.parentW) * 100;
|
|
320
|
+
var newPct = Math.max(5, resizeData.startPct + pctDelta);
|
|
321
|
+
currentImage.style.width = newPct + '%';
|
|
322
|
+
currentImage.style.height = 'auto';
|
|
323
|
+
currentImage.removeAttribute('width');
|
|
324
|
+
currentImage.removeAttribute('height');
|
|
325
|
+
showResizeHandle(currentImage);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function onResizeEnd(): void {
|
|
329
|
+
document.removeEventListener('mousemove', onResize);
|
|
330
|
+
document.removeEventListener('mouseup', onResizeEnd);
|
|
331
|
+
resizeData = null;
|
|
332
|
+
if(currentImage) posTools(currentImage);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
window.addEventListener('scroll', function(){
|
|
336
|
+
if(currentImage && tools.style.display !== 'none') posTools(currentImage);
|
|
337
|
+
});
|
|
338
|
+
window.addEventListener('resize', function(){
|
|
339
|
+
if(currentImage && tools.style.display !== 'none') posTools(currentImage);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
export default imageTools;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const indent: Plugin = {
|
|
4
|
+
name: 'indent',
|
|
5
|
+
order: 25,
|
|
6
|
+
toolbarHTML: '' +
|
|
7
|
+
'<button type="button" class="btn btn-sm btn-light" title="Indent" data-cmd="indent"><i class="ti ti-indent-increase"></i></button>' +
|
|
8
|
+
'<button type="button" class="btn btn-sm btn-light" title="Outdent" data-cmd="outdent"><i class="ti ti-indent-decrease"></i></button>',
|
|
9
|
+
init(ctx: PluginContext): void {
|
|
10
|
+
if (!ctx.features.indent) return;
|
|
11
|
+
// Buttons may be absent when a custom `toolbar` layout omits this group.
|
|
12
|
+
const indentBtn = ctx.wrapper.querySelector('[data-cmd="indent"]');
|
|
13
|
+
if (indentBtn) {
|
|
14
|
+
indentBtn.addEventListener('click', function () {
|
|
15
|
+
document.execCommand('indent', false, undefined);
|
|
16
|
+
ctx.editor.focus();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const outdentBtn = ctx.wrapper.querySelector('[data-cmd="outdent"]');
|
|
20
|
+
if (outdentBtn) {
|
|
21
|
+
outdentBtn.addEventListener('click', function () {
|
|
22
|
+
document.execCommand('outdent', false, undefined);
|
|
23
|
+
ctx.editor.focus();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default indent;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { PluginManagerInstance } from '../types';
|
|
2
|
+
|
|
3
|
+
import block from './block';
|
|
4
|
+
import inline from './inline';
|
|
5
|
+
import pre from './pre';
|
|
6
|
+
import inlineCode from './inlineCode';
|
|
7
|
+
import subSuper from './subSuper';
|
|
8
|
+
import keyboardShortcuts from './keyboardShortcuts';
|
|
9
|
+
|
|
10
|
+
import readOnly from './readOnly';
|
|
11
|
+
import linkTooltip from './linkTooltip';
|
|
12
|
+
import emojiAutocomplete from './emojiAutocomplete';
|
|
13
|
+
import statusBar from './statusBar';
|
|
14
|
+
import codeBlock from './codeBlock';
|
|
15
|
+
import mediaEmbed from './mediaEmbed';
|
|
16
|
+
import dragDrop from './dragDrop';
|
|
17
|
+
import pastePlain from './pastePlain';
|
|
18
|
+
import findReplace from './findReplace';
|
|
19
|
+
import indent from './indent';
|
|
20
|
+
import list from './list';
|
|
21
|
+
import lineHeight from './lineHeight';
|
|
22
|
+
import fontSize from './fontSize';
|
|
23
|
+
import fontFamily from './fontFamily';
|
|
24
|
+
import specialChars from './specialChars';
|
|
25
|
+
import colors from './colors';
|
|
26
|
+
import tableBg from './tableBg';
|
|
27
|
+
import align from './align';
|
|
28
|
+
import markdown from './markdown';
|
|
29
|
+
import link from './link';
|
|
30
|
+
import image from './image';
|
|
31
|
+
import imageProps from './imageProps';
|
|
32
|
+
import imageTools from './imageTools';
|
|
33
|
+
import iframe from './iframe';
|
|
34
|
+
import table from './table';
|
|
35
|
+
import tableTools from './tableTools';
|
|
36
|
+
import hr from './hr';
|
|
37
|
+
import unlink from './unlink';
|
|
38
|
+
import emoji from './emoji';
|
|
39
|
+
import direction from './direction';
|
|
40
|
+
import source from './source';
|
|
41
|
+
import history from './history';
|
|
42
|
+
import fullscreen from './fullscreen';
|
|
43
|
+
import toolbarCollapse from './toolbarCollapse';
|
|
44
|
+
import autosave from './autosave';
|
|
45
|
+
import autoLinkify from './autoLinkify';
|
|
46
|
+
import caseTransform from './caseTransform';
|
|
47
|
+
import shortcutsHelp from './shortcutsHelp';
|
|
48
|
+
import floatingToolbar from './floatingToolbar';
|
|
49
|
+
import darkMode from './darkMode';
|
|
50
|
+
import shortcutCustomizer from './shortcutCustomizer';
|
|
51
|
+
|
|
52
|
+
export default function registerDefaultPlugins(pm: PluginManagerInstance): void {
|
|
53
|
+
pm.register('block', block);
|
|
54
|
+
pm.register('inline', inline);
|
|
55
|
+
pm.register('pre', pre);
|
|
56
|
+
pm.register('inlineCode', inlineCode);
|
|
57
|
+
pm.register('subSuper', subSuper);
|
|
58
|
+
pm.register('keyboardShortcuts', keyboardShortcuts);
|
|
59
|
+
|
|
60
|
+
pm.register('readOnly', readOnly);
|
|
61
|
+
pm.register('linkTooltip', linkTooltip);
|
|
62
|
+
pm.register('emojiAutocomplete', emojiAutocomplete);
|
|
63
|
+
pm.register('statusBar', statusBar);
|
|
64
|
+
pm.register('codeBlock', codeBlock);
|
|
65
|
+
pm.register('mediaEmbed', mediaEmbed);
|
|
66
|
+
pm.register('dragDrop', dragDrop);
|
|
67
|
+
pm.register('pastePlain', pastePlain);
|
|
68
|
+
pm.register('findReplace', findReplace);
|
|
69
|
+
pm.register('indent', indent);
|
|
70
|
+
pm.register('list', list);
|
|
71
|
+
pm.register('lineHeight', lineHeight);
|
|
72
|
+
pm.register('fontSize', fontSize);
|
|
73
|
+
pm.register('fontFamily', fontFamily);
|
|
74
|
+
pm.register('specialChars', specialChars);
|
|
75
|
+
pm.register('colors', colors);
|
|
76
|
+
pm.register('tableBg', tableBg);
|
|
77
|
+
pm.register('align', align);
|
|
78
|
+
pm.register('markdown', markdown);
|
|
79
|
+
pm.register('link', link);
|
|
80
|
+
pm.register('image', image);
|
|
81
|
+
pm.register('imageProps', imageProps);
|
|
82
|
+
pm.register('imageTools', imageTools);
|
|
83
|
+
pm.register('iframe', iframe);
|
|
84
|
+
pm.register('table', table);
|
|
85
|
+
pm.register('tableTools', tableTools);
|
|
86
|
+
pm.register('hr', hr);
|
|
87
|
+
pm.register('unlink', unlink);
|
|
88
|
+
pm.register('emoji', emoji);
|
|
89
|
+
pm.register('direction', direction);
|
|
90
|
+
pm.register('source', source);
|
|
91
|
+
pm.register('history', history);
|
|
92
|
+
pm.register('fullscreen', fullscreen);
|
|
93
|
+
pm.register('toolbarCollapse', toolbarCollapse);
|
|
94
|
+
pm.register('autosave', autosave);
|
|
95
|
+
pm.register('autoLinkify', autoLinkify);
|
|
96
|
+
pm.register('caseTransform', caseTransform);
|
|
97
|
+
pm.register('shortcutsHelp', shortcutsHelp);
|
|
98
|
+
pm.register('floatingToolbar', floatingToolbar);
|
|
99
|
+
pm.register('darkMode', darkMode);
|
|
100
|
+
pm.register('shortcutCustomizer', shortcutCustomizer);
|
|
101
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const inline: Plugin = {
|
|
4
|
+
name: 'inline',
|
|
5
|
+
order: 20,
|
|
6
|
+
toolbarHTML: '' +
|
|
7
|
+
'<button type="button" class="btn btn-sm btn-light" title="Bold" data-cmd="bold"><i class="ti ti-bold"></i></button>' +
|
|
8
|
+
'<button type="button" class="btn btn-sm btn-light" title="Italic" data-cmd="italic"><i class="ti ti-italic"></i></button>' +
|
|
9
|
+
'<button type="button" class="btn btn-sm btn-light" title="Underline" data-cmd="underline"><i class="ti ti-underline"></i></button>' +
|
|
10
|
+
'<button type="button" class="btn btn-sm btn-light" title="Strikethrough" data-cmd="strikeThrough"><s>S</s></button>' +
|
|
11
|
+
'<div class="te-divider"></div>' +
|
|
12
|
+
'<button type="button" class="btn btn-sm btn-light" title="Quote" data-cmd="formatBlock" data-val="blockquote"><strong>"</strong></button>',
|
|
13
|
+
init(ctx: PluginContext): void {
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default inline;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
import * as H from '../core/helpers';
|
|
3
|
+
|
|
4
|
+
const inlineCode: Plugin = {
|
|
5
|
+
name: 'inlineCode',
|
|
6
|
+
order: 22,
|
|
7
|
+
toolbarHTML: '<button type="button" class="btn btn-sm btn-light" title="Inline code" data-cmd="inlineCode"><i class="ti ti-code-circle"></i></button>',
|
|
8
|
+
init(ctx: PluginContext): void {
|
|
9
|
+
if (!ctx.features.inlineCode) return;
|
|
10
|
+
var btn = ctx.wrapper.querySelector('[data-cmd="inlineCode"]') as HTMLElement | null;
|
|
11
|
+
if (!btn) return;
|
|
12
|
+
btn.addEventListener('click', function () {
|
|
13
|
+
var txt = H.escapeHtml(window.getSelection()!.toString() || 'code');
|
|
14
|
+
var html = '<code>' + txt + '</code>';
|
|
15
|
+
document.execCommand('insertHTML', false, html);
|
|
16
|
+
ctx.editor.focus();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default inlineCode;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Plugin, PluginContext, TeEditorElement } from '../types';
|
|
2
|
+
|
|
3
|
+
var STORAGE_KEY = 'te-shortcut-map';
|
|
4
|
+
var DEFAULT_MAP = {
|
|
5
|
+
'Bold': {key:'b', ctrl:true, shift:false},
|
|
6
|
+
'Italic': {key:'i', ctrl:true, shift:false},
|
|
7
|
+
'Underline': {key:'u', ctrl:true, shift:false},
|
|
8
|
+
'Strike': {key:'s', ctrl:true, shift:false},
|
|
9
|
+
'Undo': {key:'z', ctrl:true, shift:false},
|
|
10
|
+
'Redo': {key:'z', ctrl:true, shift:true},
|
|
11
|
+
'RedoAlt': {key:'y', ctrl:true, shift:false},
|
|
12
|
+
'Link': {key:'k', ctrl:true, shift:false}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function loadMap(){
|
|
16
|
+
try {
|
|
17
|
+
var raw = localStorage.getItem(STORAGE_KEY);
|
|
18
|
+
if(raw) return JSON.parse(raw);
|
|
19
|
+
} catch(_){}
|
|
20
|
+
return JSON.parse(JSON.stringify(DEFAULT_MAP));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const keyboardShortcuts: Plugin = {
|
|
24
|
+
name: 'keyboardShortcuts',
|
|
25
|
+
order: 5,
|
|
26
|
+
init(ctx: PluginContext): void {
|
|
27
|
+
if(ctx.features.keyboardShortcuts === false) return;
|
|
28
|
+
var editor = ctx.editor as TeEditorElement;
|
|
29
|
+
var map = loadMap();
|
|
30
|
+
|
|
31
|
+
editor.addEventListener('keydown', function(e: KeyboardEvent){
|
|
32
|
+
if(!e.ctrlKey && !e.metaKey) return;
|
|
33
|
+
for(var name in map){
|
|
34
|
+
var s = map[name];
|
|
35
|
+
if(e.key.toLowerCase() === s.key && e.ctrlKey === s.ctrl && e.shiftKey === s.shift){
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
if(name === 'Bold'){ document.execCommand('bold'); }
|
|
38
|
+
else if(name === 'Italic'){ document.execCommand('italic'); }
|
|
39
|
+
else if(name === 'Underline'){ document.execCommand('underline'); }
|
|
40
|
+
else if(name === 'Strike'){ document.execCommand('strikeThrough'); }
|
|
41
|
+
else if(name === 'Undo'){
|
|
42
|
+
var h = editor._tableHist;
|
|
43
|
+
if(h && h.undo.length){
|
|
44
|
+
editor.querySelectorAll('.te-fr-match, .te-fr-active').forEach(function(el){
|
|
45
|
+
var p = el.parentNode; if(!p) return;
|
|
46
|
+
while(el.firstChild) p.insertBefore(el.firstChild, el);
|
|
47
|
+
p.removeChild(el);
|
|
48
|
+
});
|
|
49
|
+
h.redo.push(editor.innerHTML);
|
|
50
|
+
editor.contentEditable = 'false';
|
|
51
|
+
editor.innerHTML = h.undo.pop()!;
|
|
52
|
+
editor.contentEditable = 'true';
|
|
53
|
+
editor.querySelectorAll('.te-fr-match, .te-fr-active').forEach(function(el){
|
|
54
|
+
var p = el.parentNode; if(!p) return;
|
|
55
|
+
while(el.firstChild) p.insertBefore(el.firstChild, el);
|
|
56
|
+
p.removeChild(el);
|
|
57
|
+
});
|
|
58
|
+
editor.focus();
|
|
59
|
+
} else { document.execCommand('undo'); editor.focus(); }
|
|
60
|
+
}
|
|
61
|
+
else if(name === 'Redo' || name === 'RedoAlt'){
|
|
62
|
+
var h = editor._tableHist;
|
|
63
|
+
if(h && h.redo.length){
|
|
64
|
+
editor.querySelectorAll('.te-fr-match, .te-fr-active').forEach(function(el){
|
|
65
|
+
var p = el.parentNode; if(!p) return;
|
|
66
|
+
while(el.firstChild) p.insertBefore(el.firstChild, el);
|
|
67
|
+
p.removeChild(el);
|
|
68
|
+
});
|
|
69
|
+
h.undo.push(editor.innerHTML);
|
|
70
|
+
editor.contentEditable = 'false';
|
|
71
|
+
editor.innerHTML = h.redo.pop()!;
|
|
72
|
+
editor.contentEditable = 'true';
|
|
73
|
+
editor.querySelectorAll('.te-fr-match, .te-fr-active').forEach(function(el){
|
|
74
|
+
var p = el.parentNode; if(!p) return;
|
|
75
|
+
while(el.firstChild) p.insertBefore(el.firstChild, el);
|
|
76
|
+
p.removeChild(el);
|
|
77
|
+
});
|
|
78
|
+
editor.focus();
|
|
79
|
+
} else { document.execCommand('redo'); editor.focus(); }
|
|
80
|
+
}
|
|
81
|
+
else if(name === 'Link'){
|
|
82
|
+
var linkBtn = ctx.wrapper.querySelector('[data-cmd="createLink"]') as HTMLButtonElement | null;
|
|
83
|
+
if(linkBtn) linkBtn.click();
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default keyboardShortcuts;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const lineHeight: Plugin = {
|
|
4
|
+
name: 'lineHeight',
|
|
5
|
+
order: 26,
|
|
6
|
+
css: '' +
|
|
7
|
+
'.te-lh-select { width: auto; }',
|
|
8
|
+
toolbarHTML: '' +
|
|
9
|
+
'<select class="te-lh form-select form-select-sm te-lh-select" title="Line height">' +
|
|
10
|
+
' <option value="" disabled selected>Line Height</option>' +
|
|
11
|
+
' <option value="1">1</option>' +
|
|
12
|
+
' <option value="1.15">1.15</option>' +
|
|
13
|
+
' <option value="1.5">1.5</option>' +
|
|
14
|
+
' <option value="1.8">1.8</option>' +
|
|
15
|
+
' <option value="2">2</option>' +
|
|
16
|
+
' <option value="2.5">2.5</option>' +
|
|
17
|
+
' <option value="3">3</option>' +
|
|
18
|
+
'</select>',
|
|
19
|
+
init(ctx: PluginContext): void {
|
|
20
|
+
if(!ctx.features.lineHeight) return;
|
|
21
|
+
var sel = ctx.wrapper.querySelector('.te-lh') as HTMLSelectElement | null;
|
|
22
|
+
if(!sel) return;
|
|
23
|
+
sel.addEventListener('change', function(){
|
|
24
|
+
var v = sel!.value;
|
|
25
|
+
if(!v) return;
|
|
26
|
+
document.execCommand('formatBlock', false, 'p');
|
|
27
|
+
var s = window.getSelection();
|
|
28
|
+
if(!s || !s.rangeCount) return;
|
|
29
|
+
var node: Node | null = s.anchorNode;
|
|
30
|
+
if(node && node.nodeType === 3) node = node.parentNode;
|
|
31
|
+
var block = node && (node as Element).closest && (node as Element).closest('p,h1,h2,h3,h4,h5,h6,div,li,blockquote');
|
|
32
|
+
if(block && ctx.wrapper.contains(block)){
|
|
33
|
+
(block as HTMLElement).style.lineHeight = v;
|
|
34
|
+
}
|
|
35
|
+
ctx.editor.focus();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default lineHeight;
|