ckeditor5 1.9.0 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Executes callback when DOM is ready
3
+ *
4
+ * @param {() => void} callback - Function to execute when DOM is ready
5
+ */
6
+ function execIfDOMReady(callback) {
7
+ switch (document.readyState) {
8
+ case 'loading':
9
+ document.addEventListener('DOMContentLoaded', callback, { once: true });
10
+ break;
11
+
12
+ case 'interactive':
13
+ case 'complete':
14
+ setTimeout(callback, 0);
15
+ break;
16
+
17
+ default:
18
+ console.warn('Unexpected document.readyState:', document.readyState);
19
+ setTimeout(callback, 0);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Dynamically imports modules based on configuration
25
+ *
26
+ * @param {Array<Object>} imports - Array of import configurations
27
+ * @param {Object} imports[].name - Name of inline plugin (for inline type)
28
+ * @param {Object} imports[].code - Source code of inline plugin (for inline type)
29
+ * @param {Object} imports[].import_name - Module path to import (for external type)
30
+ * @param {Object} imports[].import_as - Name to import as (for external type)
31
+ * @param {Object} imports[].window_name - Global window object name (for external type)
32
+ * @param {('inline'|'external')} imports[].type - Type of import
33
+ * @returns {Promise<Array<any>>} Array of loaded modules
34
+ * @throws {Error} When plugin loading fails
35
+ */
36
+ function loadAsyncImports(imports = []) {
37
+ const loadInlinePlugin = async ({ name, code }) => {
38
+ const module = await import(`data:text/javascript,${encodeURIComponent(code)}`);
39
+
40
+ if (!module.default) {
41
+ throw new Error(`Inline plugin "${name}" must export a default class/function!`);
42
+ }
43
+
44
+ return module.default;
45
+ };
46
+
47
+ const loadExternalPlugin = async ({ import_name, import_as, window_name }) => {
48
+ if (window_name) {
49
+ if (!Object.prototype.hasOwnProperty.call(window, window_name)) {
50
+ throw new Error(
51
+ `Plugin window['${window_name}'] not found in global scope. ` +
52
+ 'Please ensure the plugin is loaded before CKEditor initialization.'
53
+ );
54
+ }
55
+
56
+ return window[window_name];
57
+ }
58
+
59
+ const module = await import(import_name);
60
+ const imported = module[import_as || 'default'];
61
+
62
+ if (!imported) {
63
+ throw new Error(`Plugin "${import_as}" not found in the ESM module "${import_name}"!`);
64
+ }
65
+
66
+ return imported;
67
+ };
68
+
69
+ return Promise.all(imports.map(item => {
70
+ switch(item.type) {
71
+ case 'inline':
72
+ return loadInlinePlugin(item);
73
+
74
+ case 'external':
75
+ default:
76
+ return loadExternalPlugin(item);
77
+ }
78
+ }));
79
+ }
80
+
81
+ /**
82
+ * Checks if a key is safe to use in configuration objects to prevent prototype pollution
83
+ *
84
+ * @param {string} key - Key name to check
85
+ * @returns {boolean} True if key is safe to use
86
+ */
87
+ function isSafeKey(key) {
88
+ return typeof key === 'string' &&
89
+ key !== '__proto__' &&
90
+ key !== 'constructor' &&
91
+ key !== 'prototype';
92
+ }
93
+
94
+ /**
95
+ * Resolves element references in configuration object.
96
+ * Looks for objects with { $element: "selector" } format and replaces them with actual DOM elements.
97
+ *
98
+ * @param {Object} obj - Configuration object to process
99
+ * @returns {Object} Processed configuration object with resolved element references
100
+ * @throws {Error} When element reference is invalid
101
+ */
102
+ function resolveElementReferences(obj) {
103
+ if (!obj || typeof obj !== 'object') {
104
+ return obj;
105
+ }
106
+
107
+ if (Array.isArray(obj)) {
108
+ return obj.map(item => resolveElementReferences(item));
109
+ }
110
+
111
+ const result = Object.create(null);
112
+
113
+ for (const key of Object.getOwnPropertyNames(obj)) {
114
+ if (!isSafeKey(key)) {
115
+ console.warn(`Suspicious key "${key}" detected in config, skipping`);
116
+ continue;
117
+ }
118
+
119
+ const value = obj[key];
120
+
121
+ if (value && typeof value === 'object') {
122
+ if (value.$element) {
123
+ const selector = value.$element;
124
+
125
+ if (typeof selector !== 'string') {
126
+ console.warn(`Invalid selector type for "${key}", expected string`);
127
+ continue;
128
+ }
129
+
130
+ const element = document.querySelector(selector);
131
+
132
+ if (!element) {
133
+ console.warn(`Element not found for selector: ${selector}`);
134
+ }
135
+
136
+ result[key] = element || null;
137
+ } else {
138
+ result[key] = resolveElementReferences(value);
139
+ }
140
+ } else {
141
+ result[key] = value;
142
+ }
143
+ }
144
+
145
+ return result;
146
+ }
147
+
148
+ /**
149
+ * Generates a unique identifier string
150
+ *
151
+ * @returns {string} Random string that can be used as unique identifier
152
+ */
153
+ function uid() {
154
+ return Math.random().toString(36).substring(2);
155
+ }
@@ -8,38 +8,47 @@ module CKEditor5::Rails
8
8
  attr_reader :cdn, :version, :theme, :translations
9
9
 
10
10
  def initialize(version, theme: :lark, cdn: Engine.default_preset.cdn, translations: [])
11
- raise ArgumentError, 'version must be semver' unless version.is_a?(Semver)
12
- raise ArgumentError, 'theme must be a string' unless theme.is_a?(String)
13
- raise ArgumentError, 'translations must be an array' unless translations.is_a?(Array)
14
-
15
11
  super()
16
12
 
17
13
  @cdn = cdn
18
14
  @version = version
19
15
  @theme = theme
20
16
  @translations = translations
17
+
18
+ validate!
21
19
  end
22
20
 
23
21
  def scripts
24
22
  @scripts ||= [
25
23
  Assets::JSExportsMeta.new(
26
- create_cdn_url('ckbox', 'ckbox.js', version),
27
- *translations_js_exports_meta
24
+ create_cdn_url('ckbox', version, 'ckbox.js'),
25
+ *translations_js_exports_meta,
26
+ window_name: 'CKBox'
28
27
  )
29
28
  ]
30
29
  end
31
30
 
32
31
  def stylesheets
33
32
  @stylesheets ||= [
34
- create_cdn_url('ckbox', "styles/themes/#{theme}.css", version)
33
+ create_cdn_url('ckbox', version, "styles/themes/#{theme}.css")
35
34
  ]
36
35
  end
37
36
 
38
37
  private
39
38
 
39
+ def validate!
40
+ raise ArgumentError, 'version must be semver' unless version.is_a?(Semver)
41
+ raise ArgumentError, 'translations must be an array' unless translations.is_a?(Array)
42
+
43
+ return if theme.is_a?(String) || theme.is_a?(Symbol)
44
+
45
+ raise ArgumentError,
46
+ 'theme must be a string or symbol'
47
+ end
48
+
40
49
  def translations_js_exports_meta
41
50
  translations.map do |lang|
42
- url = create_cdn_url('ckbox', "translations/#{lang}.js", version)
51
+ url = create_cdn_url('ckbox', version, "translations/#{lang}.js")
43
52
 
44
53
  Assets::JSExportsMeta.new(url, window_name: 'CKBOX_TRANSLATIONS', translation: true)
45
54
  end
@@ -6,7 +6,7 @@ require_relative 'ckbox_bundle'
6
6
 
7
7
  module CKEditor5::Rails
8
8
  module Cdn::Helpers
9
- def ckeditor5_cdn_assets(preset: :default, **kwargs)
9
+ def ckeditor5_assets(preset: :default, **kwargs)
10
10
  merge_with_editor_preset(preset, **kwargs) => {
11
11
  cdn:,
12
12
  version:,
@@ -32,15 +32,7 @@ module CKEditor5::Rails
32
32
 
33
33
  Cdn::UrlGenerator::CDN_THIRD_PARTY_GENERATORS.each_key do |key|
34
34
  define_method(:"ckeditor5_#{key.to_s.parameterize}_assets") do |**kwargs|
35
- ckeditor5_cdn_assets(**kwargs.merge(cdn: key))
36
- end
37
- end
38
-
39
- def ckeditor5_assets(**kwargs)
40
- if kwargs[:license_key] && kwargs[:license_key] != 'GPL'
41
- ckeditor5_cloud_assets(**kwargs)
42
- else
43
- ckeditor5_cdn_assets(**kwargs.merge(cdn: Engine.default_preset.cdn))
35
+ ckeditor5_assets(**kwargs.merge(cdn: key))
44
36
  end
45
37
  end
46
38
 
@@ -62,7 +54,7 @@ module CKEditor5::Rails
62
54
 
63
55
  raise ArgumentError,
64
56
  "Poor thing. You forgot to define #{key}. Make sure you passed `#{key}:` parameter to " \
65
- "`ckeditor5_cdn_assets` or defined default one in your `#{preset}` preset!"
57
+ "`ckeditor5_assets` or defined default one in your `#{preset}` preset!"
66
58
  end
67
59
 
68
60
  hash
@@ -20,9 +20,11 @@ module CKEditor5::Rails::Cdn
20
20
 
21
21
  CDN_COMMERCIAL_GENERATORS = {
22
22
  cloud: lambda { |bundle, version, path|
23
- domain = bundle == 'ckbox' ? 'ckbox.io' : 'ckeditor.com'
23
+ "https://cdn.ckeditor.com/#{bundle}/#{version}/#{path}"
24
+ },
24
25
 
25
- "https://cdn.#{domain}/#{bundle}/#{version}/#{path}"
26
+ ckbox: lambda { |bundle, version, path|
27
+ "https://cdn.ckbox.io/#{bundle}/#{version}/#{path}"
26
28
  }
27
29
  }.freeze
28
30
 
@@ -31,11 +33,11 @@ module CKEditor5::Rails::Cdn
31
33
  end
32
34
 
33
35
  def create_cdn_url(bundle, version, path)
34
- generator = CDN_THIRD_PARTY_GENERATORS[cdn] || CDN_COMMERCIAL_GENERATORS[cdn] || cdn
36
+ executor = CDN_THIRD_PARTY_GENERATORS[cdn] || CDN_COMMERCIAL_GENERATORS[cdn] || cdn
35
37
 
36
- raise ArgumentError, "Unknown provider: #{cdn}" unless generator
38
+ raise ArgumentError, "Unknown provider: #{cdn}" if executor.blank? || !executor.respond_to?(:call)
37
39
 
38
- generator.call(bundle, version, path)
40
+ executor.call(bundle, version, path)
39
41
  end
40
42
  end
41
43
  end
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'cdn/helpers'
4
- require_relative 'cloud/helpers'
5
4
  require_relative 'editor/helpers'
6
5
  require_relative 'context/helpers'
7
6
 
8
7
  module CKEditor5::Rails
9
8
  module Helpers
10
9
  include Cdn::Helpers
11
- include Cloud::Helpers
12
10
  include Editor::Helpers
13
11
  include Context::Helpers
14
12
  end
@@ -22,6 +22,18 @@ module CKEditor5::Rails
22
22
  }
23
23
  end
24
24
 
25
+ def premium?
26
+ @premium
27
+ end
28
+
29
+ def gpl?
30
+ license_key == 'GPL'
31
+ end
32
+
33
+ def menubar?
34
+ @config.dig(:menuBar, :isVisible) || false
35
+ end
36
+
25
37
  def to_h_with_overrides(**overrides)
26
38
  {
27
39
  version: overrides.fetch(:version, version),
@@ -44,13 +56,18 @@ module CKEditor5::Rails
44
56
  def ckbox(version = nil, theme: :lark)
45
57
  return @ckbox if version.nil?
46
58
 
47
- @ckbox = { version: version, theme: theme }
59
+ @ckbox = {
60
+ version: version,
61
+ theme: theme
62
+ }
48
63
  end
49
64
 
50
65
  def license_key(license_key = nil)
51
66
  return @license_key if license_key.nil?
52
67
 
53
68
  @license_key = license_key
69
+
70
+ cdn(:cloud) unless gpl?
54
71
  end
55
72
 
56
73
  def gpl
@@ -134,7 +151,9 @@ module CKEditor5::Rails
134
151
  names.each { |name| plugin(name, **kwargs) }
135
152
  end
136
153
 
137
- def language(ui, content: ui) # rubocop:disable Naming/MethodParameterName
154
+ def language(ui = nil, content: ui) # rubocop:disable Naming/MethodParameterName
155
+ return @config[:language] if ui.nil?
156
+
138
157
  @config[:language] = {
139
158
  ui: ui,
140
159
  content: content
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CKEditor5
4
4
  module Rails
5
- VERSION = '1.9.0'
5
+ VERSION = '1.11.0'
6
6
  end
7
7
  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.9.0
4
+ version: 1.11.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-14 00:00:00.000000000 Z
12
+ date: 2024-11-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -45,12 +45,16 @@ files:
45
45
  - lib/ckeditor5/rails.rb
46
46
  - lib/ckeditor5/rails/assets/assets_bundle.rb
47
47
  - lib/ckeditor5/rails/assets/assets_bundle_html_serializer.rb
48
- - lib/ckeditor5/rails/assets/webcomponent.mjs
48
+ - lib/ckeditor5/rails/assets/webcomponent_bundle.rb
49
+ - lib/ckeditor5/rails/assets/webcomponents/components/context.mjs
50
+ - lib/ckeditor5/rails/assets/webcomponents/components/editable.mjs
51
+ - lib/ckeditor5/rails/assets/webcomponents/components/editor.mjs
52
+ - lib/ckeditor5/rails/assets/webcomponents/components/ui-part.mjs
53
+ - lib/ckeditor5/rails/assets/webcomponents/utils.mjs
49
54
  - lib/ckeditor5/rails/cdn/ckbox_bundle.rb
50
55
  - lib/ckeditor5/rails/cdn/ckeditor_bundle.rb
51
56
  - lib/ckeditor5/rails/cdn/helpers.rb
52
57
  - lib/ckeditor5/rails/cdn/url_generator.rb
53
- - lib/ckeditor5/rails/cloud/helpers.rb
54
58
  - lib/ckeditor5/rails/context/helpers.rb
55
59
  - lib/ckeditor5/rails/context/props.rb
56
60
  - lib/ckeditor5/rails/editor/config_helpers.rb
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CKEditor5::Rails
4
- module Cloud
5
- module Helpers
6
- def ckeditor5_cloud_assets(license_key:, **kwargs)
7
- raise 'Cloud assets are not permitted in GPL license!' if license_key == 'GPL'
8
-
9
- ckeditor5_cdn_assets(cdn: :cloud, license_key: license_key, **kwargs)
10
- end
11
- end
12
- end
13
- end