nodebb-plugin-ezoic-infinite 1.6.41 → 1.6.42

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 +83 -162
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.41",
3
+ "version": "1.6.42",
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
@@ -1646,6 +1646,85 @@ function buildOrdinalMap(items) {
1646
1646
  });
1647
1647
  }
1648
1648
 
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.
1652
+ var ezEmptySeen = Object.create(null);
1653
+ var ezEmptyPokeTimer = Object.create(null);
1654
+
1655
+ function inViewportLoose(el) {
1656
+ try {
1657
+ if (!el || !el.getBoundingClientRect) return false;
1658
+ var r = el.getBoundingClientRect();
1659
+ var vh = window.innerHeight || document.documentElement.clientHeight || 0;
1660
+ // within 1.5 viewports from top/bottom
1661
+ return r.bottom > -vh * 0.5 && r.top < vh * 1.5;
1662
+ } catch (e) {
1663
+ return false;
1664
+ }
1665
+ }
1666
+
1667
+ function pokeReflowForDivId(divId) {
1668
+ try {
1669
+ var el = document.getElementById(divId);
1670
+ if (!el) return;
1671
+ if (!inViewportLoose(el)) return;
1672
+
1673
+ // Force a tiny reflow/paint on the closest wrapper
1674
+ var wrap = el.closest ? (el.closest('.ez-fixed-wrap') || el.parentElement) : el.parentElement;
1675
+ if (!wrap) wrap = el;
1676
+
1677
+ // Toggle visibility for one frame
1678
+ var prevVis = wrap.style.visibility;
1679
+ wrap.style.visibility = 'hidden';
1680
+ requestAnimationFrame(function(){
1681
+ wrap.style.visibility = prevVis || '';
1682
+ // Dispatch scroll/resize events without moving the page
1683
+ try { window.dispatchEvent(new Event('scroll')); } catch (e) {}
1684
+ try { window.dispatchEvent(new Event('resize')); } catch (e) {}
1685
+ });
1686
+ } catch (e) {}
1687
+ }
1688
+
1689
+ function schedulePoke(divId) {
1690
+ try {
1691
+ if (!divId) return;
1692
+ var now = Date.now();
1693
+ // Cooldown per slot to avoid loops
1694
+ if (ezEmptySeen[divId] && now - ezEmptySeen[divId] < 15000) return;
1695
+ ezEmptySeen[divId] = now;
1696
+
1697
+ if (ezEmptyPokeTimer[divId]) return;
1698
+ ezEmptyPokeTimer[divId] = setTimeout(function(){
1699
+ ezEmptyPokeTimer[divId] = null;
1700
+ pokeReflowForDivId(divId);
1701
+ }, 350);
1702
+ } catch (e) {}
1703
+ }
1704
+
1705
+ function initGptEmptyPoke() {
1706
+ try {
1707
+ if (!window.googletag || !googletag.cmd || !googletag.pubads) return;
1708
+ googletag.cmd.push(function(){
1709
+ try {
1710
+ var pub = googletag.pubads();
1711
+ if (!pub || !pub.addEventListener) return;
1712
+ pub.addEventListener('slotRenderEnded', function(e){
1713
+ try {
1714
+ if (!e) return;
1715
+ // divId is the GPT container id for the slot (e.g. div-gpt-ad-...)
1716
+ var divId = e.slot && e.slot.getSlotElementId ? e.slot.getSlotElementId() : (e.slotElementId || '');
1717
+ if (e.isEmpty) {
1718
+ if (divId) console.log('[EZ EMPTY]', divId);
1719
+ schedulePoke(divId);
1720
+ }
1721
+ } catch (err) {}
1722
+ });
1723
+ } catch (err2) {}
1724
+ });
1725
+ } catch (e) {}
1726
+ }
1727
+
1649
1728
  function init() {
1650
1729
  schedule('init');
1651
1730
 
@@ -1685,6 +1764,10 @@ function buildOrdinalMap(items) {
1685
1764
  });
1686
1765
  } catch (e) {}
1687
1766
  }
1767
+
1768
+ // Listen for GPT empty renders and apply a cheap reflow "poke".
1769
+ // This is intentionally non-invasive (no showAds/refresh) to preserve fill.
1770
+ initGptEmptyPoke();
1688
1771
  }
1689
1772
 
1690
1773
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
@@ -1692,165 +1775,3 @@ function buildOrdinalMap(items) {
1692
1775
  })();
1693
1776
  // ===== /V17 =====
1694
1777
 
1695
- // ===== V17.13: empty GPT "poke" (keeps V17 pile-fix intact) =====
1696
- // Goal: some GPT containers render empty until a tiny scroll triggers a refresh.
1697
- // This watcher detects visible empty GPT slots and triggers a lightweight refresh.
1698
- (function () {
1699
- var TAG = '[EZ EMPTY]';
1700
- var seen = Object.create(null);
1701
-
1702
- function dbg() {
1703
- if (!window.__EZ_DEBUG) return;
1704
- try { console.log.apply(console, arguments); } catch (e) {}
1705
- }
1706
-
1707
- function throttle(fn, wait) {
1708
- var t = 0;
1709
- var timer = null;
1710
- return function () {
1711
- var now = Date.now();
1712
- var args = arguments;
1713
- if (now - t >= wait) {
1714
- t = now;
1715
- fn.apply(null, args);
1716
- return;
1717
- }
1718
- if (timer) return;
1719
- timer = setTimeout(function () {
1720
- timer = null;
1721
- t = Date.now();
1722
- fn.apply(null, args);
1723
- }, wait);
1724
- };
1725
- }
1726
-
1727
- function isVisible(el) {
1728
- try {
1729
- if (!el || el.nodeType !== 1) return false;
1730
- var r = el.getBoundingClientRect();
1731
- if (!r) return false;
1732
- if (r.width <= 0 || r.height <= 0) return false;
1733
- var vh = window.innerHeight || document.documentElement.clientHeight || 0;
1734
- var vw = window.innerWidth || document.documentElement.clientWidth || 0;
1735
- if (vh <= 0 || vw <= 0) return false;
1736
- return r.bottom > 0 && r.right > 0 && r.top < vh && r.left < vw;
1737
- } catch (e) {}
1738
- return false;
1739
- }
1740
-
1741
- function slotHasFill(slot) {
1742
- try {
1743
- if (slot.querySelector && slot.querySelector('iframe')) return true;
1744
- var cid = 'google_ads_iframe_';
1745
- var any = slot.querySelector && slot.querySelector('[id^="' + cid + '"] iframe');
1746
- return !!any;
1747
- } catch (e) {}
1748
- return false;
1749
- }
1750
-
1751
- function pokeRefresh() {
1752
- try {
1753
- if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
1754
- window.ezstandalone.showAds();
1755
- dbg(TAG, 'showAds called');
1756
- return true;
1757
- }
1758
- } catch (e) {}
1759
- try { window.dispatchEvent(new Event('scroll')); } catch (e) {}
1760
- try { window.dispatchEvent(new Event('resize')); } catch (e) {}
1761
- return false;
1762
- }
1763
-
1764
- function checkSlot(slot) {
1765
- try {
1766
- if (!slot || !slot.id) return;
1767
- if (slot.id.indexOf('div-gpt-ad-') !== 0) return;
1768
- if (slotHasFill(slot)) return;
1769
- if (!isVisible(slot)) return;
1770
-
1771
- var k = slot.id;
1772
- var now = Date.now();
1773
- if (seen[k] && (now - seen[k]) < 2000) return;
1774
- seen[k] = now;
1775
-
1776
- setTimeout(function () {
1777
- try {
1778
- if (!slotHasFill(slot) && isVisible(slot)) {
1779
- console.log(TAG, k);
1780
- pokeRefresh();
1781
- }
1782
- } catch (e) {}
1783
- }, 120);
1784
- } catch (e) {}
1785
- }
1786
-
1787
- function scan() {
1788
- try {
1789
- var slots = document.querySelectorAll('[id^="div-gpt-ad-"]');
1790
- if (!slots || !slots.length) {
1791
- dbg(TAG, 'none');
1792
- return;
1793
- }
1794
- for (var i = 0; i < slots.length; i++) checkSlot(slots[i]);
1795
- } catch (e) {}
1796
- }
1797
-
1798
- function observeNewSlots(io) {
1799
- try {
1800
- if (typeof MutationObserver === 'undefined') return;
1801
- var mo = new MutationObserver(function (muts) {
1802
- try {
1803
- for (var i = 0; i < muts.length; i++) {
1804
- var m = muts[i];
1805
- if (!m.addedNodes) continue;
1806
- for (var j = 0; j < m.addedNodes.length; j++) {
1807
- var n = m.addedNodes[j];
1808
- if (!n || n.nodeType !== 1) continue;
1809
- if (n.id && n.id.indexOf('div-gpt-ad-') === 0) {
1810
- try { io.observe(n); } catch (e) {}
1811
- checkSlot(n);
1812
- } else if (n.querySelectorAll) {
1813
- var inner = n.querySelectorAll('[id^="div-gpt-ad-"]');
1814
- for (var k = 0; k < inner.length; k++) {
1815
- try { io.observe(inner[k]); } catch (e) {}
1816
- checkSlot(inner[k]);
1817
- }
1818
- }
1819
- }
1820
- }
1821
- } catch (e) {}
1822
- });
1823
- mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
1824
- } catch (e) {}
1825
- }
1826
-
1827
- function initEmptyWatcher() {
1828
- scan();
1829
-
1830
- if ('IntersectionObserver' in window) {
1831
- try {
1832
- var io = new IntersectionObserver(function (entries) {
1833
- for (var i = 0; i < entries.length; i++) {
1834
- var ent = entries[i];
1835
- if (ent && ent.isIntersecting) checkSlot(ent.target);
1836
- }
1837
- }, { root: null, threshold: 0.12 });
1838
-
1839
- var slots = document.querySelectorAll('[id^="div-gpt-ad-"]');
1840
- for (var s = 0; s < slots.length; s++) {
1841
- try { io.observe(slots[s]); } catch (e) {}
1842
- }
1843
- observeNewSlots(io);
1844
- } catch (e) {}
1845
- }
1846
-
1847
- try { window.addEventListener('scroll', throttle(scan, 250), { passive: true }); } catch (e) {}
1848
- try { window.addEventListener('resize', throttle(scan, 500)); } catch (e) {}
1849
- try { document.addEventListener('visibilitychange', function () { if (!document.hidden) setTimeout(scan, 150); }); } catch (e) {}
1850
- }
1851
-
1852
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initEmptyWatcher);
1853
- else initEmptyWatcher();
1854
- })();
1855
- // ===== /V17.13 =====
1856
-