ckeditor5 1.1.7 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +57 -30
- data/lib/ckeditor5/rails/assets/webcomponent.mjs +44 -6
- data/lib/ckeditor5/rails/cdn/url_generator.rb +6 -3
- data/lib/ckeditor5/rails/editor/helpers.rb +6 -11
- data/lib/ckeditor5/rails/editor/props.rb +8 -6
- data/lib/ckeditor5/rails/presets/preset_builder.rb +13 -4
- data/lib/ckeditor5/rails/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7b18841155e113a731b8b81c953fd13547a9ed25f3d5be662786297fc6a985a
|
4
|
+
data.tar.gz: 8894ffe8876faad795fab39bdae58c03fdfb4dd014337c30c6573e20c881b74b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2abcac7b7c28c763b095b83078ce25b6d081b59120a8a8e40f48a1711334d9ac59828bd047bb3d727bc85c2e2ce2325a263c18941485f1c062fa98ddb789b22
|
7
|
+
data.tar.gz: e7d0c636c623904202bb3c6a18e5196c90f5c39bffd18f9a24749e764e338032a7ba8f2c2d0367e24e337b8ea03c616fb2a8308b95deae5f2a81f37ca0199ba8
|
data/README.md
CHANGED
@@ -39,11 +39,9 @@ In your view:
|
|
39
39
|
|
40
40
|
<% content_for :head do %>
|
41
41
|
<%= ckeditor5_assets %>
|
42
|
-
<!-- or using inline config: ckeditor5_assets version: '43.3.0', premium: false -->
|
43
42
|
<% end %>
|
44
43
|
|
45
|
-
<%= ckeditor5_editor
|
46
|
-
<!-- or using inline config: ckeditor5_editor type: :classic, config: { toolbar: [:Bold, :Italic] }, ... -->
|
44
|
+
<%= ckeditor5_editor %>
|
47
45
|
```
|
48
46
|
|
49
47
|
Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
|
@@ -55,6 +53,7 @@ Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
|
|
55
53
|
- [Table of Contents 📚](#table-of-contents-)
|
56
54
|
- [Presets 🎨](#presets-)
|
57
55
|
- [Available Configuration Methods ⚙️](#available-configuration-methods-️)
|
56
|
+
- [`cdn(cdn = nil, &block)` method](#cdncdn--nil-block-method)
|
58
57
|
- [`version(version)` method](#versionversion-method)
|
59
58
|
- [`gpl` method](#gpl-method)
|
60
59
|
- [`license_key(key)` method](#license_keykey-method)
|
@@ -75,6 +74,7 @@ Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
|
|
75
74
|
- [Commercial usage 💰](#commercial-usage-)
|
76
75
|
- [Editor placement 🏗️](#editor-placement-️)
|
77
76
|
- [Setting Initial Content 📝](#setting-initial-content-)
|
77
|
+
- [Watchdog 🐕](#watchdog-)
|
78
78
|
- [Classic editor 📝](#classic-editor-)
|
79
79
|
- [Multiroot editor 🌳](#multiroot-editor-)
|
80
80
|
- [Inline editor 📝](#inline-editor-)
|
@@ -141,21 +141,48 @@ CKEditor5::Rails.configure do |config|
|
|
141
141
|
|
142
142
|
toolbar do
|
143
143
|
remove :underline, :heading
|
144
|
-
|
145
|
-
# prepend :underline
|
146
|
-
# append :heading
|
147
144
|
end
|
148
145
|
end
|
149
146
|
end
|
150
147
|
```
|
151
148
|
|
152
|
-
Configuration of the editor can be complex, and it's recommended to use the CKEditor 5
|
149
|
+
Configuration of the editor can be complex, and it's recommended to use the [CKEditor 5 online builder](https://ckeditor.com/ckeditor-5/online-builder/) to generate the configuration. It allows you to select the features you want to include and generate the configuration code in JavaScript format. Keep in mind that you need to convert the JavaScript configuration to Ruby format before using it in this gem.
|
153
150
|
|
154
151
|
### Available Configuration Methods ⚙️
|
155
152
|
|
156
153
|
<details>
|
157
154
|
<summary>Expand to show available methods 📖</summary>
|
158
155
|
|
156
|
+
#### `cdn(cdn = nil, &block)` method
|
157
|
+
|
158
|
+
Defines the CDN to be used for CKEditor 5 assets. The example below shows how to set the CDN to `:jsdelivr`:
|
159
|
+
|
160
|
+
```rb
|
161
|
+
# config/initializers/ckeditor5.rb
|
162
|
+
|
163
|
+
config.presets.define :custom do
|
164
|
+
# ... other configuration
|
165
|
+
|
166
|
+
cdn :jsdelivr
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
It also allows you to define a custom CDN by passing a block with the bundle, version, and path arguments. The example below shows how to define it for the `jsdelivr` CDN:
|
171
|
+
|
172
|
+
```rb
|
173
|
+
# config/initializers/ckeditor5.rb
|
174
|
+
|
175
|
+
config.presets.define :custom do
|
176
|
+
# ... other configuration
|
177
|
+
|
178
|
+
cdn do |bundle, version, path|
|
179
|
+
base_url = "https://cdn.jsdelivr.net/npm/#{bundle}@#{version}/dist"
|
180
|
+
|
181
|
+
"#{base_url}/#{path.start_with?('translations/') ? '' : 'browser/'}#{path}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
159
186
|
#### `version(version)` method
|
160
187
|
|
161
188
|
Defines the version of CKEditor 5 to be used. The example below shows how to set the version to `43.2.0`:
|
@@ -490,7 +517,7 @@ If you want to use CKEditor 5 under the GPL license, you can include the assets
|
|
490
517
|
<!-- app/views/demos/index.html.erb -->
|
491
518
|
|
492
519
|
<% content_for :head do %>
|
493
|
-
<%= ckeditor5_assets %>
|
520
|
+
<%= ckeditor5_assets version: '43.3.0' %>
|
494
521
|
<% end %>
|
495
522
|
```
|
496
523
|
|
@@ -562,9 +589,6 @@ end
|
|
562
589
|
|
563
590
|
### Commercial usage 💰
|
564
591
|
|
565
|
-
<details>
|
566
|
-
<summary>Expand to show more</summary>
|
567
|
-
|
568
592
|
If you want to use CKEditor 5 under a commercial license, you can include the assets using the `ckeditor5_assets` helper method with the `license_key` keyword argument. The example below shows how to include the assets for the commercial license:
|
569
593
|
|
570
594
|
```erb
|
@@ -577,8 +601,6 @@ If you want to use CKEditor 5 under a commercial license, you can include the as
|
|
577
601
|
|
578
602
|
In this scenario, the assets are included from the official CKEditor 5 CDN which is more reliable and provides better performance, especially for commercial usage.
|
579
603
|
|
580
|
-
</details>
|
581
|
-
|
582
604
|
## Editor placement 🏗️
|
583
605
|
|
584
606
|
The `ckeditor5_editor` helper renders CKEditor 5 instances in your views. Before using it, ensure you've included the necessary assets in your page's head section otherwise the editor won't work as there are no CKEditor 5 JavaScript and CSS files loaded.
|
@@ -605,6 +627,18 @@ The example below shows how to set the initial content of the editor using the `
|
|
605
627
|
<% end %>
|
606
628
|
```
|
607
629
|
|
630
|
+
### Watchdog 🐕
|
631
|
+
|
632
|
+
CKEditor 5 uses a watchdog utility to protect you from data loss in case the editor crashes. It saves your content just before the crash and creates a new instance of the editor with your content intact. It's enabled by default in the gem.
|
633
|
+
|
634
|
+
If you want to disable the watchdog, you can pass the `watchdog` keyword argument with the value `false`:
|
635
|
+
|
636
|
+
```erb
|
637
|
+
<!-- app/views/demos/index.html.erb -->
|
638
|
+
|
639
|
+
<%= ckeditor5_editor watchdog: false %>
|
640
|
+
```
|
641
|
+
|
608
642
|
### Classic editor 📝
|
609
643
|
|
610
644
|
The classic editor is the most common type of editor. It provides a toolbar with various formatting options like bold, italic, underline, and link.
|
@@ -741,6 +775,8 @@ Decoupled editor is a variant of classic editor that allows you to separate the
|
|
741
775
|
If you want to use a decoupled editor, you can pass the `type` keyword argument with the value `:decoupled`:
|
742
776
|
|
743
777
|
```erb
|
778
|
+
<!-- app/views/demos/index.html.erb -->
|
779
|
+
|
744
780
|
<% content_for :head do %>
|
745
781
|
<%= ckeditor5_assets %>
|
746
782
|
<% end %>
|
@@ -868,34 +904,25 @@ The example below shows how to define a custom plugin that allows toggling the h
|
|
868
904
|
config.presets.define :custom do
|
869
905
|
# ... other configuration
|
870
906
|
|
871
|
-
# 1. You can
|
872
|
-
|
873
|
-
# In case if plugin is located in external file (recommended), you can simply import it:
|
874
|
-
|
875
|
-
# inline_plugin :MyCustomPlugin, <<~JS
|
876
|
-
# import MyPlugin from 'app/javascript/custom_plugins/highlight.js';
|
877
|
-
# export default MyPlugin;
|
878
|
-
# JS
|
879
|
-
|
880
|
-
# 2. You can also use "window_name" option to import plugin from window object:
|
907
|
+
# 1. You can also use "window_name" option to import plugin from window object:
|
881
908
|
|
882
909
|
# plugin :MyPlugin, window_name: 'MyPlugin'
|
883
910
|
|
884
|
-
#
|
885
|
-
#
|
886
|
-
# Your `my-custom-plugin` must be present in import map.
|
911
|
+
# 2. Create JavaScript file in app/javascript/custom_plugins/highlight.js,
|
912
|
+
# add it to import map and then load it in initializer:
|
887
913
|
|
888
914
|
# plugin :MyCustomPlugin, import_name: 'my-custom-plugin'
|
889
915
|
|
890
|
-
#
|
916
|
+
# 3 Create JavaScript file in app/javascript/custom_plugins/highlight.js
|
917
|
+
# and then load it in initializer:
|
891
918
|
|
892
919
|
# In Ruby initializer you can also load plugin code directly from file:
|
893
|
-
|
920
|
+
inline_plugin :MyCustomPlugin, File.read(
|
894
921
|
Rails.root.join('app/javascript/custom_plugins/highlight.js')
|
895
922
|
)
|
896
923
|
|
897
|
-
#
|
898
|
-
#
|
924
|
+
# 4. Or even define it inline:
|
925
|
+
# inline_plugin :MyCustomPlugin, <<~JS
|
899
926
|
# import { Plugin } from 'ckeditor5';
|
900
927
|
#
|
901
928
|
# export default class MyCustomPlugin extends Plugin {
|
@@ -45,6 +45,9 @@ class CKEditorComponent extends HTMLElement {
|
|
45
45
|
/** @type {Promise<import('ckeditor5').Editor>|null} Promise to initialize editor instance */
|
46
46
|
instancePromise = Promise.withResolvers();
|
47
47
|
|
48
|
+
/** @type {import('ckeditor5').Watchdog|null} Editor watchdog */
|
49
|
+
watchdog = null;
|
50
|
+
|
48
51
|
/** @type {import('ckeditor5').Editor|null} Current editor instance */
|
49
52
|
instance = null;
|
50
53
|
|
@@ -93,6 +96,7 @@ class CKEditorComponent extends HTMLElement {
|
|
93
96
|
async disconnectedCallback() {
|
94
97
|
try {
|
95
98
|
await this.instance?.destroy();
|
99
|
+
await this.watchdog?.destroy();
|
96
100
|
} catch (error) {
|
97
101
|
console.error('Failed to destroy editor:', error);
|
98
102
|
}
|
@@ -129,10 +133,11 @@ class CKEditorComponent extends HTMLElement {
|
|
129
133
|
}
|
130
134
|
|
131
135
|
/**
|
132
|
-
*
|
136
|
+
* Creates a new CKEditor instance
|
137
|
+
*
|
133
138
|
* @private
|
134
139
|
* @param {Record<string, HTMLElement>|CKEditorMultiRootEditablesTracker} editablesOrContent - Editable or content
|
135
|
-
* @returns {Promise<import('ckeditor5').Editor>} Initialized editor instance
|
140
|
+
* @returns {Promise<{ editor: import('ckeditor5').Editor, watchdog: editor: import('ckeditor5').EditorWatchdog }>} Initialized editor instance
|
136
141
|
* @throws {Error} When initialization fails
|
137
142
|
*/
|
138
143
|
async #initializeEditor(editablesOrContent) {
|
@@ -142,6 +147,9 @@ class CKEditorComponent extends HTMLElement {
|
|
142
147
|
this.#getTranslations()
|
143
148
|
]);
|
144
149
|
|
150
|
+
// Depending on the type of the editor the content supplied on the first
|
151
|
+
// argument is different. For ClassicEditor it's a element or string, for MultiRootEditor
|
152
|
+
// it's an object with editables, for DecoupledEditor it's string.
|
145
153
|
let content = editablesOrContent;
|
146
154
|
|
147
155
|
if (editablesOrContent instanceof CKEditorMultiRootEditablesTracker) {
|
@@ -158,13 +166,29 @@ class CKEditorComponent extends HTMLElement {
|
|
158
166
|
plugins,
|
159
167
|
};
|
160
168
|
|
161
|
-
console.warn('Initializing CKEditor with
|
169
|
+
console.warn('Initializing CKEditor with:', { config, watchdog: this.hasWatchdog() });
|
170
|
+
|
171
|
+
// Initialize watchdog if needed
|
172
|
+
let watchdog = null;
|
173
|
+
let instance = null;
|
162
174
|
|
163
|
-
|
175
|
+
if (this.hasWatchdog()) {
|
176
|
+
const { EditorWatchdog } = await import('ckeditor5');
|
177
|
+
const watchdog = new EditorWatchdog(Editor);
|
178
|
+
|
179
|
+
await watchdog.create(content, config);
|
180
|
+
|
181
|
+
instance = watchdog.editor;
|
182
|
+
} else {
|
183
|
+
instance = await Editor.create(content, config);
|
184
|
+
}
|
164
185
|
|
165
186
|
this.dispatchEvent(new CustomEvent('editor-ready', { detail: instance }));
|
166
187
|
|
167
|
-
return
|
188
|
+
return {
|
189
|
+
instance,
|
190
|
+
watchdog,
|
191
|
+
};
|
168
192
|
}
|
169
193
|
|
170
194
|
/**
|
@@ -198,7 +222,11 @@ class CKEditorComponent extends HTMLElement {
|
|
198
222
|
}
|
199
223
|
|
200
224
|
try {
|
201
|
-
|
225
|
+
const { watchdog, instance } = await this.#initializeEditor(this.editables || this.#getConfig().initialData || '');
|
226
|
+
|
227
|
+
this.watchdog = watchdog;
|
228
|
+
this.instance = instance;
|
229
|
+
|
202
230
|
this.#setupContentSync();
|
203
231
|
|
204
232
|
this.instancePromise.resolve(this.instance);
|
@@ -228,6 +256,16 @@ class CKEditorComponent extends HTMLElement {
|
|
228
256
|
return this.getAttribute('type') === 'DecoupledEditor';
|
229
257
|
}
|
230
258
|
|
259
|
+
/**
|
260
|
+
* Checks if current editor has watchdog enabled
|
261
|
+
*
|
262
|
+
* @private
|
263
|
+
* @returns {boolean}
|
264
|
+
*/
|
265
|
+
hasWatchdog() {
|
266
|
+
return this.getAttribute('watchdog') === 'true';
|
267
|
+
}
|
268
|
+
|
231
269
|
/**
|
232
270
|
* Parses editor configuration from config attribute
|
233
271
|
*
|
@@ -19,8 +19,11 @@ module CKEditor5::Rails::Cdn
|
|
19
19
|
}.freeze
|
20
20
|
|
21
21
|
CDN_COMMERCIAL_GENERATORS = {
|
22
|
-
cloud:
|
23
|
-
|
22
|
+
cloud: lambda { |bundle, version, path|
|
23
|
+
domain = bundle == 'ckbox' ? 'ckbox.io' : 'ckeditor.com'
|
24
|
+
|
25
|
+
"https://cdn.#{domain}/#{bundle}/#{version}/#{path}"
|
26
|
+
}
|
24
27
|
}.freeze
|
25
28
|
|
26
29
|
included do
|
@@ -28,7 +31,7 @@ module CKEditor5::Rails::Cdn
|
|
28
31
|
end
|
29
32
|
|
30
33
|
def create_cdn_url(bundle, version, path)
|
31
|
-
generator = CDN_THIRD_PARTY_GENERATORS[cdn] || CDN_COMMERCIAL_GENERATORS[cdn]
|
34
|
+
generator = CDN_THIRD_PARTY_GENERATORS[cdn] || CDN_COMMERCIAL_GENERATORS[cdn] || cdn
|
32
35
|
|
33
36
|
raise ArgumentError, "Unknown provider: #{cdn}" unless generator
|
34
37
|
|
@@ -9,13 +9,13 @@ module CKEditor5::Rails
|
|
9
9
|
class EditorContextError < StandardError; end
|
10
10
|
class PresetNotFoundError < ArgumentError; end
|
11
11
|
|
12
|
-
def ckeditor5_editor(
|
12
|
+
def ckeditor5_editor( # rubocop:disable Metrics/ParameterLists
|
13
13
|
config: nil, extra_config: {},
|
14
14
|
type: nil, preset: :default,
|
15
|
-
initial_data: nil,
|
15
|
+
initial_data: nil, watchdog: true,
|
16
16
|
**html_attributes, &block
|
17
17
|
)
|
18
|
-
|
18
|
+
controller_context = validate_and_get_editor_context!
|
19
19
|
preset = fetch_editor_preset(preset)
|
20
20
|
|
21
21
|
config ||= preset.config
|
@@ -26,10 +26,9 @@ module CKEditor5::Rails
|
|
26
26
|
|
27
27
|
raise ArgumentError, 'Cannot pass initial data and block at the same time.' if initial_data && block
|
28
28
|
|
29
|
-
editor_props =
|
30
|
-
|
31
|
-
|
32
|
-
context: context
|
29
|
+
editor_props = Editor::Props.new(
|
30
|
+
controller_context, type, config,
|
31
|
+
watchdog: watchdog
|
33
32
|
)
|
34
33
|
|
35
34
|
render_editor_component(editor_props, html_attributes, &block)
|
@@ -68,10 +67,6 @@ module CKEditor5::Rails
|
|
68
67
|
raise PresetNotFoundError, "Preset #{preset} is not defined."
|
69
68
|
end
|
70
69
|
|
71
|
-
def build_editor_props(config:, type:, context:)
|
72
|
-
Editor::Props.new(context, type, config)
|
73
|
-
end
|
74
|
-
|
75
70
|
def render_editor_component(props, html_attributes, &block)
|
76
71
|
tag.send(:'ckeditor-component', **props.to_attributes, **html_attributes, &block)
|
77
72
|
end
|
@@ -12,10 +12,11 @@ module CKEditor5::Rails::Editor
|
|
12
12
|
multiroot: 'MultiRootEditor'
|
13
13
|
}.freeze
|
14
14
|
|
15
|
-
def initialize(
|
15
|
+
def initialize(controller_context, type, config, watchdog: true)
|
16
16
|
raise ArgumentError, "Invalid editor type: #{type}" unless Props.valid_editor_type?(type)
|
17
17
|
|
18
|
-
@
|
18
|
+
@controller_context = controller_context
|
19
|
+
@watchdog = watchdog
|
19
20
|
@type = type
|
20
21
|
@config = config
|
21
22
|
end
|
@@ -33,18 +34,19 @@ module CKEditor5::Rails::Editor
|
|
33
34
|
|
34
35
|
private
|
35
36
|
|
36
|
-
attr_reader :
|
37
|
+
attr_reader :controller_context, :watchdog, :type, :config
|
37
38
|
|
38
39
|
def serialized_attributes
|
39
40
|
{
|
40
41
|
translations: serialize_translations,
|
41
42
|
plugins: serialize_plugins,
|
42
|
-
config: serialize_config
|
43
|
+
config: serialize_config,
|
44
|
+
watchdog: watchdog
|
43
45
|
}
|
44
46
|
end
|
45
47
|
|
46
48
|
def serialize_translations
|
47
|
-
|
49
|
+
controller_context[:bundle].translations_scripts.map(&:to_h).to_json
|
48
50
|
end
|
49
51
|
|
50
52
|
def serialize_plugins
|
@@ -54,7 +56,7 @@ module CKEditor5::Rails::Editor
|
|
54
56
|
def serialize_config
|
55
57
|
config
|
56
58
|
.except(:plugins)
|
57
|
-
.tap { |cfg| cfg[:licenseKey] =
|
59
|
+
.tap { |cfg| cfg[:licenseKey] = controller_context[:license_key] if controller_context[:license_key] }
|
58
60
|
.to_json
|
59
61
|
end
|
60
62
|
end
|
@@ -67,10 +67,19 @@ module CKEditor5::Rails
|
|
67
67
|
@version = Semver.new(version)
|
68
68
|
end
|
69
69
|
|
70
|
-
def cdn(cdn = nil)
|
71
|
-
return @cdn if cdn.nil?
|
72
|
-
|
73
|
-
|
70
|
+
def cdn(cdn = nil, &block)
|
71
|
+
return @cdn if cdn.nil? && block.nil?
|
72
|
+
|
73
|
+
if block_given?
|
74
|
+
unless block.arity == 3
|
75
|
+
raise ArgumentError,
|
76
|
+
'Block must accept exactly 3 arguments: bundle, version, path'
|
77
|
+
end
|
78
|
+
|
79
|
+
@cdn = block
|
80
|
+
else
|
81
|
+
@cdn = cdn
|
82
|
+
end
|
74
83
|
end
|
75
84
|
|
76
85
|
def type(type = nil)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ckeditor5
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mateusz Bagiński
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-11-
|
12
|
+
date: 2024-11-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|