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 // Allowed domains. 37 let allowedDomains = false; 38 if (drupalSettings.data.extlink.allowedDomains) { 39 allowedDomains = []; 40 for (let i = 0; i < drupalSettings.data.extlink.allowedDomains.length; i++) { 41 allowedDomains.push(new RegExp(`^https?:\\/\\/${drupalSettings.data.extlink.allowedDomains[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 && allowedDomains) { 112 for (let i = 0; i < allowedDomains.length; i++) { 113 if (allowedDomains[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 let newTitle = ''; 223 224 if (drupalSettings.data.extlink.extTargetAppendNewWindowDisplay) { 225 newTitle = drupalSettings.data.extlink.extTargetAppendNewWindowLabel; 226 } 227 // Determine new title based on drupalSettings extTarget configuration. 228 if (oldTitle !== null) { 229 if (Drupal.extlink.hasNewWindowText(oldTitle)) { 230 return; 231 } 232 newTitle = Drupal.extlink.combineLabels(oldTitle, newTitle); 233 } 234 if (newTitle) { 235 externalLinks[i].setAttribute('title', newTitle); 236 } 237 }); 238 } 239 240 if (drupalSettings.data.extlink.extNoreferrer) { 241 externalLinks.forEach((link, i) => { 242 // Don't add 'noreferrer' if exclude noreferrer option is set and the 243 // link matches the entered pattern to exclude 'noreferrer' tag. 244 if (drupalSettings.data.extlink.extExcludeNoreferrer && extExcludeNoreferrer.test(link.getAttribute('href'))) { 245 return; 246 } 247 248 const val = link.getAttribute('rel'); 249 // When the link does not have a rel attribute set it to 'noreferrer'. 250 if (val === null || typeof val === 'undefined') { 251 externalLinks[i].setAttribute('rel', 'noreferrer'); 252 return; 253 } 254 // Add noreferrer. 255 externalLinks[i].setAttribute('rel', `${val} noreferrer`); 256 }); 257 } 258 259 /* eslint:disable:no-empty */ 260 Drupal.extlink = Drupal.extlink || {}; 261 262 // Set up default click function for the external links popup. This should be 263 // overridden by modules wanting to alter the popup. 264 Drupal.extlink.popupClickHandler = 265 Drupal.extlink.popupClickHandler || 266 (() => { 267 if (drupalSettings.data.extlink.extAlert) { 268 // eslint-disable-next-line no-restricted-globals 269 return confirm(drupalSettings.data.extlink.extAlertText); 270 } 271 }); 272 273 const _that = this; 274 Drupal.extlink.handleClick = function (event) { 275 const shouldNavigate = Drupal.extlink.popupClickHandler.call(_that, event); 276 if (typeof shouldNavigate !== 'undefined' && !shouldNavigate) { 277 // Prevent navigation if the user clicks "Cancel". 278 event.preventDefault(); 279 } 280 }; 281 282 externalLinks.forEach((val, i) => { 283 externalLinks[i].removeEventListener('click', Drupal.extlink.handleClick); 284 externalLinks[i].addEventListener('click', Drupal.extlink.handleClick); 285 }); 286 }; 287 288 /** 289 * Check if the label already has 'new window' text. 290 * 291 * 292 * @returns boolean 293 * @param label 294 */ 295 Drupal.extlink.hasNewWindowText = function (label) { 296 return label.toLowerCase().indexOf(Drupal.t('new window')) !== -1; 297 }; 298 299 /** 300 * Combine two labels. 301 * 302 * Combine labels in a readable manner, taking into account if the label 303 * uses parenthesis or not. For examples, 304 * 305 * 1. "A" + "B" => "A, B" 306 * 2. "A" + "(B)" => "A (B)" 307 * 3. "(A)" + "B" => "B (A)" 308 * 4. "(A)" + "(B)" => "(A, B)" 309 * 310 * 311 * @returns string 312 * @param labelA 313 * @param labelB 314 */ 315 Drupal.extlink.combineLabels = function (labelA, labelB) { 316 labelA = labelA || ''; 317 labelB = labelB || ''; 318 319 // If either label is empty, return the other without adding a comma 320 if (!labelA.trim()) return labelB; 321 if (!labelB.trim()) return labelA; 322 323 const labelANoParens = labelA.trim().replace('(', '').replace(')', ''); 324 const labelBNoParens = labelB.trim().replace('(', '').replace(')', ''); 325 if (labelA === labelANoParens) { 326 if (labelB === labelBNoParens) { 327 // This is Example 1 above: "A" + "B" => "A, B" 328 return `${labelA}, ${labelB}`; 329 } 330 331 // This is Example 2 above: "A" + "(B)" => "A (B)" 332 return `${labelA} ${labelB}`; 333 } 334 335 if (labelB === labelBNoParens) { 336 // This is Example 3 above: "(A)" + "B" => "B (A)" 337 return `${labelB} ${labelA}`; 338 } 339 340 // This is Example 4 above: "(A)" + "(B)" => "(A, B)" 341 return `(${labelANoParens}, ${labelBNoParens})`; 342 }; 343 344 /** 345 * Apply a class and a trailing <span> to all links not containing images. 346 * 347 * @param {object[]} links 348 * An array of DOM elements representing the links. 349 * @param {string} className 350 * The class to apply to the links. 351 * @param {string} iconPlacement 352 * 'append' or 'prepend' the icon to the link. 353 */ 354 Drupal.extlink.applyClassAndSpan = (links, className, iconPlacement) => { 355 let linksToProcess; 356 if (drupalSettings.data.extlink.extImgClass) { 357 linksToProcess = links; 358 } else { 359 // Only text links. 360 linksToProcess = links.filter((link) => { 361 return link.querySelector('img, svg') === null; 362 }); 363 } 364 365 for (let i = 0; i < linksToProcess.length; i++) { 366 if (className !== '0') { 367 linksToProcess[i].classList.add(className); 368 } 369 370 // Additional classes: 371 if (className === drupalSettings.data.extlink.mailtoClass && drupalSettings.data.extlink.extAdditionalMailtoClasse
371s) { 372 // Is mail link: 373 linksToProcess[i].classList.add(drupalSettings.data.extlink.extAdditionalMailtoClasses); 374 } else if (className === drupalSettings.data.extlink.telClass && drupalSettings.data.extlink.extAdditionalTelClasses) { 375 // Is regular external link: 376 linksToProcess[i].classList.add(drupalSettings.data.extlink.extAdditionalTelClasses); 377 } else if (drupalSettings.data.extlink.extAdditionalLinkClasses) { 378 // Is regular external link: 379 linksToProcess[i].classList.add(drupalSettings.data.extlink.extAdditionalLinkClasses); 380 } 381 382 // Add data-extlink attribute. 383 linksToProcess[i].setAttribute('data-extlink', ''); 384 385 // Add icon. 386 if (iconPlacement) { 387 let link = linksToProcess[i]; 388 389 // Prevent appended icons from wrapping lines. 390 if (drupalSettings.data.extlink.extPreventOrphan && iconPlacement === 'append') { 391 // Find the last word in the link. 392 let lastTextNode = link.lastChild; 393 let trailingWhitespace = null; 394 let parentNode = link; 395 while (lastTextNode) { 396 if (lastTextNode.lastChild) { 397 // Last node was not text; step down into child node. 398 parentNode = lastTextNode; 399 lastTextNode = lastTextNode.lastChild; 400 } else if (lastTextNode.nodeName === '#text' && parentNode.lastElementChild && lastTextNode.textContent.trim().length === 0) { 401 // Last node was text, but it was whitespace. Step back into previous node. 402 trailingWhitespace = lastTextNode; 403 parentNode = parentNode.lastElementChild; 404 lastTextNode = parentNode.lastChild; 405 } else { 406 // Last node was null or valid text. 407 break; 408 } 409 } 410 if (lastTextNode && lastTextNode.nodeName === '#text' && lastTextNode.textContent.length > 0) { 411 const lastText = lastTextNode.textContent; 412 const lastWordRegex = new RegExp(/\S+\s*$/, 'g'); 413 const lastWord = lastText.match(lastWordRegex); 414 if (lastWord !== null) { 415 // Wrap the last word in a span. 416 const breakPreventer = document.createElement('span'); 417 breakPreventer.classList.add('extlink-nobreak'); 418 breakPreventer.textContent = lastWord[0]; 419 if (trailingWhitespace) { 420 trailingWhitespace.textContent = ''; 421 breakPreventer.append(trailingWhitespace.textContent); 422 } 423 lastTextNode.textContent = lastText.substring(0, lastText.length - lastWord[0].length); 424 lastTextNode.parentNode.append(breakPreventer); 425 // Insert the icon into the span rather than the link. 426 link = breakPreventer; 427 } 428 } 429 } 430 431 // Create an icon element. 432 let iconElement; 433 if (drupalSettings.data.extlink.extUseFontAwesome) { 434 iconElement = document.createElement('span'); 435 iconElement.setAttribute('class', `fa-${className} extlink`); 436 if (className === drupalSettings.data.extlink.mailtoClass) { 437 if (drupalSettings.data.extlink.mailtoLabel) { 438 link.ariaLabel = drupalSettings.data.extlink.mailtoLabel; 439 } 440 iconElement.innerHTML = Drupal.theme('extlink_fa_mailto', drupalSettings, iconPlacement); 441 } else if (className === drupalSettings.data.extlink.extClass) { 442 if (drupalSettings.data.extlink.extLabel) { 443 link.ariaLabel = drupalSettings.data.extlink.extLabel; 444 } 445 iconElement.innerHTML = Drupal.theme('extlink_fa_extlink', drupalSettings, iconPlacement); 446 } else if (className === drupalSettings.data.extlink.telClass) { 447 if (drupalSettings.data.extlink.telLabel) { 448 link.ariaLabel = drupalSettings.data.extlink.telLabel; 449 } 450 iconElement.innerHTML = Drupal.theme('extlink_fa_tel', drupalSettings, iconPlacement); 451 } 452 } else { 453 iconElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 454 iconElement.setAttribute('focusable', 'false'); 455 iconElement.setAttribute('width', '1em'); 456 iconElement.setAttribute('height', '1em'); 457 iconElement.classList.add(className); 458 iconElement.setAttribute('data-extlink-placement', iconPlacement); 459 if (className === drupalSettings.data.extlink.mailtoClass) { 460 iconElement = Drupal.theme('extlink_mailto', iconElement, drupalSettings); 461 } else if (className === drupalSettings.data.extlink.extClass) { 462 iconElement = Drupal.theme('extlink_extlink', iconElement, drupalSettings); 463 } else if (className === drupalSettings.data.extlink.telClass) { 464 iconElement = Drupal.theme('extlink_tel', iconElement, drupalSettings); 465 } 466 } 467 iconElement.setAttribute('role', 'img'); 468 iconElement.setAttribute('aria-hidden', drupalSettings.data.extlink.extHideIcons); 469 link[iconPlacement](iconElement); 470 } 471 } 472 }; 473 474 /** 475 * Theme function for a Font Awesome mailto icon. 476 * 477 * @param {object} drupalSettings 478 * Settings object used to construct the markup. 479 * @param {string} iconPlacement 480 * The class to apply to a link. 481 * 482 * @return {string} 483 * HTML string of the Font AweSome mailto link icon. 484 */ 485 Drupal.theme.extlink_fa_mailto = function (drupalSettings, iconPlacement) { 486 return `<span class="${drupalSettings.data.extlink.extFaMailtoClasse
486s}" data-extlink-placement="${iconPlacement}"></span>`; 487 }; 488 489 /** 490 * Theme function for a Font Awesome external link icon. 491 * 492 * @param {object} drupalSettings 493 * Settings object used to construct the markup. 494 * @param {string} iconPlacement 495 * The class to apply to a link. 496 * 497 * @return {string} 498 * HTML string of the Font AweSome external link icon. 499 */ 500 Drupal.theme.extlink_fa_extlink = function (drupalSettings, iconPlacement) { 501 return `<span class="${drupalSettings.data.extlink.extFaLinkClasses}" data-extlink-placement="${iconPlacement}"></span>`; 502 }; 503 504 /** 505 * Theme function for a Font Awesome telephone icon. 506 * 507 * @param {object} drupalSettings 508 * Settings object used to construct the markup. 509 * @param {string} iconPlacement 510 * The class to apply to a link. 511 * 512 * @return {string} 513 * HTML string of the Font AweSome telephone icon. 514 */ 515 Drupal.theme.extlink_fa_tel = function (drupalSettings, iconPlacement) { 516 return `<span class="${drupalSettings.data.extlink.extFaTelClasses}" data-extlink-placement="${iconPlacement}"></span>`; 517 }; 518 519 /** 520 * Theme function for a mailto icon. 521 * 522 * @param {object} iconElement 523 * The current iconElement being altered. 524 * @param {object} drupalSettings 525 * Settings object used to construct the markup. 526 * 527 * @return {object} 528 * The altered iconElement. 529 */ 530 Drupal.theme.extlink_mailto = function (iconElement, drupalSettings) { 531 iconElement.setAttribute('aria-label', drupalSettings.data.extlink.mailtoLabel); 532 iconElement.setAttribute('viewBox', '0 10 70 20'); 533 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"/>`; 534 return iconElement; 535 }; 536 537 /** 538 * Theme function for an external link icon. 539 * 540 * @param {object} iconElement 541 * The current iconElement being altered. 542 * @param {object} drupalSettings 543 * Settings object used to construct the markup. 544 * 545 * @return {object} 546 * The altered iconElement. 547 */ 548 Drupal.theme.extlink_extlink = function (iconElement, drupalSettings) { 549 iconElement.setAttribute('aria-label', drupalSettings.data.extlink.extLabel); 550 iconElement.setAttribute('viewBox', '0 0 80 40'); 551 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"/>`; 552 return iconElement; 553 }; 554 555 /** 556 * Theme function for an external telephone icon. 557 * 558 * @param {object} iconElement 559 * The current iconElement being altered. 560 * @param {object} drupalSettings 561 * Settings object used to construct the markup. 562 * 563 * @return {object} 564 * The altered iconElement. 565 */ 566 Drupal.theme.extlink_tel = function (iconElement, drupalSettings) { 567 iconElement.setAttribute('aria-label', drupalSettings.data.extlink.telLabel); 568 iconElement.setAttribute('viewBox', '0 0 181.352 181.352'); 569 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"/>`; 570 return iconElement; 571 }; 572 573 Drupal.behaviors.extlink = Drupal.behaviors.extlink || {}; 574 Drupal.behaviors.extlink.attach = (context, drupalSettings) => { 575 // Backwards compatibility, for the benefit of modules overriding extlink 576 // functionality by defining an "extlinkAttach" global function. 577 if (typeof extlinkAttach === 'function') { 578 // eslint-disable-next-line no-undef 579 extlinkAttach(context); 580 } else { 581 Drupal.extlink.attach(context, drupalSettings); 582 } 583 }; 584})(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.