@themarioga/grid-editor 5.3.1 → 6.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/BUILDING.md +2 -4
- package/CHANGELOG.md +59 -0
- package/README.md +34 -8
- package/UPGRADING.md +57 -12
- package/dist/grideditor.css +179 -100
- package/dist/grideditor.min.css +1 -1
- package/dist/grideditor.min.css.map +1 -1
- package/dist/jquery.grideditor.bundle.min.js +2 -2
- package/dist/jquery.grideditor.js +490 -432
- package/dist/jquery.grideditor.min.js +1 -1
- package/dist/jquery.grideditor.min.js.map +1 -1
- package/dist/locales/grideditor.es.js +12 -0
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +1 -1
- package/dist/plugins/grideditor.ckeditor.js +2 -1
- package/dist/plugins/grideditor.ckeditor.min.js.map +1 -1
- package/dist/plugins/grideditor.clipboard.js +1 -11
- package/dist/plugins/grideditor.clipboard.min.js +1 -1
- package/dist/plugins/grideditor.clipboard.min.js.map +1 -1
- package/dist/plugins/grideditor.elements.js +66 -38
- package/dist/plugins/grideditor.elements.min.js +1 -1
- package/dist/plugins/grideditor.elements.min.js.map +1 -1
- package/dist/plugins/grideditor.sections.js +2 -2
- package/dist/plugins/grideditor.sections.min.js +1 -1
- package/dist/plugins/grideditor.sections.min.js.map +1 -1
- package/dist/plugins/grideditor.spacing.js +1 -1
- package/dist/plugins/grideditor.spacing.min.js +1 -1
- package/dist/plugins/grideditor.spacing.min.js.map +1 -1
- package/dist/plugins/grideditor.summernote.js +2 -1
- package/dist/plugins/grideditor.summernote.min.js.map +1 -1
- package/dist/plugins/grideditor.tinymce.js +2 -1
- package/dist/plugins/grideditor.tinymce.min.js.map +1 -1
- package/docs/events.md +4 -3
- package/docs/locale-keys.md +23 -0
- package/docs/plugins.md +18 -20
- package/example/attributes.html +21 -37
- package/example/elements.html +25 -20
- package/example/plugins.html +5 -5
- package/example/utilities.html +31 -8
- package/package.json +1 -1
- package/src/js/jquery.grideditor.js +489 -34
- package/src/js/locales/grideditor.es.js +12 -0
- package/src/js/plugins/grideditor.ckeditor.js +2 -1
- package/src/js/plugins/grideditor.clipboard.js +1 -11
- package/src/js/plugins/grideditor.elements.js +66 -38
- package/src/js/plugins/grideditor.sections.js +2 -2
- package/src/js/plugins/grideditor.spacing.js +1 -1
- package/src/js/plugins/grideditor.summernote.js +2 -1
- package/src/js/plugins/grideditor.tinymce.js +2 -1
- package/src/less/grideditor.less +203 -112
- package/src/js/compat/bundled-texts.js +0 -14
|
@@ -363,6 +363,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
363
363
|
'locale_strings' : {}, // Overrides for individual keys
|
|
364
364
|
'callbacks' : {}, // before_*/after_* functions, the events by another route
|
|
365
365
|
'confirm_delete' : true, // Ask before deleting a row or a column
|
|
366
|
+
'settings_panel' : 'offcanvas', // Where a node's settings open: 'offcanvas', 'popover', 'modal' or 'inline'
|
|
366
367
|
'drag' : NESTED_SETTINGS.drag // How a drag behaves, whatever drives it
|
|
367
368
|
}, optionsOrMethod);
|
|
368
369
|
|
|
@@ -426,6 +427,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
426
427
|
handle.settings = settingsCopy();
|
|
427
428
|
|
|
428
429
|
removeConfirmModal();
|
|
430
|
+
removeSettingsPanels();
|
|
429
431
|
mainControls.remove();
|
|
430
432
|
createMainControls();
|
|
431
433
|
reset();
|
|
@@ -1480,12 +1482,6 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1480
1482
|
return;
|
|
1481
1483
|
}
|
|
1482
1484
|
|
|
1483
|
-
if (text.bundled) {
|
|
1484
|
-
warnOnceHere('text-bundled:' + type, 'the ' + type + ' text editor is the copy in the ' +
|
|
1485
|
-
'main bundle, which 6.0 will not include: load dist/plugins/grideditor.' + type +
|
|
1486
|
-
'.js after the editor');
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
1485
|
$(this).data('ge-text-before', attributesOf(this)).removeData('ge-text-ready');
|
|
1490
1486
|
$(this).addClass('ge-rte-active');
|
|
1491
1487
|
text.start($(this));
|
|
@@ -1497,11 +1493,19 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1497
1493
|
}
|
|
1498
1494
|
|
|
1499
1495
|
function init() {
|
|
1496
|
+
// The node whose settings were open is gone - deleted, say - and
|
|
1497
|
+
// its panel with it
|
|
1498
|
+
if (openSettingsState && !$.contains(document.documentElement, openSettingsState.node[0])) {
|
|
1499
|
+
closeSettings();
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1500
1502
|
runFilter(true);
|
|
1501
1503
|
canvas.addClass('ge-editing');
|
|
1502
1504
|
canvas.toggleClass('ge-drag-drawer', settings.drag_handle === 'drawer');
|
|
1503
1505
|
addAllColClasses();
|
|
1504
|
-
|
|
1506
|
+
var cutter = textCutter();
|
|
1507
|
+
splitTexts(cutter);
|
|
1508
|
+
wrapContent(cutter);
|
|
1505
1509
|
wrapTexts();
|
|
1506
1510
|
createRowControls();
|
|
1507
1511
|
createColControls();
|
|
@@ -1514,6 +1518,8 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1514
1518
|
}
|
|
1515
1519
|
|
|
1516
1520
|
function deinit() {
|
|
1521
|
+
// Its panel goes home to its drawer first, and both go together
|
|
1522
|
+
closeSettings();
|
|
1517
1523
|
canvas.removeClass('ge-editing ge-drag-drawer ge-dropping');
|
|
1518
1524
|
canvas.find('.ge-content').each(function() {
|
|
1519
1525
|
closeText($(this));
|
|
@@ -1566,6 +1572,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1566
1572
|
function destroy() {
|
|
1567
1573
|
deinit();
|
|
1568
1574
|
removeConfirmModal();
|
|
1575
|
+
removeSettingsPanels();
|
|
1569
1576
|
mainControls.remove();
|
|
1570
1577
|
htmlTextArea.remove();
|
|
1571
1578
|
$(window).off('scroll', onScroll);
|
|
@@ -1632,7 +1639,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1632
1639
|
// plugins setting: a page that names its containers there has
|
|
1633
1640
|
// named no editor, and still wants the one it uses
|
|
1634
1641
|
$.each($.fn.gridEditor.texts, function(type, factory) {
|
|
1635
|
-
TEXTS[type] =
|
|
1642
|
+
TEXTS[type] = factory(api);
|
|
1636
1643
|
});
|
|
1637
1644
|
|
|
1638
1645
|
// 5.x's registry, for an integration a host wrote itself. One the
|
|
@@ -1724,6 +1731,8 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1724
1731
|
// A text editor has rewritten a content area, so whatever the
|
|
1725
1732
|
// editor and its plugins had put in there goes back in
|
|
1726
1733
|
textReady: function(block) { block.trigger('ge-rte-ready'); },
|
|
1734
|
+
// A node's settings panel, in its drawer or open outside it
|
|
1735
|
+
detailsOf: detailsOf,
|
|
1727
1736
|
// A node's drawer: its first child, or for a content area the
|
|
1728
1737
|
// one beside it in its text block
|
|
1729
1738
|
drawerOf: function(node) {
|
|
@@ -2014,9 +2023,9 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2014
2023
|
|
|
2015
2024
|
/** Bring a node's panel and preview up to date with its classes. */
|
|
2016
2025
|
function refreshUtilities(node) {
|
|
2017
|
-
var details = node
|
|
2026
|
+
var details = detailsOf(node);
|
|
2018
2027
|
|
|
2019
|
-
details.
|
|
2028
|
+
details.find('.ge-classes').val(hostClasses(node).join(' '));
|
|
2020
2029
|
details.children('.ge-utilities').each(function() { renderUtilities($(this)); });
|
|
2021
2030
|
refreshPreviews(node);
|
|
2022
2031
|
}
|
|
@@ -2144,7 +2153,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2144
2153
|
.appendTo(section)
|
|
2145
2154
|
.on('click', function() {
|
|
2146
2155
|
utilitiesOpen = !section.hasClass('ge-open');
|
|
2147
|
-
|
|
2156
|
+
settingsScope().find('.ge-utilities').toggleClass('ge-open', utilitiesOpen);
|
|
2148
2157
|
})
|
|
2149
2158
|
;
|
|
2150
2159
|
|
|
@@ -2168,7 +2177,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2168
2177
|
.text(family.labelKey ? t(family.labelKey) : family.name)
|
|
2169
2178
|
.appendTo(field)
|
|
2170
2179
|
;
|
|
2171
|
-
$('<select />')
|
|
2180
|
+
$('<select class="form-select form-select-sm" />')
|
|
2172
2181
|
.appendTo(field)
|
|
2173
2182
|
.on('change', function() {
|
|
2174
2183
|
setUtility(node, family.name, this.value, { source: 'panel' });
|
|
@@ -2901,9 +2910,10 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2901
2910
|
*/
|
|
2902
2911
|
function addSettingsTool(drawer, node, presets) {
|
|
2903
2912
|
var details = createDetails(node, presets || []);
|
|
2913
|
+
node.data('ge-details', details);
|
|
2904
2914
|
|
|
2905
2915
|
createTool(drawer, t('tool.settings'), 'ge-settings', 'bi bi-gear-fill', function() {
|
|
2906
|
-
details
|
|
2916
|
+
toggleSettings(node, details, $(this));
|
|
2907
2917
|
});
|
|
2908
2918
|
|
|
2909
2919
|
// Beside the gear, because every node that has one is a node a
|
|
@@ -2923,12 +2933,18 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2923
2933
|
|
|
2924
2934
|
function createDetails(container, cssClasses) {
|
|
2925
2935
|
var detailsDiv = $('<div class="ge-details" />');
|
|
2936
|
+
var general = $('<div class="ge-details-general" />').appendTo(detailsDiv);
|
|
2937
|
+
var field = function(label) {
|
|
2938
|
+
return $('<label class="ge-field" />')
|
|
2939
|
+
.append($('<span class="ge-field-label" />').text(label))
|
|
2940
|
+
.appendTo(general);
|
|
2941
|
+
};
|
|
2926
2942
|
|
|
2927
|
-
$('<input class="ge-id" />')
|
|
2943
|
+
$('<input class="ge-id form-control form-control-sm" />')
|
|
2928
2944
|
.attr('placeholder', t('tool.id_placeholder'))
|
|
2929
2945
|
.val(container.attr('id'))
|
|
2930
2946
|
.attr('title', t('tool.id_title'))
|
|
2931
|
-
.appendTo(
|
|
2947
|
+
.appendTo(field(t('panel.id')))
|
|
2932
2948
|
.on('change', function() {
|
|
2933
2949
|
// An empty field means no id, not an empty one
|
|
2934
2950
|
if (this.value === '') {
|
|
@@ -2939,25 +2955,28 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2939
2955
|
})
|
|
2940
2956
|
;
|
|
2941
2957
|
|
|
2942
|
-
$('<input class="ge-classes" />')
|
|
2958
|
+
$('<input class="ge-classes form-control form-control-sm" />')
|
|
2943
2959
|
.attr('placeholder', t('tool.classes_placeholder'))
|
|
2944
2960
|
.attr('title', t('tool.classes_title'))
|
|
2945
2961
|
.val(hostClasses(container).join(' '))
|
|
2946
|
-
.appendTo(
|
|
2962
|
+
.appendTo(field(t('panel.classes')))
|
|
2947
2963
|
.on('change', function() {
|
|
2948
2964
|
setHostClasses(container, this.value);
|
|
2949
2965
|
refreshUtilities(container);
|
|
2950
2966
|
})
|
|
2951
2967
|
;
|
|
2952
2968
|
|
|
2953
|
-
|
|
2969
|
+
// Bootstrap 5's outline buttons, filled while their class is on;
|
|
2970
|
+
// up to 5.x they carried Bootstrap 3's btn-default, which 5 lacks
|
|
2971
|
+
var classGroup = $('<div class="btn-group btn-group-sm ge-presets" role="group" />').appendTo(general);
|
|
2954
2972
|
cssClasses.forEach(function(rowClass) {
|
|
2955
|
-
var btn = $('<a class="btn btn-sm btn-
|
|
2973
|
+
var btn = $('<a role="button" class="btn btn-sm btn-outline-secondary" />')
|
|
2956
2974
|
.html(rowClass.label)
|
|
2957
2975
|
.attr('title', rowClass.title ? rowClass.title : t('tool.toggle_class', { label: rowClass.label }))
|
|
2958
|
-
.toggleClass('active
|
|
2976
|
+
.toggleClass('active', container.hasClass(rowClass.cssClass))
|
|
2977
|
+
.attr('aria-pressed', container.hasClass(rowClass.cssClass) ? 'true' : 'false')
|
|
2959
2978
|
.on('click', function() {
|
|
2960
|
-
btn.toggleClass('active btn
|
|
2979
|
+
btn.toggleClass('active').attr('aria-pressed', btn.hasClass('active') ? 'true' : 'false');
|
|
2961
2980
|
container.toggleClass(rowClass.cssClass, btn.hasClass('active'));
|
|
2962
2981
|
refreshUtilities(container);
|
|
2963
2982
|
})
|
|
@@ -2971,6 +2990,330 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2971
2990
|
return detailsDiv;
|
|
2972
2991
|
}
|
|
2973
2992
|
|
|
2993
|
+
/* --------------------------------------------------------------
|
|
2994
|
+
* Where a node's settings open.
|
|
2995
|
+
*
|
|
2996
|
+
* Each node's panel - createDetails' fields, and what plugins add to
|
|
2997
|
+
* them - is built into its drawer, as ever. settings_panel says where
|
|
2998
|
+
* it shows: 'inline' unfolds it in the drawer; 'offcanvas', 'popover'
|
|
2999
|
+
* and 'modal' move it into one of Bootstrap's, outside the canvas,
|
|
3000
|
+
* while it is open, and back into its drawer when it closes, so
|
|
3001
|
+
* everything that finds a panel through its node keeps working.
|
|
3002
|
+
*
|
|
3003
|
+
* Bootstrap's markup and styles, but the editor's own opening and
|
|
3004
|
+
* placing: a page may load Bootstrap's javascript without Popper, or
|
|
3005
|
+
* not at all. The modal is Bootstrap's own when Bootstrap is there.
|
|
3006
|
+
* -------------------------------------------------------------- */
|
|
3007
|
+
|
|
3008
|
+
var PANEL_MODES = ['offcanvas', 'popover', 'modal', 'inline'];
|
|
3009
|
+
var settingsPanels = {}; // One of each, built on first use, outside the canvas
|
|
3010
|
+
var openSettingsState = null; // What is open: { mode, node, details, home, next, gear }
|
|
3011
|
+
|
|
3012
|
+
function panelMode() {
|
|
3013
|
+
if (PANEL_MODES.indexOf(settings.settings_panel) !== -1) { return settings.settings_panel; }
|
|
3014
|
+
|
|
3015
|
+
warnOnceHere('settings_panel', 'settings_panel "' + settings.settings_panel + '" is not one of ' +
|
|
3016
|
+
PANEL_MODES.join(', ') + ': using offcanvas');
|
|
3017
|
+
return 'offcanvas';
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
/** A node's panel, wherever it is at the moment. */
|
|
3021
|
+
function detailsOf(node) {
|
|
3022
|
+
var details = node.data('ge-details');
|
|
3023
|
+
return details && details.length ? details : $();
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
/** Where settings fields are found: the canvas, and the panel open outside it. */
|
|
3027
|
+
function settingsScope() {
|
|
3028
|
+
return openSettingsState && openSettingsState.mode !== 'inline'
|
|
3029
|
+
? canvas.add(openSettingsState.details)
|
|
3030
|
+
: canvas;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
/** The gear: its node's panel, opened or closed; another node's closes first. */
|
|
3034
|
+
function toggleSettings(node, details, gear) {
|
|
3035
|
+
var mode = panelMode();
|
|
3036
|
+
|
|
3037
|
+
if (mode === 'inline') {
|
|
3038
|
+
details.toggle();
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
var same = openSettingsState && openSettingsState.details[0] === details[0];
|
|
3043
|
+
closeSettings();
|
|
3044
|
+
if (!same) { openSettings(mode, node, details, gear); }
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
function openSettings(mode, node, details, gear) {
|
|
3048
|
+
var panel = settingsPanel(mode);
|
|
3049
|
+
|
|
3050
|
+
openSettingsState = {
|
|
3051
|
+
mode: mode,
|
|
3052
|
+
node: node,
|
|
3053
|
+
details: details,
|
|
3054
|
+
home: details.parent(),
|
|
3055
|
+
next: details.next(),
|
|
3056
|
+
gear: gear,
|
|
3057
|
+
};
|
|
3058
|
+
|
|
3059
|
+
panel.find('.ge-settings-title').text(t('panel.title', { kind: kindLabel(node) }));
|
|
3060
|
+
details.appendTo(panel.find('.ge-settings-body')).show();
|
|
3061
|
+
node.addClass('ge-settings-target');
|
|
3062
|
+
|
|
3063
|
+
var ns = '.ge-settings-' + instanceId;
|
|
3064
|
+
$(document).on('keydown' + ns, function(e) {
|
|
3065
|
+
if (e.key === 'Escape') { closeSettings(); }
|
|
3066
|
+
});
|
|
3067
|
+
|
|
3068
|
+
if (mode === 'offcanvas') {
|
|
3069
|
+
// From the bottom on a phone, from the side anywhere wider
|
|
3070
|
+
var narrow = window.innerWidth < 576;
|
|
3071
|
+
panel.toggleClass('offcanvas-end', !narrow).toggleClass('offcanvas-bottom', narrow);
|
|
3072
|
+
panel.removeClass('hiding');
|
|
3073
|
+
panel[0].getBoundingClientRect(); // So the slide in is a transition, not a jump
|
|
3074
|
+
panel.addClass('show');
|
|
3075
|
+
} else if (mode === 'popover') {
|
|
3076
|
+
placePopover(panel, gear);
|
|
3077
|
+
$(window).on('scroll' + ns + ' resize' + ns, function() { placePopover(panel, gear); });
|
|
3078
|
+
|
|
3079
|
+
// And again whenever what is in it changes size: the
|
|
3080
|
+
// Responsive section unfolding, a plugin swapping a field
|
|
3081
|
+
if (window.ResizeObserver) {
|
|
3082
|
+
openSettingsState.observer = new window.ResizeObserver(function() { placePopover(panel, gear); });
|
|
3083
|
+
openSettingsState.observer.observe(details[0]);
|
|
3084
|
+
}
|
|
3085
|
+
// A press anywhere else puts it away, as a popover does
|
|
3086
|
+
$(document).on('mousedown' + ns + ' touchstart' + ns, function(e) {
|
|
3087
|
+
if (!$(e.target).closest(panel).length && !$(e.target).closest(gear).length) { closeSettings(); }
|
|
3088
|
+
});
|
|
3089
|
+
} else {
|
|
3090
|
+
showModal(panel);
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
/** Put what is open away, and its panel back in its drawer. */
|
|
3095
|
+
function closeSettings() {
|
|
3096
|
+
var open = openSettingsState;
|
|
3097
|
+
if (!open) { return; }
|
|
3098
|
+
openSettingsState = null;
|
|
3099
|
+
|
|
3100
|
+
var panel = settingsPanels[open.mode];
|
|
3101
|
+
$(document).off('.ge-settings-' + instanceId);
|
|
3102
|
+
$(window).off('.ge-settings-' + instanceId);
|
|
3103
|
+
if (open.observer) { open.observer.disconnect(); }
|
|
3104
|
+
|
|
3105
|
+
if (open.mode === 'offcanvas') {
|
|
3106
|
+
panel.removeClass('show').addClass('hiding');
|
|
3107
|
+
window.setTimeout(function() { panel.removeClass('hiding'); }, 300);
|
|
3108
|
+
} else if (open.mode === 'popover') {
|
|
3109
|
+
panel.hide();
|
|
3110
|
+
} else {
|
|
3111
|
+
hideModal(panel);
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
open.node.removeClass('ge-settings-target');
|
|
3115
|
+
open.details.css('display', '');
|
|
3116
|
+
|
|
3117
|
+
// Where it was in its drawer, if the drawer is still there: a
|
|
3118
|
+
// node deleted while its settings were open took it with it
|
|
3119
|
+
if (open.home.length && $.contains(document.documentElement, open.home[0])) {
|
|
3120
|
+
if (open.next.length && open.next.parent()[0] === open.home[0]) {
|
|
3121
|
+
open.details.insertBefore(open.next);
|
|
3122
|
+
} else {
|
|
3123
|
+
open.details.appendTo(open.home);
|
|
3124
|
+
}
|
|
3125
|
+
} else {
|
|
3126
|
+
open.details.remove();
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
/** Built once per mode, on first use, and taken away by destroy() or setLocale(). */
|
|
3131
|
+
function settingsPanel(mode) {
|
|
3132
|
+
if (settingsPanels[mode]) { return settingsPanels[mode]; }
|
|
3133
|
+
|
|
3134
|
+
var closeButton = function(extra) {
|
|
3135
|
+
return $('<button type="button" class="btn-close ge-settings-close" />')
|
|
3136
|
+
.addClass(extra || '')
|
|
3137
|
+
.attr('aria-label', t('panel.close'));
|
|
3138
|
+
};
|
|
3139
|
+
var panel;
|
|
3140
|
+
|
|
3141
|
+
if (mode === 'offcanvas') {
|
|
3142
|
+
panel = $('<div class="offcanvas offcanvas-end ge-settings-panel ge-settings-offcanvas" tabindex="-1" role="dialog" />')
|
|
3143
|
+
.append($('<div class="offcanvas-header" />')
|
|
3144
|
+
.append('<h5 class="offcanvas-title ge-settings-title"></h5>')
|
|
3145
|
+
.append(closeButton()))
|
|
3146
|
+
.append('<div class="offcanvas-body ge-settings-body"></div>');
|
|
3147
|
+
} else if (mode === 'popover') {
|
|
3148
|
+
panel = $('<div class="popover bs-popover-bottom ge-settings-panel ge-settings-popover" role="dialog" />')
|
|
3149
|
+
.append('<div class="popover-arrow"></div>')
|
|
3150
|
+
.append($('<div class="popover-header" />')
|
|
3151
|
+
.append('<span class="ge-settings-title"></span>')
|
|
3152
|
+
.append(closeButton()))
|
|
3153
|
+
.append('<div class="popover-body ge-settings-body"></div>')
|
|
3154
|
+
.hide();
|
|
3155
|
+
} else {
|
|
3156
|
+
panel = $('<div class="modal fade ge-settings-panel ge-settings-modal" tabindex="-1" role="dialog" aria-hidden="true" />')
|
|
3157
|
+
.append($('<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" />')
|
|
3158
|
+
.append($('<div class="modal-content" />')
|
|
3159
|
+
.append($('<div class="modal-header" />')
|
|
3160
|
+
.append('<h5 class="modal-title ge-settings-title"></h5>')
|
|
3161
|
+
.append(closeButton()))
|
|
3162
|
+
.append('<div class="modal-body ge-settings-body"></div>')
|
|
3163
|
+
.append($('<div class="modal-footer" />')
|
|
3164
|
+
.append($('<button type="button" class="btn btn-primary ge-settings-close" />')
|
|
3165
|
+
.text(t('panel.done'))))));
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
panel.on('click', '.ge-settings-close', function(e) {
|
|
3169
|
+
e.preventDefault();
|
|
3170
|
+
closeSettings();
|
|
3171
|
+
});
|
|
3172
|
+
|
|
3173
|
+
settingsPanels[mode] = panel.appendTo('body');
|
|
3174
|
+
return panel;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
function removeSettingsPanels() {
|
|
3178
|
+
closeSettings();
|
|
3179
|
+
|
|
3180
|
+
$.each(settingsPanels, function(mode, panel) {
|
|
3181
|
+
if (mode === 'modal' && window.bootstrap && window.bootstrap.Modal) {
|
|
3182
|
+
var instance = window.bootstrap.Modal.getInstance(panel[0]);
|
|
3183
|
+
if (instance) { instance.dispose(); }
|
|
3184
|
+
}
|
|
3185
|
+
panel.remove();
|
|
3186
|
+
});
|
|
3187
|
+
if (settingsBackdrop) { settingsBackdrop.remove(); settingsBackdrop = null; }
|
|
3188
|
+
settingsPanels = {};
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
/**
|
|
3192
|
+
* Under the gear, or over it when there is more room there, inside
|
|
3193
|
+
* the window, and no taller than the room it has: past that its body
|
|
3194
|
+
* scrolls.
|
|
3195
|
+
*/
|
|
3196
|
+
function placePopover(panel, gear) {
|
|
3197
|
+
if (!$.contains(document.documentElement, gear[0])) { return; }
|
|
3198
|
+
|
|
3199
|
+
panel.show();
|
|
3200
|
+
|
|
3201
|
+
var tool = gear[0].getBoundingClientRect();
|
|
3202
|
+
var gap = 8;
|
|
3203
|
+
var body = panel.children('.popover-body').css('max-height', '');
|
|
3204
|
+
var width = panel.outerWidth();
|
|
3205
|
+
var height = panel.outerHeight();
|
|
3206
|
+
var roomBelow = window.innerHeight - tool.bottom - 2 * gap;
|
|
3207
|
+
var roomAbove = tool.top - 2 * gap;
|
|
3208
|
+
var below = height <= roomBelow || roomBelow >= roomAbove;
|
|
3209
|
+
var room = below ? roomBelow : roomAbove;
|
|
3210
|
+
|
|
3211
|
+
if (height > room) {
|
|
3212
|
+
body.css('max-height', Math.max(120, room - (height - body.outerHeight())));
|
|
3213
|
+
height = panel.outerHeight();
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
var top = below ? tool.bottom + gap : tool.top - gap - height;
|
|
3217
|
+
var left = Math.max(gap, Math.min(tool.left + tool.width / 2 - 24, window.innerWidth - width - gap));
|
|
3218
|
+
|
|
3219
|
+
panel
|
|
3220
|
+
.toggleClass('bs-popover-bottom', below)
|
|
3221
|
+
.toggleClass('bs-popover-top', !below)
|
|
3222
|
+
.css({ top: top + window.pageYOffset, left: left + window.pageXOffset })
|
|
3223
|
+
;
|
|
3224
|
+
|
|
3225
|
+
// The arrow points at the gear, wherever the popover had to go
|
|
3226
|
+
panel.children('.popover-arrow').css('left',
|
|
3227
|
+
Math.max(gap, Math.min(tool.left + tool.width / 2 - left - 8, width - 24)));
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
var settingsBackdrop = null; // The modal's backdrop, when Bootstrap's javascript is not there to make one
|
|
3231
|
+
|
|
3232
|
+
/**
|
|
3233
|
+
* Bootstrap's modal ignores a hide while it is still fading in, and a
|
|
3234
|
+
* show while it is fading out, so the editor asks for what it wants
|
|
3235
|
+
* and has it done once the running transition ends: a panel closed as
|
|
3236
|
+
* it opens - getHtml, a second click - would otherwise stay open, and
|
|
3237
|
+
* empty. A hide Bootstrap starts itself, from Escape or the backdrop,
|
|
3238
|
+
* closes the settings as the close button does.
|
|
3239
|
+
*/
|
|
3240
|
+
function showModal(panel) {
|
|
3241
|
+
if (!window.bootstrap || !window.bootstrap.Modal) {
|
|
3242
|
+
settingsBackdrop = $('<div class="modal-backdrop fade show ge-settings-backdrop" />')
|
|
3243
|
+
.appendTo('body')
|
|
3244
|
+
.on('click', function() { closeSettings(); });
|
|
3245
|
+
panel.addClass('show').css('display', 'block').removeAttr('aria-hidden');
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
var modal = window.bootstrap.Modal.getOrCreateInstance(panel[0]);
|
|
3250
|
+
|
|
3251
|
+
if (!panel.data('ge-watched')) {
|
|
3252
|
+
panel.data('ge-watched', true);
|
|
3253
|
+
panel.on('hide.bs.modal', function() {
|
|
3254
|
+
// Not asked for: Escape or the backdrop
|
|
3255
|
+
if (panel.data('ge-wanted') === 'open') {
|
|
3256
|
+
panel.data('ge-wanted', 'closed');
|
|
3257
|
+
panel.data('ge-busy', true);
|
|
3258
|
+
}
|
|
3259
|
+
});
|
|
3260
|
+
panel.on('shown.bs.modal', function() {
|
|
3261
|
+
panel.data('ge-busy', false);
|
|
3262
|
+
if (panel.data('ge-wanted') === 'closed') { hideModal(panel); }
|
|
3263
|
+
});
|
|
3264
|
+
panel.on('hidden.bs.modal', function() {
|
|
3265
|
+
panel.data('ge-busy', false);
|
|
3266
|
+
if (panel.data('ge-wanted') === 'open') {
|
|
3267
|
+
showModal(panel);
|
|
3268
|
+
} else if (openSettingsState && openSettingsState.mode === 'modal') {
|
|
3269
|
+
closeSettings();
|
|
3270
|
+
}
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
panel.data('ge-wanted', 'open');
|
|
3275
|
+
if (!panel.data('ge-busy')) {
|
|
3276
|
+
panel.data('ge-busy', true);
|
|
3277
|
+
modal.show();
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
function hideModal(panel) {
|
|
3282
|
+
if (!window.bootstrap || !window.bootstrap.Modal) {
|
|
3283
|
+
if (settingsBackdrop) { settingsBackdrop.remove(); settingsBackdrop = null; }
|
|
3284
|
+
panel.removeClass('show').css('display', '').attr('aria-hidden', 'true');
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
panel.data('ge-wanted', 'closed');
|
|
3289
|
+
|
|
3290
|
+
// Already out of sight - Bootstrap hid it itself, and this is its
|
|
3291
|
+
// hidden event closing the settings - and nothing is under way:
|
|
3292
|
+
// a hide now would be ignored, and no event would come to say so
|
|
3293
|
+
if (!panel.data('ge-busy') && panel.hasClass('show')) {
|
|
3294
|
+
panel.data('ge-busy', true);
|
|
3295
|
+
window.bootstrap.Modal.getOrCreateInstance(panel[0]).hide();
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
/** What a panel's title calls a node. */
|
|
3300
|
+
function kindLabel(node) {
|
|
3301
|
+
var kind = kindOf(node);
|
|
3302
|
+
|
|
3303
|
+
if (CONTAINERS[kind] && CONTAINERS[kind].labelKey) { return t(CONTAINERS[kind].labelKey); }
|
|
3304
|
+
|
|
3305
|
+
switch (kind) {
|
|
3306
|
+
case 'row': return t('panel.kind_row');
|
|
3307
|
+
case 'column': return t('panel.kind_column');
|
|
3308
|
+
case 'text': return t('panel.kind_text');
|
|
3309
|
+
case 'element': return t('panel.kind_element');
|
|
3310
|
+
case 'section': return t('panel.kind_section');
|
|
3311
|
+
case 'tab': return t('panel.kind_tab');
|
|
3312
|
+
case 'accordion-item': return t('panel.kind_accordion_item');
|
|
3313
|
+
default: return kind;
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
|
|
2974
3317
|
/**
|
|
2975
3318
|
* Make sure every column is marked as one, and that a column with no
|
|
2976
3319
|
* sizing at all gets some.
|
|
@@ -3320,7 +3663,11 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
3320
3663
|
// With the whole drawer as the handle, the tools inside it are
|
|
3321
3664
|
// still tools: a drag starting on one would swallow its click,
|
|
3322
3665
|
// and the settings panel has fields to type in
|
|
3323
|
-
|
|
3666
|
+
// - the info tools excepted, which do nothing when clicked:
|
|
3667
|
+
// a text's drawer is small and made of little but tools, and
|
|
3668
|
+
// with those turned away as well there was nothing to hold
|
|
3669
|
+
? '.ge-tools-drawer > a:not(.ge-text-info):not(.ge-element-info), .ge-details, ' +
|
|
3670
|
+
'input, textarea, button, select, option'
|
|
3324
3671
|
: 'input, textarea, button, select, option';
|
|
3325
3672
|
}
|
|
3326
3673
|
|
|
@@ -3388,7 +3735,14 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
3388
3735
|
// synthetic events, so the tests could not exist without
|
|
3389
3736
|
// this; it also gives one helper across browsers
|
|
3390
3737
|
forceFallback: true,
|
|
3391
|
-
|
|
3738
|
+
|
|
3739
|
+
// The copy that follows the pointer is appended to the
|
|
3740
|
+
// list the drag started in rather than to the body: it is
|
|
3741
|
+
// positioned fixed either way, and inside the canvas it
|
|
3742
|
+
// looks like what it is a copy of. On the body none of the
|
|
3743
|
+
// editing styles reached it - its drawers came out
|
|
3744
|
+
// unstyled, their settings panels open.
|
|
3745
|
+
fallbackOnBody: false,
|
|
3392
3746
|
|
|
3393
3747
|
ghostClass: 'ge-drag-placeholder',
|
|
3394
3748
|
chosenClass: 'ge-drag-chosen',
|
|
@@ -3475,15 +3829,12 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
3475
3829
|
|
|
3476
3830
|
/**
|
|
3477
3831
|
* What a drag reports as moving. A text block is the editor's own
|
|
3478
|
-
* wrapper, so what moved is the content area inside it
|
|
3479
|
-
* a content area moves as kind 'content', as it did in 5.x - adding
|
|
3480
|
-
* and deleting one, which 5.x never announced, already say 'text'.
|
|
3832
|
+
* wrapper, so what moved is the content area inside it.
|
|
3481
3833
|
*/
|
|
3482
3834
|
function moveSubject(item) {
|
|
3483
3835
|
var node = item.hasClass('ge-text-block') ? item.children('.ge-content') : item;
|
|
3484
|
-
var kind = kindOf(node);
|
|
3485
3836
|
|
|
3486
|
-
return { kind:
|
|
3837
|
+
return { kind: kindOf(node), node: node };
|
|
3487
3838
|
}
|
|
3488
3839
|
|
|
3489
3840
|
/** Put a node back where a refused drag found it. */
|
|
@@ -3949,10 +4300,102 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
3949
4300
|
}
|
|
3950
4301
|
}
|
|
3951
4302
|
|
|
4303
|
+
/**
|
|
4304
|
+
* What a text is never made of: rows, containers, and whatever a
|
|
4305
|
+
* feature plugin says - the elements plugin's elements. Each is a
|
|
4306
|
+
* block of the column's, beside the texts.
|
|
4307
|
+
*/
|
|
4308
|
+
function textCutter() {
|
|
4309
|
+
var cuts = ['.row', '[data-ge-container]'];
|
|
4310
|
+
|
|
4311
|
+
$.each(FEATURES, function(name, feature) {
|
|
4312
|
+
var cut = typeof feature.cuts === 'function' ? feature.cuts() : feature.cuts;
|
|
4313
|
+
if (cut) { cuts.push(cut); }
|
|
4314
|
+
});
|
|
4315
|
+
|
|
4316
|
+
return cuts.join(', ');
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
/**
|
|
4320
|
+
* Take the blocks out of the text.
|
|
4321
|
+
*
|
|
4322
|
+
* Up to 5.x an element lived inside a content area, among the text
|
|
4323
|
+
* a rich text editor edited. From 6.0 it is a block of its own, so a
|
|
4324
|
+
* content area with one inside - markup 5.x saved - is cut there: the
|
|
4325
|
+
* text before stays in the content area, the element goes into the
|
|
4326
|
+
* column after it, and the text after goes into a new content area of
|
|
4327
|
+
* the same type. Only a content area's own children cut it, which is
|
|
4328
|
+
* where 5.x recognized elements; one inside a paragraph is text, as
|
|
4329
|
+
* it was. A part with nothing but whitespace is dropped, and the first
|
|
4330
|
+
* part that has something keeps the content area, id and classes and
|
|
4331
|
+
* all.
|
|
4332
|
+
*
|
|
4333
|
+
* On every init, and a no-op on markup 6.0 made: nothing to cut. A
|
|
4334
|
+
* content area its editor is open on is left for the next init.
|
|
4335
|
+
*/
|
|
4336
|
+
function splitTexts(cutter) {
|
|
4337
|
+
canvas.find('.column > .ge-content, .column > .ge-text-block > .ge-content').each(function() {
|
|
4338
|
+
var area = $(this);
|
|
4339
|
+
if (area.hasClass('ge-rte-active') || !area.children().filter(cutter).length) { return; }
|
|
4340
|
+
|
|
4341
|
+
var type = area.attr('data-ge-content-type');
|
|
4342
|
+
var anchor = area.parent('.ge-text-block').length ? area.parent() : area;
|
|
4343
|
+
var pieces = [];
|
|
4344
|
+
var run = [];
|
|
4345
|
+
|
|
4346
|
+
$.each($.makeArray(area[0].childNodes), function(i, node) {
|
|
4347
|
+
if (node.nodeType === 1 && $(node).is(cutter)) {
|
|
4348
|
+
pieces.push({ text: run }, { block: node });
|
|
4349
|
+
run = [];
|
|
4350
|
+
} else {
|
|
4351
|
+
run.push(node);
|
|
4352
|
+
}
|
|
4353
|
+
});
|
|
4354
|
+
pieces.push({ text: run });
|
|
4355
|
+
|
|
4356
|
+
$(area[0].childNodes).detach();
|
|
4357
|
+
|
|
4358
|
+
var kept = false;
|
|
4359
|
+
var last = anchor;
|
|
4360
|
+
|
|
4361
|
+
pieces.forEach(function(piece) {
|
|
4362
|
+
var placed;
|
|
4363
|
+
|
|
4364
|
+
if (piece.block) {
|
|
4365
|
+
placed = $(piece.block);
|
|
4366
|
+
} else if (!hasContent(piece.text)) {
|
|
4367
|
+
return;
|
|
4368
|
+
} else if (kept) {
|
|
4369
|
+
// Of the same type, or of none if it had none
|
|
4370
|
+
placed = (type ? createDefaultContentWrapper(type) : $('<div class="ge-content" />'))
|
|
4371
|
+
.append(piece.text);
|
|
4372
|
+
} else {
|
|
4373
|
+
// The content area itself, moved along if an
|
|
4374
|
+
// element came before the first text
|
|
4375
|
+
area.append(piece.text);
|
|
4376
|
+
placed = anchor;
|
|
4377
|
+
kept = true;
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
if (placed[0] !== last[0]) { placed.insertAfter(last); }
|
|
4381
|
+
last = placed;
|
|
4382
|
+
});
|
|
4383
|
+
|
|
4384
|
+
if (!kept) { anchor.remove(); }
|
|
4385
|
+
});
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
/** Whether a run of nodes has anything in it but whitespace. */
|
|
4389
|
+
function hasContent(nodes) {
|
|
4390
|
+
return nodes.some(function(node) {
|
|
4391
|
+
return node.nodeType === 1 || (node.nodeType === 3 && /\S/.test(node.nodeValue));
|
|
4392
|
+
});
|
|
4393
|
+
}
|
|
4394
|
+
|
|
3952
4395
|
/**
|
|
3953
4396
|
* Wrap column content in <div class="ge-content"> where neccesary
|
|
3954
4397
|
*/
|
|
3955
|
-
function wrapContent() {
|
|
4398
|
+
function wrapContent(cutter) {
|
|
3956
4399
|
canvas.find('.column').each(function() {
|
|
3957
4400
|
var col = $(this);
|
|
3958
4401
|
var contents = $();
|
|
@@ -3966,10 +4409,10 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
3966
4409
|
// the next init.
|
|
3967
4410
|
if (child.is('.ge-tools-drawer, .ge-resize-handle')) { return; }
|
|
3968
4411
|
|
|
3969
|
-
// A container sits in the column beside
|
|
3970
|
-
// areas, not inside one, so it ends a run of
|
|
3971
|
-
// content rather than joining it
|
|
3972
|
-
if (child.is('.
|
|
4412
|
+
// A container, or an element, sits in the column beside
|
|
4413
|
+
// the content areas, not inside one, so it ends a run of
|
|
4414
|
+
// loose content rather than joining it
|
|
4415
|
+
if (child.is('.ge-content, .ge-text-block') || child.is(cutter)) {
|
|
3973
4416
|
contents = doWrap(contents);
|
|
3974
4417
|
} else {
|
|
3975
4418
|
contents = contents.add(child);
|
|
@@ -4285,7 +4728,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
4285
4728
|
|
|
4286
4729
|
if (key === from) { return; }
|
|
4287
4730
|
|
|
4288
|
-
|
|
4731
|
+
settingsScope().find('.ge-utilities').each(function() { renderUtilities($(this)); });
|
|
4289
4732
|
refreshPreviews(canvas);
|
|
4290
4733
|
plugins('onViewChange', key);
|
|
4291
4734
|
emit('view-change', { canvas: canvas, breakpoint: key, from: from, to: key });
|
|
@@ -4471,6 +4914,18 @@ $.fn.gridEditor.locales = {
|
|
|
4471
4914
|
'tool.indent_increase': 'Increase indent\n(hold shift for max)',
|
|
4472
4915
|
'tool.edit_source': 'Edit Source Code',
|
|
4473
4916
|
'tool.preview': 'Preview',
|
|
4917
|
+
'panel.title': '{kind} settings',
|
|
4918
|
+
'panel.close': 'Close',
|
|
4919
|
+
'panel.done': 'Done',
|
|
4920
|
+
'panel.id': 'Id',
|
|
4921
|
+
'panel.classes': 'Classes',
|
|
4922
|
+
'panel.kind_row': 'Row',
|
|
4923
|
+
'panel.kind_column': 'Column',
|
|
4924
|
+
'panel.kind_text': 'Text',
|
|
4925
|
+
'panel.kind_element': 'Element',
|
|
4926
|
+
'panel.kind_section': 'Section',
|
|
4927
|
+
'panel.kind_tab': 'Tab',
|
|
4928
|
+
'panel.kind_accordion_item': 'Accordion item',
|
|
4474
4929
|
'tool.id_placeholder': 'id',
|
|
4475
4930
|
'tool.id_title': 'Set a unique identifier',
|
|
4476
4931
|
'tool.classes_placeholder': 'classes',
|