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">×</button>' + 262 '<button class="lightbox__prev" aria-label="Vorige">‹</button>' + 263 '<button class="lightbox__next" aria-label="Volgende">›</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.