nodebb-plugin-ezoic-infinite 1.5.75 → 1.5.77
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 +94 -12
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) {
|
|
@@ -95,11 +135,8 @@ function tightenEzoicMinHeight(wrap) {
|
|
|
95
135
|
}
|
|
96
136
|
if (!maxBottom) return;
|
|
97
137
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
if (report && report.offsetHeight) {
|
|
101
|
-
maxBottom += report.offsetHeight;
|
|
102
|
-
}
|
|
138
|
+
// NOTE: Do NOT add the Ezoic badge (reportline) height here.
|
|
139
|
+
// It is absolutely positioned and should not reserve layout space.
|
|
103
140
|
|
|
104
141
|
const h = Math.max(1, Math.ceil(maxBottom));
|
|
105
142
|
|
|
@@ -189,6 +226,9 @@ function globalGapFixInit() {
|
|
|
189
226
|
if (window.__nodebbEzoicGapFix) return;
|
|
190
227
|
window.__nodebbEzoicGapFix = true;
|
|
191
228
|
|
|
229
|
+
// Observe only the main content area to minimize overhead.
|
|
230
|
+
const root = document.getElementById('content') || document.querySelector('[component="content"], #panel') || document.body;
|
|
231
|
+
|
|
192
232
|
const inPostArea = (el) => {
|
|
193
233
|
try {
|
|
194
234
|
return !!(el && el.closest && el.closest('[component="post"], .topic, .posts, [component="topic"]'));
|
|
@@ -209,21 +249,37 @@ function globalGapFixInit() {
|
|
|
209
249
|
});
|
|
210
250
|
};
|
|
211
251
|
|
|
212
|
-
requestAnimationFrame(() => maybeFix(
|
|
252
|
+
requestAnimationFrame(() => maybeFix(root));
|
|
253
|
+
|
|
254
|
+
// Batch DOM mutation processing into a single rAF to avoid doing work per mutation.
|
|
255
|
+
const pending = new Set();
|
|
256
|
+
let scheduled = false;
|
|
257
|
+
const scheduleFlush = () => {
|
|
258
|
+
if (scheduled) return;
|
|
259
|
+
scheduled = true;
|
|
260
|
+
requestAnimationFrame(() => {
|
|
261
|
+
scheduled = false;
|
|
262
|
+
pending.forEach((n) => {
|
|
263
|
+
try { maybeFix(n); } catch (e) {}
|
|
264
|
+
});
|
|
265
|
+
pending.clear();
|
|
266
|
+
});
|
|
267
|
+
};
|
|
213
268
|
|
|
214
269
|
const obs = new MutationObserver((muts) => {
|
|
215
270
|
for (const m of muts) {
|
|
216
|
-
const t = m.target;
|
|
217
271
|
if (m.type === 'attributes') {
|
|
218
|
-
|
|
272
|
+
const t = m.target && m.target.nodeType === 1 ? m.target : m.target && m.target.parentElement;
|
|
273
|
+
if (t) pending.add(t);
|
|
219
274
|
} else if (m.addedNodes && m.addedNodes.length) {
|
|
220
275
|
m.addedNodes.forEach((n) => {
|
|
221
|
-
if (n && n.nodeType === 1)
|
|
276
|
+
if (n && n.nodeType === 1) pending.add(n);
|
|
222
277
|
});
|
|
223
278
|
}
|
|
224
279
|
}
|
|
280
|
+
if (pending.size) scheduleFlush();
|
|
225
281
|
});
|
|
226
|
-
obs.observe(
|
|
282
|
+
obs.observe(root, { subtree: true, childList: true, attributes: true, attributeFilter: ['style'] });
|
|
227
283
|
} catch (e) {}
|
|
228
284
|
}
|
|
229
285
|
|
|
@@ -546,6 +602,10 @@ function globalGapFixInit() {
|
|
|
546
602
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
547
603
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
548
604
|
wrap.setAttribute('data-created', String(now()));
|
|
605
|
+
// "Pinned" placements (after the first item) should remain stable.
|
|
606
|
+
if (afterPos === 1) {
|
|
607
|
+
wrap.setAttribute('data-ezoic-pin', '1');
|
|
608
|
+
}
|
|
549
609
|
wrap.style.width = '100%';
|
|
550
610
|
|
|
551
611
|
if (createPlaceholder) {
|
|
@@ -604,8 +664,9 @@ function globalGapFixInit() {
|
|
|
604
664
|
}
|
|
605
665
|
|
|
606
666
|
function pruneOrphanWraps(kindClass, items) {
|
|
607
|
-
//
|
|
608
|
-
|
|
667
|
+
// Message ads should remain stable (desktop + mobile). Pruning them can make it look like
|
|
668
|
+
// ads vanish on scroll and can reduce fill on long topics.
|
|
669
|
+
if (kindClass === 'ezoic-ad-message') return 0;
|
|
609
670
|
if (!items || !items.length) return 0;
|
|
610
671
|
const itemSet = new Set(items);
|
|
611
672
|
const wraps = document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`);
|
|
@@ -631,6 +692,11 @@ function globalGapFixInit() {
|
|
|
631
692
|
};
|
|
632
693
|
|
|
633
694
|
wraps.forEach((wrap) => {
|
|
695
|
+
// Never prune pinned placements.
|
|
696
|
+
try {
|
|
697
|
+
if (wrap.getAttribute('data-ezoic-pin') === '1') return;
|
|
698
|
+
} catch (e) {}
|
|
699
|
+
|
|
634
700
|
if (isFilled(wrap)) return; // never prune filled ads
|
|
635
701
|
|
|
636
702
|
// Never prune a fresh wrap: it may fill late.
|
|
@@ -670,9 +736,19 @@ function globalGapFixInit() {
|
|
|
670
736
|
|
|
671
737
|
let removed = 0;
|
|
672
738
|
for (const w of wraps) {
|
|
739
|
+
// Never decluster pinned placements.
|
|
740
|
+
try {
|
|
741
|
+
if (w.getAttribute && w.getAttribute('data-ezoic-pin') === '1') continue;
|
|
742
|
+
} catch (e) {}
|
|
743
|
+
|
|
673
744
|
let prev = w.previousElementSibling;
|
|
674
745
|
for (let i = 0; i < 3 && prev; i++) {
|
|
675
746
|
if (isWrap(prev)) {
|
|
747
|
+
// If the previous wrap is pinned, keep this one (spacing is intentional).
|
|
748
|
+
try {
|
|
749
|
+
if (prev.getAttribute && prev.getAttribute('data-ezoic-pin') === '1') break;
|
|
750
|
+
} catch (e) {}
|
|
751
|
+
|
|
676
752
|
// Don't decluster two "fresh" empty wraps — it can reduce fill on slow auctions.
|
|
677
753
|
// Only decluster when at least one is filled, or when the newer one is stale.
|
|
678
754
|
const prevFilled = isFilled(prev);
|
|
@@ -1020,6 +1096,10 @@ function globalGapFixInit() {
|
|
|
1020
1096
|
|
|
1021
1097
|
state.heroDoneForPage = true;
|
|
1022
1098
|
observePlaceholder(id);
|
|
1099
|
+
// Hero placement is expected to be visible right away (after first item),
|
|
1100
|
+
// so kick a fill request immediately instead of waiting only for IO callbacks.
|
|
1101
|
+
enqueueShow(id);
|
|
1102
|
+
startShowQueue();
|
|
1023
1103
|
}
|
|
1024
1104
|
|
|
1025
1105
|
// ---------------- scheduler ----------------
|
|
@@ -1154,6 +1234,7 @@ function globalGapFixInit() {
|
|
|
1154
1234
|
state.pageKey = getPageKey();
|
|
1155
1235
|
blockedUntil = 0;
|
|
1156
1236
|
|
|
1237
|
+
muteNoisyConsole();
|
|
1157
1238
|
warmUpNetwork();
|
|
1158
1239
|
patchShowAds();
|
|
1159
1240
|
globalGapFixInit();
|
|
@@ -1208,6 +1289,7 @@ function globalGapFixInit() {
|
|
|
1208
1289
|
// ---------------- boot ----------------
|
|
1209
1290
|
|
|
1210
1291
|
state.pageKey = getPageKey();
|
|
1292
|
+
muteNoisyConsole();
|
|
1211
1293
|
warmUpNetwork();
|
|
1212
1294
|
patchShowAds();
|
|
1213
1295
|
ensurePreloadObserver();
|