nodebb-plugin-ezoic-infinite 1.6.44 → 1.6.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/client.js +18 -129
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1515,7 +1515,11 @@ function buildOrdinalMap(items) {
|
|
|
1515
1515
|
(function () {
|
|
1516
1516
|
// Goal: keep ad injection intact. Only repair when we detect "pile-up" of between wraps.
|
|
1517
1517
|
var TOPIC_LI_SEL = 'li[component="category/topic"]';
|
|
1518
|
-
|
|
1518
|
+
// Between-ad wrapper selector.
|
|
1519
|
+
// Note: Ezoic can inject <span class="ezoic-ad ...">; we normalize those into our wraps.
|
|
1520
|
+
var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between, span.nodebb-ezoic-wrap.ezoic-ad-between, div.ezoic-ad-between, span.ezoic-ad-between';
|
|
1521
|
+
var BETWEEN_WRAP_CHILD_SEL = ':scope > div.nodebb-ezoic-wrap.ezoic-ad-between, :scope > span.nodebb-ezoic-wrap.ezoic-ad-between, :scope > div.ezoic-ad-between, :scope > span.ezoic-ad-between';
|
|
1522
|
+
var ORPHAN_EZOIC_CHILD_SEL = ':scope > span.ezoic-ad:not(.nodebb-ezoic-wrap), :scope > div.ezoic-ad:not(.nodebb-ezoic-wrap)';
|
|
1519
1523
|
var HOST_CLASS = 'nodebb-ezoic-host';
|
|
1520
1524
|
|
|
1521
1525
|
var scheduled = false;
|
|
@@ -1607,7 +1611,19 @@ function buildOrdinalMap(items) {
|
|
|
1607
1611
|
if (!ul) return;
|
|
1608
1612
|
|
|
1609
1613
|
// Step 1: wrap any direct child between DIVs into LI hosts (makes list stable)
|
|
1610
|
-
|
|
1614
|
+
// First: any explicit between wrappers
|
|
1615
|
+
ul.querySelectorAll(BETWEEN_WRAP_CHILD_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
|
|
1616
|
+
|
|
1617
|
+
// Second: native injected Ezoic containers directly under the list.
|
|
1618
|
+
// They don't have our marker classes, so we convert them.
|
|
1619
|
+
ul.querySelectorAll(ORPHAN_EZOIC_CHILD_SEL).forEach(function(w){
|
|
1620
|
+
try { w.classList.add('nodebb-ezoic-wrap'); } catch(e) {}
|
|
1621
|
+
try { w.classList.add('ezoic-ad-between'); } catch(e) {}
|
|
1622
|
+
if (!w.getAttribute('data-ezoic-after')) {
|
|
1623
|
+
w.setAttribute('data-ezoic-after', '1');
|
|
1624
|
+
}
|
|
1625
|
+
ensureHostForWrap(w, ul);
|
|
1626
|
+
});
|
|
1611
1627
|
|
|
1612
1628
|
// Step 2: only act if we see pile-up (avoid touching infinite scroll during normal flow)
|
|
1613
1629
|
if (!detectPileUp(ul)) return;
|
|
@@ -1646,129 +1662,6 @@ function buildOrdinalMap(items) {
|
|
|
1646
1662
|
});
|
|
1647
1663
|
}
|
|
1648
1664
|
|
|
1649
|
-
// --- Empty ad "poke" helper ---
|
|
1650
|
-
// Some GPT / safeframe renders can stay visually blank until a *real* scroll happens.
|
|
1651
|
-
// We still avoid showAds()/refresh() (can hurt fill), but we mimic the user "micro scroll".
|
|
1652
|
-
var ezEmptySeen = Object.create(null);
|
|
1653
|
-
var ezEmptyPokeTimer = Object.create(null);
|
|
1654
|
-
var ezEmptyAttempts = Object.create(null);
|
|
1655
|
-
|
|
1656
|
-
function inViewportLoose(el) {
|
|
1657
|
-
try {
|
|
1658
|
-
if (!el || !el.getBoundingClientRect) return false;
|
|
1659
|
-
var r = el.getBoundingClientRect();
|
|
1660
|
-
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
1661
|
-
// within 1.5 viewports from top/bottom
|
|
1662
|
-
return r.bottom > -vh * 0.5 && r.top < vh * 1.5;
|
|
1663
|
-
} catch (e) {
|
|
1664
|
-
return false;
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
function hasAnyIframe(divEl) {
|
|
1669
|
-
try {
|
|
1670
|
-
if (!divEl) return false;
|
|
1671
|
-
return !!divEl.querySelector('iframe');
|
|
1672
|
-
} catch (e) {
|
|
1673
|
-
return false;
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
function microScrollNudge() {
|
|
1678
|
-
try {
|
|
1679
|
-
// Only if the page can actually scroll
|
|
1680
|
-
var maxY = (document.documentElement && document.documentElement.scrollHeight) ? document.documentElement.scrollHeight : 0;
|
|
1681
|
-
if (maxY <= (window.innerHeight || 0) + 2) return;
|
|
1682
|
-
|
|
1683
|
-
var x = window.scrollX || 0;
|
|
1684
|
-
var y = window.scrollY || 0;
|
|
1685
|
-
// Nudge by 1px and restore on the next frame.
|
|
1686
|
-
window.scrollTo(x, y + 1);
|
|
1687
|
-
requestAnimationFrame(function(){
|
|
1688
|
-
try { window.scrollTo(x, y); } catch (e) {}
|
|
1689
|
-
});
|
|
1690
|
-
} catch (e) {}
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
function pokeReflowForDivId(divId) {
|
|
1694
|
-
try {
|
|
1695
|
-
var el = document.getElementById(divId);
|
|
1696
|
-
if (!el) return;
|
|
1697
|
-
if (!inViewportLoose(el)) return;
|
|
1698
|
-
|
|
1699
|
-
// If the slot already has an iframe, don't keep poking.
|
|
1700
|
-
if (hasAnyIframe(el)) return;
|
|
1701
|
-
|
|
1702
|
-
// Force a tiny reflow/paint on the closest wrapper
|
|
1703
|
-
var wrap = el.closest ? (el.closest('.ez-fixed-wrap') || el.parentElement) : el.parentElement;
|
|
1704
|
-
if (!wrap) wrap = el;
|
|
1705
|
-
|
|
1706
|
-
// 1) Force layout
|
|
1707
|
-
try { void wrap.offsetHeight; } catch (e) {}
|
|
1708
|
-
|
|
1709
|
-
// 2) Toggle visibility for one frame (forces paint)
|
|
1710
|
-
var prevVis = wrap.style.visibility;
|
|
1711
|
-
wrap.style.visibility = 'hidden';
|
|
1712
|
-
requestAnimationFrame(function(){
|
|
1713
|
-
wrap.style.visibility = prevVis || '';
|
|
1714
|
-
// 3) Fire synthetic events (cheap)
|
|
1715
|
-
try { window.dispatchEvent(new Event('scroll')); } catch (e) {}
|
|
1716
|
-
try { window.dispatchEvent(new Event('resize')); } catch (e) {}
|
|
1717
|
-
|
|
1718
|
-
// 4) If the slot was "empty", a real scroll often makes it fill immediately.
|
|
1719
|
-
microScrollNudge();
|
|
1720
|
-
});
|
|
1721
|
-
} catch (e) {}
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
function schedulePoke(divId) {
|
|
1725
|
-
try {
|
|
1726
|
-
if (!divId) return;
|
|
1727
|
-
var now = Date.now();
|
|
1728
|
-
// Cooldown per slot to avoid loops
|
|
1729
|
-
if (ezEmptySeen[divId] && now - ezEmptySeen[divId] < 15000) return;
|
|
1730
|
-
// New window: reset attempts
|
|
1731
|
-
ezEmptyAttempts[divId] = 0;
|
|
1732
|
-
ezEmptySeen[divId] = now;
|
|
1733
|
-
|
|
1734
|
-
// Cap attempts per slot in a short window
|
|
1735
|
-
ezEmptyAttempts[divId] = (ezEmptyAttempts[divId] || 0) + 1;
|
|
1736
|
-
if (ezEmptyAttempts[divId] > 3) return;
|
|
1737
|
-
|
|
1738
|
-
if (ezEmptyPokeTimer[divId]) return;
|
|
1739
|
-
// Multi-poke: fast + a couple of retries (covers slow GPT / lazy paint)
|
|
1740
|
-
ezEmptyPokeTimer[divId] = setTimeout(function(){
|
|
1741
|
-
ezEmptyPokeTimer[divId] = null;
|
|
1742
|
-
pokeReflowForDivId(divId);
|
|
1743
|
-
setTimeout(function(){ pokeReflowForDivId(divId); }, 350);
|
|
1744
|
-
setTimeout(function(){ pokeReflowForDivId(divId); }, 1200);
|
|
1745
|
-
}, 80);
|
|
1746
|
-
} catch (e) {}
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
function initGptEmptyPoke() {
|
|
1750
|
-
try {
|
|
1751
|
-
if (!window.googletag || !googletag.cmd || !googletag.pubads) return;
|
|
1752
|
-
googletag.cmd.push(function(){
|
|
1753
|
-
try {
|
|
1754
|
-
var pub = googletag.pubads();
|
|
1755
|
-
if (!pub || !pub.addEventListener) return;
|
|
1756
|
-
pub.addEventListener('slotRenderEnded', function(e){
|
|
1757
|
-
try {
|
|
1758
|
-
if (!e) return;
|
|
1759
|
-
// divId is the GPT container id for the slot (e.g. div-gpt-ad-...)
|
|
1760
|
-
var divId = e.slot && e.slot.getSlotElementId ? e.slot.getSlotElementId() : (e.slotElementId || '');
|
|
1761
|
-
if (e.isEmpty) {
|
|
1762
|
-
if (divId) console.log('[EZ EMPTY]', divId);
|
|
1763
|
-
schedulePoke(divId);
|
|
1764
|
-
}
|
|
1765
|
-
} catch (err) {}
|
|
1766
|
-
});
|
|
1767
|
-
} catch (err2) {}
|
|
1768
|
-
});
|
|
1769
|
-
} catch (e) {}
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
1665
|
function init() {
|
|
1773
1666
|
schedule('init');
|
|
1774
1667
|
|
|
@@ -1808,10 +1701,6 @@ function buildOrdinalMap(items) {
|
|
|
1808
1701
|
});
|
|
1809
1702
|
} catch (e) {}
|
|
1810
1703
|
}
|
|
1811
|
-
|
|
1812
|
-
// Listen for GPT empty renders and apply a cheap reflow "poke".
|
|
1813
|
-
// This is intentionally non-invasive (no showAds/refresh) to preserve fill.
|
|
1814
|
-
initGptEmptyPoke();
|
|
1815
1704
|
}
|
|
1816
1705
|
|
|
1817
1706
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|