summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/settings_tray/js/settings_tray.js
blob: 94cfa9d49157a787d377e3036353fe66db8e8d12 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/**
 * @file
 * Drupal's Settings Tray library.
 *
 * @private
 */

(($, Drupal) => {
  const blockConfigureSelector = '[data-settings-tray-edit]';
  const toggleEditSelector = '[data-drupal-settingstray="toggle"]';
  const itemsToToggleSelector =
    '[data-off-canvas-main-canvas], #toolbar-bar, [data-drupal-settingstray="editable"] a, [data-drupal-settingstray="editable"] button';
  const contextualItemsSelector =
    '[data-contextual-id] a, [data-contextual-id] button';

  /**
   * 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();
  }

  /**
   * Close any active toolbar tray before entering edit mode.
   */
  function closeToolbarTrays() {
    $(Drupal.toolbar.models.toolbarModel.get('activeTab')).trigger('click');
  }

  /**
   * Closes/removes off-canvas.
   */
  function closeOffCanvas() {
    $('.ui-dialog-off-canvas .ui-dialog-titlebar-close').trigger('click');
  }

  /**
   * 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 switch edit mode state.
   *
   * @param {boolean} editMode
   *   True enable edit mode, false disable edit mode.
   */
  function setEditModeState(editMode) {
    if (!document.querySelector('[data-off-canvas-main-canvas]')) {
      throw new Error(
        'data-off-canvas-main-canvas is missing from settings-tray-page-wrapper.html.twig',
      );
    }
    editMode = !!editMode;
    let $editables;
    const editButton = document.querySelector(toggleEditSelector);
    // Turn on edit mode.
    if (editMode) {
      if (editButton) {
        editButton.textContent = Drupal.t('Editing');
      }
      closeToolbarTrays();

      $editables = $(
        once('settingstray', '[data-drupal-settingstray="editable"]'),
      );
      if ($editables.length) {
        // Use event capture to prevent clicks on links.
        document
          .querySelector('[data-off-canvas-main-canvas]')
          .addEventListener('click', preventClick, true);
        /**
         * When a click occurs try and find the settings-tray edit link
         * and click it.
         */
        $editables
          .not(contextualItemsSelector)
          .on('click.settingstray', (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 = $(
        once.remove('settingstray', '[data-drupal-settingstray="editable"]'),
      );
      if ($editables.length) {
        document
          .querySelector('[data-off-canvas-main-canvas]')
          .removeEventListener('click', preventClick, true);
        $editables.off('.settingstray');
      }
      if (editButton) {
        editButton.textContent = Drupal.t('Edit');
      }
      closeOffCanvas();
    }
    getItemsToToggle().toggleClass('js-settings-tray-edit-mode', editMode);
    $('.edit-mode-inactive').toggleClass('visually-hidden', editMode);
  }

  /**
   * Helper to check the state of the settings-tray mode.
   *
   * @todo don't use a class for this.
   *
   * @return {boolean}
   *   State of the settings-tray edit mode.
   */
  function isInEditMode() {
    return $('#toolbar-bar').hasClass('js-settings-tray-edit-mode');
  }

  /**
   * Helper to toggle Edit mode.
   */
  function toggleEditMode() {
    setEditModeState(!isInEditMode());
  }

  /**
   * Prepares Ajax links to work with off-canvas and Settings Tray module.
   */
  function prepareAjaxLinks() {
    // Find all Ajax instances that use the 'off_canvas' renderer.
    Drupal.ajax.instances
      /**
       * If there is an element and the renderer is 'off_canvas' then we want
       * to add our changes.
       */
      .filter(
        (instance) =>
          instance &&
          $(instance.element).attr('data-dialog-renderer') === 'off_canvas',
      )
      /**
       * Loop through all Ajax instances that use the 'off_canvas' renderer to
       * set active editable ID.
       */
      .forEach((instance) => {
        const closestSettingsTray = instance.element.closest(
          '.settings-tray-editable',
        );
        // Check to make sure existing dialogOptions aren't overridden.
        if (!instance.options.data.hasOwnProperty('dialogOptions')) {
          instance.options.data.dialogOptions = {};
        }
        instance.options.data.dialogOptions.settingsTrayActiveEditableId =
          closestSettingsTray?.id;
        instance.progress = { type: 'fullscreen' };
      });
  }

  /**
   * 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', (event, data) => {
    /**
     * When contextual links are add we need to set extra properties on the
     * instances in Drupal.ajax.instances for them to work with Edit Mode.
     */
    prepareAjaxLinks();

    // When the first contextual link is added to the page set Edit Mode.
    once('settings_tray.edit_mode_init', 'body').forEach(() => {
      const editMode =
        localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
      if (editMode) {
        setEditModeState(true);
      }
    });

    /**
     * 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.settingstray', () => {
      if (!isInEditMode()) {
        $(toggleEditSelector).trigger('click').trigger('click.settings_tray');
      }
    });
  });

  $(document).on('keyup.settingstray', (e) => {
    if (isInEditMode() && e.keyCode === 27) {
      Drupal.announce(Drupal.t('Exited edit mode.'));
      toggleEditMode();
    }
  });

  /**
   * Toggle the js-settings-tray-edit-mode class on items that we want to
   * disable while in edit mode.
   *
   * @type {Drupal~behavior}
   *
   * @prop {Drupal~behaviorAttach} attach
   *   Toggle the js-settings-tray-edit-mode class.
   */
  Drupal.behaviors.toggleEditMode = {
    attach() {
      $(once('settingstray', toggleEditSelector)).on(
        'click.settingstray',
        toggleEditMode,
      );
    },
  };

  // Manage Active editable class on opening and closing of the dialog.
  window.addEventListener('dialog:beforecreate', (e) => {
    if (e.target.id === 'drupal-off-canvas') {
      $('body .settings-tray-active-editable').removeClass(
        'settings-tray-active-editable',
      );
      const $activeElement = $(`#${e.settings.settingsTrayActiveEditableId}`);
      if ($activeElement.length) {
        $activeElement.addClass('settings-tray-active-editable');
      }
    }
  });
  window.addEventListener('dialog:beforeclose', (e) => {
    if (e.target.id === 'drupal-off-canvas') {
      $('body .settings-tray-active-editable').removeClass(
        'settings-tray-active-editable',
      );
    }
  });
})(jQuery, Drupal);