nodebb-plugin-ezoic-infinite 1.5.71 → 1.5.73
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 +143 -7
- package/public/style.css +3 -0
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
// Smoothness caps
|
|
13
13
|
const MAX_INSERTS_PER_RUN = 3;
|
|
14
14
|
|
|
15
|
+
// Keep empty (unfilled) wraps alive for a while. Topics/messages can fill late (auction/CMP).
|
|
16
|
+
// Pruning too early makes ads look like they "disappear" while scrolling.
|
|
17
|
+
// Keep empty wraps alive; mobile fills can be slow.
|
|
18
|
+
function keepEmptyWrapMs() { return isMobile() ? 120000 : 60000; }
|
|
19
|
+
|
|
15
20
|
// Preload margins
|
|
16
21
|
const PRELOAD_MARGIN_DESKTOP = '2600px 0px 2600px 0px';
|
|
17
22
|
const PRELOAD_MARGIN_MOBILE = '1500px 0px 1500px 0px';
|
|
@@ -33,7 +38,64 @@
|
|
|
33
38
|
// Production build: debug disabled
|
|
34
39
|
function dbg() {}
|
|
35
40
|
|
|
36
|
-
|
|
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 often sets inline min-height (e.g. 400px) for adaptive placements.
|
|
48
|
+
// In NodeBB topics/messages this creates visible empty space under 250px creatives.
|
|
49
|
+
// We tighten the outermost Ezoic container to the actual creative height once it exists.
|
|
50
|
+
function tightenEzoicMinHeight(wrap) {
|
|
51
|
+
try {
|
|
52
|
+
if (!wrap || !wrap.querySelector) return;
|
|
53
|
+
const outer = wrap.querySelector('.ezoic-ad-adaptive') ||
|
|
54
|
+
wrap.querySelector('.ezoic-ad[style*="min-height"]') ||
|
|
55
|
+
wrap.querySelector('.ezoic-ad');
|
|
56
|
+
if (!outer) return;
|
|
57
|
+
|
|
58
|
+
const iframes = outer.querySelectorAll('iframe');
|
|
59
|
+
if (!iframes || !iframes.length) return;
|
|
60
|
+
|
|
61
|
+
let h = 0;
|
|
62
|
+
iframes.forEach((f) => {
|
|
63
|
+
const ah = parseInt(f.getAttribute('height') || '0', 10);
|
|
64
|
+
const oh = f.offsetHeight || 0;
|
|
65
|
+
h = Math.max(h, ah, oh);
|
|
66
|
+
});
|
|
67
|
+
if (!h) return;
|
|
68
|
+
|
|
69
|
+
// Override inline min-height with a newer inline important.
|
|
70
|
+
try { outer.style.setProperty('min-height', h + 'px', 'important'); } catch (e) { outer.style.minHeight = h + 'px'; }
|
|
71
|
+
try { outer.style.setProperty('height', 'auto', 'important'); } catch (e) {}
|
|
72
|
+
|
|
73
|
+
// Mobile friendliness: avoid giant fixed widths causing overflow/reflow.
|
|
74
|
+
if (isMobile()) {
|
|
75
|
+
try { outer.style.setProperty('width', '100%', 'important'); } catch (e) {}
|
|
76
|
+
try { outer.style.setProperty('max-width', '100%', 'important'); } catch (e) {}
|
|
77
|
+
try { outer.style.setProperty('min-width', '0', 'important'); } catch (e) {}
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function watchWrapForFill(wrap) {
|
|
83
|
+
try {
|
|
84
|
+
if (!wrap || wrap.__ezFillObs) return;
|
|
85
|
+
const obs = new MutationObserver(() => {
|
|
86
|
+
if (isFilledNode(wrap)) {
|
|
87
|
+
wrap.classList.remove('is-empty');
|
|
88
|
+
tightenEzoicMinHeight(wrap);
|
|
89
|
+
try { obs.disconnect(); } catch (e) {}
|
|
90
|
+
wrap.__ezFillObs = null;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
obs.observe(wrap, { childList: true, subtree: true });
|
|
94
|
+
wrap.__ezFillObs = obs;
|
|
95
|
+
} catch (e) {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------- state ----------------
|
|
37
99
|
|
|
38
100
|
const state = {
|
|
39
101
|
pageKey: null,
|
|
@@ -207,6 +269,22 @@
|
|
|
207
269
|
return el;
|
|
208
270
|
}
|
|
209
271
|
|
|
272
|
+
function primePlaceholderPool(allIds) {
|
|
273
|
+
try {
|
|
274
|
+
if (!Array.isArray(allIds) || !allIds.length) return;
|
|
275
|
+
const pool = getPoolEl();
|
|
276
|
+
for (const id of allIds) {
|
|
277
|
+
if (!id) continue;
|
|
278
|
+
const domId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
279
|
+
if (document.getElementById(domId)) continue;
|
|
280
|
+
const ph = document.createElement('div');
|
|
281
|
+
ph.id = domId;
|
|
282
|
+
ph.setAttribute('data-ezoic-id', String(id));
|
|
283
|
+
pool.appendChild(ph);
|
|
284
|
+
}
|
|
285
|
+
} catch (e) {}
|
|
286
|
+
}
|
|
287
|
+
|
|
210
288
|
function isInPool(ph) {
|
|
211
289
|
try { return !!(ph && ph.closest && ph.closest('#' + POOL_ID)); } catch (e) { return false; }
|
|
212
290
|
}
|
|
@@ -319,6 +397,13 @@
|
|
|
319
397
|
if (!state.allTopics.length) state.allTopics = parsePool(cfg.placeholderIds);
|
|
320
398
|
if (!state.allPosts.length) state.allPosts = parsePool(cfg.messagePlaceholderIds);
|
|
321
399
|
if (!state.allCategories.length) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
400
|
+
|
|
401
|
+
// Create placeholders up-front in an offscreen pool.
|
|
402
|
+
// Ezoic may attempt to define/show ids during load; if they don't exist yet,
|
|
403
|
+
// it can spam errors and sometimes short-circuit. Pooling keeps ids existing without layout.
|
|
404
|
+
primePlaceholderPool(state.allTopics);
|
|
405
|
+
primePlaceholderPool(state.allPosts);
|
|
406
|
+
primePlaceholderPool(state.allCategories);
|
|
322
407
|
}
|
|
323
408
|
|
|
324
409
|
// ---------------- insertion primitives ----------------
|
|
@@ -328,6 +413,7 @@
|
|
|
328
413
|
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
329
414
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
330
415
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
416
|
+
wrap.setAttribute('data-created', String(now()));
|
|
331
417
|
wrap.style.width = '100%';
|
|
332
418
|
|
|
333
419
|
if (createPlaceholder) {
|
|
@@ -386,6 +472,8 @@
|
|
|
386
472
|
}
|
|
387
473
|
|
|
388
474
|
function pruneOrphanWraps(kindClass, items) {
|
|
475
|
+
// On mobile topics/messages, keep wraps alive longer to avoid 'disappearing' ads.
|
|
476
|
+
if (kindClass === 'ezoic-ad-message' && isMobile()) return 0;
|
|
389
477
|
if (!items || !items.length) return 0;
|
|
390
478
|
const itemSet = new Set(items);
|
|
391
479
|
const wraps = document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`);
|
|
@@ -398,12 +486,12 @@
|
|
|
398
486
|
const hasNearbyItem = (wrap) => {
|
|
399
487
|
// NodeBB/skins can inject separators/spacers; be tolerant.
|
|
400
488
|
let prev = wrap.previousElementSibling;
|
|
401
|
-
for (let i = 0; i <
|
|
489
|
+
for (let i = 0; i < 14 && prev; i++) {
|
|
402
490
|
if (itemSet.has(prev)) return true;
|
|
403
491
|
prev = prev.previousElementSibling;
|
|
404
492
|
}
|
|
405
493
|
let next = wrap.nextElementSibling;
|
|
406
|
-
for (let i = 0; i <
|
|
494
|
+
for (let i = 0; i < 14 && next; i++) {
|
|
407
495
|
if (itemSet.has(next)) return true;
|
|
408
496
|
next = next.nextElementSibling;
|
|
409
497
|
}
|
|
@@ -412,6 +500,13 @@
|
|
|
412
500
|
|
|
413
501
|
wraps.forEach((wrap) => {
|
|
414
502
|
if (isFilled(wrap)) return; // never prune filled ads
|
|
503
|
+
|
|
504
|
+
// Never prune a fresh wrap: it may fill late.
|
|
505
|
+
try {
|
|
506
|
+
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
507
|
+
if (created && (now() - created) < keepEmptyWrapMs()) return;
|
|
508
|
+
} catch (e) {}
|
|
509
|
+
|
|
415
510
|
if (hasNearbyItem(wrap)) return;
|
|
416
511
|
|
|
417
512
|
withInternalDomChange(() => releaseWrapNode(wrap));
|
|
@@ -428,13 +523,32 @@
|
|
|
428
523
|
|
|
429
524
|
const isWrap = (el) => !!(el && el.classList && el.classList.contains(WRAP_CLASS));
|
|
430
525
|
|
|
526
|
+
const isFilled = (wrap) => {
|
|
527
|
+
return !!(wrap && wrap.querySelector && wrap.querySelector('iframe, ins, img, video, [data-google-container-id]'));
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const isFresh = (wrap) => {
|
|
531
|
+
try {
|
|
532
|
+
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
533
|
+
return created && (now() - created) < keepEmptyWrapMs();
|
|
534
|
+
} catch (e) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
431
539
|
let removed = 0;
|
|
432
540
|
for (const w of wraps) {
|
|
433
541
|
let prev = w.previousElementSibling;
|
|
434
542
|
for (let i = 0; i < 3 && prev; i++) {
|
|
435
543
|
if (isWrap(prev)) {
|
|
436
|
-
|
|
437
|
-
|
|
544
|
+
// Don't decluster two "fresh" empty wraps — it can reduce fill on slow auctions.
|
|
545
|
+
// Only decluster when at least one is filled, or when the newer one is stale.
|
|
546
|
+
const prevFilled = isFilled(prev);
|
|
547
|
+
const curFilled = isFilled(w);
|
|
548
|
+
if (prevFilled || curFilled || !isFresh(w)) {
|
|
549
|
+
withInternalDomChange(() => releaseWrapNode(w));
|
|
550
|
+
removed++;
|
|
551
|
+
}
|
|
438
552
|
break;
|
|
439
553
|
}
|
|
440
554
|
prev = prev.previousElementSibling;
|
|
@@ -545,10 +659,23 @@
|
|
|
545
659
|
if (!ph2 || !ph2.isConnected) return;
|
|
546
660
|
const w2 = ph2.closest ? ph2.closest(`.${WRAP_CLASS}`) : null;
|
|
547
661
|
if (!w2) return;
|
|
662
|
+
|
|
663
|
+
// Don't collapse "fresh" placements; slow auctions/CMP can fill late.
|
|
664
|
+
try {
|
|
665
|
+
const created = parseInt(w2.getAttribute('data-created') || '0', 10);
|
|
666
|
+
if (created && (now() - created) < keepEmptyWrapMs()) return;
|
|
667
|
+
} catch (e) {}
|
|
668
|
+
|
|
548
669
|
const hasAd = !!(ph2.querySelector && ph2.querySelector('iframe, ins, img, .ez-ad, .ezoic-ad'));
|
|
549
|
-
if (!hasAd)
|
|
670
|
+
if (!hasAd) {
|
|
671
|
+
w2.classList.add('is-empty');
|
|
672
|
+
watchWrapForFill(w2);
|
|
673
|
+
} else {
|
|
674
|
+
w2.classList.remove('is-empty');
|
|
675
|
+
tightenEzoicMinHeight(w2);
|
|
676
|
+
}
|
|
550
677
|
} catch (e) {}
|
|
551
|
-
},
|
|
678
|
+
}, 15000);
|
|
552
679
|
} catch (e) {}
|
|
553
680
|
}
|
|
554
681
|
|
|
@@ -584,6 +711,15 @@
|
|
|
584
711
|
const doShow = () => {
|
|
585
712
|
try { ez.showAds(id); } catch (e) {}
|
|
586
713
|
try { markEmptyWrapper(id); } catch (e) {}
|
|
714
|
+
try {
|
|
715
|
+
const phw = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
716
|
+
const ww = phw && phw.closest ? phw.closest(`.${WRAP_CLASS}`) : null;
|
|
717
|
+
if (ww) {
|
|
718
|
+
watchWrapForFill(ww);
|
|
719
|
+
setTimeout(() => { try { tightenEzoicMinHeight(ww); } catch (e) {} }, 900);
|
|
720
|
+
setTimeout(() => { try { tightenEzoicMinHeight(ww); } catch (e) {} }, 2200);
|
|
721
|
+
}
|
|
722
|
+
} catch (e) {}
|
|
587
723
|
setTimeout(() => { clearTimeout(hardTimer); release(); }, 650);
|
|
588
724
|
};
|
|
589
725
|
|