1/** 2 * @file 3 * Defines the Drupal JavaScript API. 4 */ 5 6/** 7 * A jQuery object, typically the return value from a `$(selector)` call. 8 * 9 * Holds an HTMLElement or a collection of HTMLElements. 10 * 11 * @typedef {object} jQuery 12 * 13 * @prop {number} length=0 14 * Number of elements contained in the jQuery object. 15 */ 16 17/** 18 * Variable generated by Drupal that holds all translated strings from PHP. 19 * 20 * Content of this variable is automatically created by Drupal when using the 21 * Interface Translation module. It holds the translation of strings used on 22 * the page. 23 * 24 * This variable is used to pass data from the backend to the frontend. Data 25 * contained in `drupalSettings` is used during behavior initialization. 26 * 27 * @global 28 * 29 * @var {object} drupalTranslations 30 */ 31 32/** 33 * Global Drupal object. 34 * 35 * All Drupal JavaScript APIs are contained in this namespace. 36 * 37 * @global 38 * 39 * @namespace 40 */ 41window.Drupal = { behaviors: {}, locale: {} }; 42 43// JavaScript should be made compatible with libraries other than jQuery by 44// wrapping it in an anonymous closure. 45(function ( 46 Drupal, 47 drupalSettings, 48 drupalTranslations,
49 console, 50 Proxy, 51 Reflect, 52) { 53 /** 54 * Helper to rethrow errors asynchronously. 55 * 56 * This way Errors bubbles up outside of the original callstack, making it 57 * easier to debug errors in the browser. 58 * 59 * @param {Error|string} error 60 * The error to be thrown. 61 */ 62 Drupal.throwError = function (error) { 63 setTimeout(() => { 64 throw error; 65 }, 0); 66 }; 67 68 /** 69 * Custom error thrown after attach/detach if one or more behaviors failed. 70 * Initializes the JavaScript behaviors for page loads and Ajax requests. 71 * 72 * @callback Drupal~behaviorAttach 73 * 74 * @param {Document|HTMLElement} context 75 * An element to detach behaviors from. 76 * @param {?object} settings 77 * An object containing settings for the current context. It is rarely used. 78 * 79 * @see Drupal.attachBehaviors 80 */ 81 82 /** 83 * Reverts and cleans up JavaScript behavior initialization. 84 * 85 * @callback Drupal~behaviorDetach 86 * 87 * @param {Document|HTMLElement} context 88 * An element to attach behaviors to. 89 * @param {object} settings 90 * An object containing settings for the current context. 91 * @param {string} trigger 92 * One of `'unload'`, `'move'`, or `'serialize'`. 93 * 94 * @see Drupal.detachBehaviors 95 */ 96 97 /** 98 * @typedef {object} Drupal~behavior 99 * 100 * @prop {Drupal~behaviorAttach} attach 101 * Function run on page load and after an Ajax call. 102 * @prop {Drupal~behaviorDetach} [detach] 103 * Function run when content is serialized or removed from the page. 104 */ 105 106 /** 107 * Holds all initialization methods. 108 * 109 * @namespace Drupal.behaviors 110 * 111 * @type {Object.<string, Drupal~behavior>} 112 */ 113 114 /** 115 * Defines a behavior to be run during attach and detach phases. 116 * 117 * Attaches all registered behaviors to a page element. 118 * 119 * Behaviors are event-triggered actions that attach to page elements, 120 * enhancing default non-JavaScript UIs. Behaviors are registered in the 121 * {@link Drupal.behaviors} object using the method 'attach' and optionally 122 * also 'detach'. 123 * 124 * {@link Drupal.attachBehaviors} is added below to the `jQuery.ready` event 125 * and therefore runs on initial page load. Developers implementing Ajax in 126 * their solutions should also call this function after new page content has 127 * been loaded, feeding in an element to be processed, in order to attach all 128 * behaviors to the new content. 129 * 130 * Behaviors should use `var elements = 131 * once('behavior-name', selector, context);` to ensure the behavior is 132 * attached only once to a given element. (Doing so enables the reprocessing 133 * of given elements, which may be needed on occasion despite the ability to 134 * limit behavior attachment to a particular element.) 135 * 136 * @example 137 * Drupal.behaviors.behaviorName = { 138 * attach: function (context, settings) { 139 * // ... 140 * }, 141 * detach: function (context, settings, trigger) { 142 * // ... 143 * } 144 * }; 145 * 146 * @param {Document|HTMLElement} [context=document] 147 * An element to attach behaviors to. 148 * @param {object} [settings=drupalSettings] 149 * An object containing settings for the current context. If none is given, 150 * the global {@link drupalSettings} object is used. 151 * 152 * @see Drupal~behaviorAttach 153 * @see Drupal.detachBehaviors 154 * 155 * @throws {Drupal~DrupalBehaviorError} 156 */ 157 Drupal.attachBehaviors = function (context, settings) { 158 context = context || document; 159 settings = settings || drupalSettings; 160 const behaviors = Drupal.behaviors; 161 // Execute all of them. 162 Object.keys(behaviors || {}).forEach((i) => { 163 if (typeof behaviors[i].attach === 'function') { 164 // Don't stop the execution of behaviors in case of an error. 165 try { 166 behaviors[i].attach(context, settings); 167 } catch (e) { 168 Drupal.throwError(e); 169 } 170 } 171 }); 172 }; 173 174 /** 175 * Detaches registered behaviors from a page element. 176 * 177 * Developers implementing Ajax in their solutions should call this function 178 * before page content is about to be removed, feeding in an element to be 179 * processed, in order to allow special behaviors to detach from the content. 180 * 181 * Such implementations should use `once.filter()` and `once.remove()` to find 182 * elements with their corresponding `Drupal.behaviors.behaviorName.attach` 183 * implementation, i.e. `once.remove('behaviorName', selector, context)`, 184 * to ensure the behavior is detached only from previously processed elements. 185 * 186 * @param {Document|HTMLElement} [context=document] 187 * An element to detach behaviors from. 188 * @param {object} [settings=drupalSettings]
189 * An object containing settings for the current context. If none given, 190 * the global {@link drupalSettings} object is used. 191 * @param {string} [trigger='unload'] 192 * A string containing what's causing the behaviors to be detached. The 193 * possible triggers are: 194 * - `'unload'`: The context element is being removed from the DOM. 195 * - `'move'`: The element is about to be moved within the DOM (for example, 196 * during a tabledrag row swap). After the move is completed, 197 * {@link Drupal.attachBehaviors} is called, so that the behavior can undo 198 * whatever it did in response to the move. Many behaviors won't need to 199 * do anything simply in response to the element being moved, but because 200 * IFRAME elements reload their "src" when being moved within the DOM, 201 * behaviors bound to IFRAME elements (like WYSIWYG editors) may need to 202 * take some action. 203 * - `'serialize'`: When an Ajax form is submitted, this is called with the 204 * form as the context. This provides every behavior within the form an 205 * opportunity to ensure that the field elements have correct content 206 * in them before the form is serialized. The canonical use-case is so 207 * that WYSIWYG editors can update the hidden textarea to which they are 208 * bound. 209 * 210 * @throws {Drupal~DrupalBehaviorError} 211 * 212 * @see Drupal~behaviorDetach 213 * @see Drupal.attachBehaviors 214 */ 215 Drupal.detachBehaviors = function (context, settings, trigger) { 216 context = context || document; 217 settings = settings || drupalSettings; 218 trigger = trigger || 'unload'; 219 const behaviors = Drupal.behaviors; 220 // Execute all of them. 221 Object.keys(behaviors || {}).forEach((i) => { 222 if (typeof behaviors[i].detach === 'function') { 223 // Don't stop the execution of behaviors in case of an error. 224 try { 225 behaviors[i].detach(context, settings, trigger); 226 } catch (e) { 227 Drupal.throwError(e); 228 } 229 } 230 }); 231 }; 232 233 /** 234 * Encodes special characters in a plain-text string for display as HTML. 235 * 236 * @param {string} str 237 * The string to be encoded. 238 * 239 * @return {string} 240 * The encoded string. 241 * 242 * @ingroup sanitization 243 */ 244 Drupal.checkPlain = function (str) { 245 str = str 246 .toString() 247 .replace(/&/g, '&') 248 .replace(/</g, '<') 249 .replace(/>/g, '>') 250 .replace(/"/g, '"') 251 .replace(/'/g, '''); 252 return str; 253 }; 254 255 /** 256 * Replaces placeholders with sanitized values in a string. 257 * 258 * @param {string} str 259 * A string with placeholders. 260 * @param {object} args 261 * An object of replacements pairs to make. Incidences of any key in this 262 * array are replaced with the corresponding value. Based on the first 263 * character of the key, the value is escaped and/or themed: 264 * - `'!variable'`: inserted as is. 265 * - `'@variable'`: escape plain text to HTML ({@link Drupal.checkPlain}). 266 * - `'%variable'`: escape text and theme as a placeholder for user- 267 * submitted content ({@link Drupal.checkPlain} + 268 * `{@link Drupal.theme}('placeholder')`). 269 * 270 * @return {string} 271 * The formatted string. 272 * 273 * @see Drupal.t 274 */ 275 Drupal.formatString = function (str, args) { 276 // Keep args intact. 277 const processedArgs = {}; 278 // Transform arguments before inserting them. 279 Object.keys(args || {}).forEach((key) => { 280 switch (key.charAt(0)) { 281 // Escaped only. 282 case '@': 283 processedArgs[key] = Drupal.checkPlain(args[key]); 284 break; 285 286 // Pass-through. 287 case '!': 288 processedArgs[key] = args[key]; 289 break; 290 291 // Escaped and placeholder. 292 default: 293 processedArgs[key] = Drupal.theme('placeholder', args[key]); 294 break; 295 } 296 }); 297 298 return Drupal.stringReplace(str, processedArgs, null); 299 }; 300 301 /** 302 * Replaces substring. 303 * 304 * The longest keys will be tried first. Once a substring has been replaced, 305 * its new value will not be searched again. 306 * 307 * @param {string} str 308 * A string with placeholders. 309 * @param {object} args 310 * Key-value pairs. 311 * @param {Array|null} keys 312 * Array of keys from `args`. Internal use only. 313 * 314 * @return {string} 315 * The replaced string. 316 */ 317 Drupal.stringReplace = function (str, args, keys) { 318 if (str.length === 0) { 319 return str; 320 } 321 322 // If the array of keys is not passed then collect the keys from the args. 323 if (!Array.isArray(keys)) { 324 keys = Object.keys(args || {}); 325 326 // Order the keys by the character length. The shortest one is the first. 327 keys.sort((a, b) => a.length - b.length); 328 } 329 330 if (keys.length === 0) { 331 return str; 332 } 333 334 // Take next longest one from the end.
335 const key = keys.pop(); 336 const fragments = str.split(key); 337 338 if (keys.length) { 339 for (let i = 0; i < fragments.length; i++) { 340 // Process each fragment with a copy of remaining keys. 341 fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0)); 342 } 343 } 344 345 return fragments.join(args[key]); 346 }; 347 348 /** 349 * Translates strings to the page language, or a given language. 350 * 351 * See the documentation of the server-side t() function for further details. 352 * 353 * @param {string} str 354 * A string containing the English text to translate. 355 * @param {Object.<string, string>} [args] 356 * An object of replacements pairs to make after translation. Incidences 357 * of any key in this array are replaced with the corresponding value. 358 * See {@link Drupal.formatString}. 359 * @param {object} [options] 360 * Additional options for translation. 361 * @param {string} [options.context=''] 362 * The context the source string belongs to. 363 * 364 * @return {string} 365 * The formatted string. 366 * The translated string. 367 */ 368 Drupal.t = function (str, args, options) { 369 options = options || {}; 370 options.context = options.context || ''; 371 372 // Fetch the localized version of the string. 373 if ( 374 typeof drupalTranslations !== 'undefined' && 375 drupalTranslations.strings && 376 drupalTranslations.strings[options.context] && 377 drupalTranslations.strings[options.context][str] 378 ) { 379 str = drupalTranslations.strings[options.context][str]; 380 } 381 382 if (args) { 383 str = Drupal.formatString(str, args); 384 } 385 return str; 386 }; 387 388 /** 389 * Returns the URL to a Drupal page. 390 * 391 * @param {string} path 392 * Drupal path to transform to URL. 393 * 394 * @return {string} 395 * The full URL. 396 */ 397 Drupal.url = function (path) { 398 return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path; 399 }; 400 401 /** 402 * Returns the passed in URL as an absolute URL. 403 * 404 * @param {string} url 405 * The URL string to be normalized to an absolute URL. 406 * 407 * @return {string} 408 * The normalized, absolute URL. 409 * 410 * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js 411 * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript 412 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53 413 */ 414 Drupal.url.toAbsolute = function (url) { 415 const urlParsingNode = document.createElement('a'); 416 417 // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8 418 // strings may throw an exception. 419 try { 420 url = decodeURIComponent(url); 421 } catch (e) { 422 // Empty. 423 } 424 425 urlParsingNode.setAttribute('href', url); 426 427 // IE <= 7 normalizes the URL when assigned to the anchor node similar to 428 // the other browsers. 429 return urlParsingNode.cloneNode(false).href; 430 }; 431 432 /** 433 * Returns true if the URL is within Drupal's base path. 434 * 435 * @param {string} url 436 * The URL string to be tested. 437 * 438 * @return {boolean} 439 * `true` if local. 440 * 441 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58 442 */ 443 Drupal.url.isLocal = function (url) { 444 // Always use browser-derived absolute URLs in the comparison, to avoid 445 // attempts to break out of the base path using directory traversal. 446 let absoluteUrl = Drupal.url.toAbsolute(url); 447 let { protocol } = window.location; 448 449 // Consider URLs that match this site's base URL but use HTTPS instead of HTTP 450 // as local as well. 451 if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) { 452 protocol = 'https:'; 453 } 454 let baseUrl = `${protocol}//${ 455 window.location.host 456 }${drupalSettings.path.baseUrl.slice(0, -1)}`; 457 458 // Decoding non-UTF-8 strings may throw an exception. 459 try { 460 absoluteUrl = decodeURIComponent(absoluteUrl); 461 } catch (e) { 462 // Empty. 463 } 464 try { 465 baseUrl = decodeURIComponent(baseUrl); 466 } catch (e) { 467 // Empty. 468 } 469 470 // The given URL matches the site's base URL, or has a path under the site's 471 // base URL. 472 return absoluteUrl === baseUrl || absoluteUrl.indexOf(`${baseUrl}/`) === 0; 473 }; 474 475 /** 476 * Formats a string containing a count of items. 477 * 478 * This function ensures that the string is pluralized correctly. Since 479 * {@link Drupal.t} is called by this function, make sure not to pass 480 * already-localized strings to it. 481 * 482 * See the documentation of the server-side 483 * \Drupal\Core\StringTranslation\TranslationInterface::formatPlural() 484 * function for more details. 485 * 486 * @param {number} count 487 * The item count to display. 488 * @param {string} singular 489 * The string for the singular case. Make sure it is clear this is singular, 490 * to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not 491 * use @count in the singular string. 492 * @param {string} plural 493 * The string for the plural case. Make sure it is clear this is plural, to 494 * ease translation. Use @count in place of the item count, as in "@count 495 * new comments". 496 * @param {object} [args] 497 * An object of replacements pairs to make after translation. Incidences 498 * of any key in this array are replaced with the corresponding value. 499 * See {@link Drupal.formatString}.
500 * Note that you do not need to include @count in this array. 501 * This replacement is done automatically for the plural case. 502 * @param {object} [options] 503 * The options to pass to the {@link Drupal.t} function. 504 * 505 * @return {string} 506 * A translated string. 507 */ 508 Drupal.formatPlural = function (count, singular, plural, args, options) { 509 args = args || {}; 510 args['@count'] = count; 511 512 const pluralDelimiter = drupalSettings.pluralDelimiter; 513 const translations = Drupal.t( 514 singular + pluralDelimiter + plural, 515 args, 516 options, 517 ).split(pluralDelimiter); 518 let index = 0; 519 520 // Determine the index of the plural form. 521 if ( 522 typeof drupalTranslations !== 'undefined' && 523 drupalTranslations.pluralFormula 524 ) { 525 index = 526 count in drupalTranslations.pluralFormula 527 ? drupalTranslations.pluralFormula[count] 528 : drupalTranslations.pluralFormula.default; 529 } else if (args['@count'] !== 1) { 530 index = 1; 531 } 532 533 return translations[index]; 534 }; 535 536 /** 537 * Encodes a Drupal path for use in a URL. 538 * 539 * For aesthetic reasons slashes are not escaped. 540 * 541 * @param {string} item 542 * Unencoded path. 543 * 544 * @return {string} 545 * The encoded path. 546 */ 547 Drupal.encodePath = function (item) { 548 return window.encodeURIComponent(item).replace(/%2F/g, '/'); 549 }; 550 551 /** 552 * Triggers deprecation error. 553 * 554 * Deprecation errors are only triggered if deprecation errors haven't 555 * been suppressed. 556 * 557 * @param {Object} deprecation 558 * The deprecation options. 559 * @param {string} deprecation.message 560 * The deprecation message. 561 * 562 * @see https://www.drupal.org/core/deprecation#javascript 563 */ 564 Drupal.deprecationError = ({ message }) => { 565 if ( 566 drupalSettings.suppressDeprecationErrors === false && 567 typeof console !== 'undefined' && 568 console.warn 569 ) { 570 console.warn(`[Deprecation] ${message}`); 571 } 572 }; 573 574 /** 575 * Triggers deprecation error when object property is being used. 576 * 577 * @param {Object} deprecation 578 * The deprecation options. 579 * @param {Object} deprecation.target 580 * The targeted object. 581 * @param {string} deprecation.deprecatedProperty 582 * A key of the deprecated property. 583 * @param {string} deprecation.message 584 * The deprecation message. 585 * @returns {Object} 586 * 587 * @see https://www.drupal.org/core/deprecation#javascript 588 */ 589 Drupal.deprecatedProperty = ({ target, deprecatedProperty, message }) => { 590 // Proxy and Reflect are not supported by all browsers. Unsupported browsers 591 // are ignored since this is a development feature. 592 if (!Proxy || !Reflect) { 593 return target; 594 } 595 596 return new Proxy(target, { 597 get: (target, key, ...rest) => { 598 if (key === deprecatedProperty) { 599 Drupal.deprecationError({ message }); 600 } 601 return Reflect.get(target, key, ...rest); 602 }, 603 }); 604 }; 605 606 /** 607 * Generates the themed representation of a Drupal object. 608 * 609 * All requests for themed output must go through this function. It examines 610 * the request and routes it to the appropriate theme function. If the current 611 * theme does not provide an override function, the generic theme function is 612 * called. 613 * 614 * @example 615 * <caption>To retrieve the HTML for text that should be emphasized and 616 * displayed as a placeholder inside a sentence.</caption> 617 * Drupal.theme('placeholder', text); 618 * 619 * @namespace 620 * 621 * @param {function} func 622 * The name of the theme function to call. 623 * @param {...args} 624 * Additional arguments to pass along to the theme function. 625 * 626 * @return {string|object|HTMLElement|jQuery} 627 * Any data the theme function returns. This could be a plain HTML string, 628 * but also a complex object. 629 */ 630 Drupal.theme = function (func, ...args) { 631 if (func in Drupal.theme) { 632 return Drupal.theme[func](...args); 633 } 634 }; 635 636 /** 637 * Formats text for emphasized display in a placeholder inside a sentence. 638 * 639 * @param {string} str 640 * The text to format (plain-text). 641 * 642 * @return {string} 643 * The formatted text (html). 644 */ 645 Drupal.theme.placeholder = function (str) { 646 return `<em class="placeholder">${Drupal.checkPlain(str)}</em>`; 647 }; 648 649 /** 650 * Determine if an element is visible. 651 * 652 * @param {HTMLElement} elem 653 * The element to check. 654 * 655 * @return {boolean} 656 * True if the element is visible. 657 */ 658 Drupal.elementIsVisible = function (elem) { 659 return !!( 660 elem.offsetWidth || 661 elem.offsetHeight || 662 elem.getClientRects().length 663 ); 664 }; 665 666 /** 667 * Determine if an element is hidden. 668 * 669 * @param {HTMLElement} elem 670 * The element to check. 671 * 672 * @return {boolean} 673 * True if the element is hidden. 674 */ 675 Drupal.elementIsHidden = function (elem) { 676 return !Drupal.elementIsVisible(elem); 677 }; 678})( 679 Drupal, 680 window.drupalSettings, 681 window.drupalTranslations, 682 window.console, 683 window.Proxy, 684 window.Reflect, 685);
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.