ckeditor5 1.8.0 → 1.10.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.
- checksums.yaml +4 -4
- data/README.md +82 -7
- data/lib/ckeditor5/rails/assets/assets_bundle_html_serializer.rb +3 -3
- data/lib/ckeditor5/rails/assets/webcomponent_bundle.rb +22 -0
- data/lib/ckeditor5/rails/assets/webcomponents/components/context.mjs +113 -0
- data/lib/ckeditor5/rails/assets/webcomponents/components/editable.mjs +113 -0
- data/lib/ckeditor5/rails/assets/{webcomponent.mjs → webcomponents/components/editor.mjs} +164 -349
- data/lib/ckeditor5/rails/assets/webcomponents/components/ui-part.mjs +26 -0
- data/lib/ckeditor5/rails/assets/webcomponents/utils.mjs +155 -0
- data/lib/ckeditor5/rails/cdn/helpers.rb +3 -11
- data/lib/ckeditor5/rails/context/helpers.rb +13 -0
- data/lib/ckeditor5/rails/context/props.rb +30 -0
- data/lib/ckeditor5/rails/editor/helpers.rb +7 -14
- data/lib/ckeditor5/rails/helpers.rb +2 -2
- data/lib/ckeditor5/rails/presets/preset_builder.rb +17 -1
- data/lib/ckeditor5/rails/version.rb +1 -1
- metadata +10 -4
- data/lib/ckeditor5/rails/cloud/helpers.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3e06c4a4882429855d9570658be64f10fffd97371d0ae60e9aee2ecec08df00
|
4
|
+
data.tar.gz: f48eabec0f1bcab9299be0f00094f2e1078a757582a705e57d504748943b5790
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c989f7723dadc126c356b579f50160b09f3c359af8a791a919bcff0595c7387241f89f5a63a100c482878d0f067c094f45619628094d6a8c0659d797d75cb514
|
7
|
+
data.tar.gz: a411bbde271725fbaeddfc58023307837ffac5e26d4814ed13e922288846129973a2b297681076b480982485a91ed4b5c527056cf2b9b799cfbb321a5ed83762
|
data/README.md
CHANGED
@@ -101,6 +101,9 @@ Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
|
|
101
101
|
- [Inline editor 📝](#inline-editor-)
|
102
102
|
- [Balloon editor 🎈](#balloon-editor-)
|
103
103
|
- [Decoupled editor 🌐](#decoupled-editor-)
|
104
|
+
- [Using Context 📦](#using-context-)
|
105
|
+
- [Using Context in CKEditor 5 🔄](#using-context-in-ckeditor-5-)
|
106
|
+
- [Example usage of `ckeditor5_context` helper 📝](#example-usage-of-ckeditor5_context-helper-)
|
104
107
|
- [How to access editor instance? 🤔](#how-to-access-editor-instance-)
|
105
108
|
- [Common Tasks and Solutions 💡](#common-tasks-and-solutions-)
|
106
109
|
- [Setting Editor Language 🌐](#setting-editor-language-)
|
@@ -112,6 +115,8 @@ Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
|
|
112
115
|
- [Events fired by the editor 🔊](#events-fired-by-the-editor-)
|
113
116
|
- [`editor-ready` event](#editor-ready-event)
|
114
117
|
- [`editor-error` event](#editor-error-event)
|
118
|
+
- [`editor-change` event](#editor-change-event)
|
119
|
+
- [Inline event handling](#inline-event-handling)
|
115
120
|
- [Trademarks 📜](#trademarks-)
|
116
121
|
- [License 📜](#license-)
|
117
122
|
|
@@ -184,9 +189,6 @@ Configuration of the editor can be complex, and it's recommended to use the [CKE
|
|
184
189
|
|
185
190
|
### Available Configuration Methods ⚙️
|
186
191
|
|
187
|
-
<details>
|
188
|
-
<summary>Expand to show available methods 📖</summary>
|
189
|
-
|
190
192
|
#### `cdn(cdn = nil, &block)` method
|
191
193
|
|
192
194
|
Defines the CDN to be used for CKEditor 5 assets. The example below shows how to set the CDN to `:jsdelivr`:
|
@@ -569,8 +571,6 @@ CKEditor5::Rails.configure do
|
|
569
571
|
end
|
570
572
|
```
|
571
573
|
|
572
|
-
</details>
|
573
|
-
|
574
574
|
## Including CKEditor 5 assets 📦
|
575
575
|
|
576
576
|
To include CKEditor 5 assets in your application, you can use the `ckeditor5_assets` helper method. This method takes the version of CKEditor 5 as an argument and includes the necessary resources of the editor. Depending on the specified configuration, it includes the JS and CSS assets from the official CKEditor 5 CDN or one of the popular CDNs.
|
@@ -965,6 +965,45 @@ If you want to use a decoupled editor, you can pass the `type` keyword argument
|
|
965
965
|
<% end %>
|
966
966
|
```
|
967
967
|
|
968
|
+
## Using Context 📦
|
969
|
+
|
970
|
+
Context CKEditor 5 is a feature that allows multiple editor instances to share a common configuration and state. This is particularly useful in collaborative environments where multiple users are editing different parts of the same document simultaneously. By using a shared context, all editor instances can synchronize their configurations, plugins, and other settings, ensuring a consistent editing experience across all users.
|
971
|
+
|
972
|
+

|
973
|
+
|
974
|
+
### Using Context in CKEditor 5 🔄
|
975
|
+
|
976
|
+
Format of the `ckeditor5_context` helper:
|
977
|
+
|
978
|
+
```erb
|
979
|
+
<!-- app/views/demos/index.html.erb -->
|
980
|
+
|
981
|
+
<%= ckeditor5_context config: { ... }, plugins: [ ... ] do %>
|
982
|
+
<%= ckeditor5_editor %>
|
983
|
+
<%= ckeditor5_editor %>
|
984
|
+
<% end %>
|
985
|
+
```
|
986
|
+
|
987
|
+
The `ckeditor5_context` helper takes the `config` and `plugins` keyword arguments. The `config` keyword argument allows you to define the shared configuration of the editor instances, while the `plugins` keyword argument allows you to define the shared plugins. Format of these arguments is the same as in the `ckeditor5_editor` helper.
|
988
|
+
|
989
|
+
### Example usage of `ckeditor5_context` helper 📝
|
990
|
+
|
991
|
+
```erb
|
992
|
+
<!-- app/views/demos/index.html.erb -->
|
993
|
+
|
994
|
+
<% content_for :head do %>
|
995
|
+
<%= ckeditor5_assets preset: :ultrabasic %>
|
996
|
+
<% end %>
|
997
|
+
|
998
|
+
<%= ckeditor5_context do %>
|
999
|
+
<%= ckeditor5_editor initial_data: 'Hello World' %>
|
1000
|
+
|
1001
|
+
<br>
|
1002
|
+
|
1003
|
+
<%= ckeditor5_editor initial_data: 'Hello World 2' %>
|
1004
|
+
<% end %>
|
1005
|
+
```
|
1006
|
+
|
968
1007
|
## How to access editor instance? 🤔
|
969
1008
|
|
970
1009
|
You can access the editor instance using plain HTML and JavaScript, as CKEditor 5 is a web component with defined `instance`, `instancePromise` and `editables` properties.
|
@@ -1222,6 +1261,8 @@ class HighlightCommand extends Command {
|
|
1222
1261
|
|
1223
1262
|
## Events fired by the editor 🔊
|
1224
1263
|
|
1264
|
+
CKEditor 5 provides a set of events that you can listen to in order to react to changes in the editor. You can listen to these events using the `addEventListener` method or by defining event handlers directly in the view.
|
1265
|
+
|
1225
1266
|
### `editor-ready` event
|
1226
1267
|
|
1227
1268
|
The event is fired when the initialization of the editor is completed. You can listen to it using the `editor-ready` event.
|
@@ -1242,6 +1283,40 @@ document.getElementById('editor').addEventListener('editor-error', () => {
|
|
1242
1283
|
});
|
1243
1284
|
```
|
1244
1285
|
|
1286
|
+
### `editor-change` event
|
1287
|
+
|
1288
|
+
The event is fired when the content of the editor changes. You can listen to it using the `editor-change` event.
|
1289
|
+
|
1290
|
+
```js
|
1291
|
+
document.getElementById('editor').addEventListener('editor-change', () => {
|
1292
|
+
console.log('Editor content has changed');
|
1293
|
+
});
|
1294
|
+
```
|
1295
|
+
|
1296
|
+
### Inline event handling
|
1297
|
+
|
1298
|
+
You can also define event handlers directly in the view using the `oneditorchange`, `oneditorerror`, and `oneditorready` attributes.
|
1299
|
+
|
1300
|
+
```erb
|
1301
|
+
<!-- app/views/demos/index.html.erb -->
|
1302
|
+
|
1303
|
+
<script type="text/javascript">
|
1304
|
+
function onEditorChange(event) {
|
1305
|
+
// event.detail.editor, event.detail.data
|
1306
|
+
}
|
1307
|
+
|
1308
|
+
function onEditorError(event) {
|
1309
|
+
// event.detail.editor
|
1310
|
+
}
|
1311
|
+
|
1312
|
+
function onEditorReady(event) {
|
1313
|
+
// event.detail.editor
|
1314
|
+
}
|
1315
|
+
</script>
|
1316
|
+
|
1317
|
+
<%= ckeditor5_editor style: 'width: 600px', id: 'editor', oneditorchange: 'onEditorChange', oneditorerror: 'onEditorError', oneditorready: 'onEditorReady' %>
|
1318
|
+
```
|
1319
|
+
|
1245
1320
|
## Trademarks 📜
|
1246
1321
|
|
1247
1322
|
CKEditor® is a trademark of [CKSource Holding sp. z o.o.](https://cksource.com/) All rights reserved. For more information about the license of CKEditor® please visit [CKEditor's licensing page](https://ckeditor.com/legal/ckeditor-oss-license/).
|
@@ -1250,6 +1325,6 @@ This gem is not owned by CKSource and does not use the CKEditor® trademark for
|
|
1250
1325
|
|
1251
1326
|
## License 📜
|
1252
1327
|
|
1253
|
-
This project is licensed under the terms of the [GNU General Public License v2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). See the [LICENSE](LICENSE) file for details.
|
1328
|
+
This project is licensed under the terms of the [GNU General Public License v2.0 or later](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). See the [LICENSE](LICENSE) file for details.
|
1254
1329
|
|
1255
|
-
This project uses CKEditor 5 which is licensed under the terms of [GNU General Public License Version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html). For more information about CKEditor 5 licensing, please see their [official documentation](https://ckeditor.com/legal/ckeditor-oss-license/).
|
1330
|
+
This project uses CKEditor 5 which is licensed under the terms of [GNU General Public License Version 2 or later](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html). For more information about CKEditor 5 licensing, please see their [official documentation](https://ckeditor.com/legal/ckeditor-oss-license/).
|
@@ -3,9 +3,9 @@
|
|
3
3
|
require 'uri'
|
4
4
|
require 'action_view'
|
5
5
|
|
6
|
-
|
7
|
-
WEBCOMPONENT_SOURCE = File.read(File.join(__dir__, 'webcomponent.mjs')).html_safe
|
6
|
+
require_relative 'webcomponent_bundle'
|
8
7
|
|
8
|
+
module CKEditor5::Rails::Assets
|
9
9
|
class AssetsBundleHtmlSerializer
|
10
10
|
include ActionView::Helpers::TagHelper
|
11
11
|
|
@@ -38,7 +38,7 @@ module CKEditor5::Rails::Assets
|
|
38
38
|
private
|
39
39
|
|
40
40
|
def web_component_tag
|
41
|
-
@web_component_tag ||= tag.script(
|
41
|
+
@web_component_tag ||= tag.script(WebComponentBundle.source, type: 'module', nonce: true)
|
42
42
|
end
|
43
43
|
|
44
44
|
def window_scripts_tags
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CKEditor5::Rails::Assets
|
4
|
+
module WebComponentBundle
|
5
|
+
WEBCOMPONENTS_PATH = File.join(__dir__, 'webcomponents')
|
6
|
+
WEBCOMPONENTS_MODULES = [
|
7
|
+
'utils.mjs',
|
8
|
+
'components/editable.mjs',
|
9
|
+
'components/ui-part.mjs',
|
10
|
+
'components/editor.mjs',
|
11
|
+
'components/context.mjs'
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def source
|
17
|
+
@source ||= WEBCOMPONENTS_MODULES.map do |file|
|
18
|
+
File.read(File.join(WEBCOMPONENTS_PATH, file))
|
19
|
+
end.join("\n").html_safe
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class CKEditorContextComponent extends HTMLElement {
|
2
|
+
static get observedAttributes() {
|
3
|
+
return ['plugins', 'config'];
|
4
|
+
}
|
5
|
+
|
6
|
+
/** @type {import('ckeditor5').Context|null} */
|
7
|
+
instance = null;
|
8
|
+
|
9
|
+
/** @type {Promise<import('ckeditor5').Context>} */
|
10
|
+
instancePromise = Promise.withResolvers();
|
11
|
+
|
12
|
+
/** @type {Set<CKEditorComponent>} */
|
13
|
+
#connectedEditors = new Set();
|
14
|
+
|
15
|
+
async connectedCallback() {
|
16
|
+
try {
|
17
|
+
execIfDOMReady(() => this.#initializeContext());
|
18
|
+
} catch (error) {
|
19
|
+
console.error('Failed to initialize context:', error);
|
20
|
+
this.dispatchEvent(new CustomEvent('context-error', { detail: error }));
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
async attributeChangedCallback(name, oldValue, newValue) {
|
25
|
+
if (oldValue !== null && oldValue !== newValue) {
|
26
|
+
await this.#initializeContext();
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
async disconnectedCallback() {
|
31
|
+
if (this.instance) {
|
32
|
+
await this.instance.destroy();
|
33
|
+
this.instance = null;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Register editor component with this context
|
39
|
+
*
|
40
|
+
* @param {CKEditorComponent} editor
|
41
|
+
*/
|
42
|
+
registerEditor(editor) {
|
43
|
+
this.#connectedEditors.add(editor);
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Unregister editor component from this context
|
48
|
+
*
|
49
|
+
* @param {CKEditorComponent} editor
|
50
|
+
*/
|
51
|
+
unregisterEditor(editor) {
|
52
|
+
this.#connectedEditors.delete(editor);
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Initialize CKEditor context with shared configuration
|
57
|
+
*
|
58
|
+
* @private
|
59
|
+
*/
|
60
|
+
async #initializeContext() {
|
61
|
+
if (this.instance) {
|
62
|
+
this.instancePromise = Promise.withResolvers();
|
63
|
+
|
64
|
+
await this.instance.destroy();
|
65
|
+
|
66
|
+
this.instance = null;
|
67
|
+
}
|
68
|
+
|
69
|
+
const { Context, ContextWatchdog } = await import('ckeditor5');
|
70
|
+
const plugins = await this.#getPlugins();
|
71
|
+
const config = this.#getConfig();
|
72
|
+
|
73
|
+
this.instance = new ContextWatchdog(Context, {
|
74
|
+
crashNumberLimit: 10
|
75
|
+
});
|
76
|
+
|
77
|
+
await this.instance.create({
|
78
|
+
...config,
|
79
|
+
plugins
|
80
|
+
});
|
81
|
+
|
82
|
+
this.instance.on('itemError', (...args) => {
|
83
|
+
console.error('Context item error:', ...args);
|
84
|
+
});
|
85
|
+
|
86
|
+
this.instancePromise.resolve(this.instance);
|
87
|
+
this.dispatchEvent(new CustomEvent('context-ready', { detail: this.instance }));
|
88
|
+
|
89
|
+
// Reinitialize connected editors.
|
90
|
+
await Promise.all(
|
91
|
+
[...this.#connectedEditors].map(editor => editor.reinitializeEditor())
|
92
|
+
);
|
93
|
+
}
|
94
|
+
|
95
|
+
async #getPlugins() {
|
96
|
+
const raw = this.getAttribute('plugins');
|
97
|
+
|
98
|
+
return loadAsyncImports(raw ? JSON.parse(raw) : []);
|
99
|
+
}
|
100
|
+
|
101
|
+
/**
|
102
|
+
* Gets context configuration with resolved element references.
|
103
|
+
*
|
104
|
+
* @private
|
105
|
+
*/
|
106
|
+
#getConfig() {
|
107
|
+
const config = JSON.parse(this.getAttribute('config') || '{}');
|
108
|
+
|
109
|
+
return resolveElementReferences(config);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
customElements.define('ckeditor-context-component', CKEditorContextComponent);
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class CKEditorEditableComponent extends HTMLElement {
|
2
|
+
/**
|
3
|
+
* List of attributes that trigger updates when changed
|
4
|
+
*
|
5
|
+
* @static
|
6
|
+
* @returns {string[]} Array of attribute names to observe
|
7
|
+
*/
|
8
|
+
static get observedAttributes() {
|
9
|
+
return ['name'];
|
10
|
+
}
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Gets the name of this editable region
|
14
|
+
*
|
15
|
+
* @returns {string} The name attribute value
|
16
|
+
*/
|
17
|
+
get name() {
|
18
|
+
// The default value is set mainly for decoupled editors where the name is not required.
|
19
|
+
return this.getAttribute('name') || 'editable';
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Gets the actual editable DOM element
|
24
|
+
* @returns {HTMLDivElement|null} The div element containing editable content
|
25
|
+
*/
|
26
|
+
get editableElement() {
|
27
|
+
return this.querySelector('div');
|
28
|
+
}
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Lifecycle callback when element is added to DOM
|
32
|
+
* Sets up the editable element and registers it with the parent editor
|
33
|
+
*
|
34
|
+
* @throws {Error} If not used as child of ckeditor-component
|
35
|
+
*/
|
36
|
+
connectedCallback() {
|
37
|
+
execIfDOMReady(() => {
|
38
|
+
const editorComponent = this.#queryEditorElement();
|
39
|
+
|
40
|
+
if (!editorComponent ) {
|
41
|
+
throw new Error('ckeditor-editable-component must be a child of ckeditor-component');
|
42
|
+
}
|
43
|
+
|
44
|
+
this.innerHTML = `<div>${this.innerHTML}</div>`;
|
45
|
+
this.style.display = 'block';
|
46
|
+
|
47
|
+
if (editorComponent.isDecoupled()) {
|
48
|
+
editorComponent.runAfterEditorReady(editor => {
|
49
|
+
this.appendChild(editor.ui.view[this.name].element);
|
50
|
+
});
|
51
|
+
} else {
|
52
|
+
if (!this.name) {
|
53
|
+
throw new Error('Editable component missing required "name" attribute');
|
54
|
+
}
|
55
|
+
|
56
|
+
editorComponent.editables[this.name] = this;
|
57
|
+
}
|
58
|
+
});
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Lifecycle callback for attribute changes
|
63
|
+
* Handles name changes and propagates other attributes to editable element
|
64
|
+
*
|
65
|
+
* @param {string} name - Name of changed attribute
|
66
|
+
* @param {string|null} oldValue - Previous value
|
67
|
+
* @param {string|null} newValue - New value
|
68
|
+
*/
|
69
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
70
|
+
if (oldValue === newValue) {
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
|
74
|
+
if (name === 'name') {
|
75
|
+
if (!oldValue) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
|
79
|
+
const editorComponent = this.#queryEditorElement();
|
80
|
+
|
81
|
+
if (editorComponent) {
|
82
|
+
editorComponent.editables[newValue] = editorComponent.editables[oldValue];
|
83
|
+
delete editorComponent.editables[oldValue];
|
84
|
+
}
|
85
|
+
} else {
|
86
|
+
this.editableElement.setAttribute(name, newValue);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Lifecycle callback when element is removed
|
92
|
+
* Un-registers this editable from the parent editor
|
93
|
+
*/
|
94
|
+
disconnectedCallback() {
|
95
|
+
const editorComponent = this.#queryEditorElement();
|
96
|
+
|
97
|
+
if (editorComponent) {
|
98
|
+
delete editorComponent.editables[this.name];
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Finds the parent editor component
|
104
|
+
*
|
105
|
+
* @private
|
106
|
+
* @returns {CKEditorComponent|null} Parent editor component or null if not found
|
107
|
+
*/
|
108
|
+
#queryEditorElement() {
|
109
|
+
return this.closest('ckeditor-component') || document.body.querySelector('ckeditor-component');
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
customElements.define('ckeditor-editable-component', CKEditorEditableComponent);
|