ckeditor5 1.1.6 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b95ee184f10909e8acc238dcc5650a4bcbd828cb8cb32719cb98465223c7bc6c
4
- data.tar.gz: 31364b050c64792a5561b4a9fd9de3dfe86a8a338f6a8f8075ca334c1558758d
3
+ metadata.gz: '028a09c0049e3eb489434b0447796369924801d0f5273a400e9783dbd3a8a70c'
4
+ data.tar.gz: e9a3c65f03a85b1361b612c85b00c5cb9d1d8385eeca716c5cea4f0e5817474b
5
5
  SHA512:
6
- metadata.gz: 916eee6c8da16ad1b6d5ed9c7cd4358724e2a07d64b08b1ba77fdb03e2164a9633d3ce5403a6af70d938f0e1ef73e3a725549b83d93689b43c6225b0700456ea
7
- data.tar.gz: c19f382d14e66e8fc8a0f0b49dfa18687cad2697de005700a39532bd6a19928b7f1093ec1aea4fc5d067e36b4edb20ce0476b67946b0700d77045af38d1545e8
6
+ metadata.gz: 5bf454b35f45aca3a44a2463d007cea579714c835d82edf7e6ed351db3c93a94aff59489bab7cea69cd78853acf50473ad9f7cf64819ee5c3c5bc5c0ef2d20a8
7
+ data.tar.gz: e394db6e299f6a48c88f50a306500bcae7c87c0eae6092bdf44c7f62e4a179da542f7299448db18045c086047da16a5818f3915416d9b10bb00747bda9e431ce
data/README.md CHANGED
@@ -75,6 +75,7 @@ Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
75
75
  - [Commercial usage 💰](#commercial-usage-)
76
76
  - [Editor placement 🏗️](#editor-placement-️)
77
77
  - [Setting Initial Content 📝](#setting-initial-content-)
78
+ - [Watchdog 🐕](#watchdog-)
78
79
  - [Classic editor 📝](#classic-editor-)
79
80
  - [Multiroot editor 🌳](#multiroot-editor-)
80
81
  - [Inline editor 📝](#inline-editor-)
@@ -490,7 +491,7 @@ If you want to use CKEditor 5 under the GPL license, you can include the assets
490
491
  <!-- app/views/demos/index.html.erb -->
491
492
 
492
493
  <% content_for :head do %>
493
- <%= ckeditor5_assets %>
494
+ <%= ckeditor5_assets version: '43.3.0' %>
494
495
  <% end %>
495
496
  ```
496
497
 
@@ -562,9 +563,6 @@ end
562
563
 
563
564
  ### Commercial usage 💰
564
565
 
565
- <details>
566
- <summary>Expand to show more</summary>
567
-
568
566
  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
567
 
570
568
  ```erb
@@ -577,8 +575,6 @@ If you want to use CKEditor 5 under a commercial license, you can include the as
577
575
 
578
576
  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
577
 
580
- </details>
581
-
582
578
  ## Editor placement 🏗️
583
579
 
584
580
  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 +601,18 @@ The example below shows how to set the initial content of the editor using the `
605
601
  <% end %>
606
602
  ```
607
603
 
604
+ ### Watchdog 🐕
605
+
606
+ 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.
607
+
608
+ If you want to disable the watchdog, you can pass the `watchdog` keyword argument with the value `false`:
609
+
610
+ ```erb
611
+ <!-- app/views/demos/index.html.erb -->
612
+
613
+ <%= ckeditor5_editor watchdog: false %>
614
+ ```
615
+
608
616
  ### Classic editor 📝
609
617
 
610
618
  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 +749,8 @@ Decoupled editor is a variant of classic editor that allows you to separate the
741
749
  If you want to use a decoupled editor, you can pass the `type` keyword argument with the value `:decoupled`:
742
750
 
743
751
  ```erb
752
+ <!-- app/views/demos/index.html.erb -->
753
+
744
754
  <% content_for :head do %>
745
755
  <%= ckeditor5_assets %>
746
756
  <% end %>
@@ -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
- * Initializes a new CKEditor instance
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 config:', config);
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
- const instance = await Editor.create(content, config);
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 instance;
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
- this.instance = await this.#initializeEditor(this.editables || this.#getConfig().initialData || '');
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
  *
@@ -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
- context = validate_and_get_editor_context!
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 = build_editor_props(
30
- config: config,
31
- type: type,
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(context, type, config)
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
- @context = context
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 :context, :type, :config
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
- context[:bundle].translations_scripts.map(&:to_h).to_json
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] = context[:license_key] if context[:license_key] }
59
+ .tap { |cfg| cfg[:licenseKey] = controller_context[:license_key] if controller_context[:license_key] }
58
60
  .to_json
59
61
  end
60
62
  end
@@ -2,16 +2,13 @@
2
2
 
3
3
  require 'rails/engine'
4
4
 
5
- require_relative 'toolbar_builder'
6
- require_relative 'preset_builder'
7
- require_relative 'presets_manager'
8
-
5
+ require_relative 'presets/manager'
9
6
  require_relative 'hooks/form'
10
7
 
11
8
  module CKEditor5::Rails
12
9
  class Engine < ::Rails::Engine
13
10
  config.ckeditor5 = ActiveSupport::OrderedOptions.new
14
- config.ckeditor5.presets = PresetsManager.new
11
+ config.ckeditor5.presets = Presets::Manager.new
15
12
 
16
13
  initializer 'helper' do
17
14
  ActiveSupport.on_load(:action_view) { include Helpers }
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module CKEditor5::Rails
4
- class PresetsManager
3
+ require_relative 'preset_builder'
4
+ require_relative 'toolbar_builder'
5
+
6
+ module CKEditor5::Rails::Presets
7
+ class Manager
5
8
  attr_reader :presets
6
9
 
7
10
  def initialize
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CKEditor5::Rails
4
+ module Presets
5
+ class PresetBuilder
6
+ attr_reader :config
7
+
8
+ def initialize
9
+ @version = nil
10
+ @premium = false
11
+ @cdn = :jsdelivr
12
+ @translations = []
13
+ @license_key = nil
14
+ @type = :classic
15
+ @ckbox = nil
16
+ @config = {
17
+ plugins: [],
18
+ toolbar: []
19
+ }
20
+ end
21
+
22
+ def to_h_with_overrides(**overrides)
23
+ {
24
+ version: overrides.fetch(:version, version),
25
+ premium: overrides.fetch(:premium, premium),
26
+ cdn: overrides.fetch(:cdn, cdn),
27
+ translations: overrides.fetch(:translations, translations),
28
+ license_key: overrides.fetch(:license_key, license_key),
29
+ type: overrides.fetch(:type, type),
30
+ ckbox: overrides.fetch(:ckbox, ckbox),
31
+ config: config.merge(overrides.fetch(:config, {}))
32
+ }
33
+ end
34
+
35
+ def ckbox(version = nil, theme: :lark)
36
+ return @ckbox if version.nil?
37
+
38
+ @ckbox = { version: version, theme: theme }
39
+ end
40
+
41
+ def license_key(license_key = nil)
42
+ return @license_key if license_key.nil?
43
+
44
+ @license_key = license_key
45
+ end
46
+
47
+ def gpl
48
+ license_key('GPL')
49
+ premium(false)
50
+ end
51
+
52
+ def premium(premium = nil)
53
+ return @premium if premium.nil?
54
+
55
+ @premium = premium
56
+ end
57
+
58
+ def translations(*translations)
59
+ return @translations if translations.empty?
60
+
61
+ @translations = translations
62
+ end
63
+
64
+ def version(version = nil)
65
+ return @version.to_s if version.nil?
66
+
67
+ @version = Semver.new(version)
68
+ end
69
+
70
+ def cdn(cdn = nil)
71
+ return @cdn if cdn.nil?
72
+
73
+ @cdn = cdn
74
+ end
75
+
76
+ def type(type = nil)
77
+ return @type if type.nil?
78
+ raise ArgumentError, "Invalid editor type: #{type}" unless Editor::Props.valid_editor_type?(type)
79
+
80
+ @type = type
81
+ end
82
+
83
+ def configure(key, value)
84
+ @config[key] = value
85
+ end
86
+
87
+ def menubar(visible: true)
88
+ @config[:menuBar] = {
89
+ isVisible: visible
90
+ }
91
+ end
92
+
93
+ def toolbar(*items, should_group_when_full: true, &block)
94
+ if @config[:toolbar].blank? || !items.empty?
95
+ @config[:toolbar] = {
96
+ items: items,
97
+ shouldNotGroupWhenFull: !should_group_when_full
98
+ }
99
+ end
100
+
101
+ return unless block
102
+
103
+ builder = ToolbarBuilder.new(@config[:toolbar])
104
+ builder.instance_eval(&block)
105
+ end
106
+
107
+ def inline_plugin(name, code)
108
+ @config[:plugins] << Editor::PropsInlinePlugin.new(name, code)
109
+ end
110
+
111
+ def plugin(name, **kwargs)
112
+ @config[:plugins] << Editor::PropsPlugin.new(name, **kwargs)
113
+ end
114
+
115
+ def plugins(*names, **kwargs)
116
+ names.each { |name| plugin(name, **kwargs) }
117
+ end
118
+
119
+ def language(ui, content: ui) # rubocop:disable Naming/MethodParameterName
120
+ @config[:language] = {
121
+ ui: ui,
122
+ content: content
123
+ }
124
+ end
125
+ end
126
+ end
127
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module CKEditor5::Rails
3
+ module CKEditor5::Rails::Presets
4
4
  class ToolbarBuilder
5
5
  def initialize(toolbar_config)
6
6
  @toolbar_config = toolbar_config
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CKEditor5::Rails
4
- VERSION = '1.1.6'
4
+ VERSION = '1.2.0'
5
5
  end
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.1.6
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mateusz Bagiński
@@ -59,10 +59,10 @@ files:
59
59
  - lib/ckeditor5/rails/helpers.rb
60
60
  - lib/ckeditor5/rails/hooks/form.rb
61
61
  - lib/ckeditor5/rails/hooks/simple_form.rb
62
- - lib/ckeditor5/rails/preset_builder.rb
63
- - lib/ckeditor5/rails/presets_manager.rb
62
+ - lib/ckeditor5/rails/presets/manager.rb
63
+ - lib/ckeditor5/rails/presets/preset_builder.rb
64
+ - lib/ckeditor5/rails/presets/toolbar_builder.rb
64
65
  - lib/ckeditor5/rails/semver.rb
65
- - lib/ckeditor5/rails/toolbar_builder.rb
66
66
  - lib/ckeditor5/rails/version.rb
67
67
  homepage: https://github.com/Mati365/ckeditor5-rails
68
68
  licenses:
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CKEditor5::Rails
4
- class PresetBuilder
5
- attr_reader :config
6
-
7
- def initialize
8
- @version = nil
9
- @premium = false
10
- @cdn = :jsdelivr
11
- @translations = []
12
- @license_key = nil
13
- @type = :classic
14
- @ckbox = nil
15
- @config = {
16
- plugins: [],
17
- toolbar: []
18
- }
19
- end
20
-
21
- def to_h_with_overrides(**overrides)
22
- {
23
- version: overrides.fetch(:version, version),
24
- premium: overrides.fetch(:premium, premium),
25
- cdn: overrides.fetch(:cdn, cdn),
26
- translations: overrides.fetch(:translations, translations),
27
- license_key: overrides.fetch(:license_key, license_key),
28
- type: overrides.fetch(:type, type),
29
- ckbox: overrides.fetch(:ckbox, ckbox),
30
- config: config.merge(overrides.fetch(:config, {}))
31
- }
32
- end
33
-
34
- def ckbox(version = nil, theme: :lark)
35
- return @ckbox if version.nil?
36
-
37
- @ckbox = { version: version, theme: theme }
38
- end
39
-
40
- def license_key(license_key = nil)
41
- return @license_key if license_key.nil?
42
-
43
- @license_key = license_key
44
- end
45
-
46
- def gpl
47
- license_key('GPL')
48
- premium(false)
49
- end
50
-
51
- def premium(premium = nil)
52
- return @premium if premium.nil?
53
-
54
- @premium = premium
55
- end
56
-
57
- def translations(*translations)
58
- return @translations if translations.empty?
59
-
60
- @translations = translations
61
- end
62
-
63
- def version(version = nil)
64
- return @version.to_s if version.nil?
65
-
66
- @version = Semver.new(version)
67
- end
68
-
69
- def cdn(cdn = nil)
70
- return @cdn if cdn.nil?
71
-
72
- @cdn = cdn
73
- end
74
-
75
- def type(type = nil)
76
- return @type if type.nil?
77
- raise ArgumentError, "Invalid editor type: #{type}" unless Editor::Props.valid_editor_type?(type)
78
-
79
- @type = type
80
- end
81
-
82
- def configure(key, value)
83
- @config[key] = value
84
- end
85
-
86
- def menubar(visible: true)
87
- @config[:menuBar] = {
88
- isVisible: visible
89
- }
90
- end
91
-
92
- def toolbar(*items, should_group_when_full: true, &block)
93
- if @config[:toolbar].blank? || !items.empty?
94
- @config[:toolbar] = {
95
- items: items,
96
- shouldNotGroupWhenFull: !should_group_when_full
97
- }
98
- end
99
-
100
- return unless block
101
-
102
- builder = ToolbarBuilder.new(@config[:toolbar])
103
- builder.instance_eval(&block)
104
- end
105
-
106
- def inline_plugin(name, code)
107
- @config[:plugins] << Editor::PropsInlinePlugin.new(name, code)
108
- end
109
-
110
- def plugin(name, **kwargs)
111
- @config[:plugins] << Editor::PropsPlugin.new(name, **kwargs)
112
- end
113
-
114
- def plugins(*names, **kwargs)
115
- names.each { |name| plugin(name, **kwargs) }
116
- end
117
-
118
- def language(ui, content: ui) # rubocop:disable Naming/MethodParameterName
119
- @config[:language] = {
120
- ui: ui,
121
- content: content
122
- }
123
- end
124
- end
125
- end