nodebb-plugin-ezoic-infinite 1.6.63 → 1.6.65
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 +23 -21
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -401,6 +401,7 @@
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
// ─── Insertion primitives ───────────────────────────────────────────────────
|
|
404
|
+
|
|
404
405
|
function buildWrap(id, kindClass, afterPos, createPlaceholder) {
|
|
405
406
|
const wrap = document.createElement('div');
|
|
406
407
|
wrap.className = WRAP_CLASS + ' ' + kindClass;
|
|
@@ -469,23 +470,16 @@
|
|
|
469
470
|
if (!items || !items.length) return 0;
|
|
470
471
|
const itemSet = new Set(items);
|
|
471
472
|
const isMessage = kindClass === 'ezoic-ad-message';
|
|
472
|
-
// Between
|
|
473
|
-
// Releasing
|
|
474
|
-
// anchor topics return (scroll-up / NodeBB re-render), injectBetween would
|
|
475
|
-
// re-insert those IDs, but DOM ordinals have shifted → all wraps pile at top.
|
|
476
|
-
// Keeping the wrap node in DOM at its original position means findWrap() will
|
|
477
|
-
// always find it and injectBetween() will never create a duplicate.
|
|
473
|
+
// Between/category ads are NEVER fully released — only hidden/shown.
|
|
474
|
+
// Releasing frees IDs into the pool → re-injection at wrong positions.
|
|
478
475
|
const allowRelease = isMessage;
|
|
479
476
|
let removed = 0;
|
|
480
477
|
|
|
481
478
|
const hasNearbyItem = (wrap) => {
|
|
482
|
-
// If
|
|
483
|
-
// siblings, not the wrap's own siblings (the wrap has no siblings inside host).
|
|
479
|
+
// If wrap is inside a li.nodebb-ezoic-host, check the HOST's siblings.
|
|
484
480
|
const pivot = (wrap.parentElement && wrap.parentElement.classList &&
|
|
485
481
|
wrap.parentElement.classList.contains(HOST_CLASS))
|
|
486
|
-
? wrap.parentElement
|
|
487
|
-
: wrap;
|
|
488
|
-
|
|
482
|
+
? wrap.parentElement : wrap;
|
|
489
483
|
let prev = pivot.previousElementSibling;
|
|
490
484
|
for (let i = 0; i < 14 && prev; i++) {
|
|
491
485
|
if (itemSet.has(prev)) return true;
|
|
@@ -500,31 +494,24 @@
|
|
|
500
494
|
};
|
|
501
495
|
|
|
502
496
|
document.querySelectorAll('.' + WRAP_CLASS + '.' + kindClass).forEach((wrap) => {
|
|
503
|
-
// Never touch pinned placements.
|
|
504
497
|
if (wrap.getAttribute('data-ezoic-pin') === '1') return;
|
|
505
|
-
|
|
506
|
-
// Never prune very fresh wraps (slow auction / CMP fills).
|
|
507
498
|
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
508
499
|
if (created && (now() - created) < keepEmptyWrapMs()) return;
|
|
509
500
|
|
|
510
501
|
if (hasNearbyItem(wrap)) {
|
|
511
|
-
// Anchor back in DOM → restore visibility.
|
|
512
502
|
try { wrap.classList.remove('ez-orphan-hidden'); wrap.style.display = ''; } catch (e) {}
|
|
513
503
|
return;
|
|
514
504
|
}
|
|
515
505
|
|
|
516
|
-
// Anchor
|
|
506
|
+
// Anchor gone → hide.
|
|
517
507
|
try { wrap.classList.add('ez-orphan-hidden'); wrap.style.display = 'none'; } catch (e) {}
|
|
518
|
-
|
|
519
|
-
// For between/category ads: stop here — never release the node.
|
|
520
|
-
// The wrap stays in DOM (hidden) at its correct data-ezoic-after position.
|
|
521
508
|
if (!allowRelease) return;
|
|
522
509
|
|
|
523
|
-
//
|
|
510
|
+
// Message-ads only: release when far off-screen.
|
|
524
511
|
try {
|
|
525
512
|
const r = wrap.getBoundingClientRect();
|
|
526
513
|
const vh = Math.max(1, window.innerHeight || 1);
|
|
527
|
-
if (r.bottom > -vh * 2 && r.top < vh * 4) return;
|
|
514
|
+
if (r.bottom > -vh * 2 && r.top < vh * 4) return;
|
|
528
515
|
} catch (e) { return; }
|
|
529
516
|
|
|
530
517
|
withInternalDomChange(() => releaseWrapNode(wrap));
|
|
@@ -922,10 +909,25 @@
|
|
|
922
909
|
const maxInserts = MAX_INSERTS_PER_RUN + (isBoosted() ? 1 : 0);
|
|
923
910
|
let inserted = 0;
|
|
924
911
|
|
|
912
|
+
// Viewport top — we never inject after an element that is fully above this.
|
|
913
|
+
// This is the definitive guard against NodeBB loading content above the fold
|
|
914
|
+
// and our scanner immediately injecting ads on those newly-loaded top items.
|
|
915
|
+
// We use a generous negative margin so items just barely scrolled above still
|
|
916
|
+
// get ads injected (they'll be visible when the user scrolls back a little).
|
|
917
|
+
const viewportSafeTop = -(window.innerHeight || 800) * 0.5;
|
|
918
|
+
|
|
925
919
|
for (const afterPos of targets) {
|
|
926
920
|
if (inserted >= maxInserts) break;
|
|
927
921
|
const el = ordinalMap.get(afterPos);
|
|
928
922
|
if (!el || !el.isConnected) continue;
|
|
923
|
+
|
|
924
|
+
// Skip elements that are well above the viewport.
|
|
925
|
+
// getBoundingClientRect is cheap on modern engines (no forced layout).
|
|
926
|
+
try {
|
|
927
|
+
const rect = el.getBoundingClientRect();
|
|
928
|
+
if (rect.bottom < viewportSafeTop) continue;
|
|
929
|
+
} catch (e) {}
|
|
930
|
+
|
|
929
931
|
if (isAdjacentAd(el)) continue;
|
|
930
932
|
if (findWrap(kindClass, afterPos)) continue;
|
|
931
933
|
|