@themarioga/grid-editor 5.1.1 → 5.2.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 +6 -0
- package/CHANGELOG.md +45 -0
- package/README.md +18 -8
- package/UPGRADING.md +25 -0
- package/dist/grideditor.css +3 -0
- package/dist/grideditor.min.css +1 -1
- package/dist/grideditor.min.css.map +1 -1
- package/dist/jquery.grideditor.bundle.min.js +2 -2
- package/dist/jquery.grideditor.js +374 -218
- package/dist/jquery.grideditor.min.js +1 -1
- package/dist/jquery.grideditor.min.js.map +1 -1
- package/dist/plugins/grideditor.ckeditor.js +115 -0
- package/dist/plugins/grideditor.ckeditor.min.js +2 -0
- package/dist/plugins/grideditor.ckeditor.min.js.map +1 -0
- package/dist/plugins/grideditor.summernote.js +93 -0
- package/dist/plugins/grideditor.summernote.min.js +2 -0
- package/dist/plugins/grideditor.summernote.min.js.map +1 -0
- package/dist/plugins/grideditor.tinymce.js +157 -0
- package/dist/plugins/grideditor.tinymce.min.js +2 -0
- package/dist/plugins/grideditor.tinymce.min.js.map +1 -0
- package/docs/plugins.md +86 -3
- package/example/attributes.html +364 -0
- package/example/autosave.html +2 -0
- package/example/basic.html +2 -0
- package/example/ckeditor.html +2 -0
- package/example/clipboard.html +2 -0
- package/example/containers.html +2 -0
- package/example/elements.html +2 -0
- package/example/index.html +8 -0
- package/example/summernote.html +2 -0
- package/example/wrap_content.html +2 -0
- package/package.json +1 -1
- package/src/js/compat/bundled-texts.js +14 -0
- package/src/js/jquery.grideditor.js +73 -14
- package/src/js/plugins/grideditor.ckeditor.js +115 -0
- package/src/js/plugins/grideditor.summernote.js +93 -0
- package/src/js/plugins/grideditor.tinymce.js +157 -0
- package/src/less/grideditor.less +4 -0
- package/src/js/jquery.grideditor.ckeditor.js +0 -77
- package/src/js/jquery.grideditor.summernote.js +0 -72
- package/src/js/jquery.grideditor.tinymce.js +0 -135
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tinyMCE for grid-editor's content areas.
|
|
3
|
+
*
|
|
4
|
+
* A text editor plugin: load this file after the editor, and tinyMCE 6
|
|
5
|
+
* after or before it, and content_types: ['tinymce'] edits each content area
|
|
6
|
+
* with an inline tinyMCE. What it can ask the editor for is the handle its
|
|
7
|
+
* factory is called with, described in docs/plugins.md.
|
|
8
|
+
*
|
|
9
|
+
* <script src="tinymce/tinymce.min.js"></script>
|
|
10
|
+
* <script src="dist/jquery.grideditor.min.js"></script>
|
|
11
|
+
* <script src="dist/plugins/grideditor.tinymce.min.js"></script>
|
|
12
|
+
*
|
|
13
|
+
* Up to 6.0 the main bundle carries a copy of this file too.
|
|
14
|
+
*/
|
|
15
|
+
(function($) {
|
|
16
|
+
|
|
17
|
+
// tinyMCE snapshots the target element's attributes when an inline editor is
|
|
18
|
+
// created and restores them on remove(), so this has to run *after* remove()
|
|
19
|
+
// to keep the grid editor's own class and tinyMCE's leftovers off the element.
|
|
20
|
+
function cleanUp(contentArea) {
|
|
21
|
+
contentArea
|
|
22
|
+
.removeClass('active')
|
|
23
|
+
.removeClass('ge-rte-active')
|
|
24
|
+
.removeAttr('id')
|
|
25
|
+
.removeAttr('style')
|
|
26
|
+
.removeAttr('spellcheck')
|
|
27
|
+
.removeAttr('contenteditable')
|
|
28
|
+
.removeAttr('data-mce-style')
|
|
29
|
+
;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var INITIAL_CONTENT = '<p>Lorem ipsum dolores</p>';
|
|
33
|
+
|
|
34
|
+
$.fn.gridEditor.texts.tinymce = function(ge) {
|
|
35
|
+
return {
|
|
36
|
+
initialContent: INITIAL_CONTENT,
|
|
37
|
+
missingKey: 'error.tinymce_missing',
|
|
38
|
+
|
|
39
|
+
available: function() { return !!window.tinymce; },
|
|
40
|
+
|
|
41
|
+
start: function(contentAreas) {
|
|
42
|
+
var settings = ge.settings;
|
|
43
|
+
var userConfig = (settings.tinymce && settings.tinymce.config) ? settings.tinymce.config : {};
|
|
44
|
+
|
|
45
|
+
contentAreas.each(function() {
|
|
46
|
+
var contentArea = $(this);
|
|
47
|
+
if (contentArea.hasClass('active')) { return; }
|
|
48
|
+
|
|
49
|
+
if (contentArea.html() == INITIAL_CONTENT) {
|
|
50
|
+
contentArea.html('');
|
|
51
|
+
}
|
|
52
|
+
contentArea.addClass('active');
|
|
53
|
+
|
|
54
|
+
var configuration = $.extend({
|
|
55
|
+
// tinyMCE's own "Upgrade" badge in the menubar. Off by
|
|
56
|
+
// default because an inline editor here is a column of
|
|
57
|
+
// someone's page, not tinyMCE's own interface; a host that
|
|
58
|
+
// wants it back passes promotion: true.
|
|
59
|
+
promotion: false,
|
|
60
|
+
}, userConfig, {
|
|
61
|
+
target: this,
|
|
62
|
+
inline: true,
|
|
63
|
+
init_instance_callback: function(editor) {
|
|
64
|
+
// deinit ran before this init finished, so tear it down again
|
|
65
|
+
if (contentArea.data('ge-tinymce-pending-remove')) {
|
|
66
|
+
contentArea.removeData('ge-tinymce-pending-remove');
|
|
67
|
+
editor.remove();
|
|
68
|
+
// deinit already ran and cannot clean up after this
|
|
69
|
+
// late remove(), so do it here instead
|
|
70
|
+
cleanUp(contentArea);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
contentArea.data('ge-tinymce', editor);
|
|
75
|
+
|
|
76
|
+
// The editor rewrote what is inside this content area
|
|
77
|
+
// while it took it over, so whatever the grid editor
|
|
78
|
+
// had in there is gone. Saying so lets it put its own
|
|
79
|
+
// furniture back.
|
|
80
|
+
ge.textReady(contentArea);
|
|
81
|
+
|
|
82
|
+
// Undo and redo rewrite it too, from snapshots that
|
|
83
|
+
// leave out whatever is marked data-mce-bogus - an
|
|
84
|
+
// element's drawer among them - so it goes back in
|
|
85
|
+
// after each of those as well
|
|
86
|
+
editor.on('Undo Redo', function() {
|
|
87
|
+
ge.textReady(contentArea);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// The inline toolbar is laid out against the element's
|
|
91
|
+
// geometry at the moment tinyMCE draws it, and an
|
|
92
|
+
// element that has just appeared - a tab pane, an
|
|
93
|
+
// accordion body, a column whose width is still
|
|
94
|
+
// settling - may not have its own width yet. Asking
|
|
95
|
+
// for the ui again once the browser has laid the frame
|
|
96
|
+
// out measures it as it now is, instead of leaving a
|
|
97
|
+
// toolbar wrapped into a narrow column.
|
|
98
|
+
window.requestAnimationFrame(function() {
|
|
99
|
+
if (editor.removed || !editor.ui || !editor.ui.show) { return; }
|
|
100
|
+
|
|
101
|
+
editor.ui.show();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// And again whenever the user comes back to this
|
|
105
|
+
// editor: by then the element may have been resized,
|
|
106
|
+
// hidden and shown again - a tab switched away from
|
|
107
|
+
// and back, a column made narrower - and the toolbar
|
|
108
|
+
// is only ever as right as its last measurement.
|
|
109
|
+
editor.on('focus', function() {
|
|
110
|
+
window.requestAnimationFrame(function() {
|
|
111
|
+
if (editor.removed || !editor.ui || !editor.ui.show) { return; }
|
|
112
|
+
|
|
113
|
+
editor.ui.show();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Bring focus to text field
|
|
118
|
+
editor.focus();
|
|
119
|
+
|
|
120
|
+
// Call the original callbacks, if any were passed in the config
|
|
121
|
+
if (userConfig.init_instance_callback) {
|
|
122
|
+
userConfig.init_instance_callback.call(this, editor);
|
|
123
|
+
}
|
|
124
|
+
// 'oninit' is the pre-6 name, honoured so existing configs keep working
|
|
125
|
+
if (userConfig.oninit) {
|
|
126
|
+
userConfig.oninit.call(this, editor);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
// We always edit the element we were handed, so a selector in the
|
|
131
|
+
// user config would only fight with target
|
|
132
|
+
delete configuration.selector;
|
|
133
|
+
|
|
134
|
+
window.tinymce.init(configuration);
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
stop: function(contentAreas) {
|
|
139
|
+
contentAreas.filter('.active').each(function() {
|
|
140
|
+
var contentArea = $(this);
|
|
141
|
+
var editor = contentArea.data('ge-tinymce');
|
|
142
|
+
|
|
143
|
+
if (editor) {
|
|
144
|
+
contentArea.removeData('ge-tinymce');
|
|
145
|
+
editor.remove();
|
|
146
|
+
} else {
|
|
147
|
+
// tinymce.init() is asynchronous, so the editor may not exist
|
|
148
|
+
// yet. Leave a note for init_instance_callback to remove it.
|
|
149
|
+
contentArea.data('ge-tinymce-pending-remove', true);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
cleanUp(contentArea);
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
})(jQuery);
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(o=>{function a(e){e.removeClass("active").removeClass("ge-rte-active").removeAttr("id").removeAttr("style").removeAttr("spellcheck").removeAttr("contenteditable").removeAttr("data-mce-style")}var c="<p>Lorem ipsum dolores</p>";o.fn.gridEditor.texts.tinymce=function(n){return{initialContent:c,missingKey:"error.tinymce_missing",available:function(){return!!window.tinymce},start:function(e){var t=n.settings,i=t.tinymce&&t.tinymce.config?t.tinymce.config:{};e.each(function(){var e,t=o(this);t.hasClass("active")||(t.html()==c&&t.html(""),t.addClass("active"),delete(e=o.extend({promotion:!1},i,{target:this,inline:!0,init_instance_callback:function(e){t.data("ge-tinymce-pending-remove")?(t.removeData("ge-tinymce-pending-remove"),e.remove(),a(t)):(t.data("ge-tinymce",e),n.textReady(t),e.on("Undo Redo",function(){n.textReady(t)}),window.requestAnimationFrame(function(){!e.removed&&e.ui&&e.ui.show&&e.ui.show()}),e.on("focus",function(){window.requestAnimationFrame(function(){!e.removed&&e.ui&&e.ui.show&&e.ui.show()})}),e.focus(),i.init_instance_callback&&i.init_instance_callback.call(this,e),i.oninit&&i.oninit.call(this,e))}})).selector,window.tinymce.init(e))})},stop:function(e){e.filter(".active").each(function(){var e=o(this),t=e.data("ge-tinymce");t?(e.removeData("ge-tinymce"),t.remove()):e.data("ge-tinymce-pending-remove",!0),a(e)})}}}})(jQuery);
|
|
2
|
+
//# sourceMappingURL=grideditor.tinymce.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grideditor.tinymce.min.js","sources":["../../src/js/plugins/grideditor.tinymce.js"],"names":["$","cleanUp","contentArea","removeClass","removeAttr","INITIAL_CONTENT","fn","gridEditor","texts","tinymce","ge","initialContent","missingKey","available","window","start","contentAreas","settings","userConfig","config","each","configuration","this","hasClass","html","addClass","extend","promotion","target","inline","init_instance_callback","editor","data","removeData","remove","textReady","on","requestAnimationFrame","removed","ui","show","focus","call","oninit","selector","init","stop","filter","jQuery"],"mappings":"CAcUA,IAKN,SAASC,EAAQC,GACbA,EACKC,YAAY,QAAQ,EACpBA,YAAY,eAAe,EAC3BC,WAAW,IAAI,EACfA,WAAW,OAAO,EAClBA,WAAW,YAAY,EACvBA,WAAW,iBAAiB,EAC5BA,WAAW,gBAAgB,CAEpC,CAEA,IAAIC,EAAkB,6BAEtBL,EAAEM,GAAGC,WAAWC,MAAMC,QAAU,SAASC,GACrC,MAAO,CACHC,eAAgBN,EAChBO,WAAY,wBAEZC,UAAW,WAAa,MAAO,CAAC,CAACC,OAAOL,OAAS,EAEjDM,MAAO,SAASC,GACZ,IAAIC,EAAWP,EAAGO,SACdC,EAAcD,EAASR,SAAWQ,EAASR,QAAQU,OAAUF,EAASR,QAAQU,OAAS,GAE3FH,EAAaI,KAAK,WACd,IAQIC,EARAnB,EAAcF,EAAEsB,IAAI,EACpBpB,EAAYqB,SAAS,QAAQ,IAE7BrB,EAAYsB,KAAK,GAAKnB,GACtBH,EAAYsB,KAAK,EAAE,EAEvBtB,EAAYuB,SAAS,QAAQ,EAgF7B,OA9EIJ,EAAgBrB,EAAE0B,OAAO,CAKzBC,UAAW,CAAA,CACf,EAAGT,EAAY,CACXU,OAAQN,KACRO,OAAQ,CAAA,EACRC,uBAAwB,SAASC,GAEzB7B,EAAY8B,KAAK,2BAA2B,GAC5C9B,EAAY+B,WAAW,2BAA2B,EAClDF,EAAOG,OAAO,EAGdjC,EAAQC,CAAW,IAIvBA,EAAY8B,KAAK,aAAcD,CAAM,EAMrCrB,EAAGyB,UAAUjC,CAAW,EAMxB6B,EAAOK,GAAG,YAAa,WACnB1B,EAAGyB,UAAUjC,CAAW,CAC5B,CAAC,EAUDY,OAAOuB,sBAAsB,WACrBN,CAAAA,EAAOO,SAAYP,EAAOQ,IAAOR,EAAOQ,GAAGC,MAE/CT,EAAOQ,GAAGC,KAAK,CACnB,CAAC,EAODT,EAAOK,GAAG,QAAS,WACftB,OAAOuB,sBAAsB,WACrBN,CAAAA,EAAOO,SAAYP,EAAOQ,IAAOR,EAAOQ,GAAGC,MAE/CT,EAAOQ,GAAGC,KAAK,CACnB,CAAC,CACL,CAAC,EAGDT,EAAOU,MAAM,EAGTvB,EAAWY,wBACXZ,EAAWY,uBAAuBY,KAAKpB,KAAMS,CAAM,EAGnDb,EAAWyB,QACXzB,EAAWyB,OAAOD,KAAKpB,KAAMS,CAAM,EAE3C,CACJ,CAAC,GAGoBa,SAErB9B,OAAOL,QAAQoC,KAAKxB,CAAa,EACrC,CAAC,CACL,EAEAyB,KAAM,SAAS9B,GACXA,EAAa+B,OAAO,SAAS,EAAE3B,KAAK,WAChC,IAAIlB,EAAcF,EAAEsB,IAAI,EACpBS,EAAS7B,EAAY8B,KAAK,YAAY,EAEtCD,GACA7B,EAAY+B,WAAW,YAAY,EACnCF,EAAOG,OAAO,GAIdhC,EAAY8B,KAAK,4BAA6B,CAAA,CAAI,EAGtD/B,EAAQC,CAAW,CACvB,CAAC,CACL,CACJ,CACJ,CACH,GAAE8C,MAAM"}
|
package/docs/plugins.md
CHANGED
|
@@ -14,13 +14,15 @@ the one to read first if you are about to write your own.
|
|
|
14
14
|
<script src="dist/plugins/grideditor.elements.min.js"></script>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
There are
|
|
17
|
+
There are four kinds. A **container plugin** builds a type of container — it
|
|
18
18
|
registers under `$.fn.gridEditor.containers`, and the toolbar offers a button
|
|
19
19
|
for it. A **utility plugin** declares families of Bootstrap's responsive
|
|
20
20
|
utility classes — `$.fn.gridEditor.utilities` — and the editor edits them per
|
|
21
|
-
breakpoint. A **
|
|
21
|
+
breakpoint. A **text editor plugin** edits content areas with a rich text
|
|
22
|
+
editor — `$.fn.gridEditor.texts`, one per editor: tinyMCE, CKEditor,
|
|
23
|
+
summernote. A **feature plugin** is anything else the editor can do —
|
|
22
24
|
`$.fn.gridEditor.features`, hooks into the canvas, and may contribute methods.
|
|
23
|
-
All
|
|
25
|
+
All four are factories called once per editor with the same handle.
|
|
24
26
|
|
|
25
27
|
The `plugins` setting names which of the loaded ones to use:
|
|
26
28
|
|
|
@@ -124,6 +126,7 @@ not change without a major version.
|
|
|
124
126
|
| `ge.setUtility(node, family, value, options?)` | Write one through the events. `options` is a view key or `{ view, source }` |
|
|
125
127
|
| `ge.utilityField(node, family)` | A panel field for one family, for a plugin that builds its own panel |
|
|
126
128
|
| `ge.rowFromLayout(layout)` | A detached row from a layout: `[8, 4]`, `['auto', 'equal']` or `{ row_cols, columns }` |
|
|
129
|
+
| `ge.textReady(block)` | A text editor has rewritten a content area: whatever the editor and its plugins put in there goes back in |
|
|
127
130
|
| `ge.nodeHtml(node)` | One node's markup as `getHtml` would give it. The canvas leaves editing to read it and comes back, as it does for `getHtml` |
|
|
128
131
|
| `ge.toolbarItems(name)` | The toolbar buttons the feature plugin `name` declared, for showing and hiding them |
|
|
129
132
|
| `ge.bareStyle(node, family, property)` | A css property's value on the node with none of the family's classes: what a preview shows when no class applies and that is not a constant |
|
|
@@ -174,6 +177,37 @@ needs to put a new kind of block on the canvas:
|
|
|
174
177
|
- **`drawerTools(drawer, node, kind)`** runs for every drawer that has a gear,
|
|
175
178
|
as a utility plugin's does, so a tool can go on any node. The clipboard
|
|
176
179
|
plugin puts its copy and paste tools in with it.
|
|
180
|
+
|
|
181
|
+
### Settings of your own, saved on the node
|
|
182
|
+
|
|
183
|
+
A tool that opens a dialog and saves what the user chose on the node is a
|
|
184
|
+
feature plugin with `drawerTools` and nothing else.
|
|
185
|
+
[example/attributes.html](../example/attributes.html) is one, in full: an
|
|
186
|
+
animation for rows, columns, containers and elements, kept in a
|
|
187
|
+
`data-animation` attribute. Three things to get right:
|
|
188
|
+
|
|
189
|
+
- **The attribute's name.** Anything but `data-ge-*`, which `getPlainHtml`
|
|
190
|
+
takes off. `getHtml` keeps every attribute that is not the editor's.
|
|
191
|
+
- **The dialog lives outside the canvas**, appended to `body`, so it is never
|
|
192
|
+
part of the markup.
|
|
193
|
+
- **An element sits inside text a rich text editor may be editing.** Written
|
|
194
|
+
behind its back, the attribute is lost the next time the user undoes
|
|
195
|
+
something: undo restores a snapshot from before it. With tinyMCE, write it
|
|
196
|
+
through the editor's undo manager, and it becomes a step of its own:
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
var area = node.closest('.ge-content')[0];
|
|
200
|
+
var editor = window.tinymce && tinymce.get().filter(function(e) { return e.getBody() === area; })[0];
|
|
201
|
+
var apply = function() { node.attr('data-animation', JSON.stringify(value)); };
|
|
202
|
+
|
|
203
|
+
if (editor) { editor.undoManager.transact(apply); } else { apply(); }
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Rows, columns, sections and containers are outside any content area, and a
|
|
207
|
+
plain `attr()` is all they need.
|
|
208
|
+
|
|
209
|
+
A node's drawer is built again on every `init`, so a tool reads the node
|
|
210
|
+
rather than remembering anything about it.
|
|
177
211
|
- A **`toolbar`** item can also have an `iconClass`, `bi bi-plus` otherwise,
|
|
178
212
|
and a `source` for the add events, `tool` or `dragdrop` otherwise. With
|
|
179
213
|
`align: 'end'` it goes on the right of the toolbar, beside the source and
|
|
@@ -220,6 +254,55 @@ plugin knows which of its parts move: the tabs plugin makes its strip sortable
|
|
|
220
254
|
and puts the panes back in strip order in `afterPaneMove`.
|
|
221
255
|
|
|
222
256
|
|
|
257
|
+
Text editor plugins
|
|
258
|
+
-------------------
|
|
259
|
+
|
|
260
|
+
A text editor plugin edits content areas with a rich text editor. It is
|
|
261
|
+
registered under the content type it edits - the name `content_types` uses -
|
|
262
|
+
and the three that ship, `grideditor.tinymce.js`, `grideditor.ckeditor.js` and
|
|
263
|
+
`grideditor.summernote.js`, are the ones to read:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
$.fn.gridEditor.texts.mytext = function(ge) {
|
|
267
|
+
return {
|
|
268
|
+
initialContent: '<p>Write here</p>', // what a new column's content area holds
|
|
269
|
+
missingKey: 'error.mytext_missing', // the error when the library is not loaded, optional
|
|
270
|
+
available: function() { return !!window.MyText; }, // optional, true otherwise
|
|
271
|
+
|
|
272
|
+
start: function(block) { … }, // open the editor on this content area
|
|
273
|
+
stop: function(block) { … }, // close it, and leave the content area as it was
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
- **`start(block)`** is called when the user clicks a content area of this
|
|
279
|
+
type. The editor marks it `ge-rte-active` first, and does not call `start`
|
|
280
|
+
again until it has been stopped. Once the library has taken the content
|
|
281
|
+
area over, call **`ge.textReady(block)`**: it rewrote what was inside, and
|
|
282
|
+
the editor and its plugins put their own furniture back - an element's
|
|
283
|
+
drawer, for one. Call it again whenever the library rewrites the content
|
|
284
|
+
area again, as tinyMCE's undo does.
|
|
285
|
+
- **`stop(block)`** is called for every content area of this type on each
|
|
286
|
+
`deinit`, which `getHtml` runs: a content area whose editor never started is
|
|
287
|
+
the plugin's to ignore. Whatever the library and `start` added - classes,
|
|
288
|
+
`contenteditable`, inline styles, ids - comes off here.
|
|
289
|
+
- **`available()`** false and the editor logs `missingKey`'s message and
|
|
290
|
+
starts nothing, without marking the content area, so a later click tries
|
|
291
|
+
again once the library is there.
|
|
292
|
+
|
|
293
|
+
A text editor is chosen by `content_types`, never by the `plugins` setting: a
|
|
294
|
+
page that names its containers there has named no editor.
|
|
295
|
+
|
|
296
|
+
Up to 6.0 the main bundle carries a copy of the three shipped ones, marked
|
|
297
|
+
`bundled`; editing with a copy warns once that 6.0 leaves them out. A plugin's
|
|
298
|
+
own file, loaded after, registers itself again and is the one used.
|
|
299
|
+
|
|
300
|
+
5.x's `$.fn.gridEditor.RTEs.mytext = { init, deinit, initialContent }` still
|
|
301
|
+
works: `init(settings, contentAreas)` and `deinit(settings, contentAreas)` are
|
|
302
|
+
wrapped as `start` and `stop`, with a warning that `RTEs` is deprecated. It goes
|
|
303
|
+
in 7.0.
|
|
304
|
+
|
|
305
|
+
|
|
223
306
|
Utility plugins
|
|
224
307
|
---------------
|
|
225
308
|
|
|
@@ -0,0 +1,364 @@
|
|
|
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
|
+
<!-- The text editor is a plugin of its own, loaded after the editor -->
|
|
21
|
+
<script src="../dist/plugins/grideditor.tinymce.min.js"></script>
|
|
22
|
+
<script src="../dist/plugins/grideditor.card.min.js"></script>
|
|
23
|
+
<script src="../dist/plugins/grideditor.elements.min.js"></script>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
/* The drawer tool of a node that has an animation set. Under the
|
|
27
|
+
canvas's id, to outweigh the colour the editor gives each kind of
|
|
28
|
+
drawer's tools */
|
|
29
|
+
#myGrid .ge-tools-drawer > a.my-animation-tool.my-animation-set {
|
|
30
|
+
color: #d63384;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
|
|
34
|
+
<!-- A plugin of this page's own: a drawer tool that opens a modal, and
|
|
35
|
+
what the user chooses there saved as an attribute on the node. -->
|
|
36
|
+
<script id="animation-plugin">
|
|
37
|
+
(function($) {
|
|
38
|
+
|
|
39
|
+
$.extend($.fn.gridEditor.locales.en, {
|
|
40
|
+
'animation.tool': 'Animation',
|
|
41
|
+
'animation.title': 'Animate on scroll',
|
|
42
|
+
'animation.effect': 'Effect',
|
|
43
|
+
'animation.none': 'None',
|
|
44
|
+
'animation.fade': 'Fade in',
|
|
45
|
+
'animation.slide': 'Slide up',
|
|
46
|
+
'animation.zoom': 'Zoom in',
|
|
47
|
+
'animation.duration': 'Duration (ms)',
|
|
48
|
+
'animation.delay': 'Delay (ms)',
|
|
49
|
+
'animation.once': 'Only the first time it scrolls into view',
|
|
50
|
+
'animation.cancel': 'Cancel',
|
|
51
|
+
'animation.save': 'Save',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Not data-ge-*: getPlainHtml takes those off, and this has to
|
|
55
|
+
// reach the published page
|
|
56
|
+
var ATTRIBUTE = 'data-animation';
|
|
57
|
+
var EFFECTS = ['fade', 'slide', 'zoom'];
|
|
58
|
+
|
|
59
|
+
$.fn.gridEditor.features.animation = function(ge) {
|
|
60
|
+
|
|
61
|
+
var modal = null;
|
|
62
|
+
|
|
63
|
+
function read(node) {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(node.attr(ATTRIBUTE) || 'null');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* An element lives inside a content area, and while a rich
|
|
73
|
+
* text editor is open there, a change it does not know about
|
|
74
|
+
* is one its undo would take back: undoing the next thing the
|
|
75
|
+
* user types restores a snapshot from before the attribute.
|
|
76
|
+
* Made through tinyMCE's undo manager, it is a step of its
|
|
77
|
+
* own, which undo and redo treat like any other.
|
|
78
|
+
*/
|
|
79
|
+
function write(node, value) {
|
|
80
|
+
var apply = function() {
|
|
81
|
+
if (value) {
|
|
82
|
+
node.attr(ATTRIBUTE, JSON.stringify(value));
|
|
83
|
+
} else {
|
|
84
|
+
node.removeAttr(ATTRIBUTE);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var editor = editorAround(node);
|
|
88
|
+
|
|
89
|
+
if (editor) {
|
|
90
|
+
editor.undoManager.transact(apply);
|
|
91
|
+
} else {
|
|
92
|
+
apply();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The tinyMCE editor open on the content area around node, if there is one. */
|
|
97
|
+
function editorAround(node) {
|
|
98
|
+
var area = node.closest('.ge-content');
|
|
99
|
+
if (!area.length || !window.tinymce) { return null; }
|
|
100
|
+
|
|
101
|
+
return window.tinymce.get().filter(function(editor) {
|
|
102
|
+
return editor.getBody && editor.getBody() === area[0];
|
|
103
|
+
})[0] || null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Built once, outside the canvas, so it is never part of getHtml. */
|
|
107
|
+
function dialog() {
|
|
108
|
+
if (modal) { return modal; }
|
|
109
|
+
|
|
110
|
+
var effects = $('<select class="form-select" name="effect" />')
|
|
111
|
+
.append($('<option value="" />').text(ge.t('animation.none')));
|
|
112
|
+
EFFECTS.forEach(function(effect) {
|
|
113
|
+
effects.append($('<option />').val(effect).text(ge.t('animation.' + effect)));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
var field = function(label, input) {
|
|
117
|
+
return $('<div class="mb-3" />')
|
|
118
|
+
.append($('<label class="form-label" />').text(ge.t(label)))
|
|
119
|
+
.append(input);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
modal = $(
|
|
123
|
+
'<div class="modal fade my-animation-modal" tabindex="-1" aria-hidden="true">' +
|
|
124
|
+
'<div class="modal-dialog modal-dialog-centered"><form class="modal-content">' +
|
|
125
|
+
'<div class="modal-header">' +
|
|
126
|
+
'<h5 class="modal-title"></h5>' +
|
|
127
|
+
'<button type="button" class="btn-close" data-bs-dismiss="modal"></button>' +
|
|
128
|
+
'</div>' +
|
|
129
|
+
'<div class="modal-body"></div>' +
|
|
130
|
+
'<div class="modal-footer">' +
|
|
131
|
+
'<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"></button>' +
|
|
132
|
+
'<button type="submit" class="btn btn-primary"></button>' +
|
|
133
|
+
'</div>' +
|
|
134
|
+
'</form></div>' +
|
|
135
|
+
'</div>'
|
|
136
|
+
).appendTo('body');
|
|
137
|
+
|
|
138
|
+
modal.find('.modal-title').text(ge.t('animation.title'));
|
|
139
|
+
modal.find('.modal-footer .btn-secondary').text(ge.t('animation.cancel'));
|
|
140
|
+
modal.find('.modal-footer .btn-primary').text(ge.t('animation.save'));
|
|
141
|
+
modal.find('.modal-body')
|
|
142
|
+
.append(field('animation.effect', effects))
|
|
143
|
+
.append(field('animation.duration', '<input class="form-control" type="number" name="duration" min="0" step="100">'))
|
|
144
|
+
.append(field('animation.delay', '<input class="form-control" type="number" name="delay" min="0" step="100">'))
|
|
145
|
+
.append($('<div class="form-check" />')
|
|
146
|
+
.append('<input class="form-check-input" type="checkbox" name="once" id="my-animation-once">')
|
|
147
|
+
.append($('<label class="form-check-label" for="my-animation-once" />').text(ge.t('animation.once'))));
|
|
148
|
+
|
|
149
|
+
return modal;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function open(node, kind, tool) {
|
|
153
|
+
var form = dialog().find('form');
|
|
154
|
+
var current = read(node) || {};
|
|
155
|
+
|
|
156
|
+
form.find('[name=effect]').val(current.effect || '');
|
|
157
|
+
form.find('[name=duration]').val(current.duration || 600);
|
|
158
|
+
form.find('[name=delay]').val(current.delay || 0);
|
|
159
|
+
form.find('[name=once]').prop('checked', current.once !== false);
|
|
160
|
+
|
|
161
|
+
form.off('submit').on('submit', function(e) {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
|
|
164
|
+
var effect = form.find('[name=effect]').val();
|
|
165
|
+
var value = effect ? {
|
|
166
|
+
effect: effect,
|
|
167
|
+
duration: parseInt(form.find('[name=duration]').val(), 10) || 0,
|
|
168
|
+
delay: parseInt(form.find('[name=delay]').val(), 10) || 0,
|
|
169
|
+
once: form.find('[name=once]').prop('checked'),
|
|
170
|
+
} : null;
|
|
171
|
+
|
|
172
|
+
var from = read(node);
|
|
173
|
+
write(node, value);
|
|
174
|
+
tool.toggleClass('my-animation-set', !!value);
|
|
175
|
+
|
|
176
|
+
// Announced like the editor's own changes, for a host
|
|
177
|
+
// that listens: grideditor:animation-change
|
|
178
|
+
ge.emit('animation-change', ge.payloadFor(kind, node, {
|
|
179
|
+
source: 'tool', from: from, to: value,
|
|
180
|
+
}));
|
|
181
|
+
|
|
182
|
+
window.bootstrap.Modal.getInstance(modal[0]).hide();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
window.bootstrap.Modal.getOrCreateInstance(modal[0]).show();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
// Every drawer that has a gear, and the kind of its node:
|
|
190
|
+
// a row, a column, an element, a container's type, a pane
|
|
191
|
+
drawerTools: function(drawer, node, kind) {
|
|
192
|
+
var animated = ['row', 'column', 'section', 'element'].indexOf(kind) !== -1 ||
|
|
193
|
+
!!$.fn.gridEditor.containers[kind];
|
|
194
|
+
if (!animated) { return; }
|
|
195
|
+
|
|
196
|
+
ge.createTool(drawer, ge.t('animation.tool'), 'my-animation-tool', 'bi bi-magic', function() {
|
|
197
|
+
open(node, kind, tool);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Drawers are built again on every init, so the tool
|
|
201
|
+
// reads the node rather than remembering anything
|
|
202
|
+
var tool = drawer.children('.my-animation-tool').last()
|
|
203
|
+
.toggleClass('my-animation-set', !!read(node));
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
})(jQuery);
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<script>
|
|
212
|
+
$(function() {
|
|
213
|
+
$('#animation-source').text($('#animation-plugin').text().trim().replace(/^ {8}/gm, ''));
|
|
214
|
+
|
|
215
|
+
$('#myGrid').gridEditor({
|
|
216
|
+
new_row_layouts: [[12], [6, 6], [4, 4, 4]],
|
|
217
|
+
content_types: ['tinymce'],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
$('#myGrid').on('grideditor:animation-change', function(e, payload) {
|
|
221
|
+
$('#log').text('The ' + payload.kind + ' now animates: ' +
|
|
222
|
+
(payload.to ? payload.to.effect : 'no'));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
$('#output').on('click', function() {
|
|
226
|
+
$('#html').text($('#myGrid').gridEditor('getPlainHtml'));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// The published page: the plain html, and a few lines of the
|
|
230
|
+
// host's own that read data-animation. Nothing of grid-editor.
|
|
231
|
+
$('#published').on('click', function() {
|
|
232
|
+
var html = $('#myGrid').gridEditor('getPlainHtml');
|
|
233
|
+
var page = window.open('', '_blank');
|
|
234
|
+
|
|
235
|
+
page.document.write(
|
|
236
|
+
'<!DOCTYPE html><html><head>' +
|
|
237
|
+
'<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">' +
|
|
238
|
+
'<style>' +
|
|
239
|
+
'[data-animation] { transition-property: opacity, transform; }' +
|
|
240
|
+
'.animate-waiting { opacity: 0; }' +
|
|
241
|
+
'.animate-waiting.animate-slide { transform: translateY(40px); }' +
|
|
242
|
+
'.animate-waiting.animate-zoom { transform: scale(.85); }' +
|
|
243
|
+
'body { padding-bottom: 60vh; }' +
|
|
244
|
+
'.scroll-hint { height: 100vh; }' +
|
|
245
|
+
'</style>' +
|
|
246
|
+
'</head><body>' +
|
|
247
|
+
'<div class="container scroll-hint d-flex align-items-center justify-content-center text-body-secondary">' +
|
|
248
|
+
'Scroll down: each node animates as it comes into view' +
|
|
249
|
+
'</div>' +
|
|
250
|
+
'<div class="container">' + html + '</div>' +
|
|
251
|
+
'<script>' + $('#runtime').text() + '<\/script>' +
|
|
252
|
+
'</body></html>'
|
|
253
|
+
);
|
|
254
|
+
page.document.close();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
</script>
|
|
258
|
+
|
|
259
|
+
<!-- What the published page runs: not grid-editor's, the host's -->
|
|
260
|
+
<script type="text/plain" id="runtime">
|
|
261
|
+
document.querySelectorAll('[data-animation]').forEach(function(node) {
|
|
262
|
+
var options = JSON.parse(node.getAttribute('data-animation'));
|
|
263
|
+
node.style.transitionDuration = options.duration + 'ms';
|
|
264
|
+
node.style.transitionDelay = options.delay + 'ms';
|
|
265
|
+
// Prefixed: Bootstrap has a .fade class of its own
|
|
266
|
+
node.classList.add('animate-waiting', 'animate-' + options.effect);
|
|
267
|
+
|
|
268
|
+
new IntersectionObserver(function(entries, observer) {
|
|
269
|
+
entries.forEach(function(entry) {
|
|
270
|
+
node.classList.toggle('animate-waiting', !entry.isIntersecting);
|
|
271
|
+
if (entry.isIntersecting && options.once) { observer.disconnect(); }
|
|
272
|
+
});
|
|
273
|
+
}, { threshold: 0.2 }).observe(node);
|
|
274
|
+
});
|
|
275
|
+
</script>
|
|
276
|
+
|
|
277
|
+
<title>Grid Editor example - a plugin with a modal</title>
|
|
278
|
+
</head>
|
|
279
|
+
<body>
|
|
280
|
+
<!-- Back to the demo index -->
|
|
281
|
+
<div class="container pt-3">
|
|
282
|
+
<a class="link-secondary" href="index.html"><i class="bi bi-arrow-left"></i> All demos</a>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<div class="container">
|
|
286
|
+
|
|
287
|
+
<div class="row my-3">
|
|
288
|
+
<div class="col-lg-9">
|
|
289
|
+
<h1 class="h4">A plugin with a modal</h1>
|
|
290
|
+
<p class="form-text">
|
|
291
|
+
The <i class="bi bi-magic"></i> tool in the drawer of a row,
|
|
292
|
+
a column, a card or an element is a plugin written on this
|
|
293
|
+
page. It opens a modal, and what you choose there is saved
|
|
294
|
+
on the node as a <code>data-animation</code> attribute, which
|
|
295
|
+
<code>getPlainHtml</code> keeps: only <code>data-ge-*</code>
|
|
296
|
+
attributes are the editor’s to take off. A node with
|
|
297
|
+
an animation has its tool highlighted.
|
|
298
|
+
</p>
|
|
299
|
+
<p class="form-text">
|
|
300
|
+
The quote is an element, inside text tinyMCE edits. Open the
|
|
301
|
+
editor there, set an animation on the quote, keep typing
|
|
302
|
+
and press <kbd>Ctrl</kbd>+<kbd>Z</kbd>: the text goes, the
|
|
303
|
+
animation stays, because the plugin wrote it through
|
|
304
|
+
tinyMCE’s undo manager. Written behind its back,
|
|
305
|
+
undoing the typing would undo the attribute with it.
|
|
306
|
+
</p>
|
|
307
|
+
<p class="form-text">
|
|
308
|
+
<em>Open as a published page</em> puts the plain html in a
|
|
309
|
+
page of its own, with a few lines that read the attribute
|
|
310
|
+
and animate each node as it scrolls into view.
|
|
311
|
+
<span id="log" class="fw-semibold"></span>
|
|
312
|
+
</p>
|
|
313
|
+
<button class="btn btn-sm btn-secondary" id="output" type="button">Show getPlainHtml</button>
|
|
314
|
+
<button class="btn btn-sm btn-primary" id="published" type="button">Open as a published page</button>
|
|
315
|
+
<pre id="html" class="form-text"></pre>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div id="myGrid">
|
|
320
|
+
<div class="row" data-animation='{"effect":"fade","duration":800,"delay":0,"once":true}'>
|
|
321
|
+
<div class="col-lg-12">
|
|
322
|
+
<h2>This row fades in</h2>
|
|
323
|
+
<p>It came with its animation already set, in the markup the editor was started on.</p>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
<div class="row">
|
|
327
|
+
<div class="col-lg-6">
|
|
328
|
+
<p>Ordinary text, edited by tinyMCE.</p>
|
|
329
|
+
<blockquote class="blockquote" data-ge-element="quote" data-ge-label="Quote">
|
|
330
|
+
<p>An element: give it an animation while you edit the text around it.</p>
|
|
331
|
+
</blockquote>
|
|
332
|
+
<p>More text, after the element.</p>
|
|
333
|
+
</div>
|
|
334
|
+
<div class="col-lg-6">
|
|
335
|
+
<div data-ge-container="card">
|
|
336
|
+
<div class="card">
|
|
337
|
+
<div class="card-header">A card</div>
|
|
338
|
+
<div class="card-body">
|
|
339
|
+
<div class="row"><div class="col-lg-12">
|
|
340
|
+
<p>Containers take the tool too.</p>
|
|
341
|
+
</div></div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<div class="row my-4">
|
|
350
|
+
<div class="col-lg-10">
|
|
351
|
+
<h2 class="h5">The plugin, in full</h2>
|
|
352
|
+
<p class="form-text">
|
|
353
|
+
A feature plugin: <code>drawerTools</code> puts a tool in
|
|
354
|
+
every drawer that has a gear, and the kind says whose drawer
|
|
355
|
+
it is. See <a href="../docs/plugins.md">docs/plugins.md</a>.
|
|
356
|
+
</p>
|
|
357
|
+
<pre class="border rounded p-3 bg-light"><code id="animation-source"></code></pre>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
</body>
|
|
364
|
+
</html>
|