nodebb-plugin-ezoic-infinite 1.6.42 → 1.6.44

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/client.js +49 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.42",
3
+ "version": "1.6.44",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -1647,10 +1647,11 @@ function buildOrdinalMap(items) {
1647
1647
  }
1648
1648
 
1649
1649
  // --- Empty ad "poke" helper ---
1650
- // Some GPT safeframe creatives may remain visually blank until a small scroll/reflow happens.
1651
- // We avoid calling showAds()/refresh() (which can hurt fill) and instead trigger a cheap reflow.
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
1652
  var ezEmptySeen = Object.create(null);
1653
1653
  var ezEmptyPokeTimer = Object.create(null);
1654
+ var ezEmptyAttempts = Object.create(null);
1654
1655
 
1655
1656
  function inViewportLoose(el) {
1656
1657
  try {
@@ -1664,24 +1665,58 @@ function buildOrdinalMap(items) {
1664
1665
  }
1665
1666
  }
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
+
1667
1693
  function pokeReflowForDivId(divId) {
1668
1694
  try {
1669
1695
  var el = document.getElementById(divId);
1670
1696
  if (!el) return;
1671
1697
  if (!inViewportLoose(el)) return;
1672
1698
 
1699
+ // If the slot already has an iframe, don't keep poking.
1700
+ if (hasAnyIframe(el)) return;
1701
+
1673
1702
  // Force a tiny reflow/paint on the closest wrapper
1674
1703
  var wrap = el.closest ? (el.closest('.ez-fixed-wrap') || el.parentElement) : el.parentElement;
1675
1704
  if (!wrap) wrap = el;
1676
1705
 
1677
- // Toggle visibility for one frame
1706
+ // 1) Force layout
1707
+ try { void wrap.offsetHeight; } catch (e) {}
1708
+
1709
+ // 2) Toggle visibility for one frame (forces paint)
1678
1710
  var prevVis = wrap.style.visibility;
1679
1711
  wrap.style.visibility = 'hidden';
1680
1712
  requestAnimationFrame(function(){
1681
1713
  wrap.style.visibility = prevVis || '';
1682
- // Dispatch scroll/resize events without moving the page
1714
+ // 3) Fire synthetic events (cheap)
1683
1715
  try { window.dispatchEvent(new Event('scroll')); } catch (e) {}
1684
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();
1685
1720
  });
1686
1721
  } catch (e) {}
1687
1722
  }
@@ -1692,13 +1727,22 @@ function buildOrdinalMap(items) {
1692
1727
  var now = Date.now();
1693
1728
  // Cooldown per slot to avoid loops
1694
1729
  if (ezEmptySeen[divId] && now - ezEmptySeen[divId] < 15000) return;
1730
+ // New window: reset attempts
1731
+ ezEmptyAttempts[divId] = 0;
1695
1732
  ezEmptySeen[divId] = now;
1696
1733
 
1734
+ // Cap attempts per slot in a short window
1735
+ ezEmptyAttempts[divId] = (ezEmptyAttempts[divId] || 0) + 1;
1736
+ if (ezEmptyAttempts[divId] > 3) return;
1737
+
1697
1738
  if (ezEmptyPokeTimer[divId]) return;
1739
+ // Multi-poke: fast + a couple of retries (covers slow GPT / lazy paint)
1698
1740
  ezEmptyPokeTimer[divId] = setTimeout(function(){
1699
1741
  ezEmptyPokeTimer[divId] = null;
1700
1742
  pokeReflowForDivId(divId);
1701
- }, 350);
1743
+ setTimeout(function(){ pokeReflowForDivId(divId); }, 350);
1744
+ setTimeout(function(){ pokeReflowForDivId(divId); }, 1200);
1745
+ }, 80);
1702
1746
  } catch (e) {}
1703
1747
  }
1704
1748