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).

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Rajab Natshah created an issue. See original summary.

rajab natshah’s picture

StatusFileSize
new1.86 KB

rajab natshah’s picture

Assigned: rajab natshah » Unassigned
Status: Active » Needs review

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

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

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

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

rajab natshah’s picture

Issue summary: View changes
phenaproxima’s picture

Hiding patch in favor of MR.

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

This will need automated test coverage...

phenaproxima’s picture

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

See #3422821: Add a config action to add entity types and bundles to a Content Moderation workflow and #3420521: Create a config action that can instantiate a field on every bundle of the target entity type for examples of that.

rajab natshah’s picture

Title: Add Action Method to Add a button plugin and settings into the Active toolbar in editor » Add dedicated config action to Add a button plugin and settings into the Active toolbar in editor

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.

rajab natshah’s picture

Issue summary: View changes
rajab natshah’s picture

Issue summary: View changes
rajab natshah’s picture

Issue summary: View changes
rajab natshah’s picture

Title: Add dedicated config action to Add a button plugin and settings into the Active toolbar in editor » Add a dedicated config action to add a button plugin and settings into the active toolbar for a CKEditor 5 editor
Issue summary: View changes
rajab natshah’s picture

Issue summary: View changes

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

rajab natshah’s picture

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.

phenaproxima’s picture

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...

rajab natshah’s picture

Version: 10.2.x-dev » 10.3.x-dev

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.

rajab natshah’s picture

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 ) - Not sure if they could be in new issues, or just use this issue for that.

WIP on Testing coverage

phenaproxima’s picture

Version: 10.3.x-dev » 11.x-dev

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.

phenaproxima’s picture

Assigned: Unassigned » phenaproxima

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

rajab natshah’s picture

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.

phenaproxima’s picture

Assigned: phenaproxima » Unassigned
Status: Needs work » Needs review
Issue tags: -Needs tests

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

wim leers’s picture

Assigned: Unassigned » wim leers
wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs review » Needs work

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 👍

phenaproxima’s picture

Status: Needs work » Needs review

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

wim leers’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

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...

phenaproxima’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
wim leers’s picture

Status: Needs review » Reviewed & tested by the community

🚢

phenaproxima’s picture

Issue summary: View changes
phenaproxima’s picture

Issue summary: View changes

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

alexpott’s picture

Version: 11.x-dev » 10.3.x-dev
Status: Reviewed & tested by the community » Fixed

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

  • alexpott committed 448d98a0 on 11.x
    Issue #3431330 by phenaproxima, Rajab Natshah, Wim Leers: Add a...

  • alexpott committed 9da91eb5 on 10.3.x
    Issue #3431330 by phenaproxima, Rajab Natshah, Wim Leers: Add a...
thejimbirch’s picture

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

phenaproxima’s picture

Issue summary: View changes

Yes. :)

Status: Fixed » Closed (fixed)

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

n.ghunaim’s picture

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").