1(function () { 2 'use strict'; 3 4 if (typeof aelSettings === 'undefined') { 5 return; 6 } 7 8 var PROCESSED_ATTR = 'data-ael-processed'; 9 var EXTERNAL_ATTR = 'data-ael-external'; 10 var MODAL_ID = 'ael-external-link-modal'; 11 var pendingUrl = ''; 12 var pendingTarget = ''; 13 var behavior = aelSettings.linkBehavior || 'both'; 14 var showIcon = behavior !== 'modal_only'; 15 var showModal = behavior !== 'icon_only'; 16 var siteHost = normalizeHost(aelSettings.siteHost || window.location.hostname); 17 18 function normalizeHost(host) { 19 if (!host) { 20 return ''; 21 } 22 23 return String(host).toLowerCase().replace(/^www\./, ''); 24 } 25 26 function getLinkHost(href) { 27 try { 28 return normalizeHost(new URL(href, window.location.href).hostname); 29 } catch (error) { 30 return ''; 31 } 32 } 33 34 function isSkippableScheme(href) { 35 return /^(mailto:|tel:|javascript:|#)/i.test(href); 36 } 37 38 function isExternalLink(link) { 39 if (!link || link.tagName !== 'A') { 40 return false; 41 } 42 43 var href = link.getAttribute('href'); 44 45 if (!href || isSkippableScheme(href) || link.hasAttribute('download') || link.closest('[data-ael-ignore]')) { 46 return false; 47 } 48 49 var linkHost = getLinkHost(href); 50 51 return linkHost && linkHost !== siteHost; 52 } 53 54 function applyNewTabAttributes(link) { 55 if (!aelSettings.openInNewTab) { 56 return; 57 } 58 59 link.setAttribute('target', '_blank'); 60 61 var rel = (link.getAttribute('rel') || '').split(/\s+/).filter(Boolean); 62 var relSet = {}; 63 64 rel.forEach(function (item) { 65 relSet[item] = true; 66 }); 67 68 relSet.noopener = true; 69 relSet.noreferrer = true; 70 link.setAttribute('rel', Object.keys(relSet).join(' ')); 71 } 72 73 function createIcon() { 74 var icon = document.createElement('span'); 75 icon.className = 'ael-external-icon'; 76 icon.setAttribute('aria-hidden', 'true'); 77 78 if (aelSettings.iconType === 'emoji') { 79 icon.textContent = ' â'; 80 return icon; 81 } 82 83 icon.innerHTML = 84 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1em" height="1em" fill="currentColor" aria-hidden="true">' + 85 '<path d="M14 3h7v7h-2V6.41l-9.29 9.3-1.42-1.42 9.3-9.29H14V3z"></path>' + 86 '<path d="M5 5h6v2H7v10h10v-4h2v6H5V5z"></path>' + 87 '</svg>'; 88 89 return icon; 90 } 91 92 function markLink(link) { 93 if (link.getAttribute(PROCESSED_ATTR) === '1') { 94 return; 95 } 96 97 link.setAttribute(PROCESSED_ATTR, '1'); 98 99 if (!isExternalLink(link)) { 100 return; 101 } 102 103 link.classList.add('ael-external-link'); 104 105 var existingIcon = link.querySelector('.ael-external-icon'); 106 107 if (showIcon) { 108 if (!existingIcon) { 109 link.appendChild(createIcon()); 110 } 111 } else if (existingIcon) { 112 existingIcon.remove(); 113 } 114 115 if (showModal) { 116 link.setAttribute(EXTERNAL_ATTR, '1'); 117 return; 118 } 119 120 applyNewTabAttributes(link); 121 } 122 123 function processLinks(root) { 124 var scope = root && root.querySelectorAll ? root : document; 125 scope.querySelectorAll('a[href]').forEach(markLink); 126 } 127 128 function ensureModal() { 129 if (!showModal || document.getElementById(MODAL_ID)) { 130 return; 131 } 132 133 var modal = document.createElement('div'); 134 modal.id = MODAL_ID; 135 modal.className = 'ael-modal'; 136 modal.setAttribute('role', 'dialog'); 137 modal.setAttribute('aria-modal', 'true'); 138 modal.setAttribute('aria-labelledby', 'ael-modal-title'); 139 modal.setAttribute('aria-describedby', 'ael-modal-message'); 140 modal.hidden = true; 141 modal.innerHTML = 142 '<div class="ael-modal__backdrop" data-ael-close></div>' + 143 '<div class="ael-modal__panel" role="document">' + 144 '<h2 id="ael-modal-title" class="ael-modal__title"></h2>' + 145 '<p id="ael-modal-message" class="ael-modal__message"></p>' + 146 '<p id="ael-modal-url" class="ael-modal__url" hidden></p>' + 147 '<div class="ael-modal__actions">' + 148 '<button type="button" class="ael-modal__button ael-modal__button--secondary" data-ael-cancel></button>' + 149 '<button type="button" class="ael-modal__button ael-modal__button--primary" data-ael-confirm></button>' + 150 '</div></div>'; 151 152 document.body.appendChild(modal); 153 154 modal.querySelector('[data-ael-close]').addEventListener('click', closeModal); 155 modal.querySelector('[data-ael-cancel]').addEventListener('click', closeModal); 156 modal.querySelector('[data-ael-confirm]').addEventListener('click', confirmNavigation); 157 158 document.addEventListener('keydown', function (event) { 159 if (event.key === 'Escape' && !modal.hidden) { 160 closeModal(); 161 } 162 }); 163 } 164 165 function openModal(url) { 166 ensureModal(); 167 168 var modal = document.getElementById(MODAL_ID); 169 var title = modal.querySelector('.ael-modal__title'); 170 var message = modal.querySelector('.ael-modal__message'); 171 var urlNode = modal.querySelector('.ael-modal__url'); 172 var confirmButton = modal.querySelector('[data-ael-confirm]'); 173 var cancelButton = modal.querySelector('[data-ael-cancel]'); 174 175 title.textContent = aelSettings.modalTitle || 'Externer Link'; 176 message.textContent = aelSettings.modalMessage || ''; 177 confirmButton.textContent = aelSettings.confirmLabel || 'Weiter'; 178 cancelButton.textContent = aelSettings.cancelLabel || 'Abbrechen'; 179 180 if (aelSettings.showUrl) { 181 urlNode.hidden = false;
182 urlNode.textContent = url; 183 } else { 184 urlNode.hidden = true; 185 urlNode.textContent = ''; 186 } 187 188 modal.hidden = false; 189 document.documentElement.classList.add('ael-modal-open'); 190 confirmButton.focus(); 191 } 192 193 function closeModal() { 194 var modal = document.getElementById(MODAL_ID); 195 196 if (!modal) { 197 return; 198 } 199 200 modal.hidden = true; 201 document.documentElement.classList.remove('ael-modal-open'); 202 pendingUrl = ''; 203 pendingTarget = ''; 204 } 205 206 function confirmNavigation() { 207 if (!pendingUrl) { 208 closeModal(); 209 return; 210 } 211 212 var url = pendingUrl; 213 var target = pendingTarget; 214 closeModal(); 215 216 if (target === '_blank') { 217 window.open(url, '_blank', 'noopener,noreferrer'); 218 return; 219 } 220 221 window.location.href = url; 222 } 223 224 function handleClick(event) { 225 if (!showModal) { 226 return; 227 } 228 229 var link = event.target.closest('a[href]'); 230 231 if (!link || link.getAttribute(EXTERNAL_ATTR) !== '1') { 232 return; 233 } 234 235 event.preventDefault(); 236 event.stopPropagation(); 237 238 pendingUrl = link.href; 239 pendingTarget = aelSettings.openInNewTab ? '_blank' : (link.getAttribute('target') || '_self'); 240 openModal(pendingUrl); 241 } 242 243 function initObserver() { 244 if (!window.MutationObserver) { 245 return; 246 } 247 248 new MutationObserver(function (mutations) { 249 mutations.forEach(function (mutation) { 250 if (mutation.type !== 'childList') { 251 return; 252 } 253 254 mutation.addedNodes.forEach(function (node) { 255 if (node.nodeType !== 1) { 256 return; 257 } 258 259 if (node.matches && node.matches('a[href]')) { 260 markLink(node); 261 } 262 263 processLinks(node); 264 }); 265 }); 266 }).observe(document.documentElement, { childList: true, subtree: true }); 267 } 268 269 function init() { 270 processLinks(document); 271 ensureModal(); 272 initObserver(); 273 274 if (showModal) { 275 document.addEventListener('click', handleClick, true); 276 } 277 } 278 279 if (document.readyState === 'loading') { 280 document.addEventListener('DOMContentLoaded', init); 281 } else { 282 init(); 283 } 284})();
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.