ckeditor5 1.18.3 → 1.19.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60fcadf8181318c867cdb47b5baf04d0c3630faf3182145f1d3f37952778aeb9
4
- data.tar.gz: c04696863a8b29644a37c6782b75dca5317a9bd2d0e166da05ae0cab357f1bc0
3
+ metadata.gz: 39db7b05796b6fd781f005ccb45d014a7936312619e4068d053378a5a5050bfd
4
+ data.tar.gz: 2f1746e2a1cb82ea6dc65465a616995e10744983f122f5e2822158e89ac710df
5
5
  SHA512:
6
- metadata.gz: d13f472504a89a4a8ebc13caa463546995ac8592da03aba6ec52a85705990e0cf5c4b8582cccf143ee560541406db5ca449782e22642c57cf0d91beeeaa4edd3
7
- data.tar.gz: 7c2f98547ea4a7044f73ec6017f35dadae2842aa61ea91ba7961ec0e114cb81491c5e30dc90c1ec964d394e3e49b1b6a28beaeed24a3264154e4d9a6e3bc7cee
6
+ metadata.gz: 5bbf9f23f3db08a9afef4873c4ba6d9ae57289130b0aa2c8581175c380a51d116668d32b209fd817abe7a255dbc0b3efc7cb869e7ec004749705d571df4c1a75
7
+ data.tar.gz: f096236e1d7a76c12803928b3e3efdb7d07d4b4ba5d5011af9d1597e8c33ba1622897430dcba9516fb87ee538ab1d5dce49210771a357e05d233ea03ba0c3839
data/README.md CHANGED
@@ -161,6 +161,7 @@ For extending CKEditor's functionality, refer to the [plugins directory](https:/
161
161
  - [Setting the language in the initializer](#setting-the-language-in-the-initializer)
162
162
  - [Setting the language on the editor level](#setting-the-language-on-the-editor-level)
163
163
  - [Preloading multiple translation packs](#preloading-multiple-translation-packs)
164
+ - [Spell and Grammar Checking 📝](#spell-and-grammar-checking-)
164
165
  - [Integrating with Forms 📋](#integrating-with-forms-)
165
166
  - [Rails form builder integration](#rails-form-builder-integration)
166
167
  - [Simple form integration](#simple-form-integration)
@@ -928,6 +929,23 @@ CKEditor5::Rails.configure do
928
929
  end
929
930
  ```
930
931
 
932
+ If no `language` is set, the plugin will use the default language of the editor. If you want to set the language of the WProofreader plugin, you can use the `language` keyword argument:
933
+
934
+ ```rb
935
+ # config/initializers/ckeditor5.rb
936
+
937
+ CKEditor5::Rails.configure do
938
+ # ... other configuration
939
+
940
+ wproofreader serviceId: 'your-service-ID',
941
+ srcUrl: 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js',
942
+ language: :en_US,
943
+ localization: :pl
944
+ end
945
+ ```
946
+
947
+ For more info about the WProofreader plugin, check the [official documentation](https://ckeditor.com/docs/ckeditor5/latest/features/spelling-and-grammar-checking.html).
948
+
931
949
  </details>
932
950
 
933
951
  ### Controller / View helpers 📦
@@ -1585,6 +1603,23 @@ CKEditor5::Rails.configure do
1585
1603
  end
1586
1604
  ```
1587
1605
 
1606
+ ### Spell and Grammar Checking 📝
1607
+
1608
+ CKEditor 5 provides a spell and grammar checking feature through the WProofreader plugin. You can enable this feature by configuring the WProofreader plugin in the initializer.
1609
+
1610
+ ```rb
1611
+ # config/initializers/ckeditor5.rb
1612
+
1613
+ CKEditor5::Rails.configure do
1614
+ wproofreader serviceId: 'your-service-ID',
1615
+ srcUrl: 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js'
1616
+ end
1617
+ ```
1618
+
1619
+ See [`wproofreader(version: nil, cdn: nil, **config)` method](#wproofreaderversion-nil-cdn-nil-config-method) for more information about the WProofreader plugin configuration.
1620
+
1621
+ See the [official documentation](https://ckeditor.com/docs/ckeditor5/latest/features/spelling-and-grammar-checking.html) for more information about the WProofreader plugin.
1622
+
1588
1623
  ### Integrating with Forms 📋
1589
1624
 
1590
1625
  You can integrate CKEditor 5 with Rails form builders like `form_for` or `simple_form`. The example below shows how to integrate CKEditor 5 with a Rails form using the `form_for` helper:
@@ -1,11 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/module'
4
+ require 'uri'
4
5
 
5
6
  module CKEditor5::Rails::Assets
6
7
  class AssetsBundle
7
- def initialize
8
- validate_implementation!
8
+ def initialize(scripts: nil, stylesheets: nil)
9
+ @scripts = scripts
10
+ @stylesheets = stylesheets
11
+ end
12
+
13
+ def scripts
14
+ @scripts || []
15
+ end
16
+
17
+ def stylesheets
18
+ @stylesheets || []
9
19
  end
10
20
 
11
21
  def empty?
@@ -17,7 +27,7 @@ module CKEditor5::Rails::Assets
17
27
  end
18
28
 
19
29
  def preloads
20
- stylesheets + scripts.map(&:url)
30
+ stylesheets + scripts.map(&:preloads)
21
31
  end
22
32
 
23
33
  def <<(other)
@@ -26,16 +36,6 @@ module CKEditor5::Rails::Assets
26
36
  @scripts = scripts + other.scripts
27
37
  @stylesheets = stylesheets + other.stylesheets
28
38
  end
29
-
30
- private
31
-
32
- def validate_implementation!
33
- %i[scripts stylesheets].each do |method|
34
- unless respond_to?(method, true)
35
- raise NotImplementedError, "#{self.class} must implement the ##{method} method"
36
- end
37
- end
38
- end
39
39
  end
40
40
 
41
41
  class JSUrlImportMeta
@@ -56,6 +56,14 @@ module CKEditor5::Rails::Assets
56
56
  def to_h
57
57
  import_meta.to_h.merge({ url: url })
58
58
  end
59
+
60
+ def preloads
61
+ {
62
+ as: 'script',
63
+ rel: esm? ? 'modulepreload' : 'preload',
64
+ href: url
65
+ }
66
+ end
59
67
  end
60
68
 
61
69
  class JSImportMeta
@@ -19,10 +19,10 @@ module CKEditor5::Rails::Assets
19
19
 
20
20
  def to_html
21
21
  safe_join([
22
+ scripts_import_map_tag,
22
23
  preload_tags,
23
24
  styles_tags,
24
25
  window_scripts_tags,
25
- scripts_import_map_tag,
26
26
  web_component_tag
27
27
  ])
28
28
  end
@@ -51,7 +51,9 @@ module CKEditor5::Rails::Assets
51
51
  return @scripts_import_map_tag if defined?(@scripts_import_map_tag)
52
52
 
53
53
  import_map = bundle.scripts.each_with_object({}) do |script, map|
54
- map[script.import_name] = script.url if script.esm?
54
+ next if !script.esm? || looks_like_url?(script.import_name)
55
+
56
+ map[script.import_name] = script.url
55
57
  end
56
58
 
57
59
  @scripts_import_map_tag = tag.script(
@@ -68,10 +70,28 @@ module CKEditor5::Rails::Assets
68
70
  end
69
71
 
70
72
  def preload_tags
71
- @preload_tags ||= safe_join(bundle.preloads.map do |url|
72
- tag.link(href: url, rel: 'preload', as: self.class.url_resource_preload_type(url),
73
- crossorigin: 'anonymous')
73
+ @preload_tags ||= safe_join(bundle.preloads.map do |preload|
74
+ if preload.is_a?(Hash) && preload[:as] && preload[:href]
75
+ tag.link(
76
+ **preload,
77
+ crossorigin: 'anonymous'
78
+ )
79
+ else
80
+ tag.link(
81
+ href: preload,
82
+ rel: 'preload',
83
+ as: self.class.url_resource_preload_type(preload),
84
+ crossorigin: 'anonymous'
85
+ )
86
+ end
74
87
  end)
75
88
  end
89
+
90
+ def looks_like_url?(str)
91
+ uri = URI.parse(str)
92
+ uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
93
+ rescue URI::InvalidURIError
94
+ false
95
+ end
76
96
  end
77
97
  end
@@ -86,6 +86,20 @@ function loadAsyncImports(imports = []) {
86
86
  }));
87
87
  }
88
88
 
89
+ /**
90
+ * Checks if stylesheet with given href already exists in document
91
+ *
92
+ * @param {string} href - Stylesheet URL to check
93
+ * @returns {boolean} True if stylesheet already exists
94
+ */
95
+ function stylesheetExists(href) {
96
+ return Array
97
+ .from(document.styleSheets)
98
+ .some(sheet =>
99
+ sheet.href === href || sheet.href === new URL(href, window.location.href).href
100
+ );
101
+ }
102
+
89
103
  /**
90
104
  * Dynamically loads CSS files based on configuration
91
105
  *
@@ -96,19 +110,19 @@ function loadAsyncImports(imports = []) {
96
110
  function loadAsyncCSS(stylesheets = []) {
97
111
  const promises = stylesheets.map(href =>
98
112
  new Promise((resolve, reject) => {
99
- const link = document.createElement('link');
113
+ if (stylesheetExists(href)) {
114
+ resolve();
115
+ return;
116
+ }
100
117
 
118
+ const link = document.createElement('link');
101
119
  link.rel = 'stylesheet';
102
120
  link.href = href;
103
121
 
104
122
  link.onerror = reject;
105
- link.onload = () => {
106
- resolve();
107
- };
123
+ link.onload = () => resolve();
108
124
 
109
125
  document.head.appendChild(link);
110
-
111
- return link;
112
126
  })
113
127
  );
114
128
 
@@ -26,6 +26,7 @@ module CKEditor5::Rails
26
26
  bundle = build_base_cdn_bundle(cdn, version, translations)
27
27
  bundle << build_premium_cdn_bundle(cdn, version, translations) if premium
28
28
  bundle << build_ckbox_cdn_bundle(ckbox) if ckbox
29
+ bundle << build_plugins_cdn_bundle(mapped_preset.plugins.items)
29
30
 
30
31
  @__ckeditor_context = {
31
32
  license_key: license_key,
@@ -98,5 +99,11 @@ module CKEditor5::Rails
98
99
  cdn: ckbox[:cdn] || :ckbox
99
100
  )
100
101
  end
102
+
103
+ def build_plugins_cdn_bundle(plugins)
104
+ plugins.each_with_object(Assets::AssetsBundle.new(scripts: [], stylesheets: [])) do |plugin, bundle|
105
+ bundle << plugin.preload_assets_bundle if plugin.preload_assets_bundle.present?
106
+ end
107
+ end
101
108
  end
102
109
  end
@@ -8,8 +8,8 @@ module CKEditor5::Rails::Editor
8
8
  @name = name
9
9
  end
10
10
 
11
- def preload_assets_urls
12
- []
11
+ def preload_assets_bundle
12
+ nil
13
13
  end
14
14
 
15
15
  def to_h
@@ -18,8 +18,11 @@ module CKEditor5::Rails::Editor
18
18
  )
19
19
  end
20
20
 
21
- def preload_assets_urls
22
- @stylesheets + [@js_import_meta.url]
21
+ def preload_assets_bundle
22
+ @preload_assets_bundle ||= CKEditor5::Rails::Assets::AssetsBundle.new(
23
+ scripts: [@js_import_meta],
24
+ stylesheets: @stylesheets
25
+ )
23
26
  end
24
27
 
25
28
  def to_h
@@ -22,6 +22,7 @@ module CKEditor5::Rails
22
22
  end
23
23
 
24
24
  def plugin(name, **kwargs)
25
+ premium(true) if kwargs[:premium] && respond_to?(:premium)
25
26
  register_plugin(PluginsBuilder.create_plugin(name, **kwargs))
26
27
  end
27
28
 
@@ -207,8 +207,8 @@ module CKEditor5::Rails
207
207
  def wproofreader(version: nil, cdn: nil, **config)
208
208
  configure :wproofreader, config
209
209
  plugins do
210
- prepend Plugins::WProofreaderSync.new
211
- append Plugins::WProofreader.new(version: version, cdn: cdn)
210
+ prepend(Plugins::WProofreaderSync.new)
211
+ append(Plugins::WProofreader.new(version: version, cdn: cdn))
212
212
  end
213
213
  end
214
214
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CKEditor5
4
4
  module Rails
5
- VERSION = '1.18.3'
5
+ VERSION = '1.19.1'
6
6
 
7
7
  DEFAULT_CKEDITOR_VERSION = '43.3.1'
8
8
  end
@@ -3,20 +3,6 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer do
6
- let(:test_bundle_class) do
7
- Class.new(CKEditor5::Rails::Assets::AssetsBundle) do
8
- attr_accessor :scripts, :stylesheets
9
-
10
- def initialize(scripts, stylesheets)
11
- @scripts = scripts
12
- @stylesheets = stylesheets
13
- super()
14
- end
15
- end
16
- end
17
-
18
- let(:bundle) { test_bundle_class.new(scripts, stylesheets) }
19
-
20
6
  let(:scripts) do
21
7
  [
22
8
  CKEditor5::Rails::Assets::JSUrlImportMeta.new(
@@ -31,7 +17,7 @@ RSpec.describe CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer do
31
17
  end
32
18
 
33
19
  let(:stylesheets) { ['https://cdn.com/style1.css', 'https://cdn.com/style2.css'] }
34
- let(:preloads) { bundle.preloads }
20
+ let(:bundle) { CKEditor5::Rails::Assets::AssetsBundle.new(scripts: scripts, stylesheets: stylesheets) }
35
21
 
36
22
  subject(:serializer) { described_class.new(bundle) }
37
23
 
@@ -48,43 +34,102 @@ RSpec.describe CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer do
48
34
  describe '#to_html' do
49
35
  subject(:html) { serializer.to_html }
50
36
 
51
- it 'includes window scripts' do
52
- expect(html).to include(
53
- '<script src="https://cdn.com/script1.js" nonce="true" crossorigin="anonymous">'
54
- )
37
+ it 'includes window script tags' do
38
+ expect(html).to have_tag('script', with: {
39
+ src: 'https://cdn.com/script1.js',
40
+ nonce: 'true',
41
+ crossorigin: 'anonymous'
42
+ })
55
43
  end
56
44
 
57
45
  it 'includes import map' do
58
- expect(html).to include('type="importmap"')
59
- expect(html).to include('"@ckeditor/script2":"https://cdn.com/script2.js"')
46
+ expect(html).to have_tag('script', with: { type: 'importmap' }) do
47
+ with_text(%r{"@ckeditor/script2":"https://cdn\.com/script2\.js"})
48
+ end
60
49
  end
61
50
 
62
- it 'includes stylesheet links' do
63
- stylesheets.each do |url|
64
- expect(html).to include("<link href=\"#{url}\" rel=\"stylesheet\" crossorigin=\"anonymous\">")
65
- end
51
+ it 'includes import map with correct attributes' do
52
+ expect(html).to have_tag('script', with: {
53
+ type: 'importmap',
54
+ nonce: 'true'
55
+ })
66
56
  end
67
57
 
68
- it 'includes preload links' do
69
- expect(html).to include(
70
- '<link href="https://cdn.com/style1.css" rel="preload" as="style" crossorigin="anonymous">'
58
+ it 'does not include URL-like imports in import map' do
59
+ bundle = CKEditor5::Rails::Assets::AssetsBundle.new(
60
+ scripts: [
61
+ CKEditor5::Rails::Assets::JSUrlImportMeta.new(
62
+ 'https://cdn.com/script.js',
63
+ import_name: 'https://example.com/module'
64
+ ),
65
+ CKEditor5::Rails::Assets::JSUrlImportMeta.new(
66
+ 'https://cdn.com/script.js',
67
+ import_name: 'module'
68
+ )
69
+ ]
71
70
  )
71
+ html = described_class.new(bundle).to_html
72
72
 
73
- expect(html).to include(
74
- '<link href="https://cdn.com/style2.css" rel="preload" as="style" crossorigin="anonymous">'
75
- )
73
+ expect(html).to have_tag('script', with: { type: 'importmap' }) do
74
+ with_text('{"imports":{"module":"https://cdn.com/script.js"}}')
75
+ end
76
+ end
76
77
 
77
- expect(html).to include(
78
- '<link href="https://cdn.com/script1.js" rel="preload" as="script" crossorigin="anonymous">'
78
+ it 'includes only ESM scripts in import map' do
79
+ bundle = CKEditor5::Rails::Assets::AssetsBundle.new(
80
+ scripts: [
81
+ CKEditor5::Rails::Assets::JSUrlImportMeta.new(
82
+ 'https://cdn.com/script1.js',
83
+ window_name: 'WindowScript'
84
+ ),
85
+ CKEditor5::Rails::Assets::JSUrlImportMeta.new(
86
+ 'https://cdn.com/script2.js',
87
+ import_name: '@ckeditor/module'
88
+ )
89
+ ]
79
90
  )
91
+ html = described_class.new(bundle).to_html
80
92
 
81
- expect(html).to include(
82
- '<link href="https://cdn.com/script2.js" rel="preload" as="script" crossorigin="anonymous">'
83
- )
93
+ expect(html).to have_tag('script', with: { type: 'importmap' }) do
94
+ with_text('{"imports":{"@ckeditor/module":"https://cdn.com/script2.js"}}')
95
+ end
96
+ end
97
+
98
+ it 'includes stylesheet links' do
99
+ stylesheets.each do |url|
100
+ expect(html).to have_tag('link', with: {
101
+ href: url,
102
+ rel: 'stylesheet',
103
+ crossorigin: 'anonymous'
104
+ })
105
+ end
106
+ end
107
+
108
+ it 'includes preload links' do
109
+ scripts.each do |script|
110
+ expect(html).to have_tag('link', with: {
111
+ href: script.url,
112
+ rel: script.esm? ? 'modulepreload' : 'preload',
113
+ as: 'script',
114
+ crossorigin: 'anonymous'
115
+ })
116
+ end
117
+
118
+ stylesheets.each do |url|
119
+ expect(html).to have_tag('link', with: {
120
+ href: url,
121
+ rel: 'preload',
122
+ as: 'style',
123
+ crossorigin: 'anonymous'
124
+ })
125
+ end
84
126
  end
85
127
 
86
128
  it 'includes web component script' do
87
- expect(html).to include('<script type="module" nonce="true">')
129
+ expect(html).to have_tag('script', with: {
130
+ type: 'module',
131
+ nonce: 'true'
132
+ })
88
133
  end
89
134
 
90
135
  it 'memoizes scripts import map' do
@@ -108,4 +153,17 @@ RSpec.describe CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer do
108
153
  expect(described_class.url_resource_preload_type('file.unknown')).to eq('fetch')
109
154
  end
110
155
  end
156
+
157
+ describe '#looks_like_url? (private)' do
158
+ subject(:serializer) { described_class.new(CKEditor5::Rails::Assets::AssetsBundle.new) }
159
+
160
+ it 'returns false for invalid URIs' do
161
+ expect(serializer.send(:looks_like_url?, '@ckeditor/foo')).to be false
162
+ expect(serializer.send(:looks_like_url?, 'http')).to be false
163
+ expect(serializer.send(:looks_like_url?, 'invalid')).to be false
164
+ expect(serializer.send(:looks_like_url?, 'http://[invalid')).to be false
165
+ expect(serializer.send(:looks_like_url?, "http://example.com\nmalicious")).to be false
166
+ expect(serializer.send(:looks_like_url?, 'http://<invalid>')).to be false
167
+ end
168
+ end
111
169
  end
@@ -3,48 +3,45 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe CKEditor5::Rails::Assets::AssetsBundle do
6
- let(:concrete_class) do
7
- Class.new(described_class) do
8
- def scripts
9
- []
10
- end
11
-
12
- def stylesheets
13
- []
14
- end
15
- end
16
- end
17
-
18
6
  describe '#initialize' do
19
- it 'raises error when required methods are not implemented' do
20
- expect { described_class.new }.to raise_error(NotImplementedError)
7
+ it 'initializes with empty arrays by default' do
8
+ bundle = described_class.new
9
+ expect(bundle.scripts).to eq([])
10
+ expect(bundle.stylesheets).to eq([])
21
11
  end
22
12
 
23
- it 'initializes successfully when required methods are implemented' do
24
- expect { concrete_class.new }.not_to raise_error
13
+ it 'accepts scripts and stylesheets' do
14
+ bundle = described_class.new(scripts: [:script], stylesheets: [:stylesheet])
15
+ expect(bundle.scripts).to eq([:script])
16
+ expect(bundle.stylesheets).to eq([:stylesheet])
25
17
  end
26
18
  end
27
19
 
28
20
  describe '#empty?' do
29
- subject(:bundle) { concrete_class.new }
30
-
31
21
  it 'returns true when no assets are present' do
22
+ bundle = described_class.new
32
23
  expect(bundle).to be_empty
33
24
  end
25
+
26
+ it 'returns false when scripts are present' do
27
+ bundle = described_class.new(scripts: [:script])
28
+ expect(bundle).not_to be_empty
29
+ end
30
+
31
+ it 'returns false when stylesheets are present' do
32
+ bundle = described_class.new(stylesheets: [:stylesheet])
33
+ expect(bundle).not_to be_empty
34
+ end
34
35
  end
35
36
 
36
37
  describe '#translations_scripts' do
37
- let(:bundle) { concrete_class.new }
38
38
  let(:translation_script) do
39
39
  instance_double(CKEditor5::Rails::Assets::JSUrlImportMeta, translation?: true)
40
40
  end
41
41
  let(:regular_script) { instance_double(CKEditor5::Rails::Assets::JSUrlImportMeta, translation?: false) }
42
42
 
43
- before do
44
- allow(bundle).to receive(:scripts).and_return([translation_script, regular_script])
45
- end
46
-
47
43
  it 'returns only translation scripts' do
44
+ bundle = described_class.new(scripts: [translation_script, regular_script])
48
45
  expect(bundle.translations_scripts).to eq([translation_script])
49
46
  end
50
47
  end
@@ -54,41 +51,8 @@ RSpec.describe CKEditor5::Rails::Assets::AssetsBundle do
54
51
  let(:script2) { instance_double(CKEditor5::Rails::Assets::JSUrlImportMeta) }
55
52
  let(:stylesheet1) { '/path/to/style1.css' }
56
53
  let(:stylesheet2) { '/path/to/style2.css' }
57
-
58
- let(:bundle1) do
59
- Class.new(described_class) do
60
- attr_writer :scripts, :stylesheets
61
-
62
- def scripts
63
- @scripts ||= []
64
- end
65
-
66
- def stylesheets
67
- @stylesheets ||= []
68
- end
69
- end.new
70
- end
71
-
72
- let(:bundle2) do
73
- Class.new(described_class) do
74
- attr_writer :scripts, :stylesheets
75
-
76
- def scripts
77
- @scripts ||= []
78
- end
79
-
80
- def stylesheets
81
- @stylesheets ||= []
82
- end
83
- end.new
84
- end
85
-
86
- before do
87
- bundle1.scripts = [script1]
88
- bundle1.stylesheets = [stylesheet1]
89
- bundle2.scripts = [script2]
90
- bundle2.stylesheets = [stylesheet2]
91
- end
54
+ let(:bundle1) { described_class.new(scripts: [script1], stylesheets: [stylesheet1]) }
55
+ let(:bundle2) { described_class.new(scripts: [script2], stylesheets: [stylesheet2]) }
92
56
 
93
57
  it 'raises TypeError when argument is not an AssetsBundle' do
94
58
  expect { bundle1 << 'not a bundle' }.to raise_error(TypeError)
@@ -103,36 +67,23 @@ RSpec.describe CKEditor5::Rails::Assets::AssetsBundle do
103
67
  end
104
68
 
105
69
  describe '#preloads' do
106
- let(:script1) { instance_double(CKEditor5::Rails::Assets::JSUrlImportMeta, url: '/js/script1.js') }
107
- let(:script2) { instance_double(CKEditor5::Rails::Assets::JSUrlImportMeta, url: '/js/script2.js') }
70
+ let(:script1) { CKEditor5::Rails::Assets::JSUrlImportMeta.new('/js/script1.js', import_name: 'script1') }
71
+ let(:script2) { CKEditor5::Rails::Assets::JSUrlImportMeta.new('/js/script2.js', import_name: 'script2') }
108
72
  let(:stylesheet1) { '/css/style1.css' }
109
73
  let(:stylesheet2) { '/css/style2.css' }
110
-
111
74
  let(:bundle) do
112
- Class.new(described_class) do
113
- attr_writer :scripts, :stylesheets
114
-
115
- def scripts
116
- @scripts ||= []
117
- end
118
-
119
- def stylesheets
120
- @stylesheets ||= []
121
- end
122
- end.new
123
- end
124
-
125
- before do
126
- bundle.scripts = [script1, script2]
127
- bundle.stylesheets = [stylesheet1, stylesheet2]
75
+ described_class.new(
76
+ scripts: [script1, script2],
77
+ stylesheets: [stylesheet1, stylesheet2]
78
+ )
128
79
  end
129
80
 
130
81
  it 'returns array of stylesheet paths and script urls' do
131
82
  expect(bundle.preloads).to eq([
132
83
  '/css/style1.css',
133
84
  '/css/style2.css',
134
- '/js/script1.js',
135
- '/js/script2.js'
85
+ { as: 'script', rel: 'modulepreload', href: '/js/script1.js' },
86
+ { as: 'script', rel: 'modulepreload', href: '/js/script2.js' }
136
87
  ])
137
88
  end
138
89
  end
@@ -168,6 +119,18 @@ RSpec.describe CKEditor5::Rails::Assets::JSUrlImportMeta do
168
119
  })
169
120
  end
170
121
  end
122
+
123
+ describe '#preloads' do
124
+ it 'returns preload hash' do
125
+ meta = described_class.new(url, window_name: 'module')
126
+ expect(meta.preloads).to eq({ as: 'script', rel: 'preload', href: url })
127
+ end
128
+
129
+ it 'returns modulepreload hash when esm' do
130
+ meta = described_class.new(url, import_name: 'module')
131
+ expect(meta.preloads).to include({ as: 'script', rel: 'modulepreload', href: url })
132
+ end
133
+ end
171
134
  end
172
135
 
173
136
  RSpec.describe CKEditor5::Rails::Assets::JSImportMeta do
@@ -108,6 +108,30 @@ RSpec.describe CKEditor5::Rails::Cdn::Helpers do
108
108
  helper.ckeditor5_assets(preset: :default)
109
109
  end
110
110
  end
111
+
112
+ context 'with plugins having preload assets' do
113
+ let(:plugin_bundle) { CKEditor5::Rails::Assets::AssetsBundle.new(scripts: ['plugin.js']) }
114
+ let(:plugin) { instance_double('Plugin', preload_assets_bundle: plugin_bundle) }
115
+ let(:plugin_without_preload) { instance_double('Plugin', preload_assets_bundle: nil) }
116
+
117
+ before do
118
+ allow(preset).to receive_message_chain(:plugins, :items)
119
+ .and_return([plugin, plugin_without_preload])
120
+ end
121
+
122
+ it 'includes plugin preload assets in the bundle' do
123
+ helper.ckeditor5_assets(preset: :default)
124
+ expect(context[:bundle].scripts).to include('plugin.js')
125
+ end
126
+
127
+ it 'merges plugin assets with the main bundle' do
128
+ expect(serializer).to receive(:to_html)
129
+ helper.ckeditor5_assets(preset: :default)
130
+
131
+ bundle = context[:bundle]
132
+ expect(bundle.scripts).to include('plugin.js')
133
+ end
134
+ end
111
135
  end
112
136
 
113
137
  context 'when overriding preset values' do
@@ -33,11 +33,11 @@ RSpec.describe CKEditor5::Rails::Editor::PropsBasePlugin do
33
33
  end
34
34
  end
35
35
 
36
- describe '#preload_assets_urls' do
37
- it 'returns empty array' do
36
+ describe '#preload_assets_bundle' do
37
+ it 'returns nil by default' do
38
38
  plugin = described_class.new(:Bold)
39
39
 
40
- expect(plugin.preload_assets_urls).to eq([])
40
+ expect(plugin.preload_assets_bundle).to be_nil
41
41
  end
42
42
  end
43
43
 
@@ -8,7 +8,7 @@ RSpec.describe CKEditor5::Rails::Editor::PropsExternalPlugin do
8
8
  plugin = described_class.new('Test', script: 'https://example.org/plugin.js')
9
9
 
10
10
  expect(plugin.name).to eq('Test')
11
- expect(plugin.preload_assets_urls).to include('https://example.org/plugin.js')
11
+ expect(plugin.preload_assets_bundle.scripts.first.url).to eq('https://example.org/plugin.js')
12
12
  end
13
13
 
14
14
  it 'accepts optional parameters' do
@@ -20,23 +20,21 @@ RSpec.describe CKEditor5::Rails::Editor::PropsExternalPlugin do
20
20
  stylesheets: ['https://example.org/style.css']
21
21
  )
22
22
 
23
- expect(plugin.preload_assets_urls).to include('https://example.org/style.css')
23
+ expect(plugin.preload_assets_bundle.stylesheets).to include('https://example.org/style.css')
24
24
  end
25
25
  end
26
26
 
27
- describe '#preload_assets_urls' do
28
- it 'returns array with script and stylesheets urls' do
27
+ describe '#preload_assets_bundle' do
28
+ it 'returns bundle with script and stylesheets' do
29
29
  plugin = described_class.new(
30
30
  'Test',
31
31
  script: 'https://example.org/plugin.js',
32
32
  stylesheets: ['https://example.org/style1.css', 'https://example.org/style2.css']
33
33
  )
34
34
 
35
- expect(plugin.preload_assets_urls).to eq([
36
- 'https://example.org/style1.css',
37
- 'https://example.org/style2.css',
38
- 'https://example.org/plugin.js'
39
- ])
35
+ bundle = plugin.preload_assets_bundle
36
+ expect(bundle.scripts.first.url).to eq('https://example.org/plugin.js')
37
+ expect(bundle.stylesheets).to eq(['https://example.org/style1.css', 'https://example.org/style2.css'])
40
38
  end
41
39
  end
42
40
 
@@ -78,10 +78,28 @@ RSpec.describe CKEditor5::Rails::Engine do
78
78
  end
79
79
  end
80
80
 
81
- describe 'simple_form initializer', if: defined?(SimpleForm) do
82
- it 'registers ckeditor5 input type' do
83
- expect(SimpleForm::FormBuilder.mappings[:ckeditor5])
84
- .to eq(CKEditor5::Rails::Hooks::SimpleForm::CKEditor5Input)
81
+ describe 'simple_form initializer' do
82
+ context 'when SimpleForm is defined' do
83
+ it 'registers ckeditor5 input type' do
84
+ expect(SimpleForm::FormBuilder.mappings[:ckeditor5])
85
+ .to eq(CKEditor5::Rails::Hooks::SimpleForm::CKEditor5Input)
86
+ end
87
+ end
88
+
89
+ context 'when SimpleForm is not defined' do
90
+ before do
91
+ @simple_form = SimpleForm if defined?(SimpleForm)
92
+ Object.send(:remove_const, :SimpleForm) if defined?(SimpleForm)
93
+ end
94
+
95
+ after do
96
+ Object.const_set(:SimpleForm, @simple_form) if @simple_form
97
+ end
98
+
99
+ it 'does not raise error' do
100
+ initializer = described_class.initializers.find { |i| i.name == 'ckeditor5.simple_form' }
101
+ expect { initializer.run(Rails.application) }.not_to raise_error
102
+ end
85
103
  end
86
104
  end
87
105
  end
@@ -14,12 +14,10 @@ RSpec.describe CKEditor5::Rails::Plugins::WProofreader do
14
14
  expect(plugin.name).to eq(:WProofreader)
15
15
  end
16
16
 
17
- it 'returns correct preload assets urls' do
18
- expected_urls = [
19
- "#{default_cdn}@#{default_version}/dist/browser/index.css",
20
- "#{default_cdn}@#{default_version}/dist/browser/index.js"
21
- ]
22
- expect(plugin.preload_assets_urls).to eq(expected_urls)
17
+ it 'returns correct preload assets bundle' do
18
+ bundle = plugin.preload_assets_bundle
19
+ expect(bundle.stylesheets).to eq(["#{default_cdn}@#{default_version}/dist/browser/index.css"])
20
+ expect(bundle.scripts.first.url).to eq("#{default_cdn}@#{default_version}/dist/browser/index.js")
23
21
  end
24
22
 
25
23
  it 'returns correct hash representation' do
@@ -39,12 +37,10 @@ RSpec.describe CKEditor5::Rails::Plugins::WProofreader do
39
37
  let(:custom_version) { '4.0.0' }
40
38
  subject(:plugin) { described_class.new(version: custom_version, cdn: custom_cdn) }
41
39
 
42
- it 'returns correct preload assets urls with custom CDN' do
43
- expected_urls = [
44
- "#{custom_cdn}@#{custom_version}/dist/browser/index.css",
45
- "#{custom_cdn}@#{custom_version}/dist/browser/index.js"
46
- ]
47
- expect(plugin.preload_assets_urls).to eq(expected_urls)
40
+ it 'returns correct preload assets bundle with custom CDN' do
41
+ bundle = plugin.preload_assets_bundle
42
+ expect(bundle.stylesheets).to eq(["#{custom_cdn}@#{custom_version}/dist/browser/index.css"])
43
+ expect(bundle.scripts.first.url).to eq("#{custom_cdn}@#{custom_version}/dist/browser/index.js")
48
44
  end
49
45
 
50
46
  it 'returns correct hash representation with custom CDN' do
@@ -226,6 +226,48 @@ RSpec.describe CKEditor5::Rails::Presets::PresetBuilder do
226
226
  end
227
227
  end
228
228
 
229
+ describe '#plugin' do
230
+ it 'adds normalized plugin to config' do
231
+ plugin = builder.plugin('Test')
232
+
233
+ expect(builder.config[:plugins]).to include(plugin)
234
+ expect(plugin).to be_a(CKEditor5::Rails::Editor::PropsPlugin)
235
+ end
236
+
237
+ it 'accepts plugin options' do
238
+ plugin = builder.plugin('Test', premium: true)
239
+
240
+ expect(plugin.to_h[:import_name]).to eq('ckeditor5-premium-features')
241
+ end
242
+
243
+ it 'sets premium flag when premium option provided' do
244
+ builder.plugin('Test', premium: true)
245
+ expect(builder.premium?).to be true
246
+ end
247
+ end
248
+
249
+ describe '#external_plugin' do
250
+ it 'adds external plugin to config' do
251
+ plugin = builder.external_plugin('Test', script: 'https://example.org/script.js')
252
+
253
+ expect(builder.config[:plugins]).to include(plugin)
254
+ expect(plugin).to be_a(CKEditor5::Rails::Editor::PropsExternalPlugin)
255
+ end
256
+
257
+ it 'accepts plugin options' do
258
+ plugin = builder.external_plugin(
259
+ 'Test',
260
+ script: 'https://example.org/script.js',
261
+ import_as: 'ABC',
262
+ stylesheets: ['https://example.org/style.css']
263
+ )
264
+
265
+ expect(plugin.to_h[:import_name]).to eq('https://example.org/script.js')
266
+ expect(plugin.to_h[:import_as]).to eq('ABC')
267
+ expect(plugin.to_h[:stylesheets]).to include('https://example.org/style.css')
268
+ end
269
+ end
270
+
229
271
  describe '#cdn' do
230
272
  it 'returns current cdn when called without arguments' do
231
273
  expect(builder.cdn).to eq(:jsdelivr)
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.18.3
4
+ version: 1.19.1
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-28 00:00:00.000000000 Z
12
+ date: 2024-11-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails