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,166 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
var MAX_COLS = 10;
|
|
4
|
+
var MAX_ROWS = 10;
|
|
5
|
+
|
|
6
|
+
const table: Plugin = {
|
|
7
|
+
name: 'table',
|
|
8
|
+
order: 54,
|
|
9
|
+
css: '' +
|
|
10
|
+
'.te-table-grid {' +
|
|
11
|
+
' position: absolute; z-index: 3000;' +
|
|
12
|
+
' background: #fff; border: 1px solid #dee2e6;' +
|
|
13
|
+
' border-radius: .5rem; box-shadow: 0 6px 20px rgba(0,0,0,.15);' +
|
|
14
|
+
' padding: .75rem;' +
|
|
15
|
+
'}' +
|
|
16
|
+
'.te-table-grid .te-tg-label {' +
|
|
17
|
+
' font-size: .8rem; color: #6c757d; text-align: center;' +
|
|
18
|
+
' margin-bottom: .5rem;' +
|
|
19
|
+
'}' +
|
|
20
|
+
'.te-table-grid .te-tg-cells {' +
|
|
21
|
+
' display: grid; gap: 2px;' +
|
|
22
|
+
'}' +
|
|
23
|
+
'.te-table-grid .te-tg-cell {' +
|
|
24
|
+
' width: 18px; height: 18px;' +
|
|
25
|
+
' background: #f0f0f0; border: 1px solid #dee2e6;' +
|
|
26
|
+
' border-radius: 2px; cursor: pointer;' +
|
|
27
|
+
'}' +
|
|
28
|
+
'.te-table-grid .te-tg-cell.active {' +
|
|
29
|
+
' background: #86b7fe; border-color: #0d6efd;' +
|
|
30
|
+
'}' +
|
|
31
|
+
'.te-tg-header {' +
|
|
32
|
+
' display: flex; justify-content: space-between; align-items: center;' +
|
|
33
|
+
' margin-bottom: .5rem;' +
|
|
34
|
+
'}' +
|
|
35
|
+
'.te-toolbar [data-cmd="insertTable"] svg { pointer-events: none; }',
|
|
36
|
+
toolbarHTML: '<button type="button" class="btn btn-sm btn-light" title="Table" data-cmd="insertTable"><i class="ti ti-table-plus"></i></button>',
|
|
37
|
+
modalHTML: '' +
|
|
38
|
+
'<div class="te-table-props-modal te-modal" aria-hidden="true">' +
|
|
39
|
+
' <div class="te-modal-backdrop" data-te-close></div>' +
|
|
40
|
+
' <div class="te-modal-dialog">' +
|
|
41
|
+
' <div class="te-modal-header">' +
|
|
42
|
+
' <h5 class="te-modal-title m-0">Table Properties</h5>' +
|
|
43
|
+
' <button type="button" class="btn-close" data-te-close aria-label="Close"></button>' +
|
|
44
|
+
' </div>' +
|
|
45
|
+
' <form class="te-table-props-form">' +
|
|
46
|
+
' <div class="te-modal-body">' +
|
|
47
|
+
' <div class="mb-3">' +
|
|
48
|
+
' <label class="form-label">Width (e.g. 100% or 800px)</label>' +
|
|
49
|
+
' <input type="text" class="te-table-props-width form-control" placeholder="100%">' +
|
|
50
|
+
' </div>' +
|
|
51
|
+
' <div class="mb-3">' +
|
|
52
|
+
' <label class="form-label">Caption</label>' +
|
|
53
|
+
' <input type="text" class="te-table-props-caption form-control" placeholder="Optional caption">' +
|
|
54
|
+
' </div>' +
|
|
55
|
+
' </div>' +
|
|
56
|
+
' <div class="te-modal-footer">' +
|
|
57
|
+
' <button type="button" class="btn btn-outline-secondary" data-te-close>Cancel</button>' +
|
|
58
|
+
' <button type="submit" class="btn btn-primary">Apply</button>' +
|
|
59
|
+
' </div>' +
|
|
60
|
+
' </form>' +
|
|
61
|
+
' </div>' +
|
|
62
|
+
'</div>',
|
|
63
|
+
init(ctx: PluginContext): void {
|
|
64
|
+
if(!ctx.features.table) return;
|
|
65
|
+
|
|
66
|
+
var popup = document.createElement('div');
|
|
67
|
+
popup.className = 'te-table-grid';
|
|
68
|
+
popup.style.display = 'none';
|
|
69
|
+
popup.innerHTML = '' +
|
|
70
|
+
'<div class="te-tg-header">' +
|
|
71
|
+
'<span class="te-tg-label">0 × 0</span>' +
|
|
72
|
+
'<button type="button" class="btn-close te-tg-close" aria-label="Close"></button>' +
|
|
73
|
+
'</div>' +
|
|
74
|
+
'<div class="te-tg-cells" style="grid-template-columns: repeat(' + MAX_COLS + ', 18px);"></div>';
|
|
75
|
+
var cellsContainer = popup.querySelector('.te-tg-cells') as HTMLElement;
|
|
76
|
+
var label = popup.querySelector('.te-tg-label') as HTMLElement;
|
|
77
|
+
|
|
78
|
+
for(var r = 0; r < MAX_ROWS; r++){
|
|
79
|
+
for(var c = 0; c < MAX_COLS; c++){
|
|
80
|
+
var cell = document.createElement('div');
|
|
81
|
+
cell.className = 'te-tg-cell';
|
|
82
|
+
cell.setAttribute('data-r', r.toString());
|
|
83
|
+
cell.setAttribute('data-c', c.toString());
|
|
84
|
+
cellsContainer.appendChild(cell);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function clearActive(){
|
|
89
|
+
popup.querySelectorAll('.te-tg-cell.active').forEach(function(el){ el.classList.remove('active'); });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function highlight(r: number, c: number){
|
|
93
|
+
clearActive();
|
|
94
|
+
popup.querySelectorAll('.te-tg-cell').forEach(function(el){
|
|
95
|
+
var er = parseInt(el.getAttribute('data-r')!, 10);
|
|
96
|
+
var ec = parseInt(el.getAttribute('data-c')!, 10);
|
|
97
|
+
if(er <= r && ec <= c) el.classList.add('active');
|
|
98
|
+
});
|
|
99
|
+
label.textContent = (c + 1) + ' × ' + (r + 1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
cellsContainer.addEventListener('mouseover', function(e: MouseEvent){
|
|
103
|
+
var cell = (e.target as HTMLElement).closest('.te-tg-cell');
|
|
104
|
+
if(!cell) return;
|
|
105
|
+
var r = parseInt(cell.getAttribute('data-r')!, 10);
|
|
106
|
+
var c = parseInt(cell.getAttribute('data-c')!, 10);
|
|
107
|
+
highlight(r, c);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
cellsContainer.addEventListener('mouseleave', function(){
|
|
111
|
+
clearActive();
|
|
112
|
+
label.textContent = '0 × 0';
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
function insertTable(cols: number, rows: number){
|
|
116
|
+
ctx.restoreSel();
|
|
117
|
+
var html = '<table class="te-table"><tbody>';
|
|
118
|
+
for(var rr = 0; rr < rows; rr++){
|
|
119
|
+
html += '<tr>';
|
|
120
|
+
for(var cc = 0; cc < cols; cc++){
|
|
121
|
+
html += '<td><br></td>';
|
|
122
|
+
}
|
|
123
|
+
html += '</tr>';
|
|
124
|
+
}
|
|
125
|
+
html += '</tbody></table>';
|
|
126
|
+
document.execCommand('insertHTML', false, html);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
cellsContainer.addEventListener('click', function(e: MouseEvent){
|
|
130
|
+
var cell = (e.target as HTMLElement).closest('.te-tg-cell');
|
|
131
|
+
if(!cell) return;
|
|
132
|
+
var cols = parseInt(cell.getAttribute('data-c')!, 10) + 1;
|
|
133
|
+
var rows = parseInt(cell.getAttribute('data-r')!, 10) + 1;
|
|
134
|
+
popup.style.display = 'none';
|
|
135
|
+
insertTable(cols, rows);
|
|
136
|
+
ctx.editor.focus();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
document.body.appendChild(popup);
|
|
140
|
+
|
|
141
|
+
(popup.querySelector('.te-tg-close') as HTMLElement).addEventListener('click', function(){
|
|
142
|
+
popup.style.display = 'none';
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
var btn = ctx.wrapper.querySelector('[data-cmd="insertTable"]') as HTMLElement | null;
|
|
146
|
+
if(btn){
|
|
147
|
+
btn.addEventListener('click', function(e: MouseEvent){
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
ctx.saveSel();
|
|
150
|
+
var rect = btn!.getBoundingClientRect();
|
|
151
|
+
popup.style.left = (window.scrollX + rect.left) + 'px';
|
|
152
|
+
popup.style.top = (window.scrollY + rect.bottom + 4) + 'px';
|
|
153
|
+
popup.style.display = popup.style.display === 'none' ? 'block' : 'none';
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
document.addEventListener('click', function(e: MouseEvent){
|
|
158
|
+
if(popup.style.display === 'none') return;
|
|
159
|
+
if(!popup.contains(e.target as Node) && e.target !== btn){
|
|
160
|
+
popup.style.display = 'none';
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export default table;
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import type { Plugin, PluginContext, TeEditorElement } from '../types';
|
|
2
|
+
|
|
3
|
+
const tableTools: Plugin = {
|
|
4
|
+
name: 'tableTools',
|
|
5
|
+
order: 55,
|
|
6
|
+
deps: ['table'],
|
|
7
|
+
css: '' +
|
|
8
|
+
'.te-table-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: .2rem .3rem; gap: .1rem; align-items: center;' +
|
|
13
|
+
' white-space: nowrap;' +
|
|
14
|
+
'}' +
|
|
15
|
+
'.te-table-tools .btn {' +
|
|
16
|
+
' padding: .15rem .35rem !important; font-size: .7rem !important; line-height: 1 !important;' +
|
|
17
|
+
'}' +
|
|
18
|
+
'.te-table-tools .btn svg {' +
|
|
19
|
+
' width: 12px !important; height: 12px !important;' +
|
|
20
|
+
' margin-right: 2px; vertical-align: middle;' +
|
|
21
|
+
'}' +
|
|
22
|
+
'.te-table-tools .te-divider-v {' +
|
|
23
|
+
' display: inline-block; width: 1px; height: 16px;' +
|
|
24
|
+
' background: #dee2e6; margin: 0 .2rem;' +
|
|
25
|
+
'}' +
|
|
26
|
+
'.te-table-tools .tt-label {' +
|
|
27
|
+
' font-size: .6rem; color: #6c757d; text-transform: uppercase;' +
|
|
28
|
+
' letter-spacing: .5px; margin: 0 .15rem;' +
|
|
29
|
+
'}' +
|
|
30
|
+
'.te-table-tools .te-bg-input {' +
|
|
31
|
+
' width: 26px !important; height: 20px !important;' +
|
|
32
|
+
' padding: 1px !important; border: 1px solid #dee2e6;' +
|
|
33
|
+
' border-radius: 3px; cursor: pointer;' +
|
|
34
|
+
'}' +
|
|
35
|
+
'.te-cell-selected {' +
|
|
36
|
+
' outline: 2px solid #0d6efd !important;' +
|
|
37
|
+
' outline-offset: -1px;' +
|
|
38
|
+
'}',
|
|
39
|
+
init(ctx: PluginContext): void {
|
|
40
|
+
if(!ctx.features.table || !ctx.features.tableTools) return;
|
|
41
|
+
|
|
42
|
+
var wrapper = ctx.wrapper;
|
|
43
|
+
var editor = ctx.editor;
|
|
44
|
+
var ed = ctx.editor as TeEditorElement;
|
|
45
|
+
var tools = document.createElement('div');
|
|
46
|
+
tools.className = 'te-table-tools';
|
|
47
|
+
tools.style.display = 'none';
|
|
48
|
+
tools.innerHTML = '' +
|
|
49
|
+
'<span class="tt-label">Add</span>' +
|
|
50
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="row-before" title="Row above"><i class="ti ti-chevron-up"></i> Row</button>' +
|
|
51
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="row-after" title="Row below"><i class="ti ti-chevron-down"></i> Row</button>' +
|
|
52
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="col-before" title="Column before"><i class="ti ti-chevron-left"></i> Col</button>' +
|
|
53
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="col-after" title="Column after"><i class="ti ti-chevron-right"></i> Col</button>' +
|
|
54
|
+
'<span class="te-divider-v"></span>' +
|
|
55
|
+
'<span class="tt-label">Del</span>' +
|
|
56
|
+
'<button type="button" class="btn btn-sm btn-outline-danger" data-te-table="del-row" title="Delete row"><i class="ti ti-x"></i> Row</button>' +
|
|
57
|
+
'<button type="button" class="btn btn-sm btn-outline-danger" data-te-table="del-col" title="Delete column"><i class="ti ti-x"></i> Col</button>' +
|
|
58
|
+
'<button type="button" class="btn btn-sm btn-outline-danger" data-te-table="del-table" title="Delete table"><i class="ti ti-trash"></i> Tbl</button>' +
|
|
59
|
+
'<span class="te-divider-v"></span>' +
|
|
60
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="merge" title="Merge selected cells"><i class="ti ti-maximize"></i> Merge</button>' +
|
|
61
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="unmerge" title="Unmerge cells" style="display:none"><i class="ti ti-minimize"></i> Unmerge</button>' +
|
|
62
|
+
'<span class="te-divider-v"></span>' +
|
|
63
|
+
'<button type="button" class="btn btn-sm btn-outline-secondary" data-te-table="toggle-header" title="Toggle header"><i class="ti ti-typography"></i> Hdr</button>' +
|
|
64
|
+
'<button type="button" class="btn btn-sm btn-outline-secondary" data-te-table="toggle-caption" title="Toggle caption"><i class="ti ti-align-left"></i> Cap</button>' +
|
|
65
|
+
'<span class="te-divider-v"></span>' +
|
|
66
|
+
'<span class="tt-label">BG</span>' +
|
|
67
|
+
'<input type="color" class="te-bg-input form-control form-control-color" value="#ffffff" title="Cell background color" />' +
|
|
68
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="bg-apply" title="Apply background color">Apply</button>' +
|
|
69
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="bg-clear" title="Remove background color">Clear</button>' +
|
|
70
|
+
'<span class="te-divider-v"></span>' +
|
|
71
|
+
'<span class="tt-label">V</span>' +
|
|
72
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="valign-top" title="Align top">Top</button>' +
|
|
73
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="valign-middle" title="Align middle">Mid</button>' +
|
|
74
|
+
'<button type="button" class="btn btn-sm btn-light" data-te-table="valign-bottom" title="Align bottom">Bot</button>';
|
|
75
|
+
document.body.appendChild(tools);
|
|
76
|
+
|
|
77
|
+
var currentCell: HTMLElement | null = null, currentTable: HTMLElement | null = null;
|
|
78
|
+
var lastCell: HTMLElement | null = null, lastTable: HTMLElement | null = null;
|
|
79
|
+
var selectedCells: HTMLElement[] = [];
|
|
80
|
+
|
|
81
|
+
function saveHist(){
|
|
82
|
+
if(!ed._tableHist) ed._tableHist = {undo:[], redo:[]};
|
|
83
|
+
ed._tableHist.undo.push(editor.innerHTML);
|
|
84
|
+
ed._tableHist.redo = [];
|
|
85
|
+
if(ed._tableHist.undo.length > 50) ed._tableHist.undo.shift();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function updateMergeBtn(){
|
|
89
|
+
var btn = tools.querySelector('[data-te-table="merge"]') as HTMLElement | null;
|
|
90
|
+
if(!btn) return;
|
|
91
|
+
btn.style.display = (selectedCells.length >= 2) ? '' : 'none';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function clearHighlights(){
|
|
95
|
+
selectedCells.forEach(function(c){ c.classList.remove('te-cell-selected'); });
|
|
96
|
+
selectedCells = [];
|
|
97
|
+
updateMergeBtn();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toggleHighlight(cell: HTMLElement){
|
|
101
|
+
var idx = selectedCells.indexOf(cell);
|
|
102
|
+
if(idx !== -1){
|
|
103
|
+
selectedCells.splice(idx, 1);
|
|
104
|
+
cell.classList.remove('te-cell-selected');
|
|
105
|
+
} else {
|
|
106
|
+
selectedCells.push(cell);
|
|
107
|
+
cell.classList.add('te-cell-selected');
|
|
108
|
+
}
|
|
109
|
+
updateMergeBtn();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
editor.addEventListener('click', function(e: MouseEvent){
|
|
113
|
+
var cell = (e.target as HTMLElement).closest && (e.target as HTMLElement).closest('td,th') as HTMLElement | null;
|
|
114
|
+
if(!cell || !editor.contains(cell)) return;
|
|
115
|
+
if(e.ctrlKey || e.metaKey){
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
toggleHighlight(cell);
|
|
118
|
+
} else if(!e.shiftKey){
|
|
119
|
+
clearHighlights();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
function updateUnmergeBtn(cell: HTMLElement){
|
|
124
|
+
var btn = tools.querySelector('[data-te-table="unmerge"]') as HTMLElement | null;
|
|
125
|
+
if(!btn) return;
|
|
126
|
+
var cs = parseInt(cell.getAttribute('colspan')!, 10) || 1;
|
|
127
|
+
var rs = parseInt(cell.getAttribute('rowspan')!, 10) || 1;
|
|
128
|
+
btn.style.display = (cs > 1 || rs > 1) ? '' : 'none';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function updateValignBtns(cell: HTMLElement){
|
|
132
|
+
tools.querySelectorAll('[data-te-table^="valign-"]').forEach(function(b){
|
|
133
|
+
var align = b.getAttribute('data-te-table')!.replace('valign-', '');
|
|
134
|
+
var cellAlign = ((cell as HTMLElement).style.verticalAlign || '').toLowerCase();
|
|
135
|
+
b.classList.toggle('active', cellAlign === align);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function posTools(cell: HTMLElement){
|
|
140
|
+
var rect = cell.getBoundingClientRect();
|
|
141
|
+
tools.style.display = 'flex';
|
|
142
|
+
tools.style.visibility = 'hidden';
|
|
143
|
+
var toolsW = tools.offsetWidth;
|
|
144
|
+
var toolsH = tools.offsetHeight;
|
|
145
|
+
var editorRect = editor.getBoundingClientRect();
|
|
146
|
+
var sx = window.scrollX, sy = window.scrollY;
|
|
147
|
+
|
|
148
|
+
var leftPos = rect.left + sx;
|
|
149
|
+
var minLeft = editorRect.left + sx + 2;
|
|
150
|
+
var maxLeft = editorRect.right + sx - toolsW - 2;
|
|
151
|
+
if(leftPos < minLeft) leftPos = minLeft;
|
|
152
|
+
if(leftPos > maxLeft) leftPos = maxLeft;
|
|
153
|
+
tools.style.left = leftPos + 'px';
|
|
154
|
+
|
|
155
|
+
var spaceBelow = editorRect.bottom - (rect.bottom + 6);
|
|
156
|
+
var spaceAbove = rect.top - 6 - editorRect.top;
|
|
157
|
+
var topPos;
|
|
158
|
+
if(spaceBelow >= toolsH || spaceBelow >= spaceAbove){
|
|
159
|
+
topPos = rect.bottom + 6 + sy;
|
|
160
|
+
} else {
|
|
161
|
+
topPos = rect.top - 6 - toolsH + sy;
|
|
162
|
+
}
|
|
163
|
+
var minTop = editorRect.top + sy + 2;
|
|
164
|
+
var maxTop = editorRect.bottom + sy - toolsH - 2;
|
|
165
|
+
if(topPos < minTop) topPos = minTop;
|
|
166
|
+
if(topPos > maxTop) topPos = maxTop;
|
|
167
|
+
tools.style.top = topPos + 'px';
|
|
168
|
+
|
|
169
|
+
tools.style.visibility = '';
|
|
170
|
+
updateMergeBtn();
|
|
171
|
+
updateUnmergeBtn(cell);
|
|
172
|
+
updateValignBtns(cell);
|
|
173
|
+
var capBtn = tools.querySelector('[data-te-table="toggle-caption"]') as HTMLElement | null;
|
|
174
|
+
if(capBtn && currentTable) capBtn.classList.toggle('active', !!currentTable.querySelector('caption'));
|
|
175
|
+
}
|
|
176
|
+
function hideTools(){
|
|
177
|
+
tools.style.display = 'none';
|
|
178
|
+
currentCell = null;
|
|
179
|
+
currentTable = null;
|
|
180
|
+
}
|
|
181
|
+
function updateFromSelection(){
|
|
182
|
+
var sel = window.getSelection();
|
|
183
|
+
if(!sel || sel.rangeCount === 0){ hideTools(); return; }
|
|
184
|
+
var n: Node | null = sel.anchorNode;
|
|
185
|
+
if(n && n.nodeType === 3) n = n.parentNode;
|
|
186
|
+
if(!n || !wrapper.contains(n)){ hideTools(); return; }
|
|
187
|
+
var cell = (n as HTMLElement).closest && (n as HTMLElement).closest('td,th') as HTMLElement | null;
|
|
188
|
+
if(cell && editor.contains(cell)){
|
|
189
|
+
currentCell = cell;
|
|
190
|
+
currentTable = cell.closest('table') as HTMLElement | null;
|
|
191
|
+
lastCell = currentCell;
|
|
192
|
+
lastTable = currentTable;
|
|
193
|
+
posTools(cell);
|
|
194
|
+
} else {
|
|
195
|
+
hideTools();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getSelectedCellsForTools(): HTMLElement[] | null {
|
|
200
|
+
var h = tools.querySelectorAll('.te-cell-selected');
|
|
201
|
+
if(h.length) return Array.prototype.slice.call(h);
|
|
202
|
+
var s = window.getSelection();
|
|
203
|
+
if(!s || s.rangeCount === 0) return null;
|
|
204
|
+
var r = s.getRangeAt(0);
|
|
205
|
+
var tbl = currentTable || lastTable;
|
|
206
|
+
if(!tbl) return null;
|
|
207
|
+
var cells: HTMLElement[] = [];
|
|
208
|
+
tbl.querySelectorAll('td,th').forEach(function(c){
|
|
209
|
+
if(r.intersectsNode(c)) cells.push(c as HTMLElement);
|
|
210
|
+
});
|
|
211
|
+
return cells.length ? cells : null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function applyBgColor(value: string){
|
|
215
|
+
var cells = getSelectedCellsForTools();
|
|
216
|
+
if(!cells) return;
|
|
217
|
+
for(var i = 0; i < cells.length; i++){
|
|
218
|
+
cells[i].style.backgroundColor = value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function clearBgColor(){
|
|
223
|
+
var cells = getSelectedCellsForTools();
|
|
224
|
+
if(!cells) return;
|
|
225
|
+
for(var i = 0; i < cells.length; i++){
|
|
226
|
+
cells[i].style.backgroundColor = '';
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
var bgInput = tools.querySelector('.te-bg-input') as HTMLInputElement | null;
|
|
231
|
+
if(bgInput){
|
|
232
|
+
bgInput.addEventListener('change', function(){
|
|
233
|
+
if(currentCell || lastCell) applyBgColor(bgInput!.value);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
document.addEventListener('selectionchange', updateFromSelection);
|
|
238
|
+
window.addEventListener('scroll', function(){ if(currentCell) posTools(currentCell!); });
|
|
239
|
+
window.addEventListener('resize', function(){ if(currentCell) posTools(currentCell!); });
|
|
240
|
+
|
|
241
|
+
function getCellIndex(cell: HTMLElement){
|
|
242
|
+
var idx = 0, node: HTMLElement | null = cell;
|
|
243
|
+
while(node && node.previousElementSibling){ idx++; node = node.previousElementSibling as HTMLElement; }
|
|
244
|
+
return idx;
|
|
245
|
+
}
|
|
246
|
+
function forEachRow(table: HTMLElement, cb: (row: HTMLTableRowElement, sec: string) => void){
|
|
247
|
+
['thead','tbody'].forEach(function(sec){
|
|
248
|
+
var s = table.querySelector(sec);
|
|
249
|
+
if(s){
|
|
250
|
+
Array.prototype.forEach.call((s as HTMLTableSectionElement).rows, function(row){ cb(row, sec); });
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
var refocus: ReturnType<typeof setTimeout> | null;
|
|
256
|
+
|
|
257
|
+
function scheduleRefocus(){
|
|
258
|
+
if(refocus) clearTimeout(refocus);
|
|
259
|
+
refocus = setTimeout(function(){
|
|
260
|
+
ctx.editor.focus();
|
|
261
|
+
updateFromSelection();
|
|
262
|
+
refocus = null;
|
|
263
|
+
}, 0);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function mergeCells(){
|
|
267
|
+
var table = currentTable || lastTable;
|
|
268
|
+
if(!table) return;
|
|
269
|
+
var rows = Array.prototype.slice.call(table.querySelectorAll('tr')) as HTMLTableRowElement[];
|
|
270
|
+
if(!rows.length) return;
|
|
271
|
+
|
|
272
|
+
var cells: HTMLElement[];
|
|
273
|
+
if(selectedCells.length > 1){
|
|
274
|
+
cells = selectedCells.slice();
|
|
275
|
+
} else {
|
|
276
|
+
var sel = window.getSelection();
|
|
277
|
+
if(!sel || sel.rangeCount === 0) return;
|
|
278
|
+
var range = sel.getRangeAt(0);
|
|
279
|
+
cells = [];
|
|
280
|
+
table.querySelectorAll('td,th').forEach(function(cell){
|
|
281
|
+
if(range.intersectsNode(cell)) cells.push(cell as HTMLElement);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if(cells.length < 2) return;
|
|
285
|
+
saveHist();
|
|
286
|
+
|
|
287
|
+
var positions = cells.map(function(cell){
|
|
288
|
+
return {
|
|
289
|
+
cell: cell,
|
|
290
|
+
row: rows.indexOf(cell.parentElement as HTMLTableRowElement),
|
|
291
|
+
col: getCellIndex(cell)
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
var minRow = Math.min.apply(null, positions.map(function(p){ return p.row; }));
|
|
296
|
+
var maxRow = Math.max.apply(null, positions.map(function(p){ return p.row; }));
|
|
297
|
+
var minCol = Math.min.apply(null, positions.map(function(p){ return p.col; }));
|
|
298
|
+
var maxCol = Math.max.apply(null, positions.map(function(p){ return p.col; }));
|
|
299
|
+
|
|
300
|
+
if(minRow !== maxRow && minCol !== maxCol){
|
|
301
|
+
var mergeBtn = tools.querySelector('[data-te-table="merge"]') as HTMLElement | null;
|
|
302
|
+
if(mergeBtn){
|
|
303
|
+
mergeBtn.style.outline = '2px solid #dc3545';
|
|
304
|
+
mergeBtn.style.outlineOffset = '1px';
|
|
305
|
+
setTimeout(function(){ mergeBtn!.style.outline = ''; mergeBtn!.style.outlineOffset = ''; }, 600);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
var contents = cells.map(function(c){ return c.innerHTML; });
|
|
311
|
+
var kept = positions.filter(function(p){ return p.row === minRow && p.col === minCol; })[0];
|
|
312
|
+
if(!kept) kept = positions[0];
|
|
313
|
+
|
|
314
|
+
cells.forEach(function(cell){
|
|
315
|
+
if(cell !== kept.cell) cell.parentNode!.removeChild(cell);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
kept.cell.setAttribute('colspan', String(maxCol - minCol + 1));
|
|
319
|
+
kept.cell.setAttribute('rowspan', String(maxRow - minRow + 1));
|
|
320
|
+
kept.cell.innerHTML = contents.join('');
|
|
321
|
+
clearHighlights();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function unmergeCell(cell: HTMLElement){
|
|
325
|
+
var colspan = parseInt(cell.getAttribute('colspan')!, 10) || 1;
|
|
326
|
+
var rowspan = parseInt(cell.getAttribute('rowspan')!, 10) || 1;
|
|
327
|
+
if(colspan === 1 && rowspan === 1) return;
|
|
328
|
+
|
|
329
|
+
var tag = cell.tagName;
|
|
330
|
+
var table = cell.closest('table') as HTMLElement;
|
|
331
|
+
var rows = Array.prototype.slice.call(table.querySelectorAll('tr')) as HTMLTableRowElement[];
|
|
332
|
+
var row = cell.parentElement as HTMLTableRowElement;
|
|
333
|
+
var rowIdx = rows.indexOf(row);
|
|
334
|
+
var colIdx = getCellIndex(cell);
|
|
335
|
+
|
|
336
|
+
cell.removeAttribute('colspan');
|
|
337
|
+
cell.removeAttribute('rowspan');
|
|
338
|
+
cell.innerHTML = '<br>';
|
|
339
|
+
|
|
340
|
+
var ref = cell.nextSibling;
|
|
341
|
+
for(var c = 1; c < colspan; c++){
|
|
342
|
+
var nc = document.createElement(tag);
|
|
343
|
+
nc.innerHTML = '<br>';
|
|
344
|
+
row.insertBefore(nc, ref);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for(var r = 1; r < rowspan; r++){
|
|
348
|
+
var tr = rows[rowIdx + r];
|
|
349
|
+
if(!tr) break;
|
|
350
|
+
var insBefore = tr.cells[colIdx] || null;
|
|
351
|
+
for(var c = 0; c < colspan; c++){
|
|
352
|
+
var nc = document.createElement(tag);
|
|
353
|
+
nc.innerHTML = '<br>';
|
|
354
|
+
tr.insertBefore(nc, insBefore);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
tools.addEventListener('mousedown', function(e: MouseEvent){
|
|
360
|
+
var actionBtn = e.target && (e.target as HTMLElement).closest('[data-te-table]');
|
|
361
|
+
var action = actionBtn && (actionBtn as HTMLElement).getAttribute('data-te-table');
|
|
362
|
+
if(!action) return;
|
|
363
|
+
e.preventDefault();
|
|
364
|
+
var cell = currentCell || lastCell;
|
|
365
|
+
var table = currentTable || lastTable;
|
|
366
|
+
if(!cell || !table) return;
|
|
367
|
+
saveHist();
|
|
368
|
+
var row = cell.parentElement as HTMLTableRowElement;
|
|
369
|
+
var colIdx = getCellIndex(cell);
|
|
370
|
+
|
|
371
|
+
if(action === 'row-before' || action === 'row-after'){
|
|
372
|
+
var newRow = row.cloneNode(true) as HTMLTableRowElement;
|
|
373
|
+
Array.prototype.forEach.call(newRow.cells, function(td: HTMLTableCellElement){
|
|
374
|
+
td.innerHTML = '<br>';
|
|
375
|
+
td.removeAttribute('colspan');
|
|
376
|
+
td.removeAttribute('rowspan');
|
|
377
|
+
});
|
|
378
|
+
if(action === 'row-before'){ row.parentNode!.insertBefore(newRow, row); }
|
|
379
|
+
else { row.parentNode!.insertBefore(newRow, row.nextSibling); }
|
|
380
|
+
scheduleRefocus();
|
|
381
|
+
}
|
|
382
|
+
else if(action === 'col-before' || action === 'col-after'){
|
|
383
|
+
var after = (action === 'col-after');
|
|
384
|
+
forEachRow(table, function(r, sec){
|
|
385
|
+
var ref = r.cells[colIdx];
|
|
386
|
+
var newCell = document.createElement((sec === 'thead') ? 'th' : 'td');
|
|
387
|
+
newCell.innerHTML = '<br>';
|
|
388
|
+
if(after && ref && ref.nextSibling){ r.insertBefore(newCell, ref.nextSibling); }
|
|
389
|
+
else if(ref){ r.insertBefore(newCell, ref); }
|
|
390
|
+
else { r.appendChild(newCell); }
|
|
391
|
+
});
|
|
392
|
+
scheduleRefocus();
|
|
393
|
+
}
|
|
394
|
+
else if(action === 'del-row'){
|
|
395
|
+
row.parentNode!.removeChild(row);
|
|
396
|
+
if(!table.querySelector('tr')){ table.parentNode!.removeChild(table); hideTools(); }
|
|
397
|
+
else { scheduleRefocus(); }
|
|
398
|
+
}
|
|
399
|
+
else if(action === 'del-col'){
|
|
400
|
+
forEachRow(table, function(r){ if(r.cells[colIdx]) r.deleteCell(colIdx); });
|
|
401
|
+
var any = table.querySelector('tr') as HTMLTableRowElement | null;
|
|
402
|
+
if(any && any.cells.length === 0){ table.parentNode!.removeChild(table); hideTools(); }
|
|
403
|
+
else { scheduleRefocus(); }
|
|
404
|
+
}
|
|
405
|
+
else if(action === 'del-table'){ table.parentNode!.removeChild(table); hideTools(); }
|
|
406
|
+
else if(action === 'bg-apply'){ applyBgColor(bgInput ? bgInput.value : '#ffffff'); scheduleRefocus(); }
|
|
407
|
+
else if(action === 'bg-clear'){ clearBgColor(); scheduleRefocus(); }
|
|
408
|
+
else if(action === 'valign-top' || action === 'valign-middle' || action === 'valign-bottom'){
|
|
409
|
+
var cells = getSelectedCellsForTools();
|
|
410
|
+
if(!cells || !cells.length) cells = [cell];
|
|
411
|
+
var vAlign = action.replace('valign-', '');
|
|
412
|
+
for(var vi = 0; vi < cells.length; vi++){ cells[vi].style.verticalAlign = vAlign; }
|
|
413
|
+
scheduleRefocus();
|
|
414
|
+
}
|
|
415
|
+
else if(action === 'merge'){ mergeCells(); scheduleRefocus(); }
|
|
416
|
+
else if(action === 'unmerge'){ unmergeCell(cell); scheduleRefocus(); }
|
|
417
|
+
else if(action === 'toggle-header'){
|
|
418
|
+
var thead = table.querySelector('thead') as HTMLTableSectionElement | null;
|
|
419
|
+
if(thead){
|
|
420
|
+
var tbody = table.querySelector('tbody') as HTMLTableSectionElement | null || (function(){
|
|
421
|
+
var tb = document.createElement('tbody');
|
|
422
|
+
table.appendChild(tb);
|
|
423
|
+
return tb;
|
|
424
|
+
})();
|
|
425
|
+
Array.prototype.slice.call(thead.rows).forEach(function(hr: HTMLTableRowElement){
|
|
426
|
+
var tr = document.createElement('tr');
|
|
427
|
+
Array.prototype.forEach.call(hr.cells, function(hc: HTMLTableCellElement){
|
|
428
|
+
var td = document.createElement('td');
|
|
429
|
+
td.innerHTML = hc.innerHTML || '<br>';
|
|
430
|
+
tr.appendChild(td);
|
|
431
|
+
});
|
|
432
|
+
tbody.insertBefore(tr, tbody.firstChild);
|
|
433
|
+
});
|
|
434
|
+
thead.parentNode!.removeChild(thead);
|
|
435
|
+
} else {
|
|
436
|
+
var tb = table.querySelector('tbody') as HTMLTableSectionElement | null;
|
|
437
|
+
if(tb && tb.rows.length){
|
|
438
|
+
var first = tb.rows[0];
|
|
439
|
+
var newHead = document.createElement('thead');
|
|
440
|
+
var trh = document.createElement('tr');
|
|
441
|
+
Array.prototype.forEach.call(first.cells, function(td: HTMLTableCellElement){
|
|
442
|
+
var th = document.createElement('th');
|
|
443
|
+
th.innerHTML = td.innerHTML || '<br>';
|
|
444
|
+
trh.appendChild(th);
|
|
445
|
+
});
|
|
446
|
+
newHead.appendChild(trh);
|
|
447
|
+
table.insertBefore(newHead, tb);
|
|
448
|
+
tb.removeChild(first);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
scheduleRefocus();
|
|
452
|
+
}
|
|
453
|
+
else if(action === 'toggle-caption'){
|
|
454
|
+
var cap = table.querySelector('caption');
|
|
455
|
+
if(cap){
|
|
456
|
+
cap.parentNode!.removeChild(cap);
|
|
457
|
+
} else {
|
|
458
|
+
var caption = document.createElement('caption');
|
|
459
|
+
caption.innerHTML = '<br>';
|
|
460
|
+
caption.contentEditable = 'true';
|
|
461
|
+
caption.style.captionSide = 'bottom';
|
|
462
|
+
caption.style.textAlign = 'left';
|
|
463
|
+
caption.style.padding = '4px 6px';
|
|
464
|
+
caption.style.fontStyle = 'italic';
|
|
465
|
+
caption.style.color = '#6c757d';
|
|
466
|
+
table.insertBefore(caption, table.firstChild);
|
|
467
|
+
setTimeout(function(){ caption.focus(); }, 0);
|
|
468
|
+
}
|
|
469
|
+
scheduleRefocus();
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
export default tableTools;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '../types';
|
|
2
|
+
|
|
3
|
+
const toolbarCollapse: Plugin = {
|
|
4
|
+
name: 'toolbarCollapse',
|
|
5
|
+
order: 1000,
|
|
6
|
+
css: '' +
|
|
7
|
+
'.te-toolbar .btn svg, .te-toolbar .btn i {' +
|
|
8
|
+
' width: 14px !important; height: 14px !important;' +
|
|
9
|
+
' vertical-align: middle;' +
|
|
10
|
+
'}',
|
|
11
|
+
init(ctx: PluginContext): void {}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default toolbarCollapse;
|