PageSourceSearch

https://www.hiltonheadisland.org/modules/contrib/extlink/js/extlink.js?v=10.5.1

js hiltonheadisland.org collected 2026-10-02 05:15:58 UTC 24,043 bytes, 574 lines download raw bytes

1/**
2 * @file
3 * External links js file.
4 */
5
6((Drupal, drupalSettings) => {
7  Drupal.extlink = Drupal.extlink || {};
8
9  Drupal.extlink.attach = (context, drupalSettings) => {
10    if (typeof drupalSettings.data === 'undefined' || !drupalSettings.data.hasOwnProperty('extlink')) {
11      return;
12    }
13
14    // Define the method (either 'append' or 'prepend') of placing the
15    // icon, defaults to 'append'.
16    let extIconPlacement = 'append';
17    if (drupalSettings.data.extlink.extIconPlacement && drupalSettings.data.extlink.extIconPlacement !== '0') {
18      extIconPlacement = drupalSettings.data.extlink.extIconPlacement;
19    }
20
21    // Strip the host name down, removing ports, subdomains, or www.
22    const pattern = /^(([^:]+?\.)*)([^.:]+)((\.[a-z0-9]{1,253})*)(:[0-9]{1,5})?$/;
23    const host = window.location.host.replace(pattern, '$2$3$6');
24    const subdomain = window.location.host.replace(host, '');
25
26    // Determine what subdomains are considered internal.
27    let subdomains;
28    if (drupalSettings.data.extlink.extSubdomains) {
29      subdomains = '([^/]*\\.)?';
30    } else if (subdomain === 'www.' || subdomain === '') {
31      subdomains = '(www\\.)?';
32    } else {
33      subdomains = subdomain.replace('.', '\\.');
34    }
35
36    // Whitelisted domains.
37    let whitelistedDomains = false;
38    if (drupalSettings.data.extlink.whitelistedDomains) {
39      whitelistedDomains = [];
40      for (let i = 0; i < drupalSettings.data.extlink.whitelistedDomains.length; i++) {
41        whitelistedDomains.push(new RegExp(`^https?:\\/\\/${drupalSettings.data.extlink.whitelistedDomains[i].replace(/(\r\n|\n|\r)/gm, '')}.*$`, 'i'));
42      }
43    }
44
45    // Build regular expressions that define an internal link.
46    const internalLink = new RegExp(`^https?://([^@]*@)?${subdomains}${host}`, 'i');
47
48    // Extra internal link matching.
49    let extInclude = false;
50    if (drupalSettings.data.extlink.extInclude) {
51      extInclude = new RegExp(drupalSettings.data.extlink.extInclude.replace(/\\/, '\\'), 'i');
52    }
53
54    // Extra external link matching.
55    let extExclude = false;
56    if (drupalSettings.data.extlink.extExclude) {
57      extExclude = new RegExp(drupalSettings.data.extlink.extExclude.replace(/\\/, '\\'), 'i');
58    }
59
60    // Extra external link matching for excluding noreferrer.
61    let extExcludeNoreferrer = false;
62    if (drupalSettings.data.extlink.extExcludeNoreferrer) {
63      extExcludeNoreferrer = new RegExp(drupalSettings.data.extlink.extExcludeNoreferrer.replace(/\\/, '\\'), 'i');
64    }
65
66    // Extra external link CSS selector exclusion.
67    let extCssExclude = false;
68    if (drupalSettings.data.extlink.extCssExclude) {
69      extCssExclude = drupalSettings.data.extlink.extCssExclude;
70    }
71
72    // Extra external link CSS selector inclusion.
73    let extCssInclude = false;
74    if (drupalSettings.data.extlink.extCssInclude) {
75      extCssInclude = drupalSettings.data.extlink.extCssInclude;
76    }
77
78    // Extra external link CSS selector explicit.
79    let extCssExplicit = false;
80    if (drupalSettings.data.extlink.extCssExplicit) {
81      extCssExplicit = drupalSettings.data.extlink.extCssExplicit;
82    }
83
84    // Find all links which are NOT internal and begin with http as opposed
85    // to ftp://, javascript:, etc. other kinds of links.
86    // When operating on the 'this' variable, the host has been appended to
87    // all links by the browser, even local ones.
88    const externalLinks = [];
89    const mailtoLinks = [];
90    const telLinks = [];
91    const extlinks = context.querySelectorAll('a:not([data-extlink]), area:not([data-extlink])');
92    extlinks.forEach((el) => {
93      try {
94        let url = '';
95        if (typeof el.href === 'string') {
96          url = el.href.toLowerCase();
97        }
98        // Handle SVG links (xlink:href).
99        else if (typeof el.href === 'object') {
100          url = el.href.baseVal;
101        }
102        const isExtCssIncluded = extCssInclude && (el.matches(extCssInclude) || el.closest(extCssInclude));
103        if (
104          url.indexOf('http') === 0 &&
105          ((!internalLink.test(url) && !(extExclude && extExclude.test(url))) || (extInclude && extInclude.test(url)) || isExtCssIncluded) &&
106          !(extCssExclude && el.matches(extCssExclude)) &&
107          !(extCssExclude && el.closest(extCssExclude)) &&
108          !(extCssExplicit && !el.closest(extCssExplicit))
109        ) {
110          let match = false;
111          if (!isExtCssIncluded && whitelistedDomains) {
112            for (let i = 0; i < whitelistedDomains.length; i++) {
113              if (whitelistedDomains[i].test(url)) {
114                match = true;
115                break;
116              }
117            }
118          }
119          if (!match) {
120            externalLinks.push(el);
121          }
122        }
123        // Do not include area tags with begin with mailto: (this prohibits
124        // icons from being added to image-maps).
125        else if (el.tagName !== 'AREA' && !(extCssExclude && el.closest(extCssExclude)) && !(extCssExplicit && !el.closest(extCssExplicit))) {
126          if (url.indexOf('mailto:') === 0) {
127            mailtoLinks.push(el);
128          } else if (url.indexOf('tel:') === 0) {
129            telLinks.push(el);
130          }
131        }
132      } catch (error) {
133        // IE7 throws errors often when dealing with irregular links, such as:
134        // <a href="node/10"></a> Empty tags.
135        // <a href="http://user:[email protected]">example</a> User:pass syntax.
136        return false;
137      }
138    });
139
140    const hasExtIcon = drupalSettings.data.extlink.extClass !== '0' && drupalSettings.data.extlink.extClass !== '';
141    const hasAdditionalExtClasses = drupalSettings.data.extlink.extAdditionalLinkClasses !== '';
142    Drupal.extlink.applyClassAndSpan(externalLinks, 'ext', hasExtIcon ? extIconPlacement : null);
143    if (hasAdditionalExtClasses) {
144      Drupal.extlink.applyClassAndSpan(externalLinks, drupalSettings.data.extlink.extAdditionalLinkClasses, null);
145    }
146
147    const hasMailtoClass = drupalSettings.data.extlink.mailtoClass !== '0' && drupalSettings.data.extlink.mailtoClass !== '';
148    const hasAdditionalMailtoClasses = drupalSettings.data.extlink.extAdditionalMailtoClasses !== '';
149    if (hasMailtoClass) {
150      Drupal.extlink.applyClassAndSpan(mailtoLinks, drupalSettings.data.extlink.mailtoClass, extIconPlacement);
151    }
152    if (hasAdditionalMailtoClasses) {
153      Drupal.extlink.applyClassAndSpan(mailtoLinks, drupalSettings.data.extlink.extAdditionalMailtoClasses, null);
154    }
155
156    const hasTelClass = drupalSettings.data.extlink.telClass !== '0' && drupalSettings.data.extlink.telClass !== '';
157    const hasAdditionalTelClasses = drupalSettings.data.extlink.extAdditionalTelClasses !== '0' && drupalSettings.data.extlink.extAdditionalTelClasses !== '';
158    if (hasTelClass) {
159      Drupal.extlink.applyClassAndSpan(telLinks, drupalSettings.data.extlink.telClass, extIconPlacement);
160    }
161    if (hasAdditionalTelClasses) {
162      Drupal.extlink.applyClassAndSpan(mailtoLinks, drupalSettings.data.extlink.extAdditionalTelClasses, null);
163    }
164
165    if (drupalSettings.data.extlink.extTarget) {
166      // Add target attr to open link in a new tab if not set.
167      externalLinks.forEach((link, i) => {
168        if (!(drupalSettings.data.extlink.extTargetNoOverride && link.matches('a[target]'))) {
169          externalLinks[i].setAttribute('target', '_blank');
170        }
171      });
172
173      // Add noopener rel attribute to combat phishing.
174      externalLinks.forEach((link, i) => {
175        const val = link.getAttribute('rel');
176        // If no rel attribute is present, create one with the value noopener.
177        if (val === null || typeof val === 'undefined') {
178          externalLinks[i].setAttribute('rel', 'noopener');
179          return;
180        }
181        // Check to see if rel contains noopener. Add what doesn't exist.
182        if (val.indexOf('noopener') > -1) {
183          if (val.indexOf('noopener') === -1) {
184            // Add noopener.
185            externalLinks[i].setAttribute('rel', `${val} noopener`);
186          } else {
187            // Noopener exists. Nothing needs to be added.
188          }
189        }
190        // Else, append noopener to val.
191        else {
192          // Add noopener.
193          externalLinks[i].setAttribute('rel', `${val} noopener`);
194        }
195      });
196    }
197
198    if (drupalSettings.data.extlink.extNofollow) {
199      externalLinks.forEach((link, i) => {
200        const val = link.getAttribute('rel');
201        // When the link does not have a rel attribute set it to 'nofollow'.
202        if (val === null || typeof val === 'undefined') {
203          externalLinks[i].setAttribute('rel', 'nofollow');
204          return;
205        }
206        let target = 'nofollow';
207        // Change the target, if not overriding follow.
208        if (drupalSettings.data.extlink.extFollowNoOverride) {
209          target = 'follow';
210        }
211        if (val.indexOf(target) === -1) {
212          // Add nofollow.
213          externalLinks[i].setAttribute('rel', `${val} nofollow`);
214        }
215      });
216    }
217
218    if (drupalSettings.data.extlink.extTitleNoOverride === false) {
219      // Set the title attribute of all external links that opens in a new window.
220      externalLinks.forEach((link, i) => {
221        const oldTitle = link.getAttribute('title');
222
223        // Determine new title based on drupalSettings extTarget configuration.
224        let newTitle = drupalSettings.data.extlink.extTarget ? drupalSettings.data.extlink.extTargetAppendNewWindowLabel : '';
225        if (oldTitle !== null) {
226          if (Drupal.extlink.hasNewWindowText(oldTitle)) {
227            return;
228          }
229          newTitle = Drupal.extlink.combineLabels(oldTitle, newTitle);
230        }
231        if (newTitle) {
232          externalLinks[i].setAttribute('title', newTitle);
233        }
234      });
235    }
236
237    if (drupalSettings.data.extlink.extNoreferrer) {
238      externalLinks.forEach((link, i) => {
239        // Don't add 'noreferrer' if exclude noreferrer option is set and the
240        // link matches the entered pattern to exclude 'noreferrer' tag.
241        if (drupalSettings.data.extlink.extExcludeNoreferrer && extExcludeNoreferrer.test(link.getAttribute('href'))) {
242          return;
243        }
244
245        const val = link.getAttribute('rel');
246        // When the link does not have a rel attribute set it to 'noreferrer'.
247        if (val === null || typeof val === 'undefined') {
248          externalLinks[i].setAttribute('rel', 'noreferrer');
249          return;
250        }
251        // Add noreferrer.
252        externalLinks[i].setAttribute('rel', `${val} noreferrer`);
253      });
254    }
255
256    /* eslint:disable:no-empty */
257    Drupal.extlink = Drupal.extlink || {};
258
259    // Set up default click function for the external links popup. This should be
260    // overridden by modules wanting to alter the popup.
261    Drupal.extlink.popupClickHandler =
262      Drupal.extlink.popupClickHandler ||
263      (() => {
264        if (drupalSettings.data.extlink.extAlert) {
265          // eslint-disable-next-line no-restricted-globals
266          return confirm(drupalSettings.data.extlink.extAlertText);
267        }
268      });
269
270    const _that = this;
271    Drupal.extlink.handleClick = function (event) {
272      const shouldNavigate = Drupal.extlink.popupClickHandler.call(_that, event);
273      if (typeof shouldNavigate !== 'undefined' && !shouldNavigate) {
274        // Prevent navigation if the user clicks "Cancel".
275        event.preventDefault();
276      }
277    };
278
279    externalLinks.forEach((val, i) => {
280      externalLinks[i].removeEventListener('click', Drupal.extlink.handleClick);
281      externalLinks[i].addEventListener('click', Drupal.extlink.handleClick);
282    });
283  };
284
285  /**
286   * Check if the label already has 'new window' text.
287   *
288   *
289   * @returns boolean
290   * @param label
291   */
292  Drupal.extlink.hasNewWindowText = function (label) {
293    return label.toLowerCase().indexOf(Drupal.t('new window')) !== -1;
294  };
295
296  /**
297   * Combine two labels.
298   *
299   * Combine labels in a readable manner, taking into account if the label
300   * uses parenthesis or not. For examples,
301   *
302   *   1. "A" + "B" => "A, B"
303   *   2. "A" + "(B)" => "A (B)"
304   *   3. "(A)" + "B" => "B (A)"
305   *   4. "(A)" + "(B)" => "(A, B)"
306   *
307   *
308   * @returns string
309   * @param labelA
310   * @param labelB
311   */
312  Drupal.extlink.combineLabels = function (labelA, labelB) {
313    labelA = labelA || '';
314    labelB = labelB || '';
315    const labelANoParens = labelA.trim().replace('(', '').replace(')', '');
316    const labelBNoParens = labelB.trim().replace('(', '').replace(')', '');
317    if (labelA === labelANoParens) {
318      if (labelB === labelBNoParens) {
319        // This is Example 1 above: "A" + "B" => "A, B"
320        return `${labelA}, ${labelB}`;
321      }
322
323      // This is Example 2 above: "A" + "(B)" => "A (B)"
324      return `${labelA} ${labelB}`;
325    }
326
327    if (labelB === labelBNoParens) {
328      // This is Example 3 above: "(A)" + "B" => "B (A)"
329      return `${labelB} ${labelA}`;
330    }
331
332    // This is Example 4 above: "(A)" + "(B)" => "(A, B)"
333    return `(${labelANoParens}, ${labelBNoParens})`;
334  };
335
336  /**
337   * Apply a class and a trailing <span> to all links not containing images.
338   *
339   * @param {object[]} links
340   *   An array of DOM elements representing the links.
341   * @param {string} className
342   *   The class to apply to the links.
343   * @param {string} iconPlacement
344   *   'append' or 'prepend' the icon to the link.
345   */
346  Drupal.extlink.applyClassAndSpan = (links, className, iconPlacement) => {
347    let linksToProcess;
348    if (drupalSettings.data.extlink.extImgClass) {
349      linksToProcess = links;
350    } else {
351      // Only text links.
352      linksToProcess = links.filter((link) => {
353        return link.querySelector('img, svg') === null;
354      });
355    }
356
357    for (let i = 0; i < linksToProcess.length; i++) {
358      if (className !== '0') {
359        linksToProcess[i].classList.add(className);
360      }
361
362      // Additional classes:
363      if (className === drupalSettings.data.extlink.mailtoClass && drupalSettings.data.extlink.extAdditionalMailtoClasse
363s) {
364        // Is mail link:
365        linksToProcess[i].classList.add(drupalSettings.data.extlink.extAdditionalMailtoClasses);
366      } else if (className === drupalSettings.data.extlink.telClass && drupalSettings.data.extlink.extAdditionalTelClasses) {
367        // Is regular external link:
368        linksToProcess[i].classList.add(drupalSettings.data.extlink.extAdditionalTelClasses);
369      } else if (drupalSettings.data.extlink.extAdditionalLinkClasses) {
370        // Is regular external link:
371        linksToProcess[i].classList.add(drupalSettings.data.extlink.extAdditionalLinkClasses);
372      }
373
374      // Add data-extlink attribute.
375      linksToProcess[i].setAttribute('data-extlink', '');
376
377      // Add icon.
378      if (iconPlacement) {
379        let link = linksToProcess[i];
380
381        // Prevent appended icons from wrapping lines.
382        if (drupalSettings.data.extlink.extPreventOrphan && iconPlacement === 'append') {
383          // Find the last word in the link.
384          let lastTextNode = link.lastChild;
385          let trailingWhitespace = null;
386          let parentNode = link;
387          while (lastTextNode) {
388            if (lastTextNode.lastChild) {
389              // Last node was not text; step down into child node.
390              parentNode = lastTextNode;
391              lastTextNode = lastTextNode.lastChild;
392            } else if (lastTextNode.nodeName === '#text' && parentNode.lastElementChild && lastTextNode.textContent.trim().length === 0) {
393              // Last node was text, but it was whitespace. Step back into previous node.
394              trailingWhitespace = lastTextNode;
395              parentNode = parentNode.lastElementChild;
396              lastTextNode = parentNode.lastChild;
397            } else {
398              // Last node was null or valid text.
399              break;
400            }
401          }
402          if (lastTextNode && lastTextNode.nodeName === '#text' && lastTextNode.textContent.length > 0) {
403            const lastText = lastTextNode.textContent;
404            const lastWordRegex = new RegExp(/\S+\s*$/, 'g');
405            const lastWord = lastText.match(lastWordRegex);
406            if (lastWord !== null) {
407              // Wrap the last word in a span.
408              const breakPreventer = document.createElement('span');
409              breakPreventer.classList.add('extlink-nobreak');
410              breakPreventer.textContent = lastWord[0];
411              if (trailingWhitespace) {
412                trailingWhitespace.textContent = '';
413                breakPreventer.append(trailingWhitespace.textContent);
414              }
415              lastTextNode.textContent = lastText.substring(0, lastText.length - lastWord[0].length);
416              lastTextNode.parentNode.append(breakPreventer);
417              // Insert the icon into the span rather than the link.
418              link = breakPreventer;
419            }
420          }
421        }
422
423        // Create an icon element.
424        let iconElement;
425        if (drupalSettings.data.extlink.extUseFontAwesome) {
426          iconElement = document.createElement('span');
427          iconElement.setAttribute('class', `fa-${className} extlink`);
428          if (className === drupalSettings.data.extlink.mailtoClass) {
429            if (drupalSettings.data.extlink.mailtoLabel) {
430              link.ariaLabel = drupalSettings.data.extlink.mailtoLabel;
431            }
432            iconElement.innerHTML = Drupal.theme('extlink_fa_mailto', drupalSettings, iconPlacement);
433          } else if (className === drupalSettings.data.extlink.extClass) {
434            if (drupalSettings.data.extlink.extLabel) {
435              link.ariaLabel = drupalSettings.data.extlink.extLabel;
436            }
437            iconElement.innerHTML = Drupal.theme('extlink_fa_extlink', drupalSettings, iconPlacement);
438          } else if (className === drupalSettings.data.extlink.telClass) {
439            if (drupalSettings.data.extlink.telLabel) {
440              link.ariaLabel = drupalSettings.data.extlink.telLabel;
441            }
442            iconElement.innerHTML = Drupal.theme('extlink_fa_tel', drupalSettings, iconPlacement);
443          }
444        } else {
445          iconElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
446          iconElement.setAttribute('focusable', 'false');
447          iconElement.classList.add(className);
448          iconElement.setAttribute('data-extlink-placement', iconPlacement);
449          if (className === drupalSettings.data.extlink.mailtoClass) {
450            iconElement = Drupal.theme('extlink_mailto', iconElement, drupalSettings);
451          } else if (className === drupalSettings.data.extlink.extClass) {
452            iconElement = Drupal.theme('extlink_extlink', iconElement, drupalSettings);
453          } else if (className === drupalSettings.data.extlink.telClass) {
454            iconElement = Drupal.theme('extlink_tel', iconElement, drupalSettings);
455          }
456        }
457        iconElement.setAttribute('role', 'img');
458        iconElement.setAttribute('aria-hidden', drupalSettings.data.extlink.extHideIcons);
459        link[iconPlacement](iconElement);
460      }
461    }
462  };
463
464  /**
465   * Theme function for a Font Awesome mailto icon.
466   *
467   * @param {object} drupalSettings
468   *   Settings object used to construct the markup.
469   * @param {string} iconPlacement
470   *   The class to apply to a link.
471   *
472   * @return {string}
473   *   HTML string of the Font AweSome mailto link icon.
474   */
475  Drupal.theme.extlink_fa_mailto = function (drupalSettings, iconPlacement) {
476    return `<span class="${drupalSettings.data.extlink.extFaMailtoClasse
476s}" data-extlink-placement="${iconPlacement}"></span>`;
477  };
478
479  /**
480   * Theme function for a Font Awesome external link icon.
481   *
482   * @param {object} drupalSettings
483   *   Settings object used to construct the markup.
484   * @param {string} iconPlacement
485   *   The class to apply to a link.
486   *
487   * @return {string}
488   *   HTML string of the Font AweSome external link icon.
489   */
490  Drupal.theme.extlink_fa_extlink = function (drupalSettings, iconPlacement) {
491    return `<span class="${drupalSettings.data.extlink.extFaLinkClasses}" data-extlink-placement="${iconPlacement}"></span>`;
492  };
493
494  /**
495   * Theme function for a Font Awesome telephone icon.
496   *
497   * @param {object} drupalSettings
498   *   Settings object used to construct the markup.
499   * @param {string} iconPlacement
500   *   The class to apply to a link.
501   *
502   * @return {string}
503   *   HTML string of the Font AweSome telephone icon.
504   */
505  Drupal.theme.extlink_fa_tel = function (drupalSettings, iconPlacement) {
506    return `<span class="${drupalSettings.data.extlink.extFaLinkClasses}" data-extlink-placement="${iconPlacement}"></span>`;
507  };
508
509  /**
510   * Theme function for a mailto icon.
511   *
512   * @param {object} iconElement
513   *   The current iconElement being altered.
514   * @param {object} drupalSettings
515   *   Settings object used to construct the markup.
516   *
517   * @return {object}
518   *   The altered iconElement.
519   */
520  Drupal.theme.extlink_mailto = function (iconElement, drupalSettings) {
521    iconElement.setAttribute('aria-label', drupalSettings.data.extlink.mailtoLabel);
522    iconElement.setAttribute('viewBox', '0 10 70 20');
523    iconElement.innerHTML = `<title>${drupalSettings.data.extlink.mailtoLabel}</title><path d="M56 14H8c-1.1 0-2 0.9-2 2v32c0 1.1 0.9 2 2 2h48c1.1 0 2-0.9 2-2V16C58 14.9 57.1 14 56 14zM50.5 18L32 33.4 13.5 18H50.5zM10 46V20.3l20.7 17.3C31.1 37.8 31.5 38 32 38s0.9-0.2 1.3-0.5L54 20.3V46H10z"/>`;
524    return iconElement;
525  };
526
527  /**
528   * Theme function for an external link icon.
529   *
530   * @param {object} iconElement
531   *   The current iconElement being altered.
532   * @param {object} drupalSettings
533   *   Settings object used to construct the markup.
534   *
535   * @return {object}
536   *   The altered iconElement.
537   */
538  Drupal.theme.extlink_extlink = function (iconElement, drupalSettings) {
539    iconElement.setAttribute('aria-label', drupalSettings.data.extlink.extLabel);
540    iconElement.setAttribute('viewBox', '0 0 80 40');
541    iconElement.innerHTML = `<title>${drupalSettings.data.extlink.extLabel}</title><path d="M48 26c-1.1 0-2 0.9-2 2v26H10V18h26c1.1 0 2-0.9 2-2s-0.9-2-2-2H8c-1.1 0-2 0.9-2 2v40c0 1.1 0.9 2 2 2h40c1.1 0 2-0.9 2-2V28C50 26.9 49.1 26 48 26z"/><path d="M56 6H44c-1.1 0-2 0.9-2 2s0.9 2 2 2h7.2L30.6 30.6c-0.8 0.8-0.8 2 0 2.8C31 33.8 31.5 34 32 34s1-0.2 1.4-0.6L54 12.8V20c0 1.1 0.9 2 2 2s2-0.9 2-2V8C58 6.9 57.1 6 56 6z"/>`;
542    return iconElement;
543  };
544
545  /**
546   * Theme function for an external telephone icon.
547   *
548   * @param {object} iconElement
549   *   The current iconElement being altered.
550   * @param {object} drupalSettings
551   *   Settings object used to construct the markup.
552   *
553   * @return {object}
554   *   The altered iconElement.
555   */
556  Drupal.theme.extlink_tel = function (iconElement, drupalSettings) {
557    iconElement.setAttribute('aria-label', drupalSettings.data.extlink.telLabel);
558    iconElement.setAttribute('viewBox', '0 0 181.352 181.352');
559    iconElement.innerHTML = `<title>${drupalSettings.data.extlink.telLabel}</title><path xmlns="http://www.w3.org/2000/svg" d="M169.393,167.37l-14.919,9.848c-9.604,6.614-50.531,14.049-106.211-53.404C-5.415,58.873,9.934,22.86,17.134,14.555L29.523,1.678c2.921-2.491,7.328-2.198,9.839,0.811l32.583,38.543l0.02,0.02c2.384,2.824,2.306,7.22-0.83,9.868v0.029l-14.44,10.415c-5.716,5.667-0.733,14.587,5.11,23.204l27.786,32.808c12.926,12.477,20.009,18.241,26.194,14.118l12.008-13.395c2.941-2.472,7.328-2.169,9.839,0.821l32.603,38.543v0.02C172.607,160.316,172.519,164.703,169.393,167.37z"/>`;
560    return iconElement;
561  };
562
563  Drupal.behaviors.extlink = Drupal.behaviors.extlink || {};
564  Drupal.behaviors.extlink.attach = (context, drupalSettings) => {
565    // Backwards compatibility, for the benefit of modules overriding extlink
566    // functionality by defining an "extlinkAttach" global function.
567    if (typeof extlinkAttach === 'function') {
568      // eslint-disable-next-line no-undef
569      extlinkAttach(context);
570    } else {
571      Drupal.extlink.attach(context, drupalSettings);
572    }
573  };
574})(Drupal, drupalSettings);

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.