1(function (Drupal, drupalSettings) { 2 'use strict'; 3 4 function escapeRegex(s) { 5 return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 6 } 7 function normHost(h) { 8 return String(h || '').toLowerCase().replace(/^www\./, ''); 9 } 10 // Dominio âapex/registrableâ (cubre .com, .net, .es, .com.es, .co.uk, etc.) 11 function apex(hostname) { 12 const h = normHost(hostname); 13 const parts = h.split('.'); 14 if (parts.length <= 2) return h; 15 const tld = parts[parts.length - 1]; 16 const sld = parts[parts.length - 2]; 17 const knownSecondLevel = new Set(['co','com','org','net','gov','edu','gob','nom']); 18 if (tld.length === 2 && knownSecondLevel.has(sld) && parts.length >= 3) { 19 return parts.slice(-3).join('.'); 20 } 21 return parts.slice(-2).join('.'); 22 } 23 function isOwnOrSubdomain(linkHost, currentHost) { 24 // mismo apex => consideramos mismo site (incluye subdominios) 25 return apex(linkHost) === apex(currentHost); 26 } 27 function removeToken(rel, token) { 28 return rel.split(/\s+/).filter(Boolean).filter(t => t !== token).join(' '); 29 } 30 31 Drupal.behaviors.extlinkWhitelistNewTab = { 32 attach: function (context) { 33 const data = drupalSettings && drupalSettings.data && drupalSettings.data.extlink; 34 if (!data || !Array.isArray(data.whitelistedDomains) || data.whitelistedDomains.length === 0) return; 35 36 const currentHost = window.location.hostname; 37 38 // Tests para dominios whitelisted 39 const tests = data.whitelistedDomains 40 .map(d => (d || '').trim()) 41 .filter(Boolean) 42 .map(d => new RegExp('^https?:\\/\\/' + escapeRegex(d) + '(?:[\\/:?#]|$)', 'i')); 43 44 const links = context.querySelectorAll('a[href^="http"]:not([data-whitelist-tab-processed])'); 45 46 links.forEach(function (a) { 47 const href = a.getAttribute('href') || ''; 48 let linkHost = ''; 49 try { linkHost = new URL(href, window.location.href).hostname; } catch (e) {} 50 51 const own = isOwnOrSubdomain(linkHost, currentHost); 52 const isWhitelisted = tests.some(rx => rx.test(href)); 53 54 // 1) ENLACES PROPIOS (dominio + subdominios): nunca en nueva pestaña 55 if (own) { 56 if (a.target === '_blank') { 57 a.removeAttribute('target'); // corregimos 58 const rel = a.getAttribute('rel') || ''; 59 const cleaned = removeToken(rel, 'noopener'); // quita solo noopener 60 if (cleaned) a.setAttribute('rel', cleaned); else a.removeAttribute('rel'); 61 } 62 a.setAttribute('data-whitelist-tab-processed', ''); 63 return; 64 } 65 66 // 2) ENLACES WHITELIST EXTERNOS: abrir en nueva pestaña + seguridad 67 if (isWhitelisted) { 68 if (!(data.extTargetNoOverride && a.hasAttribute('target'))) { 69 a.setAttribute('target', '_blank'); 70 } 71 const relParts = (a.getAttribute('rel') || '').split(/\s+/).filter(Boolean); 72 if (!relParts.includes('noopener')) relParts.push('noopener'); 73 a.setAttribute('rel', relParts.join(' ')); 74 } 75 76 a.setAttribute('data-whitelist-tab-processed', ''); 77 }); 78 } 79 }; 80})(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.