@themarioga/grid-editor 7.1.0 → 7.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.
@@ -10,8 +10,9 @@
10
10
  <!-- Javascript dependencies -->
11
11
  <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>
12
12
 
13
- <!-- Ckeditor js -->
14
- <script src="https://cdnjs.cloudflare.com/ajax/libs/ckeditor/4.22.1/ckeditor.js"></script>
13
+ <!-- CKEditor 5: its browser build, and its stylesheet -->
14
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ckeditor5@48.5.2/dist/browser/ckeditor5.css">
15
+ <script src="https://cdn.jsdelivr.net/npm/ckeditor5@48.5.2/dist/browser/ckeditor5.umd.js"></script>
15
16
 
16
17
  <!-- Grid editor CSS -->
17
18
  <link rel="stylesheet" type="text/css" href="../dist/grideditor.css" />
@@ -28,11 +29,15 @@
28
29
  new_row_layouts: [[12], [6, 6], [9, 3]],
29
30
  content_types: ['ckeditor'],
30
31
  ckeditor: {
32
+ // Anything InlineEditor.create takes, over the plugin's
33
+ // own defaults: its plugins, toolbar and headings
31
34
  config: {
35
+ // CKEditor 5 is GPL-2.0-or-later: 'GPL' accepts that
36
+ // licence. A commercial licence passes its own key.
37
+ licenseKey: 'GPL',
32
38
  on: {
33
39
  instanceReady: function(evt) {
34
- var instance = this;
35
- console.log('instance ready', evt, instance);
40
+ console.log('instance ready', evt.editor);
36
41
  }
37
42
  }
38
43
  }
@@ -144,7 +144,7 @@
144
144
  <strong>CKEditor</strong>
145
145
  <code class="text-body-secondary">ckeditor.html</code>
146
146
  </div>
147
- <span class="text-body-secondary">CKEditor in the content areas instead of tinyMCE</span>
147
+ <span class="text-body-secondary">CKEditor 5 in the content areas instead of tinyMCE</span>
148
148
  </a>
149
149
 
150
150
  <a class="list-group-item list-group-item-action" href="summernote.html">
package/package.json CHANGED
@@ -33,7 +33,7 @@
33
33
  "comment": "A scoped package publishes restricted unless it is told otherwise.",
34
34
  "access": "public"
35
35
  },
36
- "version": "7.1.0",
36
+ "version": "7.2.0",
37
37
  "main": "dist/grideditor.js",
38
38
  "module": "dist/grideditor.esm.js",
39
39
  "types": "dist/grideditor.d.ts",
@@ -5078,7 +5078,7 @@ GridEditor.locales = {
5078
5078
  'warning.plugin_6x': 'The "{name}" plugin is written for grid-editor 6 and is not loaded. Plugins register on GridEditor since 7.0: see UPGRADING.md.',
5079
5079
  'warning.adapter_no_jquery': 'grideditor.jquery.js needs jQuery 4, and there is no jQuery on the page: the jQuery API is not there.',
5080
5080
  'error.tinymce_missing': 'tinyMCE not available! Make sure you loaded the tinyMCE js file.',
5081
- 'error.ckeditor_missing': 'CKEditor not available! Make sure you loaded the CKEditor js file.',
5081
+ 'error.ckeditor_missing': 'CKEditor 5 not available! Make sure you loaded its ckeditor5.umd.js file: CKEditor 4 is no longer supported.',
5082
5082
  'error.summernote_missing': 'Summernote not available! Make sure you loaded jQuery and the Summernote js file.',
5083
5083
  },
5084
5084
  };
@@ -169,7 +169,9 @@ GridEditor.locales.es = {
169
169
  'warning.adapter_no_jquery': 'grideditor.jquery.js necesita jQuery 4 y la página no tiene jQuery: la API de jQuery no está disponible.',
170
170
  'warning.oninit_removed': 'La opción oninit de tinyMCE se eliminó en la 7.0 y se ignora: usa init_instance_callback.',
171
171
  'error.tinymce_missing': '¡tinyMCE no está disponible! Asegúrate de haber cargado el archivo js de tinyMCE.',
172
- 'error.ckeditor_missing': '¡CKEditor no está disponible! Asegúrate de haber cargado el archivo js de CKEditor.',
172
+ 'error.ckeditor_missing': '¡CKEditor 5 no está disponible! Asegúrate de haber cargado su archivo ckeditor5.umd.js: CKEditor 4 ya no está soportado.',
173
+ 'error.ckeditor_start': 'CKEditor no ha podido arrancar: {message}',
174
+ 'warning.ckeditor_plugin': 'CKEditor no tiene ningún plugin llamado {name}, así que se deja fuera.',
173
175
  'error.codemirror_missing': '¡CodeMirror no está disponible! Asegúrate de haber cargado el archivo js de CodeMirror; sin él el código se edita en un textarea normal.',
174
176
  'error.summernote_missing': '¡Summernote no está disponible! Asegúrate de haber cargado jQuery y el archivo js de Summernote.',
175
177
  };
@@ -1,15 +1,20 @@
1
1
  /**
2
2
  * CKEditor for grid-editor's content areas.
3
3
  *
4
- * A text editor plugin: load this file after the editor, and CKEditor 4
5
- * after or before it, and a text of the ckeditor type is edited with an
6
- * inline CKEditor. What it can ask the editor for is the handle its
7
- * factory is called with, described in docs/plugins.md.
4
+ * A text editor plugin: load this file after the editor, and CKEditor 5's
5
+ * browser build after or before it, and a text of the ckeditor type is
6
+ * edited with an inline CKEditor. What it can ask the editor for is the
7
+ * handle its factory is called with, described in docs/plugins.md.
8
8
  *
9
- * <script src="ckeditor/ckeditor.js"></script>
9
+ * <link rel="stylesheet" href="ckeditor5/ckeditor5.css">
10
+ * <script src="ckeditor5/ckeditor5.umd.js"></script>
10
11
  * <script src="dist/grideditor.min.js"></script>
11
12
  * <script src="dist/plugins/grideditor.ckeditor.min.js"></script>
12
13
  *
14
+ * Up to 7.1 this was CKEditor 4, whose last open source release has known
15
+ * vulnerabilities and no fixes to come. CKEditor 5 asks for a licenseKey:
16
+ * 'GPL', or a commercial key, is the host's to pass in ckeditor.config.
17
+ *
13
18
  * Up to 5.x the main bundle carried a copy of this file; since 6.0 it is
14
19
  * loaded on its own, like every plugin. It imports what every text editor
15
20
  * shares - text blocks, the Text button, createText, making the host's plain
@@ -22,31 +27,77 @@ import '../text/grideditor.text.js';
22
27
 
23
28
  Object.assign(GridEditor.locales.en, {
24
29
  'text.ckeditor': 'CKEditor',
30
+ 'error.ckeditor_start': 'CKEditor could not start: {message}',
31
+ 'warning.ckeditor_plugin': 'CKEditor has no plugin called {name}, so it is left out.',
25
32
  });
26
33
 
27
34
  var INITIAL_CONTENT = '<p>Lorem initius... </p>';
28
35
 
29
- // The instance each content area is edited with, as this file made it
30
- var instances = new WeakMap();
36
+ // What CKEditor 5 is started with when the host's config does not say. It
37
+ // has no plugins of its own, so these are what a text can do: much what the
38
+ // CKEditor 4 build 7.1 used offered. Names, looked up on window.CKEDITOR.
39
+ var PLUGINS = [
40
+ 'Essentials', 'Autoformat', 'Paragraph', 'Heading', 'Bold', 'Italic', 'Underline', 'Strikethrough',
41
+ 'Link', 'AutoLink', 'List', 'BlockQuote', 'Indent', 'HorizontalLine', 'Table', 'TableToolbar',
42
+ 'PasteFromOffice',
43
+ ];
44
+
45
+ var DEFAULTS = {
46
+ toolbar: [
47
+ 'undo', 'redo', '|', 'heading', '|', 'bold', 'italic', 'underline', 'strikethrough', '|',
48
+ 'link', 'bulletedList', 'numberedList', 'blockQuote', '|', 'insertTable', 'horizontalLine',
49
+ ],
50
+ // CKEditor's own headings start at h2, and make every h1 in the content
51
+ // an h2: a column's text keeps the headings it has
52
+ heading: {
53
+ options: [
54
+ { model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
55
+ ].concat([1, 2, 3, 4, 5, 6].map(function(level) {
56
+ return {
57
+ model: 'heading' + level, view: 'h' + level,
58
+ title: 'Heading ' + level, class: 'ck-heading_heading' + level,
59
+ };
60
+ })),
61
+ },
62
+ table: { contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells'] },
63
+ };
64
+
65
+ // What this file keeps about a content area: its editor once it is ready,
66
+ // the start that is still to have one, and a promise of the content area
67
+ // being free again, with no editor starting or being destroyed on it:
68
+ // CKEditor does both asynchronously, and two at once on one element clash
69
+ var editors = new WeakMap();
70
+ var starting = new WeakMap();
71
+ var busy = new WeakMap();
72
+
73
+ /** The plugins the config names, as CKEditor wants them: constructors. */
74
+ function pluginsFor(ge, names) {
75
+ return names.map(function(plugin) {
76
+ if (typeof plugin !== 'string') { return plugin; }
77
+ if (!window.CKEDITOR[plugin]) { ge.warn(ge.t('warning.ckeditor_plugin', { name: plugin })); }
78
+ return window.CKEDITOR[plugin];
79
+ }).filter(Boolean);
80
+ }
31
81
 
32
82
  GridEditor.texts.ckeditor = function(ge) {
33
83
 
34
84
  /**
35
- * The instance editing this content area. Kept when it is made, and
36
- * looked for among CKEditor's own as a fallback, since an instance made
37
- * by another copy of this file is not in this one's record.
85
+ * Take the editor off a content area and leave its content behind, now:
86
+ * getHtml reads the content area the moment its editor is stopped.
87
+ *
88
+ * CKEditor puts the attributes back as destroy() is called, but its
89
+ * content only as it finishes, and then as nothing: it empties the
90
+ * element unless told otherwise. So the content goes back in twice, now
91
+ * and once CKEditor is done. The promise is of that.
38
92
  */
39
- function instanceOf(contentArea) {
40
- var kept = instances.get(contentArea);
41
- if (kept) { return kept; }
42
-
43
- var found = null;
44
- Object.keys(window.CKEDITOR.instances).forEach(function(name) {
45
- var instance = window.CKEDITOR.instances[name];
46
- if (instance.element && instance.element.$ === contentArea) { found = instance; }
93
+ function destroy(contentArea, editor) {
94
+ var data = editor.getData();
95
+ var done = editor.destroy().catch(function() {}).then(function() {
96
+ contentArea.innerHTML = data;
47
97
  });
48
98
 
49
- return found;
99
+ contentArea.innerHTML = data;
100
+ return done;
50
101
  }
51
102
 
52
103
  return {
@@ -54,53 +105,67 @@ GridEditor.texts.ckeditor = function(ge) {
54
105
  initialContent: INITIAL_CONTENT,
55
106
  missingKey: 'error.ckeditor_missing',
56
107
 
57
- available: function() { return !!window.CKEDITOR; },
108
+ // CKEditor 4 is window.CKEDITOR too, without an InlineEditor
109
+ available: function() { return !!(window.CKEDITOR && window.CKEDITOR.InlineEditor); },
58
110
 
59
111
  start: function(contentAreas) {
60
112
  var settings = ge.settings;
113
+ var userConfig = (settings.ckeditor && settings.ckeditor.config) ? settings.ckeditor.config : {};
61
114
 
62
115
  contentAreas.forEach(function(contentArea) {
63
116
  if (dom.hasClass(contentArea, 'active')) { return; }
64
117
 
65
118
  if (contentArea.innerHTML == INITIAL_CONTENT) {
66
- // CKEditor kills this '&nbsp' creating a non usable box :/
67
- contentArea.innerHTML = '&nbsp;';
119
+ contentArea.innerHTML = '';
68
120
  }
69
-
70
- // contenteditable="true", or CKEditor loads readonly
71
121
  dom.addClass(contentArea, 'active');
72
- contentArea.setAttribute('contenteditable', 'true');
73
-
74
- var configuration = Object.assign(
75
- {},
76
- (settings.ckeditor && settings.ckeditor.config ? settings.ckeditor.config : {}),
77
- {
78
- // Focus editor on creation
79
- on: {
80
- instanceReady: function( evt ) {
81
- // Call original instanceReady function, if one was passed in the config
82
- var callback;
83
- try {
84
- callback = settings.ckeditor.config.on.instanceReady;
85
- } catch (err) {
86
- // No callback passed
87
- }
88
- if (callback) {
89
- callback.call(this, evt);
90
- }
91
-
92
- // The editor owns what is inside the
93
- // content area now, so the grid editor is
94
- // told to put its own furniture back
95
- ge.textReady(contentArea);
96
-
97
- instance.focus();
98
- }
99
- }
122
+
123
+ var configuration = Object.assign({}, DEFAULTS, userConfig);
124
+ configuration.plugins = pluginsFor(ge, userConfig.plugins || PLUGINS);
125
+ // on.instanceReady is CKEditor 4's, and CKEditor 5 has no
126
+ // such option: this file calls it, as it did in 7.1
127
+ delete configuration.on;
128
+
129
+ var ticket = {};
130
+ starting.set(contentArea, ticket);
131
+
132
+ var run = (busy.get(contentArea) || Promise.resolve()).then(function() {
133
+ // Stopped again while it waited for the last editor to go
134
+ if (starting.get(contentArea) !== ticket) { return null; }
135
+
136
+ return window.CKEDITOR.InlineEditor.create(contentArea, configuration);
137
+ }).then(function(editor) {
138
+ if (!editor) { return null; }
139
+
140
+ // Stopped while it started, so it goes again
141
+ if (starting.get(contentArea) !== ticket) {
142
+ return destroy(contentArea, editor);
143
+ }
144
+ starting.delete(contentArea);
145
+ editors.set(contentArea, editor);
146
+
147
+ // Call the original instanceReady function, if one was
148
+ // passed in the config: 7.1's, with CKEditor 4's event
149
+ var callback = userConfig.on && userConfig.on.instanceReady;
150
+ if (callback) {
151
+ callback.call(editor, { editor: editor });
152
+ }
153
+
154
+ // The editor owns what is inside the content area now,
155
+ // so the grid editor is told to put its own furniture back
156
+ ge.textReady(contentArea);
157
+
158
+ editor.editing.view.focus();
159
+ return null;
160
+ }).catch(function(error) {
161
+ // A licenseKey missing is the likely one
162
+ if (starting.get(contentArea) === ticket) {
163
+ starting.delete(contentArea);
164
+ dom.removeClass(contentArea, 'active');
100
165
  }
101
- );
102
- var instance = window.CKEDITOR.inline(contentArea, configuration);
103
- instances.set(contentArea, instance);
166
+ console.error(ge.t('error.ckeditor_start', { message: (error && error.message) || error }));
167
+ });
168
+ busy.set(contentArea, run);
104
169
  });
105
170
  },
106
171
 
@@ -108,18 +173,17 @@ GridEditor.texts.ckeditor = function(ge) {
108
173
  contentAreas.filter(function(contentArea) {
109
174
  return dom.hasClass(contentArea, 'active');
110
175
  }).forEach(function(contentArea) {
111
- // This content area's instance, and no other: up to 5.x
176
+ // This content area's editor, and no other: up to 5.x
112
177
  // closing one content area destroyed every CKEditor on
113
178
  // the page, other editors' and the host's own included
114
- var instance = window.CKEDITOR ? instanceOf(contentArea) : null;
115
- if (instance) { instance.destroy(); }
116
- instances.delete(contentArea);
117
-
118
- // Cleanup
119
- dom.removeClass(contentArea, 'active cke_focus');
120
- ['id', 'style', 'spellcheck', 'contenteditable'].forEach(function(name) {
121
- contentArea.removeAttribute(name);
122
- });
179
+ var editor = editors.get(contentArea);
180
+ editors.delete(contentArea);
181
+ // One still starting sees this and goes as it is ready
182
+ starting.delete(contentArea);
183
+
184
+ if (editor) { busy.set(contentArea, destroy(contentArea, editor)); }
185
+
186
+ dom.removeClass(contentArea, 'active');
123
187
  });
124
188
  },
125
189
  };
@@ -40,7 +40,7 @@ if (!GridEditor.features.text) {
40
40
  var readReady = new WeakMap();
41
41
 
42
42
  /** What a text editor puts on a content area while it is open, whenever it likes. */
43
- var EDITOR_CLASS = /^(mce-|cke|note-)|^(active|ge-rte-active)$/;
43
+ var EDITOR_CLASS = /^(mce-|cke|ck-|note-)|^(ck|active|ge-rte-active)$/;
44
44
  var EDITOR_ATTRIBUTE = /^(data-mce-|contenteditable$|spellcheck$)/;
45
45
 
46
46
  function attributesOf(element) {