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