nodebb-plugin-ezoic-infinite 1.5.72 → 1.5.74

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.72",
3
+ "version": "1.5.74",
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
@@ -14,7 +14,8 @@
14
14
 
15
15
  // Keep empty (unfilled) wraps alive for a while. Topics/messages can fill late (auction/CMP).
16
16
  // Pruning too early makes ads look like they "disappear" while scrolling.
17
- const KEEP_EMPTY_WRAP_MS = 30000;
17
+ // Keep empty wraps alive; mobile fills can be slow.
18
+ function keepEmptyWrapMs() { return isMobile() ? 120000 : 60000; }
18
19
 
19
20
  // Preload margins
20
21
  const PRELOAD_MARGIN_DESKTOP = '2600px 0px 2600px 0px';
@@ -37,7 +38,127 @@
37
38
  // Production build: debug disabled
38
39
  function dbg() {}
39
40
 
40
- // ---------------- state ----------------
41
+
42
+
43
+ function isFilledNode(node) {
44
+ return !!(node && node.querySelector && node.querySelector('iframe, ins, img, video, [data-google-container-id]'));
45
+ }
46
+
47
+ // Ezoic injects inline `min-height:400px !important` on one or more nested wrappers.
48
+ // If the creative is 250px, this leaves ~150px empty space. Because it's inline+important,
49
+ // CSS alone cannot fix it reliably — we must rewrite the inline style.
50
+ function tightenEzoicMinHeight(wrap) {
51
+ try {
52
+ if (!wrap || !wrap.querySelector) return;
53
+
54
+ const iframes = wrap.querySelectorAll('iframe');
55
+ if (!iframes || !iframes.length) return;
56
+
57
+ // Find the closest "big" ezoic container that carries the 400px min-height.
58
+ const firstIframe = iframes[0];
59
+ let refNode = null;
60
+ let p = firstIframe && firstIframe.parentElement;
61
+ while (p && p !== wrap) {
62
+ if (p.classList && p.classList.contains('ezoic-ad')) {
63
+ const st = (p.getAttribute('style') || '').toLowerCase();
64
+ if (st.includes('min-height:400') || st.includes('min-height: 400') || st.includes('min-height:400px')) {
65
+ refNode = p;
66
+ break;
67
+ }
68
+ }
69
+ p = p.parentElement;
70
+ }
71
+ if (!refNode) {
72
+ refNode = wrap.querySelector('.ezoic-ad-adaptive') || wrap.querySelector('.ezoic-ad') || wrap;
73
+ }
74
+
75
+ let refTop = 0;
76
+ try { refTop = refNode.getBoundingClientRect().top; } catch (e) { refTop = 0; }
77
+
78
+ // Compute the rendered height needed inside refNode (visible iframes only).
79
+ let maxBottom = 0;
80
+ iframes.forEach((f) => {
81
+ if (!f || !f.getBoundingClientRect) return;
82
+ const rect = f.getBoundingClientRect();
83
+ if (rect.width <= 1 || rect.height <= 1) return;
84
+ const bottom = rect.bottom - refTop;
85
+ maxBottom = Math.max(maxBottom, bottom);
86
+ });
87
+
88
+ // Fallback to attr/offset if layout metrics are not available.
89
+ if (!maxBottom) {
90
+ iframes.forEach((f) => {
91
+ const ah = parseInt(f.getAttribute('height') || '0', 10);
92
+ const oh = f.offsetHeight || 0;
93
+ maxBottom = Math.max(maxBottom, ah, oh);
94
+ });
95
+ }
96
+ if (!maxBottom) return;
97
+
98
+ // Account for the Ezoic badge (reportline) which is absolutely positioned.
99
+ const report = refNode.querySelector('.reportline');
100
+ if (report && report.offsetHeight) {
101
+ maxBottom += report.offsetHeight;
102
+ }
103
+
104
+ const h = Math.max(1, Math.ceil(maxBottom));
105
+
106
+ const tightenNode = (node) => {
107
+ if (!node || !node.style) return;
108
+ try { node.style.setProperty('min-height', h + 'px', 'important'); } catch (e) { node.style.minHeight = h + 'px'; }
109
+ try { node.style.setProperty('height', 'auto', 'important'); } catch (e) {}
110
+ try { node.style.setProperty('line-height', '0', 'important'); } catch (e) {}
111
+ };
112
+
113
+ // Tighten refNode and any ancestor ezoic-ad nodes with the problematic min-height.
114
+ let cur = refNode;
115
+ while (cur && cur !== wrap) {
116
+ if (cur.classList && cur.classList.contains('ezoic-ad')) {
117
+ const st = (cur.getAttribute('style') || '').toLowerCase();
118
+ if (st.includes('min-height:400') || st.includes('min-height: 400') || st.includes('min-height:400px')) {
119
+ tightenNode(cur);
120
+ }
121
+ }
122
+ cur = cur.parentElement;
123
+ }
124
+ tightenNode(refNode);
125
+
126
+ // Tighten any nested wrappers that also have the 400px min-height inline.
127
+ refNode.querySelectorAll('.ezoic-ad[style*="min-height"], .ezoic-ad-adaptive').forEach((n) => {
128
+ const st = (n.getAttribute('style') || '').toLowerCase();
129
+ if (st.includes('min-height:400') || st.includes('min-height: 400') || st.includes('min-height:400px')) {
130
+ tightenNode(n);
131
+ }
132
+ });
133
+
134
+ // Mobile friendliness: avoid giant fixed widths causing overflow/reflow.
135
+ if (isMobile()) {
136
+ [refNode].forEach((n) => {
137
+ try { n.style.setProperty('width', '100%', 'important'); } catch (e) {}
138
+ try { n.style.setProperty('max-width', '100%', 'important'); } catch (e) {}
139
+ try { n.style.setProperty('min-width', '0', 'important'); } catch (e) {}
140
+ });
141
+ }
142
+ } catch (e) {}
143
+ }
144
+
145
+ function watchWrapForFill(wrap) {
146
+ try {
147
+ if (!wrap || wrap.__ezFillObs) return;
148
+ const obs = new MutationObserver(() => {
149
+ if (isFilledNode(wrap)) {
150
+ wrap.classList.remove('is-empty');
151
+ tightenEzoicMinHeight(wrap);
152
+ try { obs.disconnect(); } catch (e) {}
153
+ wrap.__ezFillObs = null;
154
+ }
155
+ });
156
+ obs.observe(wrap, { childList: true, subtree: true });
157
+ wrap.__ezFillObs = obs;
158
+ } catch (e) {}
159
+ }
160
+
161
+ // ---------------- state ----------------
41
162
 
42
163
  const state = {
43
164
  pageKey: null,
@@ -414,6 +535,8 @@
414
535
  }
415
536
 
416
537
  function pruneOrphanWraps(kindClass, items) {
538
+ // On mobile topics/messages, keep wraps alive longer to avoid 'disappearing' ads.
539
+ if (kindClass === 'ezoic-ad-message' && isMobile()) return 0;
417
540
  if (!items || !items.length) return 0;
418
541
  const itemSet = new Set(items);
419
542
  const wraps = document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`);
@@ -444,7 +567,7 @@
444
567
  // Never prune a fresh wrap: it may fill late.
445
568
  try {
446
569
  const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
447
- if (created && (now() - created) < KEEP_EMPTY_WRAP_MS) return;
570
+ if (created && (now() - created) < keepEmptyWrapMs()) return;
448
571
  } catch (e) {}
449
572
 
450
573
  if (hasNearbyItem(wrap)) return;
@@ -470,7 +593,7 @@
470
593
  const isFresh = (wrap) => {
471
594
  try {
472
595
  const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
473
- return created && (now() - created) < KEEP_EMPTY_WRAP_MS;
596
+ return created && (now() - created) < keepEmptyWrapMs();
474
597
  } catch (e) {
475
598
  return false;
476
599
  }
@@ -603,11 +726,17 @@
603
726
  // Don't collapse "fresh" placements; slow auctions/CMP can fill late.
604
727
  try {
605
728
  const created = parseInt(w2.getAttribute('data-created') || '0', 10);
606
- if (created && (now() - created) < KEEP_EMPTY_WRAP_MS) return;
729
+ if (created && (now() - created) < keepEmptyWrapMs()) return;
607
730
  } catch (e) {}
608
731
 
609
732
  const hasAd = !!(ph2.querySelector && ph2.querySelector('iframe, ins, img, .ez-ad, .ezoic-ad'));
610
- if (!hasAd) w2.classList.add('is-empty');
733
+ if (!hasAd) {
734
+ w2.classList.add('is-empty');
735
+ watchWrapForFill(w2);
736
+ } else {
737
+ w2.classList.remove('is-empty');
738
+ tightenEzoicMinHeight(w2);
739
+ }
611
740
  } catch (e) {}
612
741
  }, 15000);
613
742
  } catch (e) {}
@@ -645,6 +774,15 @@
645
774
  const doShow = () => {
646
775
  try { ez.showAds(id); } catch (e) {}
647
776
  try { markEmptyWrapper(id); } catch (e) {}
777
+ try {
778
+ const phw = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
779
+ const ww = phw && phw.closest ? phw.closest(`.${WRAP_CLASS}`) : null;
780
+ if (ww) {
781
+ watchWrapForFill(ww);
782
+ setTimeout(() => { try { tightenEzoicMinHeight(ww); } catch (e) {} }, 900);
783
+ setTimeout(() => { try { tightenEzoicMinHeight(ww); } catch (e) {} }, 2200);
784
+ }
785
+ } catch (e) {}
648
786
  setTimeout(() => { clearTimeout(hardTimer); release(); }, 650);
649
787
  };
650
788
 
package/public/style.css CHANGED
@@ -68,3 +68,14 @@
68
68
  min-height: 1px !important; /* kill 400px gaps */
69
69
  height: auto !important;
70
70
  }
71
+
72
+ /* Ensure Ezoic reportline doesn't affect layout */
73
+ .nodebb-ezoic-wrap .reportline{position:absolute!important;}
74
+
75
+ /* Ezoic sometimes injects `position: sticky` inside placements. In long NodeBB topics,
76
+ this can create "gliding" and sudden disappear/reappear effects while scrolling.
77
+ We neutralize sticky positioning *inside our injected wrappers* only. */
78
+ .nodebb-ezoic-wrap .ezads-sticky-intradiv {
79
+ position: static !important;
80
+ top: auto !important;
81
+ }