PageSourceSearch

https://roadcycling.de/wp-content/themes/roadcyclingde/theme/asset…?t=38f673bf91985017fb4ee52c3170a3bc19018aca

js roadcycling.de collected 2026-10-04 20:22:35 UTC 9,321 bytes, 239 lines download raw bytes

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.