nodebb-plugin-ezoic-infinite 1.5.96 → 1.5.98

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.96",
3
+ "version": "1.5.98",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -704,143 +704,12 @@ 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) {}
754
707
  return;
755
708
  }
756
709
 
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
- }
772
- }
773
-
774
- withInternalDomChange(() => releaseWrapNode(wrap));
775
- removed++;
776
- });
777
-
778
- return removed;
779
- }
780
-
781
710
  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
- }
711
+ return;
712
+ }
844
713
 
845
714
  // ---------------- show (preload / fast fill) ----------------
846
715
 
@@ -1475,86 +1344,70 @@ function buildOrdinalMap(items) {
1475
1344
 
1476
1345
 
1477
1346
 
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
1347
 
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
1348
 
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
1349
 
1511
- function installNeutralizer() {
1512
- try {
1513
- if (window.__ezoicAmpBetweenNeutralizerInstalled) return;
1514
- window.__ezoicAmpBetweenNeutralizerInstalled = true;
1350
+ // ===== V7.1 soft up-scroll purge for ezoic-ad-between =====
1351
+ (function () {
1352
+ var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1353
+ var ticking = false;
1354
+ var lastPurgeTs = 0;
1355
+ var PURGE_COOLDOWN_MS = 350;
1356
+
1357
+ function softPurgeBetweenWraps() {
1358
+ var now = Date.now();
1359
+ if (now - lastPurgeTs < PURGE_COOLDOWN_MS) return;
1360
+ lastPurgeTs = now;
1515
1361
 
1516
- neutralizeAmpInaboxBetween(document);
1362
+ var viewportTop = window.pageYOffset || document.documentElement.scrollTop || 0;
1363
+ var keepBuffer = 240; // garde les pubs proches du viewport pour éviter le "clignotement"
1517
1364
 
1518
- var mo = new MutationObserver(function (muts) {
1365
+ try {
1366
+ document.querySelectorAll('.nodebb-ezoic-wrap.ezoic-ad-between').forEach(function (w) {
1519
1367
  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
- });
1368
+ var r = w.getBoundingClientRect();
1369
+ var absBottom = viewportTop + r.bottom;
1370
+ // SOFT: supprimer uniquement les wraps largement au-dessus du viewport
1371
+ if (absBottom < (viewportTop - keepBuffer)) {
1372
+ w.remove();
1373
+ }
1531
1374
  } catch (e) {}
1532
1375
  });
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
1376
  } catch (e) {}
1551
1377
  }
1552
1378
 
1553
- if (document.readyState === 'loading') {
1554
- document.addEventListener('DOMContentLoaded', installNeutralizer);
1555
- } else {
1556
- installNeutralizer();
1379
+ function onScroll() {
1380
+ if (ticking) return;
1381
+ ticking = true;
1382
+ requestAnimationFrame(function () {
1383
+ var y = window.pageYOffset || document.documentElement.scrollTop || 0;
1384
+ var dy = y - lastY;
1385
+
1386
+ // uniquement quand on remonte franchement
1387
+ if (dy < -12) {
1388
+ softPurgeBetweenWraps();
1389
+ }
1390
+
1391
+ lastY = y;
1392
+ ticking = false;
1393
+ });
1394
+ }
1395
+
1396
+ window.addEventListener('scroll', onScroll, { passive: true });
1397
+
1398
+ // nettoyage doux après chargement infini / navigation ajax
1399
+ function postLoadSoftPurge() {
1400
+ setTimeout(softPurgeBetweenWraps, 0);
1401
+ setTimeout(softPurgeBetweenWraps, 220);
1402
+ setTimeout(softPurgeBetweenWraps, 600);
1403
+ }
1404
+
1405
+ if (window.jQuery) {
1406
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', postLoadSoftPurge);
1557
1407
  }
1408
+
1409
+ // premier passage
1410
+ setTimeout(softPurgeBetweenWraps, 0);
1558
1411
  })();
1559
- /// ===== /V6.5 =====
1412
+ // ===== /V7.1 =====
1560
1413
 
package/public/style.css CHANGED
@@ -79,15 +79,3 @@
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
-