@themarioga/grid-editor 3.2.1 → 4.1.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/BUILDING.md +12 -3
- package/CHANGELOG.md +87 -0
- package/README.md +79 -7
- package/UPGRADING.md +79 -0
- package/dist/grideditor.css +188 -20
- package/dist/grideditor.min.css +1 -1
- package/dist/grideditor.min.css.map +1 -1
- package/dist/jquery.grideditor.bundle.min.js +10 -0
- package/dist/jquery.grideditor.js +924 -174
- package/dist/jquery.grideditor.min.js +1 -1
- package/dist/jquery.grideditor.min.js.map +1 -1
- package/dist/locales/grideditor.es.js +34 -0
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +1 -1
- package/dist/plugins/grideditor.accordion.js +8 -0
- package/dist/plugins/grideditor.accordion.min.js +1 -1
- package/dist/plugins/grideditor.accordion.min.js.map +1 -1
- package/dist/plugins/grideditor.alignment.js +81 -0
- package/dist/plugins/grideditor.alignment.min.js +2 -0
- package/dist/plugins/grideditor.alignment.min.js.map +1 -0
- package/dist/plugins/grideditor.elements.js +5 -5
- package/dist/plugins/grideditor.elements.min.js +1 -1
- package/dist/plugins/grideditor.elements.min.js.map +1 -1
- package/dist/plugins/grideditor.gutters.js +145 -0
- package/dist/plugins/grideditor.gutters.min.js +2 -0
- package/dist/plugins/grideditor.gutters.min.js.map +1 -0
- package/dist/plugins/grideditor.order.js +206 -0
- package/dist/plugins/grideditor.order.min.js +2 -0
- package/dist/plugins/grideditor.order.min.js.map +1 -0
- package/dist/plugins/grideditor.spacing.js +299 -0
- package/dist/plugins/grideditor.spacing.min.js +2 -0
- package/dist/plugins/grideditor.spacing.min.js.map +1 -0
- package/dist/plugins/grideditor.tabs.js +8 -0
- package/dist/plugins/grideditor.tabs.min.js +1 -1
- package/dist/plugins/grideditor.tabs.min.js.map +1 -1
- package/dist/plugins/grideditor.visibility.js +172 -0
- package/dist/plugins/grideditor.visibility.min.js +2 -0
- package/dist/plugins/grideditor.visibility.min.js.map +1 -0
- package/docs/events.md +26 -8
- package/docs/locale-keys.md +54 -0
- package/docs/plugins.md +129 -10
- package/example/autosave.html +6 -1
- package/example/basic.html +8 -2
- package/example/breakpoints.html +6 -1
- package/example/ckeditor.html +6 -1
- package/example/containers.html +5 -1
- package/example/elements.html +6 -1
- package/example/index.html +24 -1
- package/example/locale.html +6 -1
- package/example/plugins.html +6 -1
- package/example/summernote.html +6 -1
- package/example/utilities.html +145 -0
- package/example/wrap_content.html +6 -1
- package/package.json +3 -2
- package/src/js/jquery.grideditor.js +924 -174
- package/src/js/locales/grideditor.es.js +34 -0
- package/src/js/plugins/grideditor.accordion.js +8 -0
- package/src/js/plugins/grideditor.alignment.js +81 -0
- package/src/js/plugins/grideditor.elements.js +5 -5
- package/src/js/plugins/grideditor.gutters.js +145 -0
- package/src/js/plugins/grideditor.order.js +206 -0
- package/src/js/plugins/grideditor.spacing.js +299 -0
- package/src/js/plugins/grideditor.tabs.js +8 -0
- package/src/js/plugins/grideditor.visibility.js +172 -0
- package/src/less/grideditor.less +210 -18
|
@@ -32,6 +32,8 @@ var METHODS = {
|
|
|
32
32
|
addTab: { value: true },
|
|
33
33
|
addAccordionItem: { value: true },
|
|
34
34
|
setLocale: {},
|
|
35
|
+
getUtility: { value: true },
|
|
36
|
+
setUtility: { value: true },
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
/**
|
|
@@ -45,14 +47,17 @@ var PLACEMENTS = ['appendTo', 'prependTo', 'insertAfter', 'insertBefore'];
|
|
|
45
47
|
* runs in: a size written for a tier applies to every wider tier that does not
|
|
46
48
|
* override it. Every size and offset class grid-editor reads or writes comes
|
|
47
49
|
* from this table, so adding a tier is a row here and nothing else.
|
|
50
|
+
*
|
|
51
|
+
* `infix` is what Bootstrap's utility classes put between the property and
|
|
52
|
+
* the value - `order-md-2`, `d-none` - and is empty for the smallest tier.
|
|
48
53
|
*/
|
|
49
54
|
var BREAKPOINTS = [
|
|
50
|
-
{ key: 'xs', colPrefix: 'col-', offsetPrefix: 'offset-', min: 0, preview: 400, labelKey: 'view.xs' },
|
|
51
|
-
{ key: 'sm', colPrefix: 'col-sm-', offsetPrefix: 'offset-sm-', min: 576, preview: 576, labelKey: 'view.sm' },
|
|
52
|
-
{ key: 'md', colPrefix: 'col-md-', offsetPrefix: 'offset-md-', min: 768, preview: 768, labelKey: 'view.md' },
|
|
53
|
-
{ key: 'lg', colPrefix: 'col-lg-', offsetPrefix: 'offset-lg-', min: 992, preview: 992, labelKey: 'view.lg' },
|
|
54
|
-
{ key: 'xl', colPrefix: 'col-xl-', offsetPrefix: 'offset-xl-', min: 1200, preview: 1200, labelKey: 'view.xl' },
|
|
55
|
-
{ key: 'xxl', colPrefix: 'col-xxl-', offsetPrefix: 'offset-xxl-', min: 1400, preview: null, labelKey: 'view.xxl' },
|
|
55
|
+
{ key: 'xs', infix: '', colPrefix: 'col-', offsetPrefix: 'offset-', min: 0, preview: 400, labelKey: 'view.xs' },
|
|
56
|
+
{ key: 'sm', infix: 'sm', colPrefix: 'col-sm-', offsetPrefix: 'offset-sm-', min: 576, preview: 576, labelKey: 'view.sm' },
|
|
57
|
+
{ key: 'md', infix: 'md', colPrefix: 'col-md-', offsetPrefix: 'offset-md-', min: 768, preview: 768, labelKey: 'view.md' },
|
|
58
|
+
{ key: 'lg', infix: 'lg', colPrefix: 'col-lg-', offsetPrefix: 'offset-lg-', min: 992, preview: 992, labelKey: 'view.lg' },
|
|
59
|
+
{ key: 'xl', infix: 'xl', colPrefix: 'col-xl-', offsetPrefix: 'offset-xl-', min: 1200, preview: 1200, labelKey: 'view.xl' },
|
|
60
|
+
{ key: 'xxl', infix: 'xxl', colPrefix: 'col-xxl-', offsetPrefix: 'offset-xxl-', min: 1400, preview: null, labelKey: 'view.xxl' },
|
|
56
61
|
];
|
|
57
62
|
|
|
58
63
|
/**
|
|
@@ -111,13 +116,29 @@ var NESTED_SETTINGS = {
|
|
|
111
116
|
},
|
|
112
117
|
resize: {
|
|
113
118
|
enabled: true,
|
|
114
|
-
handles: 'e', // Which edges carry a handle,
|
|
119
|
+
handles: 'e', // Which edges carry a handle: 'e', 'w', or 'e, w'
|
|
115
120
|
balance: 'next', // 'next' takes the units out of the following column
|
|
116
121
|
},
|
|
122
|
+
drag: {
|
|
123
|
+
delay: 0, // Milliseconds to hold before a drag starts
|
|
124
|
+
touch_delay: 100, // The same for touch, where 0 eats the page's scrolling
|
|
125
|
+
threshold: 3, // Pixels of movement before a gesture counts as a drag
|
|
126
|
+
animation: 150, // Milliseconds of reordering animation, 0 for none
|
|
127
|
+
scroll: true, // Scroll the page when a drag reaches its edge
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** Settings 4.0 took away, and what a host should reach for instead. */
|
|
132
|
+
var REMOVED_SETTINGS = {
|
|
133
|
+
sortable_options: 'drag',
|
|
134
|
+
resizable_options: 'resize',
|
|
117
135
|
};
|
|
118
136
|
|
|
119
137
|
var warned = {};
|
|
120
138
|
|
|
139
|
+
/** Editors on the page, counted so each one's sortable groups are its own. */
|
|
140
|
+
var editorCounter = 0;
|
|
141
|
+
|
|
121
142
|
/**
|
|
122
143
|
* Translate one key.
|
|
123
144
|
*
|
|
@@ -242,7 +263,8 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
242
263
|
'container_tools' : [], // Host tools on container drawers
|
|
243
264
|
'tab_tools' : [], // Host tools on tab drawers
|
|
244
265
|
'accordion_tools' : [], // Host tools on accordion item drawers
|
|
245
|
-
'plugins' : null, //
|
|
266
|
+
'plugins' : null, // Plugins to use, of any kind; null means every one loaded
|
|
267
|
+
'utilities' : {}, // Options for the utility plugins, by plugin name
|
|
246
268
|
'elements' : NESTED_SETTINGS.elements, // Element level controls, below the column
|
|
247
269
|
'custom_filter' : '',
|
|
248
270
|
'content_types' : ['tinymce'],
|
|
@@ -252,13 +274,12 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
252
274
|
'layout_modes' : VIEW_KEYS.slice(), // Which views the dropdown offers
|
|
253
275
|
'default_view' : ALL_VIEW,
|
|
254
276
|
'resize' : NESTED_SETTINGS.resize, // Resizing a column by dragging its edge
|
|
255
|
-
'resizable_options' : {}, // Merged into every jQuery UI resizable
|
|
256
277
|
'source_textarea' : '',
|
|
257
278
|
'locale' : 'en', // Code of a locale in $.fn.gridEditor.locales
|
|
258
279
|
'locale_strings' : {}, // Overrides for individual keys
|
|
259
280
|
'callbacks' : {}, // before_*/after_* functions, the events by another route
|
|
260
281
|
'confirm_delete' : true, // Ask before deleting a row or a column
|
|
261
|
-
'
|
|
282
|
+
'drag' : NESTED_SETTINGS.drag // How a drag behaves, whatever drives it
|
|
262
283
|
}, optionsOrMethod);
|
|
263
284
|
|
|
264
285
|
// Merged rather than replaced, so `elements: { auto: true }` keeps the
|
|
@@ -267,6 +288,18 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
267
288
|
settings[name] = $.extend({}, defaults, settings[name]);
|
|
268
289
|
});
|
|
269
290
|
|
|
291
|
+
// Both handed out the drag toolkit's own options, which 4.0 stops
|
|
292
|
+
// promising: there is no widget underneath a host should be reaching
|
|
293
|
+
// for. What they were used for is a setting of the editor's now.
|
|
294
|
+
$.each(REMOVED_SETTINGS, function(name, replacement) {
|
|
295
|
+
if (optionsOrMethod && optionsOrMethod[name] !== undefined) {
|
|
296
|
+
warn($.fn.gridEditor.t(settings, 'warning.setting_removed', {
|
|
297
|
+
setting: name,
|
|
298
|
+
replacement: replacement,
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
270
303
|
|
|
271
304
|
// Elems
|
|
272
305
|
var canvas,
|
|
@@ -282,6 +315,8 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
282
315
|
var sizePicker = null; // The open column size picker, if there is one
|
|
283
316
|
var dropMarker = null; // The line showing where a dragged toolbar button would land
|
|
284
317
|
var warnedHere = {}; // Deprecations are worth saying once per instance, not once per call
|
|
318
|
+
var sortables = []; // Every list made sortable, so deinit destroys exactly those
|
|
319
|
+
var instanceId = ++editorCounter; // Scopes the sortable groups to this editor
|
|
285
320
|
|
|
286
321
|
// Before anything else, because the instance handle hands the canvas
|
|
287
322
|
// to hosts and the rest of setup() runs at the end of this function
|
|
@@ -719,6 +754,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
719
754
|
// when their editor is ready, and the drawers go back in.
|
|
720
755
|
canvas.on('ge-rte-ready', '.ge-content', function() {
|
|
721
756
|
plugins('onContentReady', $(this));
|
|
757
|
+
refreshPreviews($(this));
|
|
722
758
|
});
|
|
723
759
|
}
|
|
724
760
|
|
|
@@ -874,26 +910,82 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
874
910
|
}
|
|
875
911
|
|
|
876
912
|
function makeToolbarDraggable() {
|
|
913
|
+
var buttons = mainControls.find('[data-ge-toolbar]')
|
|
914
|
+
.removeClass('ge-palette-button')
|
|
915
|
+
.off('pointerdown.ge-palette')
|
|
916
|
+
;
|
|
917
|
+
|
|
877
918
|
if (!toolbarDrags()) { return; }
|
|
878
919
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
appendTo: 'body',
|
|
882
|
-
zIndex: 1000,
|
|
883
|
-
cursorAt: { top: 14, left: 14 },
|
|
920
|
+
buttons.addClass('ge-palette-button').on('pointerdown.ge-palette', startToolbarDrag);
|
|
921
|
+
}
|
|
884
922
|
|
|
885
|
-
|
|
886
|
-
|
|
923
|
+
/**
|
|
924
|
+
* A toolbar button carried onto the canvas.
|
|
925
|
+
*
|
|
926
|
+
* Nothing happens until the pointer has moved far enough to mean it:
|
|
927
|
+
* below that the gesture is a click, and the button's own handler
|
|
928
|
+
* adds the block at the end of the canvas as it always has - which is
|
|
929
|
+
* also what keeps the add column tool's hold-to-pick working.
|
|
930
|
+
*/
|
|
931
|
+
function startToolbarDrag(e) {
|
|
932
|
+
var button = $(e.currentTarget);
|
|
933
|
+
var startX = e.pageX;
|
|
934
|
+
var startY = e.pageY;
|
|
935
|
+
var helper = null;
|
|
887
936
|
|
|
888
|
-
|
|
889
|
-
var where = dropPlaceAt(e.pageX, e.pageY);
|
|
937
|
+
if (e.button) { return; }
|
|
890
938
|
|
|
891
|
-
|
|
892
|
-
|
|
939
|
+
function far(move) {
|
|
940
|
+
return Math.abs(move.pageX - startX) + Math.abs(move.pageY - startY) >
|
|
941
|
+
settings.drag.threshold;
|
|
942
|
+
}
|
|
893
943
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
944
|
+
function onMove(move) {
|
|
945
|
+
if (!helper) {
|
|
946
|
+
if (!far(move)) { return; }
|
|
947
|
+
|
|
948
|
+
helper = button.clone()
|
|
949
|
+
.addClass('ge-toolbar-helper')
|
|
950
|
+
.appendTo('body')
|
|
951
|
+
;
|
|
952
|
+
canvas.addClass('ge-dropping');
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
helper.css({ left: move.pageX - 14, top: move.pageY - 14 });
|
|
956
|
+
showDropMarker(move.pageX, move.pageY);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function onUp(up) {
|
|
960
|
+
$(document)
|
|
961
|
+
.off('pointermove.ge-toolbar', onMove)
|
|
962
|
+
.off('pointerup.ge-toolbar pointercancel.ge-toolbar', onUp)
|
|
963
|
+
;
|
|
964
|
+
|
|
965
|
+
if (!helper) { return; }
|
|
966
|
+
|
|
967
|
+
helper.remove();
|
|
968
|
+
canvas.removeClass('ge-dropping');
|
|
969
|
+
hideDropMarker();
|
|
970
|
+
|
|
971
|
+
// The click that follows a drag would add the block a second
|
|
972
|
+
// time, at the end of the canvas
|
|
973
|
+
button.one('click', function(click) {
|
|
974
|
+
click.preventDefault();
|
|
975
|
+
click.stopImmediatePropagation();
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
var where = dropPlaceAt(up.pageX, up.pageY);
|
|
979
|
+
if (where) { insertFromToolbar(button, where); }
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// On the document, not on the button: until the gesture is far
|
|
983
|
+
// enough along to be a drag there is nothing to capture the
|
|
984
|
+
// pointer with, and the pointer has left the button by then
|
|
985
|
+
$(document)
|
|
986
|
+
.on('pointermove.ge-toolbar', onMove)
|
|
987
|
+
.on('pointerup.ge-toolbar pointercancel.ge-toolbar', onUp)
|
|
988
|
+
;
|
|
897
989
|
}
|
|
898
990
|
|
|
899
991
|
/**
|
|
@@ -1051,6 +1143,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1051
1143
|
makeSortable();
|
|
1052
1144
|
makeResizable();
|
|
1053
1145
|
switchLayout(curView);
|
|
1146
|
+
refreshPreviews(canvas);
|
|
1054
1147
|
}
|
|
1055
1148
|
|
|
1056
1149
|
function deinit() {
|
|
@@ -1071,6 +1164,10 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1071
1164
|
hideDropMarker();
|
|
1072
1165
|
canvas.find('.ge-tools-drawer').remove();
|
|
1073
1166
|
plugins('onDeinit');
|
|
1167
|
+
// After the rich text editors have let go of their content areas:
|
|
1168
|
+
// one that rebuilt its area's DOM brought the preview styles back
|
|
1169
|
+
// with it, and the attribute recording them came back too
|
|
1170
|
+
clearPreviews(canvas);
|
|
1074
1171
|
unmarkContainers();
|
|
1075
1172
|
removeSortable();
|
|
1076
1173
|
removeResizable();
|
|
@@ -1151,6 +1248,15 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1151
1248
|
if (wanted(name)) { FEATURES[name] = factory(api); }
|
|
1152
1249
|
});
|
|
1153
1250
|
|
|
1251
|
+
$.each($.fn.gridEditor.utilities, function(name, factory) {
|
|
1252
|
+
if (!wanted(name)) { return; }
|
|
1253
|
+
|
|
1254
|
+
UTILITIES[name] = factory(api);
|
|
1255
|
+
(UTILITIES[name].families || []).forEach(function(family) {
|
|
1256
|
+
registerFamily(name, UTILITIES[name], family);
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1154
1260
|
$.each(FEATURES, function(name, feature) {
|
|
1155
1261
|
$.each(feature.methods || {}, function(method, implementation) {
|
|
1156
1262
|
featureMethods[method] = implementation;
|
|
@@ -1158,7 +1264,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1158
1264
|
});
|
|
1159
1265
|
|
|
1160
1266
|
(settings.plugins || []).forEach(function(name) {
|
|
1161
|
-
if (CONTAINERS[name] || FEATURES[name]) { return; }
|
|
1267
|
+
if (CONTAINERS[name] || FEATURES[name] || UTILITIES[name]) { return; }
|
|
1162
1268
|
|
|
1163
1269
|
warnOnceHere('plugin:' + name, 'the "' + name + '" plugin is not loaded: ' +
|
|
1164
1270
|
'include dist/plugins/grideditor.' + name + '.js after the editor');
|
|
@@ -1168,7 +1274,8 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1168
1274
|
/**
|
|
1169
1275
|
* A hook every loaded plugin may have. Containers first, since a
|
|
1170
1276
|
* feature that looks at the canvas - elements, say - wants the
|
|
1171
|
-
* containers already marked
|
|
1277
|
+
* containers already marked, and utilities last, since they decorate
|
|
1278
|
+
* nodes the other two may have just made.
|
|
1172
1279
|
*/
|
|
1173
1280
|
function plugins(hook, argument) {
|
|
1174
1281
|
$.each(CONTAINERS, function(type, definition) {
|
|
@@ -1177,6 +1284,9 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1177
1284
|
$.each(FEATURES, function(name, feature) {
|
|
1178
1285
|
if (feature[hook]) { feature[hook](argument); }
|
|
1179
1286
|
});
|
|
1287
|
+
$.each(UTILITIES, function(name, utility) {
|
|
1288
|
+
if (utility[hook]) { utility[hook](argument); }
|
|
1289
|
+
});
|
|
1180
1290
|
}
|
|
1181
1291
|
|
|
1182
1292
|
/** What a plugin is handed. See docs/plugins.md. */
|
|
@@ -1202,9 +1312,491 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1202
1312
|
emit: emit,
|
|
1203
1313
|
payloadFor: payloadFor,
|
|
1204
1314
|
operate: operate,
|
|
1315
|
+
kindOf: kindOf,
|
|
1316
|
+
view: getView,
|
|
1317
|
+
viewTiers: function() {
|
|
1318
|
+
return tiersFor(curView).map(function(tier) { return tier.key; });
|
|
1319
|
+
},
|
|
1320
|
+
breakpoints: BREAKPOINTS.map(function(tier) { return tier.key; }),
|
|
1321
|
+
getUtility: getUtility,
|
|
1322
|
+
setUtility: setUtility,
|
|
1323
|
+
utilityField: utilityField,
|
|
1205
1324
|
};
|
|
1206
1325
|
}
|
|
1207
1326
|
|
|
1327
|
+
/* --------------------------------------------------------------
|
|
1328
|
+
* Utilities: Bootstrap's responsive utility classes.
|
|
1329
|
+
*
|
|
1330
|
+
* A utility plugin declares families - order, d, justify-content -
|
|
1331
|
+
* and this is everything that reads or writes one. Every family is
|
|
1332
|
+
* spelled the way Bootstrap spells them all, {prefix}-{infix}-{value}
|
|
1333
|
+
* with no infix at the smallest tier, and cascades the way the size
|
|
1334
|
+
* classes do: a tier that says nothing takes the nearest smaller one.
|
|
1335
|
+
*
|
|
1336
|
+
* The canvas cannot show a breakpoint's utilities by itself. A
|
|
1337
|
+
* per-breakpoint view narrows the canvas, not the viewport, and
|
|
1338
|
+
* Bootstrap's utilities answer to the viewport with !important. So
|
|
1339
|
+
* each node gets what its classes mean at the view being edited,
|
|
1340
|
+
* as inline !important styles, recorded in an attribute so they come
|
|
1341
|
+
* off again - exactly those, leaving the host's own style alone.
|
|
1342
|
+
* -------------------------------------------------------------- */
|
|
1343
|
+
|
|
1344
|
+
var FAMILIES = {}; // Every family the utility plugins declare, by name
|
|
1345
|
+
var UTILITY_NODES = '.row, .column, .ge-element, [data-ge-container]';
|
|
1346
|
+
var PREVIEW_ATTR = 'data-ge-preview';
|
|
1347
|
+
var utilitiesOpen = false; // Whether the panels show their Responsive section unfolded
|
|
1348
|
+
|
|
1349
|
+
function registerFamily(pluginName, utility, family) {
|
|
1350
|
+
var name = family.name || family.prefix;
|
|
1351
|
+
|
|
1352
|
+
if (FAMILIES[name]) {
|
|
1353
|
+
warn('the "' + pluginName + '" plugin declares the utility "' + name +
|
|
1354
|
+
'", which the "' + FAMILIES[name].plugin + '" plugin already declared: ignored');
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
FAMILIES[name] = {
|
|
1359
|
+
plugin: pluginName,
|
|
1360
|
+
family: $.extend({}, family, {
|
|
1361
|
+
name: name,
|
|
1362
|
+
values: family.values.map(String),
|
|
1363
|
+
appliesTo: family.appliesTo || utility.appliesTo || ['row', 'column'],
|
|
1364
|
+
}),
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/** The family a caller named, or null and a warning when no plugin declares it. */
|
|
1369
|
+
function familyNamed(name, method) {
|
|
1370
|
+
if (FAMILIES[name]) { return FAMILIES[name].family; }
|
|
1371
|
+
|
|
1372
|
+
warnOnceHere('utility:' + name, method + '(' + JSON.stringify(name) + '): no loaded ' +
|
|
1373
|
+
'plugin declares that utility');
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* Whether a family applies to a kind of node. A container answers to
|
|
1379
|
+
* `container` and to its own type, so a plugin can take them all or
|
|
1380
|
+
* name the ones it means.
|
|
1381
|
+
*/
|
|
1382
|
+
function appliesTo(family, kind) {
|
|
1383
|
+
if (family.appliesTo.indexOf(kind) !== -1) { return true; }
|
|
1384
|
+
|
|
1385
|
+
return !!CONTAINERS[kind] && family.appliesTo.indexOf('container') !== -1;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function familiesFor(kind) {
|
|
1389
|
+
return $.map(FAMILIES, function(entry) {
|
|
1390
|
+
return appliesTo(entry.family, kind) ? entry.family : null;
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
function utilityClass(family, tier, value) {
|
|
1395
|
+
return family.prefix + (tier.infix ? '-' + tier.infix : '') + '-' + value;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/** The value a node carries a class for at exactly this tier, or null. */
|
|
1399
|
+
function ownUtility(node, family, tier) {
|
|
1400
|
+
var classes = (node.attr('class') || '').split(/\s+/);
|
|
1401
|
+
|
|
1402
|
+
for (var i = 0; i < family.values.length; i++) {
|
|
1403
|
+
if (classes.indexOf(utilityClass(family, tier, family.values[i])) !== -1) {
|
|
1404
|
+
return family.values[i];
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* The tier below this one that decides the value here, and what it
|
|
1413
|
+
* says, when this tier says nothing itself. Null when nothing below
|
|
1414
|
+
* says anything either.
|
|
1415
|
+
*/
|
|
1416
|
+
function inheritedUtility(node, family, tier) {
|
|
1417
|
+
for (var i = BREAKPOINTS.indexOf(tier) - 1; i >= 0; i--) {
|
|
1418
|
+
var value = ownUtility(node, family, BREAKPOINTS[i]);
|
|
1419
|
+
if (value !== null) { return { tier: BREAKPOINTS[i], value: value }; }
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
/** What applies at a tier: its own class, or what it inherits. */
|
|
1426
|
+
function effectiveUtility(node, family, tier) {
|
|
1427
|
+
var own = ownUtility(node, family, tier);
|
|
1428
|
+
if (own !== null) { return own; }
|
|
1429
|
+
|
|
1430
|
+
var inherited = inheritedUtility(node, family, tier);
|
|
1431
|
+
return inherited ? inherited.value : null;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
/** Every tier the node carries a class of this family for. */
|
|
1435
|
+
function utilityTiers(node, family) {
|
|
1436
|
+
return BREAKPOINTS.map(function(tier) {
|
|
1437
|
+
return { tier: tier, value: ownUtility(node, family, tier) };
|
|
1438
|
+
}).filter(function(entry) { return entry.value !== null; });
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function writeUtility(node, family, tier, value) {
|
|
1442
|
+
family.values.forEach(function(candidate) {
|
|
1443
|
+
node.removeClass(utilityClass(family, tier, candidate));
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
if (value !== null) { node.addClass(utilityClass(family, tier, value)); }
|
|
1447
|
+
if (!node.attr('class')) { node.removeAttr('class'); }
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/**
|
|
1451
|
+
* What a view reads. A breakpoint reads what applies there. The all
|
|
1452
|
+
* view reads the class with no infix, since that is what it writes:
|
|
1453
|
+
* the one class that means "the same at every size".
|
|
1454
|
+
*/
|
|
1455
|
+
function readUtility(node, family, view) {
|
|
1456
|
+
return view === ALL_VIEW
|
|
1457
|
+
? ownUtility(node, family, BREAKPOINTS[0])
|
|
1458
|
+
: effectiveUtility(node, family, breakpoint(view));
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* What writing `value` in a view comes to, or null when it changes
|
|
1463
|
+
* nothing. A breakpoint writes its own tier and nothing else. The all
|
|
1464
|
+
* view writes the class with no infix and clears the family from
|
|
1465
|
+
* every other tier, and says what it cleared: choosing one value for
|
|
1466
|
+
* every size is choosing it over what the sizes said.
|
|
1467
|
+
*/
|
|
1468
|
+
function planUtility(node, family, view, value) {
|
|
1469
|
+
if (view !== ALL_VIEW) {
|
|
1470
|
+
var tier = breakpoint(view);
|
|
1471
|
+
if (ownUtility(node, family, tier) === value) { return null; }
|
|
1472
|
+
|
|
1473
|
+
return { writes: [{ tier: tier, value: value }], cleared: [] };
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
var writes = [];
|
|
1477
|
+
var cleared = [];
|
|
1478
|
+
|
|
1479
|
+
utilityTiers(node, family).forEach(function(entry) {
|
|
1480
|
+
if (entry.tier === BREAKPOINTS[0]) { return; }
|
|
1481
|
+
|
|
1482
|
+
writes.push({ tier: entry.tier, value: null });
|
|
1483
|
+
cleared.push({ breakpoint: entry.tier.key, value: entry.value });
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
if (ownUtility(node, family, BREAKPOINTS[0]) !== value) {
|
|
1487
|
+
writes.unshift({ tier: BREAKPOINTS[0], value: value });
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
return writes.length ? { writes: writes, cleared: cleared } : null;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/** getUtility(node, family, view?): the value that applies, or null. */
|
|
1494
|
+
function getUtility(node, name, view) {
|
|
1495
|
+
node = $(node).first();
|
|
1496
|
+
|
|
1497
|
+
var family = familyNamed(name, 'getUtility');
|
|
1498
|
+
if (!family || !node.length) { return null; }
|
|
1499
|
+
|
|
1500
|
+
var key = view === undefined ? curView : viewKey(view);
|
|
1501
|
+
if (key === null) {
|
|
1502
|
+
warn('getUtility(' + JSON.stringify(view) + '): no such layout mode');
|
|
1503
|
+
return null;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
return readUtility(node, family, key);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* setUtility(node, family, value, view?): write a value through the
|
|
1511
|
+
* events. Null is "inherit". The last argument is a view key, or
|
|
1512
|
+
* { view, source } from a plugin that wants its tool named in the
|
|
1513
|
+
* payload. False when a handler canceled or nothing changed.
|
|
1514
|
+
*/
|
|
1515
|
+
function setUtility(node, name, value, options) {
|
|
1516
|
+
options = typeof options == 'string' ? { view: options } : (options || {});
|
|
1517
|
+
node = $(node).first();
|
|
1518
|
+
|
|
1519
|
+
var family = familyNamed(name, 'setUtility');
|
|
1520
|
+
if (!family || !node.length) { return false; }
|
|
1521
|
+
|
|
1522
|
+
var view = options.view === undefined ? curView : viewKey(options.view);
|
|
1523
|
+
if (view === null) {
|
|
1524
|
+
warn('setUtility(' + JSON.stringify(options.view) + '): no such layout mode');
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
value = value === null || value === undefined || value === '' ? null : String(value);
|
|
1529
|
+
if (value !== null && family.values.indexOf(value) === -1) {
|
|
1530
|
+
warn('setUtility: ' + JSON.stringify(value) + ' is not a value of "' + name +
|
|
1531
|
+
'", which takes ' + JSON.stringify(family.values));
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
var kind = kindOf(node);
|
|
1536
|
+
if (!appliesTo(family, kind)) {
|
|
1537
|
+
warn('setUtility: "' + name + '" does not apply to a ' + kind);
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
var plan = planUtility(node, family, view, value);
|
|
1542
|
+
if (!plan) { return false; }
|
|
1543
|
+
|
|
1544
|
+
return operate(function() {
|
|
1545
|
+
var payload = payloadFor(kind, node, {
|
|
1546
|
+
family: name,
|
|
1547
|
+
breakpoint: view,
|
|
1548
|
+
tiers: plan.writes.map(function(write) { return write.tier.key; }),
|
|
1549
|
+
from: readUtility(node, family, view),
|
|
1550
|
+
to: value,
|
|
1551
|
+
cleared: plan.cleared,
|
|
1552
|
+
source: options.source || 'api',
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
if (!emit('before-utility', payload)) {
|
|
1556
|
+
// The panel already shows the value that was refused
|
|
1557
|
+
refreshUtilities(node);
|
|
1558
|
+
return false;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
plan.writes.forEach(function(write) {
|
|
1562
|
+
writeUtility(node, family, write.tier, write.value);
|
|
1563
|
+
});
|
|
1564
|
+
refreshUtilities(node);
|
|
1565
|
+
emit('after-utility', payload);
|
|
1566
|
+
|
|
1567
|
+
return true;
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/** Bring a node's panel and preview up to date with its classes. */
|
|
1572
|
+
function refreshUtilities(node) {
|
|
1573
|
+
var details = node.children('.ge-tools-drawer').children('.ge-details');
|
|
1574
|
+
|
|
1575
|
+
details.children('.ge-classes').val(hostClasses(node).join(' '));
|
|
1576
|
+
details.children('.ge-utilities').each(function() { renderUtilities($(this)); });
|
|
1577
|
+
refreshPreviews(node);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/* Preview */
|
|
1581
|
+
|
|
1582
|
+
function utilityNodes(scope) {
|
|
1583
|
+
return scope.find(UTILITY_NODES).addBack(UTILITY_NODES);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Put what each node's utilities mean at the view being edited on the
|
|
1588
|
+
* node itself. Only nodes that carry a class of a family get anything,
|
|
1589
|
+
* and they get the family's answer for the view even when that is
|
|
1590
|
+
* "nothing": a wider tier's class is still live in a wide window, and
|
|
1591
|
+
* has to be overruled.
|
|
1592
|
+
*
|
|
1593
|
+
* The all view gets nothing. Its canvas is not narrowed, every tier is
|
|
1594
|
+
* live, and what Bootstrap shows is the truth.
|
|
1595
|
+
*/
|
|
1596
|
+
function refreshPreviews(scope) {
|
|
1597
|
+
clearPreviews(scope);
|
|
1598
|
+
|
|
1599
|
+
if (curView !== ALL_VIEW) { previewTier(scope, breakpoint(curView)); }
|
|
1600
|
+
|
|
1601
|
+
// Whatever a plugin marks the canvas with to show its utilities
|
|
1602
|
+
// goes stale at the same moments the preview does
|
|
1603
|
+
plugins('onRefresh', scope);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function previewTier(scope, tier) {
|
|
1607
|
+
utilityNodes(scope).each(function() {
|
|
1608
|
+
var node = $(this);
|
|
1609
|
+
var kind = kindOf(node);
|
|
1610
|
+
var styles = {};
|
|
1611
|
+
|
|
1612
|
+
familiesFor(kind).forEach(function(family) {
|
|
1613
|
+
if (!family.preview || !utilityTiers(node, family).length) { return; }
|
|
1614
|
+
|
|
1615
|
+
$.extend(styles, family.preview(effectiveUtility(node, family, tier), node, kind));
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
// A plugin whose families settle one property between them
|
|
1619
|
+
// previews the node as a whole
|
|
1620
|
+
$.each(UTILITIES, function(name, utility) {
|
|
1621
|
+
if (utility.preview) { $.extend(styles, utility.preview(node, kind, tier.key)); }
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
if (!$.isEmptyObject(styles)) { applyPreview(node, styles); }
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* Set inline !important styles, which is the one thing that beats
|
|
1630
|
+
* Bootstrap's own !important, and remember what each property was so
|
|
1631
|
+
* it can be put back. The record is an attribute rather than jQuery
|
|
1632
|
+
* data because a rich text editor rebuilds the DOM of the area it
|
|
1633
|
+
* edits, and the record has to come back with the node.
|
|
1634
|
+
*/
|
|
1635
|
+
function applyPreview(node, styles) {
|
|
1636
|
+
var style = node[0].style;
|
|
1637
|
+
var was = {};
|
|
1638
|
+
|
|
1639
|
+
$.each(styles, function(property, value) {
|
|
1640
|
+
was[property] = [style.getPropertyValue(property), style.getPropertyPriority(property)];
|
|
1641
|
+
style.setProperty(property, String(value), 'important');
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
node.attr(PREVIEW_ATTR, JSON.stringify(was));
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
function clearPreviews(scope) {
|
|
1648
|
+
scope.find('[' + PREVIEW_ATTR + ']').addBack('[' + PREVIEW_ATTR + ']').each(function() {
|
|
1649
|
+
var node = $(this);
|
|
1650
|
+
var style = this.style;
|
|
1651
|
+
var was = {};
|
|
1652
|
+
|
|
1653
|
+
try { was = JSON.parse(node.attr(PREVIEW_ATTR)) || {}; } catch (error) { /* a mangled record: drop it */ }
|
|
1654
|
+
|
|
1655
|
+
$.each(was, function(property, before) {
|
|
1656
|
+
if (before && before[0]) {
|
|
1657
|
+
style.setProperty(property, before[0], before[1]);
|
|
1658
|
+
} else {
|
|
1659
|
+
style.removeProperty(property);
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
node.removeAttr(PREVIEW_ATTR);
|
|
1664
|
+
if (!node.attr('style')) { node.removeAttr('style'); }
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/* The panel */
|
|
1669
|
+
|
|
1670
|
+
/**
|
|
1671
|
+
* The Responsive section of a node's settings panel: one field per
|
|
1672
|
+
* family that applies to the node, reading and writing the view being
|
|
1673
|
+
* edited, and whatever fields a plugin builds itself. Folded until the
|
|
1674
|
+
* user unfolds one, and then unfolded on every node, since whoever
|
|
1675
|
+
* wanted it on one wants it on the next.
|
|
1676
|
+
*/
|
|
1677
|
+
function createUtilitiesSection(node) {
|
|
1678
|
+
var kind = kindOf(node);
|
|
1679
|
+
var fields = familiesFor(kind)
|
|
1680
|
+
.filter(function(family) { return family.panel !== false; })
|
|
1681
|
+
.map(function(family) { return createField(node, family); });
|
|
1682
|
+
|
|
1683
|
+
$.each(UTILITIES, function(name, utility) {
|
|
1684
|
+
var own = utility.panel ? utility.panel(node, kind) : null;
|
|
1685
|
+
if (own && own.length) { fields.push(own); }
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
if (!fields.length) { return null; }
|
|
1689
|
+
|
|
1690
|
+
var section = $('<div class="ge-utilities" />')
|
|
1691
|
+
.toggleClass('ge-open', utilitiesOpen)
|
|
1692
|
+
.data('ge-node', node)
|
|
1693
|
+
;
|
|
1694
|
+
|
|
1695
|
+
$('<a class="ge-utilities-toggle" />')
|
|
1696
|
+
.appendTo(section)
|
|
1697
|
+
.on('click', function() {
|
|
1698
|
+
utilitiesOpen = !section.hasClass('ge-open');
|
|
1699
|
+
canvas.find('.ge-utilities').toggleClass('ge-open', utilitiesOpen);
|
|
1700
|
+
})
|
|
1701
|
+
;
|
|
1702
|
+
|
|
1703
|
+
var body = $('<div class="ge-utilities-body" />').appendTo(section);
|
|
1704
|
+
fields.forEach(function(field) { field.appendTo(body); });
|
|
1705
|
+
|
|
1706
|
+
renderUtilities(section);
|
|
1707
|
+
|
|
1708
|
+
return section;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* One family's field: its label, a select that writes through
|
|
1713
|
+
* setUtility, and a note. Filled by renderField, again whenever the
|
|
1714
|
+
* view or the node's classes change.
|
|
1715
|
+
*/
|
|
1716
|
+
function createField(node, family) {
|
|
1717
|
+
var field = $('<label class="ge-utility" />').attr('data-ge-family', family.name);
|
|
1718
|
+
|
|
1719
|
+
$('<span class="ge-utility-label" />')
|
|
1720
|
+
.text(family.labelKey ? t(family.labelKey) : family.name)
|
|
1721
|
+
.appendTo(field)
|
|
1722
|
+
;
|
|
1723
|
+
$('<select />')
|
|
1724
|
+
.appendTo(field)
|
|
1725
|
+
.on('change', function() {
|
|
1726
|
+
setUtility(node, family.name, this.value, { source: 'panel' });
|
|
1727
|
+
})
|
|
1728
|
+
;
|
|
1729
|
+
$('<small class="ge-utility-note" />').appendTo(field);
|
|
1730
|
+
|
|
1731
|
+
return field;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
/** ge.utilityField(node, family): a field a plugin places in a panel of its own. */
|
|
1735
|
+
function utilityField(node, name) {
|
|
1736
|
+
var family = familyNamed(name, 'utilityField');
|
|
1737
|
+
if (!family) { return $(); }
|
|
1738
|
+
|
|
1739
|
+
var field = createField(node, family);
|
|
1740
|
+
renderField(field, node);
|
|
1741
|
+
|
|
1742
|
+
return field;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/** Fill a section's fields, the families' and the plugins' own, for the view being edited. */
|
|
1746
|
+
function renderUtilities(section) {
|
|
1747
|
+
var node = section.data('ge-node');
|
|
1748
|
+
|
|
1749
|
+
section.children('.ge-utilities-toggle')
|
|
1750
|
+
.text(t('utility.section', { view: t(labelKeyFor(curView)) }));
|
|
1751
|
+
|
|
1752
|
+
section.find('.ge-utility').each(function() { renderField($(this), node); });
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function renderField(field, node) {
|
|
1756
|
+
var family = FAMILIES[field.attr('data-ge-family')].family;
|
|
1757
|
+
var select = field.children('select').empty();
|
|
1758
|
+
var choices = family.choices ? family.choices(node, kindOf(node)) : family.values;
|
|
1759
|
+
var own, blank, note = '';
|
|
1760
|
+
|
|
1761
|
+
if (curView === ALL_VIEW) {
|
|
1762
|
+
own = ownUtility(node, family, BREAKPOINTS[0]);
|
|
1763
|
+
blank = t('utility.default');
|
|
1764
|
+
|
|
1765
|
+
var varies = utilityTiers(node, family).filter(function(entry) {
|
|
1766
|
+
return entry.tier !== BREAKPOINTS[0];
|
|
1767
|
+
});
|
|
1768
|
+
if (varies.length) {
|
|
1769
|
+
note = t('utility.varies', {
|
|
1770
|
+
breakpoints: varies.map(function(entry) { return entry.tier.key; }).join(', '),
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
} else {
|
|
1774
|
+
var tier = breakpoint(curView);
|
|
1775
|
+
var inherited = inheritedUtility(node, family, tier);
|
|
1776
|
+
|
|
1777
|
+
own = ownUtility(node, family, tier);
|
|
1778
|
+
blank = inherited
|
|
1779
|
+
? t('utility.inherit', { value: labelOf(family, inherited.value), breakpoint: inherited.tier.key })
|
|
1780
|
+
: t('utility.default');
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// A value the markup carries is shown even when the family
|
|
1784
|
+
// would not offer it here, rather than shown as something else
|
|
1785
|
+
if (own !== null && choices.indexOf(own) === -1) { choices = choices.concat([own]); }
|
|
1786
|
+
|
|
1787
|
+
$('<option value="" />').text(blank).appendTo(select);
|
|
1788
|
+
choices.forEach(function(value) {
|
|
1789
|
+
$('<option />').attr('value', value).text(labelOf(family, value)).appendTo(select);
|
|
1790
|
+
});
|
|
1791
|
+
|
|
1792
|
+
select.val(own === null ? '' : own);
|
|
1793
|
+
field.children('.ge-utility-note').text(note).toggle(note !== '');
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
function labelOf(family, value) {
|
|
1797
|
+
return family.label ? family.label(value) : value;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1208
1800
|
/* --------------------------------------------------------------
|
|
1209
1801
|
* Containers: tabs, accordions and popups.
|
|
1210
1802
|
*
|
|
@@ -1220,6 +1812,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1220
1812
|
|
|
1221
1813
|
var CONTAINERS = {}; // The container plugins in use, by the type each builds
|
|
1222
1814
|
var FEATURES = {}; // The feature plugins in use, by name
|
|
1815
|
+
var UTILITIES = {}; // The utility plugins in use, by name
|
|
1223
1816
|
var featureMethods = {}; // The methods those features contribute
|
|
1224
1817
|
var containerCounter = 0;
|
|
1225
1818
|
|
|
@@ -1526,9 +2119,9 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1526
2119
|
|
|
1527
2120
|
var room = spare(row, leadingTier());
|
|
1528
2121
|
|
|
1529
|
-
// The drawer it hangs off is raised while it is open:
|
|
1530
|
-
//
|
|
1531
|
-
//
|
|
2122
|
+
// The drawer it hangs off is raised while it is open: drawers sit
|
|
2123
|
+
// below the resize handles, so without this the drawer of the
|
|
2124
|
+
// column below takes the clicks meant for the picker
|
|
1532
2125
|
tool.closest('.ge-tools-drawer').addClass('ge-picker-open');
|
|
1533
2126
|
|
|
1534
2127
|
sizePicker = $('<div class="ge-size-picker" />').appendTo(tool);
|
|
@@ -1788,6 +2381,13 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1788
2381
|
details.toggle();
|
|
1789
2382
|
});
|
|
1790
2383
|
|
|
2384
|
+
// Beside the gear, because every node that has one is a node a
|
|
2385
|
+
// utility may apply to, whichever plugin built its drawer
|
|
2386
|
+
var kind = kindOf(node);
|
|
2387
|
+
$.each(UTILITIES, function(name, utility) {
|
|
2388
|
+
if (utility.drawerTools) { utility.drawerTools(drawer, node, kind); }
|
|
2389
|
+
});
|
|
2390
|
+
|
|
1791
2391
|
return details.appendTo(drawer);
|
|
1792
2392
|
}
|
|
1793
2393
|
|
|
@@ -1816,6 +2416,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1816
2416
|
.appendTo(detailsDiv)
|
|
1817
2417
|
.on('change', function() {
|
|
1818
2418
|
setHostClasses(container, this.value);
|
|
2419
|
+
refreshUtilities(container);
|
|
1819
2420
|
})
|
|
1820
2421
|
;
|
|
1821
2422
|
|
|
@@ -1828,11 +2429,15 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1828
2429
|
.on('click', function() {
|
|
1829
2430
|
btn.toggleClass('active btn-primary');
|
|
1830
2431
|
container.toggleClass(rowClass.cssClass, btn.hasClass('active'));
|
|
2432
|
+
refreshUtilities(container);
|
|
1831
2433
|
})
|
|
1832
2434
|
.appendTo(classGroup)
|
|
1833
2435
|
;
|
|
1834
2436
|
});
|
|
1835
2437
|
|
|
2438
|
+
var utilities = createUtilitiesSection(container);
|
|
2439
|
+
if (utilities) { utilities.appendTo(detailsDiv); }
|
|
2440
|
+
|
|
1836
2441
|
return detailsDiv;
|
|
1837
2442
|
}
|
|
1838
2443
|
|
|
@@ -1981,9 +2586,10 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1981
2586
|
}
|
|
1982
2587
|
|
|
1983
2588
|
/**
|
|
1984
|
-
* Drag resize leaves an inline pixel width behind
|
|
1985
|
-
*
|
|
1986
|
-
* canvas once the size class has
|
|
2589
|
+
* Drag resize leaves an inline pixel width behind: the column follows
|
|
2590
|
+
* the pointer in pixels while the gesture lasts. It does not belong in
|
|
2591
|
+
* the markup a host saves, or in the canvas once the size class has
|
|
2592
|
+
* been written.
|
|
1987
2593
|
*/
|
|
1988
2594
|
function stripPixelWidths(scope) {
|
|
1989
2595
|
scope.find('.column').addBack('.column').each(function() {
|
|
@@ -1997,112 +2603,196 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1997
2603
|
});
|
|
1998
2604
|
}
|
|
1999
2605
|
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2606
|
+
/**
|
|
2607
|
+
* A group name, scoped to this editor.
|
|
2608
|
+
*
|
|
2609
|
+
* Two editors on one page must not drag into each other, since a node
|
|
2610
|
+
* carries its drawer - and the events that go with it - from the
|
|
2611
|
+
* editor that made it.
|
|
2612
|
+
*/
|
|
2613
|
+
function groupName(name) {
|
|
2614
|
+
return 'ge-' + name + '-' + instanceId;
|
|
2615
|
+
}
|
|
2005
2616
|
|
|
2617
|
+
/** What must never start a drag, whatever the handle is. */
|
|
2618
|
+
function dragCancelSelector() {
|
|
2619
|
+
return settings.drag_handle === 'drawer'
|
|
2006
2620
|
// With the whole drawer as the handle, the tools inside it are
|
|
2007
2621
|
// still tools: a drag starting on one would swallow its click,
|
|
2008
2622
|
// and the settings panel has fields to type in
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2623
|
+
? '.ge-tools-drawer > a, .ge-details, input, textarea, button, select, option'
|
|
2624
|
+
: 'input, textarea, button, select, option';
|
|
2625
|
+
}
|
|
2012
2626
|
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2627
|
+
/**
|
|
2628
|
+
* Every sortable list the editor makes, core's and a plugin's alike,
|
|
2629
|
+
* is made here.
|
|
2630
|
+
*
|
|
2631
|
+
* A caller says what moves (`draggable`) and what the list connects to
|
|
2632
|
+
* (`group`, or none for a list that sorts only within itself); the
|
|
2633
|
+
* handle, the filter, the callbacks and the drag settings are this
|
|
2634
|
+
* function's business. That is the whole point of it: the toolkit
|
|
2635
|
+
* underneath is named in one place, so replacing it is one function
|
|
2636
|
+
* and not thirty call sites.
|
|
2637
|
+
*/
|
|
2638
|
+
function sortable(lists, options) {
|
|
2639
|
+
if (!lists.length || !window.Sortable) { return; }
|
|
2017
2640
|
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2641
|
+
var wholeDrawer = settings.drag_handle === 'drawer';
|
|
2642
|
+
var cancel = dragCancelSelector();
|
|
2643
|
+
var soloGroup = 0;
|
|
2644
|
+
|
|
2645
|
+
lists.each(function() {
|
|
2646
|
+
var list = this;
|
|
2647
|
+
|
|
2648
|
+
// A list with no group sorts only within itself, which is what
|
|
2649
|
+
// a tab strip wants: a name of its own, closed both ways
|
|
2650
|
+
var group = options.group
|
|
2651
|
+
? { name: groupName(options.group) }
|
|
2652
|
+
: { name: groupName(options.draggable + '-' + (++soloGroup)), pull: false, put: false };
|
|
2653
|
+
|
|
2654
|
+
sortables.push(window.Sortable.create(list, $.extend({
|
|
2655
|
+
group: group,
|
|
2656
|
+
draggable: options.draggable,
|
|
2657
|
+
handle: wholeDrawer ? '.ge-tools-drawer' : '.ge-tools-drawer .ge-move',
|
|
2658
|
+
|
|
2659
|
+
/**
|
|
2660
|
+
* Two refusals in one, because SortableJS asks once.
|
|
2661
|
+
*
|
|
2662
|
+
* A tool or a form field never starts a drag, as the
|
|
2663
|
+
* cancel list said before. And only a direct child of
|
|
2664
|
+
* this list moves: the selector is matched against every
|
|
2665
|
+
* descendant, so without this the canvas would pick up a
|
|
2666
|
+
* row nested three columns down and drag that.
|
|
2667
|
+
*/
|
|
2668
|
+
filter: function(e, item) {
|
|
2669
|
+
if ($(e.target).closest(cancel, list).length) { return true; }
|
|
2670
|
+
|
|
2671
|
+
return !item || item.parentNode !== list;
|
|
2672
|
+
},
|
|
2673
|
+
|
|
2674
|
+
// A filtered pointerdown is still a click on a tool
|
|
2675
|
+
preventOnFilter: false,
|
|
2676
|
+
|
|
2677
|
+
// The HTML5 drag and drop API cannot be driven by
|
|
2678
|
+
// synthetic events, so the tests could not exist without
|
|
2679
|
+
// this; it also gives one helper across browsers
|
|
2680
|
+
forceFallback: true,
|
|
2681
|
+
fallbackOnBody: true,
|
|
2682
|
+
|
|
2683
|
+
ghostClass: 'ge-drag-placeholder',
|
|
2684
|
+
chosenClass: 'ge-drag-chosen',
|
|
2685
|
+
dragClass: 'ge-drag-helper',
|
|
2686
|
+
fallbackClass: 'ge-drag-helper',
|
|
2687
|
+
|
|
2688
|
+
animation: settings.drag.animation,
|
|
2689
|
+
|
|
2690
|
+
// One delay, which applies to both gestures or to touch
|
|
2691
|
+
// alone: a touch drag that starts instantly takes the
|
|
2692
|
+
// page's scrolling with it, a mouse drag has no such
|
|
2693
|
+
// problem, and asking for `delay` means asking for both
|
|
2694
|
+
delay: settings.drag.delay || settings.drag.touch_delay,
|
|
2695
|
+
delayOnTouchOnly: !settings.drag.delay,
|
|
2696
|
+
|
|
2697
|
+
touchStartThreshold: settings.drag.threshold,
|
|
2698
|
+
scroll: settings.drag.scroll,
|
|
2699
|
+
|
|
2700
|
+
onStart: sortStart,
|
|
2701
|
+
onEnd: sortEnd,
|
|
2702
|
+
}, options.options || {})));
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2023
2705
|
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2706
|
+
function makeSortable() {
|
|
2707
|
+
if (!window.Sortable) {
|
|
2708
|
+
warnOnceHere('sortable_missing', t('error.sortable_missing'));
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2028
2711
|
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
}
|
|
2712
|
+
sortable(canvas.find('.row'), {
|
|
2713
|
+
draggable: '.column',
|
|
2714
|
+
group: 'column',
|
|
2715
|
+
});
|
|
2033
2716
|
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
}, shared, settings.sortable_options));
|
|
2717
|
+
sortable(canvas.add(canvas.find('.column')), {
|
|
2718
|
+
draggable: '.row, .ge-content, [data-ge-container]',
|
|
2719
|
+
group: 'block',
|
|
2720
|
+
});
|
|
2039
2721
|
|
|
2040
2722
|
// A plugin makes its own: only it knows which of its parts move
|
|
2041
|
-
plugins('onSortable',
|
|
2042
|
-
|
|
2043
|
-
/**
|
|
2044
|
-
* jQuery UI cannot refuse a drag once it has started, so a
|
|
2045
|
-
* canceled before-move is remembered here and undone on drop
|
|
2046
|
-
* (spec 2.4). The node carries the mark, because with connected
|
|
2047
|
-
* lists the drop is not always reported by the list that started
|
|
2048
|
-
* the drag.
|
|
2049
|
-
*/
|
|
2050
|
-
function sortStart(e, ui) {
|
|
2051
|
-
ui.placeholder.css({ height: ui.item.outerHeight()});
|
|
2052
|
-
|
|
2053
|
-
var node = ui.item;
|
|
2054
|
-
var from = positionOf(node);
|
|
2055
|
-
|
|
2056
|
-
node.data('ge-move-from', from);
|
|
2057
|
-
node.removeData('ge-move-canceled');
|
|
2723
|
+
plugins('onSortable', sortable);
|
|
2724
|
+
}
|
|
2058
2725
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2726
|
+
/**
|
|
2727
|
+
* A drag cannot be refused once it has started, so a canceled
|
|
2728
|
+
* before-move is remembered here and undone on drop (spec 2.4). The
|
|
2729
|
+
* node carries the mark, because with connected lists the drop is not
|
|
2730
|
+
* always reported by the list that started the drag.
|
|
2731
|
+
*/
|
|
2732
|
+
function sortStart(e) {
|
|
2733
|
+
var node = $(e.item);
|
|
2734
|
+
var from = positionOf(node);
|
|
2065
2735
|
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
}
|
|
2736
|
+
node.data('ge-move-from', from);
|
|
2737
|
+
node.removeData('ge-move-canceled');
|
|
2069
2738
|
|
|
2070
|
-
function
|
|
2071
|
-
var
|
|
2072
|
-
|
|
2739
|
+
operate(function() {
|
|
2740
|
+
var moving = emit('before-move', payloadFor(kindOf(node), node, {
|
|
2741
|
+
parent: from.parent,
|
|
2742
|
+
source: 'dragdrop',
|
|
2743
|
+
from: from,
|
|
2744
|
+
}));
|
|
2073
2745
|
|
|
2074
|
-
node.
|
|
2746
|
+
if (!moving) { node.data('ge-move-canceled', true); }
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2075
2749
|
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
return;
|
|
2080
|
-
}
|
|
2750
|
+
/** Put a node back where a refused drag found it. */
|
|
2751
|
+
function putBack(node, from) {
|
|
2752
|
+
var siblings = from.parent.children().not('.ge-tools-drawer').not(node);
|
|
2081
2753
|
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2754
|
+
if (!siblings.length || from.index >= siblings.length) {
|
|
2755
|
+
node.appendTo(from.parent);
|
|
2756
|
+
} else {
|
|
2757
|
+
node.insertBefore(siblings.eq(from.index));
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2086
2760
|
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
definition.afterPaneMove(container, node, from);
|
|
2091
|
-
}
|
|
2761
|
+
function sortEnd(e) {
|
|
2762
|
+
var node = $(e.item);
|
|
2763
|
+
var from = node.data('ge-move-from') || positionOf(node);
|
|
2092
2764
|
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
});
|
|
2765
|
+
node.removeData('ge-move-from');
|
|
2766
|
+
|
|
2767
|
+
if (node.data('ge-move-canceled')) {
|
|
2768
|
+
node.removeData('ge-move-canceled');
|
|
2769
|
+
putBack(node, from);
|
|
2770
|
+
return;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
var to = positionOf(node);
|
|
2774
|
+
if (to.parent[0] === from.parent[0] && to.index === from.index) {
|
|
2775
|
+
return; // A drag that went nowhere is not a move
|
|
2105
2776
|
}
|
|
2777
|
+
|
|
2778
|
+
var container = node.closest('[data-ge-container]');
|
|
2779
|
+
var definition = CONTAINERS[containerTypeOf(container)];
|
|
2780
|
+
if (definition && definition.afterPaneMove) {
|
|
2781
|
+
definition.afterPaneMove(container, node, from);
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
// No init() here, unlike an add: the node brought its drawer
|
|
2785
|
+
// with it, and the drag is still being finished, so this is the
|
|
2786
|
+
// wrong moment to rebuild the lists it is using.
|
|
2787
|
+
operate(function() {
|
|
2788
|
+
emit('after-move', payloadFor(kindOf(node), node, {
|
|
2789
|
+
parent: to.parent,
|
|
2790
|
+
source: 'dragdrop',
|
|
2791
|
+
from: from,
|
|
2792
|
+
to: to,
|
|
2793
|
+
container: container.length ? container : undefined,
|
|
2794
|
+
}));
|
|
2795
|
+
});
|
|
2106
2796
|
}
|
|
2107
2797
|
|
|
2108
2798
|
/**
|
|
@@ -2119,24 +2809,74 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2119
2809
|
|
|
2120
2810
|
canvas.find('.column').each(function() {
|
|
2121
2811
|
var col = $(this);
|
|
2122
|
-
if (col.
|
|
2812
|
+
if (col.find('> .ge-resize-handle').length) { return; }
|
|
2123
2813
|
|
|
2124
2814
|
$('<span class="ge-resize-size" />').appendTo(col.find('> .ge-tools-drawer'));
|
|
2125
2815
|
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2816
|
+
$.each(resizeEdges(), function(_, edge) {
|
|
2817
|
+
$('<span class="ge-resize-handle" />')
|
|
2818
|
+
.addClass('ge-resize-' + edge)
|
|
2819
|
+
.attr('data-ge-edge', edge)
|
|
2820
|
+
.on('pointerdown', startResizeDrag)
|
|
2821
|
+
.appendTo(col)
|
|
2822
|
+
;
|
|
2823
|
+
});
|
|
2132
2824
|
});
|
|
2133
2825
|
}
|
|
2134
2826
|
|
|
2827
|
+
/** Which edges carry a handle: 'e', 'w', or both. */
|
|
2828
|
+
function resizeEdges() {
|
|
2829
|
+
return String(settings.resize.handles).split(',')
|
|
2830
|
+
.map(function(edge) { return edge.trim(); })
|
|
2831
|
+
.filter(function(edge) { return edge === 'e' || edge === 'w'; });
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
/**
|
|
2835
|
+
* One resize gesture, from the pointer going down on a handle to it
|
|
2836
|
+
* coming up again.
|
|
2837
|
+
*
|
|
2838
|
+
* The pointer is captured, so a fast drag that leaves the column
|
|
2839
|
+
* behind keeps resizing it, and a refused before-resize simply never
|
|
2840
|
+
* starts: nothing is written and the column does not move.
|
|
2841
|
+
*/
|
|
2842
|
+
function startResizeDrag(e) {
|
|
2843
|
+
var handle = $(e.currentTarget);
|
|
2844
|
+
var col = handle.parent();
|
|
2845
|
+
var west = handle.attr('data-ge-edge') === 'w';
|
|
2846
|
+
var startX = e.pageX;
|
|
2847
|
+
var startWidth = col.outerWidth();
|
|
2848
|
+
|
|
2849
|
+
e.preventDefault();
|
|
2850
|
+
|
|
2851
|
+
if (!resizeStart(col)) { return; }
|
|
2852
|
+
|
|
2853
|
+
col.addClass('ge-resizing');
|
|
2854
|
+
if (e.pointerId !== undefined && handle[0].setPointerCapture) {
|
|
2855
|
+
handle[0].setPointerCapture(e.pointerId);
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
function widthAt(move) {
|
|
2859
|
+
var delta = move.pageX - startX;
|
|
2860
|
+
|
|
2861
|
+
return Math.max(1, startWidth + (west ? -delta : delta));
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
function onMove(move) {
|
|
2865
|
+
col.css('width', widthAt(move) + 'px');
|
|
2866
|
+
resizeMove(col, widthAt(move));
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
function onUp(up) {
|
|
2870
|
+
handle.off('pointermove', onMove).off('pointerup pointercancel', onUp);
|
|
2871
|
+
col.removeClass('ge-resizing');
|
|
2872
|
+
resizeStop(col, widthAt(up));
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
handle.on('pointermove', onMove).on('pointerup pointercancel', onUp);
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2135
2878
|
function removeResizable() {
|
|
2136
|
-
canvas.find('.
|
|
2137
|
-
var col = $(this);
|
|
2138
|
-
if (col.data('ui-resizable')) { col.resizable('destroy'); }
|
|
2139
|
-
});
|
|
2879
|
+
canvas.find('.ge-resize-handle').remove();
|
|
2140
2880
|
|
|
2141
2881
|
canvas.find('.ge-resize-size').remove();
|
|
2142
2882
|
stripPixelWidths(canvas);
|
|
@@ -2189,16 +2929,12 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2189
2929
|
}
|
|
2190
2930
|
|
|
2191
2931
|
/**
|
|
2192
|
-
* A canceled before-resize refuses the
|
|
2193
|
-
*
|
|
2194
|
-
*
|
|
2195
|
-
*
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
* where it began.
|
|
2199
|
-
*/
|
|
2200
|
-
function resizeStart(e, ui) {
|
|
2201
|
-
var col = $(this);
|
|
2932
|
+
* A canceled before-resize refuses the gesture before it begins, so
|
|
2933
|
+
* nothing is written and the column does not move. Under jQuery UI
|
|
2934
|
+
* this took a flag on the column and a refusal from every step of the
|
|
2935
|
+
* drag, because resizable ignores false from its start handler.
|
|
2936
|
+
*/
|
|
2937
|
+
function resizeStart(col) {
|
|
2202
2938
|
var from = currentSize(col);
|
|
2203
2939
|
|
|
2204
2940
|
var allowed = operate(function() {
|
|
@@ -2209,40 +2945,27 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2209
2945
|
}));
|
|
2210
2946
|
});
|
|
2211
2947
|
|
|
2212
|
-
if (!allowed) {
|
|
2213
|
-
col.data('ge-resize-refused', true);
|
|
2214
|
-
return false;
|
|
2215
|
-
}
|
|
2948
|
+
if (!allowed) { return false; }
|
|
2216
2949
|
|
|
2217
2950
|
col.data('ge-resize-from', from);
|
|
2218
2951
|
resizeReadout(col, sizeLabel(from));
|
|
2219
2952
|
|
|
2220
|
-
return
|
|
2953
|
+
return true;
|
|
2221
2954
|
}
|
|
2222
2955
|
|
|
2223
|
-
function resizeMove(
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
if (col.data('ge-resize-refused')) { return false; }
|
|
2227
|
-
|
|
2228
|
-
resizeReadout(col, sizeLabel(snapUnits(col, ui.size.width)));
|
|
2229
|
-
|
|
2230
|
-
return undefined;
|
|
2956
|
+
function resizeMove(col, width) {
|
|
2957
|
+
resizeReadout(col, sizeLabel(snapUnits(col, width)));
|
|
2231
2958
|
}
|
|
2232
2959
|
|
|
2233
|
-
function resizeStop(
|
|
2234
|
-
var col = $(this);
|
|
2960
|
+
function resizeStop(col, width) {
|
|
2235
2961
|
var from = col.data('ge-resize-from');
|
|
2236
|
-
var units = snapUnits(col,
|
|
2962
|
+
var units = snapUnits(col, width);
|
|
2237
2963
|
|
|
2238
2964
|
col.removeData('ge-resize-from');
|
|
2239
2965
|
resizeReadout(col, '');
|
|
2240
2966
|
stripPixelWidths(col);
|
|
2241
2967
|
|
|
2242
|
-
if (
|
|
2243
|
-
col.removeData('ge-resize-refused');
|
|
2244
|
-
return;
|
|
2245
|
-
}
|
|
2968
|
+
if (from === undefined) { return; }
|
|
2246
2969
|
|
|
2247
2970
|
// A drag of a couple of pixels lands on the size it started from,
|
|
2248
2971
|
// and is not a resize
|
|
@@ -2275,18 +2998,17 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2275
2998
|
if (plan) { writeSize(next, plan); }
|
|
2276
2999
|
}
|
|
2277
3000
|
|
|
3001
|
+
/**
|
|
3002
|
+
* Undo every list `sortable()` made, and only those.
|
|
3003
|
+
*
|
|
3004
|
+
* The registry is what makes that exact: deinit() is a public method,
|
|
3005
|
+
* so it can be called twice, and a list the host made sortable itself
|
|
3006
|
+
* is none of the editor's business.
|
|
3007
|
+
*/
|
|
2278
3008
|
function removeSortable() {
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
// jQuery UI marks what it made, so a plugin's sortables come
|
|
2283
|
-
// away with the editor's without the core knowing about them
|
|
2284
|
-
canvas.find('.ui-sortable').addBack('.ui-sortable').each(function() {
|
|
2285
|
-
var node = $(this);
|
|
2286
|
-
if (node.data('ui-sortable')) {
|
|
2287
|
-
node.sortable('destroy');
|
|
2288
|
-
}
|
|
2289
|
-
});
|
|
3009
|
+
$.each(sortables, function(_, instance) { instance.destroy(); });
|
|
3010
|
+
|
|
3011
|
+
sortables = [];
|
|
2290
3012
|
}
|
|
2291
3013
|
|
|
2292
3014
|
function createRow() {
|
|
@@ -2476,10 +3198,10 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2476
3198
|
var child = $(this);
|
|
2477
3199
|
|
|
2478
3200
|
// The editor's own furniture is not content and not a
|
|
2479
|
-
// boundary either.
|
|
2480
|
-
//
|
|
2481
|
-
//
|
|
2482
|
-
if (child.is('.ge-tools-drawer, .
|
|
3201
|
+
// boundary either. The resize handle used to be treated as
|
|
3202
|
+
// content and wrapped into a content area of its own on
|
|
3203
|
+
// the next init.
|
|
3204
|
+
if (child.is('.ge-tools-drawer, .ge-resize-handle')) { return; }
|
|
2483
3205
|
|
|
2484
3206
|
// A container sits in the column beside the content
|
|
2485
3207
|
// areas, not inside one, so it ends a run of loose
|
|
@@ -2556,7 +3278,15 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2556
3278
|
return;
|
|
2557
3279
|
}
|
|
2558
3280
|
|
|
3281
|
+
var from = curView;
|
|
2559
3282
|
switchLayout(key);
|
|
3283
|
+
|
|
3284
|
+
if (key === from) { return; }
|
|
3285
|
+
|
|
3286
|
+
canvas.find('.ge-utilities').each(function() { renderUtilities($(this)); });
|
|
3287
|
+
refreshPreviews(canvas);
|
|
3288
|
+
plugins('onViewChange', key);
|
|
3289
|
+
emit('view-change', { canvas: canvas, breakpoint: key, from: from, to: key });
|
|
2560
3290
|
}
|
|
2561
3291
|
|
|
2562
3292
|
function getView() {
|
|
@@ -2600,6 +3330,8 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2600
3330
|
return addPaneTo(container, 'accordion', options);
|
|
2601
3331
|
},
|
|
2602
3332
|
setLocale: setLocale,
|
|
3333
|
+
getUtility: getUtility,
|
|
3334
|
+
setUtility: setUtility,
|
|
2603
3335
|
canvas: canvas,
|
|
2604
3336
|
settings: settingsCopy(),
|
|
2605
3337
|
};
|
|
@@ -2651,6 +3383,18 @@ $.fn.gridEditor.containers = {};
|
|
|
2651
3383
|
*/
|
|
2652
3384
|
$.fn.gridEditor.features = {};
|
|
2653
3385
|
|
|
3386
|
+
/**
|
|
3387
|
+
* Utility plugins: Bootstrap's responsive utility classes - order-md-2,
|
|
3388
|
+
* d-lg-none - edited per breakpoint. A plugin declares families of classes
|
|
3389
|
+
* and the editor reads them, writes them, puts them in the settings panel and
|
|
3390
|
+
* previews them in each view. Same factory, same `plugins` setting.
|
|
3391
|
+
*
|
|
3392
|
+
* $.fn.gridEditor.utilities.order = function(ge) {
|
|
3393
|
+
* return { families: [{ name: 'order', prefix: 'order', values: [...] }] };
|
|
3394
|
+
* };
|
|
3395
|
+
*/
|
|
3396
|
+
$.fn.gridEditor.utilities = {};
|
|
3397
|
+
|
|
2654
3398
|
/** Translator for the editor integrations, which get settings and no instance. */
|
|
2655
3399
|
$.fn.gridEditor.t = translate;
|
|
2656
3400
|
|
|
@@ -2702,6 +3446,12 @@ $.fn.gridEditor.locales = {
|
|
|
2702
3446
|
'view.lg': 'Desktop',
|
|
2703
3447
|
'view.xl': 'Large desktop',
|
|
2704
3448
|
'view.xxl': 'Widescreen',
|
|
3449
|
+
'utility.section': 'Responsive: {view}',
|
|
3450
|
+
'utility.default': 'Default',
|
|
3451
|
+
'utility.inherit': 'Inherit: {value} (from {breakpoint})',
|
|
3452
|
+
'utility.varies': 'Changes at {breakpoints}; choosing here replaces that',
|
|
3453
|
+
'error.sortable_missing': 'SortableJS not available! Make sure you loaded the Sortable js file; dragging is off without it.',
|
|
3454
|
+
'warning.setting_removed': 'The {setting} setting was removed in 4.0. Use {replacement} instead.',
|
|
2705
3455
|
'error.tinymce_missing': 'tinyMCE not available! Make sure you loaded the tinyMCE js file.',
|
|
2706
3456
|
'error.ckeditor_missing': 'CKEditor not available! Make sure you loaded the ckeditor and jquery adapter js files.',
|
|
2707
3457
|
'error.summernote_missing': 'Summernote not available! Make sure you loaded the Summernote js file.',
|