@themarioga/grid-editor 8.10.1 → 9.0.0-beta.2
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/CHANGELOG.md +23 -0
- package/README.md +21 -3
- package/UPGRADING.md +19 -0
- package/dist/grideditor.bundle.min.js +2 -2
- package/dist/grideditor.d.ts +6 -4
- package/dist/grideditor.esm.js +43 -2
- package/dist/grideditor.js +43 -2
- package/dist/grideditor.min.js +1 -1
- package/dist/grideditor.min.js.map +3 -3
- package/dist/plugins/grideditor.clipboard.esm.js +116 -2
- package/dist/plugins/grideditor.clipboard.js +116 -2
- package/dist/plugins/grideditor.clipboard.min.js +1 -1
- package/dist/plugins/grideditor.clipboard.min.js.map +3 -3
- package/docs/events.md +1 -1
- package/docs/plugins.md +4 -0
- package/package.json +1 -1
- package/src/js/grideditor.js +32 -1
- package/src/js/plugins/grideditor.clipboard.js +125 -2
- package/types/grideditor.d.ts +6 -4
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
*
|
|
18
18
|
* A paste is an add like any other - before-add-* and after-add-*, with
|
|
19
19
|
* `source: 'paste'` - so a host can turn one away.
|
|
20
|
+
*
|
|
21
|
+
* With active_target (which the sidebar turns on), Ctrl+C copies the node
|
|
22
|
+
* selected, or the target, and Ctrl+V pastes into the target, or the node
|
|
23
|
+
* selected. `clipboard: { shortcuts: false }` turns them off. They go through
|
|
24
|
+
* the same store as the tools, never the system clipboard.
|
|
20
25
|
*/
|
|
21
26
|
import { GridEditor } from '../grideditor.js';
|
|
22
27
|
import * as dom from '../dom.js';
|
|
@@ -85,10 +90,41 @@ window.addEventListener('storage', function(e) {
|
|
|
85
90
|
if (e.key === STORAGE_ITEM) { announce(); }
|
|
86
91
|
});
|
|
87
92
|
|
|
93
|
+
/** The editors listening for the shortcuts, and the one used last, which a key press goes to. */
|
|
94
|
+
var listeningEditors = [];
|
|
95
|
+
var lastEditor = null;
|
|
96
|
+
|
|
97
|
+
function track(e) {
|
|
98
|
+
lastEditor = listeningEditors.filter(function(ge) { return ge.contains(e.target); })[0] || null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Captured: a control that stops the event still counts as using its editor
|
|
102
|
+
document.addEventListener('pointerdown', track, true);
|
|
103
|
+
document.addEventListener('focusin', track, true);
|
|
104
|
+
|
|
105
|
+
/** 'c', 'v' or null, from the letter typed or, on a layout without Latin letters, the key's place. */
|
|
106
|
+
function shortcutKey(e) {
|
|
107
|
+
var key = (e.key || '').toLowerCase();
|
|
108
|
+
if (!/^[a-z]$/.test(key)) { key = { KeyC: 'c', KeyV: 'v' }[e.code] || ''; }
|
|
109
|
+
|
|
110
|
+
return key === 'c' || key === 'v' ? key : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function typing() {
|
|
114
|
+
var focus = document.activeElement;
|
|
115
|
+
return !!focus && (dom.is(focus, 'input, textarea, select') || focus.isContentEditable);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function textSelected() {
|
|
119
|
+
var selection = window.getSelection ? window.getSelection() : null;
|
|
120
|
+
return !!selection && !selection.isCollapsed && selection.toString() !== '';
|
|
121
|
+
}
|
|
122
|
+
|
|
88
123
|
GridEditor.features.clipboard = function(ge) {
|
|
89
124
|
|
|
90
125
|
var shown = null; // What the toolbar's paste buttons stand for
|
|
91
126
|
var listening = false; // Whether refresh is on the document's change event
|
|
127
|
+
var shortcuts = shortcutsSetting();
|
|
92
128
|
|
|
93
129
|
function used(name) {
|
|
94
130
|
return !ge.settings.plugins || ge.settings.plugins.indexOf(name) !== -1;
|
|
@@ -115,11 +151,29 @@ GridEditor.features.clipboard = function(ge) {
|
|
|
115
151
|
return ['row', 'column', 'section', 'text', 'element'].indexOf(kind) !== -1 ? kind : null;
|
|
116
152
|
}
|
|
117
153
|
|
|
118
|
-
|
|
154
|
+
/** On, unless the host said otherwise: with no target there is nothing for a key to act on. */
|
|
155
|
+
function shortcutsSetting() {
|
|
156
|
+
var wanted = (ge.settings.clipboard || {}).shortcuts;
|
|
157
|
+
var target = !!ge.settings.active_target;
|
|
158
|
+
|
|
159
|
+
if (wanted !== undefined && typeof wanted !== 'boolean') {
|
|
160
|
+
ge.warn('clipboard.shortcuts is true or false: ' + (target ? 'on' : 'off') + ', as with active_target, is used');
|
|
161
|
+
wanted = undefined;
|
|
162
|
+
}
|
|
163
|
+
if (wanted === undefined) { return target; }
|
|
164
|
+
if (wanted && !target) {
|
|
165
|
+
ge.warn('clipboard.shortcuts needs active_target, or settings_panel "sidebar": without it, Ctrl+C and Ctrl+V do nothing');
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return wanted;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function copy(node, kind, source) {
|
|
119
173
|
var html = ge.nodeHtml(node);
|
|
120
174
|
|
|
121
175
|
write({ version: VERSION, category: categoryOf(kind), kind: kind, html: html });
|
|
122
|
-
ge.emit('after-copy', ge.payloadFor(kind, node, { source: 'tool' }));
|
|
176
|
+
ge.emit('after-copy', ge.payloadFor(kind, node, { source: source || 'tool' }));
|
|
123
177
|
|
|
124
178
|
// The canvas came back with new drawers: the one to flash is the
|
|
125
179
|
// node's new copy tool
|
|
@@ -142,6 +196,67 @@ GridEditor.features.clipboard = function(ge) {
|
|
|
142
196
|
ge.place(ge.freshNode(clip.html), clip.kind, { appendTo: target, source: 'paste' });
|
|
143
197
|
}
|
|
144
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Ctrl+V: into the target as the toolbar's paste button would - a
|
|
201
|
+
* section, which only the canvas takes, after the block the target is in
|
|
202
|
+
* - and what that button has none for as the target's paste tool would.
|
|
203
|
+
* With no target, as the selected node's paste tool. What does not fit
|
|
204
|
+
* is not pasted anywhere else.
|
|
205
|
+
*/
|
|
206
|
+
function pasteByShortcut() {
|
|
207
|
+
var clip = read();
|
|
208
|
+
var target = ge.getActiveTarget();
|
|
209
|
+
var node = target || ge.getSelected();
|
|
210
|
+
if (!clip || !node) { return false; }
|
|
211
|
+
|
|
212
|
+
var placement = { appendTo: node, source: 'paste' };
|
|
213
|
+
if (target && clip.category === 'section') {
|
|
214
|
+
if (!available(clip)) { return false; }
|
|
215
|
+
var top = target;
|
|
216
|
+
while (top.parentElement && top.parentElement !== ge.canvas) { top = top.parentElement; }
|
|
217
|
+
placement = { insertAfter: top, source: 'paste' };
|
|
218
|
+
} else if (!fits(clip, TARGETS[ge.kindOf(node)] || [])) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
var made = ge.place(ge.freshNode(clip.html), clip.kind, placement);
|
|
223
|
+
if (made && made.scrollIntoView) { made.scrollIntoView({ behavior: 'smooth' }); }
|
|
224
|
+
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function copyByShortcut() {
|
|
229
|
+
if (textSelected()) { return false; }
|
|
230
|
+
|
|
231
|
+
var node = ge.getSelected() || ge.getActiveTarget();
|
|
232
|
+
var kind = node && ge.kindOf(node);
|
|
233
|
+
if (!kind || !categoryOf(kind)) { return false; }
|
|
234
|
+
|
|
235
|
+
copy(node, kind, 'shortcut');
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function onKeydown(e) {
|
|
240
|
+
if (lastEditor !== ge || e.repeat || e.shiftKey || e.altKey || !(e.ctrlKey || e.metaKey)) { return; }
|
|
241
|
+
var key = shortcutKey(e);
|
|
242
|
+
if (!key || !dom.hasClass(ge.canvas, 'ge-editing') || typing() || ge.panelOpen()) { return; }
|
|
243
|
+
|
|
244
|
+
// Left alone, it is the browser's: a copy of the text, a paste in a field
|
|
245
|
+
if (key === 'c' ? copyByShortcut() : pasteByShortcut()) { e.preventDefault(); }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function listenForShortcuts(on) {
|
|
249
|
+
var at = listeningEditors.indexOf(ge);
|
|
250
|
+
|
|
251
|
+
if (on && at === -1) {
|
|
252
|
+
listeningEditors.push(ge);
|
|
253
|
+
document.addEventListener('keydown', onKeydown);
|
|
254
|
+
} else if (!on && at !== -1) {
|
|
255
|
+
listeningEditors.splice(at, 1);
|
|
256
|
+
document.removeEventListener('keydown', onKeydown);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
145
260
|
/** Show the paste tools, and the toolbar's paste buttons, that fit what is copied. */
|
|
146
261
|
function refresh() {
|
|
147
262
|
var clip = read();
|
|
@@ -203,11 +318,19 @@ GridEditor.features.clipboard = function(ge) {
|
|
|
203
318
|
document.addEventListener(CHANGE, refresh);
|
|
204
319
|
listening = true;
|
|
205
320
|
}
|
|
321
|
+
listenForShortcuts(shortcuts);
|
|
206
322
|
},
|
|
207
323
|
|
|
208
324
|
onDeinit: function() {
|
|
209
325
|
document.removeEventListener(CHANGE, refresh);
|
|
210
326
|
listening = false;
|
|
327
|
+
// A copy takes the canvas out of editing and back: the editor
|
|
328
|
+
// stays the one used last, as it is still
|
|
329
|
+
listenForShortcuts(false);
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
onDestroy: function() {
|
|
333
|
+
if (lastEditor === ge) { lastEditor = null; }
|
|
211
334
|
},
|
|
212
335
|
|
|
213
336
|
// Nothing of its own on the canvas: the tools are in the drawers
|
package/types/grideditor.d.ts
CHANGED
|
@@ -161,7 +161,7 @@ export interface GridEditorOptions {
|
|
|
161
161
|
drawer_overflow?: 'menu' | 'wrap';
|
|
162
162
|
/** The add buttons in categories - rows, content, and a plugin's own - one shown at a time, chosen with tabs. Default false. */
|
|
163
163
|
toolbar_groups?: boolean;
|
|
164
|
-
/** A click in a column or a section makes it where the toolbar's buttons add. Default false. */
|
|
164
|
+
/** A click in a column or a section makes it where the toolbar's buttons add. Default false, and true with settings_panel 'sidebar', which throws if it is given false. */
|
|
165
165
|
active_target?: boolean;
|
|
166
166
|
/** The plugins to use, of any kind; null is every one loaded. */
|
|
167
167
|
plugins?: string[] | null;
|
|
@@ -178,6 +178,8 @@ export interface GridEditorOptions {
|
|
|
178
178
|
elements?: { enabled?: boolean | 'auto'; selector?: string; auto?: boolean; types?: ElementType[] };
|
|
179
179
|
/** The autosave plugin: what is edited, kept in the browser's storage as it changes. */
|
|
180
180
|
autosave?: AutosaveOptions;
|
|
181
|
+
/** The clipboard plugin. `shortcuts`: Ctrl+C and Ctrl+V on the node selected or the target; default active_target. */
|
|
182
|
+
clipboard?: { shortcuts?: boolean };
|
|
181
183
|
custom_filter?: CustomFilter | CustomFilter[] | '';
|
|
182
184
|
valid_col_sizes?: ColumnSize[];
|
|
183
185
|
valid_col_offsets?: number[];
|
|
@@ -195,7 +197,7 @@ export interface GridEditorOptions {
|
|
|
195
197
|
locale_strings?: Record<string, string>;
|
|
196
198
|
callbacks?: Partial<GridEditorCallbacks>;
|
|
197
199
|
confirm_delete?: boolean;
|
|
198
|
-
/** Where a node's settings open. 'sidebar' is a panel that stays on the right while editing, showing the selected node's; one editor per page has it. */
|
|
200
|
+
/** Where a node's settings open. 'sidebar' is a panel that stays on the right while editing, showing the selected node's; one editor per page has it. It turns active_target on, and the constructor throws a TypeError if active_target is false. */
|
|
199
201
|
settings_panel?: 'offcanvas' | 'popover' | 'modal' | 'inline' | 'sidebar';
|
|
200
202
|
/** The 'sidebar' panel. */
|
|
201
203
|
sidebar?: SidebarSettings;
|
|
@@ -271,7 +273,7 @@ export interface Payload {
|
|
|
271
273
|
parent: HTMLElement | null;
|
|
272
274
|
canvas: HTMLElement;
|
|
273
275
|
breakpoint: View;
|
|
274
|
-
source: 'api' | 'tool' | 'dragdrop' | 'paste' | 'panel' | string;
|
|
276
|
+
source: 'api' | 'tool' | 'dragdrop' | 'paste' | 'panel' | 'shortcut' | string;
|
|
275
277
|
/** A pane inside a container. */
|
|
276
278
|
container?: HTMLElement;
|
|
277
279
|
}
|
|
@@ -487,7 +489,7 @@ export declare class GridEditor {
|
|
|
487
489
|
/** Take the editor off: the markup stays, everything else goes. */
|
|
488
490
|
destroy(): this;
|
|
489
491
|
changeView(view: View): this;
|
|
490
|
-
/** The column or region the toolbar adds to, or null. Null without active_target. */
|
|
492
|
+
/** The column or region the toolbar adds to, or null. Null without active_target, which settings_panel 'sidebar' turns on. */
|
|
491
493
|
getActiveTarget(): HTMLElement | null;
|
|
492
494
|
/** Make a column or region, or the first a selector matches, where the toolbar adds; null for none. */
|
|
493
495
|
setActiveTarget(target: HTMLElement | string | null): this;
|