nodebb-plugin-ezoic-infinite 1.5.94 → 1.5.96
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 +219 -2
- package/public/style.css +12 -0
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -704,13 +704,144 @@ function globalGapFixInit() {
|
|
|
704
704
|
}
|
|
705
705
|
|
|
706
706
|
function pruneOrphanWraps(kindClass, items) {
|
|
707
|
+
// Topic pages can be virtualized (posts removed from DOM as you scroll).
|
|
708
|
+
// When that happens, previously-inserted ad wraps may become "orphan" nodes with no
|
|
709
|
+
// nearby post containers, which leads to ads clustering together when scrolling back up.
|
|
710
|
+
// We prune only *true* orphans that are far offscreen to keep the UI stable.
|
|
711
|
+
if (!items || !items.length) return 0;
|
|
712
|
+
const itemSet = new Set(items);
|
|
713
|
+
const wraps = document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`);
|
|
714
|
+
let removed = 0;
|
|
715
|
+
|
|
716
|
+
const isFilled = (wrap) => {
|
|
717
|
+
return !!(wrap.querySelector('iframe, ins, img, video, [data-google-container-id]'));
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const hasNearbyItem = (wrap) => {
|
|
721
|
+
// NodeBB/skins can inject separators/spacers; be tolerant.
|
|
722
|
+
let prev = wrap.previousElementSibling;
|
|
723
|
+
for (let i = 0; i < 14 && prev; i++) {
|
|
724
|
+
if (itemSet.has(prev)) return true;
|
|
725
|
+
prev = prev.previousElementSibling;
|
|
726
|
+
}
|
|
727
|
+
let next = wrap.nextElementSibling;
|
|
728
|
+
for (let i = 0; i < 14 && next; i++) {
|
|
729
|
+
if (itemSet.has(next)) return true;
|
|
730
|
+
next = next.nextElementSibling;
|
|
731
|
+
}
|
|
732
|
+
return false;
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
wraps.forEach((wrap) => {
|
|
736
|
+
// Never prune pinned placements.
|
|
737
|
+
try {
|
|
738
|
+
if (wrap.getAttribute('data-ezoic-pin') === '1') return;
|
|
739
|
+
} catch (e) {}
|
|
740
|
+
|
|
741
|
+
// For message/topic pages we may prune filled or empty orphans if they are far away,
|
|
742
|
+
// otherwise consecutive "stacks" can appear when posts are virtualized.
|
|
743
|
+
const isMessage = (kindClass === 'ezoic-ad-message');
|
|
744
|
+
if (!isMessage && isFilled(wrap)) return; // never prune filled ads for non-message lists
|
|
745
|
+
|
|
746
|
+
// Never prune a fresh wrap: it may fill late.
|
|
747
|
+
try {
|
|
748
|
+
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
749
|
+
if (created && (now() - created) < keepEmptyWrapMs()) return;
|
|
750
|
+
} catch (e) {}
|
|
751
|
+
|
|
752
|
+
if (hasNearbyItem(wrap)) {
|
|
753
|
+
try { wrap.classList && wrap.classList.remove('ez-orphan-hidden'); wrap.style && (wrap.style.display = ''); } catch (e) {}
|
|
707
754
|
return;
|
|
708
755
|
}
|
|
709
756
|
|
|
710
|
-
|
|
711
|
-
|
|
757
|
+
// If the anchor item is no longer in the DOM (virtualized), hide the wrap so ads never "stack"
|
|
758
|
+
// back-to-back while scrolling. We'll recycle it when its anchor comes back.
|
|
759
|
+
try { wrap.classList && wrap.classList.add('ez-orphan-hidden'); wrap.style && (wrap.style.display = 'none'); } catch (e) {}
|
|
760
|
+
|
|
761
|
+
// For message ads: only release if far offscreen to avoid perceived "vanishing" during fast scroll.
|
|
762
|
+
if (isMessage) {
|
|
763
|
+
try {
|
|
764
|
+
const r = wrap.getBoundingClientRect();
|
|
765
|
+
const vh = Math.max(1, window.innerHeight || 1);
|
|
766
|
+
const farAbove = r.bottom < -vh * 2;
|
|
767
|
+
const farBelow = r.top > vh * 4;
|
|
768
|
+
if (!farAbove && !farBelow) return;
|
|
769
|
+
} catch (e) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
712
772
|
}
|
|
713
773
|
|
|
774
|
+
withInternalDomChange(() => releaseWrapNode(wrap));
|
|
775
|
+
removed++;
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
return removed;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function decluster(kindClass) {
|
|
782
|
+
// Remove "near-consecutive" wraps (keep the first). Be tolerant of spacer nodes.
|
|
783
|
+
const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`));
|
|
784
|
+
if (wraps.length < 2) return 0;
|
|
785
|
+
|
|
786
|
+
const isWrap = (el) => !!(el && el.classList && el.classList.contains(WRAP_CLASS));
|
|
787
|
+
|
|
788
|
+
const isFilled = (wrap) => {
|
|
789
|
+
return !!(wrap && wrap.querySelector && wrap.querySelector('iframe, ins, img, video, [data-google-container-id]'));
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const isFresh = (wrap) => {
|
|
793
|
+
try {
|
|
794
|
+
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
795
|
+
return created && (now() - created) < keepEmptyWrapMs();
|
|
796
|
+
} catch (e) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
let removed = 0;
|
|
802
|
+
for (const w of wraps) {
|
|
803
|
+
// Never decluster pinned placements.
|
|
804
|
+
try {
|
|
805
|
+
if (w.getAttribute && w.getAttribute('data-ezoic-pin') === '1') continue;
|
|
806
|
+
} catch (e) {}
|
|
807
|
+
|
|
808
|
+
let prev = w.previousElementSibling;
|
|
809
|
+
for (let i = 0; i < 3 && prev; i++) {
|
|
810
|
+
if (isWrap(prev)) {
|
|
811
|
+
// If the previous wrap is pinned, keep this one (spacing is intentional).
|
|
812
|
+
try {
|
|
813
|
+
if (prev.getAttribute && prev.getAttribute('data-ezoic-pin') === '1') break;
|
|
814
|
+
} catch (e) {}
|
|
815
|
+
|
|
816
|
+
// Never remove a wrap that is already filled; otherwise it looks like
|
|
817
|
+
// ads "disappear" while scrolling. Only remove the empty neighbour.
|
|
818
|
+
const prevFilled = isFilled(prev);
|
|
819
|
+
const curFilled = isFilled(w);
|
|
820
|
+
|
|
821
|
+
if (curFilled) {
|
|
822
|
+
// If the previous one is empty (and not fresh), drop the previous instead.
|
|
823
|
+
if (!prevFilled && !isFresh(prev)) {
|
|
824
|
+
withInternalDomChange(() => releaseWrapNode(prev));
|
|
825
|
+
removed++;
|
|
826
|
+
}
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Current is empty.
|
|
831
|
+
// Don't decluster two "fresh" empty wraps — it can reduce fill on slow auctions.
|
|
832
|
+
// Only decluster when previous is filled, or when current is stale.
|
|
833
|
+
if (prevFilled || !isFresh(w)) {
|
|
834
|
+
withInternalDomChange(() => releaseWrapNode(w));
|
|
835
|
+
removed++;
|
|
836
|
+
}
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
prev = prev.previousElementSibling;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return removed;
|
|
843
|
+
}
|
|
844
|
+
|
|
714
845
|
// ---------------- show (preload / fast fill) ----------------
|
|
715
846
|
|
|
716
847
|
function ensurePreloadObserver() {
|
|
@@ -1341,3 +1472,89 @@ function buildOrdinalMap(items) {
|
|
|
1341
1472
|
insertHeroAdEarly().catch(() => {});
|
|
1342
1473
|
requestBurst();
|
|
1343
1474
|
})();
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
|
|
1478
|
+
// ===== V6.5 AMP-off-between patch =====
|
|
1479
|
+
(function () {
|
|
1480
|
+
function neutralizeAmpInaboxBetween(root) {
|
|
1481
|
+
var scope = root || document;
|
|
1482
|
+
var wraps;
|
|
1483
|
+
try { wraps = scope.querySelectorAll('.nodebb-ezoic-wrap.ezoic-ad-between'); } catch (e) { return; }
|
|
1484
|
+
wraps.forEach(function (wrap) {
|
|
1485
|
+
try {
|
|
1486
|
+
wrap.querySelectorAll('.ezads-sticky-intradiv, [style*="position: sticky"], [style*="position:sticky"]').forEach(function (n) {
|
|
1487
|
+
try { n.style.setProperty('position', 'static', 'important'); } catch (e) {}
|
|
1488
|
+
try { n.style.setProperty('top', 'auto', 'important'); } catch (e) {}
|
|
1489
|
+
try { n.style.setProperty('z-index', '0', 'important'); } catch (e) {}
|
|
1490
|
+
try { n.classList.remove('ezads-sticky-intradiv'); } catch (e) {}
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
wrap.querySelectorAll('iframe[src*="safeframe"], iframe[src*="ampproject"], iframe[src*="doubleclick"]').forEach(function (f) {
|
|
1494
|
+
try { f.style.setProperty('position', 'static', 'important'); } catch (e) {}
|
|
1495
|
+
try { f.style.setProperty('top', 'auto', 'important'); } catch (e) {}
|
|
1496
|
+
try { f.style.setProperty('transform', 'none', 'important'); } catch (e) {}
|
|
1497
|
+
try { f.style.setProperty('margin', '0 auto', 'important'); } catch (e) {}
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
wrap.querySelectorAll('iframe').forEach(function (f) {
|
|
1501
|
+
var p = f.parentElement;
|
|
1502
|
+
if (!p) return;
|
|
1503
|
+
try { p.style.setProperty('position', 'static', 'important'); } catch (e) {}
|
|
1504
|
+
try { p.style.setProperty('top', 'auto', 'important'); } catch (e) {}
|
|
1505
|
+
try { p.style.setProperty('transform', 'none', 'important'); } catch (e) {}
|
|
1506
|
+
});
|
|
1507
|
+
} catch (e) {}
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function installNeutralizer() {
|
|
1512
|
+
try {
|
|
1513
|
+
if (window.__ezoicAmpBetweenNeutralizerInstalled) return;
|
|
1514
|
+
window.__ezoicAmpBetweenNeutralizerInstalled = true;
|
|
1515
|
+
|
|
1516
|
+
neutralizeAmpInaboxBetween(document);
|
|
1517
|
+
|
|
1518
|
+
var mo = new MutationObserver(function (muts) {
|
|
1519
|
+
try {
|
|
1520
|
+
muts.forEach(function (m) {
|
|
1521
|
+
if (!m.addedNodes) return;
|
|
1522
|
+
m.addedNodes.forEach(function (n) {
|
|
1523
|
+
if (!n || n.nodeType !== 1) return;
|
|
1524
|
+
if (n.matches && n.matches('.nodebb-ezoic-wrap.ezoic-ad-between')) {
|
|
1525
|
+
neutralizeAmpInaboxBetween(n.parentNode || document);
|
|
1526
|
+
} else if (n.querySelectorAll && n.querySelector('.nodebb-ezoic-wrap.ezoic-ad-between')) {
|
|
1527
|
+
neutralizeAmpInaboxBetween(n);
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
});
|
|
1531
|
+
} catch (e) {}
|
|
1532
|
+
});
|
|
1533
|
+
mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
|
|
1534
|
+
|
|
1535
|
+
var t = null;
|
|
1536
|
+
window.addEventListener('scroll', function () {
|
|
1537
|
+
if (t) return;
|
|
1538
|
+
t = setTimeout(function () {
|
|
1539
|
+
t = null;
|
|
1540
|
+
neutralizeAmpInaboxBetween(document);
|
|
1541
|
+
}, 120);
|
|
1542
|
+
}, { passive: true });
|
|
1543
|
+
|
|
1544
|
+
if (window.jQuery) {
|
|
1545
|
+
window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function () {
|
|
1546
|
+
setTimeout(function(){ neutralizeAmpInaboxBetween(document); }, 0);
|
|
1547
|
+
setTimeout(function(){ neutralizeAmpInaboxBetween(document); }, 300);
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
} catch (e) {}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
if (document.readyState === 'loading') {
|
|
1554
|
+
document.addEventListener('DOMContentLoaded', installNeutralizer);
|
|
1555
|
+
} else {
|
|
1556
|
+
installNeutralizer();
|
|
1557
|
+
}
|
|
1558
|
+
})();
|
|
1559
|
+
/// ===== /V6.5 =====
|
|
1560
|
+
|
package/public/style.css
CHANGED
|
@@ -79,3 +79,15 @@
|
|
|
79
79
|
position: static !important;
|
|
80
80
|
top: auto !important;
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
/* ===== V6.5 fallback CSS: disable sticky behavior in between wrappers ===== */
|
|
85
|
+
.nodebb-ezoic-wrap.ezoic-ad-between .ezads-sticky-intradiv,
|
|
86
|
+
.nodebb-ezoic-wrap.ezoic-ad-between [style*="position: sticky"],
|
|
87
|
+
.nodebb-ezoic-wrap.ezoic-ad-between [style*="position:sticky"] {
|
|
88
|
+
position: static !important;
|
|
89
|
+
top: auto !important;
|
|
90
|
+
transform: none !important;
|
|
91
|
+
}
|
|
92
|
+
/* ===== /V6.5 ===== */
|
|
93
|
+
|