@themarioga/grid-editor 8.4.0 → 8.6.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +105 -5
  3. package/dist/grideditor.bundle.min.js +3 -3
  4. package/dist/grideditor.css +15 -4
  5. package/dist/grideditor.d.ts +76 -4
  6. package/dist/grideditor.esm.js +212 -38
  7. package/dist/grideditor.js +212 -38
  8. package/dist/grideditor.min.css +1 -1
  9. package/dist/grideditor.min.css.map +1 -1
  10. package/dist/grideditor.min.js +2 -2
  11. package/dist/grideditor.min.js.map +3 -3
  12. package/dist/locales/grideditor.es.esm.js +5 -0
  13. package/dist/locales/grideditor.es.js +5 -0
  14. package/dist/locales/grideditor.es.min.js +1 -1
  15. package/dist/locales/grideditor.es.min.js.map +2 -2
  16. package/dist/plugins/grideditor.accordion.esm.js +7 -0
  17. package/dist/plugins/grideditor.accordion.js +7 -0
  18. package/dist/plugins/grideditor.accordion.min.js +1 -1
  19. package/dist/plugins/grideditor.accordion.min.js.map +3 -3
  20. package/dist/plugins/grideditor.autosave.esm.js +309 -0
  21. package/dist/plugins/grideditor.autosave.js +316 -0
  22. package/dist/plugins/grideditor.autosave.min.js +1 -0
  23. package/dist/plugins/grideditor.autosave.min.js.map +7 -0
  24. package/dist/plugins/grideditor.card.esm.js +10 -0
  25. package/dist/plugins/grideditor.card.js +10 -0
  26. package/dist/plugins/grideditor.card.min.js +1 -1
  27. package/dist/plugins/grideditor.card.min.js.map +3 -3
  28. package/dist/plugins/grideditor.ckeditor.esm.js +30 -1
  29. package/dist/plugins/grideditor.ckeditor.js +30 -1
  30. package/dist/plugins/grideditor.ckeditor.min.js +1 -1
  31. package/dist/plugins/grideditor.ckeditor.min.js.map +3 -3
  32. package/dist/plugins/grideditor.clipboard.esm.js +3 -0
  33. package/dist/plugins/grideditor.clipboard.js +3 -0
  34. package/dist/plugins/grideditor.clipboard.min.js +1 -1
  35. package/dist/plugins/grideditor.clipboard.min.js.map +2 -2
  36. package/dist/plugins/grideditor.codemirror-inline.esm.js +11 -0
  37. package/dist/plugins/grideditor.codemirror-inline.js +11 -0
  38. package/dist/plugins/grideditor.codemirror-inline.min.js +1 -1
  39. package/dist/plugins/grideditor.codemirror-inline.min.js.map +3 -3
  40. package/dist/plugins/grideditor.elements.esm.js +78 -4
  41. package/dist/plugins/grideditor.elements.js +78 -4
  42. package/dist/plugins/grideditor.elements.min.js +1 -1
  43. package/dist/plugins/grideditor.elements.min.js.map +3 -3
  44. package/dist/plugins/grideditor.gutters.esm.js +5 -0
  45. package/dist/plugins/grideditor.gutters.js +5 -0
  46. package/dist/plugins/grideditor.gutters.min.js +1 -1
  47. package/dist/plugins/grideditor.gutters.min.js.map +3 -3
  48. package/dist/plugins/grideditor.inline-style.esm.js +18 -10
  49. package/dist/plugins/grideditor.inline-style.js +18 -10
  50. package/dist/plugins/grideditor.inline-style.min.js +3 -3
  51. package/dist/plugins/grideditor.inline-style.min.js.map +3 -3
  52. package/dist/plugins/grideditor.order.esm.js +5 -0
  53. package/dist/plugins/grideditor.order.js +5 -0
  54. package/dist/plugins/grideditor.order.min.js +1 -1
  55. package/dist/plugins/grideditor.order.min.js.map +3 -3
  56. package/dist/plugins/grideditor.popup.esm.js +17 -6
  57. package/dist/plugins/grideditor.popup.js +17 -6
  58. package/dist/plugins/grideditor.popup.min.js +1 -1
  59. package/dist/plugins/grideditor.popup.min.js.map +3 -3
  60. package/dist/plugins/grideditor.sections.esm.js +6 -3
  61. package/dist/plugins/grideditor.sections.js +6 -3
  62. package/dist/plugins/grideditor.sections.min.js +1 -1
  63. package/dist/plugins/grideditor.sections.min.js.map +3 -3
  64. package/dist/plugins/grideditor.summernote.esm.js +40 -1
  65. package/dist/plugins/grideditor.summernote.js +40 -1
  66. package/dist/plugins/grideditor.summernote.min.js +1 -1
  67. package/dist/plugins/grideditor.summernote.min.js.map +3 -3
  68. package/dist/plugins/grideditor.tabs.esm.js +7 -0
  69. package/dist/plugins/grideditor.tabs.js +7 -0
  70. package/dist/plugins/grideditor.tabs.min.js +1 -1
  71. package/dist/plugins/grideditor.tabs.min.js.map +3 -3
  72. package/dist/plugins/grideditor.tinymce.esm.js +32 -1
  73. package/dist/plugins/grideditor.tinymce.js +32 -1
  74. package/dist/plugins/grideditor.tinymce.min.js +1 -1
  75. package/dist/plugins/grideditor.tinymce.min.js.map +3 -3
  76. package/docs/events.md +8 -0
  77. package/docs/locale-keys.md +16 -1
  78. package/docs/plugins.md +60 -7
  79. package/example/autosave.html +39 -22
  80. package/example/index.html +1 -1
  81. package/example/plugins.html +10 -0
  82. package/package.json +1 -2
  83. package/src/js/grideditor.js +248 -28
  84. package/src/js/inline-style/display.js +4 -3
  85. package/src/js/inline-style/drawerflow.js +10 -5
  86. package/src/js/locales/grideditor.es.js +5 -0
  87. package/src/js/plugins/grideditor.accordion.js +6 -0
  88. package/src/js/plugins/grideditor.autosave.js +349 -0
  89. package/src/js/plugins/grideditor.card.js +6 -0
  90. package/src/js/plugins/grideditor.ckeditor.js +6 -0
  91. package/src/js/plugins/grideditor.clipboard.js +3 -0
  92. package/src/js/plugins/grideditor.codemirror-inline.js +7 -0
  93. package/src/js/plugins/grideditor.elements.js +98 -4
  94. package/src/js/plugins/grideditor.gutters.js +4 -0
  95. package/src/js/plugins/grideditor.inline-style.js +2 -0
  96. package/src/js/plugins/grideditor.order.js +4 -0
  97. package/src/js/plugins/grideditor.popup.js +15 -6
  98. package/src/js/plugins/grideditor.sections.js +4 -3
  99. package/src/js/plugins/grideditor.summernote.js +14 -0
  100. package/src/js/plugins/grideditor.tabs.js +6 -0
  101. package/src/js/plugins/grideditor.tinymce.js +8 -0
  102. package/src/js/text/grideditor.text.js +27 -1
  103. package/src/less/grideditor.less +16 -8
  104. package/types/grideditor.d.ts +76 -4
  105. package/AUTO_SAVE.md +0 -30
@@ -142,6 +142,7 @@ var NESTED_SETTINGS = {
142
142
  enabled: 'auto', // 'auto' turns them on when the page has any
143
143
  selector: '[data-ge-element]', // What the host marks an element with
144
144
  auto: false, // Treat every child of a content area as an element
145
+ types: [], // The elements the toolbar offers: { type, html, label | labelKey, iconClass, group }
145
146
  },
146
147
  resize: {
147
148
  enabled: true, // The handle on the column's edge
@@ -627,30 +628,61 @@ function build(instance, baseElem, optionsOrMethod) {
627
628
  return;
628
629
  }
629
630
 
631
+ confirmWith(message, { danger: true }, function(confirmed) {
632
+ if (confirmed) { whenConfirmed(); }
633
+ });
634
+ }
635
+
636
+ /**
637
+ * ge.confirm(): a question of a plugin's own, in the same modal.
638
+ * `answer` is called once: true for the ok button, false for cancel,
639
+ * the close button or Escape, and null when the question goes
640
+ * unanswered because the modal is taken away - setLocale, destroy, or
641
+ * another question asked over it.
642
+ */
643
+ function confirmWith(message, options, answer) {
644
+ options = options || {};
630
645
  var Modal = modalLibrary();
631
646
 
632
647
  if (!Modal) {
633
- if (window.confirm(message)) { whenConfirmed(); }
648
+ answer(window.confirm(message));
634
649
  return;
635
650
  }
636
651
 
637
652
  var modal = confirmModal();
638
653
  var confirmed = false;
654
+ var ok = dom.one(modal, '.ge-confirm-ok');
655
+
656
+ // The question before this one, if it is still up, goes unanswered
657
+ if (pendingAnswer) { pendingAnswer(null); }
658
+
659
+ var settle = function(value) {
660
+ if (pendingAnswer !== settle) { return; }
661
+ pendingAnswer = null;
662
+ answer(value);
663
+ };
664
+ pendingAnswer = settle;
665
+
666
+ dom.one(modal, '.modal-title').textContent = options.title || t('confirm.title');
667
+ dom.one(modal, '.ge-confirm-cancel').textContent = options.cancel || t('confirm.cancel');
668
+ ok.textContent = options.ok || t('confirm.ok');
669
+ dom.toggleClass(ok, 'btn-danger', !!options.danger);
670
+ dom.toggleClass(ok, 'btn-primary', !options.danger);
639
671
 
640
672
  dom.one(modal, '.ge-confirm-message').textContent = message;
641
- dom.one(modal, '.ge-confirm-ok').onclick = function() {
673
+ ok.onclick = function() {
642
674
  confirmed = true;
643
675
  Modal.getInstance(modal).hide();
644
676
  };
645
677
 
646
678
  confirmHandlers.hidden = function() {
647
679
  // After the modal is out of the way, so the backdrop is not
648
- // sitting over the animation the delete runs
649
- if (confirmed) { whenConfirmed(); }
680
+ // sitting over the animation a delete runs
681
+ settle(confirmed);
650
682
  };
651
683
 
652
684
  confirmHandlers.shown = function() {
653
- dom.one(modal, '.ge-confirm-ok').focus();
685
+ ok.focus();
654
686
  };
655
687
 
656
688
  Modal.getOrCreateInstance(modal).show();
@@ -659,6 +691,7 @@ function build(instance, baseElem, optionsOrMethod) {
659
691
  // What the confirm modal does when Bootstrap says it is shown or
660
692
  // hidden: this question's answer, replaced by the next question's
661
693
  var confirmHandlers = { hidden: null, shown: null };
694
+ var pendingAnswer = null; // The open question's answer, until it is given
662
695
 
663
696
  /** Built once per instance, and taken away again by destroy(). */
664
697
  function confirmModal() {
@@ -689,11 +722,15 @@ function build(instance, baseElem, optionsOrMethod) {
689
722
  dom.one(confirmDialog, '.ge-confirm-ok').textContent = t('confirm.ok');
690
723
 
691
724
  trackModal(confirmDialog);
725
+ // Only while it is the modal asking: one being taken away -
726
+ // setLocale builds another - finishes hiding after the next
727
+ // question is up, and that answer is not its to give
728
+ var asking = confirmDialog;
692
729
  confirmDialog.addEventListener('hidden.bs.modal', function() {
693
- if (confirmHandlers.hidden) { confirmHandlers.hidden(); }
730
+ if (asking === confirmDialog && confirmHandlers.hidden) { confirmHandlers.hidden(); }
694
731
  });
695
732
  confirmDialog.addEventListener('shown.bs.modal', function() {
696
- if (confirmHandlers.shown) { confirmHandlers.shown(); }
733
+ if (asking === confirmDialog && confirmHandlers.shown) { confirmHandlers.shown(); }
697
734
  });
698
735
 
699
736
  return confirmDialog;
@@ -708,6 +745,9 @@ function build(instance, baseElem, optionsOrMethod) {
708
745
  confirmHandlers.shown = null;
709
746
  retireModal(confirmDialog);
710
747
  confirmDialog = null;
748
+
749
+ // A plugin's question goes unanswered, and the plugin is told so
750
+ if (pendingAnswer) { pendingAnswer(null); }
711
751
  }
712
752
 
713
753
  /**
@@ -1080,7 +1120,7 @@ function build(instance, baseElem, optionsOrMethod) {
1080
1120
  settings.new_row_layouts.forEach(function(layout) {
1081
1121
  var grouped = !Array.isArray(layout);
1082
1122
  var btn = dom.element('a', {
1083
- 'class': 'btn btn-sm btn-primary',
1123
+ 'class': 'btn btn-sm btn-light',
1084
1124
  title: grouped
1085
1125
  ? t('row.add_row_cols', { columns: layout.columns, counts: rowColsText(layout.row_cols) })
1086
1126
  : t('row.add', { layout: layout.join('-') }),
@@ -1125,7 +1165,7 @@ function build(instance, baseElem, optionsOrMethod) {
1125
1165
  // buttons next to these ones do
1126
1166
  Object.keys(CONTAINERS).forEach(function(type) {
1127
1167
  var definition = CONTAINERS[type];
1128
- var button = labelButton(dom.element('a', { 'class': 'btn btn-sm btn-primary ge-add-container' }),
1168
+ var button = labelButton(dom.element('a', { 'class': 'btn btn-sm btn-light ge-add-container' }),
1129
1169
  t(definition.labelKey), definition.iconClass);
1130
1170
 
1131
1171
  dom.attr(button, { 'data-ge-toolbar': 'container', 'data-ge-container-type': type });
@@ -1239,6 +1279,7 @@ function build(instance, baseElem, optionsOrMethod) {
1239
1279
  var CORE_GROUPS = [
1240
1280
  { name: 'rows', labelKey: 'group.rows' },
1241
1281
  { name: 'content', labelKey: 'group.content' },
1282
+ { name: 'elements', labelKey: 'group.elements' },
1242
1283
  ];
1243
1284
 
1244
1285
  /** A plugin's category, when it names one. */
@@ -1272,7 +1313,7 @@ function build(instance, baseElem, optionsOrMethod) {
1272
1313
  names.forEach(function(name) {
1273
1314
  var tab = tabs.appendChild(dom.element('button', {
1274
1315
  type: 'button',
1275
- 'class': 'btn btn-sm btn-outline-primary',
1316
+ 'class': 'btn btn-sm btn-outline-secondary',
1276
1317
  role: 'tab',
1277
1318
  'data-ge-group': name,
1278
1319
  }, groupLabel(name)));
@@ -1331,7 +1372,7 @@ function build(instance, baseElem, optionsOrMethod) {
1331
1372
  */
1332
1373
  function createOverflowMenu(start) {
1333
1374
  var more = dom.create('<div class="ge-toolbar-more">' +
1334
- '<button type="button" class="btn btn-sm btn-primary"><i class="bi bi-three-dots"></i></button>' +
1375
+ '<button type="button" class="btn btn-sm btn-light"><i class="bi bi-three-dots"></i></button>' +
1335
1376
  '<div class="ge-toolbar-overflow">' +
1336
1377
  '<div class="ge-addRowGroup btn-group"></div>' +
1337
1378
  '<div class="ge-addContainerGroup btn-group"></div>' +
@@ -1440,7 +1481,10 @@ function build(instance, baseElem, optionsOrMethod) {
1440
1481
  // A label of its own, worked out when the toolbar is built - and
1441
1482
  // built again by setLocale - or the string its labelKey names
1442
1483
  var label = typeof item.label === 'function' ? item.label() : (item.label || t(item.labelKey));
1443
- var button = labelButton(dom.element('a', { 'class': 'btn btn-sm btn-primary ge-add-container ge-add-feature' }), label, iconClass);
1484
+ // Grey with the add buttons on the left, blue with the source and
1485
+ // preview buttons on the right
1486
+ var colour = item.align === 'end' ? 'btn-primary' : 'btn-light';
1487
+ var button = labelButton(dom.element('a', { 'class': 'btn btn-sm ' + colour + ' ge-add-container ge-add-feature' }), label, iconClass);
1444
1488
 
1445
1489
  dom.addClass(button, item.className || '');
1446
1490
  dom.attr(button, {
@@ -1452,6 +1496,8 @@ function build(instance, baseElem, optionsOrMethod) {
1452
1496
  if (activeTarget) { addToTarget(button, item.source || 'tool'); return; }
1453
1497
 
1454
1498
  var made = item.create();
1499
+ // A plugin that could not make one says so with null
1500
+ if (!made) { return; }
1455
1501
 
1456
1502
  // One that belongs in a column brings a row and a column
1457
1503
  // of its own, the way a container does
@@ -1611,6 +1657,7 @@ function build(instance, baseElem, optionsOrMethod) {
1611
1657
  function insertFeatureFromToolbar(button, where, source) {
1612
1658
  var item = FEATURES[button.getAttribute('data-ge-feature')].toolbar[parseInt(button.getAttribute('data-ge-item'), 10)];
1613
1659
  var made = item.create();
1660
+ if (!made) { return null; }
1614
1661
  var placed = made;
1615
1662
 
1616
1663
  if (item.inColumn) {
@@ -2085,26 +2132,144 @@ function build(instance, baseElem, optionsOrMethod) {
2085
2132
  hideDropMarker();
2086
2133
  // In case a dialog was opened with no settings panel open
2087
2134
  closeDialog();
2088
- dom.all(canvas, '.ge-tools-drawer').forEach(function(drawer) { drawer.remove(); });
2135
+ removeDrawers(canvas);
2089
2136
  pluginFields.clear();
2090
- unwrapTexts();
2137
+ unwrapTexts(canvas);
2091
2138
  plugins('onDeinit');
2092
2139
  // After the rich text editors have let go of their content areas:
2093
2140
  // one that rebuilt its area's DOM brought the preview styles back
2094
2141
  // with it, and the attribute recording them came back too
2095
2142
  clearPreviews(canvas);
2096
- dom.all(canvas, '[data-ge-row-cols]').forEach(function(row) { row.removeAttribute('data-ge-row-cols'); });
2143
+ clearRowCols(canvas);
2097
2144
  unmarkContainers();
2098
2145
  removeSortable();
2099
2146
  removeResizable();
2100
- runFilter(false);
2147
+ runFilter(false, canvas);
2148
+ }
2149
+
2150
+ function removeDrawers(root) {
2151
+ dom.all(root, '.ge-tools-drawer').forEach(function(drawer) { drawer.remove(); });
2152
+ }
2153
+
2154
+ function clearRowCols(root) {
2155
+ dom.all(root, '[data-ge-row-cols]').forEach(function(row) { row.removeAttribute('data-ge-row-cols'); });
2156
+ }
2157
+
2158
+ /**
2159
+ * The plugins that cannot be read without the canvas leaving editing:
2160
+ * the ones that put something on the canvas - an unmark, an onDeinit
2161
+ * or an onBeforeDeinit takes it off - and say nothing of how to take
2162
+ * it off a copy.
2163
+ */
2164
+ function pluginsWithoutCleanMarkup() {
2165
+ var names = [];
2166
+
2167
+ [CONTAINERS, FEATURES, UTILITIES].forEach(function(registry) {
2168
+ Object.keys(registry).forEach(function(name) {
2169
+ var plugin = registry[name];
2170
+ var marks = plugin.unmark || plugin.onDeinit || plugin.onBeforeDeinit;
2171
+ if (marks && !plugin.cleanMarkup) { names.push(name); }
2172
+ });
2173
+ });
2174
+
2175
+ return names;
2176
+ }
2177
+
2178
+ /**
2179
+ * A copy of the canvas, and the way from each of its elements back to
2180
+ * the one it was copied from. The two trees are walked side by side,
2181
+ * which works because a deep clone has the same shape.
2182
+ */
2183
+ function copyCanvas() {
2184
+ var copy = canvas.cloneNode(true);
2185
+ var toLive = new Map();
2186
+ var toCopy = new Map();
2187
+ var live = document.createTreeWalker(canvas, NodeFilter.SHOW_ELEMENT);
2188
+ var copied = document.createTreeWalker(copy, NodeFilter.SHOW_ELEMENT);
2189
+ var a = live.currentNode;
2190
+ var b = copied.currentNode;
2191
+
2192
+ while (a && b) {
2193
+ toLive.set(b, a);
2194
+ toCopy.set(a, b);
2195
+ a = live.nextNode();
2196
+ b = copied.nextNode();
2197
+ }
2198
+
2199
+ return {
2200
+ root: copy,
2201
+ liveOf: function(node) { return toLive.get(node) || null; },
2202
+ copyOf: function(node) { return toCopy.get(node) || null; },
2203
+ };
2204
+ }
2205
+
2206
+ /**
2207
+ * getHtml without the canvas leaving editing: what deinit takes off,
2208
+ * taken off a copy, so the text being typed into, the settings panel
2209
+ * and the focus are where they were. The same markup getHtml gives,
2210
+ * or getHtml itself - with a word, once - when a plugin can only take
2211
+ * its marking off the canvas, or will not this time.
2212
+ */
2213
+ function snapshotHtml() {
2214
+ // Nothing is being edited to keep: the canvas is out of editing
2215
+ if (sourceOpen) { return getHtml(); }
2216
+
2217
+ var missing = pluginsWithoutCleanMarkup();
2218
+ if (missing.length) {
2219
+ missing.forEach(function(name) {
2220
+ warnOnceHere('snapshot:' + name, 'the "' + name + '" plugin has no cleanMarkup, so the canvas ' +
2221
+ 'leaves editing to be read: open texts and settings panels close');
2222
+ });
2223
+ return getHtml();
2224
+ }
2225
+
2226
+ var copy = copyCanvas();
2227
+ var root = copy.root;
2228
+
2229
+ [root].concat(dom.all(root, '.ge-active-target, .ge-settings-target')).forEach(function(node) {
2230
+ dom.removeClass(node, 'ge-active-target ge-settings-target');
2231
+ dom.dropEmptyClass(node);
2232
+ });
2233
+ dom.all(root, '.ge-drop-marker').forEach(function(marker) { marker.remove(); });
2234
+ removeDrawers(root);
2235
+ unwrapTexts(root);
2236
+
2237
+ var refused = null;
2238
+ [CONTAINERS, FEATURES, UTILITIES].forEach(function(registry) {
2239
+ Object.keys(registry).forEach(function(name) {
2240
+ var plugin = registry[name];
2241
+ if (refused || !plugin.cleanMarkup) { return; }
2242
+ if (plugin.cleanMarkup(root, copy.liveOf) === false) { refused = name; }
2243
+ });
2244
+ });
2245
+
2246
+ if (refused) {
2247
+ warnOnceHere('snapshot-refused:' + refused, 'the "' + refused + '" plugin cannot take its marking off ' +
2248
+ 'a copy right now, so the canvas leaves editing to be read');
2249
+ return getHtml();
2250
+ }
2251
+
2252
+ clearPreviews(root);
2253
+ clearRowCols(root);
2254
+ dom.all(root, '[data-ge-container]').forEach(unmarkContainerClasses);
2255
+ sortables.forEach(function(sortableInstance) {
2256
+ var list = copy.copyOf(sortableInstance.el);
2257
+ if (list) { dom.all(list, '[draggable]').forEach(function(node) { node.removeAttribute('draggable'); }); }
2258
+ });
2259
+ removeResizeFurniture(root);
2260
+ runFilter(false, root);
2261
+ stripPixelWidths(root);
2262
+
2263
+ return root.innerHTML;
2101
2264
  }
2102
2265
 
2103
2266
  /**
2104
2267
  * The markup as a host would save it: no drawers, no editor, no
2105
2268
  * sortables. The canvas goes back to editing afterwards.
2106
2269
  */
2107
- function getHtml() {
2270
+ function getHtml(options) {
2271
+ if (options && options.keepEditing) { return snapshotHtml(); }
2272
+
2108
2273
  deinit();
2109
2274
  stripPixelWidths(canvas);
2110
2275
  var html = canvas.innerHTML;
@@ -2125,8 +2290,31 @@ function build(instance, baseElem, optionsOrMethod) {
2125
2290
  }
2126
2291
 
2127
2292
  /** getHtml with grid-editor's own classes and attributes taken off. */
2128
- function getPlainHtml() {
2129
- return plainHtml(getHtml());
2293
+ function getPlainHtml(options) {
2294
+ return plainHtml(getHtml(options));
2295
+ }
2296
+
2297
+ /**
2298
+ * ge.setHtml(): the canvas made of other markup, and edited again. Not
2299
+ * an add or a delete of anything, so no events: what was there is
2300
+ * replaced as a whole. innerHTML, as the source view does, so a
2301
+ * <script> in it is markup and does not run in the editor.
2302
+ */
2303
+ function setHtml(html) {
2304
+ // What is in the textarea is not wanted - html replaces it - but
2305
+ // the source view closes the one way it always does
2306
+ if (sourceOpen) {
2307
+ closeSource();
2308
+ if (mainControls) {
2309
+ dom.all(mainControls, '.gm-edit-mode').forEach(function(button) {
2310
+ dom.removeClass(button, 'active btn-danger');
2311
+ });
2312
+ }
2313
+ }
2314
+
2315
+ deinit();
2316
+ dom.setHtml(canvas, html);
2317
+ init();
2130
2318
  }
2131
2319
 
2132
2320
  /**
@@ -2136,6 +2324,8 @@ function build(instance, baseElem, optionsOrMethod) {
2136
2324
  * as a framework tearing a component down does.
2137
2325
  */
2138
2326
  function destroy() {
2327
+ // First, while the editor is whole: a plugin may still read it
2328
+ plugins('onDestroy');
2139
2329
  // The html being edited is what the canvas is left with
2140
2330
  if (sourceOpen) { closeSource(); }
2141
2331
  deinit();
@@ -2263,6 +2453,11 @@ function build(instance, baseElem, optionsOrMethod) {
2263
2453
  closeDialog: closeDialog,
2264
2454
  rowFromLayout: rowFromLayoutValue,
2265
2455
  nodeHtml: nodeHtml,
2456
+ // The markup as getHtml gives it, with the canvas left editing
2457
+ snapshotHtml: snapshotHtml,
2458
+ setHtml: setHtml,
2459
+ // A question in the editor's confirm modal
2460
+ confirm: confirmWith,
2266
2461
  // A text editor has rewritten a content area, so whatever the
2267
2462
  // editor and its plugins had put in there goes back in
2268
2463
  textReady: textReady,
@@ -2985,11 +3180,15 @@ function build(instance, baseElem, optionsOrMethod) {
2985
3180
 
2986
3181
  if (definition) { definition.unmark(container); }
2987
3182
 
2988
- dom.removeClass(container, 'ge-container ge-container-' + containerTypeOf(container));
2989
- dom.dropEmptyClass(container);
3183
+ unmarkContainerClasses(container);
2990
3184
  });
2991
3185
  }
2992
3186
 
3187
+ function unmarkContainerClasses(container) {
3188
+ dom.removeClass(container, 'ge-container ge-container-' + containerTypeOf(container));
3189
+ dom.dropEmptyClass(container);
3190
+ }
3191
+
2993
3192
  /** A new drawer as the first child of `node`. */
2994
3193
  function prependDrawer(node, className) {
2995
3194
  var drawer = dom.element('div', { 'class': className });
@@ -4918,10 +5117,14 @@ function build(instance, baseElem, optionsOrMethod) {
4918
5117
  }
4919
5118
 
4920
5119
  function removeResizable() {
4921
- dom.all(canvas, '.ge-resize-handle').forEach(function(handle) { handle.remove(); });
5120
+ removeResizeFurniture(canvas);
5121
+ }
4922
5122
 
4923
- dom.all(canvas, '.ge-resize-size').forEach(function(readout) { readout.remove(); });
4924
- stripPixelWidths(canvas);
5123
+ function removeResizeFurniture(root) {
5124
+ dom.all(root, '.ge-resize-handle').forEach(function(handle) { handle.remove(); });
5125
+
5126
+ dom.all(root, '.ge-resize-size').forEach(function(readout) { readout.remove(); });
5127
+ stripPixelWidths(root);
4925
5128
  }
4926
5129
 
4927
5130
  /**
@@ -5247,7 +5450,7 @@ function build(instance, baseElem, optionsOrMethod) {
5247
5450
  * function, or the name of one on window, and gets the canvas and
5248
5451
  * whether this is init.
5249
5452
  */
5250
- function runFilter(isInit) {
5453
+ function runFilter(isInit, root) {
5251
5454
  if (!settings.custom_filter || !settings.custom_filter.length) { return; }
5252
5455
 
5253
5456
  var filters = typeof settings.custom_filter === 'string' || typeof settings.custom_filter === 'function'
@@ -5259,7 +5462,7 @@ function build(instance, baseElem, optionsOrMethod) {
5259
5462
  func = window[func];
5260
5463
  }
5261
5464
 
5262
- func(canvas, isInit);
5465
+ func(root || canvas, isInit);
5263
5466
  });
5264
5467
  }
5265
5468
 
@@ -5452,8 +5655,8 @@ function build(instance, baseElem, optionsOrMethod) {
5452
5655
  }
5453
5656
 
5454
5657
  /** After the drawers are gone, so a wrapper holds its content area and nothing else. */
5455
- function unwrapTexts() {
5456
- dom.all(canvas, '.ge-text-block').forEach(function(textBlock) {
5658
+ function unwrapTexts(root) {
5659
+ dom.all(root, '.ge-text-block').forEach(function(textBlock) {
5457
5660
  dom.unwrap(textBlock);
5458
5661
  });
5459
5662
  }
@@ -5661,6 +5864,22 @@ function build(instance, baseElem, optionsOrMethod) {
5661
5864
  // Again, with what the plugins filled in: the text editors'
5662
5865
  // content_types when the host gave none
5663
5866
  instance.settings = settingsCopy();
5867
+
5868
+ // A feature plugin's methods that are not the editor's own documented
5869
+ // ones - the autosave plugin's - are the instance's too, while the
5870
+ // plugin is there
5871
+ Object.keys(featureMethods).forEach(function(name) {
5872
+ if (Object.prototype.hasOwnProperty.call(own, name) || name in instance) { return; }
5873
+
5874
+ instance[name] = function() {
5875
+ if (destroyed) {
5876
+ warnOnceHere('destroyed:' + name, t('warning.destroyed', { method: name }));
5877
+ return null;
5878
+ }
5879
+
5880
+ return featureMethods[name].apply(null, arguments);
5881
+ };
5882
+ });
5664
5883
  setup();
5665
5884
  init();
5666
5885
 
@@ -5770,6 +5989,7 @@ GridEditor.locales = {
5770
5989
  'tool.more': 'More',
5771
5990
  'group.rows': 'Rows',
5772
5991
  'group.content': 'Content',
5992
+ 'group.elements': 'Elements',
5773
5993
  'group.select': 'Add',
5774
5994
  'panel.title': '{kind} settings',
5775
5995
  'panel.close': 'Close',
@@ -177,8 +177,8 @@ export function displayPart(ge, given) {
177
177
  });
178
178
  }
179
179
 
180
- function unmark() {
181
- dom.all(ge.canvas, '.ge-hidden-in-view, [data-ge-hidden-in], [' + SHOWN_ATTR + ']').forEach(function(node) {
180
+ function unmark(root) {
181
+ dom.all(root || ge.canvas, '.ge-hidden-in-view, [data-ge-hidden-in], [' + SHOWN_ATTR + ']').forEach(function(node) {
182
182
  dom.removeClass(node, 'ge-hidden-in-view');
183
183
  node.removeAttribute('data-ge-hidden-in');
184
184
  node.removeAttribute(SHOWN_ATTR);
@@ -224,6 +224,7 @@ export function displayPart(ge, given) {
224
224
  },
225
225
 
226
226
  onRefresh: mark,
227
- onDeinit: unmark,
227
+ onDeinit: function() { unmark(); },
228
+ cleanMarkup: unmark,
228
229
  };
229
230
  }
@@ -95,15 +95,19 @@ export function drawerflowPart(ge) {
95
95
  });
96
96
  }
97
97
 
98
- function unmark() {
99
- if (observer) { observer.disconnect(); }
100
- observer = null;
101
-
102
- dom.all(ge.canvas, '.' + OUT_CLASS).forEach(function(node) {
98
+ function unmarkNodes(root) {
99
+ dom.all(root, '.' + OUT_CLASS).forEach(function(node) {
103
100
  dom.removeClass(node, OUT_CLASS);
104
101
  node.removeAttribute(OUT_ATTR);
105
102
  dom.dropEmptyClass(node);
106
103
  });
104
+ }
105
+
106
+ function unmark() {
107
+ if (observer) { observer.disconnect(); }
108
+ observer = null;
109
+
110
+ unmarkNodes(ge.canvas);
107
111
 
108
112
  if (sheet) { sheet.remove(); }
109
113
  sheet = null;
@@ -114,5 +118,6 @@ export function drawerflowPart(ge) {
114
118
  families: [],
115
119
  onRefresh: mark,
116
120
  onDeinit: unmark,
121
+ cleanMarkup: unmarkNodes,
117
122
  };
118
123
  }
@@ -54,6 +54,7 @@ GridEditor.locales.es = {
54
54
  'tool.more': 'Más',
55
55
  'group.rows': 'Filas',
56
56
  'group.content': 'Contenido',
57
+ 'group.elements': 'Elementos',
57
58
  'group.select': 'Añadir',
58
59
  'panel.title': 'Ajustes: {kind}',
59
60
  'panel.close': 'Cerrar',
@@ -139,6 +140,10 @@ GridEditor.locales.es = {
139
140
  'tool.paste': 'Pegar',
140
141
  'clipboard.paste_row': 'Pegar fila',
141
142
  'clipboard.paste_section': 'Pegar sección',
143
+ 'autosave.restore_title': '¿Recuperar el borrador?',
144
+ 'autosave.restore_message': 'Hay un borrador de esta página guardado el {date}, con cambios que no se publicaron. ¿Quieres recuperarlo?',
145
+ 'autosave.restore': 'Recuperar',
146
+ 'autosave.discard': 'Descartar',
142
147
  'utility.col_from_row': 'De la fila: {count}',
143
148
  'utility.row_cols': 'Columnas por fila',
144
149
  'badge.row_cols': '{count} por fila',
@@ -206,6 +206,12 @@ GridEditor.containers.accordion = function(ge) {
206
206
  ge.unwrapLabels(container);
207
207
  },
208
208
 
209
+ // The same, on a copy of the canvas: unmark only touches markup
210
+ cleanMarkup: function(root) {
211
+ var definition = this;
212
+ dom.all(root, '[data-ge-container="accordion"]').forEach(function(container) { definition.unmark(container); });
213
+ },
214
+
209
215
  afterPaneMove: function(container, item) {
210
216
  reparentAccordionItem(container, item);
211
217
  },