@themarioga/grid-editor 9.0.0-beta.1 → 9.0.0-beta.3
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 +28 -0
- package/README.md +70 -0
- package/UPGRADING.md +5 -0
- package/dist/grideditor.bundle.min.js +2 -2
- package/dist/grideditor.d.ts +41 -1
- package/dist/grideditor.esm.js +68 -2
- package/dist/grideditor.js +68 -2
- package/dist/grideditor.min.js +1 -1
- package/dist/grideditor.min.js.map +3 -3
- package/dist/locales/grideditor.es.esm.js +2 -0
- package/dist/locales/grideditor.es.js +2 -0
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +2 -2
- 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/dist/plugins/grideditor.history.esm.js +360 -0
- package/dist/plugins/grideditor.history.js +365 -0
- package/dist/plugins/grideditor.history.min.js +1 -0
- package/dist/plugins/grideditor.history.min.js.map +7 -0
- package/docs/events.md +4 -1
- package/docs/locale-keys.md +11 -0
- package/docs/plugins.md +12 -1
- package/example/clipboard.html +22 -0
- package/example/history.html +94 -0
- package/example/index.html +8 -0
- package/package.json +1 -1
- package/src/js/grideditor.js +51 -1
- package/src/js/locales/grideditor.es.js +2 -0
- package/src/js/plugins/grideditor.clipboard.js +125 -2
- package/src/js/plugins/grideditor.history.js +367 -0
- package/types/grideditor.d.ts +41 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
5
|
+
|
|
6
|
+
<!-- CSS dependencies -->
|
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
|
8
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css">
|
|
9
|
+
|
|
10
|
+
<!-- Javascript dependencies -->
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
|
12
|
+
|
|
13
|
+
<!-- Grid editor CSS -->
|
|
14
|
+
<link rel="stylesheet" type="text/css" href="../dist/grideditor.css" />
|
|
15
|
+
<!-- Grid editor javascript -->
|
|
16
|
+
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
|
|
17
|
+
<script src="../dist/grideditor.min.js"></script>
|
|
18
|
+
<!-- Undo and redo are a plugin of its own, loaded after the editor. The
|
|
19
|
+
others are here so there is a bit of everything to undo. -->
|
|
20
|
+
<script src="../dist/plugins/grideditor.history.min.js"></script>
|
|
21
|
+
<script src="../dist/plugins/grideditor.clipboard.min.js"></script>
|
|
22
|
+
<script src="../dist/plugins/grideditor.tabs.min.js"></script>
|
|
23
|
+
<script src="../dist/plugins/grideditor.elements.min.js"></script>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
27
|
+
var grid = document.getElementById('myGrid');
|
|
28
|
+
var log = document.getElementById('log');
|
|
29
|
+
|
|
30
|
+
new GridEditor(grid, {
|
|
31
|
+
new_row_layouts: [[12], [6, 6], [4, 4, 4]],
|
|
32
|
+
content_types: [],
|
|
33
|
+
// What was selected is selected again after an undo
|
|
34
|
+
settings_panel: 'sidebar',
|
|
35
|
+
history: {
|
|
36
|
+
max: 50, // the steps that can be undone
|
|
37
|
+
delay: 500, // a drag or a burst of changes is one step
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// A step done says where it came from: 'shortcut', 'tool' or 'api'
|
|
42
|
+
['undo', 'redo'].forEach(function(name) {
|
|
43
|
+
grid.addEventListener('grideditor:after-' + name, function(e) {
|
|
44
|
+
log.textContent = name + ' (' + e.detail.source + ')';
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<title>Grid Editor example - undo and redo</title>
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
<!-- Back to the demo index -->
|
|
54
|
+
<div class="container pt-3">
|
|
55
|
+
<a class="link-secondary" href="index.html"><i class="bi bi-arrow-left"></i> All demos</a>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="container py-3">
|
|
59
|
+
<h2>Undo and redo</h2>
|
|
60
|
+
<p class="text-body-secondary">
|
|
61
|
+
Delete a row, drag a column to another size, paste something, change
|
|
62
|
+
an id in the panel: each is a step. <kbd>Ctrl</kbd>+<kbd>Z</kbd>
|
|
63
|
+
(<kbd>⌘</kbd>+<kbd>Z</kbd> on a Mac) goes back one, and
|
|
64
|
+
<kbd>Ctrl</kbd>+<kbd>Y</kbd> or <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd>
|
|
65
|
+
comes forward again, as do the <i class="bi bi-arrow-counterclockwise"></i>
|
|
66
|
+
and <i class="bi bi-arrow-clockwise"></i> buttons on the right of
|
|
67
|
+
the toolbar. A change made after going back ends what could be redone.
|
|
68
|
+
</p>
|
|
69
|
+
<p class="text-body-secondary">
|
|
70
|
+
While you are writing in a text or a field, the keys undo what you
|
|
71
|
+
typed there, as usual; it becomes a step when you are done. The
|
|
72
|
+
column or row selected in the sidebar is selected again after a step.
|
|
73
|
+
<span id="log" class="fw-semibold"></span>
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<div id="myGrid">
|
|
77
|
+
<div class="row">
|
|
78
|
+
<div class="col-lg-6">
|
|
79
|
+
<h3>Change this page</h3>
|
|
80
|
+
<p>Then undo it.</p>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="col-lg-6">
|
|
83
|
+
<p>Delete this column, or the whole row.</p>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="row">
|
|
87
|
+
<div class="col-lg-4"><p>One.</p></div>
|
|
88
|
+
<div class="col-lg-4"><p>Two.</p></div>
|
|
89
|
+
<div class="col-lg-4"><p>Three.</p></div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
package/example/index.html
CHANGED
|
@@ -178,6 +178,14 @@
|
|
|
178
178
|
<span class="text-body-secondary">Copying a row, a column or a container and pasting it somewhere else, in this editor or another</span>
|
|
179
179
|
</a>
|
|
180
180
|
|
|
181
|
+
<a class="list-group-item list-group-item-action" href="history.html">
|
|
182
|
+
<div class="d-flex justify-content-between">
|
|
183
|
+
<strong>Undo and redo</strong>
|
|
184
|
+
<code class="text-body-secondary">history.html</code>
|
|
185
|
+
</div>
|
|
186
|
+
<span class="text-body-secondary">The history plugin: Ctrl+Z and Ctrl+Y, and buttons on the toolbar, over what the editor changes</span>
|
|
187
|
+
</a>
|
|
188
|
+
|
|
181
189
|
<a class="list-group-item list-group-item-action" href="wrap_content.html">
|
|
182
190
|
<div class="d-flex justify-content-between">
|
|
183
191
|
<strong>Wrapped content</strong>
|
package/package.json
CHANGED
package/src/js/grideditor.js
CHANGED
|
@@ -1614,6 +1614,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1614
1614
|
var colour = item.align === 'end' ? 'btn-primary' : 'btn-light';
|
|
1615
1615
|
var button = labelButton(dom.element('a', { 'class': 'btn btn-sm ' + colour + ' ge-add-container ge-add-feature' }), label, iconClass);
|
|
1616
1616
|
|
|
1617
|
+
var acting = typeof item.action === 'function';
|
|
1618
|
+
|
|
1617
1619
|
dom.addClass(button, item.className || '');
|
|
1618
1620
|
// Hidden as it is now: a plugin that shows and hides its buttons
|
|
1619
1621
|
// only hears of what changes, not of the toolbar being built again
|
|
@@ -1623,7 +1625,21 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1623
1625
|
'data-ge-feature': name,
|
|
1624
1626
|
'data-ge-item': index,
|
|
1625
1627
|
});
|
|
1628
|
+
if (acting) {
|
|
1629
|
+
// Not a block to add: it is not carried onto the canvas, and
|
|
1630
|
+
// the active target is none of its business
|
|
1631
|
+
button.setAttribute('data-ge-action', '');
|
|
1632
|
+
if (typeof item.disabled === 'function' ? item.disabled() : item.disabled) {
|
|
1633
|
+
dom.addClass(button, 'disabled');
|
|
1634
|
+
button.setAttribute('aria-disabled', 'true');
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1626
1637
|
button.addEventListener('click', function() {
|
|
1638
|
+
if (acting) {
|
|
1639
|
+
if (!dom.hasClass(button, 'disabled')) { item.action(button); }
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1627
1643
|
if (activeTarget) { addToTarget(button, item.source || 'tool'); return; }
|
|
1628
1644
|
|
|
1629
1645
|
var made = item.create();
|
|
@@ -1661,7 +1677,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1661
1677
|
}
|
|
1662
1678
|
|
|
1663
1679
|
function makeToolbarDraggable() {
|
|
1664
|
-
var buttons = dom.all(mainControls, '[data-ge-toolbar]');
|
|
1680
|
+
var buttons = dom.all(mainControls, '[data-ge-toolbar]:not([data-ge-action])');
|
|
1665
1681
|
|
|
1666
1682
|
buttons.forEach(function(button) {
|
|
1667
1683
|
dom.removeClass(button, 'ge-palette-button');
|
|
@@ -2622,6 +2638,40 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2622
2638
|
? dom.all(mainControls, '[data-ge-toolbar="feature"][data-ge-feature="' + name + '"]')
|
|
2623
2639
|
: [];
|
|
2624
2640
|
},
|
|
2641
|
+
getSelected: function() { return panelMode() === 'sidebar' ? selected : null; },
|
|
2642
|
+
getActiveTarget: function() { return settings.active_target ? activeTarget : null; },
|
|
2643
|
+
// True when it took: a node that cannot be, or no setting
|
|
2644
|
+
// for it, says nothing, since a plugin putting back what it
|
|
2645
|
+
// remembers cannot know the node is still one
|
|
2646
|
+
setSelected: function(node) {
|
|
2647
|
+
var chosen = panelMode() === 'sidebar' && !!node && node !== canvas && canvas.contains(node) && detailsFor.has(node);
|
|
2648
|
+
if (chosen) { changeSelection(node); }
|
|
2649
|
+
return chosen;
|
|
2650
|
+
},
|
|
2651
|
+
setActiveTarget: function(node) {
|
|
2652
|
+
var chosen = !!settings.active_target && isTarget(node);
|
|
2653
|
+
if (chosen) { changeTarget(node); }
|
|
2654
|
+
return chosen;
|
|
2655
|
+
},
|
|
2656
|
+
// What keeps Escape from the selection and the target, and an
|
|
2657
|
+
// inline panel unfolded in a drawer, which Escape leaves alone
|
|
2658
|
+
panelOpen: function() {
|
|
2659
|
+
if (openSettingsState || dialogState) { return true; }
|
|
2660
|
+
if (confirmDialog && dom.hasClass(confirmDialog, 'show')) { return true; }
|
|
2661
|
+
|
|
2662
|
+
return panelMode() === 'inline' && dom.all(canvas, '.ge-tools-drawer > .ge-details').some(dom.visible);
|
|
2663
|
+
},
|
|
2664
|
+
// The toolbar and the settings panels are outside the canvas
|
|
2665
|
+
contains: function(node) {
|
|
2666
|
+
if (!node || node.nodeType !== 1) { return false; }
|
|
2667
|
+
if (canvas.contains(node) || (mainControls && mainControls.contains(node))) { return true; }
|
|
2668
|
+
if (dialogPanel && dialogPanel.contains(node)) { return true; }
|
|
2669
|
+
if (confirmDialog && confirmDialog.contains(node)) { return true; }
|
|
2670
|
+
|
|
2671
|
+
return Object.keys(settingsPanels).some(function(mode) {
|
|
2672
|
+
return settingsPanels[mode].contains(node);
|
|
2673
|
+
});
|
|
2674
|
+
},
|
|
2625
2675
|
};
|
|
2626
2676
|
}
|
|
2627
2677
|
|
|
@@ -175,6 +175,8 @@ GridEditor.locales.es = {
|
|
|
175
175
|
'autosave.restore_message': 'Hay un borrador de esta página guardado el {date}, con cambios que no se publicaron. ¿Quieres recuperarlo?',
|
|
176
176
|
'autosave.restore': 'Recuperar',
|
|
177
177
|
'autosave.discard': 'Descartar',
|
|
178
|
+
'history.undo': 'Deshacer',
|
|
179
|
+
'history.redo': 'Rehacer',
|
|
178
180
|
'utility.col_from_row': 'De la fila: {count}',
|
|
179
181
|
'utility.row_cols': 'Columnas por fila',
|
|
180
182
|
'badge.row_cols': '{count} por fila',
|
|
@@ -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
|