ckeditor5 1.9.0 → 1.11.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/Gemfile +10 -0
- data/README.md +63 -11
- data/lib/ckeditor5/rails/assets/assets_bundle_html_serializer.rb +4 -4
- 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} +104 -479
- 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/ckbox_bundle.rb +17 -8
- data/lib/ckeditor5/rails/cdn/helpers.rb +3 -11
- data/lib/ckeditor5/rails/cdn/url_generator.rb +7 -5
- data/lib/ckeditor5/rails/helpers.rb +0 -2
- data/lib/ckeditor5/rails/presets/preset_builder.rb +21 -2
- data/lib/ckeditor5/rails/version.rb +1 -1
- metadata +8 -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: cbd754782a22b999b5843eedf90b499ba82f44dfac2ceb51f3c46898800482e2
|
4
|
+
data.tar.gz: c7945b1c9efeb3579a70ac0aaa832bac46147c512b9de98dff3dbf5a3187afad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1263373ca9e36adf9d63b8ff23d88933231cdacb8378cedc9f17819ae59924064e04bec14731e3907808af138ecd2afc4cd80cb2db9b943f221d1b8d9ccdf43
|
7
|
+
data.tar.gz: a8c822abfc9f0f191785d2cea17d5eb7d5d9696c2d3337c9fbdd4ff302f9ef1bf2b46fca90cc9b221f00c1a37c0d124c92fb74dde125dc5e8cd2aaf9b683f8bc
|
data/Gemfile
CHANGED
@@ -6,11 +6,21 @@ gem 'bundler', '~> 2.5', '>= 2.5.21'
|
|
6
6
|
|
7
7
|
group :development do
|
8
8
|
gem 'brakeman', '~> 6.1', '>= 6.1.1', require: false
|
9
|
+
gem 'guard', '~> 2.19', '>= 2.19.0'
|
10
|
+
gem 'guard-process', '~> 1.2'
|
11
|
+
gem 'pry', '~> 0.15', '>= 0.15.0'
|
12
|
+
gem 'rails', '~> 7.0', '>= 7.0.0'
|
9
13
|
gem 'rake', '~> 13.2', '>= 13.2.1'
|
10
14
|
gem 'rspec', '~> 3.13'
|
15
|
+
gem 'rspec-expectations', '~> 3.13'
|
16
|
+
gem 'rspec-rails', '~> 7.0'
|
11
17
|
gem 'rubocop', '~> 1.66', require: false
|
12
18
|
gem 'rubocop-rails', '~> 2.26', '>= 2.26.2', require: false
|
13
19
|
gem 'rubocop-rails-omakase', '~> 1.0.0', require: false
|
20
|
+
gem 'simple_form', '~> 5.3', '>= 5.3.0'
|
21
|
+
gem 'slim', '~> 5.2', '>= 5.2.0'
|
22
|
+
gem 'sprockets-rails', '~> 3.2', '>= 3.2.2'
|
23
|
+
gem 'sqlite3', '>= 1.4'
|
14
24
|
end
|
15
25
|
|
16
26
|
group :test, :development do
|
data/README.md
CHANGED
@@ -102,8 +102,8 @@ Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
|
|
102
102
|
- [Balloon editor 🎈](#balloon-editor-)
|
103
103
|
- [Decoupled editor 🌐](#decoupled-editor-)
|
104
104
|
- [Using Context 📦](#using-context-)
|
105
|
-
- [Benefits of Using Context in Collaboration 🤝](#benefits-of-using-context-in-collaboration-)
|
106
105
|
- [Using Context in CKEditor 5 🔄](#using-context-in-ckeditor-5-)
|
106
|
+
- [Example usage of `ckeditor5_context` helper 📝](#example-usage-of-ckeditor5_context-helper-)
|
107
107
|
- [How to access editor instance? 🤔](#how-to-access-editor-instance-)
|
108
108
|
- [Common Tasks and Solutions 💡](#common-tasks-and-solutions-)
|
109
109
|
- [Setting Editor Language 🌐](#setting-editor-language-)
|
@@ -115,6 +115,9 @@ Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
|
|
115
115
|
- [Events fired by the editor 🔊](#events-fired-by-the-editor-)
|
116
116
|
- [`editor-ready` event](#editor-ready-event)
|
117
117
|
- [`editor-error` event](#editor-error-event)
|
118
|
+
- [`editor-change` event](#editor-change-event)
|
119
|
+
- [Inline event handling](#inline-event-handling)
|
120
|
+
- [Gem Development 🛠](#gem-development-)
|
118
121
|
- [Trademarks 📜](#trademarks-)
|
119
122
|
- [License 📜](#license-)
|
120
123
|
|
@@ -967,25 +970,24 @@ If you want to use a decoupled editor, you can pass the `type` keyword argument
|
|
967
970
|
|
968
971
|
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.
|
969
972
|
|
970
|
-
|
971
|
-
|
972
|
-
1. **Consistency**: Ensures that all editor instances have the same configuration, plugins, and settings, providing a uniform editing experience.
|
973
|
-
2. **Synchronization**: Allows real-time synchronization of content and changes across multiple editor instances, making collaborative editing seamless.
|
974
|
-
3. **Resource Sharing**: Reduces the overhead of loading and initializing multiple editor instances by sharing common resources and configurations.
|
975
|
-
4. **Simplified Management**: Makes it easier to manage and update the configuration and state of multiple editor instances from a single point.
|
973
|
+

|
976
974
|
|
977
975
|
### Using Context in CKEditor 5 🔄
|
978
976
|
|
979
977
|
Format of the `ckeditor5_context` helper:
|
980
978
|
|
981
979
|
```erb
|
982
|
-
|
980
|
+
<!-- app/views/demos/index.html.erb -->
|
981
|
+
|
982
|
+
<%= ckeditor5_context config: { ... }, plugins: [ ... ] do %>
|
983
983
|
<%= ckeditor5_editor %>
|
984
984
|
<%= ckeditor5_editor %>
|
985
985
|
<% end %>
|
986
986
|
```
|
987
987
|
|
988
|
-
|
988
|
+
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.
|
989
|
+
|
990
|
+
### Example usage of `ckeditor5_context` helper 📝
|
989
991
|
|
990
992
|
```erb
|
991
993
|
<!-- app/views/demos/index.html.erb -->
|
@@ -1260,6 +1262,8 @@ class HighlightCommand extends Command {
|
|
1260
1262
|
|
1261
1263
|
## Events fired by the editor 🔊
|
1262
1264
|
|
1265
|
+
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.
|
1266
|
+
|
1263
1267
|
### `editor-ready` event
|
1264
1268
|
|
1265
1269
|
The event is fired when the initialization of the editor is completed. You can listen to it using the `editor-ready` event.
|
@@ -1280,6 +1284,54 @@ document.getElementById('editor').addEventListener('editor-error', () => {
|
|
1280
1284
|
});
|
1281
1285
|
```
|
1282
1286
|
|
1287
|
+
### `editor-change` event
|
1288
|
+
|
1289
|
+
The event is fired when the content of the editor changes. You can listen to it using the `editor-change` event.
|
1290
|
+
|
1291
|
+
```js
|
1292
|
+
document.getElementById('editor').addEventListener('editor-change', () => {
|
1293
|
+
console.log('Editor content has changed');
|
1294
|
+
});
|
1295
|
+
```
|
1296
|
+
|
1297
|
+
### Inline event handling
|
1298
|
+
|
1299
|
+
You can also define event handlers directly in the view using the `oneditorchange`, `oneditorerror`, and `oneditorready` attributes.
|
1300
|
+
|
1301
|
+
```erb
|
1302
|
+
<!-- app/views/demos/index.html.erb -->
|
1303
|
+
|
1304
|
+
<script type="text/javascript">
|
1305
|
+
function onEditorChange(event) {
|
1306
|
+
// event.detail.editor, event.detail.data
|
1307
|
+
}
|
1308
|
+
|
1309
|
+
function onEditorError(event) {
|
1310
|
+
// event.detail.editor
|
1311
|
+
}
|
1312
|
+
|
1313
|
+
function onEditorReady(event) {
|
1314
|
+
// event.detail.editor
|
1315
|
+
}
|
1316
|
+
</script>
|
1317
|
+
|
1318
|
+
<%= ckeditor5_editor id: 'editor',
|
1319
|
+
oneditorchange: 'onEditorChange',
|
1320
|
+
oneditorerror: 'onEditorError',
|
1321
|
+
oneditorready: 'onEditorReady'
|
1322
|
+
%>
|
1323
|
+
```
|
1324
|
+
|
1325
|
+
## Gem Development 🛠
|
1326
|
+
|
1327
|
+
If you want to contribute to the gem, you can clone the repository and run the following commands:
|
1328
|
+
|
1329
|
+
```sh
|
1330
|
+
gem install bundler -v 2.5.22
|
1331
|
+
bundle install
|
1332
|
+
bundle exec guard
|
1333
|
+
```
|
1334
|
+
|
1283
1335
|
## Trademarks 📜
|
1284
1336
|
|
1285
1337
|
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/).
|
@@ -1288,6 +1340,6 @@ This gem is not owned by CKSource and does not use the CKEditor® trademark for
|
|
1288
1340
|
|
1289
1341
|
## License 📜
|
1290
1342
|
|
1291
|
-
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.
|
1343
|
+
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.
|
1292
1344
|
|
1293
|
-
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/).
|
1345
|
+
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,12 +38,12 @@ 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
|
45
45
|
@window_scripts_tags ||= safe_join(bundle.scripts.filter_map do |script|
|
46
|
-
tag.script(src: script.url, nonce: true, async: true) if script.window?
|
46
|
+
tag.script(src: script.url, nonce: true, async: true, crossorigin: 'anonymous') if script.window?
|
47
47
|
end)
|
48
48
|
end
|
49
49
|
|
@@ -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);
|