PageSourceSearch

https://itdaily.com/wp-content/themes/itdaily-v2/js/main.js?ver=1790320751

js itdaily.com collected 2026-09-26 10:55:58 UTC 17,318 bytes, 445 lines download raw bytes

1/**
2 * ITdaily V2 - Main scripts
3 *
4 * @package ITdaily_V2
5 */
6
7// Scheme Switcher (dark/light) — toggle only, init is inline in <head>
8window.itdailyToggleScheme = function() {
9    var next = localStorage.getItem('itdaily_theme') === 'light' ? 'dark' : 'light';
10    localStorage.setItem('itdaily_theme', next);
11    console.log('itdaily_theme:', next);
12    document.documentElement.classList.add('scheme-switching');
13    document.documentElement.classList.toggle('bring-the-light', next === 'light');
14    setTimeout(function() {
15        document.documentElement.classList.remove('scheme-switching');
16    }, 400);
17};
18
19// Language Switcher
20(function() {
21    document.addEventListener('click', function(e) {
22        var toggle = e.target.closest('.lang-switcher__toggle');
23        var switcher = e.target.closest('.lang-switcher');
24
25        // Close all dropdowns when clicking outside
26        if (!switcher) {
27            document.querySelectorAll('.lang-switcher__toggle[aria-expanded="true"]').forEach(function(btn) {
28                btn.setAttribute('aria-expanded', 'false');
29            });
30            return;
31        }
32
33        // Toggle dropdown
34        if (toggle) {
35            var expanded = toggle.getAttribute('aria-expanded') === 'true';
36            toggle.setAttribute('aria-expanded', !expanded);
37        }
38    });
39})();
40
41// Hamburger Menu + Mobile Overlay
42document.addEventListener('DOMContentLoaded', function() {
43    var hamburger = document.getElementById('hamburger');
44    var overlay = document.getElementById('mobile-overlay');
45    if (!hamburger) return;
46
47    hamburger.addEventListener('click', function() {
48        var isOpen = hamburger.classList.toggle('active');
49        document.body.classList.toggle('menu-open', isOpen);
50        hamburger.setAttribute('aria-expanded', String(isOpen));
51        if (overlay) {
52            overlay.setAttribute('aria-hidden', String(!isOpen));
53        }
54    });
55});
56
57// Infinite Scroll — Single post
58document.addEventListener('DOMContentLoaded', function() {
59    if (typeof itdaily_single === 'undefined') return;
60
61    var container = document.querySelector('.col-content');
62    if (!container) return;
63
64    var shownIds  = [itdaily_single.post_id];
65    var articleNum = 1;
66    var loading   = false;
67    var noMore    = false;
68    var currentArticleId = String(itdaily_single.post_id);
69
70    // Track scroll direction so we only refresh the sidebar ad on downward scrolls.
71    var lastScrollY = window.scrollY || window.pageYOffset || 0;
72    var scrollingDown = true;
73    window.addEventListener('scroll', function() {
74        var y = window.scrollY || window.pageYOffset || 0;
75        if (y > lastScrollY) scrollingDown = true;
76        else if (y < lastScrollY) scrollingDown = false;
77        lastScrollY = y;
78    }, { passive: true });
79
80    function parseSize(str) {
81        if (!str) return null;
82        return str.split(',').map(function(s) {
83            return s.trim().split('x').map(Number);
84        }).filter(function(a) { return a.length === 2; });
85    }
86
87    function initAd(el) {
88        if (!window.googletag || !el || !el.dataset.adUnit) return;
89        googletag.cmd.push(function() {
90            var isMobile = window.matchMedia('(max-width:767px)').matches;
91            var sizes = parseSize(isMobile && el.dataset.sizeMobile ? el.dataset.sizeMobile : el.dataset.size);
92            if (!sizes) return;
93            var slot = googletag.defineSlot(el.dataset.adUnit, sizes.length === 1 ? sizes[0] : sizes, el.id);
94            if (slot) {
95                slot.addService(googletag.pubads());
96                googletag.display(el.id);
97            }
98        });
99    }
100
101    function loadNext() {
102        if (loading || noMore) return;
103        loading = true;
104        articleNum++;
105
106        var url = itdaily_single.ajax_url
107            + '?action=itdaily_next_article'
108            + '&cat_id=' + itdaily_single.cat_id
109            + '&exclude=' + shownIds.join(',')
110            + '&num=' + articleNum
111            + (itdaily_single.lang ? '&lang=' + itdaily_single.lang : '');
112
113        fetch(url)
114            .then(function(r) { return r.json(); })
115            .then(function(data) {
116                loading = false;
117                if (!data.has_more || !data.html) {
118                    noMore = true;
119                    return;
120                }
121
122                // Insert HTML
123                var wrapper = document.createElement('div');
124                wrapper.innerHTML = data.html;
125                while (wrapper.firstChild) {
126                    container.appendChild(wrapper.firstChild);
127                }
128
129                // Track
130                shownIds.push(data.post_id);
131
132                // Init ad in new content
133                var newAd = document.getElementById('itdaily-single-bottom-' + articleNum);
134                if (newAd) initAd(newAd);
135
136                // Observe new article for URL update + next load trigger
137                observeEntries();
138            })
139            .catch(function() { loading = false; });
140    }
141
142    // Track which article is in viewport → update URL
143    function observeEntries() {
144        var entries = container.querySelectorAll('.entry');
145        var last = entries[entries.length - 1];
146        if (!last) return;
147
148        // Load trigger: fire when 50% of the article has been scrolled past
149        var loadObserver = new IntersectionObserver(function(items) {
150            items.forEach(function(item) {
151                if (item.isIntersecting) {
152                    loadObserver.disconnect();
153                    loadNext();
154                }
155            });
156        }, { threshold: 0 });
157
158        // Create a marker at 50% height of the article
159        var marker = document.createElement('div');
160        marker.style.cssText = 'height:0;overflow:hidden;pointer-events:none';
161        var content = last.querySelector('.entry__content');
162        if (content && content.children.length > 2) {
163            var mid = Math.floor(content.children.length / 2);
164            content.children[mid].before(marker);
165        } else {
166            (last.querySelector('.entry__footer') || last).before(marker);
167        }
168        loadObserver.observe(marker);
169
170        // Fallback: if marker is already visible on page load, trigger immediately
171        var rect = marker.getBoundingClientRect();
172        if (rect.top < window.innerHeight && rect.bottom >= 0) {
173            loadObserver.disconnect();
174            loadNext();
175        }
176
177        // URL update: when article top reaches top 10% of viewport
178        entries.forEach(function(entry) {
179            if (entry.dataset.observed) return;
180            entry.dataset.observed = '1';
181
182            var urlObserver = new IntersectionObserver(function(items) {
183                items.forEach(function(item) {
184                    if (item.isIntersecting) {
185                        var link = item.target.dataset.permalink;
186                        var name = item.target.querySelector('.entry__title');
187                        var articleId = item.target.dataset.postId || item.target.id || link || '';
188                        if (link && history.replaceState) {
189                            var prevUrl = location.href;
190                            history.replaceState(null, '', link);
191                            if (name) document.title = name.textContent.trim() + ' - ITdaily';
192
193                            // Track pageview in Plausible when URL changes
194                            if (link !== prevUrl && window.plausible) {
195                                plausible('pageview', { u: link });
196                            }
197                        }
198
199                        // Refresh sticky sidebar ad each time a new article becomes the active one —
200                        // but only on downward scroll, so scrolling back up doesn't burn impressions.
201                        if (articleId && articleId !== currentArticleId) {
202                            var shouldRefresh = scrollingDown;
203                            currentArticleId = articleId;
204                            if (shouldRefresh && typeof window.itdailyRefreshAd === 'function') {
205                                window.itdailyRefreshAd('itdaily-sidebar-bottom');
206                            }
207                        }
208                    }
209                });
210            }
210, { rootMargin: '-10% 0px -90% 0px' });
211            urlObserver.observe(entry);
212        });
213    }
214
215    observeEntries();
216});
217
218// Share buttons: Copy link + Print
219document.addEventListener('click', function(e) {
220    var copyBtn = e.target.closest('.js-copy-link');
221    if (copyBtn) {
222        var url = copyBtn.dataset.url || window.location.href;
223        if (navigator.clipboard && navigator.clipboard.writeText) {
224            navigator.clipboard.writeText(url);
225        } else {
226            var tmp = document.createElement('textarea');
227            tmp.value = url;
228            tmp.style.position = 'fixed';
229            tmp.style.opacity = '0';
230            document.body.appendChild(tmp);
231            tmp.select();
232            document.execCommand('copy');
233            document.body.removeChild(tmp);
234        }
235
236        // Visuele feedback
237        var original = copyBtn.innerHTML;
238        copyBtn.innerHTML = '✓';
239        copyBtn.style.pointerEvents = 'none';
240        setTimeout(function() {
241            copyBtn.innerHTML = original;
242            copyBtn.style.pointerEvents = '';
243        }, 1500);
244        return;
245    }
246
247    var printBtn = e.target.closest('.js-print');
248    if (printBtn) {
249        window.print();
250    }
251});
252
253// Lightbox for wp-block-gallery
254(function() {
255    var overlay, imgEl, counter, images, current;
256
257    function create() {
258        overlay = document.createElement('div');
259        overlay.className = 'lightbox';
260        overlay.innerHTML =
261            '<button class="lightbox__close" aria-label="Sluiten">&times;</button>' +
262            '<button class="lightbox__prev" aria-label="Vorige">&#8249;</button>' +
263            '<button class="lightbox__next" aria-label="Volgende">&#8250;</button>' +
264            '<img class="lightbox__img">' +
265            '<span class="lightbox__counter"></span>';
266        document.body.appendChild(overlay);
267
268        imgEl   = overlay.querySelector('.lightbox__img');
269        counter = overlay.querySelector('.lightbox__counter');
270
271        overlay.querySelector('.lightbox__close').addEventListener('click', close);
272        overlay.querySelector('.lightbox__prev').addEventListener('click', function() { navigate(-1); });
273        overlay.querySelector('.lightbox__next').addEventListener('click', function() { navigate(1); });
274        overlay.addEventListener('click', function(e) {
275            if (e.target === overlay) close();
276        });
277
278        document.addEventListener('keydown', function(e) {
279            if (!overlay.classList.contains('lightbox--open')) return;
280            if (e.key === 'Escape') close();
281            if (e.key === 'ArrowLeft') navigate(-1);
282            if (e.key === 'ArrowRight') navigate(1);
283        });
284    }
285
286    function open(gallery, index) {
287        if (!overlay) create();
288        images  = Array.from(gallery.querySelectorAll('img'));
289        current = index;
290        show();
291        overlay.classList.add('lightbox--open');
292        document.body.style.overflow = 'hidden';
293    }
294
295    function close() {
296        overlay.classList.remove('lightbox--open');
297        document.body.style.overflow = '';
298    }
299
300    function navigate(dir) {
301        current = (current + dir + images.length) % images.length;
302        show();
303    }
304
305    function fullSizeUrl(img) {
306        // 1. Media-file link on the image (WP gallery "Link to: Media file")
307        var link = img.closest('a');
308        if (link && link.href && /\.(jpe?g|png|gif|webp|avif)(\?|$)/i.test(link.href)) {
309            return link.href;
310        }
311        // 2. Explicit data attribute
312        if (img.dataset.fullUrl) return img.dataset.fullUrl;
313        // 3. Strip WordPress crop suffix (-1024x768) and the "big-image threshold"
314        //    -scaled suffix, so the lightbox shows the original upload. The extension
315        //    group also matches Smush's double extension pattern like ".jpg.webp".
316        var ext = '(\\.[a-z0-9]+(?:\\.[a-z0-9]+)?)(\\?|$)';
317        var src = img.currentSrc || img.src;
318        src = src.replace(new RegExp('-\\d+x\\d+' + ext, 'i'), '$1$2');
319        src = src.replace(new RegExp('-scaled' + ext, 'i'), '$1$2');
320        return src;
321    }
322
323    function show() {
324        var img = images[current];
325        imgEl.src = fullSizeUrl(img);
326        counter.textContent = (current + 1) + ' / ' + images.length;
327        overlay.querySelector('.lightbox__prev').style.display = images.length > 1 ? '' : 'none';
328        overlay.querySelector('.lightbox__next').style.display = images.length > 1 ? '' : 'none';
329    }
330
331    // Delegate clicks on gallery images and linked content images
332    document.addEventListener('click', function(e) {
333        // Gallery images
334        var img = e.target.closest('.wp-block-gallery img');
335        if (img) {
336            e.preventDefault();
337            var gallery = img.closest('.wp-block-gallery');
338            var imgs = Array.from(gallery.querySelectorAll('img'));
339            open(gallery, imgs.indexOf(img));
340            return;
341        }
342
343        // Linked images in content (wp-block-image with <a>)
344        var link = e.target.closest('.entry__content .wp-block-image a');
345        if (link && link.querySelector('img')) {
346            e.preventDefault();
347            open(link.closest('.wp-block-image'), 0);
348        }
349    });
350
351    // Touch swipe support
352    var touchStartX = 0;
353    document.addEventListener('touchstart', function(e) {
354        if (overlay && overlay.classList.contains('lightbox--open')) {
355            touchStartX = e.changedTouches[0].screenX;
356        }
357    }, { passive: true });
358    document.addEventListener('touchend', function(e) {
359        if (!overlay || !overlay.classList.contains('lightbox--open')) return;
360        var diff = e.changedTouches[0].screenX - touchStartX;
361        if (Math.abs(diff) > 50) {
362            navigate(diff > 0 ? -1 : 1);
363        }
364    }, { passive: true });
365})();
366
367// Sticky sidebar ad — becomes fixed when scrolled to top of viewport
368document.addEventListener('DOMContentLoaded', function() {
369    var wrapper = document.querySelector('.sidebar__sticky');
370    var ad = document.getElementById('itdaily-sidebar-bottom');
371    if (!wrapper || !ad || window.innerWidth < 992) return;
372
373    var sentinel = document.createElement('div');
374    sentinel.style.cssText = 'height:1px;width:100%;visibility:hidden;pointer-events:none';
375    wrapper.parentNode.insertBefore(sentinel, wrapper);
376
377    var observer = new IntersectionObserver(function(entries) {
378        entries.forEach(function(entry) {
379            if (!entry.isIntersecting && entry.boundingClientRect.top < 0) {
380                var wrapperRect = wrapper.getBoundingClientRect();
381                wrapper.classList.add('is-fixed');
382                wrapper.style.width = wrapperRect.width + 'px';
383                wrapper.style.left = wrapperRect.left + 'px';
384            } else if (entry.isIntersecting) {
385                wrapper.classList.remove('is-fixed');
386                wrapper.style.width = '';
387                wrapper.style.left = '';
388            }
389        });
390    }, { threshold: 0 });
391
392    observer.observe(sentinel);
393
394    window.addEventListener('resize', function() {
395        wrapper.classList.remove('is-fixed');
396        wrapper.style.width = '';
397        wrapper.style.left = '';
398    }, { passive: true });
399});
400
401// Scroll direction detection (with threshold to prevent jitter)
402(function() {
403    var lastY = window.scrollY;
404    var directionY = window.scrollY;
405    var direction = '';
406    var ticking = false;
407    var threshold = 10;
408
409    window.addEventListener('scroll', function() {
410        if (!ticking) {
411            window.requestAnimationFrame(function() {
412                var y = window.scrollY;
413
414                // Always show nav at top of page
415                if (y <= 50) {
416                    document.body.classList.remove('scroll-down');
417                    document.body.classList.add('scroll-up');
418                    directionY = y;
419                    direction = 'up';
420                } else if (y > lastY && direction !== 'down') {
421                    // Started scrolling down — record start position
422                    directionY = lastY;
423                    direction = 'down';
424                } else if (y < lastY && direction !== 'up') {
425                    // Started scrolling up — record start position
426                    directionY = lastY;
427                    direction = 'up';
428                }
429
430                // Only apply class after passing threshold
431                if (direction === 'down' && y - directionY > threshold) {
432                    document.body.classList.add('scroll-down');
433                    document.body.classList.remove('scroll-up');
434                } else if (direction === 'up' && directionY - y > threshold) {
435                    document.body.classList.add('scroll-up');
436                    document.body.classList.remove('scroll-down');
437                }
438
439                lastY = y;
440                ticking = false;
441            });
442            ticking = true;
443        }
444    }, { passive: true });
445})();

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.