mercury_editor-2.0.x-dev/build/js/dialog.drupal.js

build/js/dialog.drupal.js
(function () {
  'use strict';

  (($, Drupal, drupalSettings) => {
    // TODO: This does not seem to be used, but keeping for reference.
    // drupalSettings.mercuryDialog = {
    //   autoOpen: true,
    //   autoResize: false,
    //   dialogClass: '',
    //   buttonClass: 'button',
    //   buttonPrimaryClass: 'button--primary',
    // };

    function dispatchDialogEvent(eventType, dialog, element, settings) {
      // Use the jQuery if the DrupalDialogEvent is not defined for BC.
      if (typeof DrupalDialogEvent === 'undefined') {
        $(window).trigger(`dialog:${eventType}`, [dialog, $(element), settings]);
      } else {
        // eslint-disable-next-line no-undef
        const event = new DrupalDialogEvent(eventType, dialog, settings || {});
        element.dispatchEvent(event);
      }
    }

    Drupal.mercuryDialog = (element, baseOptions) => {
      // Override the jQuery dialog method to return the element, preventing
      // uncaught errors from core jQuery dialog being thrown.
      const $element = $(element);
      $element.dialog = () => $element;

      let dialogElement;
      let undef;

      // The main dialog object that will be decorated with functions and returned.
      const dialog = {
        open: false,
        returnValue: undef,
      };

      /**
       * Determines which element to append the dialog to. Defaults to the <body>
       * @param {Element} appendTo The element to append the dialog to.
       * @return {jQuery} The element to append the dialog to.
       */
      function _appendTo(appendTo) {
        if (appendTo && (appendTo.jquery || appendTo.nodeType)) {
          return $(appendTo);
        }
        return $(document)
          .find(appendTo || 'body')
          .eq(0);
      }

      /**
       * Create a button pane similar to jQuery UI dialog.
       * @param {Array} buttons The buttons to create.
       */
      function _createButtonPane(buttons) {
        const existing = dialogElement.querySelector('.me-dialog__buttonpane');
        if (existing) {
          existing.remove();
        }
        const uiDialogButtonPane = document.createElement('div');
        uiDialogButtonPane.setAttribute('slot', 'footer');
        uiDialogButtonPane.classList.add('me-dialog__buttonpane');
        dialogElement.appendChild(uiDialogButtonPane);
        if (
          $.isEmptyObject(buttons) ||
          (Array.isArray(buttons) && !buttons.length)
        ) {
          return;
        }
        buttons.forEach((props) => {
          const button = document.createElement('button');
          button.classList = props.class;
          button.classList.add('button');
          button.appendChild(document.createTextNode(props.text));
          button.addEventListener('click', props.click);
          uiDialogButtonPane.appendChild(button);
        });
      }

      /**
       * ResizeObserver callback that runs when the shadow dialog element resizes.
       *
       * @param {ResizeObserverEntry[]} entries The entries to observe.
       */
      function onDockResize(entries) {
        entries.forEach((entry) => {
          const dockElement = entry.target;
          const dockDirection = dockElement.getAttribute('data-dock');
          if (!dockElement.open || !dockDirection || dockDirection === 'none') {
            return;
          }
          const { width } = entry.contentRect;
          const { height } = entry.contentRect;
          // Dispatch custom event with resize data
          const resizeEvent = new CustomEvent('mercury:dockResize', {
            detail: { width, height },
            bubbles: true,
          });
          dialogElement.dispatchEvent(resizeEvent);
        });
      }
      const dockObserver = new ResizeObserver(onDockResize);

      /**
       * MutationObserver callback that will start the resize observer when the dialog opens.
       */
      function onDialogMutate() {
        // @todo: We may only want to attach this if the dialog is transitioning to an open state.
        dockObserver.observe(dialogElement.shadowRoot.querySelector('dialog'));
      }
      const dialogMutationObserver = new MutationObserver(onDialogMutate);

      /**
       * Converts a numeric only value to a px based CSS value.
       * @param {*} value The value to convert.
       * @return {string} A CSS length value.
       */
      function getCSSLength(value) {
        if (typeof value === 'undefined' || !/^\d+$/.test(value)) {
          return value;
        }
        return `${value}px`;
      }

      /**
       * Apply options to the <mercury-dialog> element.
       * @param {Object} options The options to apply.
       * @return {Element} The <mercury-dialog> element.
       */
      function applyOptions(options) {
        // Attributes that will be set on the <mercury-dialog> element.
        const attributeOptions = [
          'title',
          'modal',
          'dock',
          'push',
          'resizable',
          'moveable',
        ];
        // Set these options as HTML attributes on the element.
        attributeOptions.forEach((option) => {
          if (typeof options[option] !== 'undefined') {
            dialogElement.setAttribute(option, options[option]);
          }
        });

        // Set the dialog classes.
        if (options.dialogClass) {
          dialogElement.classList.add(...options.dialogClass.split(' '));
        }
        const dialogElements = {
          'ui-dialog': dialogElement,
          'ui-dialog-titlebar': dialogElement.shadowRoot.querySelector(
            'header[slot="header"]',
          ),
          'ui-dialog-title': dialogElement.shadowRoot.querySelector(
            'h1.me-dialog__title',
          ),
          'ui-dialog-content': dialogElement.shadowRoot.querySelector('main'),
          'ui-dialog-buttonpane':
            dialogElement.shadowRoot.querySelector('div[slot="footer"]'),
          'ui-dialog-buttonset':
            dialogElement.shadowRoot.querySelector('div[slot="footer"]'),
        };
        Object.keys(options.classes || {}).forEach((key) => {
          if (dialogElements[key] && options.classes[key]) {
            dialogElements[key].classList.add(...options.classes[key].split(' '));
          }
        });

        const dock = options.dock || dialogElement.getAttribute('dock');
        if (dock === 'right') {
          // Options for the main Mercury Editor tray.
          const isTrayCollapsed =
            localStorage.getItem('mercury-dialog-dock-collapsed') === 'true';

          if (!isTrayCollapsed) {
            const dialogWidth = options.width;
            const dialogHeight = options.height;
            if (
              options?.defaultWidth &&
              !document.documentElement.style.getPropertyValue(
                '--me-dialog-dock-right-width',
              )
            ) {
              document.documentElement.style.setProperty(
                '--me-dialog-dock-right-width',
                getCSSLength(options.defaultWidth),
              );
            }
            if (dialogWidth) {
              document.documentElement.style.setProperty(
                '--me-dialog-dock-right-width',
                getCSSLength(dialogWidth),
              );
            }
            if (dialogHeight) {
              document.documentElement.style.setProperty(
                '--me-dialog-dock-right-height',
                getCSSLength(dialogHeight),
              );
            }
          } else {
            document.documentElement.style.setProperty(
              '--me-dialog-dock-right-width',
              '10px',
            );
          }
        } else {
          // Options for all other dialogs.
          if (options.width) {
            document.documentElement.style.setProperty(
              '--me-dialog-width',
              getCSSLength(options.width),
            );
          }

          if (options.height) {
            document.documentElement.style.setProperty(
              '--me-dialog-height',
              getCSSLength(options.height),
            );
          }
        }

        // TODO: Determine if we need to persist the width and height of docked dialogs.
        // if (options.dock) {
        //   if (savedWidth && ['right', 'left'].includes(options.dock)) {
        //     dialogElement.setAttribute('width', savedWidth);
        //   }
        //   if (savedHeight && ['top', 'bottom'].includes(options.dock)) {
        //     dialogElement.setAttribute('height', savedHeight);
        //   }
        // }

        if (options.drupalAutoButtons && !options.buttons) {
          options.buttons = Drupal.behaviors.mercuryDialog.prepareDialogButtons(
            $(dialogElement),
          );
        }

        if (options.buttons && options.buttons.length) {
          _createButtonPane(options.buttons);
        }

        return dialogElement;
      }

      /**
       * Initializes the dialog element.
       * @param {Object} settings The settings to apply to the dialog.
       */
      function init(settings) {
        // Wrap the element in a <mercury-dialog> if it isn't already.
        if (element.tagName !== 'MERCURY-DIALOG') {
          const wrapper = $('<mercury-dialog>')
            .append($element)
            .appendTo(_appendTo(settings.appendTo));
          [dialogElement] = wrapper;
        } else {
          dialogElement = element;
        }
        applyOptions(settings);
      }

      /**
       * Closes the dialog element.
       * @param {Mixed} value The value to return from the dialog.
       */
      function closeDialog(value) {
        dispatchDialogEvent('beforeclose', dialog, $element.get(0));
        // Stop observing height and width changes.
        dockObserver.disconnect();
        dialogMutationObserver.disconnect();
        Drupal.detachBehaviors(element, null, 'unload');
        element.close();
        dialog.returnValue = value;
        dispatchDialogEvent('afterclose', dialog, $element.get(0));
        $element.remove();
      }

      /**
       * Initializes and opens a dialog element.
       * @param {Object} settings Dialog settings mimicking jQuery UI for compatibility.
       */
      function openDialog(settings) {
        settings = {
          ...drupalSettings.dialog,
          ...drupalSettings.mercuryEditor,
          ...baseOptions,
          ...settings,
        };
        dispatchDialogEvent('beforecreate', dialog, $element.get(0), settings);
        init(settings);
        dialogElement[settings.modal ? 'showModal' : 'show']();
        // Set autoResize to false to prevent Drupal core's jQuery dialog from
        // attempting to resize, which would throw an error.
        const originalResizeSetting = settings.autoResize;
        settings.autoResize = false;
        dispatchDialogEvent('aftercreate', dialog, $element.get(0), settings);
        settings.autoResize = originalResizeSetting;
        // Add a mutation observer to the dialog element.
        dialogMutationObserver.observe(dialogElement, {
          childList: true,
          attributes: true,
        });
        dialogElement.addEventListener('close', () => {
          closeDialog();
        });
      }

      dialog.show = () => {
        openDialog({ modal: false });
      };

      dialog.showModal = () => {
        openDialog({ modal: true });
      };

      dialog.applyOptions = (dialogOptions) => {
        init(dialogOptions);
      };

      dialog.close = closeDialog;

      return dialog;
    };

    Drupal.behaviors.mercuryDialog = {
      attach: (context) => {
        // Provide a known 'drupal-mercury-dialog' DOM element for Drupal-based modal
        // dialogs. Non-modal dialogs are responsible for creating their own
        // elements, since there can be multiple non-modal dialogs at a time.

        if (!$('#drupal-mercury-dialog').length) {
          // Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete
          // sit on top of dialogs. For more information see
          // http://api.jqueryui.com/theming/stacking-elements/.
          // @todo: .ui-front just sets a z-index of 100 which is not high enough
          // to overlay Gin's Toolbar.
          $(
            '<mercury-dialog id="drupal-mercury-dialog"></mercury-dialog>',
          ).appendTo('body');
        }

        // Special behaviors specific when attaching content within a dialog.
        // These behaviors usually fire after a validation error inside a dialog.
        const $dialog = $(context).closest('mercury-dialog');
        if ($dialog.length) {
          $dialog.trigger('dialogButtonsChange');
        }
      },

      prepareDialogButtons: function prepareDialogButtons($dialog) {
        const buttons = [];
        const dialogElement = $dialog[0];
        const allFormActions = dialogElement.querySelectorAll('.form-actions');
        if (allFormActions.length === 0) {
          return buttons;
        }
        const formActions = allFormActions[allFormActions.length - 1];
        formActions
          .querySelectorAll('input[type=submit], a.button, a.action-link')
          .forEach((button) => {
            button.style.display = 'none';
            buttons.push({
              text: button.textContent || button.getAttribute('value'),
              class: button.className,
              click: function click(e) {
                if (button.tagName.toLowerCase() === 'a') {
                  button.click();
                } else {
                  ['mousedown', 'mouseup', 'click'].forEach((type) => {
                    button.dispatchEvent(
                      new MouseEvent(type, {
                        bubbles: true,
                        cancelable: true,
                        view: window,
                      }),
                    );
                  });
                }
                e.preventDefault();
              },
            });
          });

        return buttons;
      },
    };

    // Moves Layout Paragraphs form buttons into the dialog button pane.
    function moveFormButtonsToDialog(event, dialog, $dialog) {
      if ($dialog[0].tagName !== 'MERCURY-DIALOG') {
        return;
      }
      if ($dialog.attr('id').indexOf('lpb-dialog-') === 0) {
        const buttons =
          Drupal.behaviors.mercuryDialog.prepareDialogButtons($dialog);
        if (buttons.length) {
          Drupal.mercuryDialog($dialog[0]).applyOptions({ buttons });
        }
      }
    }
    $(window).on('dialog:aftercreate', moveFormButtonsToDialog);

    /**
     * ResizeObserver callback that resizes the parent iframe based on
     * the height of the child document html element.
     *
     * @param {HTMLIFrameElement} iframe The iframe element to resize.
     * @return {Function} The callback function.
     */
    function onBodyResize(iframe) {
      return (entries) => {
        // Resize the parent iframe based on the html's border box height.
        if (iframe && entries.length) {
          iframe.style.height = `${entries[0].borderBoxSize[0].blockSize + 1}px`;
          iframe.style.width = `${entries[0].borderBoxSize[0].inlineSize + 1}px`;
        }
      };
    }

    /**
     * Set a max-width on the iframe's <body> to match the dialog's
     * max-width to prevent horizontal scrolling.
     *
     * @param {HTMLIFrameElement} iframe
     *   The iframe element within a mercury dialog.
     * @param {HTMLBodyElement} framedBody
     *   The body element within the iframe.
     */
    function setFrameBodyMaxWidth(iframe, framedBody) {
      const dialogStyles = window.getComputedStyle(
        iframe.closest('mercury-dialog').shadowRoot.querySelector('dialog'),
      );
      const dialogMainStyles = window.getComputedStyle(
        iframe.closest('mercury-dialog').shadowRoot.querySelector('main'),
      );
      framedBody.style.maxWidth = `calc(${dialogStyles.getPropertyValue('max-width')} - ${dialogMainStyles.getPropertyValue('padding-left')} - ${dialogMainStyles.getPropertyValue('padding-right')} - 2px)`;
    }

    /**
     * Resizes an iFrame to match the height of its inner <body> element.
     * @param {HTMLIFrameElement} iframe The iframe element to resize.
     */
    function resizeIframe(iframe) {
      const framedBody = iframe.contentWindow.document.body;
      framedBody.style.width = 'max-content';
      framedBody.style.height = 'fit-content';

      setFrameBodyMaxWidth(iframe, framedBody);

      // Observe changes to the iframe's inner <body> dimensions.
      new ResizeObserver(onBodyResize(iframe)).observe(framedBody, {
        box: 'border-box',
      });
    }

    function updateIframeSize(event, dialog, $dialog) {
      if ($dialog[0].tagName !== 'MERCURY-DIALOG') {
        return;
      }

      const iframe = $dialog[0].querySelector('iframe');
      if (!iframe) {
        return;
      }

      $dialog[0].style.setProperty('--me-dialog-height-default', 'fit-content');

      const framedBody = iframe?.contentWindow?.document?.body;
      iframe.onload = () => {
        resizeIframe(iframe);
        setFrameBodyMaxWidth(iframe, framedBody);
      };
      if (framedBody) {
        window.addEventListener('resize', () => {
          setFrameBodyMaxWidth(iframe, framedBody);
        });
      }
    }
    $(window).on('dialog:aftercreate', updateIframeSize);

    // Store open modals.
    const modalStack = [];

    // The following event handlers are used to manage the modal stack.
    // Since native dialog['modal'] elements live in the browser's top-level,
    // we need to make sure any jQuery ui modals that are opened from within
    // a mercury-dialog element get nested within the top-level modal.
    // Otherwise, the jQuery dialog will be obscured by the mercury-dialog modal.
    // See https://developer.chrome.com/blog/what-is-the-top-layer/
    function addModalToStack(event, dialog, $dialog) {
      if ($dialog[0].tagName !== 'MERCURY-DIALOG') {
        return;
      }
      if (
        $dialog[0].hasAttribute('modal') &&
        $dialog[0].getAttribute('modal') !== 'false'
      ) {
        modalStack.push($dialog);
      }
    }
    $(window).on('dialog:aftercreate', addModalToStack);

    function removeModalFromStack(event, dialog, $dialog) {
      if ($dialog[0].tagName !== 'MERCURY-DIALOG') {
        return;
      }
      if (
        $dialog[0].hasAttribute('modal') &&
        $dialog[0].getAttribute('modal') !== 'false'
      ) {
        const index = modalStack.indexOf($dialog);
        if (index > -1) {
          modalStack.splice(index, 1);
        }
      }
    }
    $(window).on('dialog:beforeclose', removeModalFromStack);

    function nestDialogInModal(event, dialog, $dialog) {
      if ($dialog[0].tagName !== 'MERCURY-DIALOG') {
        return;
      }
      if (modalStack.length > 0) {
        const $parent = $dialog.parent('.ui-dialog');
        const $overlay = $parent.next('.ui-widget-overlay');
        modalStack.slice(-1)[0].append([$parent, $overlay]);
      }
    }
    $(window).on('dialog:aftercreate', nestDialogInModal);
  })(jQuery, Drupal, drupalSettings);

})();

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

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