@themarioga/grid-editor 5.1.1 → 5.1.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.
@@ -0,0 +1,362 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
+
6
+ <!-- CSS dependencies -->
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css">
9
+
10
+ <!-- Javascript dependencies -->
11
+ <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.8.6/tinymce.min.js"></script>
14
+
15
+ <!-- Grid editor CSS -->
16
+ <link rel="stylesheet" type="text/css" href="../dist/grideditor.css" />
17
+ <!-- Grid editor javascript -->
18
+ <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
19
+ <script src="../dist/jquery.grideditor.min.js"></script>
20
+ <script src="../dist/plugins/grideditor.card.min.js"></script>
21
+ <script src="../dist/plugins/grideditor.elements.min.js"></script>
22
+
23
+ <style>
24
+ /* The drawer tool of a node that has an animation set. Under the
25
+ canvas's id, to outweigh the colour the editor gives each kind of
26
+ drawer's tools */
27
+ #myGrid .ge-tools-drawer > a.my-animation-tool.my-animation-set {
28
+ color: #d63384;
29
+ }
30
+ </style>
31
+
32
+ <!-- A plugin of this page's own: a drawer tool that opens a modal, and
33
+ what the user chooses there saved as an attribute on the node. -->
34
+ <script id="animation-plugin">
35
+ (function($) {
36
+
37
+ $.extend($.fn.gridEditor.locales.en, {
38
+ 'animation.tool': 'Animation',
39
+ 'animation.title': 'Animate on scroll',
40
+ 'animation.effect': 'Effect',
41
+ 'animation.none': 'None',
42
+ 'animation.fade': 'Fade in',
43
+ 'animation.slide': 'Slide up',
44
+ 'animation.zoom': 'Zoom in',
45
+ 'animation.duration': 'Duration (ms)',
46
+ 'animation.delay': 'Delay (ms)',
47
+ 'animation.once': 'Only the first time it scrolls into view',
48
+ 'animation.cancel': 'Cancel',
49
+ 'animation.save': 'Save',
50
+ });
51
+
52
+ // Not data-ge-*: getPlainHtml takes those off, and this has to
53
+ // reach the published page
54
+ var ATTRIBUTE = 'data-animation';
55
+ var EFFECTS = ['fade', 'slide', 'zoom'];
56
+
57
+ $.fn.gridEditor.features.animation = function(ge) {
58
+
59
+ var modal = null;
60
+
61
+ function read(node) {
62
+ try {
63
+ return JSON.parse(node.attr(ATTRIBUTE) || 'null');
64
+ } catch (error) {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * An element lives inside a content area, and while a rich
71
+ * text editor is open there, a change it does not know about
72
+ * is one its undo would take back: undoing the next thing the
73
+ * user types restores a snapshot from before the attribute.
74
+ * Made through tinyMCE's undo manager, it is a step of its
75
+ * own, which undo and redo treat like any other.
76
+ */
77
+ function write(node, value) {
78
+ var apply = function() {
79
+ if (value) {
80
+ node.attr(ATTRIBUTE, JSON.stringify(value));
81
+ } else {
82
+ node.removeAttr(ATTRIBUTE);
83
+ }
84
+ };
85
+ var editor = editorAround(node);
86
+
87
+ if (editor) {
88
+ editor.undoManager.transact(apply);
89
+ } else {
90
+ apply();
91
+ }
92
+ }
93
+
94
+ /** The tinyMCE editor open on the content area around node, if there is one. */
95
+ function editorAround(node) {
96
+ var area = node.closest('.ge-content');
97
+ if (!area.length || !window.tinymce) { return null; }
98
+
99
+ return window.tinymce.get().filter(function(editor) {
100
+ return editor.getBody && editor.getBody() === area[0];
101
+ })[0] || null;
102
+ }
103
+
104
+ /** Built once, outside the canvas, so it is never part of getHtml. */
105
+ function dialog() {
106
+ if (modal) { return modal; }
107
+
108
+ var effects = $('<select class="form-select" name="effect" />')
109
+ .append($('<option value="" />').text(ge.t('animation.none')));
110
+ EFFECTS.forEach(function(effect) {
111
+ effects.append($('<option />').val(effect).text(ge.t('animation.' + effect)));
112
+ });
113
+
114
+ var field = function(label, input) {
115
+ return $('<div class="mb-3" />')
116
+ .append($('<label class="form-label" />').text(ge.t(label)))
117
+ .append(input);
118
+ };
119
+
120
+ modal = $(
121
+ '<div class="modal fade my-animation-modal" tabindex="-1" aria-hidden="true">' +
122
+ '<div class="modal-dialog modal-dialog-centered"><form class="modal-content">' +
123
+ '<div class="modal-header">' +
124
+ '<h5 class="modal-title"></h5>' +
125
+ '<button type="button" class="btn-close" data-bs-dismiss="modal"></button>' +
126
+ '</div>' +
127
+ '<div class="modal-body"></div>' +
128
+ '<div class="modal-footer">' +
129
+ '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"></button>' +
130
+ '<button type="submit" class="btn btn-primary"></button>' +
131
+ '</div>' +
132
+ '</form></div>' +
133
+ '</div>'
134
+ ).appendTo('body');
135
+
136
+ modal.find('.modal-title').text(ge.t('animation.title'));
137
+ modal.find('.modal-footer .btn-secondary').text(ge.t('animation.cancel'));
138
+ modal.find('.modal-footer .btn-primary').text(ge.t('animation.save'));
139
+ modal.find('.modal-body')
140
+ .append(field('animation.effect', effects))
141
+ .append(field('animation.duration', '<input class="form-control" type="number" name="duration" min="0" step="100">'))
142
+ .append(field('animation.delay', '<input class="form-control" type="number" name="delay" min="0" step="100">'))
143
+ .append($('<div class="form-check" />')
144
+ .append('<input class="form-check-input" type="checkbox" name="once" id="my-animation-once">')
145
+ .append($('<label class="form-check-label" for="my-animation-once" />').text(ge.t('animation.once'))));
146
+
147
+ return modal;
148
+ }
149
+
150
+ function open(node, kind, tool) {
151
+ var form = dialog().find('form');
152
+ var current = read(node) || {};
153
+
154
+ form.find('[name=effect]').val(current.effect || '');
155
+ form.find('[name=duration]').val(current.duration || 600);
156
+ form.find('[name=delay]').val(current.delay || 0);
157
+ form.find('[name=once]').prop('checked', current.once !== false);
158
+
159
+ form.off('submit').on('submit', function(e) {
160
+ e.preventDefault();
161
+
162
+ var effect = form.find('[name=effect]').val();
163
+ var value = effect ? {
164
+ effect: effect,
165
+ duration: parseInt(form.find('[name=duration]').val(), 10) || 0,
166
+ delay: parseInt(form.find('[name=delay]').val(), 10) || 0,
167
+ once: form.find('[name=once]').prop('checked'),
168
+ } : null;
169
+
170
+ var from = read(node);
171
+ write(node, value);
172
+ tool.toggleClass('my-animation-set', !!value);
173
+
174
+ // Announced like the editor's own changes, for a host
175
+ // that listens: grideditor:animation-change
176
+ ge.emit('animation-change', ge.payloadFor(kind, node, {
177
+ source: 'tool', from: from, to: value,
178
+ }));
179
+
180
+ window.bootstrap.Modal.getInstance(modal[0]).hide();
181
+ });
182
+
183
+ window.bootstrap.Modal.getOrCreateInstance(modal[0]).show();
184
+ }
185
+
186
+ return {
187
+ // Every drawer that has a gear, and the kind of its node:
188
+ // a row, a column, an element, a container's type, a pane
189
+ drawerTools: function(drawer, node, kind) {
190
+ var animated = ['row', 'column', 'section', 'element'].indexOf(kind) !== -1 ||
191
+ !!$.fn.gridEditor.containers[kind];
192
+ if (!animated) { return; }
193
+
194
+ ge.createTool(drawer, ge.t('animation.tool'), 'my-animation-tool', 'bi bi-magic', function() {
195
+ open(node, kind, tool);
196
+ });
197
+
198
+ // Drawers are built again on every init, so the tool
199
+ // reads the node rather than remembering anything
200
+ var tool = drawer.children('.my-animation-tool').last()
201
+ .toggleClass('my-animation-set', !!read(node));
202
+ },
203
+ };
204
+ };
205
+
206
+ })(jQuery);
207
+ </script>
208
+
209
+ <script>
210
+ $(function() {
211
+ $('#animation-source').text($('#animation-plugin').text().trim().replace(/^ {8}/gm, ''));
212
+
213
+ $('#myGrid').gridEditor({
214
+ new_row_layouts: [[12], [6, 6], [4, 4, 4]],
215
+ content_types: ['tinymce'],
216
+ });
217
+
218
+ $('#myGrid').on('grideditor:animation-change', function(e, payload) {
219
+ $('#log').text('The ' + payload.kind + ' now animates: ' +
220
+ (payload.to ? payload.to.effect : 'no'));
221
+ });
222
+
223
+ $('#output').on('click', function() {
224
+ $('#html').text($('#myGrid').gridEditor('getPlainHtml'));
225
+ });
226
+
227
+ // The published page: the plain html, and a few lines of the
228
+ // host's own that read data-animation. Nothing of grid-editor.
229
+ $('#published').on('click', function() {
230
+ var html = $('#myGrid').gridEditor('getPlainHtml');
231
+ var page = window.open('', '_blank');
232
+
233
+ page.document.write(
234
+ '<!DOCTYPE html><html><head>' +
235
+ '<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">' +
236
+ '<style>' +
237
+ '[data-animation] { transition-property: opacity, transform; }' +
238
+ '.animate-waiting { opacity: 0; }' +
239
+ '.animate-waiting.animate-slide { transform: translateY(40px); }' +
240
+ '.animate-waiting.animate-zoom { transform: scale(.85); }' +
241
+ 'body { padding-bottom: 60vh; }' +
242
+ '.scroll-hint { height: 100vh; }' +
243
+ '</style>' +
244
+ '</head><body>' +
245
+ '<div class="container scroll-hint d-flex align-items-center justify-content-center text-body-secondary">' +
246
+ 'Scroll down: each node animates as it comes into view' +
247
+ '</div>' +
248
+ '<div class="container">' + html + '</div>' +
249
+ '<script>' + $('#runtime').text() + '<\/script>' +
250
+ '</body></html>'
251
+ );
252
+ page.document.close();
253
+ });
254
+ });
255
+ </script>
256
+
257
+ <!-- What the published page runs: not grid-editor's, the host's -->
258
+ <script type="text/plain" id="runtime">
259
+ document.querySelectorAll('[data-animation]').forEach(function(node) {
260
+ var options = JSON.parse(node.getAttribute('data-animation'));
261
+ node.style.transitionDuration = options.duration + 'ms';
262
+ node.style.transitionDelay = options.delay + 'ms';
263
+ // Prefixed: Bootstrap has a .fade class of its own
264
+ node.classList.add('animate-waiting', 'animate-' + options.effect);
265
+
266
+ new IntersectionObserver(function(entries, observer) {
267
+ entries.forEach(function(entry) {
268
+ node.classList.toggle('animate-waiting', !entry.isIntersecting);
269
+ if (entry.isIntersecting && options.once) { observer.disconnect(); }
270
+ });
271
+ }, { threshold: 0.2 }).observe(node);
272
+ });
273
+ </script>
274
+
275
+ <title>Grid Editor example - a plugin with a modal</title>
276
+ </head>
277
+ <body>
278
+ <!-- Back to the demo index -->
279
+ <div class="container pt-3">
280
+ <a class="link-secondary" href="index.html"><i class="bi bi-arrow-left"></i> All demos</a>
281
+ </div>
282
+
283
+ <div class="container">
284
+
285
+ <div class="row my-3">
286
+ <div class="col-lg-9">
287
+ <h1 class="h4">A plugin with a modal</h1>
288
+ <p class="form-text">
289
+ The <i class="bi bi-magic"></i> tool in the drawer of a row,
290
+ a column, a card or an element is a plugin written on this
291
+ page. It opens a modal, and what you choose there is saved
292
+ on the node as a <code>data-animation</code> attribute, which
293
+ <code>getPlainHtml</code> keeps: only <code>data-ge-*</code>
294
+ attributes are the editor&rsquo;s to take off. A node with
295
+ an animation has its tool highlighted.
296
+ </p>
297
+ <p class="form-text">
298
+ The quote is an element, inside text tinyMCE edits. Open the
299
+ editor there, set an animation on the quote, keep typing
300
+ and press <kbd>Ctrl</kbd>+<kbd>Z</kbd>: the text goes, the
301
+ animation stays, because the plugin wrote it through
302
+ tinyMCE&rsquo;s undo manager. Written behind its back,
303
+ undoing the typing would undo the attribute with it.
304
+ </p>
305
+ <p class="form-text">
306
+ <em>Open as a published page</em> puts the plain html in a
307
+ page of its own, with a few lines that read the attribute
308
+ and animate each node as it scrolls into view.
309
+ <span id="log" class="fw-semibold"></span>
310
+ </p>
311
+ <button class="btn btn-sm btn-secondary" id="output" type="button">Show getPlainHtml</button>
312
+ <button class="btn btn-sm btn-primary" id="published" type="button">Open as a published page</button>
313
+ <pre id="html" class="form-text"></pre>
314
+ </div>
315
+ </div>
316
+
317
+ <div id="myGrid">
318
+ <div class="row" data-animation='{"effect":"fade","duration":800,"delay":0,"once":true}'>
319
+ <div class="col-lg-12">
320
+ <h2>This row fades in</h2>
321
+ <p>It came with its animation already set, in the markup the editor was started on.</p>
322
+ </div>
323
+ </div>
324
+ <div class="row">
325
+ <div class="col-lg-6">
326
+ <p>Ordinary text, edited by tinyMCE.</p>
327
+ <blockquote class="blockquote" data-ge-element="quote" data-ge-label="Quote">
328
+ <p>An element: give it an animation while you edit the text around it.</p>
329
+ </blockquote>
330
+ <p>More text, after the element.</p>
331
+ </div>
332
+ <div class="col-lg-6">
333
+ <div data-ge-container="card">
334
+ <div class="card">
335
+ <div class="card-header">A card</div>
336
+ <div class="card-body">
337
+ <div class="row"><div class="col-lg-12">
338
+ <p>Containers take the tool too.</p>
339
+ </div></div>
340
+ </div>
341
+ </div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+
347
+ <div class="row my-4">
348
+ <div class="col-lg-10">
349
+ <h2 class="h5">The plugin, in full</h2>
350
+ <p class="form-text">
351
+ A feature plugin: <code>drawerTools</code> puts a tool in
352
+ every drawer that has a gear, and the kind says whose drawer
353
+ it is. See <a href="../docs/plugins.md">docs/plugins.md</a>.
354
+ </p>
355
+ <pre class="border rounded p-3 bg-light"><code id="animation-source"></code></pre>
356
+ </div>
357
+ </div>
358
+
359
+ </div>
360
+
361
+ </body>
362
+ </html>
@@ -61,6 +61,14 @@
61
61
  <span class="text-body-secondary">The plugin model, with a container plugin written in the page itself</span>
62
62
  </a>
63
63
 
64
+ <a class="list-group-item list-group-item-action" href="attributes.html">
65
+ <div class="d-flex justify-content-between">
66
+ <strong>A plugin with a modal</strong>
67
+ <code class="text-body-secondary">attributes.html</code>
68
+ </div>
69
+ <span class="text-body-secondary">A drawer tool that opens a modal and saves its settings on the node as an attribute, with tinyMCE&rsquo;s undo in mind</span>
70
+ </a>
71
+
64
72
  <a class="list-group-item list-group-item-action" href="containers.html">
65
73
  <div class="d-flex justify-content-between">
66
74
  <strong>Containers</strong>
package/package.json CHANGED
@@ -34,7 +34,7 @@
34
34
  "comment": "A scoped package publishes restricted unless it is told otherwise.",
35
35
  "access": "public"
36
36
  },
37
- "version": "5.1.1",
37
+ "version": "5.1.2",
38
38
  "main": "dist/jquery.grideditor.js",
39
39
  "files": [
40
40
  "dist",
@@ -894,7 +894,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
894
894
 
895
895
  // Add row
896
896
  addRowGroup = $('<div class="ge-addRowGroup btn-group" />').appendTo(wrapper);
897
- addContainerGroup = $('<div class="ge-addContainerGroup btn-group" />');
897
+ addContainerGroup = $('<div class="ge-addContainerGroup btn-group ms-1" />');
898
898
  $.each(settings.new_row_layouts, function(j, layout) {
899
899
  var grouped = !Array.isArray(layout);
900
900
  var btn = $('<a class="btn btn-sm btn-primary" />')
@@ -1040,9 +1040,11 @@ $.fn.gridEditor = function( optionsOrMethod ) {
1040
1040
  ;
1041
1041
 
1042
1042
  // Floated right after the source and preview buttons, so it
1043
- // stands to their left
1043
+ // stands to their left. Not a btn-group: its buttons show one at
1044
+ // a time, and a hidden one still counts as a neighbour to
1045
+ // Bootstrap, which squares off the corners they would share.
1044
1046
  if (endItems.length) {
1045
- $('<div class="btn-group pull-right ge-toolbar-end" />')
1047
+ $('<div class="pull-right ge-toolbar-end" />')
1046
1048
  .append(endItems)
1047
1049
  .appendTo(wrapper)
1048
1050
  ;
@@ -64,6 +64,14 @@
64
64
  // furniture back (see RTE_READY in the core).
65
65
  contentArea.trigger('ge-rte-ready');
66
66
 
67
+ // Undo and redo rewrite it too, from snapshots that
68
+ // leave out whatever is marked data-mce-bogus - an
69
+ // element's drawer among them - so it goes back in
70
+ // after each of those as well
71
+ editor.on('Undo Redo', function() {
72
+ contentArea.trigger('ge-rte-ready');
73
+ });
74
+
67
75
  // The inline toolbar is laid out against the element's
68
76
  // geometry at the moment tinyMCE draws it, and an
69
77
  // element that has just appeared - a tab pane, an
@@ -67,6 +67,10 @@
67
67
 
68
68
  .ge-toolbar-end {
69
69
  margin-right: 5px;
70
+
71
+ > .btn + .btn {
72
+ margin-left: 5px;
73
+ }
70
74
  }
71
75
 
72
76
  .btn:focus {