@themarioga/grid-editor 7.2.0 → 8.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 +101 -0
- package/README.md +96 -30
- package/UPGRADING.md +86 -0
- package/dist/grideditor.bundle.min.js +3 -3
- package/dist/grideditor.css +149 -6
- package/dist/grideditor.d.ts +18 -0
- package/dist/grideditor.esm.js +257 -48
- package/dist/grideditor.jquery.js +0 -13
- package/dist/grideditor.jquery.min.js +1 -1
- package/dist/grideditor.jquery.min.js.map +3 -3
- package/dist/grideditor.js +257 -48
- 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 +80 -6
- package/dist/locales/grideditor.es.js +80 -6
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +2 -2
- package/dist/plugins/grideditor.accordion.min.js.map +2 -2
- package/dist/plugins/grideditor.alignment.esm.js +11 -38
- package/dist/plugins/grideditor.alignment.js +11 -38
- package/dist/plugins/grideditor.alignment.min.js +1 -1
- package/dist/plugins/grideditor.alignment.min.js.map +3 -3
- package/dist/plugins/grideditor.card.min.js.map +1 -1
- package/dist/plugins/grideditor.ckeditor.esm.js +0 -5
- package/dist/plugins/grideditor.ckeditor.js +0 -5
- package/dist/plugins/grideditor.ckeditor.min.js +1 -1
- package/dist/plugins/grideditor.ckeditor.min.js.map +3 -3
- package/dist/plugins/grideditor.clipboard.min.js.map +1 -1
- package/dist/plugins/grideditor.codemirror-inline.min.js.map +1 -1
- package/dist/plugins/grideditor.codemirror.min.js.map +1 -1
- package/dist/plugins/grideditor.elements.min.js.map +2 -2
- package/dist/plugins/grideditor.gutters.min.js.map +1 -1
- package/dist/plugins/grideditor.order.min.js.map +1 -1
- package/dist/plugins/grideditor.popup.min.js.map +1 -1
- package/dist/plugins/grideditor.sections.min.js.map +2 -2
- package/dist/plugins/grideditor.style.esm.js +1926 -0
- package/dist/plugins/grideditor.style.js +1923 -0
- package/dist/plugins/grideditor.style.min.js +4 -0
- package/dist/plugins/grideditor.style.min.js.map +7 -0
- package/dist/plugins/grideditor.summernote.min.js.map +2 -2
- package/dist/plugins/grideditor.tabs.min.js.map +2 -2
- package/dist/plugins/grideditor.tinymce.esm.js +1 -8
- package/dist/plugins/grideditor.tinymce.js +1 -8
- package/dist/plugins/grideditor.tinymce.min.js +1 -1
- package/dist/plugins/grideditor.tinymce.min.js.map +3 -3
- package/docs/events.md +4 -6
- package/docs/locale-keys.md +99 -15
- package/docs/plugins.md +47 -23
- package/example/ckeditor.html +0 -5
- package/example/index.html +17 -2
- package/example/style.html +163 -0
- package/example/utilities.html +21 -16
- package/package.json +1 -1
- package/src/js/dom.js +1 -2
- package/src/js/grideditor.jquery.js +7 -28
- package/src/js/grideditor.js +336 -67
- package/src/js/locales/grideditor.es.js +80 -6
- package/src/js/plugins/grideditor.alignment.js +21 -50
- package/src/js/plugins/grideditor.ckeditor.js +10 -23
- package/src/js/plugins/grideditor.style.js +185 -0
- package/src/js/plugins/grideditor.summernote.js +4 -5
- package/src/js/plugins/grideditor.tinymce.js +4 -17
- package/src/js/style/accordion.js +168 -0
- package/src/js/style/catalog.js +76 -0
- package/src/js/style/custom.js +84 -0
- package/src/js/style/display.js +229 -0
- package/src/js/style/drawerflow.js +118 -0
- package/src/js/style/fields.js +382 -0
- package/src/js/style/flex.js +156 -0
- package/src/js/{plugins/grideditor.float.js → style/float.js} +6 -16
- package/src/js/style/options.js +60 -0
- package/src/js/style/sections.js +298 -0
- package/src/js/{plugins/grideditor.spacing.js → style/spacing.js} +7 -19
- package/src/js/style/sticky.js +39 -0
- package/src/js/{plugins/grideditor.textalign.js → style/textalign.js} +6 -14
- package/src/js/text/grideditor.text.js +1 -1
- package/src/less/grideditor.less +151 -10
- package/types/grideditor.d.ts +18 -0
- package/dist/plugins/grideditor.float.esm.js +0 -34
- package/dist/plugins/grideditor.float.js +0 -41
- package/dist/plugins/grideditor.float.min.js +0 -1
- package/dist/plugins/grideditor.float.min.js.map +0 -7
- package/dist/plugins/grideditor.spacing.esm.js +0 -318
- package/dist/plugins/grideditor.spacing.js +0 -323
- package/dist/plugins/grideditor.spacing.min.js +0 -1
- package/dist/plugins/grideditor.spacing.min.js.map +0 -7
- package/dist/plugins/grideditor.textalign.esm.js +0 -40
- package/dist/plugins/grideditor.textalign.js +0 -47
- package/dist/plugins/grideditor.textalign.min.js +0 -1
- package/dist/plugins/grideditor.textalign.min.js.map +0 -7
- package/dist/plugins/grideditor.visibility.esm.js +0 -158
- package/dist/plugins/grideditor.visibility.js +0 -163
- package/dist/plugins/grideditor.visibility.min.js +0 -1
- package/dist/plugins/grideditor.visibility.min.js.map +0 -7
- package/src/js/plugins/grideditor.visibility.js +0 -171
package/src/js/grideditor.js
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* A fork of https://github.com/Friendly-Pixel/grid-editor by Simon Epskamp,
|
|
5
5
|
* maintained at https://github.com/themarioga/grid-editor.
|
|
6
6
|
*
|
|
7
|
-
* Plain DOM
|
|
8
|
-
*
|
|
9
|
-
* see UPGRADING.md.
|
|
7
|
+
* Plain DOM: nothing here needs jQuery. A page written against the jQuery
|
|
8
|
+
* API loads grideditor.jquery.js beside this file.
|
|
10
9
|
*/
|
|
11
10
|
import * as dom from './dom.js';
|
|
12
11
|
|
|
@@ -73,8 +72,6 @@ var ALL_VIEW_LABEL_KEY = 'view.all';
|
|
|
73
72
|
/** Every view key the dropdown can offer, in the order it offers them. */
|
|
74
73
|
var VIEW_KEYS = [ALL_VIEW].concat(BREAKPOINTS.map(function(tier) { return tier.key; }));
|
|
75
74
|
|
|
76
|
-
/** What the three layout mode indexes of 2.x meant. */
|
|
77
|
-
var LEGACY_VIEW_INDEXES = ['lg', 'sm', 'xs'];
|
|
78
75
|
|
|
79
76
|
var MAX_COL_SIZE = 12;
|
|
80
77
|
var MAX_COL_OFFSET = 11;
|
|
@@ -158,12 +155,6 @@ var NESTED_SETTINGS = {
|
|
|
158
155
|
},
|
|
159
156
|
};
|
|
160
157
|
|
|
161
|
-
/** Settings 4.0 took away, and what a host should reach for instead. */
|
|
162
|
-
var REMOVED_SETTINGS = {
|
|
163
|
-
sortable_options: 'drag',
|
|
164
|
-
resizable_options: 'resize',
|
|
165
|
-
};
|
|
166
|
-
|
|
167
158
|
var warned = {};
|
|
168
159
|
|
|
169
160
|
/** Editors on the page, counted so each one's sortable groups are its own. */
|
|
@@ -372,7 +363,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
372
363
|
'element_tools' : [], // Host tools on element drawers, same shape as row_tools
|
|
373
364
|
'element_classes' : [], // Preset class toggles on an element's settings panel
|
|
374
365
|
// content_types, text_tools and text_classes are the text editor
|
|
375
|
-
// plugins' settings
|
|
366
|
+
// plugins' settings: see grideditor.text.js
|
|
376
367
|
'container_classes' : [], // The same, on a container's panel
|
|
377
368
|
'pane_classes' : [], // And on a tab's or an accordion item's
|
|
378
369
|
'container_tools' : [], // Host tools on container drawers
|
|
@@ -405,18 +396,6 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
405
396
|
settings[name] = Object.assign({}, NESTED_SETTINGS[name], settings[name]);
|
|
406
397
|
});
|
|
407
398
|
|
|
408
|
-
// Both handed out the drag toolkit's own options, which 4.0 stops
|
|
409
|
-
// promising: there is no widget underneath a host should be reaching
|
|
410
|
-
// for. What they were used for is a setting of the editor's now.
|
|
411
|
-
Object.keys(REMOVED_SETTINGS).forEach(function(name) {
|
|
412
|
-
if (optionsOrMethod && optionsOrMethod[name] !== undefined) {
|
|
413
|
-
warn(translate(settings, 'warning.setting_removed', {
|
|
414
|
-
setting: name,
|
|
415
|
-
replacement: REMOVED_SETTINGS[name],
|
|
416
|
-
}));
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
|
|
420
399
|
|
|
421
400
|
// Elems
|
|
422
401
|
var canvas,
|
|
@@ -432,7 +411,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
432
411
|
var sizePicker = null; // The open column size picker, if there is one
|
|
433
412
|
var sourceOpen = false; // Whether the canvas is being edited as html
|
|
434
413
|
var dropMarker = null; // The line showing where a dragged toolbar button would land
|
|
435
|
-
var warnedHere = {}; //
|
|
414
|
+
var warnedHere = {}; // Some warnings are worth saying once per instance, not once per call
|
|
436
415
|
var sortables = []; // Every list made sortable, so deinit destroys exactly those
|
|
437
416
|
var instanceId = ++editorCounter; // Scopes the sortable groups to this editor
|
|
438
417
|
var destroyed = false;
|
|
@@ -475,6 +454,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
475
454
|
|
|
476
455
|
removeConfirmModal();
|
|
477
456
|
removeSettingsPanels();
|
|
457
|
+
removeDialog();
|
|
478
458
|
mainControls.remove();
|
|
479
459
|
createMainControls();
|
|
480
460
|
reset();
|
|
@@ -1753,6 +1733,10 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1753
1733
|
if (openSettingsState && !dom.attached(openSettingsState.node)) {
|
|
1754
1734
|
closeSettings();
|
|
1755
1735
|
}
|
|
1736
|
+
// And a dialog showing part of a panel that is gone
|
|
1737
|
+
if (dialogState && dialogState.home && !dom.attached(dialogState.home)) {
|
|
1738
|
+
closeDialog();
|
|
1739
|
+
}
|
|
1756
1740
|
|
|
1757
1741
|
runFilter(true);
|
|
1758
1742
|
dom.addClass(canvas, 'ge-editing');
|
|
@@ -1781,7 +1765,10 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1781
1765
|
plugins('onBeforeDeinit');
|
|
1782
1766
|
closeSizePicker();
|
|
1783
1767
|
hideDropMarker();
|
|
1768
|
+
// In case a dialog was opened with no settings panel open
|
|
1769
|
+
closeDialog();
|
|
1784
1770
|
dom.all(canvas, '.ge-tools-drawer').forEach(function(drawer) { drawer.remove(); });
|
|
1771
|
+
pluginFields.clear();
|
|
1785
1772
|
unwrapTexts();
|
|
1786
1773
|
plugins('onDeinit');
|
|
1787
1774
|
// After the rich text editors have let go of their content areas:
|
|
@@ -1836,6 +1823,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1836
1823
|
deinit();
|
|
1837
1824
|
removeConfirmModal();
|
|
1838
1825
|
removeSettingsPanels();
|
|
1826
|
+
removeDialog();
|
|
1839
1827
|
mainControls.remove();
|
|
1840
1828
|
htmlTextArea.remove();
|
|
1841
1829
|
lifetime.abort();
|
|
@@ -1948,6 +1936,12 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1948
1936
|
setUtility: setUtility,
|
|
1949
1937
|
utilityField: utilityField,
|
|
1950
1938
|
bareStyle: bareStyle,
|
|
1939
|
+
// The host's own style, under the preview
|
|
1940
|
+
hostStyle: hostStyle,
|
|
1941
|
+
setHostStyle: setHostStyle,
|
|
1942
|
+
// A modal of the editor's own, over the settings
|
|
1943
|
+
openDialog: openDialog,
|
|
1944
|
+
closeDialog: closeDialog,
|
|
1951
1945
|
rowFromLayout: rowFromLayoutValue,
|
|
1952
1946
|
nodeHtml: nodeHtml,
|
|
1953
1947
|
// A text editor has rewritten a content area, so whatever the
|
|
@@ -2266,6 +2260,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2266
2260
|
if (classes) { classes.value = hostClasses(node).join(' '); }
|
|
2267
2261
|
dom.children(details, '.ge-utilities').forEach(function(section) { renderUtilities(section); });
|
|
2268
2262
|
}
|
|
2263
|
+
renderPluginFields(node);
|
|
2269
2264
|
refreshPreviews(node);
|
|
2270
2265
|
}
|
|
2271
2266
|
|
|
@@ -2362,6 +2357,84 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2362
2357
|
});
|
|
2363
2358
|
}
|
|
2364
2359
|
|
|
2360
|
+
/* The host's own style, under the preview */
|
|
2361
|
+
|
|
2362
|
+
/**
|
|
2363
|
+
* The node's style as the host wrote it: its declarations with every
|
|
2364
|
+
* property the preview set put back to what the preview recorded.
|
|
2365
|
+
* Held in a detached element's style, which parses and serializes it
|
|
2366
|
+
* the way the node's own would.
|
|
2367
|
+
*/
|
|
2368
|
+
function hostDeclaration(node) {
|
|
2369
|
+
var scratch = document.createElement('div');
|
|
2370
|
+
var was = {};
|
|
2371
|
+
|
|
2372
|
+
scratch.style.cssText = node.style.cssText;
|
|
2373
|
+
try { was = JSON.parse(node.getAttribute(PREVIEW_ATTR)) || {}; } catch (error) { /* no record */ }
|
|
2374
|
+
|
|
2375
|
+
Object.keys(was).forEach(function(property) {
|
|
2376
|
+
var before = was[property];
|
|
2377
|
+
|
|
2378
|
+
if (before && before[0]) {
|
|
2379
|
+
scratch.style.setProperty(property, before[0], before[1]);
|
|
2380
|
+
} else {
|
|
2381
|
+
scratch.style.removeProperty(property);
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2384
|
+
|
|
2385
|
+
return scratch.style;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
/**
|
|
2389
|
+
* ge.hostStyle(node, property): { value, priority } of one property
|
|
2390
|
+
* of the host's style, never the preview's. With no property, the
|
|
2391
|
+
* host's whole style as css text.
|
|
2392
|
+
*/
|
|
2393
|
+
function hostStyle(node, property) {
|
|
2394
|
+
var declaration = hostDeclaration(node);
|
|
2395
|
+
|
|
2396
|
+
if (property === undefined) { return declaration.cssText; }
|
|
2397
|
+
|
|
2398
|
+
return {
|
|
2399
|
+
value: declaration.getPropertyValue(property),
|
|
2400
|
+
priority: declaration.getPropertyPriority(property),
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
/**
|
|
2405
|
+
* ge.setHostStyle(node, property, value, priority): write one property
|
|
2406
|
+
* of the host's style, or take it off with an empty value. The
|
|
2407
|
+
* preview comes off the node while it is written and goes back on
|
|
2408
|
+
* after, so what the preview put back on deinit is the host's new
|
|
2409
|
+
* value. False, and nothing written, when the browser refuses the
|
|
2410
|
+
* value. `priority` left out keeps the one the property had.
|
|
2411
|
+
*/
|
|
2412
|
+
function setHostStyle(node, property, value, priority) {
|
|
2413
|
+
value = value === null || value === undefined ? '' : String(value).trim();
|
|
2414
|
+
|
|
2415
|
+
if (priority === undefined) { priority = hostStyle(node, property).priority; }
|
|
2416
|
+
|
|
2417
|
+
if (value !== '') {
|
|
2418
|
+
var check = document.createElement('div').style;
|
|
2419
|
+
check.setProperty(property, value, priority || '');
|
|
2420
|
+
if (check.getPropertyValue(property) === '') { return false; }
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
var previewed = node.hasAttribute(PREVIEW_ATTR);
|
|
2424
|
+
if (previewed) { clearPreviews(node); }
|
|
2425
|
+
|
|
2426
|
+
if (value === '') {
|
|
2427
|
+
node.style.removeProperty(property);
|
|
2428
|
+
} else {
|
|
2429
|
+
node.style.setProperty(property, value, priority || '');
|
|
2430
|
+
}
|
|
2431
|
+
dom.dropEmptyStyle(node);
|
|
2432
|
+
|
|
2433
|
+
if (previewed) { refreshPreviews(node); }
|
|
2434
|
+
|
|
2435
|
+
return true;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2365
2438
|
/* The panel */
|
|
2366
2439
|
|
|
2367
2440
|
/**
|
|
@@ -2434,10 +2507,32 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2434
2507
|
|
|
2435
2508
|
var field = createField(node, family);
|
|
2436
2509
|
renderField(field, node);
|
|
2510
|
+
pluginFields.set(field, node);
|
|
2437
2511
|
|
|
2438
2512
|
return field;
|
|
2439
2513
|
}
|
|
2440
2514
|
|
|
2515
|
+
/**
|
|
2516
|
+
* The fields a plugin made, wherever it put them: a Responsive
|
|
2517
|
+
* section renders its own, and these are the rest - in a plugin's
|
|
2518
|
+
* section of the panel, or in the dialog. Each one's node's, or
|
|
2519
|
+
* every node's; a field no longer on the page is forgotten.
|
|
2520
|
+
*/
|
|
2521
|
+
var pluginFields = new Map(); // field -> the node it edits
|
|
2522
|
+
|
|
2523
|
+
function renderPluginFields(node) {
|
|
2524
|
+
pluginFields.forEach(function(fieldNode, field) {
|
|
2525
|
+
if (!field.isConnected) {
|
|
2526
|
+
pluginFields.delete(field);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
if (node && fieldNode !== node) { return; }
|
|
2530
|
+
if (dom.closest(field, '.ge-utilities')) { return; }
|
|
2531
|
+
|
|
2532
|
+
renderField(field, fieldNode);
|
|
2533
|
+
});
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2441
2536
|
/** Fill a section's fields, the families' and the plugins' own, for the view being edited. */
|
|
2442
2537
|
function renderUtilities(section) {
|
|
2443
2538
|
var node = sectionNodes.get(section);
|
|
@@ -3146,8 +3241,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3146
3241
|
refreshUtilities(container);
|
|
3147
3242
|
});
|
|
3148
3243
|
|
|
3149
|
-
// Bootstrap
|
|
3150
|
-
// up to 5.x they carried Bootstrap 3's btn-default, which 5 lacks
|
|
3244
|
+
// Bootstrap's outline buttons, filled while their class is on
|
|
3151
3245
|
var classGroup = general.appendChild(dom.element('div', { 'class': 'btn-group btn-group-sm ge-presets', role: 'group' }));
|
|
3152
3246
|
cssClasses.forEach(function(rowClass) {
|
|
3153
3247
|
var on = dom.hasClass(container, rowClass.cssClass);
|
|
@@ -3158,7 +3252,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3158
3252
|
'aria-pressed': on ? 'true' : 'false',
|
|
3159
3253
|
});
|
|
3160
3254
|
|
|
3161
|
-
// A preset's label is the host's markup
|
|
3255
|
+
// A preset's label is the host's markup
|
|
3162
3256
|
btn.innerHTML = rowClass.label;
|
|
3163
3257
|
dom.toggleClass(btn, 'active', on);
|
|
3164
3258
|
btn.addEventListener('click', function() {
|
|
@@ -3170,12 +3264,59 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3170
3264
|
classGroup.appendChild(btn);
|
|
3171
3265
|
});
|
|
3172
3266
|
|
|
3267
|
+
pluginSections(container).forEach(function(section) { detailsDiv.appendChild(section); });
|
|
3268
|
+
|
|
3173
3269
|
var utilities = createUtilitiesSection(container);
|
|
3174
3270
|
if (utilities) { detailsDiv.appendChild(utilities); }
|
|
3175
3271
|
|
|
3176
3272
|
return detailsDiv;
|
|
3177
3273
|
}
|
|
3178
3274
|
|
|
3275
|
+
/**
|
|
3276
|
+
* What plugins add to a node's panel, between its general fields and
|
|
3277
|
+
* the Responsive section: each plugin's panelSection(node, kind), in
|
|
3278
|
+
* the order they were registered. Where the panel has room - the
|
|
3279
|
+
* offcanvas, the modal - the section is in it. A popover or a panel
|
|
3280
|
+
* inline in the drawer gets a button instead, and the section opens
|
|
3281
|
+
* in the dialog.
|
|
3282
|
+
*/
|
|
3283
|
+
function pluginSections(node) {
|
|
3284
|
+
var kind = kindOf(node);
|
|
3285
|
+
var mode = panelMode();
|
|
3286
|
+
var sections = [];
|
|
3287
|
+
|
|
3288
|
+
[CONTAINERS, FEATURES, UTILITIES].forEach(function(registry) {
|
|
3289
|
+
Object.keys(registry).forEach(function(name) {
|
|
3290
|
+
var plugin = registry[name];
|
|
3291
|
+
var section = plugin.panelSection ? plugin.panelSection(node, kind) : null;
|
|
3292
|
+
if (!section || !section.body) { return; }
|
|
3293
|
+
|
|
3294
|
+
var holder = dom.element('div', { 'class': 'ge-panel-section', 'data-ge-plugin': name });
|
|
3295
|
+
|
|
3296
|
+
if (mode === 'offcanvas' || mode === 'modal') {
|
|
3297
|
+
holder.appendChild(section.body);
|
|
3298
|
+
} else {
|
|
3299
|
+
var label = t(section.labelKey);
|
|
3300
|
+
var open = holder.appendChild(dom.element('button', {
|
|
3301
|
+
type: 'button',
|
|
3302
|
+
'class': 'btn btn-sm btn-outline-secondary ge-panel-section-open',
|
|
3303
|
+
}, label));
|
|
3304
|
+
|
|
3305
|
+
dom.hide(section.body);
|
|
3306
|
+
holder.appendChild(section.body);
|
|
3307
|
+
open.addEventListener('click', function() {
|
|
3308
|
+
openDialog(section.titleKey ? t(section.titleKey, { kind: kindLabel(node) }) : label,
|
|
3309
|
+
section.body, open);
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
sections.push(holder);
|
|
3314
|
+
});
|
|
3315
|
+
});
|
|
3316
|
+
|
|
3317
|
+
return sections;
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3179
3320
|
/* --------------------------------------------------------------
|
|
3180
3321
|
* Where a node's settings open.
|
|
3181
3322
|
*
|
|
@@ -3194,7 +3335,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3194
3335
|
var PANEL_MODES = ['offcanvas', 'popover', 'modal', 'inline'];
|
|
3195
3336
|
var settingsPanels = {}; // One of each, built on first use, outside the canvas
|
|
3196
3337
|
var openSettingsState = null; // What is open: { mode, node, details, home, next, gear }
|
|
3197
|
-
var modalState = new WeakMap(); // modal panel -> { watched, wanted, busy }
|
|
3338
|
+
var modalState = new WeakMap(); // modal panel -> { watched, wanted, busy, backdrop, dismissed }
|
|
3198
3339
|
|
|
3199
3340
|
function panelMode() {
|
|
3200
3341
|
if (PANEL_MODES.indexOf(settings.settings_panel) !== -1) { return settings.settings_panel; }
|
|
@@ -3251,7 +3392,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3251
3392
|
|
|
3252
3393
|
var signal = { signal: listening.signal };
|
|
3253
3394
|
document.addEventListener('keydown', function(e) {
|
|
3254
|
-
|
|
3395
|
+
// A dialog over the panel takes the Escape for itself
|
|
3396
|
+
if (e.key === 'Escape' && !dialogState) { closeSettings(); }
|
|
3255
3397
|
}, signal);
|
|
3256
3398
|
|
|
3257
3399
|
if (mode === 'offcanvas') {
|
|
@@ -3274,15 +3416,20 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3274
3416
|
}
|
|
3275
3417
|
// A press anywhere else puts it away, as a popover does
|
|
3276
3418
|
dom.on(document, 'mousedown touchstart', function(e) {
|
|
3277
|
-
if (!panel.contains(e.target) && !gear.contains(e.target)) { closeSettings(); }
|
|
3419
|
+
if (!dialogState && !panel.contains(e.target) && !gear.contains(e.target)) { closeSettings(); }
|
|
3278
3420
|
}, signal);
|
|
3279
3421
|
} else {
|
|
3280
|
-
showModal(panel)
|
|
3422
|
+
showModal(panel, function() {
|
|
3423
|
+
if (openSettingsState && openSettingsState.mode === 'modal') { closeSettings(); }
|
|
3424
|
+
});
|
|
3281
3425
|
}
|
|
3282
3426
|
}
|
|
3283
3427
|
|
|
3284
3428
|
/** Put what is open away, and its panel back in its drawer. */
|
|
3285
3429
|
function closeSettings() {
|
|
3430
|
+
// A dialog opened from the panel goes with it
|
|
3431
|
+
closeDialog();
|
|
3432
|
+
|
|
3286
3433
|
var open = openSettingsState;
|
|
3287
3434
|
if (!open) { return; }
|
|
3288
3435
|
openSettingsState = null;
|
|
@@ -3381,7 +3528,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3381
3528
|
settingsPanels[mode].remove();
|
|
3382
3529
|
}
|
|
3383
3530
|
});
|
|
3384
|
-
|
|
3531
|
+
Object.keys(settingsPanels).forEach(function(mode) { removeBackdrop(settingsPanels[mode]); });
|
|
3385
3532
|
settingsPanels = {};
|
|
3386
3533
|
}
|
|
3387
3534
|
|
|
@@ -3424,23 +3571,34 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3424
3571
|
});
|
|
3425
3572
|
}
|
|
3426
3573
|
|
|
3427
|
-
var settingsBackdrop = null; // The modal's backdrop, when Bootstrap's javascript is not there to make one
|
|
3428
|
-
|
|
3429
3574
|
/**
|
|
3430
3575
|
* Bootstrap's modal ignores a hide while it is still fading in, and a
|
|
3431
3576
|
* show while it is fading out, so the editor asks for what it wants
|
|
3432
3577
|
* and has it done once the running transition ends: a panel closed as
|
|
3433
3578
|
* it opens - getHtml, a second click - would otherwise stay open, and
|
|
3434
3579
|
* empty. A hide Bootstrap starts itself, from Escape or the backdrop,
|
|
3435
|
-
*
|
|
3580
|
+
* calls `dismissed`, as the close button does: the settings modal
|
|
3581
|
+
* closes the settings, the dialog closes the dialog.
|
|
3582
|
+
*
|
|
3583
|
+
* Without Bootstrap's javascript the modal is shown by hand, over a
|
|
3584
|
+
* backdrop of the editor's own that dismisses it when clicked.
|
|
3436
3585
|
*/
|
|
3437
|
-
function showModal(panel) {
|
|
3586
|
+
function showModal(panel, dismissed) {
|
|
3438
3587
|
var Modal = modalLibrary();
|
|
3588
|
+
var state = modalState.get(panel);
|
|
3589
|
+
|
|
3590
|
+
if (!state) {
|
|
3591
|
+
state = { wanted: null, busy: false, backdrop: null, dismissed: dismissed };
|
|
3592
|
+
modalState.set(panel, state);
|
|
3593
|
+
}
|
|
3594
|
+
state.dismissed = dismissed;
|
|
3439
3595
|
|
|
3440
3596
|
if (!Modal) {
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3597
|
+
if (!state.backdrop) {
|
|
3598
|
+
state.backdrop = dom.element('div', { 'class': 'modal-backdrop fade show ge-settings-backdrop' });
|
|
3599
|
+
state.backdrop.addEventListener('click', function() { state.dismissed(); });
|
|
3600
|
+
document.body.appendChild(state.backdrop);
|
|
3601
|
+
}
|
|
3444
3602
|
dom.addClass(panel, 'show');
|
|
3445
3603
|
panel.style.display = 'block';
|
|
3446
3604
|
panel.removeAttribute('aria-hidden');
|
|
@@ -3448,11 +3606,9 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3448
3606
|
}
|
|
3449
3607
|
|
|
3450
3608
|
var modal = Modal.getOrCreateInstance(panel);
|
|
3451
|
-
var state = modalState.get(panel);
|
|
3452
3609
|
|
|
3453
|
-
if (!state) {
|
|
3454
|
-
state =
|
|
3455
|
-
modalState.set(panel, state);
|
|
3610
|
+
if (!state.watched) {
|
|
3611
|
+
state.watched = true;
|
|
3456
3612
|
trackModal(panel);
|
|
3457
3613
|
|
|
3458
3614
|
panel.addEventListener('hide.bs.modal', function() {
|
|
@@ -3469,9 +3625,9 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3469
3625
|
panel.addEventListener('hidden.bs.modal', function() {
|
|
3470
3626
|
state.busy = false;
|
|
3471
3627
|
if (state.wanted === 'open') {
|
|
3472
|
-
showModal(panel);
|
|
3473
|
-
} else
|
|
3474
|
-
|
|
3628
|
+
showModal(panel, state.dismissed);
|
|
3629
|
+
} else {
|
|
3630
|
+
state.dismissed();
|
|
3475
3631
|
}
|
|
3476
3632
|
});
|
|
3477
3633
|
}
|
|
@@ -3485,18 +3641,19 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3485
3641
|
|
|
3486
3642
|
function hideModal(panel) {
|
|
3487
3643
|
var Modal = modalLibrary();
|
|
3644
|
+
var state = modalState.get(panel);
|
|
3645
|
+
|
|
3646
|
+
if (!state) { return; }
|
|
3647
|
+
state.wanted = 'closed';
|
|
3488
3648
|
|
|
3489
3649
|
if (!Modal) {
|
|
3490
|
-
if (
|
|
3650
|
+
if (state.backdrop) { state.backdrop.remove(); state.backdrop = null; }
|
|
3491
3651
|
dom.removeClass(panel, 'show');
|
|
3492
3652
|
panel.style.removeProperty('display');
|
|
3493
3653
|
panel.setAttribute('aria-hidden', 'true');
|
|
3494
3654
|
return;
|
|
3495
3655
|
}
|
|
3496
3656
|
|
|
3497
|
-
var state = modalState.get(panel) || { wanted: null, busy: false };
|
|
3498
|
-
state.wanted = 'closed';
|
|
3499
|
-
|
|
3500
3657
|
// Already out of sight - Bootstrap hid it itself, and this is its
|
|
3501
3658
|
// hidden event closing the settings - and nothing is under way:
|
|
3502
3659
|
// a hide now would be ignored, and no event would come to say so
|
|
@@ -3506,6 +3663,131 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3506
3663
|
}
|
|
3507
3664
|
}
|
|
3508
3665
|
|
|
3666
|
+
/** A modal's hand made backdrop goes with it. */
|
|
3667
|
+
function removeBackdrop(panel) {
|
|
3668
|
+
var state = modalState.get(panel);
|
|
3669
|
+
if (state && state.backdrop) { state.backdrop.remove(); state.backdrop = null; }
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
/* --------------------------------------------------------------
|
|
3673
|
+
* The dialog: a modal of the editor's own, over whatever settings
|
|
3674
|
+
* panel is open, for what does not fit in one. A plugin's section
|
|
3675
|
+
* of the panel opens in it when the panel is a popover or inline,
|
|
3676
|
+
* and a plugin can open one itself through ge.openDialog.
|
|
3677
|
+
*
|
|
3678
|
+
* What it shows is borrowed: the body goes into the dialog when it
|
|
3679
|
+
* opens and back where it was when it closes, so everything that
|
|
3680
|
+
* finds it through its node keeps working. One per editor, built
|
|
3681
|
+
* on first use, outside the canvas.
|
|
3682
|
+
* -------------------------------------------------------------- */
|
|
3683
|
+
|
|
3684
|
+
var dialogPanel = null;
|
|
3685
|
+
var dialogState = null; // What is open: { body, home, next, opener, listening }
|
|
3686
|
+
|
|
3687
|
+
function dialog() {
|
|
3688
|
+
if (dialogPanel) { return dialogPanel; }
|
|
3689
|
+
|
|
3690
|
+
dialogPanel = dom.create('<div class="modal fade ge-dialog" tabindex="-1" role="dialog" aria-hidden="true">' +
|
|
3691
|
+
'<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">' +
|
|
3692
|
+
'<div class="modal-content">' +
|
|
3693
|
+
'<div class="modal-header"><h5 class="modal-title ge-dialog-title"></h5></div>' +
|
|
3694
|
+
'<div class="modal-body ge-dialog-body"></div>' +
|
|
3695
|
+
'<div class="modal-footer"></div>' +
|
|
3696
|
+
'</div>' +
|
|
3697
|
+
'</div>' +
|
|
3698
|
+
'</div>');
|
|
3699
|
+
dom.one(dialogPanel, '.modal-header').appendChild(dom.element('button', {
|
|
3700
|
+
type: 'button',
|
|
3701
|
+
'class': 'btn-close ge-dialog-close',
|
|
3702
|
+
'aria-label': t('panel.close'),
|
|
3703
|
+
}));
|
|
3704
|
+
dom.one(dialogPanel, '.modal-footer').appendChild(dom.element('button', {
|
|
3705
|
+
type: 'button',
|
|
3706
|
+
'class': 'btn btn-primary ge-dialog-close',
|
|
3707
|
+
}, t('panel.done')));
|
|
3708
|
+
|
|
3709
|
+
dom.delegate(dialogPanel, 'click', '.ge-dialog-close', function(e) {
|
|
3710
|
+
e.preventDefault();
|
|
3711
|
+
closeDialog();
|
|
3712
|
+
});
|
|
3713
|
+
|
|
3714
|
+
return document.body.appendChild(dialogPanel);
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
/**
|
|
3718
|
+
* ge.openDialog(title, body, opener): show `body` in the dialog. One
|
|
3719
|
+
* already open closes first. `opener`, when given, has the focus back
|
|
3720
|
+
* when it closes.
|
|
3721
|
+
*/
|
|
3722
|
+
function openDialog(title, body, opener) {
|
|
3723
|
+
closeDialog();
|
|
3724
|
+
|
|
3725
|
+
var panel = dialog();
|
|
3726
|
+
var listening = new AbortController();
|
|
3727
|
+
|
|
3728
|
+
dialogState = {
|
|
3729
|
+
body: body,
|
|
3730
|
+
home: body.parentElement,
|
|
3731
|
+
next: body.nextElementSibling,
|
|
3732
|
+
opener: opener || document.activeElement,
|
|
3733
|
+
listening: listening,
|
|
3734
|
+
};
|
|
3735
|
+
|
|
3736
|
+
dom.one(panel, '.ge-dialog-title').textContent = title;
|
|
3737
|
+
dom.one(panel, '.ge-dialog-body').appendChild(body);
|
|
3738
|
+
dom.show(body);
|
|
3739
|
+
|
|
3740
|
+
// Bootstrap's modal takes its own Escape; a hand made one does not
|
|
3741
|
+
if (!modalLibrary()) {
|
|
3742
|
+
document.addEventListener('keydown', function(e) {
|
|
3743
|
+
if (e.key === 'Escape') { closeDialog(); }
|
|
3744
|
+
}, { signal: listening.signal });
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
// Bootstrap puts a popover over a modal: the panel the dialog was
|
|
3748
|
+
// opened from goes under its backdrop while it is up
|
|
3749
|
+
Object.keys(settingsPanels).forEach(function(mode) {
|
|
3750
|
+
dom.addClass(settingsPanels[mode], 'ge-under-dialog');
|
|
3751
|
+
});
|
|
3752
|
+
|
|
3753
|
+
showModal(panel, function() { closeDialog(); });
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
/** ge.closeDialog(): put the dialog away, and what it showed back where it was. */
|
|
3757
|
+
function closeDialog() {
|
|
3758
|
+
var open = dialogState;
|
|
3759
|
+
if (!open) { return; }
|
|
3760
|
+
dialogState = null;
|
|
3761
|
+
|
|
3762
|
+
open.listening.abort();
|
|
3763
|
+
hideModal(dialogPanel);
|
|
3764
|
+
Object.keys(settingsPanels).forEach(function(mode) {
|
|
3765
|
+
dom.removeClass(settingsPanels[mode], 'ge-under-dialog');
|
|
3766
|
+
});
|
|
3767
|
+
|
|
3768
|
+
dom.hide(open.body);
|
|
3769
|
+
if (open.home && dom.attached(open.home)) {
|
|
3770
|
+
if (open.next && open.next.parentElement === open.home) {
|
|
3771
|
+
open.home.insertBefore(open.body, open.next);
|
|
3772
|
+
} else {
|
|
3773
|
+
open.home.appendChild(open.body);
|
|
3774
|
+
}
|
|
3775
|
+
} else {
|
|
3776
|
+
open.body.remove();
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
if (open.opener && dom.attached(open.opener) && open.opener.focus) { open.opener.focus(); }
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
function removeDialog() {
|
|
3783
|
+
closeDialog();
|
|
3784
|
+
if (!dialogPanel) { return; }
|
|
3785
|
+
|
|
3786
|
+
removeBackdrop(dialogPanel);
|
|
3787
|
+
retireModal(dialogPanel);
|
|
3788
|
+
dialogPanel = null;
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3509
3791
|
/** What a panel's title calls a node. */
|
|
3510
3792
|
function kindLabel(node) {
|
|
3511
3793
|
var kind = kindOf(node);
|
|
@@ -4802,20 +5084,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4802
5084
|
dom.one(layoutDropdown, 'button').textContent = t(labelKeyFor(view));
|
|
4803
5085
|
}
|
|
4804
5086
|
|
|
4805
|
-
/**
|
|
4806
|
-
* The view key a caller asked for, or null. 2.x callers passed a
|
|
4807
|
-
* layout mode index, which still works and says so once.
|
|
4808
|
-
*/
|
|
5087
|
+
/** The view key a caller asked for, or null. */
|
|
4809
5088
|
function viewKey(view) {
|
|
4810
|
-
if (typeof view == 'number') {
|
|
4811
|
-
warnOnceHere('changeView-index', 'changeView(' + view + '): layout modes are ' +
|
|
4812
|
-
'identified by breakpoint key now, so pass one of ' +
|
|
4813
|
-
JSON.stringify(VIEW_KEYS) + '. Numeric indexes still work, ' +
|
|
4814
|
-
'but they mean what they meant in 2.x (' +
|
|
4815
|
-
LEGACY_VIEW_INDEXES.join(', ') + ') and will be dropped.');
|
|
4816
|
-
return LEGACY_VIEW_INDEXES[view] || null;
|
|
4817
|
-
}
|
|
4818
|
-
|
|
4819
5089
|
return VIEW_KEYS.indexOf(view) === -1 ? null : view;
|
|
4820
5090
|
}
|
|
4821
5091
|
|
|
@@ -4835,6 +5105,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4835
5105
|
settingsScope().forEach(function(scope) {
|
|
4836
5106
|
dom.all(scope, '.ge-utilities').forEach(function(section) { renderUtilities(section); });
|
|
4837
5107
|
});
|
|
5108
|
+
renderPluginFields(null);
|
|
4838
5109
|
refreshPreviews(canvas);
|
|
4839
5110
|
plugins('onViewChange', key);
|
|
4840
5111
|
emit('view-change', { canvas: canvas, breakpoint: key, from: from, to: key });
|
|
@@ -5071,14 +5342,12 @@ GridEditor.locales = {
|
|
|
5071
5342
|
'utility.inherit': 'Inherit: {value} (from {breakpoint})',
|
|
5072
5343
|
'utility.varies': 'Changes at {breakpoints}; choosing here replaces that',
|
|
5073
5344
|
'error.sortable_missing': 'SortableJS not available! Make sure you loaded the Sortable js file; dragging is off without it.',
|
|
5074
|
-
'warning.setting_removed': 'The {setting} setting was removed in 4.0. Use {replacement} instead.',
|
|
5075
5345
|
'warning.already_editing': 'This element already has an editor: that one is handed back, with the options it was made with.',
|
|
5076
5346
|
'warning.destroyed': '{method}() was called on an editor that has been destroyed, and does nothing.',
|
|
5077
5347
|
'warning.duplicate_build': 'grideditor.js was loaded twice: the first GridEditor is kept.',
|
|
5078
|
-
'warning.plugin_6x': 'The "{name}" plugin is written for grid-editor 6 and is not loaded. Plugins register on GridEditor since 7.0: see UPGRADING.md.',
|
|
5079
5348
|
'warning.adapter_no_jquery': 'grideditor.jquery.js needs jQuery 4, and there is no jQuery on the page: the jQuery API is not there.',
|
|
5080
5349
|
'error.tinymce_missing': 'tinyMCE not available! Make sure you loaded the tinyMCE js file.',
|
|
5081
|
-
'error.ckeditor_missing': 'CKEditor 5 not available! Make sure you loaded its ckeditor5.umd.js file
|
|
5350
|
+
'error.ckeditor_missing': 'CKEditor 5 not available! Make sure you loaded its ckeditor5.umd.js file.',
|
|
5082
5351
|
'error.summernote_missing': 'Summernote not available! Make sure you loaded jQuery and the Summernote js file.',
|
|
5083
5352
|
},
|
|
5084
5353
|
};
|