1/** 2 * @param {String} selector 3 * @param {String} text 4 */ 5function setPlaceholder(selector, text) { 6 /** @type {HTMLInputElement | null} */ 7 const elem = document.querySelector(selector); 8 if (elem) { 9 elem.placeholder = text; 10 } 11} 12 13function initJobsiteIframe() { 14 /** @type {HTMLIFrameElement | null} */ 15 const iframe = document.querySelector('#jobs-iframe iframe'); 16 if (iframe) { 17 window.addEventListener('message', function (event) { 18 if (event.data.height && event.origin.match(/https:\/\/(staging\.)?jobs-at\.pulsepublishing\.de(:[0-9]+)?/)) { 19 // Set the iframe height to the received value 20 iframe.style.height = `${event.data.height}px`; 21 } 22 }); 23 } 24} 25 26/** 27 * @param {Node} node 28 * @param {Number} count Number of already processed words 29 */ 30function delinkFirst50Words(node, count) { 31 /** @param {String} text */ 32 function wordCount(text) { 33 // Strip punctuation marks at the beginning to handle things like `[â¦]</strong>, foo` 34 text = text.replace(/^[,.!?]+/, '').trim(); 35 return text ? text.split(/\s+/).length : 0; 36 } 37 38 for (const child of node.childNodes) { 39 if (count > 50) break; 40 switch (child.nodeName) { 41 case '#text': 42 count += wordCount(child.nodeValue); 43 break; 44 case 'A': 45 if (child.classList.contains('sk2l-generated')) { 46 count += wordCount(child.firstChild.nodeValue); 47 child.replaceWith(child.firstChild); 48 } else { 49 count += wordCount(child.textContent); 50 } 51 break; 52 default: 53 count = delinkFirst50Words(child, count); 54 break; 55 } 56 } 57 return count; 58} 59 60/** 61 * Delinking and adding of affiliate icons for async loaded posts. 62 * 63 * @param {MutationRecord[]} mutations 64 */ 65function moCallback(mutations) { 66 for (const mutation of mutations) { 67 for (const node of mutation.addedNodes) { 68 // Check if it's an async loaded post 69 if (node.nodeName === 'DIV' && node.classList.contains('js-async-posts-wrap')) { 70 // undo some links created by simple-keyword-to-link plugin 71 const intro = node.querySelector(".o-section-intro > div.c-article-body > div.c-article-copy"); 72 if (intro) delinkFirst50Words(intro, 0); 73 // Add affiliate icons 74 addAffiToExternalLinks(node); 75 } 76 } 77 } 78} 79 80function commonInit() { 81 setPlaceholder("[name='user_login']", "*Bitte hier die Marke eintragen..."); 82 setPlaceholder("[name='first_name']", "*Vorname..."); 83 setPlaceholder("[name='last_name']", "*Nachname..."); 84 setPlaceholder("[name='user_email']", "*E-Mail..."); 85 setPlaceholder("[name='pass1']", "*Passwort..."); 86 setPlaceholder("[name='pass2']", "*Bitte Passwort bestätigen..."); 87 //automatic height adaptation 88 initJobsiteIframe(); 89 // undo some links created by simple-keyword-to-link plugin 90 const intro = document.querySelector(".o-section-intro > div.c-article-body > div.c-article-copy"); 91 if (intro) delinkFirst50Words(intro, 0); 92 // Delinking and adding of affiliate icons for async loaded posts 93 const mo = new MutationObserver(moCallback); 94 mo.observe(document.body, {childList: true}); 95} 96 97if (document.readyState !== 'loading') { 98 commonInit(); 99} else { 100 document.addEventListener('DOMContentLoaded', commonInit); 101} 102 103// Event Calender add claas if just primary layout 104document.addEventListener('DOMContentLoaded', function() { 105 // Select the parent element 106 const parentElement = document.querySelector('.tribe-events-event-meta.primary'); 107 108 // Check if the child element with the specified class exists 109 if (parentElement && parentElement.querySelector('.tribe-events-meta-group-gmap')) { 110 // Add the class "only" to the parent element 111 parentElement.classList.add('only'); 112 } 113 114 // Slider Kachel FIX 115 setTimeout(function() { 116 /** @type {NodeListOf<HTMLElement>} */ 117 const tiles = document.querySelectorAll('.sa_hover_container'); 118 let maxHeight = 0; 119 120 // Höchste Kachel ermitteln
121 tiles.forEach(function(tile) { 122 let height = tile.offsetHeight; 123 if (height > maxHeight) { 124 maxHeight = height; 125 } 126 }); 127 128 // Alle Kacheln auf die gleiche Höhe setzen 129 tiles.forEach(function(tile) { 130 tile.style.height = maxHeight + 'px'; 131 }); 132 }, 500); // 0.5 Sekunden Delay 133 134 // Remove Twitter link from social navigation 135 const socialNav = document.querySelector('.c-navbar-primary__social'); 136 if (socialNav) { 137 const twitterLink = socialNav.querySelector("a[href*='twitter.com']"); 138 if (twitterLink) { 139 twitterLink.remove(); // Remove the Twitter link 140 } 141 } 142}); 143 144// AFFI Basket 145const SHEET_ID = "1dmUzDS3SkMdIsSvlfW5JW--LNE_bGqcRiizqM7cMYts"; 146const API_KEY = "AIzaSyCzOYOl_j8WpoHOZENILKUQFaDEXaOgxqY"; 147const RANGE_COMBINED = "A2:B"; // Spalten mit ausgeschlossenen Domains (A) und Präfixen (B) 148/** @type {string[]} */ 149let noAffiIconDomains = []; // Hier wird die aktualisierte Liste gespeichert 150/** @type {string[]} */ 151let noAffiIconPrefixes = []; // Hier werden die Präfixe gespeichert 152// Fallback Domains, für Probleme mit der API 153const fallbackDomains = [ 154 "blonde.de", "dirtmountainbike.de", "explore-magazine.de", "freedombmx.de", "golfpunk.de", "nylonmag.de", 155 "pulsepublishing.de", "roadcycling.de", "skateboardmsm.de", "skiing.de", "snowboardermbm.de", 156 "surfersmag.de", 157 "facebook.com", "google.com", "google.de", "instagram.com", "vimeo.com", "wikipedia.org", "youtube.com", 158 "open.spotify.com", 159]; 160 161/** 162 * Holt die ausgeschlossenen Domains und Präfixe aus Google Sheets. 163 */ 164async function fetchDomainsAndPrefixes() { 165 const cachekey = 'excludedDomainsAndPrefixes'; 166 const cachedData = JSON.parse(localStorage.getItem(cachekey)); 167 if (cachedData && cachedData.expireTime > Date.now()) { 168 noAffiIconDomains = cachedData.domains; 169 noAffiIconPrefixes = cachedData.prefixes; 170 console.log("Using cached data"); 171 return; 172 } 173 174 const url = `https://sheets.googleapis.com/v4/spreadsheets/${SHEET_ID}/values/${RANGE_COMBINED}?key=${API_KEY}&majorDimension=COLUMNS`; 175 let response = await fetch(url); 176 let data = await response.json(); 177 // retry once on error, except for permission denied or quota exceeded 178 if ((!response.ok && ![403, 429].includes(response.status)) || !data.values) { 179 await new Promise(resolve => setTimeout(resolve, 500)); 180 response = await fetch(url); 181 data = await response.json(); 182 } 183 let expireTime; 184 if (response.ok && data.values) { 185 noAffiIconDomains = data.values[0].filter(e => e !== ''); 186 // If the second column is empty in the sheet, it will be missing in the response 187 noAffiIconPrefixes = data.values.length > 1 ? data.values[1].filter(e => e !== '') : []; 188 expireTime = Date.now() + 60*60 * 1000; 189 console.log("Updated domains and prefixes"); 190 } else { 191 if (data.error) { 192 console.error("Error fetching domains and prefixes:", data.error.message, {code: data.error.code, status: data.error.status}); 193 } else { 194 console.error("Error fetching domains and prefixes: No data found in sheet"); 195 } 196 if (cachedData && cachedData.domains.length + cachedData.prefixes.length > 0) { 197 // If we have cached domains or prefixes, use them 198 console.warn("Using stale excluded domains and prefixes"); 199 noAffiIconDomains = cachedData.domains; 200 noAffiIconPrefixes = cachedData.prefixes; 201 expireTime = Date.now() + 10*60 * 1000; // extend lifetime 202 } else { 203 // To prevent immediate retry, cache fallback data 204 console.warn("Using fallback excluded domains"); 205 noAffiIconDomains = fallbackDomains; 206 expireTime = Date.now() + 30 * 1000; 207 } 208 } 209 localStorage.setItem(cachekey, JSON.stringify({ domains: noAffiIconDomains, prefixes: noAffiIconPrefixes, expireTime })); 210} 211 212/** 213 * Fügt AFFi-Icon zu externen Links hinzu. 214 * @param {Element} rootNode 215 */ 216async function addAffiToExternalLinks(rootNode) { 217 await fetchDomainsAndPrefixes(); 218 219 const baseDomain = window.location.hostname.replace('staging.', ''); 220 const links = rootNode.querySelectorAll('a'); 221 222 links.forEach(link => { 223 const hostname = link.hostname; 224 const href = link.href; 225 226 if (!link.closest('h1, h2, h3, h4, h5, h6') && 227 !link.querySelector('img, i') && 228 !link.closest('.c-article-body__aside') && 229 !link.closest('footer') && 230 link.protocol !== 'mailto:' && 231 !hostname.includes(baseDomain) && 232 !noAffiIconDomains.some(domain => hostname.endsWith(domain)) && 233 !noAffiIconPrefixes.some(prefix => href.startsWith(prefix)) 234 ) { 235 link.innerHTML += ' <i class="fa fa-shopping-cart"></i>'; 236 } 237 }); 238} 239document.addEventListener('DOMContentLoaded', () => addAffiToExternalLinks(document.documentElement));
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.