ckeditor5 1.26.1 → 1.27.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 +4 -4
- data/README.md +81 -9
- data/lib/ckeditor5/rails/editor/helpers/config_helpers.rb +15 -4
- data/lib/ckeditor5/rails/engine.rb +1 -1
- data/lib/ckeditor5/rails/plugins/custom_translations_loader.rb +97 -0
- data/lib/ckeditor5/rails/plugins.rb +1 -0
- data/lib/ckeditor5/rails/presets/concerns/plugin_methods.rb +1 -19
- data/lib/ckeditor5/rails/presets/preset_builder.rb +42 -10
- data/lib/ckeditor5/rails/version.rb +2 -2
- data/spec/e2e/features/ajax_form_integration_spec.rb +1 -1
- data/spec/e2e/features/editor_types_spec.rb +3 -3
- data/spec/e2e/features/form_integration_spec.rb +1 -1
- data/spec/lib/ckeditor5/rails/editor/helpers/config_helpers_spec.rb +0 -17
- data/spec/lib/ckeditor5/rails/presets/preset_builder_spec.rb +56 -1
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 93b0f87e014924cf2ba04dc4d8ce96afef84152c4c8fc7bdebf5a736db864b95
         | 
| 4 | 
            +
              data.tar.gz: 02b97648d5c94907741382795721e5944ecc0752c3b2792cfe0c7455c482f81f
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 992f3a903d5bd123e91b572f0d0154c42044f90768264fe07a5c61d343431921ed3a45515f8f74ad48a68a0870876e9f73184cdc14266f4c0e43a4f393306a93
         | 
| 7 | 
            +
              data.tar.gz: 64a1063477787141603fd850b6de704223c15b8dd6dbedcd713ea4a5ae69fb1ebf630ba1eaef9fc245a95272f4b32b40618065e04e0c69d72b8536677ef107bd
         | 
    
        data/README.md
    CHANGED
    
    | @@ -8,7 +8,10 @@ | |
| 8 8 | 
             
            
         | 
| 9 9 | 
             
            [](https://github.com/Mati365/ckeditor5-rails/issues)
         | 
| 10 10 |  | 
| 11 | 
            -
             | 
| 11 | 
            +
            CKEditor 5 Ruby on Rails integration gem. Provides seamless integration of CKEditor 5 with Rails applications through web components and helper methods. This gem supports various editor types, including classic, inline, balloon, and decoupled editors. It also includes support for custom plugins, translations, and configuration options.
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            > [!IMPORTANT]
         | 
| 14 | 
            +
            > This gem is unofficial and not maintained by CKSource. For official CKEditor 5 documentation, visit [ckeditor.com](https://ckeditor.com/docs/ckeditor5/latest/). If you encounter any issues in integration, please report them on the [GitHub repository](https://github.com/Mati365/ckeditor5-rails/issues).
         | 
| 12 15 |  | 
| 13 16 | 
             
            <p align="center">
         | 
| 14 17 | 
             
              <img src="docs/intro-classic-editor.png" alt="CKEditor 5 Classic Editor in Ruby on Rails application">
         | 
| @@ -22,6 +25,10 @@ Add this line to your application's Gemfile: | |
| 22 25 | 
             
            gem 'ckeditor5'
         | 
| 23 26 | 
             
            ```
         | 
| 24 27 |  | 
| 28 | 
            +
            > [!NOTE]
         | 
| 29 | 
            +
            > This gem uses importmaps and does not require Webpacker or any other JavaScript bundler. It's compatible with Rails 6.0+ and `importmap-rails` gem.
         | 
| 30 | 
            +
            > While installation is simplified, it's recommended to check if jsdelivr or unpkg CDN is accessible in your environment, otherwise, you may need to configure a custom CDN (or use a commercial one).
         | 
| 31 | 
            +
             | 
| 25 32 | 
             
            In your layout:
         | 
| 26 33 |  | 
| 27 34 | 
             
            ```erb
         | 
| @@ -80,7 +87,7 @@ CKEditor5::Rails.configure do | |
| 80 87 |  | 
| 81 88 | 
             
              # Optionally, you can specify version of CKEditor 5 to use.
         | 
| 82 89 | 
             
              # If it's not specified the default version specified in the gem will be used.
         | 
| 83 | 
            -
              # version '44. | 
| 90 | 
            +
              # version '44.2.0'
         | 
| 84 91 |  | 
| 85 92 | 
             
              # Upload images to the server using the simple upload adapter, instead of Base64 encoding.
         | 
| 86 93 | 
             
              # simple_upload_adapter
         | 
| @@ -142,7 +149,9 @@ For extending CKEditor's functionality, refer to the [plugins directory](https:/ | |
| 142 149 | 
             
                  - [`simple_upload_adapter(url)` method](#simple_upload_adapterurl-method)
         | 
| 143 150 | 
             
                  - [`special_characters(&block)` method](#special_charactersblock-method)
         | 
| 144 151 | 
             
                  - [`wproofreader(version: nil, cdn: nil, **config)` method](#wproofreaderversion-nil-cdn-nil-config-method)
         | 
| 152 | 
            +
                  - [`custom_translations(lang_code = nil, translations = {})` method](#custom_translationslang_code--nil-translations---method)
         | 
| 145 153 | 
             
                - [Controller / View helpers 📦](#controller--view-helpers-)
         | 
| 154 | 
            +
                  - [`ckeditor5_translation_ref(key)` method](#ckeditor5_translation_refkey-method)
         | 
| 146 155 | 
             
                  - [`ckeditor5_element_ref(selector)` method](#ckeditor5_element_refselector-method)
         | 
| 147 156 | 
             
                  - [`ckeditor5_preset(name = nil, &block)` method](#ckeditor5_presetname--nil-block-method)
         | 
| 148 157 | 
             
              - [Including CKEditor 5 assets 📦](#including-ckeditor-5-assets-)
         | 
| @@ -201,7 +210,7 @@ You can create your own by defining it in the `config/initializers/ckeditor5.rb` | |
| 201 210 |  | 
| 202 211 | 
             
            CKEditor5::Rails.configure do
         | 
| 203 212 | 
             
              # It's possible to override the default preset right in the initializer.
         | 
| 204 | 
            -
              version '44. | 
| 213 | 
            +
              version '44.2.0'
         | 
| 205 214 |  | 
| 206 215 | 
             
              # New presets inherit properties from the default preset defined in the initializer.
         | 
| 207 216 | 
             
              # In this example, the custom preset inherits everything from default but disables the menubar:
         | 
| @@ -211,7 +220,7 @@ CKEditor5::Rails.configure do | |
| 211 220 |  | 
| 212 221 | 
             
              # In order to define preset from scratch, you can use the `inherit: false` option.
         | 
| 213 222 | 
             
              presets.define :blank_preset, inherit: false do
         | 
| 214 | 
            -
                version '44. | 
| 223 | 
            +
                version '44.2.0'
         | 
| 215 224 |  | 
| 216 225 | 
             
                # It tells the integration to fetch the newest security patches and bug fixes.
         | 
| 217 226 | 
             
                # It may be disabled, but it's highly recommended to keep it enabled to avoid
         | 
| @@ -333,7 +342,7 @@ Defines the version of CKEditor 5 to be used. The example below shows how to set | |
| 333 342 | 
             
            CKEditor5::Rails.configure do
         | 
| 334 343 | 
             
              # ... other configuration
         | 
| 335 344 |  | 
| 336 | 
            -
              version '44. | 
| 345 | 
            +
              version '44.2.0'
         | 
| 337 346 | 
             
            end
         | 
| 338 347 | 
             
            ```
         | 
| 339 348 |  | 
| @@ -345,7 +354,7 @@ In order to disable default patches, you can pass the `apply_patches: false` key | |
| 345 354 | 
             
            CKEditor5::Rails.configure do
         | 
| 346 355 | 
             
              # ... other configuration
         | 
| 347 356 |  | 
| 348 | 
            -
              version '44. | 
| 357 | 
            +
              version '44.2.0', apply_patches: false
         | 
| 349 358 | 
             
            end
         | 
| 350 359 | 
             
            ```
         | 
| 351 360 |  | 
| @@ -1190,8 +1199,71 @@ For more info about the WProofreader plugin, check the [official documentation]( | |
| 1190 1199 |  | 
| 1191 1200 | 
             
            </details>
         | 
| 1192 1201 |  | 
| 1202 | 
            +
            #### `custom_translations(lang_code = nil, translations = {})` method
         | 
| 1203 | 
            +
             | 
| 1204 | 
            +
            <details>
         | 
| 1205 | 
            +
              <summary>Define custom translations for CKEditor components and UI</summary>
         | 
| 1206 | 
            +
             | 
| 1207 | 
            +
            <br />
         | 
| 1208 | 
            +
             | 
| 1209 | 
            +
            Allows setting custom translations for editor components, UI elements, and headings. The translations are applied globally since they override the global translation object.
         | 
| 1210 | 
            +
             | 
| 1211 | 
            +
            > [!NOTE]
         | 
| 1212 | 
            +
            > This helper allows overriding builtin translations of the editor, but translations are overridden globally, as the CKEditor 5 uses a single translation object for all instances of the editor. It's recommended to use the `ckeditor5_translation_ref` helper to reference the translations in the configuration.
         | 
| 1213 | 
            +
             | 
| 1214 | 
            +
            ```rb
         | 
| 1215 | 
            +
            # config/initializers/ckeditor5.rb
         | 
| 1216 | 
            +
             | 
| 1217 | 
            +
            CKEditor5::Rails.configure do
         | 
| 1218 | 
            +
              # ... other configuration
         | 
| 1219 | 
            +
             | 
| 1220 | 
            +
              custom_translations :en, {
         | 
| 1221 | 
            +
                'Heading 1': 'Your custom translation value'
         | 
| 1222 | 
            +
              }
         | 
| 1223 | 
            +
             | 
| 1224 | 
            +
              configure :heading, {
         | 
| 1225 | 
            +
                options: [
         | 
| 1226 | 
            +
                  { model: 'heading1', title: ckeditor5_translation_ref('Heading 1') },
         | 
| 1227 | 
            +
                  # ...
         | 
| 1228 | 
            +
                ]
         | 
| 1229 | 
            +
              }
         | 
| 1230 | 
            +
            end
         | 
| 1231 | 
            +
            ```
         | 
| 1232 | 
            +
             | 
| 1233 | 
            +
            </details>
         | 
| 1234 | 
            +
             | 
| 1193 1235 | 
             
            ### Controller / View helpers 📦
         | 
| 1194 1236 |  | 
| 1237 | 
            +
            #### `ckeditor5_translation_ref(key)` method
         | 
| 1238 | 
            +
             | 
| 1239 | 
            +
            <details>
         | 
| 1240 | 
            +
              <summary>Defines a reference to a CKEditor 5 translation</summary>
         | 
| 1241 | 
            +
             | 
| 1242 | 
            +
            <br />
         | 
| 1243 | 
            +
             | 
| 1244 | 
            +
            Allows you to reference CKEditor 5 translations in the configuration. It's particularly useful when you want to use custom translations in the editor configuration.
         | 
| 1245 | 
            +
             | 
| 1246 | 
            +
            ```rb
         | 
| 1247 | 
            +
            # config/initializers/ckeditor5.rb
         | 
| 1248 | 
            +
             | 
| 1249 | 
            +
            CKEditor5::Rails.configure do
         | 
| 1250 | 
            +
              # ... other configuration
         | 
| 1251 | 
            +
             | 
| 1252 | 
            +
              custom_translations :en, {
         | 
| 1253 | 
            +
                'Heading 1': 'Your custom translation value'
         | 
| 1254 | 
            +
              }
         | 
| 1255 | 
            +
             | 
| 1256 | 
            +
              configure :heading, {
         | 
| 1257 | 
            +
                options: [
         | 
| 1258 | 
            +
                  { model: 'heading1', title: ckeditor5_translation_ref('Heading 1') },
         | 
| 1259 | 
            +
                  # ...
         | 
| 1260 | 
            +
                ]
         | 
| 1261 | 
            +
              }
         | 
| 1262 | 
            +
            end
         | 
| 1263 | 
            +
            ```
         | 
| 1264 | 
            +
             | 
| 1265 | 
            +
            </details>
         | 
| 1266 | 
            +
             | 
| 1195 1267 | 
             
            #### `ckeditor5_element_ref(selector)` method
         | 
| 1196 1268 |  | 
| 1197 1269 | 
             
            <details>
         | 
| @@ -1229,7 +1301,7 @@ It may be useful when you want to define a preset based on the current user or r | |
| 1229 1301 | 
             
            class ApplicationController < ActionController::Base
         | 
| 1230 1302 | 
             
              def show
         | 
| 1231 1303 | 
             
                @preset = ckeditor5_preset do
         | 
| 1232 | 
            -
                  version '44. | 
| 1304 | 
            +
                  version '44.2.0'
         | 
| 1233 1305 |  | 
| 1234 1306 | 
             
                  toolbar :sourceEditing, :|, :bold, :italic, :underline, :strikethrough,
         | 
| 1235 1307 | 
             
                          :subscript, :superscript, :removeFormat, :|, :bulletedList, :numberedList,
         | 
| @@ -1264,7 +1336,7 @@ If you want to override the preset defined in the initializer, you can search fo | |
| 1264 1336 | 
             
            class ApplicationController < ActionController::Base
         | 
| 1265 1337 | 
             
              def show
         | 
| 1266 1338 | 
             
                @preset = ckeditor5_preset(:default).override do
         | 
| 1267 | 
            -
                  version '44. | 
| 1339 | 
            +
                  version '44.2.0'
         | 
| 1268 1340 |  | 
| 1269 1341 | 
             
                  toolbar :sourceEditing, :|, :bold, :italic, :underline, :strikethrough,
         | 
| 1270 1342 | 
             
                          :subscript, :superscript, :removeFormat, :|, :bulletedList, :numberedList,
         | 
| @@ -1437,7 +1509,7 @@ In that scenario it's recommended to add `gpl` method to the initializer along w | |
| 1437 1509 |  | 
| 1438 1510 | 
             
            CKEditor5::Rails.configure do
         | 
| 1439 1511 | 
             
              gpl
         | 
| 1440 | 
            -
              version '44. | 
| 1512 | 
            +
              version '44.2.0'
         | 
| 1441 1513 | 
             
            end
         | 
| 1442 1514 | 
             
            ```
         | 
| 1443 1515 |  | 
| @@ -17,6 +17,20 @@ module CKEditor5::Rails::Editor::Helpers | |
| 17 17 | 
             
                  { '$element': selector }
         | 
| 18 18 | 
             
                end
         | 
| 19 19 |  | 
| 20 | 
            +
                # Creates a reference to a translation key that will be used by CKEditor.
         | 
| 21 | 
            +
                # This helper creates a special object that CKEditor recognizes as a translation reference.
         | 
| 22 | 
            +
                #
         | 
| 23 | 
            +
                # @param key [String] The translation key to reference
         | 
| 24 | 
            +
                # @return [Hash] A hash with the translation reference in CKEditor's format
         | 
| 25 | 
            +
                #
         | 
| 26 | 
            +
                # @example Referencing a translation in plugin configuration
         | 
| 27 | 
            +
                #   configure :toolbar, {
         | 
| 28 | 
            +
                #     placeholder: ckeditor5_translation_ref("editor.placeholder")
         | 
| 29 | 
            +
                #   }
         | 
| 30 | 
            +
                def ckeditor5_translation_ref(key)
         | 
| 31 | 
            +
                  { '$translation': key }
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 20 34 | 
             
                # Creates or retrieves a preset configuration for CKEditor.
         | 
| 21 35 | 
             
                # When called with a name, finds and returns an existing preset.
         | 
| 22 36 | 
             
                # When called with a block, creates a new preset with the given configuration.
         | 
| @@ -50,10 +64,7 @@ module CKEditor5::Rails::Editor::Helpers | |
| 50 64 |  | 
| 51 65 | 
             
                  raise ArgumentError, 'Configuration block is required for preset definition' unless block_given?
         | 
| 52 66 |  | 
| 53 | 
            -
                  CKEditor5::Rails::Presets::PresetBuilder.new(
         | 
| 54 | 
            -
                    disallow_inline_plugins: true,
         | 
| 55 | 
            -
                    &block
         | 
| 56 | 
            -
                  )
         | 
| 67 | 
            +
                  CKEditor5::Rails::Presets::PresetBuilder.new(&block)
         | 
| 57 68 | 
             
                end
         | 
| 58 69 | 
             
              end
         | 
| 59 70 | 
             
            end
         | 
| @@ -78,7 +78,7 @@ module CKEditor5::Rails | |
| 78 78 | 
             
                           :type, :menubar, :plugins, :plugin, :inline_plugin,
         | 
| 79 79 | 
             
                           :toolbar, :block_toolbar, :balloon_toolbar,
         | 
| 80 80 | 
             
                           :language, :ckbox, :configure, :automatic_upgrades, :simple_upload_adapter,
         | 
| 81 | 
            -
                           :editable_height, :wproofreader, to: :default_preset
         | 
| 81 | 
            +
                           :editable_height, :wproofreader, :custom_translations, to: :default_preset
         | 
| 82 82 |  | 
| 83 83 | 
             
                  def initialize(configuration)
         | 
| 84 84 | 
             
                    @configuration = configuration
         | 
| @@ -0,0 +1,97 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../editor/props_inline_plugin'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module CKEditor5::Rails::Plugins
         | 
| 6 | 
            +
              class CustomTranslationsLoader < CKEditor5::Rails::Editor::PropsInlinePlugin
         | 
| 7 | 
            +
                def initialize(translations) # rubocop:disable Metrics/MethodLength
         | 
| 8 | 
            +
                  code = <<~JS.freeze
         | 
| 9 | 
            +
                    const { Plugin } = await import('ckeditor5');
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    function resolveTranslationReferences(uiLanguage, config, visited = new WeakSet()) {
         | 
| 12 | 
            +
                      if (!config || typeof config !== 'object') {
         | 
| 13 | 
            +
                        return config;
         | 
| 14 | 
            +
                      }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      if (visited.has(config)) {
         | 
| 17 | 
            +
                        return config;
         | 
| 18 | 
            +
                      }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      visited.add(config);
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      if (Array.isArray(config)) {
         | 
| 23 | 
            +
                        config.forEach((item, index) => {
         | 
| 24 | 
            +
                          config[index] = resolveTranslationReferences(uiLanguage, item, visited);
         | 
| 25 | 
            +
                        });
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        return config;
         | 
| 28 | 
            +
                      }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                      for (const key of Object.getOwnPropertyNames(config)) {
         | 
| 31 | 
            +
                        const value = config[key];
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                        if (value && typeof value === 'object') {
         | 
| 34 | 
            +
                          if (value.$translation) {
         | 
| 35 | 
            +
                            const translationKey = value.$translation;
         | 
| 36 | 
            +
                            const translations = window.CKEDITOR_TRANSLATIONS?.[uiLanguage];
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                            if (!translations?.dictionary[translationKey]) {
         | 
| 39 | 
            +
                              console.warn(`Translation not found for key: ${translationKey}`);
         | 
| 40 | 
            +
                            }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                            config[key] = translations?.dictionary[translationKey] || translationKey;
         | 
| 43 | 
            +
                          } else {
         | 
| 44 | 
            +
                            resolveTranslationReferences(uiLanguage, value, visited);
         | 
| 45 | 
            +
                          }
         | 
| 46 | 
            +
                        }
         | 
| 47 | 
            +
                      }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      return config;
         | 
| 50 | 
            +
                    }
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    return class CustomTranslationsLoader extends Plugin {
         | 
| 53 | 
            +
                      static CUSTOM_TRANSLATIONS = Object.create( #{translations.to_json} );
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      static get pluginName() {
         | 
| 56 | 
            +
                        return 'CustomTranslationsLoader';
         | 
| 57 | 
            +
                      }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      constructor( editor ) {
         | 
| 60 | 
            +
                        super( editor );
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                        const { locale, config } = this.editor;
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                        this.#extendPack();
         | 
| 65 | 
            +
                        resolveTranslationReferences(locale.uiLanguage, config._config)
         | 
| 66 | 
            +
                      }
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      #extendPack() {
         | 
| 69 | 
            +
                        const { uiLanguage } = this.editor.locale;
         | 
| 70 | 
            +
                        const translations = this.#translations;
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                        if (!window.CKEDITOR_TRANSLATIONS) {
         | 
| 73 | 
            +
                          window.CKEDITOR_TRANSLATIONS = {};
         | 
| 74 | 
            +
                        }
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                        if (!window.CKEDITOR_TRANSLATIONS[uiLanguage]) {
         | 
| 77 | 
            +
                          window.CKEDITOR_TRANSLATIONS[uiLanguage] = { dictionary: {} };
         | 
| 78 | 
            +
                        }
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                        Object.entries(translations).forEach(([key, value]) => {
         | 
| 81 | 
            +
                          window.CKEDITOR_TRANSLATIONS[uiLanguage].dictionary[key] = value;
         | 
| 82 | 
            +
                        });
         | 
| 83 | 
            +
                      }
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                      get #translations() {
         | 
| 86 | 
            +
                        const { uiLanguage } = this.editor.locale;
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                        return CustomTranslationsLoader.CUSTOM_TRANSLATIONS[uiLanguage];
         | 
| 89 | 
            +
                      }
         | 
| 90 | 
            +
                    }
         | 
| 91 | 
            +
                  JS
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  super(:CustomTranslationsLoader, code)
         | 
| 94 | 
            +
                  compress!
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
            end
         | 
| @@ -9,6 +9,7 @@ end | |
| 9 9 | 
             
            require_relative 'plugins/simple_upload_adapter'
         | 
| 10 10 | 
             
            require_relative 'plugins/wproofreader'
         | 
| 11 11 | 
             
            require_relative 'plugins/special_characters_bootstrap'
         | 
| 12 | 
            +
            require_relative 'plugins/custom_translations_loader'
         | 
| 12 13 |  | 
| 13 14 | 
             
            # Plugin patches and fixes
         | 
| 14 15 | 
             
            require_relative 'plugins/patches/fix_color_picker_race_condition'
         | 
| @@ -9,13 +9,12 @@ module CKEditor5::Rails | |
| 9 9 | 
             
                  module PluginMethods
         | 
| 10 10 | 
             
                    extend ActiveSupport::Concern
         | 
| 11 11 |  | 
| 12 | 
            -
                    class DisallowedInlinePluginError < ArgumentError; end
         | 
| 13 12 | 
             
                    class MissingInlinePluginError < StandardError; end
         | 
| 14 13 | 
             
                    class UnsupportedESModuleError < StandardError; end
         | 
| 15 14 | 
             
                    class InvalidPatchPluginError < ArgumentError; end
         | 
| 16 15 |  | 
| 17 16 | 
             
                    included do
         | 
| 18 | 
            -
                      attr_reader : | 
| 17 | 
            +
                      attr_reader :disallow_inline_plugin_compression
         | 
| 19 18 | 
             
                    end
         | 
| 20 19 |  | 
| 21 20 | 
             
                    # Registers an external plugin loaded from a URL
         | 
| @@ -124,28 +123,11 @@ module CKEditor5::Rails | |
| 124 123 |  | 
| 125 124 | 
             
                    private
         | 
| 126 125 |  | 
| 127 | 
            -
                    # Check if the plugin looks like an inline plugin
         | 
| 128 | 
            -
                    # @param plugin [Editor::PropsBasePlugin] Plugin instance
         | 
| 129 | 
            -
                    # @return [Boolean] True if the plugin is an inline plugin
         | 
| 130 | 
            -
                    def looks_like_unsafe_inline_plugin?(plugin)
         | 
| 131 | 
            -
                      plugin.respond_to?(:code) &&
         | 
| 132 | 
            -
                        plugin.code.present? &&
         | 
| 133 | 
            -
                        !plugin.is_a?(CKEditor5::Rails::Editor::PropsPatchPlugin)
         | 
| 134 | 
            -
                    end
         | 
| 135 | 
            -
             | 
| 136 126 | 
             
                    # Register a plugin in the editor configuration.
         | 
| 137 127 | 
             
                    #
         | 
| 138 | 
            -
                    # It will raise an error if inline plugins are not allowed and the plugin is an inline plugin.
         | 
| 139 | 
            -
                    # Most likely, this is being thrown when you use inline_plugin definition in a place where
         | 
| 140 | 
            -
                    # it's not allowed (e.g. in a preset definition placed in controller).
         | 
| 141 | 
            -
                    #
         | 
| 142 128 | 
             
                    # @param plugin_obj [Editor::PropsBasePlugin] Plugin instance to register
         | 
| 143 129 | 
             
                    # @return [Editor::PropsBasePlugin] The registered plugin
         | 
| 144 130 | 
             
                    def register_plugin(plugin_obj)
         | 
| 145 | 
            -
                      if disallow_inline_plugins && looks_like_unsafe_inline_plugin?(plugin_obj)
         | 
| 146 | 
            -
                        raise DisallowedInlinePluginError, 'Inline plugins are not allowed here.'
         | 
| 147 | 
            -
                      end
         | 
| 148 | 
            -
             | 
| 149 131 | 
             
                      config[:plugins] ||= []
         | 
| 150 132 | 
             
                      config[:plugins] << plugin_obj
         | 
| 151 133 | 
             
                      plugin_obj
         | 
| @@ -6,7 +6,7 @@ require_relative 'special_characters_builder' | |
| 6 6 |  | 
| 7 7 | 
             
            module CKEditor5::Rails
         | 
| 8 8 | 
             
              module Presets
         | 
| 9 | 
            -
                class PresetBuilder
         | 
| 9 | 
            +
                class PresetBuilder # rubocop:disable Metrics/ClassLength
         | 
| 10 10 | 
             
                  include Editor::Helpers::Config
         | 
| 11 11 | 
             
                  include Concerns::ConfigurationMethods
         | 
| 12 12 | 
             
                  include Concerns::PluginMethods
         | 
| @@ -17,8 +17,7 @@ module CKEditor5::Rails | |
| 17 17 | 
             
                  #     gpl
         | 
| 18 18 | 
             
                  #     type :classic
         | 
| 19 19 | 
             
                  #   end
         | 
| 20 | 
            -
                  def initialize( | 
| 21 | 
            -
                    @disallow_inline_plugins = disallow_inline_plugins
         | 
| 20 | 
            +
                  def initialize(&block)
         | 
| 22 21 | 
             
                    @version = nil
         | 
| 23 22 | 
             
                    @premium = false
         | 
| 24 23 | 
             
                    @cdn = :jsdelivr
         | 
| @@ -28,6 +27,7 @@ module CKEditor5::Rails | |
| 28 27 | 
             
                    @ckbox = nil
         | 
| 29 28 | 
             
                    @editable_height = nil
         | 
| 30 29 | 
             
                    @automatic_upgrades = false
         | 
| 30 | 
            +
                    @custom_translations = {}
         | 
| 31 31 | 
             
                    @config = {
         | 
| 32 32 | 
             
                      plugins: [],
         | 
| 33 33 | 
             
                      toolbar: []
         | 
| @@ -36,6 +36,12 @@ module CKEditor5::Rails | |
| 36 36 | 
             
                    instance_eval(&block) if block_given?
         | 
| 37 37 | 
             
                  end
         | 
| 38 38 |  | 
| 39 | 
            +
                  def deconstruct_keys(keys)
         | 
| 40 | 
            +
                    keys.index_with do |key|
         | 
| 41 | 
            +
                      public_send(key)
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 39 45 | 
             
                  # @example Copy preset and modify it
         | 
| 40 46 | 
             
                  #   original = PresetBuilder.new
         | 
| 41 47 | 
             
                  #   copied = original.initialize_copy(original)
         | 
| @@ -44,6 +50,7 @@ module CKEditor5::Rails | |
| 44 50 |  | 
| 45 51 | 
             
                    @translations = source.translations.dup
         | 
| 46 52 | 
             
                    @ckbox = source.ckbox.dup if source.ckbox
         | 
| 53 | 
            +
                    @custom_translations = source.custom_translations.deep_dup
         | 
| 47 54 | 
             
                    @config = {
         | 
| 48 55 | 
             
                      plugins: source.config[:plugins].map(&:dup),
         | 
| 49 56 | 
             
                      toolbar: deep_copy_toolbar(source.config[:toolbar])
         | 
| @@ -64,12 +71,6 @@ module CKEditor5::Rails | |
| 64 71 | 
             
                    license_key == 'GPL'
         | 
| 65 72 | 
             
                  end
         | 
| 66 73 |  | 
| 67 | 
            -
                  def deconstruct_keys(keys)
         | 
| 68 | 
            -
                    keys.index_with do |key|
         | 
| 69 | 
            -
                      public_send(key)
         | 
| 70 | 
            -
                    end
         | 
| 71 | 
            -
                  end
         | 
| 72 | 
            -
             | 
| 73 74 | 
             
                  # Create a new preset by overriding current configuration
         | 
| 74 75 | 
             
                  # @example Override existing preset
         | 
| 75 76 | 
             
                  #   preset.override do
         | 
| @@ -200,7 +201,7 @@ module CKEditor5::Rails | |
| 200 201 | 
             
                  # Apply integration patches for the current version
         | 
| 201 202 | 
             
                  # @return [void]
         | 
| 202 203 | 
             
                  def apply_integration_patches
         | 
| 203 | 
            -
                    patch_plugin | 
| 204 | 
            +
                    patch_plugin(Plugins::Patches::FixColorPickerRaceCondition.new)
         | 
| 204 205 | 
             
                  end
         | 
| 205 206 |  | 
| 206 207 | 
             
                  # Enable or disable automatic version upgrades
         | 
| @@ -334,6 +335,37 @@ module CKEditor5::Rails | |
| 334 335 | 
             
                    config[:language].present?
         | 
| 335 336 | 
             
                  end
         | 
| 336 337 |  | 
| 338 | 
            +
                  # Configure custom translations for the editor
         | 
| 339 | 
            +
                  # @param lang_code [Symbol] Language code for translations (e.g. :en, :pl)
         | 
| 340 | 
            +
                  # @param translations [Hash] A hash containing translations in format { key => translation }
         | 
| 341 | 
            +
                  # @example Add multiple translations
         | 
| 342 | 
            +
                  #   custom_translations :en, {
         | 
| 343 | 
            +
                  #     'my.button': 'My Button'
         | 
| 344 | 
            +
                  #   }
         | 
| 345 | 
            +
                  #   custom_translations :pl, {
         | 
| 346 | 
            +
                  #     'my.button': 'Mój przycisk'
         | 
| 347 | 
            +
                  #   }
         | 
| 348 | 
            +
                  # @example Use translations in configuration with ckeditor5_translation_ref
         | 
| 349 | 
            +
                  #   custom_translations :en, {
         | 
| 350 | 
            +
                  #     'custom.heading': 'Custom Section'
         | 
| 351 | 
            +
                  #   }
         | 
| 352 | 
            +
                  #
         | 
| 353 | 
            +
                  #   configure :heading, {
         | 
| 354 | 
            +
                  #     options: [
         | 
| 355 | 
            +
                  #       { model: 'heading1', title: ckeditor5_translation_ref('custom.heading') }
         | 
| 356 | 
            +
                  #     ]
         | 
| 357 | 
            +
                  #   }
         | 
| 358 | 
            +
                  # @return [void]
         | 
| 359 | 
            +
                  def custom_translations(lang_code = nil, translations = {})
         | 
| 360 | 
            +
                    return @custom_translations if lang_code.blank?
         | 
| 361 | 
            +
             | 
| 362 | 
            +
                    @custom_translations[lang_code.to_sym] ||= {}
         | 
| 363 | 
            +
                    @custom_translations[lang_code.to_sym].merge!(translations)
         | 
| 364 | 
            +
             | 
| 365 | 
            +
                    plugins.remove(:CustomTranslationsLoader)
         | 
| 366 | 
            +
                    plugins.prepend(Plugins::CustomTranslationsLoader.new(@custom_translations))
         | 
| 367 | 
            +
                  end
         | 
| 368 | 
            +
             | 
| 337 369 | 
             
                  # Configure editor language
         | 
| 338 370 | 
             
                  # @param ui [Symbol, nil] UI language code
         | 
| 339 371 | 
             
                  # @param content [Symbol] Content language code
         | 
| @@ -8,7 +8,7 @@ RSpec.describe 'AJAX Form Integration', type: :feature, js: true do | |
| 8 8 | 
             
                setup_form_tracking(page)
         | 
| 9 9 | 
             
              end
         | 
| 10 10 |  | 
| 11 | 
            -
              shared_examples 'an ajax form with CKEditor' do |form_testid, editor_testid, submit_testid, response_id| | 
| 11 | 
            +
              shared_examples 'an ajax form with CKEditor' do |form_testid, editor_testid, submit_testid, response_id|
         | 
| 12 12 | 
             
                let(:form) { find("[data-testid='#{form_testid}']") }
         | 
| 13 13 | 
             
                let(:editor) { find("[data-testid='#{editor_testid}']") }
         | 
| 14 14 | 
             
                let(:editable) { editor.find('.ck-editor__editable') }
         | 
| @@ -44,10 +44,10 @@ RSpec.describe 'CKEditor5 Types Integration', type: :feature, js: true do | |
| 44 44 | 
             
                end
         | 
| 45 45 | 
             
              end
         | 
| 46 46 |  | 
| 47 | 
            -
              shared_examples 'a multiroot editor that fires change events' do |path, editables| | 
| 47 | 
            +
              shared_examples 'a multiroot editor that fires change events' do |path, editables|
         | 
| 48 48 | 
             
                before { visit path }
         | 
| 49 49 |  | 
| 50 | 
            -
                it 'sends properly change events with proper payload for editables' do | 
| 50 | 
            +
                it 'sends properly change events with proper payload for editables' do
         | 
| 51 51 | 
             
                  editors = editables.map do |name|
         | 
| 52 52 | 
             
                    find("[data-testid='#{name}-editable']")
         | 
| 53 53 | 
             
                  end
         | 
| @@ -151,7 +151,7 @@ RSpec.describe 'CKEditor5 Types Integration', type: :feature, js: true do | |
| 151 151 | 
             
                  expect(page).to have_css('.ck-toolbar', count: 1)
         | 
| 152 152 | 
             
                end
         | 
| 153 153 |  | 
| 154 | 
            -
                it 'handles dynamically added editables' do | 
| 154 | 
            +
                it 'handles dynamically added editables' do
         | 
| 155 155 | 
             
                  # Set up event listener
         | 
| 156 156 | 
             
                  page.execute_script(<<~JS)
         | 
| 157 157 | 
             
                    window._newEditableEvents = [];
         | 
| @@ -8,7 +8,7 @@ RSpec.describe 'Form Integration', type: :feature, js: true do | |
| 8 8 | 
             
                setup_form_tracking(page)
         | 
| 9 9 | 
             
              end
         | 
| 10 10 |  | 
| 11 | 
            -
              shared_examples 'a form with CKEditor' do |form_testid, editor_testid, submit_testid| | 
| 11 | 
            +
              shared_examples 'a form with CKEditor' do |form_testid, editor_testid, submit_testid|
         | 
| 12 12 | 
             
                let(:form) { find("[data-testid='#{form_testid}']") }
         | 
| 13 13 | 
             
                let(:editor) { find("[data-testid='#{editor_testid}']") }
         | 
| 14 14 | 
             
                let(:editable) { editor.find('.ck-editor__editable') }
         | 
| @@ -38,23 +38,6 @@ 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 | 
            -
                          const { Plugin } = await import( 'ckeditor5' );
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                          return class CustomPlugin extends Plugin {
         | 
| 49 | 
            -
                            static get pluginName() { return 'CustomPlugin'; }
         | 
| 50 | 
            -
                          }
         | 
| 51 | 
            -
                        JS
         | 
| 52 | 
            -
                      end
         | 
| 53 | 
            -
                    end.to raise_error(
         | 
| 54 | 
            -
                      CKEditor5::Rails::Presets::Concerns::PluginMethods::DisallowedInlinePluginError,
         | 
| 55 | 
            -
                      'Inline plugins are not allowed here.'
         | 
| 56 | 
            -
                    )
         | 
| 57 | 
            -
                  end
         | 
| 58 41 | 
             
                end
         | 
| 59 42 |  | 
| 60 43 | 
             
                context 'when neither name nor block is provided' do
         | 
| @@ -494,6 +494,61 @@ RSpec.describe CKEditor5::Rails::Presets::PresetBuilder do | |
| 494 494 | 
             
                end
         | 
| 495 495 | 
             
              end
         | 
| 496 496 |  | 
| 497 | 
            +
              describe '#custom_translations' do
         | 
| 498 | 
            +
                it 'returns empty hash when called without arguments' do
         | 
| 499 | 
            +
                  expect(builder.custom_translations).to eq({})
         | 
| 500 | 
            +
                end
         | 
| 501 | 
            +
             | 
| 502 | 
            +
                it 'stores translations for a language' do
         | 
| 503 | 
            +
                  translations = { 'my.button': 'My Button' }
         | 
| 504 | 
            +
                  builder.custom_translations(:en, translations)
         | 
| 505 | 
            +
                  expect(builder.custom_translations).to eq({ en: translations })
         | 
| 506 | 
            +
                end
         | 
| 507 | 
            +
             | 
| 508 | 
            +
                it 'merges translations for the same language' do
         | 
| 509 | 
            +
                  builder.custom_translations(:en, { 'button.one': 'One' })
         | 
| 510 | 
            +
                  builder.custom_translations(:en, { 'button.two': 'Two' })
         | 
| 511 | 
            +
             | 
| 512 | 
            +
                  expect(builder.custom_translations[:en]).to eq({
         | 
| 513 | 
            +
                                                                   'button.one': 'One',
         | 
| 514 | 
            +
                                                                   'button.two': 'Two'
         | 
| 515 | 
            +
                                                                 })
         | 
| 516 | 
            +
                end
         | 
| 517 | 
            +
             | 
| 518 | 
            +
                it 'handles multiple languages' do
         | 
| 519 | 
            +
                  builder.custom_translations(:en, { 'button': 'Button' })
         | 
| 520 | 
            +
                  builder.custom_translations(:pl, { 'button': 'Przycisk' })
         | 
| 521 | 
            +
             | 
| 522 | 
            +
                  expect(builder.custom_translations).to eq({
         | 
| 523 | 
            +
                                                              en: { button: 'Button' },
         | 
| 524 | 
            +
                                                              pl: { button: 'Przycisk' }
         | 
| 525 | 
            +
                                                            })
         | 
| 526 | 
            +
                end
         | 
| 527 | 
            +
             | 
| 528 | 
            +
                it 'normalizes language codes to symbols' do
         | 
| 529 | 
            +
                  builder.custom_translations('EN', { 'button': 'Button' })
         | 
| 530 | 
            +
                  expect(builder.custom_translations.keys).to eq([:EN])
         | 
| 531 | 
            +
                end
         | 
| 532 | 
            +
             | 
| 533 | 
            +
                it 'adds and replaces CustomTranslationsLoader plugin' do
         | 
| 534 | 
            +
                  translations = { 'my.button': 'My Button' }
         | 
| 535 | 
            +
             | 
| 536 | 
            +
                  expect do
         | 
| 537 | 
            +
                    builder.custom_translations(:en, translations)
         | 
| 538 | 
            +
                  end.to change { builder.config[:plugins].count }.by(1)
         | 
| 539 | 
            +
             | 
| 540 | 
            +
                  plugin = builder.config[:plugins].first
         | 
| 541 | 
            +
                  expect(plugin).to be_a(CKEditor5::Rails::Plugins::CustomTranslationsLoader)
         | 
| 542 | 
            +
             | 
| 543 | 
            +
                  # Adding more translations should replace the plugin
         | 
| 544 | 
            +
                  expect do
         | 
| 545 | 
            +
                    builder.custom_translations(:pl, { 'my.button': 'Mój przycisk' })
         | 
| 546 | 
            +
                  end.not_to(change { builder.config[:plugins].count })
         | 
| 547 | 
            +
             | 
| 548 | 
            +
                  expect(builder.config[:plugins].first).to be_a(CKEditor5::Rails::Plugins::CustomTranslationsLoader)
         | 
| 549 | 
            +
                end
         | 
| 550 | 
            +
              end
         | 
| 551 | 
            +
             | 
| 497 552 | 
             
              describe '#deep_copy_toolbar' do
         | 
| 498 553 | 
             
                context 'with array toolbar' do
         | 
| 499 554 | 
             
                  it 'returns duplicated array' do
         | 
| @@ -552,7 +607,7 @@ RSpec.describe CKEditor5::Rails::Presets::PresetBuilder do | |
| 552 607 | 
             
                  expect { builder.special_characters }.not_to raise_error
         | 
| 553 608 | 
             
                end
         | 
| 554 609 |  | 
| 555 | 
            -
                it 'configures special characters with groups and items' do | 
| 610 | 
            +
                it 'configures special characters with groups and items' do
         | 
| 556 611 | 
             
                  builder.special_characters do
         | 
| 557 612 | 
             
                    group 'Emoji', label: 'Emoticons' do
         | 
| 558 613 | 
             
                      item 'smiley', '😊'
         | 
    
        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. | 
| 4 | 
            +
              version: 1.27.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-02- | 
| 12 | 
            +
            date: 2025-02-14 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: rails
         | 
| @@ -90,6 +90,7 @@ files: | |
| 90 90 | 
             
            - lib/ckeditor5/rails/hooks/importmap.rb
         | 
| 91 91 | 
             
            - lib/ckeditor5/rails/hooks/simple_form.rb
         | 
| 92 92 | 
             
            - lib/ckeditor5/rails/plugins.rb
         | 
| 93 | 
            +
            - lib/ckeditor5/rails/plugins/custom_translations_loader.rb
         | 
| 93 94 | 
             
            - lib/ckeditor5/rails/plugins/patches/fix_color_picker_race_condition.rb
         | 
| 94 95 | 
             
            - lib/ckeditor5/rails/plugins/simple_upload_adapter.rb
         | 
| 95 96 | 
             
            - lib/ckeditor5/rails/plugins/special_characters_bootstrap.rb
         |