@themarioga/grid-editor 5.3.1 → 6.0.0-beta.1

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 (38) hide show
  1. package/BUILDING.md +2 -4
  2. package/CHANGELOG.md +30 -0
  3. package/README.md +19 -8
  4. package/UPGRADING.md +57 -12
  5. package/dist/grideditor.css +13 -0
  6. package/dist/grideditor.min.css +1 -1
  7. package/dist/grideditor.min.css.map +1 -1
  8. package/dist/jquery.grideditor.bundle.min.js +2 -2
  9. package/dist/jquery.grideditor.js +104 -416
  10. package/dist/jquery.grideditor.min.js +1 -1
  11. package/dist/jquery.grideditor.min.js.map +1 -1
  12. package/dist/plugins/grideditor.ckeditor.js +2 -1
  13. package/dist/plugins/grideditor.ckeditor.min.js.map +1 -1
  14. package/dist/plugins/grideditor.clipboard.js +1 -11
  15. package/dist/plugins/grideditor.clipboard.min.js +1 -1
  16. package/dist/plugins/grideditor.clipboard.min.js.map +1 -1
  17. package/dist/plugins/grideditor.elements.js +66 -38
  18. package/dist/plugins/grideditor.elements.min.js +1 -1
  19. package/dist/plugins/grideditor.elements.min.js.map +1 -1
  20. package/dist/plugins/grideditor.summernote.js +2 -1
  21. package/dist/plugins/grideditor.summernote.min.js.map +1 -1
  22. package/dist/plugins/grideditor.tinymce.js +2 -1
  23. package/dist/plugins/grideditor.tinymce.min.js.map +1 -1
  24. package/docs/events.md +4 -3
  25. package/docs/plugins.md +16 -19
  26. package/example/attributes.html +21 -37
  27. package/example/elements.html +25 -20
  28. package/example/plugins.html +5 -5
  29. package/example/utilities.html +1 -1
  30. package/package.json +1 -1
  31. package/src/js/jquery.grideditor.js +103 -18
  32. package/src/js/plugins/grideditor.ckeditor.js +2 -1
  33. package/src/js/plugins/grideditor.clipboard.js +1 -11
  34. package/src/js/plugins/grideditor.elements.js +66 -38
  35. package/src/js/plugins/grideditor.summernote.js +2 -1
  36. package/src/js/plugins/grideditor.tinymce.js +2 -1
  37. package/src/less/grideditor.less +17 -0
  38. package/src/js/compat/bundled-texts.js +0 -14
@@ -1480,12 +1480,6 @@ $.fn.gridEditor = function( optionsOrMethod ) {
1480
1480
  return;
1481
1481
  }
1482
1482
 
1483
- if (text.bundled) {
1484
- warnOnceHere('text-bundled:' + type, 'the ' + type + ' text editor is the copy in the ' +
1485
- 'main bundle, which 6.0 will not include: load dist/plugins/grideditor.' + type +
1486
- '.js after the editor');
1487
- }
1488
-
1489
1483
  $(this).data('ge-text-before', attributesOf(this)).removeData('ge-text-ready');
1490
1484
  $(this).addClass('ge-rte-active');
1491
1485
  text.start($(this));
@@ -1501,7 +1495,9 @@ $.fn.gridEditor = function( optionsOrMethod ) {
1501
1495
  canvas.addClass('ge-editing');
1502
1496
  canvas.toggleClass('ge-drag-drawer', settings.drag_handle === 'drawer');
1503
1497
  addAllColClasses();
1504
- wrapContent();
1498
+ var cutter = textCutter();
1499
+ splitTexts(cutter);
1500
+ wrapContent(cutter);
1505
1501
  wrapTexts();
1506
1502
  createRowControls();
1507
1503
  createColControls();
@@ -1632,7 +1628,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
1632
1628
  // plugins setting: a page that names its containers there has
1633
1629
  // named no editor, and still wants the one it uses
1634
1630
  $.each($.fn.gridEditor.texts, function(type, factory) {
1635
- TEXTS[type] = $.extend({ bundled: !!factory.bundled }, factory(api));
1631
+ TEXTS[type] = factory(api);
1636
1632
  });
1637
1633
 
1638
1634
  // 5.x's registry, for an integration a host wrote itself. One the
@@ -3475,15 +3471,12 @@ $.fn.gridEditor = function( optionsOrMethod ) {
3475
3471
 
3476
3472
  /**
3477
3473
  * What a drag reports as moving. A text block is the editor's own
3478
- * wrapper, so what moved is the content area inside it, and until 6.0
3479
- * a content area moves as kind 'content', as it did in 5.x - adding
3480
- * and deleting one, which 5.x never announced, already say 'text'.
3474
+ * wrapper, so what moved is the content area inside it.
3481
3475
  */
3482
3476
  function moveSubject(item) {
3483
3477
  var node = item.hasClass('ge-text-block') ? item.children('.ge-content') : item;
3484
- var kind = kindOf(node);
3485
3478
 
3486
- return { kind: kind === 'text' ? 'content' : kind, node: node };
3479
+ return { kind: kindOf(node), node: node };
3487
3480
  }
3488
3481
 
3489
3482
  /** Put a node back where a refused drag found it. */
@@ -3949,10 +3942,102 @@ $.fn.gridEditor = function( optionsOrMethod ) {
3949
3942
  }
3950
3943
  }
3951
3944
 
3945
+ /**
3946
+ * What a text is never made of: rows, containers, and whatever a
3947
+ * feature plugin says - the elements plugin's elements. Each is a
3948
+ * block of the column's, beside the texts.
3949
+ */
3950
+ function textCutter() {
3951
+ var cuts = ['.row', '[data-ge-container]'];
3952
+
3953
+ $.each(FEATURES, function(name, feature) {
3954
+ var cut = typeof feature.cuts === 'function' ? feature.cuts() : feature.cuts;
3955
+ if (cut) { cuts.push(cut); }
3956
+ });
3957
+
3958
+ return cuts.join(', ');
3959
+ }
3960
+
3961
+ /**
3962
+ * Take the blocks out of the text.
3963
+ *
3964
+ * Up to 5.x an element lived inside a content area, among the text
3965
+ * a rich text editor edited. From 6.0 it is a block of its own, so a
3966
+ * content area with one inside - markup 5.x saved - is cut there: the
3967
+ * text before stays in the content area, the element goes into the
3968
+ * column after it, and the text after goes into a new content area of
3969
+ * the same type. Only a content area's own children cut it, which is
3970
+ * where 5.x recognized elements; one inside a paragraph is text, as
3971
+ * it was. A part with nothing but whitespace is dropped, and the first
3972
+ * part that has something keeps the content area, id and classes and
3973
+ * all.
3974
+ *
3975
+ * On every init, and a no-op on markup 6.0 made: nothing to cut. A
3976
+ * content area its editor is open on is left for the next init.
3977
+ */
3978
+ function splitTexts(cutter) {
3979
+ canvas.find('.column > .ge-content, .column > .ge-text-block > .ge-content').each(function() {
3980
+ var area = $(this);
3981
+ if (area.hasClass('ge-rte-active') || !area.children().filter(cutter).length) { return; }
3982
+
3983
+ var type = area.attr('data-ge-content-type');
3984
+ var anchor = area.parent('.ge-text-block').length ? area.parent() : area;
3985
+ var pieces = [];
3986
+ var run = [];
3987
+
3988
+ $.each($.makeArray(area[0].childNodes), function(i, node) {
3989
+ if (node.nodeType === 1 && $(node).is(cutter)) {
3990
+ pieces.push({ text: run }, { block: node });
3991
+ run = [];
3992
+ } else {
3993
+ run.push(node);
3994
+ }
3995
+ });
3996
+ pieces.push({ text: run });
3997
+
3998
+ $(area[0].childNodes).detach();
3999
+
4000
+ var kept = false;
4001
+ var last = anchor;
4002
+
4003
+ pieces.forEach(function(piece) {
4004
+ var placed;
4005
+
4006
+ if (piece.block) {
4007
+ placed = $(piece.block);
4008
+ } else if (!hasContent(piece.text)) {
4009
+ return;
4010
+ } else if (kept) {
4011
+ // Of the same type, or of none if it had none
4012
+ placed = (type ? createDefaultContentWrapper(type) : $('<div class="ge-content" />'))
4013
+ .append(piece.text);
4014
+ } else {
4015
+ // The content area itself, moved along if an
4016
+ // element came before the first text
4017
+ area.append(piece.text);
4018
+ placed = anchor;
4019
+ kept = true;
4020
+ }
4021
+
4022
+ if (placed[0] !== last[0]) { placed.insertAfter(last); }
4023
+ last = placed;
4024
+ });
4025
+
4026
+ if (!kept) { anchor.remove(); }
4027
+ });
4028
+ }
4029
+
4030
+ /** Whether a run of nodes has anything in it but whitespace. */
4031
+ function hasContent(nodes) {
4032
+ return nodes.some(function(node) {
4033
+ return node.nodeType === 1 || (node.nodeType === 3 && /\S/.test(node.nodeValue));
4034
+ });
4035
+ }
4036
+
3952
4037
  /**
3953
4038
  * Wrap column content in <div class="ge-content"> where neccesary
3954
4039
  */
3955
- function wrapContent() {
4040
+ function wrapContent(cutter) {
3956
4041
  canvas.find('.column').each(function() {
3957
4042
  var col = $(this);
3958
4043
  var contents = $();
@@ -3966,10 +4051,10 @@ $.fn.gridEditor = function( optionsOrMethod ) {
3966
4051
  // the next init.
3967
4052
  if (child.is('.ge-tools-drawer, .ge-resize-handle')) { return; }
3968
4053
 
3969
- // A container sits in the column beside the content
3970
- // areas, not inside one, so it ends a run of loose
3971
- // content rather than joining it
3972
- if (child.is('.row, .ge-content, .ge-text-block, [data-ge-container]')) {
4054
+ // A container, or an element, sits in the column beside
4055
+ // the content areas, not inside one, so it ends a run of
4056
+ // loose content rather than joining it
4057
+ if (child.is('.ge-content, .ge-text-block') || child.is(cutter)) {
3973
4058
  contents = doWrap(contents);
3974
4059
  } else {
3975
4060
  contents = contents.add(child);
@@ -10,7 +10,8 @@
10
10
  * <script src="dist/jquery.grideditor.min.js"></script>
11
11
  * <script src="dist/plugins/grideditor.ckeditor.min.js"></script>
12
12
  *
13
- * Up to 6.0 the main bundle carries a copy of this file too.
13
+ * Up to 5.x the main bundle carried a copy of this file; since 6.0 it is
14
+ * loaded on its own, like every plugin.
14
15
  */
15
16
  (function($) {
16
17
 
@@ -140,17 +140,7 @@ $.fn.gridEditor.features.clipboard = function(ge) {
140
140
  var clip = read();
141
141
  if (!fits(clip, categories)) { return; }
142
142
 
143
- var node = fresh(clip.html);
144
- var into = target;
145
-
146
- // An element goes into the column's last content area. With none,
147
- // the column's next init wraps it in one of its own
148
- if (clip.category === 'element') {
149
- var area = target.find('> .ge-text-block > .ge-content, > .ge-content').last();
150
- if (area.length) { into = area; }
151
- }
152
-
153
- ge.place(node, clip.kind, { appendTo: into, source: 'paste' });
143
+ ge.place(fresh(clip.html), clip.kind, { appendTo: target, source: 'paste' });
154
144
  }
155
145
 
156
146
  /**
@@ -2,12 +2,16 @@
2
2
  * Element level controls for grid-editor.
3
3
  *
4
4
  * A feature plugin: load this file after the editor and the nodes a host
5
- * marked inside a content area become elements - one movable, deletable thing
6
- * each, instead of rich text. What it can ask the editor for is the handle its
7
- * factory is called with, described in docs/plugins.md.
5
+ * marked become elements - one movable, deletable block each, in the column
6
+ * beside the texts, instead of rich text. What it can ask the editor for is
7
+ * the handle its factory is called with, described in docs/plugins.md.
8
8
  *
9
9
  * <script src="dist/jquery.grideditor.min.js"></script>
10
10
  * <script src="dist/plugins/grideditor.elements.min.js"></script>
11
+ *
12
+ * Up to 5.x an element lived inside a content area, among the text. Markup
13
+ * saved that way still loads: the editor takes each element out of the text
14
+ * it sits in, which is what `cuts` is for.
11
15
  */
12
16
  (function($) {
13
17
 
@@ -17,37 +21,41 @@ $.extend($.fn.gridEditor.locales.en, {
17
21
  'confirm.delete_element': 'Delete element?',
18
22
  });
19
23
 
24
+ /** A column's children that are something else than an element, whatever elements.auto says. */
25
+ var NOT_ELEMENTS = '.row, .ge-content, .ge-text-block, [data-ge-container], .ge-tools-drawer, .ge-resize-handle';
26
+
20
27
  $.fn.gridEditor.features.elements = function(ge) {
21
28
 
29
+ var warnedIntoText = false;
30
+
22
31
  function elementsEnabled() {
23
32
  if (ge.settings.elements.enabled !== 'auto') { return !!ge.settings.elements.enabled; }
24
33
 
25
34
  return ge.settings.elements.auto || ge.canvas.find(ge.settings.elements.selector).length > 0;
26
35
  }
27
36
 
28
- function elementsIn(contentArea) {
37
+ /** Whether a child of a column is an element. */
38
+ function isElement(node) {
29
39
  return ge.settings.elements.auto
30
- ? contentArea.children()
31
- : contentArea.children(ge.settings.elements.selector);
40
+ ? !node.is(NOT_ELEMENTS)
41
+ : node.is(ge.settings.elements.selector);
32
42
  }
33
43
 
34
44
  function markElements() {
35
45
  if (!elementsEnabled()) { return; }
36
46
 
37
- ge.canvas.find('.ge-content').each(function() {
38
- elementsIn($(this)).each(function() {
39
- var element = $(this).addClass('ge-element').attr('contenteditable', 'false');
47
+ ge.canvas.find('.column').children().each(function() {
48
+ var element = $(this);
49
+ if (!element.hasClass('ge-element') && !isElement(element)) { return; }
40
50
 
41
- if (element.find('> .ge-tools-drawer').length) { return; }
42
-
43
- createElementControls(element);
44
- });
51
+ element.addClass('ge-element');
52
+ if (!element.find('> .ge-tools-drawer').length) { createElementControls(element); }
45
53
  });
46
54
  }
47
55
 
48
56
  function unmarkElements() {
49
57
  ge.canvas.find('.ge-element').each(function() {
50
- var element = $(this).removeClass('ge-element').removeAttr('contenteditable');
58
+ var element = $(this).removeClass('ge-element');
51
59
 
52
60
  // A host element that had no class of its own should not come
53
61
  // back from getHtml carrying an empty one
@@ -56,16 +64,7 @@ $.fn.gridEditor.features.elements = function(ge) {
56
64
  }
57
65
 
58
66
  function createElementControls(element) {
59
- // data-mce-bogus="all" is how tinyMCE is told that a node is the
60
- // editor's furniture rather than content: it leaves the subtree
61
- // alone and keeps it out of what it serializes. Without it the
62
- // drawer's tools are inline elements with no text, which is
63
- // exactly what its cleanup removes, so an element inside an open
64
- // editor would lose its move and delete tools.
65
- var drawer = $('<div class="ge-tools-drawer ge-element-drawer" />')
66
- .attr('data-mce-bogus', 'all')
67
- .prependTo(element)
68
- ;
67
+ var drawer = $('<div class="ge-tools-drawer ge-element-drawer" />').prependTo(element);
69
68
 
70
69
  ge.createMoveTool(drawer);
71
70
  ge.createTool(drawer, ge.t('tool.element_info', { name: elementName(element) }),
@@ -93,6 +92,32 @@ $.fn.gridEditor.features.elements = function(ge) {
93
92
  return label || type || element[0].tagName.toLowerCase();
94
93
  }
95
94
 
95
+ /**
96
+ * Placed into a content area, as 5.x did, an element would be text
97
+ * again, and the next init would cut it out. So it goes beside that
98
+ * content area instead - after it for appendTo, before it for
99
+ * prependTo - with a word to the host.
100
+ */
101
+ function placementBesideText(options) {
102
+ var placed = $.extend({}, options);
103
+
104
+ [['appendTo', 'insertAfter'], ['prependTo', 'insertBefore']].forEach(function(pair) {
105
+ var target = options[pair[0]] !== undefined ? $(options[pair[0]]) : null;
106
+ if (!target || !target.is('.ge-content')) { return; }
107
+
108
+ if (!warnedIntoText) {
109
+ warnedIntoText = true;
110
+ ge.warn('createElement: an element is a block of the column since 6.0, not part of a ' +
111
+ 'content area\'s text; it goes beside the content area. Place it in a column instead.');
112
+ }
113
+
114
+ delete placed[pair[0]];
115
+ placed[pair[1]] = target.parent('.ge-text-block').length ? target.parent() : target;
116
+ });
117
+
118
+ return placed;
119
+ }
120
+
96
121
  function apiCreateElement(content, options) {
97
122
  options = options || {};
98
123
 
@@ -104,7 +129,7 @@ $.fn.gridEditor.features.elements = function(ge) {
104
129
  element.attr('data-ge-label', options.label);
105
130
  }
106
131
 
107
- return ge.place(element, 'element', options);
132
+ return ge.place(element, 'element', placementBesideText(options));
108
133
  }
109
134
 
110
135
  return {
@@ -117,24 +142,27 @@ $.fn.gridEditor.features.elements = function(ge) {
117
142
  return node.hasClass('ge-element') ? 'element' : null;
118
143
  },
119
144
 
120
- onInit: markElements,
121
- onDeinit: unmarkElements,
122
-
123
145
  /**
124
- * An editor rewrites the content area as it takes it over, drawers
125
- * included, so they go back in when it says it is ready.
146
+ * An element cuts the text it sits in: 5.x's markup, or markup a host
147
+ * wrote the same way, has it inside a content area, and it comes out.
126
148
  */
127
- onContentReady: markElements,
149
+ cuts: function() {
150
+ if (!elementsEnabled()) { return null; }
128
151
 
129
- /** Elements move within a content area and between them. */
130
- onSortable: function(sortable) {
131
- if (!elementsEnabled()) { return; }
152
+ return ge.settings.elements.auto ? '*' : ge.settings.elements.selector;
153
+ },
132
154
 
133
- sortable(ge.canvas.find('.ge-content'), {
134
- draggable: '.ge-element',
135
- group: 'element',
136
- });
155
+ // A block the columns move, beside the texts, the rows and the
156
+ // containers - and only the columns
157
+ blocks: '.ge-element',
158
+ accepts: function(region, node) {
159
+ if (node.hasClass('ge-element')) { return region.is('.column'); }
160
+
161
+ return true;
137
162
  },
163
+
164
+ onInit: markElements,
165
+ onDeinit: unmarkElements,
138
166
  };
139
167
  };
140
168
 
@@ -10,7 +10,8 @@
10
10
  * <script src="dist/jquery.grideditor.min.js"></script>
11
11
  * <script src="dist/plugins/grideditor.summernote.min.js"></script>
12
12
  *
13
- * Up to 6.0 the main bundle carries a copy of this file too.
13
+ * Up to 5.x the main bundle carried a copy of this file; since 6.0 it is
14
+ * loaded on its own, like every plugin.
14
15
  */
15
16
  (function($) {
16
17
 
@@ -10,7 +10,8 @@
10
10
  * <script src="dist/jquery.grideditor.min.js"></script>
11
11
  * <script src="dist/plugins/grideditor.tinymce.min.js"></script>
12
12
  *
13
- * Up to 6.0 the main bundle carries a copy of this file too.
13
+ * Up to 5.x the main bundle carried a copy of this file; since 6.0 it is
14
+ * loaded on its own, like every plugin.
14
15
  */
15
16
  (function($) {
16
17
 
@@ -603,6 +603,23 @@
603
603
  }
604
604
  }
605
605
 
606
+ /* While the text is being edited it would sit over the end of its
607
+ first line, so it stands back - faint, still there to click - until
608
+ the pointer is on the drawer itself */
609
+ &:has(> .ge-rte-active) > .ge-text-drawer:not(:hover) {
610
+ opacity: 0.35;
611
+ }
612
+
613
+ /* Except when something in it is in use: its settings, a choice it
614
+ offers, a drag */
615
+ &.ge-drag-chosen,
616
+ &:has(> .ge-text-drawer.ge-picker-open),
617
+ &:has(> .ge-text-drawer > .ge-details[style*="block"]) {
618
+ > .ge-text-drawer:not(:hover) {
619
+ opacity: 1;
620
+ }
621
+ }
622
+
606
623
  /* With its settings open it spans the text, so the panel has room */
607
624
  &:has(> .ge-text-drawer > .ge-details[style*="block"]) > .ge-text-drawer {
608
625
  left: 0;
@@ -1,14 +0,0 @@
1
- /**
2
- * Up to 6.0 the main bundle carries a copy of each text editor plugin, so a
3
- * page that loaded only the editor keeps the editor it had. This marks those
4
- * copies, and the editor warns once when a content area is edited with one:
5
- * 6.0 leaves them out. Loaded on its own, a plugin's file registers itself
6
- * again, unmarked, and is the one used.
7
- *
8
- * Only ever concatenated after the plugins it marks, never built on its own.
9
- */
10
- (function($) {
11
- ['tinymce', 'ckeditor', 'summernote'].forEach(function(type) {
12
- if ($.fn.gridEditor.texts[type]) { $.fn.gridEditor.texts[type].bundled = true; }
13
- });
14
- })(jQuery);