nodebb-plugin-ezoic-infinite 1.5.70 → 1.5.72
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 -76
- package/public/style.css +6 -4
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -12,6 +12,10 @@
|
|
|
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
|
+
const KEEP_EMPTY_WRAP_MS = 30000;
|
|
18
|
+
|
|
15
19
|
// Preload margins
|
|
16
20
|
const PRELOAD_MARGIN_DESKTOP = '2600px 0px 2600px 0px';
|
|
17
21
|
const PRELOAD_MARGIN_MOBILE = '1500px 0px 1500px 0px';
|
|
@@ -207,6 +211,22 @@
|
|
|
207
211
|
return el;
|
|
208
212
|
}
|
|
209
213
|
|
|
214
|
+
function primePlaceholderPool(allIds) {
|
|
215
|
+
try {
|
|
216
|
+
if (!Array.isArray(allIds) || !allIds.length) return;
|
|
217
|
+
const pool = getPoolEl();
|
|
218
|
+
for (const id of allIds) {
|
|
219
|
+
if (!id) continue;
|
|
220
|
+
const domId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
221
|
+
if (document.getElementById(domId)) continue;
|
|
222
|
+
const ph = document.createElement('div');
|
|
223
|
+
ph.id = domId;
|
|
224
|
+
ph.setAttribute('data-ezoic-id', String(id));
|
|
225
|
+
pool.appendChild(ph);
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {}
|
|
228
|
+
}
|
|
229
|
+
|
|
210
230
|
function isInPool(ph) {
|
|
211
231
|
try { return !!(ph && ph.closest && ph.closest('#' + POOL_ID)); } catch (e) { return false; }
|
|
212
232
|
}
|
|
@@ -319,6 +339,13 @@
|
|
|
319
339
|
if (!state.allTopics.length) state.allTopics = parsePool(cfg.placeholderIds);
|
|
320
340
|
if (!state.allPosts.length) state.allPosts = parsePool(cfg.messagePlaceholderIds);
|
|
321
341
|
if (!state.allCategories.length) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
342
|
+
|
|
343
|
+
// Create placeholders up-front in an offscreen pool.
|
|
344
|
+
// Ezoic may attempt to define/show ids during load; if they don't exist yet,
|
|
345
|
+
// it can spam errors and sometimes short-circuit. Pooling keeps ids existing without layout.
|
|
346
|
+
primePlaceholderPool(state.allTopics);
|
|
347
|
+
primePlaceholderPool(state.allPosts);
|
|
348
|
+
primePlaceholderPool(state.allCategories);
|
|
322
349
|
}
|
|
323
350
|
|
|
324
351
|
// ---------------- insertion primitives ----------------
|
|
@@ -328,6 +355,7 @@
|
|
|
328
355
|
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
329
356
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
330
357
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
358
|
+
wrap.setAttribute('data-created', String(now()));
|
|
331
359
|
wrap.style.width = '100%';
|
|
332
360
|
|
|
333
361
|
if (createPlaceholder) {
|
|
@@ -391,43 +419,79 @@
|
|
|
391
419
|
const wraps = document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`);
|
|
392
420
|
let removed = 0;
|
|
393
421
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
422
|
+
const isFilled = (wrap) => {
|
|
423
|
+
return !!(wrap.querySelector('iframe, ins, img, video, [data-google-container-id]'));
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const hasNearbyItem = (wrap) => {
|
|
427
|
+
// NodeBB/skins can inject separators/spacers; be tolerant.
|
|
398
428
|
let prev = wrap.previousElementSibling;
|
|
399
|
-
for (let i = 0; i <
|
|
400
|
-
if (itemSet.has(prev))
|
|
429
|
+
for (let i = 0; i < 14 && prev; i++) {
|
|
430
|
+
if (itemSet.has(prev)) return true;
|
|
401
431
|
prev = prev.previousElementSibling;
|
|
402
432
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (itemSet.has(next)) { ok = true; break; }
|
|
408
|
-
next = next.nextElementSibling;
|
|
409
|
-
}
|
|
433
|
+
let next = wrap.nextElementSibling;
|
|
434
|
+
for (let i = 0; i < 14 && next; i++) {
|
|
435
|
+
if (itemSet.has(next)) return true;
|
|
436
|
+
next = next.nextElementSibling;
|
|
410
437
|
}
|
|
438
|
+
return false;
|
|
439
|
+
};
|
|
411
440
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
441
|
+
wraps.forEach((wrap) => {
|
|
442
|
+
if (isFilled(wrap)) return; // never prune filled ads
|
|
443
|
+
|
|
444
|
+
// Never prune a fresh wrap: it may fill late.
|
|
445
|
+
try {
|
|
446
|
+
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
447
|
+
if (created && (now() - created) < KEEP_EMPTY_WRAP_MS) return;
|
|
448
|
+
} catch (e) {}
|
|
449
|
+
|
|
450
|
+
if (hasNearbyItem(wrap)) return;
|
|
451
|
+
|
|
452
|
+
withInternalDomChange(() => releaseWrapNode(wrap));
|
|
453
|
+
removed++;
|
|
416
454
|
});
|
|
417
455
|
|
|
418
456
|
return removed;
|
|
419
457
|
}
|
|
420
458
|
|
|
421
459
|
function decluster(kindClass) {
|
|
422
|
-
// Remove consecutive wraps (keep the first)
|
|
460
|
+
// Remove "near-consecutive" wraps (keep the first). Be tolerant of spacer nodes.
|
|
423
461
|
const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`));
|
|
424
462
|
if (wraps.length < 2) return 0;
|
|
463
|
+
|
|
464
|
+
const isWrap = (el) => !!(el && el.classList && el.classList.contains(WRAP_CLASS));
|
|
465
|
+
|
|
466
|
+
const isFilled = (wrap) => {
|
|
467
|
+
return !!(wrap && wrap.querySelector && wrap.querySelector('iframe, ins, img, video, [data-google-container-id]'));
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const isFresh = (wrap) => {
|
|
471
|
+
try {
|
|
472
|
+
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
473
|
+
return created && (now() - created) < KEEP_EMPTY_WRAP_MS;
|
|
474
|
+
} catch (e) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
425
479
|
let removed = 0;
|
|
426
480
|
for (const w of wraps) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
481
|
+
let prev = w.previousElementSibling;
|
|
482
|
+
for (let i = 0; i < 3 && prev; i++) {
|
|
483
|
+
if (isWrap(prev)) {
|
|
484
|
+
// Don't decluster two "fresh" empty wraps — it can reduce fill on slow auctions.
|
|
485
|
+
// Only decluster when at least one is filled, or when the newer one is stale.
|
|
486
|
+
const prevFilled = isFilled(prev);
|
|
487
|
+
const curFilled = isFilled(w);
|
|
488
|
+
if (prevFilled || curFilled || !isFresh(w)) {
|
|
489
|
+
withInternalDomChange(() => releaseWrapNode(w));
|
|
490
|
+
removed++;
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
prev = prev.previousElementSibling;
|
|
431
495
|
}
|
|
432
496
|
}
|
|
433
497
|
return removed;
|
|
@@ -479,9 +543,6 @@
|
|
|
479
543
|
if (!ph || !ph.isConnected) return;
|
|
480
544
|
ph.setAttribute('data-ezoic-id', String(id));
|
|
481
545
|
|
|
482
|
-
// When the ad actually fills, tighten height to the real iframe height and remove empty-collapsing.
|
|
483
|
-
armFillObserver(ph);
|
|
484
|
-
|
|
485
546
|
const io = ensurePreloadObserver();
|
|
486
547
|
try { io && io.observe(ph); } catch (e) {}
|
|
487
548
|
|
|
@@ -494,56 +555,6 @@
|
|
|
494
555
|
} catch (e) {}
|
|
495
556
|
}
|
|
496
557
|
|
|
497
|
-
// ---------------- fill tightening ----------------
|
|
498
|
-
|
|
499
|
-
const _fillObserved = new WeakSet();
|
|
500
|
-
function tightenFilled(ph) {
|
|
501
|
-
if (!ph || !ph.isConnected) return;
|
|
502
|
-
const wrap = ph.closest ? ph.closest(`.${WRAP_CLASS}`) : null;
|
|
503
|
-
if (!wrap) return;
|
|
504
|
-
|
|
505
|
-
// If we have any real ad content, we should not be in "empty" mode.
|
|
506
|
-
const frame = ph.querySelector ? ph.querySelector('iframe') : null;
|
|
507
|
-
const hasAd = !!(frame || (ph.querySelector && ph.querySelector('ins, img, .ez-ad, .ezoic-ad')));
|
|
508
|
-
if (!hasAd) return;
|
|
509
|
-
|
|
510
|
-
try { wrap.classList.remove('is-empty'); } catch (e) {}
|
|
511
|
-
|
|
512
|
-
// Remove the common "extra reserved space" created by Ezoic's min-height styles.
|
|
513
|
-
// Set minHeight to the actual iframe height when available.
|
|
514
|
-
try {
|
|
515
|
-
if (frame && frame.offsetHeight) {
|
|
516
|
-
const h = frame.offsetHeight;
|
|
517
|
-
const ezoicSpan = ph.closest ? ph.closest('span.ezoic-ad, .ezoic-ad') : null;
|
|
518
|
-
if (ezoicSpan && ezoicSpan.style) {
|
|
519
|
-
ezoicSpan.style.minHeight = `${h}px`;
|
|
520
|
-
}
|
|
521
|
-
// Also tighten the wrapper itself to avoid "space under" when the parent is taller.
|
|
522
|
-
wrap.style.minHeight = `${h}px`;
|
|
523
|
-
}
|
|
524
|
-
} catch (e) {}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function armFillObserver(ph) {
|
|
528
|
-
if (!ph || _fillObserved.has(ph)) return;
|
|
529
|
-
_fillObserved.add(ph);
|
|
530
|
-
|
|
531
|
-
// Fast path: already filled
|
|
532
|
-
try { tightenFilled(ph); } catch (e) {}
|
|
533
|
-
|
|
534
|
-
// Observe until it fills, then disconnect.
|
|
535
|
-
try {
|
|
536
|
-
const mo = new MutationObserver(() => {
|
|
537
|
-
try {
|
|
538
|
-
tightenFilled(ph);
|
|
539
|
-
const hasFrame = !!(ph.querySelector && ph.querySelector('iframe'));
|
|
540
|
-
if (hasFrame) mo.disconnect();
|
|
541
|
-
} catch (e) {}
|
|
542
|
-
});
|
|
543
|
-
mo.observe(ph, { childList: true, subtree: true });
|
|
544
|
-
} catch (e) {}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
558
|
function enqueueShow(id) {
|
|
548
559
|
if (!id || isBlocked()) return;
|
|
549
560
|
|
|
@@ -588,10 +599,17 @@
|
|
|
588
599
|
if (!ph2 || !ph2.isConnected) return;
|
|
589
600
|
const w2 = ph2.closest ? ph2.closest(`.${WRAP_CLASS}`) : null;
|
|
590
601
|
if (!w2) return;
|
|
602
|
+
|
|
603
|
+
// Don't collapse "fresh" placements; slow auctions/CMP can fill late.
|
|
604
|
+
try {
|
|
605
|
+
const created = parseInt(w2.getAttribute('data-created') || '0', 10);
|
|
606
|
+
if (created && (now() - created) < KEEP_EMPTY_WRAP_MS) return;
|
|
607
|
+
} catch (e) {}
|
|
608
|
+
|
|
591
609
|
const hasAd = !!(ph2.querySelector && ph2.querySelector('iframe, ins, img, .ez-ad, .ezoic-ad'));
|
|
592
610
|
if (!hasAd) w2.classList.add('is-empty');
|
|
593
611
|
} catch (e) {}
|
|
594
|
-
},
|
|
612
|
+
}, 15000);
|
|
595
613
|
} catch (e) {}
|
|
596
614
|
}
|
|
597
615
|
|
package/public/style.css
CHANGED
|
@@ -22,10 +22,6 @@
|
|
|
22
22
|
.nodebb-ezoic-wrap .ezoic-ad {
|
|
23
23
|
margin: 0 !important;
|
|
24
24
|
padding: 0 !important;
|
|
25
|
-
/* Ezoic sometimes reserves extra height (e.g. min-height: 400px) even when the filled iframe is smaller.
|
|
26
|
-
We prefer zero unused space; JS will also tighten after fill to the real iframe height. */
|
|
27
|
-
min-height: 0 !important;
|
|
28
|
-
height: auto !important;
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
/* Remove the classic "gap under iframe" (baseline/inline-block) */
|
|
@@ -66,3 +62,9 @@
|
|
|
66
62
|
margin: 0 !important;
|
|
67
63
|
padding: 0 !important;
|
|
68
64
|
}
|
|
65
|
+
/* Remove Ezoic's large reserved min-height inside our wrappers (topics/messages) */
|
|
66
|
+
.nodebb-ezoic-wrap .ezoic-ad,
|
|
67
|
+
.nodebb-ezoic-wrap span.ezoic-ad {
|
|
68
|
+
min-height: 1px !important; /* kill 400px gaps */
|
|
69
|
+
height: auto !important;
|
|
70
|
+
}
|