PageSourceSearch

https://penn.museum/media/system/js/core.js?a3d8f8

js penn.museum collected 2026-09-24 15:25:01 UTC 26,311 bytes, 788 lines download raw bytes

1(function () {
2  'use strict';
3
4  function _extends() {
5    return _extends = Object.assign ? Object.assign.bind() : function (n) {
6      for (var e = 1; e < arguments.length; e++) {
7        var t = arguments[e];
8        for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
9      }
10      return n;
11    }, _extends.apply(null, arguments);
12  }
13
14  /**
15   * --------------------------------------------------------------------------
16   * Bootstrap util/sanitizer.js
17   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
18   * --------------------------------------------------------------------------
19   */
20
21  // js-docs-end allow-list
22
23  const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);
24
25  /**
26   * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
27   * contexts.
28   *
29   * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
30   */
31  const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;
32  const allowedAttribute = (attribute, allowedAttributeList) => {
33    const attributeName = attribute.nodeName.toLowerCase();
34    if (allowedAttributeList.includes(attributeName)) {
35      if (uriAttributes.has(attributeName)) {
36        return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));
37      }
38      return true;
39    }
40
41    // Check if a regular expression validates the attribute.
42    return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));
43  };
44  function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
45    if (!unsafeHtml.length) {
46      return unsafeHtml;
47    }
48    if (sanitizeFunction && typeof sanitizeFunction === 'function') {
49      return sanitizeFunction(unsafeHtml);
50    }
51    const domParser = new window.DOMParser();
52    const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');
53    const elements = [].concat(...createdDocument.body.querySelectorAll('*'));
54    for (const element of elements) {
55      const elementName = element.nodeName.toLowerCase();
56      if (!Object.keys(allowList).includes(elementName)) {
57        element.remove();
58        continue;
59      }
60      const attributeList = [].concat(...element.attributes);
61      const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);
62      for (const attribute of attributeList) {
63        if (!allowedAttribute(attribute, allowedAttributes)) {
64          element.removeAttribute(attribute.nodeName);
65        }
66      }
67    }
68    return createdDocument.body.innerHTML;
69  }
70
71  /**
72   * @copyright  (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
73   * @license    GNU General Public License version 2 or later; see LICENSE.txt
74   */
75
76  const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i;
77  const DATA_ATTRIBUTE_PATTERN = /^data-[\w-]*$/i;
78  const DefaultAllowlist = {
79    // Global attributes allowed on any supplied element below.
80    '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN, DATA_ATTRIBUTE_PATTERN],
81    a: ['target', 'href', 'title', 'rel'],
82    area: [],
83    b: [],
84    br: [],
85    col: [],
86    code: [],
87    div: [],
88    em: [],
89    hr: [],
90    h1: [],
91    h2: [],
92    h3: [],
93    h4: [],
94    h5: [],
95    h6: [],
96    i: [],
97    img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
98    li: [],
99    ol: [],
100    p: [],
101    pre: [],
102    s: [],
103    small: [],
104    span: [],
105    sub: [],
106    sup: [],
107    strong: [],
108    u: [],
109    ul: [],
110    button: ['type'],
111    input: ['accept', 'alt', 'autocomplete', 'autofocus', 'capture', 'checked', 'dirname', 'disabled', 'height', 'list', 'max', 'maxlength', 'min', 'minlength', 'multiple', 'type', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'size', 'src', 'step', 'value', 'width', 'inputmode'],
112    select: ['name'],
113    textarea: ['name'],
114    option: ['value', 'selected'],
115    details: ['open'],
116    summary: []
117  };
118
119  // Only define the Joomla namespace if not defined.
120  window.Joomla = window.Joomla || {};
121
122  // Only define editors if not defined
123  Joomla.editors = Joomla.editors || {};
124
125  // An object to hold each editor instance on page, only define if not defined.
126  Joomla.editors.instances = Joomla.editors.instances || {
127    /**
128     * *****************************************************************
129     * All Editors MUST register, per instance, the following callbacks:
130     * *****************************************************************
131     *
132     * getValue         Type  Function  Should return the complete data from the editor
133     *                                  Example: () => { return this.element.value; }
134     * setValue         Type  Function  Should replace the complete data of the editor
135     *                                  Example: (text) => { return this.element.value = text; }
136     * getSelection     Type  Function  Should return the selected text from the editor
137     *                                  Example: function () { return this.selectedText; }
138     * disable          Type  Function  Toggles the editor into disabled mode. When the editor is
139     *                                  active then everything should be usable. When inactive the
140     *                                  editor should be unusable AND disabled for form validation
141     *                                  Example: (bool) => { return this.disable = value; }
142     * replaceSelection Type  Function  Should replace the selected text of the editor
143     *                                  If nothing selected, will insert the data at the cursor
144     *                                  Example:
145     *                                  (text) => {
146     *                                    return insertAtCursor(this.element, text);
147     *                                    }
148     *
149     * USAGE (assuming that jform_articletext is the textarea id)
150     * {
151     * To get the current editor value:
152     *  Joomla.editors.instances['jform_articletext'].getValue();
153     * To set the current editor value:
154     *  Joomla.editors.instances['jform_articletext'].setValue('Joomla! rocks');
155     * To replace(selection) or insert a value at  the current editor cursor (replaces the J3
156     * jInsertEditorText API):
157     *  replaceSelection:
158     *  Joomla.editors.instances['jform_articletext'].replaceSelection('Joomla! rocks')
159     * }
160     *
161     * *********************************************************
162     * ANY INTERACTION WITH THE EDITORS SHOULD USE THE ABOVE API
163     * *********************************************************
164     */
165  };
166  Joomla.Modal = Joomla.Modal || {
167    /**
168     * *****************************************************************
169     * Modals should implement
170     * *****************************************************************
171     *
172     * getCurrent  Type  Function  Should return the modal element
173     * setCurrent  Type  Function  Should set the modal element
174     * current     Type  {node}    The modal element
175     *
176     * USAGE (assuming that exampleId is the modal id)
177     * To get the current modal element:
178     *   Joomla.Modal.getCurrent(); // Returns node element, eg: document.getElementById('exampleId')
179     * To set the current modal element:
180     *   Joomla.Modal.setCurrent(document.getElementById('exampleId'));
181     *
182     * *************************************************************
183     * Joomla's UI modal uses `element.close();` to close the modal
184     * and `element.open();` to open the modal
185     * If you are using another modal make sure the same
186     * functionality is bound to the modal element
187     * @see media/legacy/bootstrap.init.js
188     * *************************************************************
189     */
190    current: '',
191    setCurrent: element => {
192      Joomla.Modal.current = element;
193    },
194    getCurrent: () => Joomla.Modal.current
195  };
196
197  /**
198   * Method to Extend Objects
199   *
200   * @param  {Object}  destination
201   * @param  {Object}  source
202   *
203   * @return Object
204   */
205  Joomla.extend = (destination, source) => {
206    let newDestination = destination;
207    /**
208     * Technically null is an object, but trying to treat the destination as one in this
209     * context will error out.
210     * So emulate jQuery.extend(), and treat a destination null as an empty object.
211     */
212    if (destination === null) {
213      newDestination = {};
214    }
215    Object.keys(source).forEach(key => {
216      newDestination[key] = source[key];
217    });
218    return destination;
219  };
220
221  /**
222   * Joomla options storage
223   *
224   * @type {{}}
225   *
226   * @since 3.7.0
227   */
228  Joomla.optionsStorage = Joomla.optionsStorage || null;
229
230  /**
231   * Get script(s) options
232   *
233   * @param  {String}  key  Name in Storage
234   * @param  {mixed}   def  Default value if nothing found
235   *
236   * @return {mixed}
237   *
238   * @since 3.7.0
239   */
240  Joomla.getOptions = (key, def) => {
241    // Load options if they not exists
242    if (!Joomla.optionsStorage) {
243      Joomla.loadOptions();
244    }
245    return Joomla.optionsStorage[key] !== undefined ? Joomla.optionsStorage[key] : def;
246  };
247
248  /**
249   * Load new options from given options object or from Element
250   *
251   * @param  {Object|undefined}  options  The options object to load.
252   * Eg {"com_foobar" : {"option1": 1, "option2": 2}}
253   *
254   * @since 3.7.0
255   */
256  Joomla.loadOptions = options => {
257    // Load form the script container
258    if (!options) {
259      let counter = 0;
260      document.querySelectorAll('.joomla-script-options.new').forEach(element => {
261        const str = element.text || element.textContent;
262        const option = JSON.parse(str);
263        if (option) {
264          Joomla.loadOptions(option);
265          counter += 1;
266        }
267        element.className = element.className.replace(' new', ' loaded');
268      });
269      if (counter) {
270        return;
271      }
272    }
273
274    // Initial loading
275    if (!Joomla.optionsStorage) {
276      Joomla.optionsStorage = options || {};
277    } else if (options) {
278      // Merge with existing
279      Object.keys(options).forEach(key => {
280        /**
281         * If both existing and new options are objects, merge them with Joomla.extend().
282         * But test for new option being null, as null is an object, but we want to allow
283         * clearing of options with ...
284         *
285         * Joomla.loadOptions({'joomla.jtext': null});
286         */
287        if (options[key] !== null && typeof Joomla.optionsStorage[key] === 'object' && typeof options[key] === 'object') {
288          Joomla.optionsStorage[key] = Joomla.extend(Joomla.optionsStorage[key], options[key]);
289        } else {
290          Joomla.optionsStorage[key] = options[key];
291        }
292      });
293    }
294  };
295
296  /**
297   * Custom behavior for JavaScript I18N in Joomla! 1.6
298   *
299   * @type {{}}
300   *
301   * Allows you to call Joomla.Text._() to get a translated JavaScript string
302   * pushed in with Text::script() in Joomla.
303   */
304  Joomla.Text = {
305    strings: {},
306    /**
307     * Translates a string into the current language.
308     *
309     * @param {String} key   The string to translate
310     * @param {String} def   Default string
311     *
312     * @returns {String}
313     */
314    _: (key, def) => {
315      let newKey = key;
316      let newDef = def;
317      // Check for new strings in the optionsStorage, and load them
318      const newStrings = Joomla.getOptions('joomla.jtext');
319      if (newStrings) {
320        Joomla.Text.load(newStrings);
321
322        // Clean up the optionsStorage from useless data
323        Joomla.loadOptions({
324          'joomla.jtext': null
325        });
326      }
327      newDef = newDef === undefined ? newKey : newDef;
328      newKey = newKey.toUpperCase();
329      return Joomla.Text.strings[newKey] !== undefined ? Joomla.Text.strings[newKey] : newDef;
330    },
331    /**
332     * Load new strings in to Joomla.Text
333     *
334     * @param {Object} object  Object with new strings
335     * @returns {Joomla.Text}
336     */
337    load: object => {
338      Object.keys(object).forEach(key => {
339        Joomla.Text.strings[key.toUpperCase()] = object[key];
340      });
341      return Joomla.Text;
342    }
343  };
344
345  /**
346   * For B/C we still support Joomla.JText
347   *
348   * @type {{}}
349   *
350   * @deprecated   4.0 will be removed in 6.0
351   *               Example: Joomla.Text._('...');
352   *                        Joomla.Text.load(...);
353   */
354  Joomla.JText = Joomla.Text;
355
356  /**
357   * Generic submit form
358   *
359   * @param  {String}  task      The given task
360   * @param  {node}    form      The form element
361   * @param  {bool}    validate  The form element
362   *
363   * @returns  {void}
364   */
365  Joomla.submitform = (task, form, validate) => {
366    let newForm = form;
367    const newTask = task;
368    if (!newForm) {
369      newForm = document.getElementById('adminForm');
370    }
371    if (newTask) {
372      newForm.task.value = newTask;
373    }
374
375    // Toggle HTML5 validation
376    newForm.noValidate = !validate;
377    if (!validate) {
378      newForm.setAttribute('novalidate', '');
379    } else if (newForm.hasAttribute('novalidate')) {
380      newForm.removeAttribute('novalidate');
381    }
382
383    // Submit the form.
384    // Create the input type="submit"
385    const button = document.createElement('input');
386    button.classList.add('hidden');
387    button.type = 'submit';
388
389    // Append it and click it
390    newForm.appendChild(button).click();
391
392    // If "submit" was prevented, make sure we don't get a build up of buttons
393    newForm.removeChild(button);
394  };
395
396  /**
397   * Default function. Can be overridden by the component to add custom logic
398   *
399   * @param  {String}  task            The given task
400   * @param  {String}  formSelector    The form selector eg '#adminForm'
401   * @param  {bool}    validate        The form element
402   *
403   * @returns {void}
404   */
405  Joomla.submitbutton = (task, formSelector, validate) => {
406    let form = document.querySelector(formSelector || 'form.form-validate');
407    let newValidate = validate;
408    if (typeof formSelector === 'string' && form === null) {
409      form = document.querySelector(`#${formSelector}`);
410    }
411    if (form) {
412      if (newValidate === undefined || newValidate === null) {
413        const pressbutton = task.split('.');
414        let cancelTask = form.getAttribute('data-cancel-task');
415        if (!cancelTask) {
416          cancelTask = `${pressbutton[0]}.cancel`;
417        }
418        newValidate = task !== cancelTask;
419      }
420      if (!newValidate || document.formvalidator.isValid(form)) {
421        Joomla.submitform(task, form);
422      }
423    } else {
424      Joomla.submitform(task);
425    }
426  };
427
428  /**
429   * USED IN: all list forms.
430   *
431   * Toggles the check state of a group of boxes
432   *
433   * Checkboxes must have an id attribute in the form cb0, cb1...
434   *
435   * @param {mixed}  checkbox The number of box to 'check', for a checkbox element
436   * @param {string} stub     An alternative field name
437   *
438   * @return {boolean}
439   */
440  Joomla.checkAll = (checkbox, stub) => {
441    if (!checkbox.form) {
442      return false;
443    }
444    const currentStab = stub || 'cb';
445    const elements = [].slice.call(checkbox.form.elements);
446    let state = 0;
447    elements.forEach(element => {
448      if (element.type === checkbox.type && element.id.indexOf(currentStab) === 0) {
449        element.checked = checkbox.checked;
450        state += element.checked ? 1 : 0;
451      }
452    });
453    if (checkbox.form.boxchecked) {
454      checkbox.form.boxchecked.value = state;
455      checkbox.form.boxchecked.dispatchEvent(new CustomEvent('change', {
456        bubbles: true,
457        cancelable: true
458      }));
459    }
460    return true;
461  };
462
463  /**
464   * USED IN: administrator/components/com_cache/views/cache/tmpl/default.php
465   * administrator/components/com_installer/views/discover/tmpl/default_item.php
466   * administrator/components/com_installer/views/update/tmpl/default_item.php
467   * administrator/components/com_languages/helpers/html/languages.php
468   * libraries/joomla/html/html/grid.php
469   *
470   * @param  {boolean}  isitchecked  Flag for checked
471   * @param  {node}     form         The form
472   *
473   * @return  {void}
474   */
475  Joomla.isChecked = (isitchecked, form) => {
476    let newForm = form;
477    if (typeof newForm === 'undefined') {
478      newForm = document.getElementById('adminForm');
479    } else if (typeof form === 'string') {
480      newForm = document.getElementById(form);
481    }
482    newForm.boxchecked.value = isitchecked ? parseInt(newForm.boxchecked.value, 10) + 1 : parseInt(newForm.boxchecked.value, 10) - 1;
483    newForm.boxchecked.dispatchEvent(new CustomEvent('change', {
484      bubbles: true,
485      cancelable: true
486    }));
487
488    // If we don't have a checkall-toggle, done.
489    if (!newForm.elements['checkall-toggle']) {
490      return;
491    }
492
493    // Toggle main toggle checkbox depending on checkbox selection
494    let c = true;
495    let i;
496    let e;
497    let n;
498    for (i = 0, n = newForm.elements.length; i < n; i++) {
499      e = newForm.elements[i];
500      if (e.type === 'checkbox' && e.name !== 'checkall-toggle' && !e.checked) {
501        c = false;
502        break;
503      }
504    }
505    newForm.elements['checkall-toggle'].checked = c;
506  };
507
508  /**
509   * USED IN: libraries/joomla/html/html/grid.php
510   * In other words, on any reorderable table
511   *
512   * @param  {string}  order  The order value
513   * @param  {string}  dir    The direction
514   * @param  {string}  task   The task
515   * @param  {node}    form   The form
516   *
517   * return  {void}
518   */
519  Joomla.tableOrdering = (order, dir, task, form) => {
520    let newForm = form;
521    if (typeof newForm === 'undefined') {
522      newForm = document.getElementById('adminForm');
523    } else if (typeof form === 'string') {
524      newForm = document.getElementById(form);
525    }
526    newForm.filter_order.value = order;
527    newForm.filter_order_Dir.value = dir;
528    Joomla.submitform(task, newForm);
529  };
530
531  /**
532   * USED IN: all over :)
533   *
534   * @param  {string}  id    The id
535   * @param  {string}  task  The task
536   * @param  {string}  form  The optional form
537   *
538   * @return {boolean}
539   */
540  Joomla.listItemTask = (id, task, form = null) => {
541    let newForm = form;
542    if (form !== null) {
543      newForm = document.getElementById(form);
544    } else {
545      newForm = document.adminForm;
546    }
547    const cb = newForm[id];
548    let i = 0;
549    let cbx;
550    if (!cb) {
551      return false;
552    }
553    while (true) {
554      cbx = newForm[`cb${i}`];
555      if (!cbx) {
556        break;
557      }
558      cbx.checked = false;
559      i += 1;
560    }
561    cb.checked = true;
562    newForm.boxchecked.value = 1;
563    Joomla.submitform(task, newForm);
564    return false;
565  };
566
567  /**
568   * Method to replace all request tokens on the page with a new one.
569   *
570   * @param {String}  newToken  The token
571   *
572   * Used in Joomla Installation
573   */
574  Joomla.replaceTokens = newToken => {
575    if (!/^[0-9A-F]{32}$/i.test(newToken)) {
576      return;
577    }
578    document.querySelectorAll('input[type="hidden"]').forEach(element => {
579      if (element.value === '1' && element.name.length === 32) {
580        element.name = newToken;
581      }
582    });
583  };
584
585  /**
586   * Method to perform AJAX request
587   *
588   * @param {Object} options   Request options:
589   * {
590   *    url:     'index.php', Request URL
591   *    method:  'GET',       Request method GET (default), POST
592   *    data:    null,        Data to be sent, see
593   *                https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/send
594   *    perform: true,        Perform the request immediately
595   *              or return XMLHttpRequest instance and perform it later
596   *    headers: null,        Object of custom headers, eg {'X-Foo': 'Bar', 'X-Bar': 'Foo'}
597   *    promise: false        Whether return a Promise instance.
598   *              When true then next options is ignored: perform, onSuccess, onError, onComplete
599   *
600   *    onBefore:  (xhr) => {}            // Callback on before the request
601   *    onSuccess: (response, xhr) => {}, // Callback on the request success
602   *    onError:   (xhr) => {},           // Callback on the request error
603   *    onComplete: (xhr) => {},          // Callback on the request completed, with/without error
604   * }
605   *
606   * @return XMLHttpRequest|Boolean
607   *
608   * @example
609   *
610   *   Joomla.request({
611   *    url: 'index.php?option=com_example&view=example',
612   *    onSuccess: (response, xhr) => {
613   *     JSON.parse(response);
614   *    }
615   *   })
616   *
617   * @see    https://developer.mozilla.org/docs/Web/API/XMLHttpRequest
618   */
619  Joomla.request = options => {
620    // Prepare the options
621    const newOptions = Joomla.extend({
622      url: '',
623      method: 'GET',
624      data: null,
625      perform: true,
626      promise: false
627    }, options);
628
629    // Setup XMLHttpRequest instance
630    const createRequest = (onSuccess, onError) => {
631      const xhr = new XMLHttpRequest();
632      xhr.open(newOptions.method, newOptions.url, true);
633
634      // Set the headers
635      xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
636      xhr.setRequestHeader('X-Ajax-Engine', 'Joomla!');
637      if (newOptions.method !== 'GET') {
638        const token = Joomla.getOptions('csrf.token', '');
639
640        // Use the CSRF only on the site's domain
641        if (token && (!newOptions.url.startsWith('http:') && !newOptions.url.startsWith('https:') || newOptions.url.startsWith(window.location.origin))) {
642          xhr.setRequestHeader('X-CSRF-Token', token);
643        }
644        if (typeof newOptions.data === 'string' && (!newOptions.headers || !newOptions.headers['Content-Type'])) {
645          xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
646        }
647      }
648
649      // Custom headers
650      if (newOptions.headers) {
651        Object.keys(newOptions.headers).forEach(key => {
652          // Allow request without Content-Type
653          if (key === 'Content-Type' && newOptions.headers['Content-Type'] === 'false') ; else {
654            xhr.setRequestHeader(key, newOptions.headers[key]);
655          }
656        });
657      }
658      xhr.onreadystatechange = () => {
659        // Request not finished
660        if (xhr.readyState !== 4) {
661          return;
662        }
663
664        // Request finished and response is ready
665        if (xhr.status === 200) {
666          if (newOptions.promise) {
667            // A Promise accepts only one argument
668            onSuccess.call(window, xhr);
669          } else {
670            onSuccess.call(window, xhr.responseText, xhr);
671          }
672        } else {
673          onError.call(window, xhr);
674        }
675        if (newOptions.onComplete && !newOptions.promise) {
676          newOptions.onComplete.call(window, xhr);
677        }
678      };
679
680      // Do request
681      if (newOptions.perform) {
682        if (newOptions.onBefore && newOptions.onBefore.call(window, xhr) === false) {
683          // Request interrupted
684          if (newOptions.promise) {
685            onSuccess.call(window, xhr);
686          }
687          return xhr;
688        }
689        xhr.send(newOptions.data);
690      }
691      return xhr;
692    };
693
694    // Return a Promise
695    if (newOptions.promise) {
696      return new Promise((resolve, reject) => {
697        newOptions.perform = true;
698        createRequest(resolve, reject);
699      });
700    }
701
702    // Return a Request
703    try {
704      return createRequest(newOptions.onSuccess || (() => {}), newOptions.onError || (() => {}));
705    } catch (error) {
706      console.error(error);
707      return false;
708    }
709  };
710  let lastRequestPromise;
711
712  /**
713   * Joomla Request queue.
714   *
715   * A FIFO queue of requests to execute serially. Used to prevent simultaneous execution of
716   * multiple requests against the server which could trigger its Denial of Service protection.
717   *
718   * @param {object} options Options for Joomla.request()
719   * @returns {Promise}
720   */
721  Joomla.enqueueRequest = options => {
722    if (!options.promise) {
723      throw new Error('Joomla.enqueueRequest supports only Joomla.request as Promise');
724    }
725    if (!lastRequestPromise) {
726      lastRequestPromise = Joomla.request(options);
727    } else {
728      lastRequestPromise = lastRequestPromise.then(() => Joomla.request(options));
729    }
730    return lastRequestPromise;
731  };
732
733  /**
734   *
735   * @param {string} unsafeHtml The html for sanitization
736   * @param {object} allowList The list of HTMLElements with an array of allowed attributes
737   * @param {function} sanitizeFn A custom sanitization function
738   *
739   * @return string
740   */
741  Joomla.sanitizeHtml = (unsafeHtml, allowList, sanitizeFn) => {
742    const allowed = allowList === undefined || allowList === null ? DefaultAllowlist : _extends({}, DefaultAllowlist, allowList);
743    return sanitizeHtml(unsafeHtml, allowed, sanitizeFn);
744  };
745
746  /**
747   * Treat AJAX errors.
748   * Used by some javascripts such as sendtestmail.js and permissions.js
749   *
750   * @param   {object}  xhr         XHR object.
751   * @param   {string}  textStatus  Type of error that occurred.
752   * @param   {string}  error       Textual portion of the HTTP status.
753   *
754   * @return  {object}  JavaScript object containing the system error message.
755   *
756   * @since  3.6.0
757   */
758  Joomla.ajaxErrorsMessages = (xhr, textStatus) => {
759    const msg = {};
760    if (textStatus === 'parsererror') {
761      // For jQuery jqXHR
762      const buf = [];
763
764      // Html entity encode.
765      let encodedJson = xhr.responseText.trim();
766      for (let i = encodedJson.length - 1; i >= 0; i--) {
767        buf.unshift(['&#', encodedJson[i].charCodeAt(), ';'].join(''));
768      }
769      encodedJson = buf.join('');
770      msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_PARSE').replace('%s', encodedJson)];
771    } else if (textStatus === 'nocontent') {
772      msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_NO_CONTENT')];
773    } else if (textStatus === 'timeout') {
774      msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_TIMEOUT')];
775    } else if (textStatus === 'abort') {
776      msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_CONNECTION_ABORT')];
777    } else if (xhr.responseJSON && xhr.responseJSON.message) {
778      // For vanilla XHR
779      msg.error = [`${Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)} <em>${xhr.responseJSON.message}</em>`];
780    } else if (xhr.statusText) {
781      msg.error = [`${Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)} <em>${xhr.statusText}</em>`];
782    } else {
783      msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)];
784    }
785    return msg;
786  };
787
788})();

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.