ckeditor5 1.9.0 → 1.11.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.
@@ -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