diff --git a/bootstrap.info.yml b/bootstrap.info.yml index 153d89f..b2e6695 100644 --- a/bootstrap.info.yml +++ b/bootstrap.info.yml @@ -35,6 +35,8 @@ libraries-extend: - bootstrap/drupal.states core/drupal.tabledrag: - bootstrap/drupal.tabledrag + outside_in/drupal.outside_in: + - bootstrap/drupal.outside_in views/views.ajax: - bootstrap/views.ajax diff --git a/bootstrap.libraries.yml b/bootstrap.libraries.yml index aed147f..7337970 100644 --- a/bootstrap.libraries.yml +++ b/bootstrap.libraries.yml @@ -91,6 +91,13 @@ views.ajax: js: js/modules/views/ajax_view.js: {} +# Create a library placeholder for outside_in module fix. +# This is altered dynamically based on whether modals are enabled +# @see \Drupal\bootstrap\Plugin\Alter\LibraryInfo::alter +drupal.outside_in: + js: + js/modules/outside_in/outside_in.js: {} + # libraries-override drupal.batch: js: diff --git a/js/modules/outside_in/outside_in.js b/js/modules/outside_in/outside_in.js new file mode 100644 index 0000000..d034baa --- /dev/null +++ b/js/modules/outside_in/outside_in.js @@ -0,0 +1,170 @@ +/** + * @file + * Drupal's Settings Tray library. + */ + +// We swap the Drupal.behaviors.toggleEditMode behavior because we do not want +// the dialog to render in offcanvas when Bootstrap modals are enabled (we +// rather prefer Bootstrap dialog over the offcanvas ones). +// The code below is heavily copy-pasted from +// ./core/modules/outside_in/js/outside_in.js but that's the best we can do. +(function ($, Drupal) { + + 'use strict'; + + var blockConfigureSelector = '[data-dialog-renderer="offcanvas"]'; + var toggleEditSelector = '[data-drupal-outsidein="toggle"]'; + var itemsToToggleSelector = '#main-canvas, #toolbar-bar, [data-drupal-outsidein="editable"] a, [data-drupal-outsidein="editable"] button'; + var contextualItemsSelector = '[data-contextual-id] a, [data-contextual-id] button'; + + /** + * Reacts to contextual links being added. + * + * @param {jQuery.Event} event + * The `drupalContextualLinkAdded` event. + * @param {object} data + * An object containing the data relevant to the event. + * + * @listens event:drupalContextualLinkAdded + */ + $(document).on('drupalContextualLinkAdded', function (event, data) { + // Bind Ajax behaviors to all items showing the class. + // @todo Fix contextual links to work with use-ajax links in + // https://www.drupal.org/node/2764931. + Drupal.attachBehaviors(data.$el[0]); + + // Bind a listener to all 'Quick edit' links for blocks + // Click "Edit" button in toolbar to force Contextual Edit which starts + // Settings Tray edit mode also. + data.$el.find(blockConfigureSelector) + .on('click.outsidein', function () { + if (!isInEditMode()) { + $(toggleEditSelector).trigger('click.outsidein'); + } + }); + }); + + /** + * Gets all items that should be toggled with class during edit mode. + * + * @return {jQuery} + * Items that should be toggled. + */ + function getItemsToToggle() { + return $(itemsToToggleSelector).not(contextualItemsSelector); + } + + /** + * Helper to check the state of the outside-in mode. + * + * @todo don't use a class for this. + * + * @return {boolean} + * State of the outside-in edit mode. + */ + function isInEditMode() { + return $('#toolbar-bar').hasClass('js-outside-in-edit-mode'); + } + + /** + * Helper to toggle Edit mode. + */ + function toggleEditMode() { + setEditModeState(!isInEditMode()); + } + + /** + * Prevent default click events except contextual links. + * + * In edit mode the default action of click events is suppressed. + * + * @param {jQuery.Event} event + * The click event. + */ + function preventClick(event) { + // Do not prevent contextual links. + if ($(event.target).closest('.contextual-links').length) { + return; + } + event.preventDefault(); + } + + /** + * Helper to switch edit mode state. + * + * @param {boolean} editMode + * True enable edit mode, false disable edit mode. + */ + function setEditModeState(editMode) { + editMode = !!editMode; + var $editButton = $(toggleEditSelector); + var $editables; + // Turn on edit mode. + if (editMode) { + $editButton.text(Drupal.t('Editing')); + // Close the Manage tray if open when entering edit mode. + if ($('#toolbar-item-administration-tray').hasClass('is-active')) { + $('#toolbar-item-administration').trigger('click'); + } + + $editables = $('[data-drupal-outsidein="editable"]').once('outsidein'); + if ($editables.length) { + // Use event capture to prevent clicks on links. + document.querySelector('#main-canvas').addEventListener('click', preventClick, true); + + // When a click occurs try and find the outside-in edit link + // and click it. + $editables + .not(contextualItemsSelector) + .on('click.outsidein', function (e) { + // Contextual links are allowed to function in Edit mode. + if ($(e.target).closest('.contextual').length || !localStorage.getItem('Drupal.contextualToolbar.isViewing')) { + return; + } + + $(e.currentTarget).find(blockConfigureSelector).trigger('click'); + }); + } + } + // Disable edit mode. + else { + $editables = $('[data-drupal-outsidein="editable"]').removeOnce('outsidein'); + if ($editables.length) { + document.querySelector('#main-canvas').removeEventListener('click', preventClick, true); + $editables.off('.outsidein'); + } + + $editButton.text(Drupal.t('Edit')); + // Close/remove offcanvas. + $('.ui-dialog-offcanvas .ui-dialog-titlebar-close').trigger('click'); + } + getItemsToToggle().toggleClass('js-outside-in-edit-mode', editMode); + $('.edit-mode-inactive').toggleClass('visually-hidden', editMode); + } + + Drupal.behaviors.toggleEditMode.attach = function () { + + $(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode); + + var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog'; + var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_offcanvas'; + // Loop through all Ajax links and change the format to offcanvas when + // needed. + Drupal.ajax.instances + .filter(function (instance) { + var hasElement = instance && !!instance.element; + var rendererOffcanvas = false; + var wrapperOffcanvas = false; + if (hasElement) { + rendererOffcanvas = $(instance.element).attr('data-dialog-renderer') === 'offcanvas'; + wrapperOffcanvas = instance.options.url.indexOf('drupal_dialog_offcanvas') === -1; + } + return hasElement && rendererOffcanvas && wrapperOffcanvas; + }) + .forEach(function (instance) { + instance.options.data.dialogOptions = {outsideInActiveEditableId: $(instance.element).parents('.outside-in-editable').attr('id')}; + instance.progress = {type: 'fullscreen'}; + }); + } + +})(jQuery, Drupal); diff --git a/src/Plugin/Alter/LibraryInfo.php b/src/Plugin/Alter/LibraryInfo.php index d91f539..ecb662b 100644 --- a/src/Plugin/Alter/LibraryInfo.php +++ b/src/Plugin/Alter/LibraryInfo.php @@ -77,6 +77,10 @@ class LibraryInfo extends PluginBase implements AlterInterface { break; } } + + if (!$this->theme->getSetting('modal_enabled')) { + unset($libraries['drupal.outside_in']['js']['js/modules/outside_in/outside_in.js']); + } } // Core replacements. elseif ($extension === 'core') {