ckeditor5 1.8.0 → 1.10.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
+ }
@@ -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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'props'
4
+
5
+ module CKEditor5::Rails::Context
6
+ module Helpers
7
+ def ckeditor5_context(**config, &block)
8
+ context_props = Props.new(config)
9
+
10
+ tag.send(:'ckeditor-context-component', **context_props.to_attributes, &block)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CKEditor5::Rails
4
+ module Context
5
+ class Props
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def to_attributes
11
+ {
12
+ plugins: serialize_plugins,
13
+ config: serialize_config
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :config
20
+
21
+ def serialize_plugins
22
+ (config[:plugins] || []).map { |plugin| Editor::PropsPlugin.normalize(plugin).to_h }.to_json
23
+ end
24
+
25
+ def serialize_config
26
+ config.except(:plugins).to_json
27
+ end
28
+ end
29
+ end
30
+ end
@@ -29,16 +29,13 @@ module CKEditor5::Rails
29
29
  type ||= preset.type
30
30
 
31
31
  validated_height = validate_editable_height(type, editable_height) || preset.editable_height
32
- editor_props = Editor::Props.new(
33
- controller_context, type, config,
34
- watchdog: watchdog
35
- )
36
-
37
- render_editor_component(
38
- editor_props,
39
- html_attributes.merge(validated_height ? { 'editable-height' => validated_height } : {}),
40
- &block
41
- )
32
+ editor_props = Editor::Props.new(controller_context, type, config, watchdog: watchdog)
33
+
34
+ tag_attributes = html_attributes
35
+ .merge(editor_props.to_attributes)
36
+ .merge(validated_height ? { 'editable-height' => validated_height } : {})
37
+
38
+ tag.send(:'ckeditor-component', **tag_attributes, &block)
42
39
  end
43
40
 
44
41
  def ckeditor5_editable(name = nil, **kwargs, &block)
@@ -91,10 +88,6 @@ module CKEditor5::Rails
91
88
  raise PresetNotFoundError, "Preset #{preset} is not defined."
92
89
  end
93
90
 
94
- def render_editor_component(props, html_attributes, &block)
95
- tag.send(:'ckeditor-component', **props.to_attributes, **html_attributes, &block)
96
- end
97
-
98
91
  def validate_editable_height(type, height)
99
92
  return nil if height.nil?
100
93
 
@@ -1,13 +1,13 @@
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'
5
+ require_relative 'context/helpers'
6
6
 
7
7
  module CKEditor5::Rails
8
8
  module Helpers
9
9
  include Cdn::Helpers
10
- include Cloud::Helpers
11
10
  include Editor::Helpers
11
+ include Context::Helpers
12
12
  end
13
13
  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),
@@ -51,6 +63,8 @@ module CKEditor5::Rails
51
63
  return @license_key if license_key.nil?
52
64
 
53
65
  @license_key = license_key
66
+
67
+ cdn(:cloud) unless gpl?
54
68
  end
55
69
 
56
70
  def gpl
@@ -134,7 +148,9 @@ module CKEditor5::Rails
134
148
  names.each { |name| plugin(name, **kwargs) }
135
149
  end
136
150
 
137
- def language(ui, content: ui) # rubocop:disable Naming/MethodParameterName
151
+ def language(ui = nil, content: ui) # rubocop:disable Naming/MethodParameterName
152
+ return @config[:language] if ui.nil?
153
+
138
154
  @config[:language] = {
139
155
  ui: ui,
140
156
  content: content
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CKEditor5
4
4
  module Rails
5
- VERSION = '1.8.0'
5
+ VERSION = '1.10.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.8.0
4
+ version: 1.10.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-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -45,12 +45,18 @@ 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
58
+ - lib/ckeditor5/rails/context/helpers.rb
59
+ - lib/ckeditor5/rails/context/props.rb
54
60
  - lib/ckeditor5/rails/editor/config_helpers.rb
55
61
  - lib/ckeditor5/rails/editor/helpers.rb
56
62
  - lib/ckeditor5/rails/editor/props.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