Unverified Commit 69a6d5be authored by Lauri Timmanee's avatar Lauri Timmanee
Browse files

Issue #3245720 by hooroomoo, nod_, Wim Leers, lauriii, yash.rode:...

Issue #3245720 by hooroomoo, nod_, Wim Leers, lauriii, yash.rode: [drupalMedia] Support choosing a view mode for <drupal-media>
parent 850e8905
Loading
Loading
Loading
Loading
+9 −9
Original line number Diff line number Diff line
@@ -585,7 +585,7 @@ media_media:
    library: ckeditor5/drupal.ckeditor5.media
    class: Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media
    elements:
      - <drupal-media data-entity-type data-entity-uuid alt>
      - <drupal-media data-entity-type data-entity-uuid data-view-mode alt>
    conditions:
      filter: media_embed

@@ -612,20 +612,20 @@ media_mediaAlign:
      - drupalMedia.DrupalElementStyle
    config:
      drupalElementStyles:
        options:
          - name: 'alignRight'
        align:
          - name: 'right'
            title: 'Align right and wrap text'
            icon: 'objectRight'
            attributeName: 'data-align'
            attributeValue: 'right'
            modelElements: [ 'drupalMedia' ]
          - name: 'alignLeft'
          - name: 'left'
            title: 'Align left and wrap text'
            icon: 'objectLeft'
            attributeName: 'data-align'
            attributeValue: 'left'
            modelElements: [ 'drupalMedia' ]
          - name: 'alignCenter'
          - name: 'center'
            title: 'Align center and break text'
            icon: 'objectCenter'
            attributeName: 'data-align'
@@ -639,10 +639,10 @@ media_mediaAlign:
      drupalMedia:
        toolbar:
          - '|'
          - 'drupalElementStyle:breakText'
          - 'drupalElementStyle:alignLeft'
          - 'drupalElementStyle:alignCenter'
          - 'drupalElementStyle:alignRight'
          - 'drupalElementStyle:align:breakText'
          - 'drupalElementStyle:align:left'
          - 'drupalElementStyle:align:center'
          - 'drupalElementStyle:align:right'
          - '|'
  drupal:
    label: Media align
+1 −1

File changed.

Preview size limit exceeded, changes collapsed.

+66 −70
Original line number Diff line number Diff line
/* eslint-disable import/no-extraneous-dependencies */
/* cspell:words documentselection */
import { Command } from 'ckeditor5/src/core';
import { getClosestElementWithElementStyleAttribute } from './utils';
import { groupNameToModelAttributeKey } from '../utils';

/**
 * @module drupalMedia/drupalelementstyle/drupalelementstylecommand
 */

/**
 * Gets closest element that has drupalElementStyle attribute in schema.
 *
 * @param {module:engine/model/documentselection~DocumentSelection} selection
 *   The current document selection.
 * @param {module:engine/model/schema~Schema} schema
 *   The model schema.
 *
 * @return {null|module:engine/model/element~Element}
 *   The closest element that supports element styles.
 */
function getClosestElementWithElementStyleAttribute(selection, schema) {
  const selectedElement = selection.getSelectedElement();

  if (
    selectedElement &&
    schema.checkAttribute(selectedElement, 'drupalElementStyle')
  ) {
    return selectedElement;
  }

  let parent = selection.getFirstPosition().parent;

  while (parent) {
    if (
      parent.is('element') &&
      schema.checkAttribute(parent, 'drupalElementStyle')
    ) {
      return parent;
    }

    parent = parent.parent;
  }

  return null;
}

/**
 * The Drupal Element style command.
 *
 * This is used to apply Drupal Element style option to supported model elements.
 * This is used to apply the Drupal Element Style option to supported model
 * elements.
 *
 * @extends module:core/command~Command
 *
@@ -58,81 +23,112 @@ export default class DrupalElementStyleCommand extends Command {
   *
   * @param {module:core/editor/editor~Editor} editor
   *   The editor instance.
   * @param {Drupal.CKEditor5~DrupalElementStyle[]} styles
   * @param {Object<string, Drupal.CKEditor5~DrupalElementStyleDefinition>} styles
   *   All available Drupal Element Styles.
   */
  constructor(editor, styles) {
    super(editor);
    this._styles = new Map(
      styles.map((style) => {
    this.styles = {};
    Object.keys(styles).forEach((group) => {
      this.styles[group] = new Map(
        styles[group].map((style) => {
          return [style.name, style];
        }),
      );
    });
    this.modelAttributes = [];
    // eslint-disable-next-line no-restricted-syntax
    for (const group of Object.keys(styles)) {
      const modelAttribute = groupNameToModelAttributeKey(group);
      // Generate list of model attributes.
      this.modelAttributes.push(modelAttribute);
    }
  }

  /**
   * @inheritDoc
   */
  refresh() {
    const editor = this.editor;
    const { editor } = this;
    const element = getClosestElementWithElementStyleAttribute(
      editor.model.document.selection,
      editor.model.schema,
      this.modelAttributes,
    );

    this.isEnabled = !!element;

    if (this.isEnabled) {
      this.value = element.getAttribute('drupalElementStyle');
      // Assign value to be corresponding command value based on the element's modelAttribute.
      this.value = this.getValue(element);
    } else {
      this.value = false;
    }
  }

      // If value is falsy, check if there is a default style to apply to the
      // element.
      if (!this.value) {
  /**
   * Gets the command value including groups and values.
   *
   * @example {drupalAlign: 'left', drupalViewMode: 'full'}
   *
   * @param {module:engine/model/element~Element} element
   *   The element.
   *
   * @return {Object}
   *   The groups and values in the form of an object.
   */
  getValue(element) {
    const value = {};
    // Get value for each of the Drupal Element Style groups.
    Object.keys(this.styles).forEach((group) => {
      const modelAttribute = groupNameToModelAttributeKey(group);
      if (element.hasAttribute(modelAttribute)) {
        value[group] = element.getAttribute(modelAttribute);
      } else {
        // eslint-disable-next-line no-restricted-syntax
        for (const [name, style] of this._styles.entries()) {
        for (const [, style] of this.styles[group]) {
          // Set it to the default value.
          if (style.isDefault) {
            const appliesToCurrentElement = style.modelElements.find(
              (modelElement) => element.is('element', modelElement),
            );
            if (appliesToCurrentElement) {
              this.value = name;
              break;
            }
            value[group] = style.name;
          }
        }
      }
    } else {
      this.value = false;
    }
    });
    return value;
  }

  /**
   * Executes the command and applies the style to the selected model element.
   *
   * @example
   *    editor.execute('drupalElementStyle', { value: 'alignLeft' });
   *    editor.execute('drupalElementStyle', { value: 'left', group: 'align'});
   *
   * @param {Object} options
   *   The command options.
   * @param {string} options.value
   *   The name of the style as configured in the Drupal Element style
   *   configuration.
   * @param {string} options.group
   *   The group name of the drupalElementStyle.
   */
  execute(options = {}) {
    const editor = this.editor;
    const model = editor.model;

    const {
      editor: { model },
    } = this;
    const { value, group } = options;
    const modelAttribute = groupNameToModelAttributeKey(group);
    model.change((writer) => {
      const requestedStyle = options.value;
      const element = getClosestElementWithElementStyleAttribute(
        model.document.selection,
        model.schema,
        this.modelAttributes,
      );

      if (!requestedStyle || this._styles.get(requestedStyle).isDefault) {
        writer.removeAttribute('drupalElementStyle', element);
      if (!value || this.styles[group].get(value).isDefault) {
        // Remove attribute from the element.
        writer.removeAttribute(modelAttribute, element);
      } else {
        writer.setAttribute('drupalElementStyle', requestedStyle, element);
        // Set the attribute value on the element.
        writer.setAttribute(modelAttribute, value, element);
      }
    });
  }
+139 −95
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
import { Plugin, icons } from 'ckeditor5/src/core';
import { first } from 'ckeditor5/src/utils';
import DrupalElementStyleCommand from './drupalelementstylecommand';
import { groupNameToModelAttributeKey } from '../utils';

/**
 * @module drupalMedia/drupalelementstyle/drupalelementstyleediting
@@ -58,7 +59,10 @@ function modelToViewStyleAttribute(styles) {
    if (newStyle) {
      if (newStyle.attributeName === 'class') {
        viewWriter.addClass(newStyle.attributeValue, viewElement);
      } else {
      } else if (!newStyle.isDefault) {
        // We only reach this condition if the style is not the default value.
        // In those instances, there is no need to downcast as the default value
        // is set automatically when necessary.
        viewWriter.setAttribute(
          newStyle.attributeName,
          newStyle.attributeValue,
@@ -75,9 +79,9 @@ function modelToViewStyleAttribute(styles) {
 * This view to model converted supports styles that are configured to use
 * either CSS class or an attribute.
 *
 * Note that only one style can be applied to each model element.
 * Note that more than one style can be applied to each modelElement.
 */
function viewToModelStyleAttribute(styles) {
function viewToModelStyleAttribute(styles, modelAttribute) {
  // Convert only non–default styles.
  const nonDefaultStyles = styles.filter((style) => !style.isDefault);

@@ -94,11 +98,9 @@ function viewToModelStyleAttribute(styles) {
      return;
    }

    // Stop conversion early if the drupalElementStyle attribute isn't allowed
    // Stop conversion early if modelAttribute represents an attribute that isn't allowed
    // for the element.
    if (
      !conversionApi.schema.checkAttribute(modelElement, 'drupalElementStyle')
    ) {
    if (!conversionApi.schema.checkAttribute(modelElement, modelAttribute)) {
      return;
    }

@@ -114,7 +116,7 @@ function viewToModelStyleAttribute(styles) {
        ) {
          // And convert this style to model attribute.
          conversionApi.writer.setAttribute(
            'drupalElementStyle',
            modelAttribute,
            style.name,
            modelElement,
          );
@@ -131,7 +133,7 @@ function viewToModelStyleAttribute(styles) {
            viewElement.getAttribute(style.attributeName)
          ) {
            conversionApi.writer.setAttribute(
              'drupalElementStyle',
              modelAttribute,
              style.name,
              modelElement,
            );
@@ -145,21 +147,48 @@ function viewToModelStyleAttribute(styles) {
/**
 * The Drupal Element Style editing plugin.
 *
 * Additional Drupal Element styles can be defined with `drupalElementStyles`
 * Additional Drupal Element Styles can be defined with `drupalElementStyles`
 * configuration key.
 *
 * Additional Drupal Element Styles can support multiple axes (e.g. media
 * alignment and media view modes) by adding the new group under
 * drupalElementStyles.
 *
 * @example
 *    config:
 *      drupalElementStyles:
 *         options:
 *        side:
 *          - name: 'side'
 *            icon: 'objectBlockRight'
 *            title: 'Side image'
 *            attributeName: 'class'
 *            attributeValue: 'image-side'
 *             modelElement: ['drupalMedia']
 *            modelElements: ['drupalMedia']
 *        align:
 *           - name: 'right'
 *             title: 'Right aligned media'
 *             icon: 'objectRight'
 *             attributeName: 'data-align'
 *             modelElements: [ 'drupalMedia' ]
 *           - name: 'left'
 *             title: 'Left aligned media'
 *             icon: 'objectLeft'
 *             attributeName: 'data-align'
 *             attributeValue: 'left'
 *             modelElements: [ 'drupalMedia' ]
 *        viewMode:
 *           - name: 'full view mode'
 *             title: 'Full view mode'
 *             attributeName: 'data-view-mode'
 *             attributeValue: 'full'
 *             modelElements: [ 'drupalMedia' ]
 *           - name: 'compact view mode'
 *             title: 'Compact view mode'
 *             attributeName: 'data-view-mode'
 *             attributeValue: 'compact'
 *             modelElements: [ 'drupalMedia' ]
 *
 * @see Drupal.CKEditor5~DrupalElementStyle
 * @see Drupal.CKEditor5~DrupalElementStyleDefinition
 *
 * @extends module:core/plugin~Plugin
 *
@@ -170,16 +199,17 @@ export default class DrupalElementStyleEditing extends Plugin {
   * @inheritDoc
   */
  init() {
    const editor = this.editor;
    const { editor } = this;

    // Ensure that the drupalElementStyles.options exists always.
    editor.config.define('drupalElementStyles', { options: [] });
    const stylesConfig = editor.config.get('drupalElementStyles').options;
    const stylesConfig = editor.config.get('drupalElementStyles');
    this.normalizedStyles = {};

    /**
     * The Drupal Element Styles.
     * The Drupal Element Style definitions.
     *
     * @typedef {Object} Drupal.CKEditor5~DrupalElementStyle
     * @typedef {Object} Drupal.CKEditor5~DrupalElementStyleDefinition
     *   Object that contains an array of DrupalElementStyle objects for each
     *   group.
     *
     * @prop {string} name
     *   The name of the style used for identifying the button.
@@ -195,9 +225,10 @@ export default class DrupalElementStyleEditing extends Plugin {
     *   An icon for the style button. This needs to either refer to an icon in
     *   the CKEditor 5 core icons, or this can be the XML content of the icon.
     *
     * @type {Drupal.CKEditor5~DrupalElementStyle[]}
     * @type {Drupal.CKEditor5~DrupalElementStyleDefinition}
     */
    this.normalizedStyles = stylesConfig
    Object.keys(stylesConfig).forEach((group) => {
      this.normalizedStyles[group] = stylesConfig[group] // array of styles
        .map((style) => {
          // Allow defining style icon as a string that is referring to the
          // CKEditor 5 default icons.
@@ -206,6 +237,10 @@ export default class DrupalElementStyleEditing extends Plugin {
              style.icon = icons[style.icon];
            }
          }
          if (style.name) {
            // Make sure names are all strings.
            style.name = `${style.name}`;
          }
          return style;
        })
        .filter((style) => {
@@ -214,7 +249,7 @@ export default class DrupalElementStyleEditing extends Plugin {
            (!style.attributeName || !style.attributeValue)
          ) {
            console.warn(
            'drupalElementStyles options must include attributeName and attributeValue.',
              `${style.attributeValue} drupalElementStyles options must include attributeName and attributeValue.`,
            );
            return false;
          }
@@ -232,6 +267,7 @@ export default class DrupalElementStyleEditing extends Plugin {

          return true;
        });
    });

    this._setupConversion();

@@ -250,30 +286,36 @@ export default class DrupalElementStyleEditing extends Plugin {
   * @private
   */
  _setupConversion() {
    const editor = this.editor;
    const schema = editor.model.schema;
    const { editor } = this;
    const { schema } = editor.model;

    const groupNamesArr = Object.keys(this.normalizedStyles);

    groupNamesArr.forEach((group) => {
      const modelAttribute = groupNameToModelAttributeKey(group);

      const modelToViewConverter = modelToViewStyleAttribute(
      this.normalizedStyles,
        this.normalizedStyles[group],
      );
      const viewToModelConverter = viewToModelStyleAttribute(
      this.normalizedStyles,
        this.normalizedStyles[group],
        modelAttribute,
      );

      editor.editing.downcastDispatcher.on(
      'attribute:drupalElementStyle',
        `attribute:${modelAttribute}`,
        modelToViewConverter,
      );
      editor.data.downcastDispatcher.on(
      'attribute:drupalElementStyle',
        `attribute:${modelAttribute}`,
        modelToViewConverter,
      );

    // Allow drupalElementStyle on all model elements that have associated
    // styles.
      // Allow drupalElementStyle model attributes on all model elements that
      // have associated styles.
      const modelElements = [
        ...new Set(
        this.normalizedStyles
          this.normalizedStyles[group]
            .map((style) => {
              return style.modelElements;
            })
@@ -281,9 +323,10 @@ export default class DrupalElementStyleEditing extends Plugin {
        ),
      ];
      modelElements.forEach((modelElement) => {
      schema.extend(modelElement, { allowAttributes: 'drupalElementStyle' });
        schema.extend(modelElement, {
          allowAttributes: modelAttribute,
        });
      });

      // View to model converter that runs on all elements.
      editor.data.upcastDispatcher.on(
        'element',
@@ -292,6 +335,7 @@ export default class DrupalElementStyleEditing extends Plugin {
        // the element has been converted to a model element.
        { priority: 'low' },
      );
    });
  }

  /**
Loading