| Line 6: |
Line 6: |
| | | | |
| | /* Other JS: */ | | /* Other JS: */ |
| − | /* Force-load images/media when a MobileFrontend section is expanded */ | + | /* Force open sections and mw-collapsible; eagerly load media inside */ |
| | (function () { | | (function () { |
| | function forceLoadIn(root) { | | function forceLoadIn(root) { |
| | if (!root) return; | | if (!root) return; |
| | | | |
| − | // <img> with native lazy or data-* sources | + | // IMG & PICTURE sources (native lazy + data-* patterns) |
| − | var imgs = root.querySelectorAll('img[loading="lazy"], img[data-src], img[data-srcset], img.lazyload'); | + | var imgs = root.querySelectorAll('img, picture source'); |
| − | imgs.forEach(function (img) { | + | imgs.forEach(function (el) { |
| | try { | | try { |
| − | // Promote data-* to real attrs if present | + | var tag = el.tagName.toLowerCase(); |
| − | if (img.dataset) {
| + | var img = tag === 'img' ? el : null; |
| − | if (img.dataset.src) img.src = img.dataset.src; | + | |
| − | if (img.dataset.srcset) img.srcset = img.dataset.srcset; | + | ['src', 'srcset', 'sizes'].forEach(function (k) { |
| − | if (img.dataset.sizes) img.sizes = img.dataset.sizes; | + | var dk = 'data-' + k; |
| | + | if (el.hasAttribute && el.hasAttribute(dk)) { |
| | + | el.setAttribute(k, el.getAttribute(dk)); |
| | + | el.removeAttribute(dk); |
| | + | } |
| | + | if (img && img.dataset && img.dataset[k]) { |
| | + | img[k] = img.dataset[k]; |
| | + | } |
| | + | }); |
| | + | |
| | + | if (img) { |
| | + | try { img.loading = 'eager'; } catch (e) {} |
| | + | try { img.decoding = 'sync'; } catch (e) {} |
| | + | var s = img.currentSrc || img.src; |
| | + | if (s) { img.src = ''; img.src = s; } // nudge reload if it was idle |
| | } | | } |
| − | // Disable lazy & decode ASAP
| |
| − | img.loading = 'eager';
| |
| − | try { img.decoding = 'sync'; } catch (e) {}
| |
| − |
| |
| − | // Kick browsers that already set src but kept it idle due to prior hidden state
| |
| − | var s = img.currentSrc || img.src;
| |
| − | if (s) { img.src = ''; img.src = s; }
| |
| | } catch (e) {} | | } catch (e) {} |
| | }); | | }); |
| | | | |
| | // Background images deferred via data-bg | | // Background images deferred via data-bg |
| − | var bgs = root.querySelectorAll('[data-bg]'); | + | root.querySelectorAll('[data-bg]').forEach(function (el) { |
| − | bgs.forEach(function (el) {
| |
| | var bg = el.getAttribute('data-bg'); | | var bg = el.getAttribute('data-bg'); |
| | if (bg) { | | if (bg) { |
| Line 41: |
Line 47: |
| | }); | | }); |
| | | | |
| − | // Videos commonly start with preload="none" on mobile | + | // Videos (mobile often sets preload="none") |
| − | var vids = root.querySelectorAll('video[preload="none"], video[data-poster]'); | + | root.querySelectorAll('video').forEach(function (v) { |
| − | vids.forEach(function (v) {
| |
| | try { | | try { |
| − | v.preload = 'metadata'; | + | if (v.getAttribute('preload') === 'none') v.setAttribute('preload', 'metadata'); |
| | if (v.dataset && v.dataset.poster && !v.poster) v.poster = v.dataset.poster; | | if (v.dataset && v.dataset.poster && !v.poster) v.poster = v.dataset.poster; |
| − | // Nudge the browser | + | v.load(); |
| − | if (v.paused) v.load();
| |
| | } catch (e) {} | | } catch (e) {} |
| | }); | | }); |
| | } | | } |
| | | | |
| − | // On heading click (MobileFrontend/Minerva uses section headings as toggles) | + | function openMFSection(el) { |
| − | function onToggle(e) {
| + | if (!el) return; |
| − | var heading = e.target && e.target.closest && | + | el.classList.add('open-block'); |
| − | e.target.closest('.section-heading, .mf-section-heading, .toc-mobile__section-heading, h2, h3, h4');
| + | el.classList.remove('collapsed'); |
| − | if (!heading) return; | + | el.setAttribute('aria-expanded', 'true'); |
| | + | (el.querySelector('.collapsible-block-content, .mf-collapsible-content, .section-content') || el) |
| | + | .style.display = 'block'; |
| | + | forceLoadIn(el); |
| | + | } |
| | | | |
| − | // Wait for classes/ARIA to update | + | function openAll() { |
| − | setTimeout(function () { | + | // MobileFrontend sections |
| − | var section = heading.closest('.section, .mf-section, .collapsible-block, .mf-collapsible-block, .mobile-collapse');
| + | document.querySelectorAll('.mf-section, .collapsible-block, .mf-collapsible-block, .mobile-collapse') |
| − | if (!section) return; | + | .forEach(openMFSection); |
| − | var isOpen = section.classList.contains('open-block') || | + | |
| − | section.getAttribute('aria-expanded') === 'true';
| + | // Core mw-collapsible (templates/navboxes) |
| − | if (isOpen) forceLoadIn(section); | + | document.querySelectorAll('.mw-collapsible').forEach(function (el) { |
| − | }, 0); | + | el.classList.remove('mw-collapsed'); |
| | + | var c = el.querySelector('.mw-collapsible-content'); |
| | + | if (c) c.style.display = 'block'; |
| | + | forceLoadIn(el); |
| | + | }); |
| | } | | } |
| − | document.addEventListener('click', onToggle, true);
| |
| | | | |
| − | // Fallback: observe class/ARIA flips (covers non-click expansions) | + | function neutralizeToggles() { |
| − | var mo = new MutationObserver(function (mutations) {
| + | // Remove heading click handlers that re-collapse |
| − | mutations.forEach(function (m) {
| + | document.querySelectorAll('.section-heading, .mf-section-heading, .toc-mobile__section-heading, h2, h3, h4') |
| − | var el = m.target;
| + | .forEach(function (h) { |
| − | if (!el) return;
| + | var clone = h.cloneNode(true); |
| − | var isOpen = el.classList && el.classList.contains('open-block');
| + | h.parentNode && h.parentNode.replaceChild(clone, h); |
| − | if (!isOpen && el.getAttribute) { | + | }); |
| − | isOpen = el.getAttribute('aria-expanded') === 'true';
| |
| − | }
| |
| − | if (isOpen) forceLoadIn(el);
| |
| − | });
| |
| − | });
| |
| | | | |
| − | function attachObservers() {
| + | // Disable core mw-collapsible toggles |
| − | document.querySelectorAll('.mf-collapsible-block, .collapsible-block, .section, .mf-section, .mobile-collapse') | + | document.querySelectorAll('.mw-collapsible .mw-collapsible-toggle, .mw-collapsible .mw-collapsible-button') |
| − | .forEach(function (el) { | + | .forEach(function (t) { |
| − | mo.observe(el, { attributes: true, attributeFilter: ['class', 'aria-expanded'] }); | + | var c = t.cloneNode(true); |
| | + | c.addEventListener('click', function (e) { e.stopImmediatePropagation(); e.preventDefault(); }, true); |
| | + | t.parentNode && t.parentNode.replaceChild(c, t); |
| | }); | | }); |
| | + | } |
| | + | |
| | + | function init() { |
| | + | neutralizeToggles(); |
| | + | openAll(); |
| | + | |
| | + | // Keep overriding as MF/core mutate DOM |
| | + | var mo = new MutationObserver(function () { |
| | + | neutralizeToggles(); |
| | + | openAll(); |
| | + | }); |
| | + | mo.observe(document.documentElement, { childList: true, subtree: true, attributes: true }); |
| | } | | } |
| | | | |
| | if (document.readyState === 'loading') { | | if (document.readyState === 'loading') { |
| − | document.addEventListener('DOMContentLoaded', attachObservers); | + | document.addEventListener('DOMContentLoaded', init); |
| | } else { | | } else { |
| − | attachObservers(); | + | init(); |
| | } | | } |
| | | | |
| − | // Ensure lead content eagerly loads as well | + | // Also eagerly load the lead |
| | var lead = document.querySelector('#mf-section-0, .lead, #bodyContent, .content'); | | var lead = document.querySelector('#mf-section-0, .lead, #bodyContent, .content'); |
| | if (lead) forceLoadIn(lead); | | if (lead) forceLoadIn(lead); |
| | })(); | | })(); |