@themarioga/grid-editor 8.3.0 → 8.5.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/CHANGELOG.md +40 -0
- package/README.md +43 -1
- package/dist/grideditor.bundle.min.js +3 -3
- package/dist/grideditor.css +49 -0
- package/dist/grideditor.d.ts +21 -1
- package/dist/grideditor.esm.js +211 -9
- package/dist/grideditor.js +211 -9
- package/dist/grideditor.min.css +1 -1
- package/dist/grideditor.min.css.map +1 -1
- package/dist/grideditor.min.js +2 -2
- package/dist/grideditor.min.js.map +3 -3
- package/dist/locales/grideditor.es.esm.js +4 -0
- package/dist/locales/grideditor.es.js +4 -0
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +2 -2
- package/dist/plugins/grideditor.ckeditor.esm.js +1 -0
- package/dist/plugins/grideditor.ckeditor.js +1 -0
- package/dist/plugins/grideditor.ckeditor.min.js +1 -1
- package/dist/plugins/grideditor.ckeditor.min.js.map +2 -2
- package/dist/plugins/grideditor.elements.esm.js +72 -1
- package/dist/plugins/grideditor.elements.js +72 -1
- package/dist/plugins/grideditor.elements.min.js +1 -1
- package/dist/plugins/grideditor.elements.min.js.map +3 -3
- package/dist/plugins/grideditor.sections.esm.js +1 -0
- package/dist/plugins/grideditor.sections.js +1 -0
- package/dist/plugins/grideditor.sections.min.js +1 -1
- package/dist/plugins/grideditor.sections.min.js.map +2 -2
- package/dist/plugins/grideditor.summernote.esm.js +1 -0
- package/dist/plugins/grideditor.summernote.js +1 -0
- package/dist/plugins/grideditor.summernote.min.js +1 -1
- package/dist/plugins/grideditor.summernote.min.js.map +2 -2
- package/dist/plugins/grideditor.tinymce.esm.js +1 -0
- package/dist/plugins/grideditor.tinymce.js +1 -0
- package/dist/plugins/grideditor.tinymce.min.js +1 -1
- package/dist/plugins/grideditor.tinymce.min.js.map +2 -2
- package/docs/locale-keys.md +5 -1
- package/docs/plugins.md +24 -1
- package/package.json +1 -1
- package/src/js/grideditor.js +252 -8
- package/src/js/locales/grideditor.es.js +4 -0
- package/src/js/plugins/grideditor.elements.js +94 -1
- package/src/js/plugins/grideditor.sections.js +1 -0
- package/src/js/text/grideditor.text.js +1 -0
- package/src/less/grideditor.less +58 -0
- package/types/grideditor.d.ts +21 -1
package/docs/locale-keys.md
CHANGED
|
@@ -46,7 +46,11 @@ The tools in a drawer, and the buttons in the toolbar above the canvas.
|
|
|
46
46
|
| `tool.indent_increase` | Increase indent\n(hold shift for max) | Column drawer, adds offset units |
|
|
47
47
|
| `tool.edit_source` | Edit Source Code | Toolbar, swaps the canvas for its html |
|
|
48
48
|
| `tool.preview` | Preview | Toolbar, hides the editing furniture while held |
|
|
49
|
-
| `tool.more` | More | Toolbar, the button that opens the add buttons that don't fit on its line |
|
|
49
|
+
| `tool.more` | More | Toolbar, the button that opens the add buttons that don't fit on its line; drawers, the tool that unfolds the ones that don't fit on theirs |
|
|
50
|
+
| `group.rows` | Rows | Toolbar, with `toolbar_groups`: the tab of the add row buttons |
|
|
51
|
+
| `group.content` | Content | Toolbar, with `toolbar_groups`: the tab of the containers, the sections and the plugins' buttons that join it |
|
|
52
|
+
| `group.elements` | Elements | Toolbar, with `toolbar_groups`: the tab of the texts, the elements of `elements.types` and the plugins' buttons that join it |
|
|
53
|
+
| `group.select` | Add | Toolbar, with `toolbar_groups`: the accessible name of the tabs |
|
|
50
54
|
| `tool.id_placeholder` | id | Placeholder of the id input in the settings panel |
|
|
51
55
|
| `tool.id_title` | Set a unique identifier | Tooltip of that input |
|
|
52
56
|
| `tool.classes_placeholder` | classes | Placeholder of the css class input beside it |
|
package/docs/plugins.md
CHANGED
|
@@ -62,6 +62,7 @@ GridEditor.containers.carousel = function(ge) {
|
|
|
62
62
|
return {
|
|
63
63
|
labelKey: 'container.add_carousel', // the toolbar button's label
|
|
64
64
|
iconClass: 'bi bi-images', // optional: the button shows it in place of the label
|
|
65
|
+
group: 'media', // optional: its toolbar tab, with toolbar_groups
|
|
65
66
|
addPaneKey: 'container.add_slide', // the drawer's add pane tool
|
|
66
67
|
paneKind: 'slide', // the kind its panes report
|
|
67
68
|
|
|
@@ -100,6 +101,10 @@ written for 6.x.
|
|
|
100
101
|
- **`iconClass`**, when given, is the toolbar button's face: the icon alone,
|
|
101
102
|
with the label as its title. Without one the button shows a plus and the
|
|
102
103
|
label.
|
|
104
|
+
- **`group`**, when given, is the toolbar tab the button goes in when the
|
|
105
|
+
host turns on `toolbar_groups`. Without one a container goes in `content`,
|
|
106
|
+
with the texts. See the `toolbar` item's `group` below for how tabs are
|
|
107
|
+
named and labelled.
|
|
103
108
|
- **`create(options)`** returns a detached container. It must carry
|
|
104
109
|
`data-ge-container="<type>"`: that attribute, and never a class, is how the
|
|
105
110
|
editor recognises one.
|
|
@@ -218,7 +223,8 @@ needs to put a new kind of block on the canvas:
|
|
|
218
223
|
- **`toolbar`** adds buttons beside the containers'. A click appends what
|
|
219
224
|
`create()` returns to the canvas, announced as `kind`; a drop puts it where it
|
|
220
225
|
was dropped, or, when that region will not have it, on the canvas just after
|
|
221
|
-
the block it was dropped in.
|
|
226
|
+
the block it was dropped in. A `create()` that returns `null` adds nothing:
|
|
227
|
+
that is how a plugin says it could not make one this time.
|
|
222
228
|
|
|
223
229
|
- **`drawerTools(drawer, node, kind)`** runs for every drawer that has a gear,
|
|
224
230
|
as a utility plugin's does, so a tool can go on any node. The clipboard
|
|
@@ -280,6 +286,23 @@ rather than remembering anything about it.
|
|
|
280
286
|
container or a text does: a click puts it in a row and a column of its own
|
|
281
287
|
at the end of the canvas, and a drop anywhere but in a column does the
|
|
282
288
|
same where it was dropped.
|
|
289
|
+
- A **`toolbar`** item's **`group`** is the tab it goes in when the host turns
|
|
290
|
+
on `toolbar_groups`. The core has three: `rows` - the add row buttons -,
|
|
291
|
+
`content` - the containers and the sections - and `elements` - the texts
|
|
292
|
+
and the elements of `elements.types` - and an item may join any of them. Any other name is a tab of its own, shared by every item, of
|
|
293
|
+
any plugin, that names it. An item without a `group` goes in a tab named
|
|
294
|
+
after its plugin. A tab's label is the `group.<name>` locale string, which
|
|
295
|
+
the plugin registers with its others, or its first button's label when
|
|
296
|
+
there is none. Tabs come in this order: `rows`, `content`, `elements`, then the others
|
|
297
|
+
as their first button comes. `group` is ignored with `align: 'end'`: the
|
|
298
|
+
buttons on the right are not grouped.
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
Object.assign(GridEditor.locales.en, { 'group.media': 'Media' });
|
|
302
|
+
GridEditor.features.gallery = function(ge) {
|
|
303
|
+
return { toolbar: [{ labelKey: 'gallery.add', kind: 'gallery', group: 'media', create: … }] };
|
|
304
|
+
};
|
|
305
|
+
```
|
|
283
306
|
|
|
284
307
|
- **`onSortable(sortable)`** is handed the function the editor makes all of its
|
|
285
308
|
own lists with. A plugin describes a list; it never touches the drag toolkit
|
package/package.json
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"comment": "A scoped package publishes restricted unless it is told otherwise.",
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
|
-
"version": "8.
|
|
36
|
+
"version": "8.5.0",
|
|
37
37
|
"main": "dist/grideditor.js",
|
|
38
38
|
"module": "dist/grideditor.esm.js",
|
|
39
39
|
"types": "dist/grideditor.d.ts",
|
package/src/js/grideditor.js
CHANGED
|
@@ -142,6 +142,7 @@ var NESTED_SETTINGS = {
|
|
|
142
142
|
enabled: 'auto', // 'auto' turns them on when the page has any
|
|
143
143
|
selector: '[data-ge-element]', // What the host marks an element with
|
|
144
144
|
auto: false, // Treat every child of a content area as an element
|
|
145
|
+
types: [], // The elements the toolbar offers: { type, html, label | labelKey, iconClass, group }
|
|
145
146
|
},
|
|
146
147
|
resize: {
|
|
147
148
|
enabled: true, // The handle on the column's edge
|
|
@@ -198,6 +199,15 @@ function translate(settings, key, params) {
|
|
|
198
199
|
});
|
|
199
200
|
}
|
|
200
201
|
|
|
202
|
+
/** Whether some locale has a string for the key, asked without the warning a missing one gets. */
|
|
203
|
+
function hasString(settings, key) {
|
|
204
|
+
var locales = GridEditor.locales;
|
|
205
|
+
|
|
206
|
+
return (settings.locale_strings || {})[key] !== undefined ||
|
|
207
|
+
(locales[settings.locale] || {})[key] !== undefined ||
|
|
208
|
+
locales.en[key] !== undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
201
211
|
function warn(message) {
|
|
202
212
|
if (window.console && window.console.warn) {
|
|
203
213
|
window.console.warn('grid-editor: ' + message);
|
|
@@ -367,6 +377,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
367
377
|
'drag_handle' : 'tool', // 'tool' for the move tool, 'drawer' for the whole drawer
|
|
368
378
|
'toolbar_drag' : 'auto', // Drag the toolbar's buttons onto the canvas. 'auto' follows drag_handle
|
|
369
379
|
'toolbar_overflow' : 'menu', // What doesn't fit on one line: 'menu' behind a button, 'wrap' onto another line
|
|
380
|
+
'drawer_overflow' : 'menu', // The same for the drawers' tools: 'menu' behind a button, 'wrap' onto another line
|
|
381
|
+
'toolbar_groups' : false, // The add buttons in categories, one shown at a time, picked with tabs
|
|
370
382
|
'active_target' : false, // A click in a column makes it where the toolbar's buttons add
|
|
371
383
|
'element_tools' : [], // Host tools on element drawers, same shape as row_tools
|
|
372
384
|
'element_classes' : [], // Preset class toggles on an element's settings panel
|
|
@@ -423,6 +435,9 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
423
435
|
var activeTarget = null; // The column or region the toolbar adds to, with active_target
|
|
424
436
|
var toolbarLifetime = null; // Aborted when the toolbar is rebuilt or taken away
|
|
425
437
|
var closeOverflowMenu = function() {}; // Folds the toolbar's overflow menu away, when it has one
|
|
438
|
+
var refitToolbar = function() {}; // Works out again what the toolbar's overflow menu holds, when it has one
|
|
439
|
+
var addButtons = []; // The toolbar's add buttons, in toolbar order: { button, home, group }
|
|
440
|
+
var toolbarGroup = null; // The category shown, with toolbar_groups. Kept when the toolbar is rebuilt
|
|
426
441
|
var warnedHere = {}; // Some warnings are worth saying once per instance, not once per call
|
|
427
442
|
var sortables = []; // Every list made sortable, so deinit destroys exactly those
|
|
428
443
|
var instanceId = ++editorCounter; // Scopes the sortable groups to this editor
|
|
@@ -972,6 +987,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
972
987
|
canvas.parentNode.insertBefore(htmlTextArea, canvas);
|
|
973
988
|
|
|
974
989
|
createMainControls();
|
|
990
|
+
watchDrawers();
|
|
975
991
|
|
|
976
992
|
var signal = { signal: lifetime.signal };
|
|
977
993
|
|
|
@@ -1003,6 +1019,16 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1003
1019
|
if (!dom.hasClass(canvas, 'ge-editing')) { return; }
|
|
1004
1020
|
if (from && Math.abs(e.clientX - from.x) + Math.abs(e.clientY - from.y) > settings.drag.threshold) { return; }
|
|
1005
1021
|
var region = dom.closest(e.target, targetSelector());
|
|
1022
|
+
|
|
1023
|
+
// The target's own background, or its drawer's, clicked
|
|
1024
|
+
// again takes it back. What is in it - a text, a tool, a
|
|
1025
|
+
// nested column - keeps it, or makes that the target
|
|
1026
|
+
if (region && region === activeTarget &&
|
|
1027
|
+
(e.target === region || e.target === dom.child(region, '.ge-tools-drawer'))) {
|
|
1028
|
+
changeTarget(null);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1006
1032
|
changeTarget(region && region !== canvas && canvas.contains(region) ? region : null);
|
|
1007
1033
|
}, signal);
|
|
1008
1034
|
|
|
@@ -1047,6 +1073,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1047
1073
|
start = wrapper.appendChild(dom.element('div', { 'class': 'ge-toolbar-start' }));
|
|
1048
1074
|
}
|
|
1049
1075
|
|
|
1076
|
+
addButtons = [];
|
|
1077
|
+
|
|
1050
1078
|
// Add row
|
|
1051
1079
|
addRowGroup = start.appendChild(dom.element('div', { 'class': 'ge-addRowGroup btn-group' }));
|
|
1052
1080
|
addContainerGroup = dom.element('div', { 'class': 'ge-addContainerGroup btn-group ms-1' });
|
|
@@ -1077,6 +1105,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1077
1105
|
}
|
|
1078
1106
|
});
|
|
1079
1107
|
addRowGroup.appendChild(btn);
|
|
1108
|
+
addButtons.push({ button: btn, home: addRowGroup, group: 'rows' });
|
|
1080
1109
|
|
|
1081
1110
|
btn.appendChild(dom.create('<i class="bi bi-plus"></i>'));
|
|
1082
1111
|
|
|
@@ -1113,6 +1142,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1113
1142
|
}, { parent: canvas, source: 'tool' });
|
|
1114
1143
|
});
|
|
1115
1144
|
addContainerGroup.appendChild(button);
|
|
1145
|
+
addButtons.push({ button: button, home: addContainerGroup, group: toolbarGroupOf(definition.group) || 'content' });
|
|
1116
1146
|
});
|
|
1117
1147
|
|
|
1118
1148
|
// A feature plugin's own buttons, beside the containers': what
|
|
@@ -1128,6 +1158,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1128
1158
|
endItems.push(button);
|
|
1129
1159
|
} else {
|
|
1130
1160
|
addContainerGroup.appendChild(button);
|
|
1161
|
+
// One that names no category of its own has its plugin's
|
|
1162
|
+
addButtons.push({ button: button, home: addContainerGroup, group: toolbarGroupOf(item.group) || name });
|
|
1131
1163
|
}
|
|
1132
1164
|
});
|
|
1133
1165
|
});
|
|
@@ -1198,16 +1230,97 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1198
1230
|
endItems.forEach(function(button) { end.appendChild(button); });
|
|
1199
1231
|
}
|
|
1200
1232
|
|
|
1233
|
+
if (settings.toolbar_groups) { createGroupTabs(start); }
|
|
1201
1234
|
if (overflowMenu) { createOverflowMenu(start); }
|
|
1202
1235
|
|
|
1203
1236
|
makeToolbarDraggable();
|
|
1204
1237
|
}
|
|
1205
1238
|
|
|
1239
|
+
/** The core's categories, ahead of the plugins' own. */
|
|
1240
|
+
var CORE_GROUPS = [
|
|
1241
|
+
{ name: 'rows', labelKey: 'group.rows' },
|
|
1242
|
+
{ name: 'content', labelKey: 'group.content' },
|
|
1243
|
+
{ name: 'elements', labelKey: 'group.elements' },
|
|
1244
|
+
];
|
|
1245
|
+
|
|
1246
|
+
/** A plugin's category, when it names one. */
|
|
1247
|
+
function toolbarGroupOf(value) {
|
|
1248
|
+
return typeof value === 'string' && value.trim() !== '' ? value : null;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* toolbar_groups: tabs at the start of the toolbar, one per category
|
|
1253
|
+
* of add buttons, and only the chosen one's buttons on it. The rest
|
|
1254
|
+
* wait in a hidden stash inside the toolbar, so toolbarItems still
|
|
1255
|
+
* finds them and the btn-groups round the corners of what shows. With
|
|
1256
|
+
* a single category there is nothing to choose, and no tabs.
|
|
1257
|
+
*/
|
|
1258
|
+
function createGroupTabs(start) {
|
|
1259
|
+
// The core's two first, then the rest as their first button comes
|
|
1260
|
+
var names = CORE_GROUPS.map(function(group) { return group.name; });
|
|
1261
|
+
addButtons.forEach(function(entry) {
|
|
1262
|
+
if (names.indexOf(entry.group) < 0) { names.push(entry.group); }
|
|
1263
|
+
});
|
|
1264
|
+
names = names.filter(function(name) {
|
|
1265
|
+
return addButtons.some(function(entry) { return entry.group === name; });
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
if (names.length < 2) { return; }
|
|
1269
|
+
|
|
1270
|
+
var tabs = dom.element('div', { 'class': 'ge-toolbar-groups btn-group', role: 'tablist', 'aria-label': t('group.select') });
|
|
1271
|
+
start.insertBefore(tabs, start.firstChild);
|
|
1272
|
+
var stash = mainControls.appendChild(dom.element('div', { 'class': 'ge-toolbar-stash', hidden: 'hidden' }));
|
|
1273
|
+
|
|
1274
|
+
names.forEach(function(name) {
|
|
1275
|
+
var tab = tabs.appendChild(dom.element('button', {
|
|
1276
|
+
type: 'button',
|
|
1277
|
+
'class': 'btn btn-sm btn-outline-primary',
|
|
1278
|
+
role: 'tab',
|
|
1279
|
+
'data-ge-group': name,
|
|
1280
|
+
}, groupLabel(name)));
|
|
1281
|
+
|
|
1282
|
+
tab.addEventListener('click', function() { showGroup(name); });
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
showGroup(names.indexOf(toolbarGroup) >= 0 ? toolbarGroup : names[0]);
|
|
1286
|
+
|
|
1287
|
+
function showGroup(name) {
|
|
1288
|
+
toolbarGroup = name;
|
|
1289
|
+
|
|
1290
|
+
addButtons.forEach(function(entry) {
|
|
1291
|
+
(entry.group === name ? entry.home : stash).appendChild(entry.button);
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
dom.children(tabs).forEach(function(tab) {
|
|
1295
|
+
var chosen = tab.getAttribute('data-ge-group') === name;
|
|
1296
|
+
dom.toggleClass(tab, 'active', chosen);
|
|
1297
|
+
tab.setAttribute('aria-selected', chosen ? 'true' : 'false');
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
refitToolbar();
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/** A category's label: its group.<name> string, or its first button's when it has none. */
|
|
1305
|
+
function groupLabel(name) {
|
|
1306
|
+
var key = 'group.' + name;
|
|
1307
|
+
if (hasString(settings, key)) { return t(key); }
|
|
1308
|
+
|
|
1309
|
+
var first = addButtons.filter(function(entry) { return entry.group === name; })[0];
|
|
1310
|
+
return first.button.getAttribute('title') || name;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/** The add buttons on the toolbar's line: all of them, or the chosen category's. */
|
|
1314
|
+
function shownAddButtons() {
|
|
1315
|
+
return addButtons.filter(function(entry) { return !dom.closest(entry.button, '.ge-toolbar-stash'); });
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1206
1318
|
/** The toolbar off the page, with what keeps its overflow menu up to date. */
|
|
1207
1319
|
function removeMainControls() {
|
|
1208
1320
|
if (toolbarLifetime) { toolbarLifetime.abort(); }
|
|
1209
1321
|
toolbarLifetime = null;
|
|
1210
1322
|
closeOverflowMenu = function() {};
|
|
1323
|
+
refitToolbar = function() {};
|
|
1211
1324
|
mainControls.remove();
|
|
1212
1325
|
}
|
|
1213
1326
|
|
|
@@ -1231,13 +1344,10 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1231
1344
|
var toggle = dom.one(more, 'button');
|
|
1232
1345
|
var panel = dom.one(more, '.ge-toolbar-overflow');
|
|
1233
1346
|
var spares = dom.all(panel, '.btn-group');
|
|
1234
|
-
//
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
homes.push({ button: button, home: group, spare: spares[index] });
|
|
1239
|
-
});
|
|
1240
|
-
});
|
|
1347
|
+
// The spare group in the menu for each group on the line
|
|
1348
|
+
function spareOf(home) {
|
|
1349
|
+
return home === addRowGroup ? spares[0] : spares[1];
|
|
1350
|
+
}
|
|
1241
1351
|
|
|
1242
1352
|
toggle.setAttribute('title', t('tool.more'));
|
|
1243
1353
|
toggle.setAttribute('aria-haspopup', 'true');
|
|
@@ -1258,6 +1368,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1258
1368
|
if (e.key === 'Escape') { openOverflow(false); }
|
|
1259
1369
|
}, signal);
|
|
1260
1370
|
closeOverflowMenu = function() { openOverflow(false); };
|
|
1371
|
+
// A category chosen: another set of buttons to fit
|
|
1372
|
+
refitToolbar = fit;
|
|
1261
1373
|
|
|
1262
1374
|
fit();
|
|
1263
1375
|
|
|
@@ -1288,6 +1400,9 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1288
1400
|
|
|
1289
1401
|
/** Back on the line, then the last ones off it until the rest fit. */
|
|
1290
1402
|
function fit() {
|
|
1403
|
+
// Only the chosen category's, with toolbar_groups: the others
|
|
1404
|
+
// wait in their stash
|
|
1405
|
+
var homes = shownAddButtons();
|
|
1291
1406
|
homes.forEach(function(entry) { entry.home.appendChild(entry.button); });
|
|
1292
1407
|
dom.removeClass(more, 'ge-needed');
|
|
1293
1408
|
|
|
@@ -1298,7 +1413,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1298
1413
|
|
|
1299
1414
|
dom.addClass(more, 'ge-needed');
|
|
1300
1415
|
for (var i = homes.length - 1; i >= 0 && start.scrollWidth > start.clientWidth; i--) {
|
|
1301
|
-
|
|
1416
|
+
var into = spareOf(homes[i].home);
|
|
1417
|
+
into.insertBefore(homes[i].button, into.firstChild);
|
|
1302
1418
|
}
|
|
1303
1419
|
}
|
|
1304
1420
|
}
|
|
@@ -1338,6 +1454,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1338
1454
|
if (activeTarget) { addToTarget(button, item.source || 'tool'); return; }
|
|
1339
1455
|
|
|
1340
1456
|
var made = item.create();
|
|
1457
|
+
// A plugin that could not make one says so with null
|
|
1458
|
+
if (!made) { return; }
|
|
1341
1459
|
|
|
1342
1460
|
// One that belongs in a column brings a row and a column
|
|
1343
1461
|
// of its own, the way a container does
|
|
@@ -1497,6 +1615,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1497
1615
|
function insertFeatureFromToolbar(button, where, source) {
|
|
1498
1616
|
var item = FEATURES[button.getAttribute('data-ge-feature')].toolbar[parseInt(button.getAttribute('data-ge-item'), 10)];
|
|
1499
1617
|
var made = item.create();
|
|
1618
|
+
if (!made) { return null; }
|
|
1500
1619
|
var placed = made;
|
|
1501
1620
|
|
|
1502
1621
|
if (item.inColumn) {
|
|
@@ -3330,6 +3449,127 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3330
3449
|
return createTool(drawer, t('tool.move'), 'ge-move', 'bi bi-arrows-move');
|
|
3331
3450
|
}
|
|
3332
3451
|
|
|
3452
|
+
/**
|
|
3453
|
+
* The drawers that span their node - a row's, a column's, a
|
|
3454
|
+
* container's, an element's, a section's - keep their tools to one
|
|
3455
|
+
* line. The tools that don't fit are hidden from the end, and a more
|
|
3456
|
+
* tool at the drawer's end unfolds it to show them all, wrapping as
|
|
3457
|
+
* it would otherwise. They stay where they are, so whatever finds a
|
|
3458
|
+
* tool in its drawer, or hangs a picker off it, still does.
|
|
3459
|
+
*
|
|
3460
|
+
* A text's drawer sits over the text, as wide as its tools, and a
|
|
3461
|
+
* pane's sits by its label: neither is one of them.
|
|
3462
|
+
*/
|
|
3463
|
+
var FITTED_DRAWERS = '.ge-tools-drawer:not(.ge-text-drawer):not(.ge-pane-drawer):not(.ge-code-inline)';
|
|
3464
|
+
|
|
3465
|
+
/**
|
|
3466
|
+
* Fit every drawer as it comes onto the canvas, and again when it
|
|
3467
|
+
* changes width or a tool is added to it, shown or hidden. Plugins
|
|
3468
|
+
* add tools at their own moments, so this watches rather than
|
|
3469
|
+
* waiting to be told.
|
|
3470
|
+
*/
|
|
3471
|
+
function watchDrawers() {
|
|
3472
|
+
if (settings.drawer_overflow === 'wrap' || !window.ResizeObserver || !window.MutationObserver) { return; }
|
|
3473
|
+
|
|
3474
|
+
var due = new Set();
|
|
3475
|
+
var frame = null;
|
|
3476
|
+
|
|
3477
|
+
// Once a frame, after the layout: moving tools in the observers'
|
|
3478
|
+
// callbacks would resize what they are watching
|
|
3479
|
+
function schedule(drawer) {
|
|
3480
|
+
due.add(drawer);
|
|
3481
|
+
if (frame === null) { frame = window.requestAnimationFrame(flush); }
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
function flush() {
|
|
3485
|
+
frame = null;
|
|
3486
|
+
var drawers = Array.from(due);
|
|
3487
|
+
due.clear();
|
|
3488
|
+
drawers.forEach(function(drawer) {
|
|
3489
|
+
if (!drawer.isConnected) {
|
|
3490
|
+
drawerResizes.unobserve(drawer);
|
|
3491
|
+
return;
|
|
3492
|
+
}
|
|
3493
|
+
fitDrawer(drawer);
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
var drawerResizes = new window.ResizeObserver(function(entries) {
|
|
3498
|
+
entries.forEach(function(entry) { schedule(entry.target); });
|
|
3499
|
+
});
|
|
3500
|
+
|
|
3501
|
+
var mutations = new window.MutationObserver(function(records) {
|
|
3502
|
+
records.forEach(function(record) {
|
|
3503
|
+
var target = record.target;
|
|
3504
|
+
|
|
3505
|
+
// A tool shown or hidden
|
|
3506
|
+
if (record.type === 'attributes') {
|
|
3507
|
+
var parent = target.parentNode;
|
|
3508
|
+
if (parent && parent.nodeType === 1 && parent.matches(FITTED_DRAWERS)) { schedule(parent); }
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
// A tool added
|
|
3513
|
+
if (target.nodeType === 1 && target.matches(FITTED_DRAWERS)) {
|
|
3514
|
+
schedule(target);
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
record.addedNodes.forEach(function(node) {
|
|
3519
|
+
if (node.nodeType !== 1) { return; }
|
|
3520
|
+
dom.selfAndAll(node, FITTED_DRAWERS).forEach(function(drawer) {
|
|
3521
|
+
dom.addClass(drawer, 'ge-drawer-fit');
|
|
3522
|
+
drawerResizes.observe(drawer);
|
|
3523
|
+
schedule(drawer);
|
|
3524
|
+
});
|
|
3525
|
+
});
|
|
3526
|
+
});
|
|
3527
|
+
});
|
|
3528
|
+
mutations.observe(canvas, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });
|
|
3529
|
+
|
|
3530
|
+
lifetime.signal.addEventListener('abort', function() {
|
|
3531
|
+
mutations.disconnect();
|
|
3532
|
+
drawerResizes.disconnect();
|
|
3533
|
+
if (frame !== null) { window.cancelAnimationFrame(frame); }
|
|
3534
|
+
});
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
/** Hide the tools that don't fit on the drawer's line, from the end, behind its more tool. */
|
|
3538
|
+
function fitDrawer(drawer) {
|
|
3539
|
+
var tools = dom.children(drawer, 'a').filter(function(tool) { return !dom.hasClass(tool, 'ge-drawer-more'); });
|
|
3540
|
+
var expanded = dom.hasClass(drawer, 'ge-drawer-expanded');
|
|
3541
|
+
|
|
3542
|
+
// Measured on one line, with every tool back on it
|
|
3543
|
+
dom.removeClass(drawer, 'ge-drawer-overflow ge-drawer-expanded');
|
|
3544
|
+
tools.forEach(function(tool) { dom.removeClass(tool, 'ge-tool-overflow'); });
|
|
3545
|
+
|
|
3546
|
+
if (!drawerOverflows(drawer, tools)) { return; }
|
|
3547
|
+
|
|
3548
|
+
dom.addClass(drawer, 'ge-drawer-overflow');
|
|
3549
|
+
if (!dom.child(drawer, '.ge-drawer-more')) {
|
|
3550
|
+
createTool(drawer, t('tool.more'), 'ge-drawer-more', 'bi bi-three-dots', function() {
|
|
3551
|
+
dom.toggleClass(drawer, 'ge-drawer-expanded');
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
for (var i = tools.length - 1; i >= 0 && drawerOverflows(drawer, tools); i--) {
|
|
3556
|
+
dom.addClass(tools[i], 'ge-tool-overflow');
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
dom.toggleClass(drawer, 'ge-drawer-expanded', expanded);
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
function drawerOverflows(drawer, tools) {
|
|
3563
|
+
var style = window.getComputedStyle(drawer);
|
|
3564
|
+
var limit = drawer.getBoundingClientRect().right -
|
|
3565
|
+
parseFloat(style.borderRightWidth) - parseFloat(style.paddingRight);
|
|
3566
|
+
|
|
3567
|
+
return tools.some(function(tool) {
|
|
3568
|
+
var rects = tool.getClientRects();
|
|
3569
|
+
return rects.length > 0 && rects[0].right > limit + 0.5;
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3333
3573
|
/**
|
|
3334
3574
|
* A tool in a drawer: a link with an icon, and what it does. The
|
|
3335
3575
|
* handlers are a click handler, or { eventName: handler }; each gets
|
|
@@ -5533,6 +5773,10 @@ GridEditor.locales = {
|
|
|
5533
5773
|
'tool.edit_source': 'Edit Source Code',
|
|
5534
5774
|
'tool.preview': 'Preview',
|
|
5535
5775
|
'tool.more': 'More',
|
|
5776
|
+
'group.rows': 'Rows',
|
|
5777
|
+
'group.content': 'Content',
|
|
5778
|
+
'group.elements': 'Elements',
|
|
5779
|
+
'group.select': 'Add',
|
|
5536
5780
|
'panel.title': '{kind} settings',
|
|
5537
5781
|
'panel.close': 'Close',
|
|
5538
5782
|
'panel.done': 'Done',
|
|
@@ -52,6 +52,10 @@ GridEditor.locales.es = {
|
|
|
52
52
|
'tool.edit_source': 'Editar el código fuente',
|
|
53
53
|
'tool.preview': 'Vista previa',
|
|
54
54
|
'tool.more': 'Más',
|
|
55
|
+
'group.rows': 'Filas',
|
|
56
|
+
'group.content': 'Contenido',
|
|
57
|
+
'group.elements': 'Elementos',
|
|
58
|
+
'group.select': 'Añadir',
|
|
55
59
|
'panel.title': 'Ajustes: {kind}',
|
|
56
60
|
'panel.close': 'Cerrar',
|
|
57
61
|
'panel.done': 'Hecho',
|
|
@@ -28,11 +28,95 @@ var NOT_ELEMENTS = '.row, .ge-content, .ge-text-block, [data-ge-container], .ge-
|
|
|
28
28
|
GridEditor.features.elements = function(ge) {
|
|
29
29
|
|
|
30
30
|
var warnedIntoText = false;
|
|
31
|
+
var types = validTypes();
|
|
31
32
|
|
|
32
33
|
function elementsEnabled() {
|
|
33
34
|
if (ge.settings.elements.enabled !== 'auto') { return !!ge.settings.elements.enabled; }
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
// A host that offers elements on the toolbar uses them, even on a
|
|
37
|
+
// page that has none yet
|
|
38
|
+
return ge.settings.elements.auto || types.length > 0 ||
|
|
39
|
+
!!ge.canvas.querySelector(ge.settings.elements.selector);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The elements.types the toolbar can offer. One that can't be made is left out, with a word to the host. */
|
|
43
|
+
function validTypes() {
|
|
44
|
+
return (ge.settings.elements.types || []).filter(function(type, index) {
|
|
45
|
+
var named = type && typeof type.type === 'string' && type.type.trim() !== '';
|
|
46
|
+
var html = type && (typeof type.html === 'function' || (typeof type.html === 'string' && type.html.trim() !== ''));
|
|
47
|
+
|
|
48
|
+
if (!named || !html) {
|
|
49
|
+
ge.warn('elements.types[' + index + ']: a type needs a type name and its html, as a string or a ' +
|
|
50
|
+
'function; it is left off the toolbar.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return named && html;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A type's toolbar button. */
|
|
58
|
+
function typeItem(type) {
|
|
59
|
+
var mismatched = false;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
// Asked again whenever the toolbar is built, so setLocale
|
|
63
|
+
// translates a labelKey
|
|
64
|
+
label: function() { return labelOf(type); },
|
|
65
|
+
iconClass: type.iconClass,
|
|
66
|
+
group: type.group || 'elements',
|
|
67
|
+
kind: 'element',
|
|
68
|
+
inColumn: true,
|
|
69
|
+
create: function() {
|
|
70
|
+
var element = makeElement(type);
|
|
71
|
+
|
|
72
|
+
if (element && !mismatched && !ge.settings.elements.auto && !element.matches(ge.settings.elements.selector)) {
|
|
73
|
+
mismatched = true;
|
|
74
|
+
ge.warn('elements.types "' + type.type + '": what it makes does not match elements.selector "' +
|
|
75
|
+
ge.settings.elements.selector + '", so it will not be an element once the editor looks again.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return element;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function labelOf(type) {
|
|
84
|
+
if (type.labelKey) { return ge.t(type.labelKey); }
|
|
85
|
+
|
|
86
|
+
return type.label || type.type;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* What a type's button makes: the root of its html, marked as the host
|
|
91
|
+
* marks its own, or a div around it when it has no single root. Null,
|
|
92
|
+
* with a word to the host, when there is nothing to make.
|
|
93
|
+
*/
|
|
94
|
+
function makeElement(type) {
|
|
95
|
+
var made;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
made = typeof type.html === 'function' ? type.html() : type.html;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
ge.warn('elements.types "' + type.type + '": its html function threw (' + error.message + '); nothing was added.');
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
var nodes = typeof made === 'string' ? dom.parse(made) : (made && made.nodeType ? [made] : []);
|
|
105
|
+
// Whitespace between tags is not content
|
|
106
|
+
nodes = nodes.filter(function(node) { return node.nodeType !== 3 || node.textContent.trim() !== ''; });
|
|
107
|
+
|
|
108
|
+
if (!nodes.length) {
|
|
109
|
+
ge.warn('elements.types "' + type.type + '": its html came out empty; nothing was added.');
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var element = nodes.length === 1 && nodes[0].nodeType === 1 ? nodes[0] : dom.element('div');
|
|
114
|
+
if (element !== nodes[0] || nodes.length > 1) { fill(element, nodes); }
|
|
115
|
+
|
|
116
|
+
element.setAttribute('data-ge-element', type.type);
|
|
117
|
+
if (!element.hasAttribute('data-ge-label')) { element.setAttribute('data-ge-label', labelOf(type)); }
|
|
118
|
+
|
|
119
|
+
return element;
|
|
36
120
|
}
|
|
37
121
|
|
|
38
122
|
/** Whether a child of a column is an element. */
|
|
@@ -121,6 +205,11 @@ GridEditor.features.elements = function(ge) {
|
|
|
121
205
|
return placed;
|
|
122
206
|
}
|
|
123
207
|
|
|
208
|
+
/** enabled: false takes the toolbar's element buttons away too. */
|
|
209
|
+
function elementsOff() {
|
|
210
|
+
return ge.settings.elements.enabled === false;
|
|
211
|
+
}
|
|
212
|
+
|
|
124
213
|
/** A place a caller named: an element, or the first one a selector matches. */
|
|
125
214
|
function nodeFrom(node) {
|
|
126
215
|
if (typeof node === 'string') { return document.querySelector(node); }
|
|
@@ -163,6 +252,10 @@ GridEditor.features.elements = function(ge) {
|
|
|
163
252
|
createElement: apiCreateElement,
|
|
164
253
|
},
|
|
165
254
|
|
|
255
|
+
// A button for each type the host offers, in the elements category
|
|
256
|
+
// unless the type names another
|
|
257
|
+
toolbar: elementsOff() ? [] : types.map(typeItem),
|
|
258
|
+
|
|
166
259
|
/** A node this plugin marked is an element, whatever else it is. */
|
|
167
260
|
kindOf: function(node) {
|
|
168
261
|
return dom.hasClass(node, 'ge-element') ? 'element' : null;
|