fullcalendar_block-1.0.0-rc4/js/fullcalendar_block.js

js/fullcalendar_block.js
/**
 * @file
 * Fullcalendar Block JavaScript file.
 */

/* eslint no-unused-vars: ["warn", { "argsIgnorePattern": "^_" }] */
/* eslint no-console: ["warn", { allow: ["warn"] }] */

// Codes run both on normal page loads and when data is loaded by AJAX (or BigPipe!)
// @See https://www.drupal.org/docs/drupal-apis/javascript-api/javascript-api-overview
(($, Drupal, once, drupalSettings, DOMPurify, FullCalendar) => {
  /**
   * @namespace
   */
  Drupal.fullcalendar_block = {};

  Drupal.fullcalendar_block.instances = {};

  /**
   * Retrieves a block instance setting.
   *
   * If the settings path is not specified, then the entire block instance settings is returned instead.
   *
   * @param {string} blockIndex - The index of the block.
   * @param {string|null} settingPath - The path to the desired setting.
   * @param {*} defaultValue - The default value to return if the setting is not found.
   * @return {*} The value of the requested setting or the default value.
   */
  Drupal.fullcalendar_block.getSettings = function getSettings(
    blockIndex,
    settingPath = null,
    defaultValue = null,
  ) {
    const blockSettings =
      drupalSettings &&
      drupalSettings.fullCalendarBlock &&
      drupalSettings.fullCalendarBlock[blockIndex];
    if (!settingPath) {
      return blockSettings;
    }
    // https://stackoverflow.com/a/43849204
    return settingPath
      .split(/[.\\[\]'"]/)
      .filter(Boolean)
      .reduce((o, p) => {
        if (o && typeof o[p] !== 'undefined') {
          return o[p];
        }
        return defaultValue;
      }, blockSettings);
  };

  /**
   * Sanitize a piece of text. Addressing any potential XSS attacks.
   *
   * @param {string} html - The HTML text to be sanitized.
   * @param {string} blockIndex - The block index associated with the description.
   * @param {object} _info - Additional information related to the description.
   * @return {string} The sanitized HTML text.
   */
  Drupal.fullcalendar_block.sanitizeDescription = function sanitizeDescription(
    html,
    blockIndex,
    _info,
  ) {
    return DOMPurify.sanitize(
      html,
      Drupal.fullcalendar_block.getSettings(
        blockIndex,
        'advanced.dompurify_options',
        {},
      ),
    );
  };

  /**
   * Open a URL in the current tab or a new one.
   *
   * @param {string} url - The URL to navigate to.
   * @param {boolean} newTab - A boolean indicating whether to open the URL in a new tab.
   */
  Drupal.fullcalendar_block.gotoURL = function gotoURL(url, newTab) {
    const eventURL = new URL(url, window.location.origin);
    if (!eventURL.origin || eventURL.origin === 'null') {
      // https://bugs.chromium.org/p/chromium/issues/detail?id=608606
      return;
    }
    if (newTab) {
      // Open a new window to show the details of the event.
      window.open(url, '_blank').focus();
    } else {
      window.location.href = url;
    }
  };

  /**
   * Sanitize and render the title to account for ampersands.
   *
   * @param {string} title - The title to be sanitized and rendered.
   * @return {string} The sanitized and rendered title.
   */
  Drupal.fullcalendar_block.sanitizeTitle = function sanitizeTitle(title) {
    const doc = new DOMParser().parseFromString(title, 'text/html');
    return doc.documentElement.innerText;
  };

  /**
   * Transform event object to respond to the Drupal settings.
   *
   * @param {string} blockIndex - The index of the block.
   * @param {Object} info - The event object information.
   */
  function eventDataTransform(blockIndex, info) {
    // Replace the title with the sanitized and rendered version.
    const sanitizeHtmlTitle = Drupal.fullcalendar_block.getSettings(
      blockIndex,
      'advanced.html_title',
      false,
    );
    const rawTitleField = Drupal.fullcalendar_block.getSettings(
      blockIndex,
      'advanced.raw_title_field',
      'rawTitle',
    );
    if (
      (sanitizeHtmlTitle && info[rawTitleField] !== false) ||
      info[rawTitleField] === true
    ) {
      info.title = Drupal.fullcalendar_block.sanitizeTitle(info.title);
    }
    // Clean up rrule string generated by a Drupal view.
    if (
      info.rrule &&
      typeof (info.rrule === 'string' || info.rrule instanceof String)
    ) {
      // Format the rrule string.
      // Remove all breaks(new line).
      info.rrule = info.rrule.replaceAll(/[\s\n\r\\n]+(?=RRULE:)/g, '');
      info.rrule = info.rrule.replaceAll(' ', '');
      // Remove br tag.
      info.rrule = info.rrule.replaceAll('<br/>', '');
      // Put one new line back.
      info.rrule = info.rrule.replace('RRULE:', '\nRRULE:');
    }
    // Event background color.
    const backgroundFieldName = Drupal.fullcalendar_block.getSettings(
      blockIndex,
      'advanced.event_background.field_name',
      false,
    );
    if (backgroundFieldName && info[backgroundFieldName]) {
      const colorMap = Drupal.fullcalendar_block.getSettings(
        blockIndex,
        'advanced.event_background.color_map',
        false,
      );
      if (colorMap && Array.isArray(colorMap)) {
        // Find the first match to use for the background colour.
        colorMap.some((map) => {
          if (typeof map === 'string') {
            const colorSet = map.split(' ');
            if (
              colorSet[0] &&
              colorSet[1] &&
              colorSet[0] === info[backgroundFieldName]
            ) {
              [, info.backgroundColor] = colorSet;
              return true;
            }
          }
          return false;
        });
      }
    }
  }

  /**
   * The event click callback.
   *
   * @param {Object} info - Information about the clicked event.
   * @param {Object} info.event - The associated event object.
   * @param {Object} info.view - The current View Object.
   * @param {HTMLElement} info.el - The HTML element for this event.
   * @param {Event} info.jsEvent - The native JavaScript event with low-level information such as click coordinates.
   */
  function eventClick(info) {
    info.jsEvent.preventDefault();
    const blockIndex = this.el.getAttribute('data-calendar-block-index');
    const openMode = parseInt(
      Drupal.fullcalendar_block.getSettings(blockIndex, 'dialog_open', 1),
      10,
    );
    if (openMode === 1) {
      // Open in a dialog.
      let dialogWidth = parseInt(
        Drupal.fullcalendar_block.getSettings(blockIndex, 'dialog_width', 400),
        10,
      );
      const dialogType = Drupal.fullcalendar_block.getSettings(
        blockIndex,
        'advanced.dialog_type',
        'modal',
      );
      const dialogOptions = Drupal.fullcalendar_block.getSettings(
        blockIndex,
        'advanced.dialog_options',
        {
          // autoResize option will turn off resizable by default.
          // autoResize: false,
        },
      );

      if (dialogWidth <= 0) {
        // The dialog width is unneeded.
        dialogWidth = undefined;
      }

      const draggableDialog = Drupal.fullcalendar_block.getSettings(
        blockIndex,
        'advanced.draggable',
        false,
      );
      const resizableDialog = Drupal.fullcalendar_block.getSettings(
        blockIndex,
        'advanced.resizable',
        false,
      );

      // Attempt to render descriptions.
      const descriptionPopup = Drupal.fullcalendar_block.getSettings(
        blockIndex,
        'advanced.description_popup',
        false,
      );
      const description =
        info.event.extendedProps[
          Drupal.fullcalendar_block.getSettings(
            blockIndex,
            'advanced.description_field',
            'des',
          )
        ];

      if (descriptionPopup && description) {
        // Create a Drupal dialog to render the description.
        const descriptionEl = document.createElement('div');
        descriptionEl.setAttribute('id', 'fullcalendar-block-dialog');
        descriptionEl.innerHTML = Drupal.fullcalendar_block.sanitizeDescription(
          description,
          blockIndex,
          info,
        );
        Drupal.dialog(
          descriptionEl,
          $.extend(
            {
              title: info.event.title,
              dialogClass:
                'fullcalendar-block-dialog fullcalendar-block-dialog--description',
              resizable: resizableDialog,
              draggable: draggableDialog,
              width: dialogWidth,
              fullcalendarBlockIndex: blockIndex,
            },
            dialogOptions,
          ),
        ).showModal();
        // Trigger Drupal's attachment behaviour.
        Drupal.attachBehaviors(descriptionEl);
        return;
      }

      if (!info.event.url) {
        return;
      }
      const openDialog = Drupal.ajax({
        url: info.event.url,
        dialogType,
        dialog: $.extend(
          {
            dialogClass:
              'fullcalendar-block-dialog fullcalendar-block-dialog--url',
            resizable: resizableDialog,
            draggable: draggableDialog,
            width: dialogWidth,
            fullcalendarBlockIndex: blockIndex,
          },
          dialogOptions,
        ),
      }).execute();

      openDialog
        .done(() => {
          // The modal dialog open successfully.
        })
        .fail(() => {
          // The Ajax modal couldn't open.
          // Open in a new tab instead.
          Drupal.fullcalendar_block.gotoURL(info.event.url, true);
        });
    } else if (openMode === 2 && info.event.url) {
      // Open in the current window.
      Drupal.fullcalendar_block.gotoURL(info.event.url, false);
    } else if (info.event.url) {
      // Open in a new tab.
      Drupal.fullcalendar_block.gotoURL(info.event.url, true);
    }
  }

  $(window).on('dialog:aftercreate', (e, dialog, $element, settings) => {
    const blockIndex = settings.fullcalendarBlockIndex;
    if (blockIndex) {
      // Attempt to pass the draggable/resizable options.
      // @see dialog.position.es6.js
      const autoResize =
        settings.autoResize === true || settings.autoResize === 'true';
      const $dialog = $element.dialog('widget');
      if (
        (settings.draggable === true || settings.draggable === 'true') &&
        typeof $.fn.draggable === 'function'
      ) {
        if (autoResize) {
          // Make the dialog instance draggable.
          $element.dialog('option', { draggable: true });
        }
        // Add the custom draggable options.
        $dialog.draggable(
          'option',
          Drupal.fullcalendar_block.getSettings(
            blockIndex,
            'advanced.draggable_options',
            {},
          ),
        );
      }
      if (
        // Resizable is fundamentally unsupported by the resize widget due to the already created event listeners.
        !autoResize &&
        (settings.resizable === true || settings.resizable === 'true') &&
        typeof $.fn.resizable === 'function'
      ) {
        if ($dialog.resizable('option', 'handles') === 'true') {
          // Drupal AJAX can mangle up the default resizable handle options when it returns. Reset it ourselves.
          // @see jquery.ui.dialog _makeResizable()
          $dialog.resizable('option', 'handles', 'n,e,s,w,se,sw,ne,nw');
        }
        // Add the custom resizable options.
        $dialog.resizable(
          'option',
          Drupal.fullcalendar_block.getSettings(
            blockIndex,
            'advanced.resizable_options',
            {},
          ),
        );
      }
    }
  });

  Drupal.behaviors.buildCalendarBlock = {
    attach(context) {
      once('buildFullcalendarBlock', '.fullcalendar-block', context).forEach(
        (element) => {
          const blockIndex = element.getAttribute('data-calendar-block-index');
          element.setAttribute('data-calendar-block-initialized', 'true');
          const blockSettings =
            Drupal.fullcalendar_block.getSettings(blockIndex);
          if (!blockSettings) {
            console.warn(
              'Could not fetch fullcalendar_block settings',
              blockIndex,
              element,
            );
            return;
          }
          const calendarOptions =
            typeof blockSettings.calendar_options === 'string'
              ? JSON.parse(blockSettings.calendar_options)
              : $.extend(true, {}, blockSettings.calendar_options);

          if (
            typeof calendarOptions.events === 'string' &&
            Drupal.url.isLocal(calendarOptions.events)
          ) {
            // Check if there are any GET parameters to send to the internal
            // Drupal path. This should provide better integration with Views exposed filters.
            let queryString = window.location.search || '';

            if (queryString !== '') {
              // Remove the question mark and any Drupal path component,
              // existing fullcalendar search filters if any.
              const startParam = calendarOptions.startParam || 'start';
              const initialDateParam =
                calendarOptions.initialDateParam || startParam;
              const specialParams = [
                // Internal Drupal paths.
                'q',
                'render',
                // Remove parameters that will be sent by fullcalendar.
                startParam,
                calendarOptions.endParam || 'end',
                calendarOptions.timeZoneParam || 'timeZone',
                // Remove custom fullcalendar block parameters.
                initialDateParam,
              ];

              const query = new URLSearchParams(queryString);

              // Specify the default view mode.
              const initialViewParam =
                calendarOptions.initialViewParam || 'viewMode';
              if (query.get(initialViewParam)) {
                calendarOptions.initialView = query.get(initialViewParam);
              }

              // Specify the default initial date.
              const initialDate = query.get(initialDateParam);
              if (initialDate) {
                const date = new Date(initialDate);
                if (date instanceof Date && !Number.isNaN(date.getTime())) {
                  // Valid date string.
                  calendarOptions.initialDate = date.toISOString();
                }
              }

              // Remove all special parameters.
              specialParams.forEach((specialParam) => {
                query.delete(specialParam);
              });
              queryString = query.toString();

              if (queryString !== '') {
                // If there is a '?' in events URL, & should be used to add
                // parameters.
                queryString =
                  (calendarOptions.events.indexOf('?') === -1 ? '?' : '&') +
                  queryString;
                // Add the current query string to the query.
                // Useful for taking in exposed filters.
                calendarOptions.events += queryString;
              }
            }
          }

          // Bind the event click handler.
          calendarOptions.eventClick = eventClick;

          // Bind the default event data transform handler.
          calendarOptions.eventDataTransform = eventDataTransform.bind(
            element,
            blockIndex,
          );

          // Trigger an event to let other modules know that a calendar
          // will be built.
          $(document).trigger(
            'fullcalendar_block.beforebuild',
            calendarOptions,
          );
          const calendar = new FullCalendar.Calendar(element, calendarOptions);
          calendar.render();
          // Store a reference to the calendar block instance.
          Drupal.fullcalendar_block.instances[blockIndex] = {
            element,
            index: blockIndex,
            calendar,
            calendarOptions,
            blockSettings,
          };
          // Trigger an event to let other modules know when a calendar
          // has been built.
          $(document).trigger('fullcalendar_block.build', [
            Drupal.fullcalendar_block.instances[blockIndex],
          ]);
        },
      );
    },
    detach(context, settings, trigger) {
      if (trigger === 'unload') {
        once
          .remove('buildFullcalendarBlock', '.fullcalendar-block', context)
          .forEach((element) => {
            // Unload the calendar block instance.
            const blockIndex = element.getAttribute(
              'data-calendar-block-index',
            );
            element.removeAttribute('data-calendar-block-initialized');
            if (Drupal.fullcalendar_block.instances[blockIndex]) {
              if (Drupal.fullcalendar_block.instances[blockIndex].calendar) {
                Drupal.fullcalendar_block.instances[
                  blockIndex
                ].calendar.destroy();
              }
              delete Drupal.fullcalendar_block.instances[blockIndex];
            }
          });
      }
    },
  };
})(jQuery, Drupal, once, drupalSettings, window.DOMPurify, window.FullCalendar);

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

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