nodebb-plugin-ezoic-infinite 1.6.46 → 1.6.48

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 +111 -80
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.46",
3
+ "version": "1.6.48",
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
@@ -1693,105 +1693,136 @@ function buildOrdinalMap(items) {
1693
1693
  // ===== /V17 =====
1694
1694
 
1695
1695
 
1696
- // ===== V17.17 TOPFIX (minimal, non-invasive) =====
1697
- // Goal: prevent "between" ad wrappers from being inserted at the very top of the topic list
1698
- // (before any real topic LI), without moving or re-wrapping existing GPT containers.
1699
- // We only:
1700
- // 1) If a between wrapper is inserted as the first child and a topic LI exists, move its host
1701
- // to after the first topic LI (one-time).
1702
- // 2) If no topic LI exists yet, mark it with data-ezoic-after="1" and leave it in place.
1703
- // This avoids the heavier "pending pool" logic that can reduce fill.
1704
- (function(){
1705
- 'use strict';
1706
1696
 
1707
- var UL_SEL = 'ul.topics, ul.topic-list, ul.category-list';
1708
- var BETWEEN_WRAP_SEL = '.nodebb-ezoic-wrap.ezoic-ad-between';
1709
- var HOST_CLASS = 'ezoic-ad-host';
1697
+ // ===== V17 empty-refresh (no move) =====
1698
+ // Goal: if a between-ad is visible but still has no ad iframe, gently poke Ezoic/GPT to render.
1699
+ (function () {
1700
+ var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1701
+ var seen = new WeakMap();
1702
+ var io = null;
1703
+ // NOTE: Calling ezstandalone.showAds() was triggering extra bidder requests
1704
+ // (and in your setup, surfacing ServiceWorker/CORS failures) which made
1705
+ // fills slower and created visible repositioning. We only do a tiny scroll
1706
+ // nudge here, because manual micro-scroll reliably triggers a fill.
1707
+ var microScrollCooldownUntil = 0;
1710
1708
 
1711
- function getUl(){
1712
- return document.querySelector(UL_SEL);
1709
+ function now() { return Date.now ? Date.now() : +new Date(); }
1710
+
1711
+ function getSlotId(wrap) {
1712
+ try {
1713
+ var gpt = wrap.querySelector('[id^="div-gpt-ad-"]');
1714
+ return gpt ? gpt.id : null;
1715
+ } catch (e) { return null; }
1713
1716
  }
1714
1717
 
1715
- function firstTopicLi(ul){
1716
- if (!ul) return null;
1717
- // Consider any LI that is not our ad host as a topic LI
1718
- var lis = ul.querySelectorAll(':scope > li');
1719
- for (var i=0;i<lis.length;i++){
1720
- if (!lis[i].classList.contains(HOST_CLASS)) return lis[i];
1721
- }
1722
- return null;
1718
+ function hasAnyCreative(wrap) {
1719
+ try {
1720
+ // Any iframe (safeframe or normal) inside the wrap counts as rendered.
1721
+ if (wrap.querySelector('iframe')) return true;
1722
+ // Some formats use ins/amp/other nodes.
1723
+ if (wrap.querySelector('amp-ad, amp-embed, ins.adsbygoogle')) return true;
1724
+ } catch (e) {}
1725
+ return false;
1723
1726
  }
1724
1727
 
1725
- function hostFromWrap(wrap){
1726
- if (!wrap) return null;
1727
- // expected structure: li.ezoic-ad-host > (something) > span.nodebb-ezoic-wrap
1728
- var host = wrap.closest('li');
1729
- if (host && host.classList && host.classList.contains(HOST_CLASS)) return host;
1730
- return null;
1728
+ function microScrollNudge() {
1729
+ var t = now();
1730
+ if (t < microScrollCooldownUntil) return;
1731
+ microScrollCooldownUntil = t + 1200;
1732
+
1733
+ // Prefer a real 1px scroll + restore: this matches the observed manual fix.
1734
+ try {
1735
+ var y = window.pageYOffset || document.documentElement.scrollTop || 0;
1736
+ window.scrollTo(0, y + 1);
1737
+ (window.requestAnimationFrame || setTimeout)(function () {
1738
+ try { window.scrollTo(0, y); } catch (e) {}
1739
+ try { window.dispatchEvent(new Event('scroll')); } catch (e) {}
1740
+ }, 16);
1741
+ return;
1742
+ } catch (e) {}
1743
+
1744
+ // Fallback: nudge listeners.
1745
+ try { window.dispatchEvent(new Event('scroll')); } catch (e) {}
1731
1746
  }
1732
1747
 
1733
- function handleWrapInserted(ul, wrap){
1748
+ function scheduleCheck(wrap) {
1734
1749
  try {
1735
- if (!ul || !wrap) return;
1736
- var host = hostFromWrap(wrap);
1737
- if (!host) return;
1738
-
1739
- // Only act when the host is at the very top (before any topic LI)
1740
- if (host.parentElement !== ul) return;
1741
- if (ul.firstElementChild !== host) return;
1742
-
1743
- var anchor = firstTopicLi(ul);
1744
- if (anchor) {
1745
- // One-time move to just after first topic LI
1746
- anchor.insertAdjacentElement('afterend', host);
1747
- } else {
1748
- // No anchor yet: mark to discourage top placement; do not move later
1749
- try { wrap.setAttribute('data-ezoic-after', '1'); } catch(e) {}
1750
+ if (!wrap || wrap.nodeType !== 1) return;
1751
+ if (hasAnyCreative(wrap)) return;
1752
+
1753
+ var meta = seen.get(wrap);
1754
+ var t = now();
1755
+ if (!meta) {
1756
+ meta = { firstSeen: t, checks: 0 };
1757
+ seen.set(wrap, meta);
1750
1758
  }
1759
+
1760
+ // Give the normal pipeline time to render.
1761
+ if (meta.checks === 0) {
1762
+ setTimeout(function () { scheduleCheck(wrap); }, 700);
1763
+ meta.checks++;
1764
+ return;
1765
+ }
1766
+
1767
+ if (hasAnyCreative(wrap)) return;
1768
+
1769
+ // Still empty -> poke (micro-scroll only; no showAds/refresh calls).
1770
+ var id = getSlotId(wrap) || 'none';
1771
+ try { console.log('[EZ EMPTY]', id); } catch (e) {}
1772
+ microScrollNudge();
1773
+
1774
+ // Re-check once after the nudge, but do not loop.
1775
+ setTimeout(function () {
1776
+ if (!wrap.isConnected) return;
1777
+ if (hasAnyCreative(wrap)) return;
1778
+ // Still empty: leave it to the native pipeline; avoid repeated nudges.
1779
+ }, 900);
1751
1780
  } catch (e) {}
1752
1781
  }
1753
1782
 
1754
- function scanExisting(ul){
1783
+ function ensureIO() {
1784
+ if (io) return;
1785
+ if (!('IntersectionObserver' in window)) return;
1786
+
1787
+ io = new IntersectionObserver(function (entries) {
1788
+ for (var i = 0; i < entries.length; i++) {
1789
+ var e = entries[i];
1790
+ if (!e.isIntersecting) continue;
1791
+ scheduleCheck(e.target);
1792
+ }
1793
+ }, { root: null, rootMargin: '900px 0px 900px 0px', threshold: 0.01 });
1794
+
1795
+ // Observe existing wraps.
1755
1796
  try {
1756
- var wraps = ul ? ul.querySelectorAll(BETWEEN_WRAP_SEL) : [];
1757
- wraps.forEach(function(w){ handleWrapInserted(ul, w); });
1758
- } catch(e) {}
1759
- }
1760
-
1761
- function init(){
1762
- var ul = getUl();
1763
- if (!ul) return;
1764
-
1765
- scanExisting(ul);
1766
-
1767
- if (typeof MutationObserver === 'undefined') return;
1768
- var mo = new MutationObserver(function(muts){
1769
- for (var i=0;i<muts.length;i++){
1770
- var m = muts[i];
1771
- if (!m.addedNodes) continue;
1772
- for (var j=0;j<m.addedNodes.length;j++){
1773
- var n = m.addedNodes[j];
1774
- if (!n || n.nodeType !== 1) continue;
1775
-
1776
- // direct wrap
1777
- if (n.matches && n.matches(BETWEEN_WRAP_SEL)) {
1778
- handleWrapInserted(ul, n);
1779
- continue;
1797
+ var wraps = document.querySelectorAll(BETWEEN_WRAP_SEL);
1798
+ for (var j = 0; j < wraps.length; j++) io.observe(wraps[j]);
1799
+ } catch (e) {}
1800
+
1801
+ // Observe new wraps.
1802
+ try {
1803
+ var mo = new MutationObserver(function (muts) {
1804
+ for (var k = 0; k < muts.length; k++) {
1805
+ var m = muts[k];
1806
+ if (!m.addedNodes) continue;
1807
+ for (var n = 0; n < m.addedNodes.length; n++) {
1808
+ var node = m.addedNodes[n];
1809
+ if (!node || node.nodeType !== 1) continue;
1810
+ if (node.matches && node.matches(BETWEEN_WRAP_SEL)) {
1811
+ io.observe(node);
1812
+ } else if (node.querySelectorAll) {
1813
+ var inner = node.querySelectorAll(BETWEEN_WRAP_SEL);
1814
+ for (var q = 0; q < inner.length; q++) io.observe(inner[q]);
1815
+ }
1780
1816
  }
1781
- // wrap somewhere inside
1782
- var inner = n.querySelector ? n.querySelector(BETWEEN_WRAP_SEL) : null;
1783
- if (inner) handleWrapInserted(ul, inner);
1784
1817
  }
1785
- }
1786
- });
1787
-
1788
- mo.observe(ul, { childList: true, subtree: true });
1818
+ });
1819
+ mo.observe(document.body, { childList: true, subtree: true });
1820
+ } catch (e) {}
1789
1821
  }
1790
1822
 
1791
1823
  if (document.readyState === 'loading') {
1792
- document.addEventListener('DOMContentLoaded', init);
1824
+ document.addEventListener('DOMContentLoaded', ensureIO, { once: true });
1793
1825
  } else {
1794
- init();
1826
+ ensureIO();
1795
1827
  }
1796
1828
  })();
1797
- // ===== /V17.17 TOPFIX =====