@themarioga/grid-editor 9.0.0-beta.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.
@@ -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
- function copy(node, kind) {
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
@@ -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[];
@@ -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
  }