ckeditor5 1.24.10 → 1.26.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbaa06c0cde62bfe01904bb70ca7c8e0f4db04610d2f71543d1fbd657182e781
4
- data.tar.gz: 36ac98e55c8d927fc094cac0b60dd23284a0667ca0d3387b0858d980bc0f33bd
3
+ metadata.gz: ae47ab2c50827fd1a0716bc093475ce4e55a237ad3a9f4c35ba0b66d8ea46f7c
4
+ data.tar.gz: 13e648a1a348203b84f7220b7160c9d98e25753a97a207e4c5260d8d4d598571
5
5
  SHA512:
6
- metadata.gz: 525255ffbc1aadedfe42eea761822f123f3f4de0617e19a443dcb3051d95841ced3f3fda0c8d73241094c84c4e6eac410f26b33ea9ebd1271d9ddd15ab52bcf3
7
- data.tar.gz: 85f17299ba06f3187f7211eb345e93c4d5e7efbd6c2772fb855df883c7af732fd61ce8f32c788899fba3012fcb90770627c33b52182a280b70329a88f8361ce6
6
+ metadata.gz: c311294593a9de01ba525eab43d6dbf0418f454e6c7754a8c2a607464539ca82ed39f61a66d58f5c3a977f89a39c591adfb60b63711c47f3c2657e6c3442c49e
7
+ data.tar.gz: 64f021c1316971588676e9819d422b09d83bea982d74e94544ccbcc703ffb381778dc08aa308620559701a030d88423c20b5b3a678abfc1854daebb33f9fccc1
data/README.md CHANGED
@@ -119,8 +119,7 @@ For extending CKEditor's functionality, refer to the [plugins directory](https:/
119
119
  - [Automatic upgrades 🔄](#automatic-upgrades-)
120
120
  - [Available Configuration Methods ⚙️](#available-configuration-methods-️)
121
121
  - [`cdn(cdn = nil, &block)` method](#cdncdn--nil-block-method)
122
- - [`version(version)` method](#versionversion-method)
123
- - [`automatic_upgrades(enabled: true)` method](#automatic_upgradesenabled-true-method)
122
+ - [`version(version, apply_patches: true)` method](#versionversion-apply_patches-true-method)
124
123
  - [`gpl` method](#gpl-method)
125
124
  - [`license_key(key)` method](#license_keykey-method)
126
125
  - [`premium` method](#premium-method)
@@ -138,7 +137,10 @@ For extending CKEditor's functionality, refer to the [plugins directory](https:/
138
137
  - [`plugins(*names, **kwargs)` method](#pluginsnames-kwargs-method)
139
138
  - [`inline_plugin(name, code)` method](#inline_pluginname-code-method)
140
139
  - [`external_plugin(name, script:, import_as: nil, window_name: nil, stylesheets: [])` method](#external_pluginname-script-import_as-nil-window_name-nil-stylesheets--method)
140
+ - [`patch_plugin(plugin)`](#patch_pluginplugin)
141
+ - [`apply_integration_patches` method](#apply_integration_patches-method)
141
142
  - [`simple_upload_adapter(url)` method](#simple_upload_adapterurl-method)
143
+ - [`special_characters(&block)` method](#special_charactersblock-method)
142
144
  - [`wproofreader(version: nil, cdn: nil, **config)` method](#wproofreaderversion-nil-cdn-nil-config-method)
143
145
  - [Controller / View helpers 📦](#controller--view-helpers-)
144
146
  - [`ckeditor5_element_ref(selector)` method](#ckeditor5_element_refselector-method)
@@ -316,7 +318,7 @@ end
316
318
  ```
317
319
  </details>
318
320
 
319
- #### `version(version)` method
321
+ #### `version(version, apply_patches: true)` method
320
322
 
321
323
  <details>
322
324
  <summary>Set up the version of CKEditor 5 to be used by the integration</summary>
@@ -334,6 +336,23 @@ CKEditor5::Rails.configure do
334
336
  version '44.1.0'
335
337
  end
336
338
  ```
339
+
340
+ In order to disable default patches, you can pass the `apply_patches: false` keyword argument to the `version` method.
341
+
342
+ ```rb
343
+ # config/initializers/ckeditor5.rb
344
+
345
+ CKEditor5::Rails.configure do
346
+ # ... other configuration
347
+
348
+ version '44.1.0', apply_patches: false
349
+ end
350
+ ```
351
+
352
+ The patches are defined in the `lib/ckeditor5/rails/plugins/patches` directory. If you want to apply custom patches, you can use the `patch_plugin` method.
353
+
354
+ ```rb
355
+
337
356
  </details>
338
357
 
339
358
  #### `automatic_upgrades(enabled: true)` method
@@ -978,6 +997,76 @@ end
978
997
 
979
998
  </details>
980
999
 
1000
+ #### `patch_plugin(plugin)`
1001
+
1002
+ <details>
1003
+ <summary>Appends plugin that applies patch to the specific versions of CKEditor 5</summary>
1004
+
1005
+ <br />
1006
+
1007
+ Defines a plugin that applies a patch to the specific versions of CKEditor 5. The example below shows how to define a plugin that applies a patch to the `44.1.0` version:
1008
+
1009
+ ```rb
1010
+ # config/initializers/ckeditor5.rb
1011
+
1012
+ CKEditor5::Rails.configure do
1013
+ # ... other configuration
1014
+
1015
+ patch_plugin MyPatchPlugin.new
1016
+ end
1017
+ ```
1018
+
1019
+ where the `MyPatchPlugin` should inherit from `KEditor5::Rails::Editor::PropsPatchPlugin` and implement the `initialize` method. For example:
1020
+
1021
+ ```rb
1022
+ class YourPatch < CKEditor5::Rails::Editor::PropsPatchPlugin
1023
+ PLUGIN_CODE = <<~JS
1024
+ const { Plugin, ColorPickerView, debounce } = await import( 'ckeditor5' );
1025
+
1026
+ return class YourPatch extends Plugin {
1027
+ static get pluginName() {
1028
+ return 'YourPatch';
1029
+ }
1030
+
1031
+ constructor(editor) {
1032
+ super(editor);
1033
+
1034
+ // ... your patch
1035
+ }
1036
+ }
1037
+ JS
1038
+
1039
+ def initialize
1040
+ super(:YourPatch, PLUGIN_CODE, min_version: nil, max_version: '45.0.0')
1041
+ compress!
1042
+ end
1043
+ end
1044
+ ```
1045
+ </details>
1046
+
1047
+ #### `apply_integration_patches` method
1048
+
1049
+ <details>
1050
+ <summary>Apply patches to the specific versions of CKEditor 5</summary>
1051
+
1052
+ <br />
1053
+
1054
+ Defines a method that applies patches to the specific versions of CKEditor 5. The example below shows how to apply patches to the `44.1.0` version:
1055
+
1056
+ ```rb
1057
+ # config/initializers/ckeditor5.rb
1058
+
1059
+ CKEditor5::Rails.configure do
1060
+ # ... other configuration
1061
+
1062
+ apply_integration_patches
1063
+ end
1064
+ ```
1065
+
1066
+ It's useful when you want to apply patches to the specific versions of CKEditor 5. The patches are defined in the `lib/ckeditor5/rails/plugins/patches` directory.
1067
+
1068
+ </details>
1069
+
981
1070
  #### `simple_upload_adapter(url)` method
982
1071
 
983
1072
  <details>
@@ -1001,6 +1090,52 @@ end
1001
1090
  ```
1002
1091
  </details>
1003
1092
 
1093
+ #### `special_characters(&block)` method
1094
+
1095
+ <details>
1096
+ <summary>Configure special characters plugin</summary>
1097
+
1098
+ <br />
1099
+
1100
+ Defines the special characters plugin to be included in the editor. The example below shows how to include the special characters plugin:
1101
+
1102
+ ```rb
1103
+ # config/initializers/ckeditor5.rb
1104
+
1105
+ CKEditor5::Rails.configure do
1106
+ # ... other configuration
1107
+
1108
+ special_characters do
1109
+ # Enable built-in packs using symbols
1110
+ packs :Text, :Essentials, :Currency, :Mathematical, :Latin
1111
+
1112
+ # Custom groups
1113
+ group 'Emoji', label: 'Emoticons' do
1114
+ item 'smiley', '😊'
1115
+ item 'heart', '❤️'
1116
+ end
1117
+
1118
+ group 'Arrows',
1119
+ items: [
1120
+ { title: 'right arrow', character: '→' },
1121
+ { title: 'left arrow', character: '←' }
1122
+ ]
1123
+
1124
+ group 'Mixed',
1125
+ items: [{ title: 'star', character: '⭐' }],
1126
+ label: 'Mixed Characters' do
1127
+ item 'heart', '❤️'
1128
+ end
1129
+
1130
+ order :Text, :Currency, :Mathematical, :Latin, :Emoji, :Arrows, :Mixed
1131
+ end
1132
+ end
1133
+ ```
1134
+
1135
+ For more info about the special characters plugin, check the [official documentation](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html).
1136
+
1137
+ </details>
1138
+
1004
1139
  #### `wproofreader(version: nil, cdn: nil, **config)` method
1005
1140
 
1006
1141
  <details>
@@ -3,6 +3,7 @@
3
3
  require_relative 'props_plugin'
4
4
  require_relative 'props_inline_plugin'
5
5
  require_relative 'props_external_plugin'
6
+ require_relative 'props_patch_plugin'
6
7
  require_relative 'props'
7
8
 
8
9
  module CKEditor5::Rails::Editor::Helpers
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../semver'
4
+ require_relative 'props_inline_plugin'
5
+
6
+ module CKEditor5::Rails::Editor
7
+ class PropsPatchPlugin < PropsInlinePlugin
8
+ attr_reader :min_version, :max_version
9
+
10
+ def initialize(name, code, min_version: nil, max_version: nil)
11
+ super(name, code)
12
+
13
+ @min_version = min_version && CKEditor5::Rails::Semver.new(min_version)
14
+ @max_version = max_version && CKEditor5::Rails::Semver.new(max_version)
15
+
16
+ compress!
17
+ end
18
+
19
+ def self.applicable_for_version?(editor_version, min_version: nil, max_version: nil)
20
+ return true if min_version.nil? && max_version.nil?
21
+
22
+ current_version = CKEditor5::Rails::Semver.new(editor_version)
23
+
24
+ min_check = min_version.nil? || current_version >= CKEditor5::Rails::Semver.new(min_version)
25
+ max_check = max_version.nil? || current_version <= CKEditor5::Rails::Semver.new(max_version)
26
+
27
+ min_check && max_check
28
+ end
29
+
30
+ def applicable_for_version?(editor_version)
31
+ self.class.applicable_for_version?(
32
+ editor_version,
33
+ min_version: @min_version&.to_s,
34
+ max_version: @max_version&.to_s
35
+ )
36
+ end
37
+ end
38
+ end
@@ -3,8 +3,7 @@
3
3
  require 'rails/engine'
4
4
 
5
5
  require_relative 'presets/manager'
6
- require_relative 'plugins/simple_upload_adapter'
7
- require_relative 'plugins/wproofreader'
6
+ require_relative 'plugins'
8
7
 
9
8
  module CKEditor5::Rails
10
9
  class PresetNotFoundError < ArgumentError; end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../editor/props_patch_plugin'
4
+
5
+ module CKEditor5::Rails::Plugins::Patches
6
+ # Fixes focus management issues in the CKEditor5 color picker component
7
+ # by ensuring proper focus handling when the color picker is opened.
8
+ #
9
+ # The patch modifies the ColorPickerView's focus behavior to properly
10
+ # focus either the hex input field (when visible) or the first slider.
11
+ #
12
+ # @see https://github.com/ckeditor/ckeditor5/issues/17069
13
+ class FixColorPickerRaceCondition < CKEditor5::Rails::Editor::PropsPatchPlugin
14
+ PLUGIN_CODE = <<~JS
15
+ const { Plugin, ColorPickerView, debounce } = await import( 'ckeditor5' );
16
+
17
+ return class FixColorPickerRaceCondition extends Plugin {
18
+ static get pluginName() {
19
+ return 'FixColorPickerRaceCondition';
20
+ }
21
+
22
+ constructor(editor) {
23
+ super(editor);
24
+ this.editor = editor;
25
+ this.#applyPatch();
26
+ }
27
+
28
+ #applyPatch() {
29
+ const { focus } = ColorPickerView.prototype;
30
+
31
+ ColorPickerView.prototype.focus = function() {
32
+ try {
33
+ if (!this._config.hideInput) {
34
+ this.hexInputRow.children.get( 1 ).focus();
35
+ }
36
+
37
+ this.slidersView.first.focus();
38
+ } catch (error) {
39
+ focus.apply(this, arguments);
40
+ }
41
+ }
42
+ }
43
+ }
44
+ JS
45
+
46
+ def initialize
47
+ super(:FixColorPickerRaceCondition, PLUGIN_CODE)
48
+ compress!
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../editor/props_inline_plugin'
4
+
3
5
  module CKEditor5::Rails::Plugins
4
6
  class SimpleUploadAdapter < CKEditor5::Rails::Editor::PropsInlinePlugin
5
7
  PLUGIN_CODE = <<~JS
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../editor/props_inline_plugin'
4
+
5
+ module CKEditor5::Rails::Plugins
6
+ class SpecialCharactersBootstrap < CKEditor5::Rails::Editor::PropsInlinePlugin
7
+ PLUGIN_CODE = <<~JS
8
+ const { Plugin } = await import( 'ckeditor5' );
9
+
10
+ return class SpecialCharactersBootstrap extends Plugin {
11
+ static get pluginName() {
12
+ return 'SpecialCharactersBootstrap';
13
+ }
14
+
15
+ get bootstrapConfig() {
16
+ return this.editor.config.get('specialCharactersBootstrap');
17
+ }
18
+
19
+ async init() {
20
+ const { editor, bootstrapConfig } = this;
21
+ const currentConfig = editor.config.get('specialCharacters');
22
+
23
+ if (!bootstrapConfig) {
24
+ return;
25
+ }
26
+
27
+ editor.config.define('specialCharacters', {
28
+ ...currentConfig,
29
+ order: bootstrapConfig.order || currentConfig.order,
30
+ } );
31
+ }
32
+
33
+ async afterInit() {
34
+ const { editor, bootstrapConfig } = this;
35
+ const specialCharacters = editor.plugins.get('SpecialCharacters');
36
+
37
+ if (!specialCharacters || !bootstrapConfig) {
38
+ return;
39
+ }
40
+
41
+ const groups = bootstrapConfig.groups || [];
42
+
43
+ for (const { name, items, options } of groups) {
44
+ specialCharacters.addItems(name, items, {
45
+ label: name,
46
+ ...options
47
+ });
48
+ }
49
+ }
50
+ }
51
+ JS
52
+
53
+ def initialize
54
+ super(:SpecialCharactersBootstrap, PLUGIN_CODE)
55
+ compress!
56
+ end
57
+ end
58
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../editor/props_external_plugin'
3
+ require_relative '../editor/props_inline_plugin'
4
4
 
5
5
  module CKEditor5::Rails::Plugins
6
6
  class WProofreader < CKEditor5::Rails::Editor::PropsExternalPlugin
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CKEditor5::Rails::Plugins
4
+ module Patches
5
+ end
6
+ end
7
+
8
+ # Core plugins
9
+ require_relative 'plugins/simple_upload_adapter'
10
+ require_relative 'plugins/wproofreader'
11
+ require_relative 'plugins/special_characters_bootstrap'
12
+
13
+ # Plugin patches and fixes
14
+ require_relative 'plugins/patches/fix_color_picker_race_condition'
@@ -12,6 +12,7 @@ module CKEditor5::Rails
12
12
  class DisallowedInlinePluginError < ArgumentError; end
13
13
  class MissingInlinePluginError < StandardError; end
14
14
  class UnsupportedESModuleError < StandardError; end
15
+ class InvalidPatchPluginError < ArgumentError; end
15
16
 
16
17
  included do
17
18
  attr_reader :disallow_inline_plugins, :disallow_inline_plugin_compression
@@ -67,6 +68,23 @@ module CKEditor5::Rails
67
68
  register_plugin(plugin)
68
69
  end
69
70
 
71
+ # Registers a patch plugin that modifies CKEditor behavior for specific versions
72
+ #
73
+ # @param plugin [Editor::PropsPatchPlugin] Patch plugin instance to register
74
+ # @raise [InvalidPatchPluginError] When provided plugin is not a PropsPatchPlugin
75
+ # @return [Editor::PropsPatchPlugin, nil] Returns plugin if registered, nil if not applicable
76
+ # @example Apply patch for specific CKEditor versions
77
+ # patch_plugin PropsPatchPlugin.new(:PatchName, code, min_version: '35.0.0', max_version: '36.0.0')
78
+ def patch_plugin(plugin)
79
+ unless plugin.is_a?(Editor::PropsPatchPlugin)
80
+ raise InvalidPatchPluginError, 'Provided plugin must be a PropsPatchPlugin instance'
81
+ end
82
+
83
+ return unless plugin.applicable_for_version?(config[:version])
84
+
85
+ register_plugin(plugin)
86
+ end
87
+
70
88
  # Register a single plugin by name
71
89
  #
72
90
  # @param name [Symbol, Editor::PropsBasePlugin] Plugin name or instance
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'concerns/configuration_methods'
4
4
  require_relative 'concerns/plugin_methods'
5
+ require_relative 'special_characters_builder'
5
6
 
6
7
  module CKEditor5::Rails
7
8
  module Presets
@@ -171,11 +172,16 @@ module CKEditor5::Rails
171
172
  end
172
173
 
173
174
  # Set or get editor version
174
- # @param version [String, nil] Editor version
175
+ # @param version [String, nil] Editor version to set
176
+ # @param apply_patches [Boolean] Whether to apply integration patches after setting version
175
177
  # @example Set specific version
176
178
  # version '43.3.1'
177
- # @return [String, nil] Current version
178
- def version(version = nil)
179
+ # @example Set version without applying patches
180
+ # version '43.3.1', apply_patches: false
181
+ # @example Get current version
182
+ # version # => "43.3.1"
183
+ # @return [String, nil] Current version string or nil if not set
184
+ def version(version = nil, apply_patches: true)
179
185
  return @version&.to_s if version.nil?
180
186
 
181
187
  if @automatic_upgrades && version
@@ -184,6 +190,14 @@ module CKEditor5::Rails
184
190
  else
185
191
  @version = Semver.new(version)
186
192
  end
193
+
194
+ apply_integration_patches if apply_patches
195
+ end
196
+
197
+ # Apply integration patches for the current version
198
+ # @return [void]
199
+ def apply_integration_patches
200
+ patch_plugin Plugins::Patches::FixColorPickerRaceCondition.new
187
201
  end
188
202
 
189
203
  # Enable or disable automatic version upgrades
@@ -368,6 +382,46 @@ module CKEditor5::Rails
368
382
  end
369
383
  end
370
384
 
385
+ # Configure special characters plugin
386
+ #
387
+ # @yield Block for configuring special characters
388
+ # @example Basic configuration with block
389
+ # special_characters do
390
+ # group 'Emoji', label: 'Emoticons' do
391
+ # item 'smiley', '😊'
392
+ # item 'heart', '❤️'
393
+ # end
394
+ # order :Text, :Emoji
395
+ # end
396
+ # @example Configuration with direct items array
397
+ # special_characters do
398
+ # group 'Arrows',
399
+ # items: [
400
+ # { title: 'right', character: '→' },
401
+ # { title: 'left', character: '←' }
402
+ # ]
403
+ # end
404
+ # @example Mixed configuration
405
+ # special_characters do
406
+ # group 'Mixed',
407
+ # items: [{ title: 'star', character: '⭐' }],
408
+ # label: 'Mixed Characters' do
409
+ # item 'heart', '❤️'
410
+ # end
411
+ # end
412
+ def special_characters(&block)
413
+ builder = SpecialCharactersBuilder.new
414
+ builder.instance_eval(&block) if block_given?
415
+
416
+ plugins do
417
+ append(:SpecialCharacters)
418
+ builder.packs_plugins.each { |pack| append(pack) }
419
+ prepend(Plugins::SpecialCharactersBootstrap.new)
420
+ end
421
+
422
+ configure(:specialCharactersBootstrap, builder.to_h)
423
+ end
424
+
371
425
  private
372
426
 
373
427
  def deep_copy_toolbar(toolbar)
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CKEditor5::Rails::Presets
4
+ # Builder class for configuring special characters in CKEditor5
5
+ #
6
+ # @example Basic configuration
7
+ # special_characters do
8
+ # group 'Emoji', label: 'Emoticons' do
9
+ # item 'smiley', '😊'
10
+ # end
11
+ #
12
+ # order :Text, :Mathematical, :Emoji
13
+ # end
14
+ class SpecialCharactersBuilder
15
+ attr_reader :packs_plugins
16
+
17
+ # Builder class for configuring special characters groups
18
+ #
19
+ # @example Basic group configuration
20
+ # group = Group.new('Emoji', label: 'Emoticons')
21
+ # group.item('smiley', '😊')
22
+ class Group
23
+ attr_reader :name, :label
24
+ private attr_reader :items
25
+
26
+ # Initialize a new special characters group
27
+ #
28
+ # @param name [String] Name of the group
29
+ # @param label [String, nil] Optional display label for the group
30
+ def initialize(name, label: nil)
31
+ @name = name
32
+ @label = label
33
+ @items = []
34
+ end
35
+
36
+ # Add a single character to the group
37
+ #
38
+ # @param title [String] Character title/description
39
+ # @param character [String] The special character
40
+ # @example Add smiley face
41
+ # item 'smiley face', '😊'
42
+ def item(title, character)
43
+ @items << { title: title, character: character }
44
+ end
45
+
46
+ # Add multiple characters to the group at once
47
+ #
48
+ # @param collection [Array<Hash>] Array of character definitions
49
+ # @example Add multiple items
50
+ # add_items [
51
+ # { title: 'star', character: '⭐' },
52
+ # { title: 'heart', character: '❤️' }
53
+ # ]
54
+ def add_items(collection)
55
+ collection.each do |item|
56
+ @items << item.slice(:title, :character)
57
+ end
58
+ end
59
+
60
+ # Add multiple characters to the group
61
+ #
62
+ # @param items [Array<Hash>] Array of character definitions
63
+ def add_characters(items)
64
+ items.each do |item|
65
+ @items << item.slice(:title, :character)
66
+ end
67
+ end
68
+
69
+ # Convert group configuration to hash
70
+ #
71
+ # @return [Hash] Group configuration hash
72
+ def to_h
73
+ {
74
+ name: @name,
75
+ items: @items,
76
+ options: label ? { label: label } : {}
77
+ }
78
+ end
79
+ end
80
+
81
+ # Initialize a new special characters builder
82
+ #
83
+ # @example Create new builder
84
+ # SpecialCharactersBuilder.new
85
+ def initialize
86
+ @groups = []
87
+ @order = []
88
+ @packs_plugins = []
89
+ end
90
+
91
+ # Define a new special characters group
92
+ #
93
+ # @param name [String] Name of the group
94
+ # @param items [Array<Hash>] Optional array of character items
95
+ # @param label [String, nil] Optional display label
96
+ # @yield Group configuration block
97
+ # @return [Group] Created group instance
98
+ # @example Define group with items array
99
+ # group 'Emoji',
100
+ # items: [
101
+ # { title: 'smiley', character: '😊' },
102
+ # { title: 'heart', character: '❤️' }
103
+ # ],
104
+ # label: 'Emoticons'
105
+ # @example Define group with block
106
+ # group 'Emoji', label: 'Emoticons' do
107
+ # item 'smiley', '😊'
108
+ # item 'heart', '❤️'
109
+ # end
110
+ def group(name, items: [], label: nil, &block)
111
+ group = Group.new(name, label: label)
112
+ group.add_characters(items) if items.any?
113
+ group.instance_eval(&block) if block_given?
114
+ @groups << group
115
+ group
116
+ end
117
+
118
+ # Enable special characters packs
119
+ #
120
+ # @param names [Array<Symbol, String>] Pack names to enable
121
+ # @example Enable essential and extended characters
122
+ # packs :Text, :Currency, :Mathematical
123
+ def packs(*names)
124
+ names.each do |name|
125
+ plugin_name = "SpecialCharacters#{name.to_s.capitalize}"
126
+ @packs_plugins << plugin_name
127
+ end
128
+ end
129
+
130
+ # Set the display order of character groups
131
+ #
132
+ # @param categories [Array<Symbol, String>] Category names in desired order
133
+ # @example Set display order
134
+ # order :Text, :Mathematical, 'Currency', :Emoji
135
+ def order(*categories)
136
+ @order = categories.map(&:to_s)
137
+ end
138
+
139
+ # Convert builder configuration to hash
140
+ #
141
+ # @return [Hash] Complete special characters configuration
142
+ def to_h
143
+ {
144
+ groups: @groups.map(&:to_h),
145
+ order: @order,
146
+ packs: @packs_plugins
147
+ }
148
+ end
149
+ end
150
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CKEditor5
4
4
  module Rails
5
- VERSION = '1.24.10'
5
+ VERSION = '1.26.0'
6
6
 
7
7
  DEFAULT_CKEDITOR_VERSION = '44.1.0'
8
8
  end
@@ -20,7 +20,7 @@ RSpec.describe CKEditor5::Rails::Cdn::Helpers do
20
20
  let(:helper) { test_class.new }
21
21
  let(:preset) do
22
22
  CKEditor5::Rails::Presets::PresetBuilder.new do
23
- version '34.1.0'
23
+ version '34.1.0', apply_patches: false
24
24
  type :classic
25
25
  translations :pl
26
26
  cdn :cloud
@@ -151,7 +151,7 @@ RSpec.describe CKEditor5::Rails::Cdn::Helpers do
151
151
  context 'when overriding preset values' do
152
152
  let(:preset) do
153
153
  CKEditor5::Rails::Presets::PresetBuilder.new do
154
- version '34.1.0'
154
+ version '34.1.0', apply_patches: false
155
155
  type :classic
156
156
  language :pl
157
157
  cdn :cloud
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe CKEditor5::Rails::Editor::PropsPatchPlugin do
6
+ let(:plugin_name) { 'testPlugin' }
7
+ let(:plugin_code) { 'console.log("test");' }
8
+
9
+ describe '#initialize' do
10
+ it 'creates plugin with version constraints' do
11
+ plugin = described_class.new(plugin_name, plugin_code, min_version: '29.0.0', max_version: '30.0.0')
12
+
13
+ expect(plugin.min_version.to_s).to eq('29.0.0')
14
+ expect(plugin.max_version.to_s).to eq('30.0.0')
15
+ end
16
+
17
+ it 'creates plugin without version constraints' do
18
+ plugin = described_class.new(plugin_name, plugin_code)
19
+
20
+ expect(plugin.min_version).to be_nil
21
+ expect(plugin.max_version).to be_nil
22
+ end
23
+ end
24
+
25
+ describe '.applicable_for_version?' do
26
+ it 'returns true when no version constraints' do
27
+ expect(described_class.applicable_for_version?('29.0.0')).to be true
28
+ end
29
+
30
+ it 'returns true when version is within constraints' do
31
+ result = described_class.applicable_for_version?(
32
+ '29.1.0',
33
+ min_version: '29.0.0',
34
+ max_version: '30.0.0'
35
+ )
36
+ expect(result).to be true
37
+ end
38
+
39
+ it 'returns false when version is too low' do
40
+ result = described_class.applicable_for_version?(
41
+ '28.9.9',
42
+ min_version: '29.0.0',
43
+ max_version: '30.0.0'
44
+ )
45
+ expect(result).to be false
46
+ end
47
+
48
+ it 'returns false when version is too high' do
49
+ result = described_class.applicable_for_version?(
50
+ '30.0.1',
51
+ min_version: '29.0.0',
52
+ max_version: '30.0.0'
53
+ )
54
+ expect(result).to be false
55
+ end
56
+ end
57
+
58
+ describe '#applicable_for_version?' do
59
+ let(:plugin) do
60
+ described_class.new(plugin_name, plugin_code, min_version: '29.0.0', max_version: '30.0.0')
61
+ end
62
+
63
+ it 'returns true for version within constraints' do
64
+ expect(plugin.applicable_for_version?('29.1.0')).to be true
65
+ end
66
+
67
+ it 'returns false for version outside constraints' do
68
+ expect(plugin.applicable_for_version?('28.9.9')).to be false
69
+ end
70
+ end
71
+ end
@@ -20,7 +20,7 @@ RSpec.describe CKEditor5::Rails::Presets::Manager do
20
20
  it 'creates new preset based on default' do
21
21
  manager.define(:custom) do
22
22
  automatic_upgrades enabled: false
23
- version '36.0.0'
23
+ version '36.0.0', apply_patches: false
24
24
  end
25
25
 
26
26
  expect(manager[:custom].version).to eq('36.0.0')
@@ -32,7 +32,7 @@ RSpec.describe CKEditor5::Rails::Presets::Manager do
32
32
  it 'creates completely new preset' do
33
33
  manager.define(:custom, inherit: false) do
34
34
  automatic_upgrades enabled: false
35
- version '36.0.0'
35
+ version '36.0.0', apply_patches: false
36
36
  end
37
37
 
38
38
  expect(manager[:custom].version).to eq('36.0.0')
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
- require_relative './plugins_builder_spec'
5
- require_relative './toolbar_builder_spec'
6
4
 
7
5
  RSpec.describe CKEditor5::Rails::Presets::PresetBuilder do
8
6
  let(:builder) { described_class.new }
@@ -520,4 +518,145 @@ RSpec.describe CKEditor5::Rails::Presets::PresetBuilder do
520
518
  expect(builder.config[:wproofreader]).to eq({ language: 'en' })
521
519
  end
522
520
  end
521
+
522
+ describe '#special_characters' do
523
+ it 'configures special characters with groups and items' do # rubocop:disable Metrics/BlockLength
524
+ builder.special_characters do
525
+ group 'Emoji', label: 'Emoticons' do
526
+ item 'smiley', '😊'
527
+ item 'heart', '❤️'
528
+ end
529
+
530
+ group 'Arrows',
531
+ items: [
532
+ { title: 'right', character: '→' },
533
+ { title: 'left', character: '←' }
534
+ ]
535
+
536
+ group 'Mixed',
537
+ items: [{ title: 'star', character: '⭐' }],
538
+ label: 'Mixed Characters' do
539
+ item 'heart', '❤️'
540
+ end
541
+
542
+ order :Text, :Arrows, :Emoji, :Mixed
543
+ end
544
+
545
+ expect(builder.config[:specialCharactersBootstrap]).to eq({
546
+ groups: [
547
+ {
548
+ name: 'Emoji',
549
+ items: [
550
+ { title: 'smiley', character: '😊' },
551
+ { title: 'heart', character: '❤️' }
552
+ ],
553
+ options: { label: 'Emoticons' }
554
+ },
555
+ {
556
+ name: 'Arrows',
557
+ items: [
558
+ { title: 'right', character: '→' },
559
+ { title: 'left', character: '←' }
560
+ ],
561
+ options: {}
562
+ },
563
+ {
564
+ name: 'Mixed',
565
+ items: [
566
+ { title: 'star', character: '⭐' },
567
+ { title: 'heart', character: '❤️' }
568
+ ],
569
+ options: { label: 'Mixed Characters' }
570
+ }
571
+ ],
572
+ order: %w[Text Arrows Emoji Mixed],
573
+ packs: []
574
+ })
575
+
576
+ plugin_names = builder.config[:plugins].map(&:name)
577
+ expect(plugin_names).to include(:SpecialCharacters)
578
+ expect(plugin_names).to include(:SpecialCharactersBootstrap)
579
+ end
580
+
581
+ it 'enables special characters packs' do
582
+ builder.special_characters do
583
+ packs :Text, :Mathematical, :Currency
584
+ end
585
+
586
+ plugin_names = builder.config[:plugins].map(&:name)
587
+ expect(plugin_names).to include(
588
+ :SpecialCharactersBootstrap,
589
+ :SpecialCharacters,
590
+ 'SpecialCharactersText',
591
+ 'SpecialCharactersMathematical',
592
+ 'SpecialCharactersCurrency'
593
+ )
594
+ end
595
+ end
596
+
597
+ describe '#patch_plugin' do
598
+ let(:patch_plugin) do
599
+ CKEditor5::Rails::Editor::PropsPatchPlugin.new(
600
+ :TestPatch,
601
+ <<~JAVASCRIPT
602
+ const { Plugin } = await import('ckeditor5');
603
+ return class TestPatch extends Plugin {
604
+ init() {}
605
+ }
606
+ JAVASCRIPT
607
+ )
608
+ end
609
+
610
+ it 'raises error when plugin is not a PropsPatchPlugin' do
611
+ invalid_plugin = CKEditor5::Rails::Editor::PropsPlugin.new(:InvalidPatch)
612
+
613
+ expect { builder.patch_plugin(invalid_plugin) }
614
+ .to raise_error(
615
+ CKEditor5::Rails::Presets::Concerns::PluginMethods::InvalidPatchPluginError,
616
+ 'Provided plugin must be a PropsPatchPlugin instance'
617
+ )
618
+ end
619
+
620
+ it 'applies patch when applicable' do
621
+ allow(patch_plugin).to receive(:applicable_for_version?).and_return(true)
622
+
623
+ builder.version('35.0.0', apply_patches: false)
624
+ builder.patch_plugin(patch_plugin)
625
+
626
+ plugin_names = builder.config[:plugins].map(&:name)
627
+ expect(plugin_names).to include(:TestPatch)
628
+ end
629
+
630
+ it 'skips patch when not applicable' do
631
+ allow(patch_plugin).to receive(:applicable_for_version?).and_return(false)
632
+
633
+ builder.version('35.0.0')
634
+ builder.patch_plugin(patch_plugin)
635
+
636
+ plugin_names = builder.config[:plugins].map(&:name)
637
+ expect(plugin_names).not_to include(:TestPatch)
638
+ end
639
+
640
+ it 'applies patch when version is not set' do
641
+ builder.patch_plugin(patch_plugin)
642
+
643
+ plugin_names = builder.config[:plugins].map(&:name)
644
+ expect(plugin_names).to include(:TestPatch)
645
+ end
646
+ end
647
+
648
+ describe '#apply_integration_patches' do
649
+ it 'applies known integration patches' do
650
+ builder.version('35.0.0', apply_patches: false)
651
+ expect { builder.apply_integration_patches }
652
+ .to change { builder.config[:plugins].count }
653
+ .by(1)
654
+ end
655
+
656
+ it 'apply patches when version is not set' do
657
+ expect { builder.apply_integration_patches }
658
+ .to change { builder.config[:plugins].count }
659
+ .by(1)
660
+ end
661
+ end
523
662
  end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe CKEditor5::Rails::Presets::SpecialCharactersBuilder do
6
+ subject(:builder) { described_class.new }
7
+
8
+ describe '#group' do
9
+ it 'creates a group with items array' do
10
+ group = builder.group('Arrows', items: [
11
+ { title: 'right', character: '→' },
12
+ { title: 'left', character: '←' }
13
+ ])
14
+
15
+ expect(group.to_h).to eq({
16
+ name: 'Arrows',
17
+ items: [
18
+ { title: 'right', character: '→' },
19
+ { title: 'left', character: '←' }
20
+ ],
21
+ options: {}
22
+ })
23
+ end
24
+
25
+ it 'creates a group with block syntax' do
26
+ group = builder.group('Emoji', label: 'Emoticons') do
27
+ item 'smiley', '😊'
28
+ item 'heart', '❤️'
29
+ end
30
+
31
+ expect(group.to_h).to eq({
32
+ name: 'Emoji',
33
+ items: [
34
+ { title: 'smiley', character: '😊' },
35
+ { title: 'heart', 'character': '❤️' }
36
+ ],
37
+ options: { label: 'Emoticons' }
38
+ })
39
+ end
40
+
41
+ it 'creates a group with mixed configuration' do
42
+ group = builder.group('Mixed',
43
+ items: [{ title: 'star', character: '⭐' }],
44
+ label: 'Mixed Characters') do
45
+ item 'heart', '❤️'
46
+ end
47
+
48
+ expect(group.to_h).to eq({
49
+ name: 'Mixed',
50
+ items: [
51
+ { title: 'star', character: '⭐' },
52
+ { title: 'heart', 'character': '❤️' }
53
+ ],
54
+ options: { label: 'Mixed Characters' }
55
+ })
56
+ end
57
+ end
58
+
59
+ describe '#packs' do
60
+ it 'registers special characters plugins' do
61
+ builder.packs(:Text, :Mathematical, :Currency)
62
+
63
+ expect(builder.packs_plugins).to eq(%w[
64
+ SpecialCharactersText
65
+ SpecialCharactersMathematical
66
+ SpecialCharactersCurrency
67
+ ])
68
+ end
69
+ end
70
+
71
+ describe '#order' do
72
+ it 'sets the display order of character groups' do
73
+ builder.order(:Text, :Mathematical, 'Currency', :Emoji)
74
+
75
+ expect(builder.to_h[:order]).to eq(%w[Text Mathematical Currency Emoji])
76
+ end
77
+ end
78
+
79
+ describe '#to_h' do
80
+ it 'returns complete configuration' do
81
+ builder.group('Emoji', label: 'Emoticons') do
82
+ item 'smiley', '😊'
83
+ end
84
+
85
+ builder.packs(:Text, :Mathematical)
86
+ builder.order(:Text, :Mathematical, :Emoji)
87
+
88
+ expect(builder.to_h).to eq({
89
+ groups: [{
90
+ name: 'Emoji',
91
+ items: [{ title: 'smiley', character: '😊' }],
92
+ options: { label: 'Emoticons' }
93
+ }],
94
+ order: %w[Text Mathematical Emoji],
95
+ packs: %w[SpecialCharactersText SpecialCharactersMathematical]
96
+ })
97
+ end
98
+ end
99
+
100
+ describe CKEditor5::Rails::Presets::SpecialCharactersBuilder::Group do
101
+ subject(:group) { described_class.new('Test', label: 'Test Group') }
102
+
103
+ describe '#item' do
104
+ it 'adds a single character' do
105
+ group.item('test', '*')
106
+ expect(group.to_h[:items]).to eq([{ title: 'test', character: '*' }])
107
+ end
108
+ end
109
+
110
+ describe '#add_items' do
111
+ it 'adds multiple characters' do
112
+ group.add_items([
113
+ { title: 'star', character: '⭐' },
114
+ { title: 'heart', 'character': '❤️' }
115
+ ])
116
+
117
+ expect(group.to_h[:items]).to eq([
118
+ { title: 'star', character: '⭐' },
119
+ { title: 'heart', 'character': '❤️' }
120
+ ])
121
+ end
122
+
123
+ it 'filters out invalid keys' do
124
+ group.add_items([
125
+ { title: 'star', character: '⭐', invalid: 'key' }
126
+ ])
127
+
128
+ expect(group.to_h[:items]).to eq([
129
+ { title: 'star', character: '⭐' }
130
+ ])
131
+ end
132
+ end
133
+
134
+ describe '#to_h' do
135
+ it 'includes group name, items and options' do
136
+ group.item('test', '*')
137
+
138
+ expect(group.to_h).to eq({
139
+ name: 'Test',
140
+ items: [{ title: 'test', character: '*' }],
141
+ options: { label: 'Test Group' }
142
+ })
143
+ end
144
+
145
+ it 'omits empty options' do
146
+ group = described_class.new('Test')
147
+ group.item('test', '*')
148
+
149
+ expect(group.to_h).to eq({
150
+ name: 'Test',
151
+ items: [{ title: 'test', character: '*' }],
152
+ options: {}
153
+ })
154
+ end
155
+ end
156
+ end
157
+ 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.24.10
4
+ version: 1.26.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: 2025-01-28 00:00:00.000000000 Z
12
+ date: 2025-02-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -82,19 +82,24 @@ files:
82
82
  - lib/ckeditor5/rails/editor/props_base_plugin.rb
83
83
  - lib/ckeditor5/rails/editor/props_external_plugin.rb
84
84
  - lib/ckeditor5/rails/editor/props_inline_plugin.rb
85
+ - lib/ckeditor5/rails/editor/props_patch_plugin.rb
85
86
  - lib/ckeditor5/rails/editor/props_plugin.rb
86
87
  - lib/ckeditor5/rails/engine.rb
87
88
  - lib/ckeditor5/rails/helpers.rb
88
89
  - lib/ckeditor5/rails/hooks/form.rb
89
90
  - lib/ckeditor5/rails/hooks/importmap.rb
90
91
  - lib/ckeditor5/rails/hooks/simple_form.rb
92
+ - lib/ckeditor5/rails/plugins.rb
93
+ - lib/ckeditor5/rails/plugins/patches/fix_color_picker_race_condition.rb
91
94
  - lib/ckeditor5/rails/plugins/simple_upload_adapter.rb
95
+ - lib/ckeditor5/rails/plugins/special_characters_bootstrap.rb
92
96
  - lib/ckeditor5/rails/plugins/wproofreader.rb
93
97
  - lib/ckeditor5/rails/presets/concerns/configuration_methods.rb
94
98
  - lib/ckeditor5/rails/presets/concerns/plugin_methods.rb
95
99
  - lib/ckeditor5/rails/presets/manager.rb
96
100
  - lib/ckeditor5/rails/presets/plugins_builder.rb
97
101
  - lib/ckeditor5/rails/presets/preset_builder.rb
102
+ - lib/ckeditor5/rails/presets/special_characters_builder.rb
98
103
  - lib/ckeditor5/rails/presets/toolbar_builder.rb
99
104
  - lib/ckeditor5/rails/semver.rb
100
105
  - lib/ckeditor5/rails/version.rb
@@ -123,6 +128,7 @@ files:
123
128
  - spec/lib/ckeditor5/rails/editor/props_base_plugin_spec.rb
124
129
  - spec/lib/ckeditor5/rails/editor/props_external_plugin_spec.rb
125
130
  - spec/lib/ckeditor5/rails/editor/props_inline_plugin_spec.rb
131
+ - spec/lib/ckeditor5/rails/editor/props_patch_plugin_spec.rb
126
132
  - spec/lib/ckeditor5/rails/editor/props_plugin_spec.rb
127
133
  - spec/lib/ckeditor5/rails/editor/props_spec.rb
128
134
  - spec/lib/ckeditor5/rails/engine_spec.rb
@@ -133,6 +139,7 @@ files:
133
139
  - spec/lib/ckeditor5/rails/presets/manager_spec.rb
134
140
  - spec/lib/ckeditor5/rails/presets/plugins_builder_spec.rb
135
141
  - spec/lib/ckeditor5/rails/presets/preset_builder_spec.rb
142
+ - spec/lib/ckeditor5/rails/presets/special_characters_builder_spec.rb
136
143
  - spec/lib/ckeditor5/rails/presets/toolbar_builder_spec.rb
137
144
  - spec/lib/ckeditor5/rails/semver_spec.rb
138
145
  - spec/lib/ckeditor5/rails/version_detector_spec.rb
@@ -191,6 +198,7 @@ test_files:
191
198
  - spec/lib/ckeditor5/rails/editor/props_base_plugin_spec.rb
192
199
  - spec/lib/ckeditor5/rails/editor/props_external_plugin_spec.rb
193
200
  - spec/lib/ckeditor5/rails/editor/props_inline_plugin_spec.rb
201
+ - spec/lib/ckeditor5/rails/editor/props_patch_plugin_spec.rb
194
202
  - spec/lib/ckeditor5/rails/editor/props_plugin_spec.rb
195
203
  - spec/lib/ckeditor5/rails/editor/props_spec.rb
196
204
  - spec/lib/ckeditor5/rails/engine_spec.rb
@@ -201,6 +209,7 @@ test_files:
201
209
  - spec/lib/ckeditor5/rails/presets/manager_spec.rb
202
210
  - spec/lib/ckeditor5/rails/presets/plugins_builder_spec.rb
203
211
  - spec/lib/ckeditor5/rails/presets/preset_builder_spec.rb
212
+ - spec/lib/ckeditor5/rails/presets/special_characters_builder_spec.rb
204
213
  - spec/lib/ckeditor5/rails/presets/toolbar_builder_spec.rb
205
214
  - spec/lib/ckeditor5/rails/semver_spec.rb
206
215
  - spec/lib/ckeditor5/rails/version_detector_spec.rb