Add a dedicated config action to add a button plugin and settings into the active toolbar for a CKEditor 5 editor

Created on 18 March 2024, 8 months ago
Updated 28 July 2024, 4 months ago

Problem/Motivation

Not able to add Button plugins in the active toolbar for the text formats with editor

Proposed resolution

Add a config action that can add a new toolbar item, optionally at a specific position, and optionally replacing the item that's already in that position.

Sort of like this:

config:
  actions:
    editor.editor.foo:
      addItemToToolbar:
        item_name: MediaLibrary
        position: 3  # At a specific zero-based position in the toolbar - if not specified, just append the item to the toolbar
        replace: true # If there's already something at that position, replace it - for example, replacing the Image button with the media library

If the item is associated with a configurable plugin, that plugin's default settings should be automatically added to the editor (if the editor doesn't already have settings for that plugin).

✨ Feature request
Status

Fixed

Version

10.3

Component

Code

Created by

πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @Rajab Natshah
  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan
  • Pipeline finished with Success
    8 months ago
    Total: 139s
    #122447
  • Pipeline finished with Success
    8 months ago
    #122450
  • Issue was unassigned.
  • Status changed to Needs review 8 months ago
  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan

    First of all, thank you, for all your work on the Drupal Recipes!

    Learning how to use, and how to manage action methods.

    So many ideas, it feels that a recipe can so many things. ( when extended )

    This is my first suggestion (playground case), which I needed recipes to target the Editor entity. not only with simple_config_update

  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Hiding patch in favor of MR.

  • Status changed to Needs work 8 months ago
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    This will need automated test coverage...

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    I think this is a good idea but I don't think it should be done with an ActionMethod, which is a very specialized attribute specifically targeting pre-existing entity methods. For something this specialized, we should use a dedicated, specialized config action.

  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan

    Thanks, Adam, for having a look
    Noted;
    1- No more patch files.
    2- Use a dedicated config action.

    Exploring how to manage a config action for the editor config entity.

  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan
  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan
  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan
  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan
  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan

    Hetting the following error when converting to config action ( entity_method, Entity Method Deriver ) add_button_to_toolbar (addButtonToToolbar)

    In DiscoveryTrait.php line 53:
                                                                                                                                                                                     
      The "add_button_to_toolbar" plugin does not exist. Valid plugin IDs for Drupal\Core\Config\Action\ConfigActionManager are: entity_create:ensure_exists, entity_create:create,  
       simple_config_update, entity_method:field.field:setLabel, entity_method:field.field:setLabels, entity_method:filter.format:setFilterConfig, entity_method:filter.format:setF  
      ilterConfigs, entity_method:user.role:grantPermission, entity_method:user.role:grantPermissions, entity_method:core.base_field_override:setLabel, entity_method:core.base_fie  
      ld_override:setLabels, entity_method:core.entity_form_display:setComponent, entity_method:core.entity_form_display:setComponents, entity_method:core.entity_view_display:setC  
      omponent, entity_method:core.entity_view_display:setComponents                                                                                                                 
                                                                                                                                                                                     
    
    recipe <path>

    for

    <code>
    name: Add OpenAI to Basic HTMl text format
    description: A recipe to manage default OpenAI button and plugin settings for the Basic HTMl with CKEditor 5
    type: site
    config:
      actions:
        editor.editor.basic_html:
          add_button_to_toolbar:
            button_name: openai
            button_index: 1
            plugin_name: openai_ckeditor_openai
            plugin_settings:
              completion:
                enabled: true
                model: gpt-4
                temperature: 0.2
                max_tokens: 512
    

  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan

    Trying with addButtonToToolbar.php in core/modules/editor/src/Plugin/ConfigActionβ€Ž/addButtonToToolbar.php

    <?php
    
    namespace Drupal\editor\Plugin\ConfigAction;
    
    use Drupal\Core\Config\Action\ConfigActionException;
    use Drupal\Core\Config\Action\ConfigActionPluginInterface;
    use Drupal\Core\Config\ConfigFactoryInterface;
    use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    
    /**
     * @ConfigAction(
     *   id = "editor:add_button_to_toolbar",
     *   label = @Translation("Add button to toolbar"),
     *   entity_types = {"editor"},
     *   description = @Translation("Add a button plugin and settings into the Active toolbar for a CKEditor 5 editor")
     * )
     *
     * @internal
     *   This API is experimental.
     */
    final class addButtonToToolbar implements ConfigActionPluginInterface, ContainerFactoryPluginInterface {
    
      /**
       * Constructs a SimpleConfigUpdate object.
       *
       * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
       *   The config factory.
       */
      public function __construct(
        protected readonly ConfigFactoryInterface $configFactory,
      ) {
      }
    
      /**
       * {@inheritdoc}
       */
      public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
        return new static($container->get('config.factory'));
      }
    
      /**
       * {@inheritdoc}
       */
      public function apply(string $configName, mixed $value): void {
    
        $config = $this->configFactory->getEditable($configName);
    
        if ($config->isNew()) {
          throw new ConfigActionException(sprintf('Config %s does not exist so can not be updated', $configName));
        }
    
        $editor = $config->get();
    
        // Check for editor: ckeditor5 .
        if (isset($editor['editor']) && $editor['editor'] != 'ckeditor5') {
          throw new ConfigActionException(sprintf('This %s editor is not a CKEditor 5 editor', $configName));
        }
    
        if (empty($value['button_name'])) {
          throw new ConfigActionException(sprintf('Plugin name is not provided'));
        }
    
        $button_name = $value['button_name'];
    
        $button_index = -1;
        if (!empty($value['button_index'])) {
          $button_index = $value['button_index'];
        }
    
        $plugin_name = '';
        if (!empty($value['plugin_name'])) {
          $plugin_name = $value['plugin_name'];
        }
    
        $plugin_settings = [];
        if (!empty($value['plugin_settings'])) {
          $plugin_settings = $value['plugin_settings'];
        }
    
        if (!in_array($button_name, $editor['toolbar']['items'])) {
          if ($button_index == -1) {
            $editor['toolbar']['items'][] = $button_name;
          }
          elseif ($button_index == 0) {
            array_unshift($editor['toolbar']['items'], $button_name);
          }
          else {
            array_splice($editor['toolbar']['items'], $button_index, 0, $button_name);
          }
    
          if ($plugin_name != '') {
            $editor['plugins'][$plugin_name] = $plugin_settings;
          }
        }
    
        $config->setData($editor)->save();
    
      }
    
    }
    
    

    It feels that the \Drupal\Core\Config\Action\Plugin\ConfigAction\Deriver\EntityMethodDeriver is not hooking the scannner for the plugin.
    Or it is must have custom addButtonToToolbarDeriver.php
    Still learning, and exploring Entity Method Deriver, and Config Method Deriver
    Thinking of having a custom repo packages to add custom Config Actions, Config Methods, Entity Methods, or they must be located in the same config or data entity classes.

    Maybe if the ConfigActionManager support to scan for plugins in other module packages or vender packages

        // Enable this namespace to be searched for plugins.
        $namespaces[__NAMESPACE__] = 'core/lib/Drupal/Core/Config/Action';
    
        parent::__construct('Plugin/ConfigAction', $namespaces, $module_handler, 'Drupal\Core\Config\Action\ConfigActionPluginInterface', 'Drupal\Core\Config\Action\Annotation\ConfigAction');
    
    

    Looked at

    Information about the classes and interfaces that make up the Config Action
    API.

    Configuration actions are plugins that manipulate simple configuration or
    configuration entities. The configuration action plugin manager can apply
    configuration actions. For example, the API is leveraged by recipes to create
    roles if they do not exist already and grant permissions to those roles.

    To define a configuration action in a module you need to:
    - Define a Config Action plugin by creating a new class that implements the
    \Drupal\Core\Config\Action\ConfigActionPluginInterface, in namespace
    Plugin\ConfigAction under your module namespace. For more information about
    creating plugins, see the @link plugin_api Plugin API topic. @endlink
    - Config action plugins use the annotations defined by
    \Drupal\Core\Config\Action\Annotation\ConfigAction. See the
    @link annotation Annotations topic @endlink for more information about
    annotations.

    Further information and examples:
    - \Drupal\Core\Config\Action\Plugin\ConfigAction\EntityMethod derives
    configuration actions from config entity methods which have the
    \Drupal\Core\Config\Action\Attribute\ActionMethod attribute.
    - \Drupal\Core\Config\Action\Plugin\ConfigAction\EntityCreate allows you to
    create configuration entities if they do not exist.
    - \Drupal\Core\Config\Action\Plugin\ConfigAction\SimpleConfigUpdate allows
    you to update simple configuration using a config action.

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Part of the problem here might be that you're using an annotation. As of #3427874: Move all ConfigAction annotations to attributes β†’ , committed a few days ago, config action plugins now use PHP attributes for discovery.

    Forget about EntityMethodDeriver - it's not relevant. :) It doesn't do the plugin scanning. The plugin system already scans installed modules for plugins.

    Other than that, the code looks right to me...

  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan

    Got it, I see it now.
    To continue playing with PHP attributes using the 10.3.x branch
    Thanks, for following up and the hint.

  • Pipeline finished with Success
    8 months ago
    Total: 218s
    #128611
  • Pipeline finished with Success
    8 months ago
    Total: 283s
    #128610
  • Pipeline finished with Success
    8 months ago
    Total: 179s
    #128625
  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan

    Ready for any of the following:
    - Better naming conventions.
    - Better ways on (logic, Config Action Exception lookup messages)
    - More needed config actions ( remove_button_from_toolbar, update_editor_plugin

    WIP on Testing coverage

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    I have some feedback - this is a good start but should be using the entity API, not the simple config system.

    Additionally, the MR should be filed against 11.x, not 10.3.x.

  • Assigned to phenaproxima
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Looks like we're actually going to need this action for a project we're working on, so self-assigning to push this forward. :)

  • πŸ‡―πŸ‡΄Jordan Rajab Natshah Jordan

    Thanks, Adam!
    I agree with all your feedback points.

    Only drafting a needed case, and exploring what can do.
    Happy with all changes.

    Noted;
    It is needed for number of our projects/products too.

  • Pipeline finished with Success
    8 months ago
    Total: 190s
    #129576
  • Pipeline finished with Failed
    8 months ago
    #129586
  • Pipeline finished with Failed
    8 months ago
    Total: 390s
    #129588
  • Pipeline finished with Success
    8 months ago
    Total: 388s
    #129590
  • Pipeline finished with Success
    8 months ago
    Total: 387s
    #129658
  • Pipeline finished with Failed
    8 months ago
    Total: 288s
    #129695
  • Pipeline finished with Failed
    8 months ago
    Total: 394s
    #129699
  • Pipeline finished with Failed
    8 months ago
    Total: 482s
    #129721
  • Pipeline finished with Failed
    8 months ago
    Total: 129s
    #129756
  • Pipeline finished with Running
    8 months ago
    #129760
  • Issue was unassigned.
  • Status changed to Needs review 8 months ago
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Added test coverage. :) I think this is ready for an initial review.

  • Pipeline finished with Failed
    8 months ago
    #129765
  • Pipeline finished with Failed
    8 months ago
    Total: 214s
    #129770
  • Pipeline finished with Failed
    8 months ago
    Total: 148s
    #129777
  • Pipeline finished with Failed
    8 months ago
    Total: 290s
    #129784
  • Pipeline finished with Failed
    8 months ago
    #129787
  • Pipeline finished with Failed
    8 months ago
    Total: 397s
    #129788
  • Pipeline finished with Canceled
    8 months ago
    Total: 7s
    #130144
  • Pipeline finished with Running
    8 months ago
    #130145
  • Pipeline finished with Success
    8 months ago
    Total: 453s
    #130154
  • Assigned to wim leers
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Issue was unassigned.
  • Status changed to Needs work 8 months ago
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    I feel strongly about the naming "nit", because it introduces inconsistent terminology that will be confusing when creators of recipes using this config action look at *.ckeditor5.yml files.

    All my feedback is trivial to address though! πŸ˜„ And once addressed, I'll happily RTBC πŸ‘

  • Pipeline finished with Failed
    8 months ago
    Total: 218s
    #132561
  • Pipeline finished with Success
    8 months ago
    Total: 399s
    #132567
  • Status changed to Needs review 8 months ago
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Thanks for reviewing, Wim! Your feedback makes sense to me. I think I've addressed it all.

  • Status changed to Needs work 8 months ago
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Re-read the entire MR + existing review by @phenaproxima, and that's how I discovered we're missing one bit of test coverage that ensures a good recipe authoring experience: https://git.drupalcode.org/project/distributions_recipes/-/merge_request...

  • Status changed to Needs review 8 months ago
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts
  • Pipeline finished with Success
    8 months ago
    Total: 483s
    #135505
  • Status changed to RTBC 8 months ago
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    🚒

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts
  • Pipeline finished with Canceled
    8 months ago
    Total: 64s
    #136944
  • Pipeline finished with Success
    8 months ago
    Total: 384s
    #136945
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    alexpott β†’ changed the visibility of the branch 3431330-add-action-method to hidden.

  • Status changed to Fixed 8 months ago
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Committed and pushed 448d98a0df9 to 11.x and 9da91eb5e17 to 10.3.x. Thanks!

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    It doesn't look like this had a documentation follow-up ticket. Is the config action in the issue summary the final solution?

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Yes. :)

  • Automatically closed - issue fixed for 2 weeks with no activity.

  • πŸ‡―πŸ‡΄Jordan n.ghunaim Amman - Jordan

    Currently, I'm not able to add more than one item to the toolbar, any suggestions regarding that? do I need to have a new recipe file for each item?
    [error] Duplicate key "addItemToToolbar" detected at line 16 (near " item_name: fullScreen").

Production build 0.71.5 2024