ckeditor5 1.23.2 → 1.23.3
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 +2 -2
- data/lib/ckeditor5/rails/assets/webcomponent_bundle.rb +48 -32
- data/lib/ckeditor5/rails/assets/webcomponents/components/editor.mjs +6 -2
- data/lib/ckeditor5/rails/assets/webcomponents/utils.mjs +21 -1
- data/lib/ckeditor5/rails/cdn/helpers.rb +2 -1
- data/lib/ckeditor5/rails/context/helpers.rb +2 -2
- data/lib/ckeditor5/rails/context/preset_builder.rb +2 -1
- data/lib/ckeditor5/rails/editor/helpers/config_helpers.rb +4 -1
- data/lib/ckeditor5/rails/editor/helpers/editor_helpers.rb +8 -4
- data/lib/ckeditor5/rails/editor/props.rb +1 -1
- data/lib/ckeditor5/rails/editor/props_inline_plugin.rb +29 -0
- data/lib/ckeditor5/rails/presets/concerns/plugin_methods.rb +30 -10
- data/lib/ckeditor5/rails/presets/preset_builder.rb +2 -1
- data/lib/ckeditor5/rails/version.rb +1 -1
- data/spec/lib/ckeditor5/rails/context/helpers_spec.rb +13 -0
- data/spec/lib/ckeditor5/rails/editor/helpers/config_helpers_spec.rb +16 -0
- data/spec/lib/ckeditor5/rails/editor/helpers/editor_helpers_spec.rb +50 -0
- data/spec/lib/ckeditor5/rails/hooks/form_spec.rb +0 -14
- 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: 0d717a5397379dffdfd252d92c6b6f58a524430c0ae99798a46ff7f71556d3b9
|
4
|
+
data.tar.gz: 046715b5823ae3f38b3b2c1175fa4f1bdd5e34f9e260777dbcb714be287ec18b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a552a32f61e75023f913b6df8c414b9af075942b97144e10582c4cfa1955bf988de8be6f2e652618ae3640364691f20c9ae3b679f3eb2017f78d3aa740580616
|
7
|
+
data.tar.gz: 934d9853eb527d2f8a65ecc87e6c4fac989a0bb1133f5a3b6154ebe8aca3239199a84a801c048e6909919b76295bae3e29f6af9e60f98f2cee46cfb8167647d7
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# CKEditor 5 Rails Integration ✨
|
2
2
|
|
3
3
|
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
4
|
-

|
5
|
-

|
4
|
+
[](https://rubygems.org/gems/ckeditor5)
|
5
|
+
[](https://rubygems.org/gems/ckeditor5)
|
6
6
|
[](https://codecov.io/gh/mati365/ckeditor5-rails)
|
7
7
|
[](http://makeapullrequest.com)
|
8
8
|

|
@@ -3,38 +3,54 @@
|
|
3
3
|
require 'singleton'
|
4
4
|
require 'terser'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
6
|
+
require_relative '../editor/props_inline_plugin'
|
7
|
+
|
8
|
+
module CKEditor5::Rails
|
9
|
+
module Assets
|
10
|
+
class WebComponentBundle
|
11
|
+
include ActionView::Helpers::TagHelper
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
WEBCOMPONENTS_PATH = File.join(__dir__, 'webcomponents')
|
15
|
+
WEBCOMPONENTS_MODULES = [
|
16
|
+
'utils.mjs',
|
17
|
+
'components/editable.mjs',
|
18
|
+
'components/ui-part.mjs',
|
19
|
+
'components/editor.mjs',
|
20
|
+
'components/context.mjs'
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
def source
|
24
|
+
@source ||= compress_source(raw_source)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_html
|
28
|
+
@to_html ||= tag.script(source, type: 'module', nonce: true)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def raw_source
|
34
|
+
@raw_source ||= WEBCOMPONENTS_MODULES.map do |file|
|
35
|
+
content = File.read(File.join(WEBCOMPONENTS_PATH, file))
|
36
|
+
|
37
|
+
if file == 'utils.mjs'
|
38
|
+
inject_inline_code_signatures(content)
|
39
|
+
else
|
40
|
+
content
|
41
|
+
end
|
42
|
+
end.join("\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
def inject_inline_code_signatures(content)
|
46
|
+
json_signatures = Editor::InlinePluginsSignaturesRegistry.instance.to_a.to_json
|
47
|
+
|
48
|
+
content.sub('__INLINE_CODE_SIGNATURES_PLACEHOLDER__', json_signatures)
|
49
|
+
end
|
50
|
+
|
51
|
+
def compress_source(code)
|
52
|
+
Terser.new(compress: true, mangle: true).compile(code).html_safe
|
53
|
+
end
|
38
54
|
end
|
39
55
|
end
|
40
56
|
end
|
@@ -568,10 +568,14 @@ class CKEditorComponent extends HTMLElement {
|
|
568
568
|
}
|
569
569
|
|
570
570
|
/**
|
571
|
-
* Gets bundle JSON description from translations attribute
|
571
|
+
* Gets bundle JSON description from translations attribute.
|
572
|
+
*
|
573
|
+
* **warning**: It's present only if the editor is loaded lazily.
|
572
574
|
*/
|
573
575
|
#getBundle() {
|
574
|
-
return this.#bundle ||= JSON.parse(
|
576
|
+
return this.#bundle ||= JSON.parse(
|
577
|
+
this.getAttribute('bundle') || '{ "scripts": [], "stylesheets": [] }'
|
578
|
+
);
|
575
579
|
}
|
576
580
|
|
577
581
|
|
@@ -20,6 +20,22 @@ function execIfDOMReady(callback) {
|
|
20
20
|
}
|
21
21
|
}
|
22
22
|
|
23
|
+
/**
|
24
|
+
* Verifies code signature using SHA-256 hash.
|
25
|
+
*
|
26
|
+
* @param {string} code - Source code to verify
|
27
|
+
* @returns {Promise<boolean>} True if code signature is valid
|
28
|
+
*/
|
29
|
+
async function verifyCodeSignature(code) {
|
30
|
+
const signature = await crypto.subtle
|
31
|
+
.digest('SHA-256', new TextEncoder().encode(code))
|
32
|
+
.then(hash => Array.from(new Uint8Array(hash))
|
33
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
34
|
+
.join(''));
|
35
|
+
|
36
|
+
return __INLINE_CODE_SIGNATURES_PLACEHOLDER__.includes(signature);
|
37
|
+
}
|
38
|
+
|
23
39
|
/**
|
24
40
|
* Dynamically imports modules based on configuration
|
25
41
|
*
|
@@ -31,10 +47,14 @@ function execIfDOMReady(callback) {
|
|
31
47
|
* @param {Object} imports[].window_name - Global window object name (for external type)
|
32
48
|
* @param {('inline'|'external')} imports[].type - Type of import
|
33
49
|
* @returns {Promise<Array<any>>} Array of loaded modules
|
34
|
-
* @throws {Error} When plugin loading fails
|
50
|
+
* @throws {Error} When plugin loading fails or signature validation fails
|
35
51
|
*/
|
36
52
|
function loadAsyncImports(imports = []) {
|
37
53
|
const loadInlinePlugin = async ({ name, code }) => {
|
54
|
+
if (!await verifyCodeSignature(code)) {
|
55
|
+
throw new Error(`Invalid code signature for inline plugin "${name}"!`);
|
56
|
+
}
|
57
|
+
|
38
58
|
const module = await import(`data:text/javascript,${encodeURIComponent(code)}`);
|
39
59
|
|
40
60
|
if (!module.default) {
|
@@ -40,8 +40,8 @@ module CKEditor5::Rails::Context
|
|
40
40
|
# <% preset = ckeditor5_context_preset do
|
41
41
|
# plugins :Comments, :TrackChanges, :Collaboration # Shared functionality plugins
|
42
42
|
# end %>
|
43
|
-
def ckeditor5_context_preset(&block)
|
44
|
-
PresetBuilder.new(&block)
|
43
|
+
def ckeditor5_context_preset(**kwargs, &block)
|
44
|
+
PresetBuilder.new(disallow_inline_plugins: true, **kwargs, &block)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -50,7 +50,10 @@ module CKEditor5::Rails::Editor::Helpers
|
|
50
50
|
|
51
51
|
raise ArgumentError, 'Configuration block is required for preset definition' unless block_given?
|
52
52
|
|
53
|
-
CKEditor5::Rails::Presets::PresetBuilder.new(
|
53
|
+
CKEditor5::Rails::Presets::PresetBuilder.new(
|
54
|
+
disallow_inline_plugins: true,
|
55
|
+
&block
|
56
|
+
)
|
54
57
|
end
|
55
58
|
end
|
56
59
|
end
|
@@ -56,7 +56,7 @@ module CKEditor5::Rails
|
|
56
56
|
# <%= form_for @post do |f| %>
|
57
57
|
# <%= f.ckeditor5 :content, required: true %>
|
58
58
|
# <% end %>
|
59
|
-
def ckeditor5_editor( # rubocop:disable Metrics/ParameterLists
|
59
|
+
def ckeditor5_editor( # rubocop:disable Metrics/ParameterLists,Metrics/CyclomaticComplexity
|
60
60
|
preset: nil,
|
61
61
|
config: nil, extra_config: {}, type: nil,
|
62
62
|
initial_data: nil, watchdog: true,
|
@@ -78,7 +78,9 @@ module CKEditor5::Rails
|
|
78
78
|
|
79
79
|
editor_props = Editor::Props.new(
|
80
80
|
type, config,
|
81
|
-
|
81
|
+
# Use fallback only if there is no `ckeditor5_assets` in the head.
|
82
|
+
# So web-component will be loaded from the CDN.
|
83
|
+
bundle: context[:lazy] ? context[:bundle] : nil,
|
82
84
|
watchdog: watchdog,
|
83
85
|
editable_height: editable_height || preset.editable_height
|
84
86
|
)
|
@@ -159,13 +161,15 @@ module CKEditor5::Rails
|
|
159
161
|
|
160
162
|
return {
|
161
163
|
bundle: create_preset_bundle(found_preset),
|
162
|
-
preset: found_preset
|
164
|
+
preset: found_preset,
|
165
|
+
lazy: true
|
163
166
|
}
|
164
167
|
end
|
165
168
|
|
166
169
|
{
|
167
170
|
bundle: nil,
|
168
|
-
preset: Engine.default_preset
|
171
|
+
preset: Engine.default_preset,
|
172
|
+
lazy: true
|
169
173
|
}
|
170
174
|
end
|
171
175
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'singleton'
|
4
|
+
require 'digest'
|
5
|
+
|
3
6
|
require_relative 'props_base_plugin'
|
4
7
|
|
5
8
|
module CKEditor5::Rails::Editor
|
@@ -11,6 +14,8 @@ module CKEditor5::Rails::Editor
|
|
11
14
|
|
12
15
|
@code = code
|
13
16
|
validate_code!
|
17
|
+
|
18
|
+
InlinePluginsSignaturesRegistry.instance.register(code)
|
14
19
|
end
|
15
20
|
|
16
21
|
def to_h
|
@@ -32,4 +37,28 @@ module CKEditor5::Rails::Editor
|
|
32
37
|
'Code must include `export default` that exports plugin definition!'
|
33
38
|
end
|
34
39
|
end
|
40
|
+
|
41
|
+
class InlinePluginsSignaturesRegistry
|
42
|
+
include Singleton
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
@signatures = Set.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def register(code)
|
49
|
+
signature = generate_signature(code)
|
50
|
+
@signatures.add(signature)
|
51
|
+
signature
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_a
|
55
|
+
@signatures.to_a
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def generate_signature(code)
|
61
|
+
Digest::SHA256.hexdigest(code)
|
62
|
+
end
|
63
|
+
end
|
35
64
|
end
|
@@ -1,21 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_support'
|
4
|
+
|
3
5
|
module CKEditor5::Rails
|
4
6
|
module Presets
|
5
7
|
module Concerns
|
6
8
|
module PluginMethods
|
7
|
-
|
9
|
+
extend ActiveSupport::Concern
|
8
10
|
|
9
|
-
|
10
|
-
#
|
11
|
-
# @param plugin_obj [Editor::PropsBasePlugin] Plugin instance to register
|
12
|
-
# @return [Editor::PropsBasePlugin] The registered plugin
|
13
|
-
def register_plugin(plugin_obj)
|
14
|
-
config[:plugins] << plugin_obj
|
15
|
-
plugin_obj
|
16
|
-
end
|
11
|
+
class DisallowedInlinePlugin < ArgumentError; end
|
17
12
|
|
18
|
-
|
13
|
+
included do
|
14
|
+
attr_reader :disallow_inline_plugins
|
15
|
+
end
|
19
16
|
|
20
17
|
# Registers an external plugin loaded from a URL
|
21
18
|
#
|
@@ -89,6 +86,29 @@ module CKEditor5::Rails
|
|
89
86
|
builder.instance_eval(&block) if block_given?
|
90
87
|
builder
|
91
88
|
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def looks_like_inline_plugin?(plugin)
|
93
|
+
plugin.to_h[:type] == :inline
|
94
|
+
end
|
95
|
+
|
96
|
+
# Register a plugin in the editor configuration.
|
97
|
+
#
|
98
|
+
# It will raise an error if inline plugins are not allowed and the plugin is an inline plugin.
|
99
|
+
# Most likely, this is being thrown when you use inline_plugin definition in a place where
|
100
|
+
# it's not allowed (e.g. in a preset definition placed in controller).
|
101
|
+
#
|
102
|
+
# @param plugin_obj [Editor::PropsBasePlugin] Plugin instance to register
|
103
|
+
# @return [Editor::PropsBasePlugin] The registered plugin
|
104
|
+
def register_plugin(plugin_obj)
|
105
|
+
if disallow_inline_plugins && looks_like_inline_plugin?(plugin_obj)
|
106
|
+
raise DisallowedInlinePlugin, 'Inline plugins are not allowed here.'
|
107
|
+
end
|
108
|
+
|
109
|
+
config[:plugins] << plugin_obj
|
110
|
+
plugin_obj
|
111
|
+
end
|
92
112
|
end
|
93
113
|
end
|
94
114
|
end
|
@@ -107,5 +107,18 @@ RSpec.describe CKEditor5::Rails::Context::Helpers do
|
|
107
107
|
preset: :custom
|
108
108
|
)
|
109
109
|
end
|
110
|
+
|
111
|
+
it 'raises error when trying to define inline plugin' do
|
112
|
+
expect do
|
113
|
+
helper.ckeditor5_context_preset do
|
114
|
+
inline_plugin :TestPlugin, <<~JS
|
115
|
+
export default class TestPlugin { }
|
116
|
+
JS
|
117
|
+
end
|
118
|
+
end.to raise_error(
|
119
|
+
CKEditor5::Rails::Presets::Concerns::PluginMethods::DisallowedInlinePlugin,
|
120
|
+
'Inline plugins are not allowed here.'
|
121
|
+
)
|
122
|
+
end
|
110
123
|
end
|
111
124
|
end
|
@@ -38,6 +38,22 @@ RSpec.describe CKEditor5::Rails::Editor::Helpers::Config do
|
|
38
38
|
it 'yields the block to PresetBuilder' do
|
39
39
|
expect { |b| helper.ckeditor5_preset(&b) }.to yield_control
|
40
40
|
end
|
41
|
+
|
42
|
+
it 'does not allow inline plugins definition' do
|
43
|
+
expect do
|
44
|
+
helper.ckeditor5_preset do
|
45
|
+
inline_plugin :CustomPlugin, <<~JS
|
46
|
+
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
47
|
+
export default class CustomPlugin extends Plugin {
|
48
|
+
static get pluginName() { return 'CustomPlugin'; }
|
49
|
+
}
|
50
|
+
JS
|
51
|
+
end
|
52
|
+
end.to raise_error(
|
53
|
+
CKEditor5::Rails::Presets::Concerns::PluginMethods::DisallowedInlinePlugin,
|
54
|
+
'Inline plugins are not allowed here.'
|
55
|
+
)
|
56
|
+
end
|
41
57
|
end
|
42
58
|
|
43
59
|
context 'when neither name nor block is provided' do
|
@@ -58,6 +58,7 @@ RSpec.describe CKEditor5::Rails::Editor::Helpers::Editor do
|
|
58
58
|
result = helper.ckeditor5_context_or_fallback(:custom)
|
59
59
|
expect(result).to match({
|
60
60
|
bundle: 'custom-bundle',
|
61
|
+
lazy: true,
|
61
62
|
preset: custom_preset
|
62
63
|
})
|
63
64
|
end
|
@@ -71,9 +72,33 @@ RSpec.describe CKEditor5::Rails::Editor::Helpers::Editor do
|
|
71
72
|
result = helper.ckeditor5_context_or_fallback(nil)
|
72
73
|
expect(result).to match({
|
73
74
|
bundle: nil,
|
75
|
+
lazy: true,
|
74
76
|
preset: :default
|
75
77
|
})
|
76
78
|
end
|
79
|
+
|
80
|
+
it 'includes lazy flag in fallback context' do
|
81
|
+
allow(CKEditor5::Rails::Engine).to receive(:default_preset)
|
82
|
+
.and_return(:default)
|
83
|
+
|
84
|
+
result = helper.ckeditor5_context_or_fallback(nil)
|
85
|
+
expect(result[:lazy]).to be true
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'includes lazy flag in preset context' do
|
89
|
+
custom_preset = instance_double(CKEditor5::Rails::Presets::PresetBuilder)
|
90
|
+
|
91
|
+
allow(CKEditor5::Rails::Engine).to receive(:find_preset)
|
92
|
+
.with(:custom)
|
93
|
+
.and_return(custom_preset)
|
94
|
+
|
95
|
+
allow(helper).to receive(:create_preset_bundle)
|
96
|
+
.with(custom_preset)
|
97
|
+
.and_return('custom-bundle')
|
98
|
+
|
99
|
+
result = helper.ckeditor5_context_or_fallback(:custom)
|
100
|
+
expect(result[:lazy]).to be true
|
101
|
+
end
|
77
102
|
end
|
78
103
|
|
79
104
|
describe '#ckeditor5_editor' do
|
@@ -225,6 +250,31 @@ RSpec.describe CKEditor5::Rails::Editor::Helpers::Editor do
|
|
225
250
|
expect(helper.ckeditor5_editor(editable_height: 600)).to include('editable-height="600px"')
|
226
251
|
end
|
227
252
|
end
|
253
|
+
|
254
|
+
context 'when using bundles' do
|
255
|
+
let(:bundle) { 'test-bundle' }
|
256
|
+
let(:context) { { preset: :default, bundle: bundle, lazy: true } }
|
257
|
+
|
258
|
+
it 'uses bundle from context when lazy is true' do
|
259
|
+
expect(CKEditor5::Rails::Editor::Props).to receive(:new)
|
260
|
+
.with(anything, anything, hash_including(bundle: bundle))
|
261
|
+
.and_call_original
|
262
|
+
|
263
|
+
helper.ckeditor5_editor
|
264
|
+
end
|
265
|
+
|
266
|
+
context 'when lazy is false' do
|
267
|
+
let(:context) { { preset: :default, bundle: bundle, lazy: false } }
|
268
|
+
|
269
|
+
it 'does not use bundle from context' do
|
270
|
+
expect(CKEditor5::Rails::Editor::Props).to receive(:new)
|
271
|
+
.with(anything, anything, hash_including(bundle: nil))
|
272
|
+
.and_call_original
|
273
|
+
|
274
|
+
helper.ckeditor5_editor
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
228
278
|
end
|
229
279
|
|
230
280
|
describe '#ckeditor5_editable' do
|
@@ -18,24 +18,10 @@ RSpec.describe CKEditor5::Rails::Hooks::Form do
|
|
18
18
|
subject(:rendered_editor) { builder.build_editor(:content) }
|
19
19
|
|
20
20
|
it 'renders ckeditor element' do
|
21
|
-
bundle_json = {
|
22
|
-
scripts: [
|
23
|
-
{
|
24
|
-
import_name: 'ckeditor5',
|
25
|
-
url: 'https://cdn.jsdelivr.net/npm/ckeditor5@34.1.0/dist/browser/ckeditor5.js',
|
26
|
-
translation: false
|
27
|
-
}
|
28
|
-
],
|
29
|
-
stylesheets: [
|
30
|
-
'https://cdn.jsdelivr.net/npm/ckeditor5@34.1.0/dist/browser/ckeditor5.css'
|
31
|
-
]
|
32
|
-
}.to_json
|
33
|
-
|
34
21
|
attrs = {
|
35
22
|
name: 'post[content]',
|
36
23
|
id: 'post_content',
|
37
24
|
type: 'ClassicEditor',
|
38
|
-
bundle: bundle_json,
|
39
25
|
watchdog: 'true'
|
40
26
|
}
|
41
27
|
|
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.23.
|
4
|
+
version: 1.23.3
|
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-12-
|
12
|
+
date: 2024-12-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|