1(function () { 2 'use strict'; 3 4 (function ($, Drupal, drupalSettings) { 5 // TODO: This does not seem to be used, but keeping for reference. 6 // drupalSettings.mercuryDialog = { 7 // autoOpen: true, 8 // autoResize: false, 9 // dialogClass: '', 10 // buttonClass: 'button', 11 // buttonPrimaryClass: 'button--primary', 12 // }; 13 14 function dispatchDialogEvent(eventType, dialog, element, settings) { 15 // Use the jQuery if the DrupalDialogEvent is not defined for BC. 16 if (typeof DrupalDialogEvent === 'undefined') { 17 $(window).trigger(`dialog:${eventType}`, [dialog, $(element), settings]); 18 } else { 19 const event = new DrupalDialogEvent(eventType, dialog, settings || {}); 20 element.dispatchEvent(event); 21 } 22 } 23 24 Drupal.mercuryDialog = function (element, options) { 25 var undef; 26 var $element = $(element); 27 // Override the jQuery dialog method to return the element, preventing 28 // uncaught errors from core jQuery dialog being thrown. 29 $element.dialog = () => $element; 30 var dialogElement; 31 var dialog = { 32 open: false, 33 returnValue: undef, 34 }; 35 36 /** 37 * ResizeObserver callback that runs when the shadow dialog element resizes. 38 * 39 * @param {ResizeObserverEntry[]} entries 40 */ 41 function onDockResize(entries) { 42 for (const entry of entries) { 43 const element = entry.target; 44 const dockDirection = element.getAttribute('data-dock'); 45 if (!element.open || !dockDirection || dockDirection == 'none') { 46 return; 47 } 48 49 // Dispatch custom event with resize data 50 const resizeEvent = new CustomEvent('mercury:dockResize', { 51 detail: { 52 width: entry.contentRect.width, 53 height: entry.contentRect.height 54 }, 55 bubbles: true 56 }); 57 58 dialogElement.setAttribute('width', entry.contentRect.width); 59 dialogElement.dispatchEvent(resizeEvent); 60 } 61 } 62 63 const dockObserver = new ResizeObserver(onDockResize); 64 65 /** 66 * MutationObserver callback that will start the resize observer when the dialog opens. 67 * 68 * @param {MutationRecord[]} entries 69 */ 70 function onDialogMutate(records) { 71 // @todo: We may only want to attach this if the dialog is transitioning to an open state. 72 dockObserver.observe(dialogElement.shadowRoot.querySelector('dialog')); 73 } 74 75 const dialogMutationObserver = new MutationObserver(onDialogMutate); 76 77 /** 78 * Converts a numeric only value to a px based CSS value. 79 * @param {*} value 80 * @returns {string} 81 * A CSS length value. 82 */ 83 function getCSSLength(value) { 84 if (typeof value === 'undefined' || !/^\d+$/.test(value)) { 85 return value; 86 } 87 return `${value}px`; 88 } 89 90 /** 91 * Apply options to the <mercury-dialog> element. 92 * 93 * @param {Element} dialogElement 94 * @param {Object} options 95 */ 96 function applyOptions(dialogElement, options) { 97 const attributeOptions = [ 98 'title', 99 'modal', 100 'dock', 101 'push', 102 'resizable', 103 'moveable', 104 ]; 105 106 // Set these options as HTML attributes on the element. 107 attributeOptions.forEach(option => { 108 if (typeof options[option] !== 'undefined') { 109 dialogElement.setAttribute(option, options[option]); 110 } 111 }); 112 113 if (options.dialogClass) { 114 dialogElement.classList.add(...options.dialogClass.split(' ')); 115 } 116 117 if (dialogElement.id === 'me-edit-screen') { 118 // Options for the main Mercury Editor tray. 119 let isTrayCollapsed = localStorage.getItem('mercury-dialog-dock-collapsed') === 'true'; 120 let savedWidth = localStorage.getItem('mercury-dialog-dock-width'); 121 let savedHeight = localStorage.getItem('mercury-dialog-dock-height'); 122 123 if (!isTrayCollapsed) { 124 let dialogWidth = savedWidth || options.width; 125 let dialogHeight = savedHeight || options.height; 126 127 if (dialogWidth) { 128 dialogElement.setAttribute('width', dialogWidth); 129 document.documentElement.style.setProperty('--me-dialog-dock-width', getCSSLength(dialogWidth)); 130 } 131 132 if (dialogHeight) { 133 dialogElement.setAttribute('height', dialogHeight); 134 document.documentElement.style.setProperty('--me-dialog-dock-height', getCSSLength(dialogHeight)); 135 } 136 } 137 else { 138 dialogElement.setAttribute('width', '10'); 139 document.documentElement.style.setProperty('--me-dialog-dock-width', '10px'); 140 } 141 } 142 else { 143 // Options for all other dialogs. 144 if (options.width) { 145 dialogElement.setAttribute('width', options.width); 146 document.documentElement.style.setProperty('--me-dialog-width', getCSSLength(options.width)); 147 } 148 149 if (options.height) { 150 dialogElement.setAttribute('height', options.height); 151 document.documentElement.style.setProperty('--me-dialog-height', getCSSLength(options.height)); 152 } 153 } 154 155 // TODO: Determine if we need to persist the width and height of docked dialogs. 156 // if (options.dock) { 157 // if (savedWidth && ['right', 'left'].includes(options.dock)) { 158 // dialogElement.setAttribute('width', savedWidth); 159 // } 160 // if (savedHeight && ['top', 'bottom'].includes(options.dock)) { 161 // dialogElement.setAttribute('height', savedHeight); 162 // } 163 // } 164 165 if (options.drupalAutoButtons && !options.buttons) { 166 options.buttons = Drupal.behaviors.mercuryDialog.prepareDialogButtons($(dialogElement)); 167 } 168 169 if (options.buttons && options.buttons.length) { 170 _createButtonPane(options.buttons); 171 } 172 173 return dialogElement; 174 } 175 176 /** 177 * Determines which element to append the dialog to. Defaults to the <body> 178 * @returns Element 179 */ 180 function _appendTo() { 181 var element = options.appendTo; 182 if (element && (element.jquery || element.nodeType)) { 183 return $(element); 184 } 185 return $(document).find(element || "body").eq(0); 186 } 187 /** 188 * Create a button pane similar to jQuery UI dialog. 189 */ 190 function _createButtonPane(buttons) { 191 const existing = dialogElement.querySelector('.me-dialog__buttonpane'); 192 if (existing) { 193 existing.remove(); 194 } 195 const uiDialogButtonPane = document.createElement('div'); 196 uiDialogButtonPane.setAttribute('slot', 'footer'); 197 uiDialogButtonPane.classList.add('me-dialog__buttonpane'); 198 dialogElement.appendChild(uiDialogButtonPane); 199 if ($.isEmptyObject(buttons) || (Array.isArray(buttons) && !buttons.length)) { 200 return; 201 } 202 buttons.forEach((props) => { 203 const button = document.createElement('button'); 204 button.classList = props.class; 205 button.classList.add('button'); 206 button.appendChild(document.createTextNode(props.text)); 207 button.addEventListener('click', props.click); 208 uiDialogButtonPane.appendChild(button); 209 }); 210 } 211 function init(settings) { 212 // Wrap the element in a <mercury-dialog> if it isn't already. 213 if (element.tagName !== 'MERCURY-DIALOG') { 214 const wrapper = $('<mercury-dialog>').append($element).appendTo(_appendTo()); 215 dialogElement = wrapper[0]; 216 } else { 217 dialogElement = element; 218 } 219 applyOptions(dialogElement, settings); 220 } 221 222 /** 223 * Initializes and opens a dialog element. 224 * @param {Object} settings 225 * Dialog settings mimicing jQuery UI for compatability. 226 */
227 function openDialog(settings) { 228 settings = { ...drupalSettings.dialog, ...drupalSettings.mercuryEditor, ...options, ...settings }; 229 dispatchDialogEvent('beforecreate', dialog, $element.get(0), settings); 230 init(settings); 231 dialogElement[settings.modal ? 'showModal' : 'show'](); 232 // Set autoResize to false to prevent Drupal core's jQuery dialog from 233 // attemping to resize, which would throw an error. 234 const originalResizeSetting = settings.autoResize; 235 settings.autoResize = false; 236 dispatchDialogEvent('aftercreate', dialog, $element.get(0), settings); 237 settings.autoResize = originalResizeSetting; 238 // Add a mutation observer to the dialog element. 239 dialogMutationObserver.observe(dialogElement, { childList: true, attributes: true }); 240 dialogElement.addEventListener('close', function () { 241 closeDialog(); 242 }); 243 } 244 245 function closeDialog(value) { 246 dispatchDialogEvent('beforeclose', dialog, $element.get(0)); 247 // Stop observing height and width changes. 248 dockObserver.disconnect(); 249 dialogMutationObserver.disconnect(); 250 Drupal.detachBehaviors(element, null, 'unload'); 251 element.close(); 252 dialog.returnValue = value; 253 dispatchDialogEvent('afterclose', dialog, $element.get(0)); 254 $element.remove(); 255 } 256 257 dialog.show = function () { 258 openDialog({ 259 modal: false, 260 }); 261 }; 262 263 dialog.showModal = function () { 264 openDialog({ 265 modal: true, 266 }); 267 }; 268 269 dialog.applyOptions = function (options) { 270 init(options); 271 }; 272 273 dialog.close = closeDialog; 274 return dialog; 275 }; 276 277 Drupal.behaviors.mercuryDialog = { 278 attach: function (context, settings) { 279 // Provide a known 'drupal-mercury-dialog' DOM element for Drupal-based modal 280 // dialogs. Non-modal dialogs are responsible for creating their own 281 // elements, since there can be multiple non-modal dialogs at a time. 282 283 if (!$('#drupal-mercury-dialog').length) { 284 // Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete 285 // sit on top of dialogs. For more information see 286 // http://api.jqueryui.com/theming/stacking-elements/. 287 // @todo: .ui-front just sets a z-index of 100 which is not high enough 288 // to overlay Gin's Toolbar. 289 $('<mercury-dialog id="drupal-mercury-dialog"></mercury-dialog>') 290 .appendTo('body'); 291 } 292 293 // Special behaviors specific when attaching content within a dialog. 294 // These behaviors usually fire after a validation error inside a dialog. 295 const $dialog = $(context).closest('mercury-dialog'); 296 if ($dialog.length) { 297 $dialog.trigger('dialogButtonsChange'); 298 } 299 300 }, 301 302 prepareDialogButtons: function prepareDialogButtons($dialog) { 303 var buttons = []; 304 var $buttons = $dialog.find('.form-actions').last().find('input[type=submit], a.button, a.action-link'); 305 $buttons.each(function () { 306 var $originalButton = $(this).css({ 307 display: 'none' 308 }); 309 buttons.push({ 310 text: $originalButton.html() || $originalButton.attr('value'), 311 class: $originalButton.attr('class'), 312 click: function click(e) { 313 if ($originalButton.is('a')) { 314 $originalButton[0].click(); 315 } else { 316 $originalButton.trigger('mousedown').trigger('mouseup').trigger('click'); 317 e.preventDefault(); 318 } 319 } 320 }); 321 }); 322 return buttons; 323 } 324 }; 325 326 // Moves Layout Paragraphs form buttons into the dialog button pane. 327 function moveFormButtonsToDialog(event, dialog, $dialog) { 328 if ($dialog[0].tagName !== 'MERCURY-DIALOG') { 329 return; 330 } 331 if ($dialog.attr('id').indexOf('lpb-dialog-') === 0) { 332 const buttons = Drupal.behaviors.mercuryDialog.prepareDialogButtons($dialog); 333 if (buttons.length) { 334 Drupal.mercuryDialog($dialog[0]).applyOptions({ buttons: buttons }); 335 } 336 } 337 } 338 $(window).on('dialog:aftercreate', moveFormButtonsToDialog); 339 340 /** 341 * ResizeObserver callback that resizes the parent iframe based on 342 * the height of the child document html element. 343 * 344 * @param {ResizeObserverEntry[]} entries 345 */ 346 function onBodyResize(iframe) { 347 return (entries) => { 348 // Resize the parent iframe based on the html's border box height. 349 if (iframe && entries.length) { 350 iframe.style.height = `${entries[0].borderBoxSize[0].blockSize + 1}px`; 351 iframe.style.width = `${entries[0].borderBoxSize[0].inlineSize + 1}px`; 352 } 353 } 354 } 355 356 /** 357 * Set a max-width on the iframe's <body> to match the dialog's 358 * max-width to prevent horizontal scrolling. 359 * 360 * @param {HTMLIFrameElement} iframe 361 * The iframe element within a mercury dialog. 362 * @param {HTMLBodyElement} framedBody 363 * The body element within the iframe. 364 */ 365 function setFrameBodyMaxWidth(iframe, framedBody) { 366 const dialogStyles = window.getComputedStyle(iframe.closest('mercur
366y-dialog').shadowRoot.querySelector('dialog')); 367 const dialogMainStyles = window.getComputedStyle(iframe.closest('mercury-dialog').shadowRoot.querySelector('main')); 368 framedBody.style.maxWidth = `calc(${dialogStyles.getPropertyValue('max-width')} - ${dialogMainStyles.getPropertyValue('padding-left')} - ${dialogMainStyles.getPropertyValue('padding-right')} - 2px)`; 369 } 370 371 /** 372 * Resizes an iFrame to match the height of its inner <body> element. 373 * @param {HTMLIFrameElement} iframe 374 */ 375 function resizeIframe(iframe) { 376 const framedBody = iframe.contentWindow.document.body; 377 framedBody.style.width = 'max-content'; 378 framedBody.style.height = 'fit-content'; 379 380 setFrameBodyMaxWidth(iframe, framedBody); 381 382 // Observe changes to the iframe's inner <body> dimensions. 383 new ResizeObserver(onBodyResize(iframe)).observe(framedBody, { box: 'border-box' }); 384 } 385 386 function updateIframeSize(event, dialog, $dialog) { 387 if ($dialog[0].tagName !== 'MERCURY-DIALOG') { 388 return; 389 } 390 391 const iframe = $dialog[0].querySelector('iframe'); 392 if (!iframe) { 393 return; 394 } 395 396 $dialog[0].style.setProperty('--me-dialog-height-default', 'fit-content'); 397 398 iframe.onload = function () { 399 resizeIframe(iframe); 400 setFrameBodyMaxWidth(iframe, framedBody); 401 }; 402 403 const framedBody = iframe?.contentWindow?.document?.body; 404 if (framedBody) { 405 window.addEventListener('resize', function () { 406 setFrameBodyMaxWidth(iframe, framedBody); 407 }); 408 } 409 } 410 $(window).on('dialog:aftercreate', updateIframeSize); 411 412 // Store open modals. 413 const modalStack = []; 414 415 // The following event handlers are used to manage the modal stack. 416 // Since native dialog['modal'] elements live in the browser's top-level, 417 // we need to make sure any jQuery ui modals that are opended from within 418 // a mercury-dialog element get nested within the top-level modal. 419 // Otherwise the jQuery dialog will be obscured by the mercury-dialog modal. 420 // See https://developer.chrome.com/blog/what-is-the-top-layer/ 421 function addModalToStack(event, dialog, $dialog) { 422 if ($dialog[0].tagName !== 'MERCURY-DIALOG') { 423 return; 424 } 425 if ($dialog[0].hasAttribute('modal') && $dialog[0].getAttribute('modal') !== 'false') { 426 modalStack.push($dialog); 427 } 428 } 429 $(window).on('dialog:aftercreate', addModalToStack); 430 431 function removeModalFromStack(event, dialog, $dialog) { 432 if ($dialog[0].tagName !== 'MERCURY-DIALOG') { 433 return; 434 } 435 if ($dialog[0].hasAttribute('modal') && $dialog[0].getAttribute('modal') !== 'false') { 436 const index = modalStack.indexOf($dialog); 437 if (index > -1) { 438 modalStack.splice(index, 1); 439 } 440 } 441 } 442 $(window).on('dialog:beforeclose', removeModalFromStack); 443 444 function nestDialogInModal(event, dialog, $dialog) { 445 if ($dialog[0].tagName !== 'MERCURY-DIALOG') { 446 return; 447 } 448 if (modalStack.length > 0) { 449 const $parent = $dialog.parent('.ui-dialog'); 450 const $overlay = $parent.next('.ui-widget-overlay'); 451 modalStack.slice(-1)[0].append([$parent, $overlay]); 452 } 453 } 454 $(window).on('dialog:aftercreate', nestDialogInModal); 455 456 })(jQuery, Drupal, drupalSettings); 457 458})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.