nodebb-plugin-ezoic-infinite 1.5.74 → 1.5.76
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 +141 -5
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -38,6 +38,46 @@
|
|
|
38
38
|
// Production build: debug disabled
|
|
39
39
|
function dbg() {}
|
|
40
40
|
|
|
41
|
+
// Ezoic (and some partner scripts) can be very noisy in console on SPA/Ajaxify setups.
|
|
42
|
+
// These warnings are not actionable for end-users and can flood the console.
|
|
43
|
+
// We selectively silence the known spam patterns while keeping other warnings intact.
|
|
44
|
+
function muteNoisyConsole() {
|
|
45
|
+
try {
|
|
46
|
+
if (window.__nodebbEzoicConsoleMuted) return;
|
|
47
|
+
window.__nodebbEzoicConsoleMuted = true;
|
|
48
|
+
|
|
49
|
+
const shouldMute = (args) => {
|
|
50
|
+
try {
|
|
51
|
+
if (!args || !args.length) return false;
|
|
52
|
+
const s0 = typeof args[0] === 'string' ? args[0] : '';
|
|
53
|
+
// Duplicate placeholder definition spam (common when reusing ids in SPA/Ajaxify).
|
|
54
|
+
if (s0.includes('[EzoicAds JS]: Placeholder Id') && s0.includes('has already been defined')) return true;
|
|
55
|
+
// Ezoic debugger iframe spam.
|
|
56
|
+
if (s0.includes('Debugger iframe already exists')) return true;
|
|
57
|
+
// Missing placeholder spam (we already guard showAds; Ezoic still logs sometimes).
|
|
58
|
+
if (s0.includes('HTML element with id ezoic-pub-ad-placeholder-') && s0.includes('does not exist')) return true;
|
|
59
|
+
return false;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const wrap = (method) => {
|
|
66
|
+
const orig = console[method];
|
|
67
|
+
if (typeof orig !== 'function') return;
|
|
68
|
+
console[method] = function (...args) {
|
|
69
|
+
if (shouldMute(args)) return;
|
|
70
|
+
return orig.apply(console, args);
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
wrap('log');
|
|
75
|
+
wrap('info');
|
|
76
|
+
wrap('warn');
|
|
77
|
+
wrap('error');
|
|
78
|
+
} catch (e) {}
|
|
79
|
+
}
|
|
80
|
+
|
|
41
81
|
|
|
42
82
|
|
|
43
83
|
function isFilledNode(node) {
|
|
@@ -145,19 +185,88 @@ function tightenEzoicMinHeight(wrap) {
|
|
|
145
185
|
function watchWrapForFill(wrap) {
|
|
146
186
|
try {
|
|
147
187
|
if (!wrap || wrap.__ezFillObs) return;
|
|
148
|
-
|
|
188
|
+
|
|
189
|
+
// Ezoic can (re)apply inline styles after fill; keep tightening for a short window.
|
|
190
|
+
const start = now();
|
|
191
|
+
const tightenBurst = () => {
|
|
192
|
+
try { tightenEzoicMinHeight(wrap); } catch (e) {}
|
|
193
|
+
if (now() - start < 6000) {
|
|
194
|
+
setTimeout(tightenBurst, 350);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const obs = new MutationObserver((muts) => {
|
|
199
|
+
// If anything that looks like ad content appears, treat as filled.
|
|
149
200
|
if (isFilledNode(wrap)) {
|
|
150
201
|
wrap.classList.remove('is-empty');
|
|
151
|
-
|
|
202
|
+
tightenBurst();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// If Ezoic changes inline style on descendants (min-height:400!important), tighten again.
|
|
206
|
+
for (const m of muts) {
|
|
207
|
+
if (m.type === 'attributes' && m.attributeName === 'style') {
|
|
208
|
+
try { tightenEzoicMinHeight(wrap); } catch (e) {}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Disconnect only after the burst window to avoid missing late style rewrites.
|
|
214
|
+
if (now() - start > 7000) {
|
|
152
215
|
try { obs.disconnect(); } catch (e) {}
|
|
153
216
|
wrap.__ezFillObs = null;
|
|
154
217
|
}
|
|
155
218
|
});
|
|
156
|
-
|
|
219
|
+
|
|
220
|
+
obs.observe(wrap, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });
|
|
157
221
|
wrap.__ezFillObs = obs;
|
|
158
222
|
} catch (e) {}
|
|
159
223
|
}
|
|
160
224
|
|
|
225
|
+
// Global safety net: sometimes Ezoic swaps nodes in ways that bypass our per-wrap observers.
|
|
226
|
+
// When we see an Ezoic container with min-height:400!important inside posts/topics, shrink it.
|
|
227
|
+
function globalGapFixInit() {
|
|
228
|
+
try {
|
|
229
|
+
if (window.__nodebbEzoicGapFix) return;
|
|
230
|
+
window.__nodebbEzoicGapFix = true;
|
|
231
|
+
|
|
232
|
+
const inPostArea = (el) => {
|
|
233
|
+
try {
|
|
234
|
+
return !!(el && el.closest && el.closest('[component="post"], .topic, .posts, [component="topic"]'));
|
|
235
|
+
} catch (e) { return false; }
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const maybeFix = (root) => {
|
|
239
|
+
if (!root || !root.querySelectorAll) return;
|
|
240
|
+
const nodes = root.querySelectorAll('.ezoic-ad[style*="min-height"], .ezoic-ad-adaptive[style*="min-height"]');
|
|
241
|
+
nodes.forEach((n) => {
|
|
242
|
+
const st = (n.getAttribute('style') || '').toLowerCase();
|
|
243
|
+
if (!st.includes('min-height:400')) return;
|
|
244
|
+
if (!inPostArea(n)) return;
|
|
245
|
+
try {
|
|
246
|
+
const tmpWrap = n.closest('.' + WRAP_CLASS) || n.parentElement;
|
|
247
|
+
tightenEzoicMinHeight(tmpWrap || n);
|
|
248
|
+
} catch (e) {}
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
requestAnimationFrame(() => maybeFix(document));
|
|
253
|
+
|
|
254
|
+
const obs = new MutationObserver((muts) => {
|
|
255
|
+
for (const m of muts) {
|
|
256
|
+
const t = m.target;
|
|
257
|
+
if (m.type === 'attributes') {
|
|
258
|
+
maybeFix(t && t.nodeType === 1 ? t : t && t.parentElement);
|
|
259
|
+
} else if (m.addedNodes && m.addedNodes.length) {
|
|
260
|
+
m.addedNodes.forEach((n) => {
|
|
261
|
+
if (n && n.nodeType === 1) maybeFix(n);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
obs.observe(document.documentElement, { subtree: true, childList: true, attributes: true, attributeFilter: ['style'] });
|
|
267
|
+
} catch (e) {}
|
|
268
|
+
}
|
|
269
|
+
|
|
161
270
|
// ---------------- state ----------------
|
|
162
271
|
|
|
163
272
|
const state = {
|
|
@@ -477,6 +586,10 @@ function watchWrapForFill(wrap) {
|
|
|
477
586
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
478
587
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
479
588
|
wrap.setAttribute('data-created', String(now()));
|
|
589
|
+
// "Pinned" placements (after the first item) should remain stable.
|
|
590
|
+
if (afterPos === 1) {
|
|
591
|
+
wrap.setAttribute('data-ezoic-pin', '1');
|
|
592
|
+
}
|
|
480
593
|
wrap.style.width = '100%';
|
|
481
594
|
|
|
482
595
|
if (createPlaceholder) {
|
|
@@ -535,8 +648,9 @@ function watchWrapForFill(wrap) {
|
|
|
535
648
|
}
|
|
536
649
|
|
|
537
650
|
function pruneOrphanWraps(kindClass, items) {
|
|
538
|
-
//
|
|
539
|
-
|
|
651
|
+
// Message ads should remain stable (desktop + mobile). Pruning them can make it look like
|
|
652
|
+
// ads vanish on scroll and can reduce fill on long topics.
|
|
653
|
+
if (kindClass === 'ezoic-ad-message') return 0;
|
|
540
654
|
if (!items || !items.length) return 0;
|
|
541
655
|
const itemSet = new Set(items);
|
|
542
656
|
const wraps = document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`);
|
|
@@ -562,6 +676,11 @@ function watchWrapForFill(wrap) {
|
|
|
562
676
|
};
|
|
563
677
|
|
|
564
678
|
wraps.forEach((wrap) => {
|
|
679
|
+
// Never prune pinned placements.
|
|
680
|
+
try {
|
|
681
|
+
if (wrap.getAttribute('data-ezoic-pin') === '1') return;
|
|
682
|
+
} catch (e) {}
|
|
683
|
+
|
|
565
684
|
if (isFilled(wrap)) return; // never prune filled ads
|
|
566
685
|
|
|
567
686
|
// Never prune a fresh wrap: it may fill late.
|
|
@@ -601,9 +720,19 @@ function watchWrapForFill(wrap) {
|
|
|
601
720
|
|
|
602
721
|
let removed = 0;
|
|
603
722
|
for (const w of wraps) {
|
|
723
|
+
// Never decluster pinned placements.
|
|
724
|
+
try {
|
|
725
|
+
if (w.getAttribute && w.getAttribute('data-ezoic-pin') === '1') continue;
|
|
726
|
+
} catch (e) {}
|
|
727
|
+
|
|
604
728
|
let prev = w.previousElementSibling;
|
|
605
729
|
for (let i = 0; i < 3 && prev; i++) {
|
|
606
730
|
if (isWrap(prev)) {
|
|
731
|
+
// If the previous wrap is pinned, keep this one (spacing is intentional).
|
|
732
|
+
try {
|
|
733
|
+
if (prev.getAttribute && prev.getAttribute('data-ezoic-pin') === '1') break;
|
|
734
|
+
} catch (e) {}
|
|
735
|
+
|
|
607
736
|
// Don't decluster two "fresh" empty wraps — it can reduce fill on slow auctions.
|
|
608
737
|
// Only decluster when at least one is filled, or when the newer one is stale.
|
|
609
738
|
const prevFilled = isFilled(prev);
|
|
@@ -951,6 +1080,10 @@ function watchWrapForFill(wrap) {
|
|
|
951
1080
|
|
|
952
1081
|
state.heroDoneForPage = true;
|
|
953
1082
|
observePlaceholder(id);
|
|
1083
|
+
// Hero placement is expected to be visible right away (after first item),
|
|
1084
|
+
// so kick a fill request immediately instead of waiting only for IO callbacks.
|
|
1085
|
+
enqueueShow(id);
|
|
1086
|
+
startShowQueue();
|
|
954
1087
|
}
|
|
955
1088
|
|
|
956
1089
|
// ---------------- scheduler ----------------
|
|
@@ -1085,8 +1218,10 @@ function watchWrapForFill(wrap) {
|
|
|
1085
1218
|
state.pageKey = getPageKey();
|
|
1086
1219
|
blockedUntil = 0;
|
|
1087
1220
|
|
|
1221
|
+
muteNoisyConsole();
|
|
1088
1222
|
warmUpNetwork();
|
|
1089
1223
|
patchShowAds();
|
|
1224
|
+
globalGapFixInit();
|
|
1090
1225
|
ensurePreloadObserver();
|
|
1091
1226
|
ensureDomObserver();
|
|
1092
1227
|
|
|
@@ -1138,6 +1273,7 @@ function watchWrapForFill(wrap) {
|
|
|
1138
1273
|
// ---------------- boot ----------------
|
|
1139
1274
|
|
|
1140
1275
|
state.pageKey = getPageKey();
|
|
1276
|
+
muteNoisyConsole();
|
|
1141
1277
|
warmUpNetwork();
|
|
1142
1278
|
patchShowAds();
|
|
1143
1279
|
ensurePreloadObserver();
|