ckeditor5-1.0.x-dev/js/ckeditor5.admin.js

js/ckeditor5.admin.js
(function (Drupal, drupalSettings, $) {
  const announcements = {
    onButtonMovedActive(name) {
      Drupal.announce(`Button ${name} has been moved to the active toolbar.`);
    },
    onButtonCopiedActive(name) {
      Drupal.announce(`Button ${name} has been copied to the active toolbar.`);
    },
    onButtonMovedInactive(name) {
      Drupal.announce(`Button ${name} has been removed from the active toolbar.`);
    },
  }

  const toolbarHelp = [
    {
      message: Drupal.t(
        "The toolbar buttons that don't fit the user's browser window width will be grouped in a dropdown. If multiple toolbar rows are preferred, those can be configured by adding an explicit wrapping breakpoint wherever you want to start a new row.",
        null,
        {
          context: "CKEditor 5 toolbar help text, default, no explicit wrapping breakpoint",
        }
      ),
      button: "-",
      condition: false,
    },
    {
      message: Drupal.t(
        "You have configured a multi-row toolbar by using an explicit wrapping breakpoint. This may not work well in narrow browser windows. To use automatic grouping, remove any of these divider buttons.",
        null,
        {
          context: "CKEditor 5 toolbar help text, with explicit wrapping breakpoint",
        }
      ),
      button: "-",
      condition: true,
    },
  ];

  /**
   * CKEditor 5 Admin provided in admin.js, exposes mountApp() and unmountApp().
   *
   * @type {Drupal~behavior}
   *
   * @prop {Drupal~behaviorAttach} attach
   *   Attaches admin app to edit the CKEditor 5 toolbar.
   * @prop {Drupal~behaviorDetach} detach
   *   Detaches admin app from the CKEditor 5 configuration form on 'unload'.
   *
   * @see https://github.com/zrpnr/ckeditor5-drupal-admin
   */
  Drupal.behaviors.ckeditor5Admin = {
    buttonValue: '',
    attach(context) {
      const container = context.querySelector('#ckeditor5-toolbar-app');
      const available = context.querySelector('#ckeditor5-toolbar-buttons-available');
      const selected = context.querySelector('[class*="editor-settings-toolbar-items"]');
      const { language } = drupalSettings.ckeditor5;

      if (container && window.CKEDITOR5_ADMIN) {
        // Attempting to mount the app multiple times can cause errors.
        if (!container.dataset.hasOwnProperty('vApp')) {
          [available, selected]
            .filter((el) => el)
            .forEach((el) => {
              el.classList.add('visually-hidden');
            });

          CKEDITOR5_ADMIN.mountApp({ announcements, toolbarHelp, language });
        }
      }

      // Safari's focus outlines take into account absolute positioned elements.
      // When a toolbar option is blurred, the portion of the focus outline
      // surrounding the absolutely positioned tooltip does not go away. To
      // prevent keyboard navigation users from seeing outline artifacts for
      // every option they've tabbed through, we provide a keydown listener
      // that can catch blur-causing events before the blur happens. If the
      // tooltip is hidden before the blur event, the outline will disappear
      // correctly.
      once('safari-focus-fix', document.querySelectorAll('.ckeditor5-toolbar-item')).forEach((item) => {
        item.addEventListener('keydown', (e) => {
          const keyCodeDirections = {
            9: 'tab',
            37: 'left',
            38: 'up',
            39: 'right',
            40: 'down',
          }
          if (['tab', 'left', 'up', 'right', 'down'].includes(keyCodeDirections[e.keyCode])) {
            let hideTip = false;
            const isActive = e.target.closest('[data-button-list="ckeditor5-toolbar-active__buttons"]');
            if (isActive) {
              if (['tab', 'left', 'up', 'right'].includes(keyCodeDirections[e.keyCode])) {
                hideTip = true;
              }
            } else {
              if (['tab', 'down'].includes(keyCodeDirections[e.keyCode])) {
                hideTip = true;
              }
            }
            if (hideTip) {
              e.target.querySelector('[data-expanded]').setAttribute('data-expanded', 'false');
            }
          }
        });
      });

      /**
       * Updates the UI state info in the form's 'data-drupal-ui-state' attribute.
       *
       * @param {object} states
       *   An object with one or more items with the structure { ui-property: stored-value }
       */
      const updateUiStateStorage = (states) => {
        const form = document.querySelector('#filter-format-edit-form, #filter-format-add-form');

        // Get the current stored UI state as an object.
        const currentStates = form.hasAttribute('data-drupal-ui-state') ? JSON.parse(
          form.getAttribute('data-drupal-ui-state'),
        ) : {};

        // Store the updated UI state object as an object literal in the parent
        // form's 'data-drupal-ui-state' attribute.
        form.setAttribute('data-drupal-ui-state', JSON.stringify({...currentStates, ...states}));
      };

      /**
       * Gets a stored UI state property.
       *
       * @param {string} property
       *   The UI state property to retrieve the value of.
       *
       * @return {string|null}
       *   When present, the stored value of the property.
       */
      const getUiStateStorage = (property) => {
        const form = document.querySelector('#filter-format-edit-form, #filter-format-add-form');

        if (form === null) {
          return;
        }

        // Parse the object literal stored in the form's 'data-drupal-ui-state'
        // attribute and return the value of the object property that matches
        // the 'property' argument.
        return form.hasAttribute('data-drupal-ui-state') ? JSON.parse(
          form.getAttribute('data-drupal-ui-state'),
        )[property] : null;
      }

      // Add an attribute to the parent form for storing UI states, so this
      // information can be retrieved after AJAX rebuilds.
      once('ui-state-storage', document.querySelector('#filter-format-edit-form, #filter-format-add-form')).forEach((form) => {
        form.setAttribute('data-drupal-ui-state', JSON.stringify({}));
      });

      /**
       * Maintains the active vertical tab after AJAX rebuild.
       *
       * @param {Element} verticalTabs
       *   The vertical tabs element.
       */
      const maintainActiveVerticalTab = (verticalTabs) => {
        const id = verticalTabs.id;

        // If the UI state storage has an active tab, click that tab.
        const activeTab = getUiStateStorage(`${id}-active-tab`);
        if (activeTab) {
          setTimeout(() => {
            document.querySelector(activeTab).click();
          });
        }

        // Add click listener that adds any tab click into UI storage.
        verticalTabs.querySelectorAll('.vertical-tabs__menu').forEach((tab) => {
          tab.addEventListener('click', (e) => {
            const state = {}
            const href = e.target.closest('[href]').getAttribute('href').split('--')[0];
            state[`${id}-active-tab`] = `#${id} [href^='${href}']`
            updateUiStateStorage(state);
          });
        });
      }

      once('plugin-settings', document.querySelector('#plugin-settings-wrapper')).forEach(maintainActiveVerticalTab);
      once('filter-settings', document.querySelector('#filter-settings-wrapper')).forEach(maintainActiveVerticalTab);

      // Add listeners to maintain focus after AJAX rebuilds.
      const selectedButtons = document.querySelector('#ckeditor5-toolbar-buttons-selected');
      once('textarea-listener', selectedButtons).forEach(textarea => {
        textarea.addEventListener('change', (e) => {
          const buttonName = document.activeElement.getAttribute('data-button-name');
          if(!buttonName) {
            // If there is no 'data-button-name' attribute, then the config
            // is happening via mouse.
            return;
          }
          let focusSelector = '';

          // Divider elements are treated differently as there can be multiple
          // elements with the same button name.
          if(['divider','wrapping'].includes(buttonName)) {
            const oldConfig = JSON.parse(e.detail.priorValue);
            const newConfig = JSON.parse(e.target.innerHTML);

            // If the divider is being removed from active buttons, it does not
            // 'move' anywhere. Move focus to the prior active button
            if (oldConfig.length > newConfig.length) {
              for (let item = 0; item < newConfig.length; item++) {
                if (newConfig[item] !== oldConfig[item]) {
                  focusSelector = `[data-button-list="ckeditor5-toolbar-active__buttons"] li:nth-child(${Math.min(item - 1, 0)})`;
                  break;
                }
              }
            } else if (oldConfig.length < newConfig.length) {
              // If the divider is being added, it will be the last active item.
              focusSelector = '[data-button-list="ckeditor5-toolbar-active__buttons"] li:last-child'
            } else {
              // When moving a dividers position within the active buttons.
              document.querySelectorAll(`[data-button-list="ckeditor5-toolbar-active__buttons"] [data-button-name='${buttonName}']`).forEach((divider, index) => {
                if (divider === document.activeElement) {
                  focusSelector = `${buttonName}|${index}`
                }
              });
            }
          } else {
            focusSelector = `[data-button-name='${buttonName}']`
          }

          // Store the focus selector in an attribute on the form itself, which
          // will not be overwritten after the AJAX rebuild. This makes it
          // the value available to the textarea focus listener that is
          // triggered after the AJAX rebuild.
          updateUiStateStorage({focusSelector: focusSelector});
        });

        textarea.addEventListener('focus', (e) => {
          // The selector that should receive focus is stored in the parent
          // form element. Move focus to that selector.
          const focusSelector = getUiStateStorage('focusSelector');

          if (focusSelector) {
            // If focusSelector includes '|', it is a separator that is being
            // moved within the active button list. Different logic us used to
            // determine focus since there can be multiple separators of the
            // same type within the active buttons list.
            if (focusSelector.includes('|')) {
              [buttonName, count] = focusSelector.split('|');
              document.querySelectorAll(`[data-button-list="ckeditor5-toolbar-active__buttons"] [data-button-name='${buttonName}']`).forEach((item, index) => {
                if (index === parseInt(count, 10)) {
                  item.focus();
                }
              });
            } else {
              const toFocus = document.querySelector(focusSelector);
              if (toFocus) {
                toFocus.focus();
              }
            }
          }
        });
      });

    },
    detach(context, settings, trigger) {
      const container = context.querySelector('#ckeditor5-toolbar-app');
      if (container && trigger === 'unload' && window.CKEDITOR5_ADMIN) {
        CKEDITOR5_ADMIN.unmountApp();
      }
    }
  }

  // Make a copy of the default filterStatus attach behaviors so it can be
  // called within this module's override of it.
  const originalFilterStatusAttach = Drupal.behaviors.filterStatus.attach;

  // Overrides the default filterStatus to provided functionality needs
  // specific to CKEditor 5.
  Drupal.behaviors.filterStatus.attach = function(context, settings) {
    // CKEditor 5 has uses cases that require updating the filter settings via
    // AJAX. When this happens, the checkboxes that enable filters must be
    // reprocessed by the filterStatus behavior. For this to occur:
    // - The element must be unregistered with jQuery once() so the process can
    //   run again and take into account any filter settings elements that have
    //   been added or removed from the DOM.
    // - Any listeners to the 'click.filterUpdate' event should be removed so
    //   they do not conflict with event listeners that are added as part of the
    //   AJAX refresh.
    $(context)
      .find('#filters-status-wrapper input.form-checkbox')
      .removeOnce('filter-status')
      .off('click.filterUpdate');

    // Call the original
    originalFilterStatusAttach(context, settings);

  }
})(Drupal, drupalSettings, jQuery, once);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc