nodebb-plugin-ezoic-infinite 1.6.76 → 1.6.78

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.6.76",
3
+ "version": "1.6.78",
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
@@ -133,29 +133,7 @@ function tightenEzoicMinHeight(wrap) {
133
133
  if (!wrap || !wrap.querySelector) return;
134
134
 
135
135
  const iframes = wrap.querySelectorAll('iframe');
136
-
137
- // If the wrap is still empty, Ezoic often leaves inline `min-height:400px !important` on
138
- // nested `.ezoic-ad` containers, which creates large blank gaps. We can't remove it with CSS.
139
- // We collapse those gaps after a short grace period, while still allowing late fill.
140
- if (!iframes || !iframes.length) {
141
- try {
142
- const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
143
- const age = created ? (now() - created) : 0;
144
- // Give auctions/CMP a bit of time; then collapse the notorious 400px gap.
145
- if (age >= 12000) {
146
- const nodes = wrap.querySelectorAll('.ezoic-ad, .ezoic-ad-adaptive');
147
- nodes.forEach((n) => {
148
- const st = (n.getAttribute('style') || '').toLowerCase();
149
- if (st.includes('min-height:400') || st.includes('min-height: 400') || st.includes('min-height:400px')) {
150
- try { n.style.setProperty('min-height', '1px', 'important'); } catch (e) { n.style.minHeight = '1px'; }
151
- try { n.style.setProperty('height', 'auto', 'important'); } catch (e) {}
152
- try { n.style.setProperty('line-height', '0', 'important'); } catch (e) {}
153
- }
154
- });
155
- }
156
- } catch (e) {}
157
- return;
158
- }
136
+ if (!iframes || !iframes.length) return;
159
137
 
160
138
  // Find the closest "big" ezoic container that carries the 400px min-height.
161
139
  const firstIframe = iframes[0];
@@ -763,49 +741,38 @@ function globalGapFixInit() {
763
741
  // For message/topic pages we may prune filled or empty orphans if they are far away,
764
742
  // otherwise consecutive "stacks" can appear when posts are virtualized.
765
743
  const isMessage = (kindClass === 'ezoic-ad-message');
766
- // IMPORTANT:
767
- // On category/topic lists, NodeBB can virtualize/recycle list items while scrolling.
768
- // If we keep *filled* wraps when their anchor items are gone, they can visually
769
- // "stack" together on upward scroll. We therefore allow filled wraps to be hidden
770
- // when orphaned, but we still avoid fully releasing them (so they can come back
771
- // when anchors reappear).
772
-
773
- // If the wrap is anchored to current items, ensure it is visible.
774
- if (hasNearbyItem(wrap)) {
775
- try { wrap.classList && wrap.classList.remove('ez-orphan-hidden'); wrap.style && (wrap.style.display = ''); } catch (e) {}
776
- return;
777
- }
778
-
779
- // If the anchor item is no longer in the DOM (virtualized), hide the wrap so ads never 'stack'
780
- // back-to-back while scrolling. We'll restore it when its anchor comes back.
781
- try { wrap.classList && wrap.classList.add('ez-orphan-hidden'); wrap.style && (wrap.style.display = 'none'); } catch (e) {}
782
-
783
- // On topic/category lists, never release orphan wraps. Releasing moves placeholders around and makes
784
- // ads appear to 'travel' from top to bottom as you scroll. Parking (hiding) keeps DOM order stable.
785
- const isList = (kindClass === 'ezoic-ad-between' || kindClass === 'ezoic-ad-categories');
786
- if (isList) return;
744
+ if (!isMessage && isFilled(wrap)) return; // never prune filled ads for non-message lists
787
745
 
788
- // Never release a fresh wrap: it may fill late (auction/CMP). Keep it hidden for a while.
746
+ // Never prune a fresh wrap: it may fill late.
789
747
  try {
790
748
  const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
791
749
  if (created && (now() - created) < keepEmptyWrapMs()) return;
792
750
  } catch (e) {}
793
751
 
794
- // For message ads: only release if far offscreen to avoid perceived 'vanishing' during fast scroll.
795
- if (isMessage) {
796
- try {
797
- const r = wrap.getBoundingClientRect();
798
- const vh = Math.max(1, window.innerHeight || 1);
799
- const farAbove = r.bottom < -vh * 2;
800
- const farBelow = r.top > vh * 4;
801
- if (!farAbove && !farBelow) return;
802
- } catch (e) {
803
- return;
804
- }
805
- }
752
+ if (hasNearbyItem(wrap)) {
753
+ try { wrap.classList && wrap.classList.remove('ez-orphan-hidden'); wrap.style && (wrap.style.display = ''); } catch (e) {}
754
+ return;
755
+ }
756
+
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) {}
806
760
 
807
- withInternalDomChange(() => releaseWrapNode(wrap));
808
- removed++;
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++;
809
776
  });
810
777
 
811
778
  return removed;
@@ -851,12 +818,6 @@ function globalGapFixInit() {
851
818
  const prevFilled = isFilled(prev);
852
819
  const curFilled = isFilled(w);
853
820
 
854
- // On topic/category lists, prevent visible stacking by hiding the later wrap immediately.
855
- if ((kindClass === 'ezoic-ad-between' || kindClass === 'ezoic-ad-categories') && prevFilled && curFilled) {
856
- try { w.classList && w.classList.add('ez-stack-hidden'); w.style && (w.style.display = 'none'); } catch (e) {}
857
- break;
858
- }
859
-
860
821
  if (curFilled) {
861
822
  // If the previous one is empty (and not fresh), drop the previous instead.
862
823
  if (!prevFilled && !isFresh(prev)) {
@@ -1058,8 +1019,6 @@ function globalGapFixInit() {
1058
1019
  watchWrapForFill(ww);
1059
1020
  setTimeout(() => { try { tightenEzoicMinHeight(ww); } catch (e) {} }, 900);
1060
1021
  setTimeout(() => { try { tightenEzoicMinHeight(ww); } catch (e) {} }, 2200);
1061
- // Collapse the notorious 400px inline min-height if the slot stays empty.
1062
- setTimeout(() => { try { tightenEzoicMinHeight(ww); } catch (e) {} }, 12500);
1063
1022
  }
1064
1023
  } catch (e) {}
1065
1024
  setTimeout(() => { clearTimeout(hardTimer); release(); }, 650);
@@ -1145,7 +1104,7 @@ function buildOrdinalMap(items) {
1145
1104
  if (!id) {
1146
1105
  // Safe mode: disable recycling for topic message ads to prevent visual "jumping"
1147
1106
  // (ads seemingly moving back under the first post during virtualized scroll).
1148
- const allowRecycle = kindClass !== 'ezoic-ad-message' && kindClass !== 'ezoic-ad-between' && kindClass !== 'ezoic-ad-categories';
1107
+ const allowRecycle = kindClass !== 'ezoic-ad-message';
1149
1108
  recycledWrap = (allowRecycle && scrollDir > 0) ? pickRecyclableWrap(kindClass) : null;
1150
1109
  if (recycledWrap) {
1151
1110
  id = recycledWrap.getAttribute('data-ezoic-wrapid') || '';
@@ -1549,187 +1508,3 @@ function buildOrdinalMap(items) {
1549
1508
  insertHeroAdEarly().catch(() => {});
1550
1509
  requestBurst();
1551
1510
  })();
1552
-
1553
-
1554
-
1555
- // ===== V17 minimal pile-fix (no insert hooks) =====
1556
- (function () {
1557
- // Goal: keep ad injection intact. Only repair when we detect "pile-up" of between wraps.
1558
- var TOPIC_LI_SEL = 'li[component="category/topic"]';
1559
- var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1560
- var HOST_CLASS = 'nodebb-ezoic-host';
1561
-
1562
- var scheduled = false;
1563
- var lastRun = 0;
1564
- var COOLDOWN = 180;
1565
-
1566
- function getTopicList() {
1567
- try {
1568
- var li = document.querySelector(TOPIC_LI_SEL);
1569
- if (!li) return null;
1570
- return li.closest ? li.closest('ul,ol') : null;
1571
- } catch (e) { return null; }
1572
- }
1573
-
1574
- function isHost(node) {
1575
- return !!(node && node.nodeType === 1 && node.tagName === 'LI' && node.classList && node.classList.contains(HOST_CLASS));
1576
- }
1577
-
1578
- function ensureHostForWrap(wrap, ul) {
1579
- try {
1580
- if (!wrap || wrap.nodeType !== 1) return null;
1581
- if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1582
-
1583
- var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1584
- if (host) return host;
1585
-
1586
- if (!ul) ul = wrap.closest ? wrap.closest('ul,ol') : null;
1587
- if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return null;
1588
-
1589
- // Only wrap if direct child of list (invalid / fragile)
1590
- if (wrap.parentElement === ul) {
1591
- host = document.createElement('li');
1592
- host.className = HOST_CLASS;
1593
- host.setAttribute('role', 'listitem');
1594
- host.style.listStyle = 'none';
1595
- host.style.width = '100%';
1596
- ul.insertBefore(host, wrap);
1597
- host.appendChild(wrap);
1598
- try { wrap.style.width = '100%'; } catch (e) {}
1599
- return host;
1600
- }
1601
- } catch (e) {}
1602
- return null;
1603
- }
1604
-
1605
- function previousTopicLi(node) {
1606
- try {
1607
- var prev = node.previousElementSibling;
1608
- while (prev) {
1609
- if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1610
- // skip other hosts/wraps
1611
- prev = prev.previousElementSibling;
1612
- }
1613
- } catch (e) {}
1614
- return null;
1615
- }
1616
-
1617
- function detectPileUp(ul) {
1618
- // Pile-up signature: 2+ between wraps/hosts adjacent with no topics between, often near top.
1619
- try {
1620
- var kids = ul.children;
1621
- var run = 0;
1622
- var maxRun = 0;
1623
- for (var i = 0; i < kids.length; i++) {
1624
- var el = kids[i];
1625
- var isBetween = false;
1626
- if (isHost(el)) {
1627
- isBetween = !!(el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1628
- } else if (el.matches && el.matches(BETWEEN_WRAP_SEL)) {
1629
- isBetween = true;
1630
- }
1631
- if (isBetween) {
1632
- run++;
1633
- if (run > maxRun) maxRun = run;
1634
- } else if (el.matches && el.matches(TOPIC_LI_SEL)) {
1635
- run = 0;
1636
- } else {
1637
- // other nodes reset lightly
1638
- run = 0;
1639
- }
1640
- }
1641
- return maxRun >= 2;
1642
- } catch (e) {}
1643
- return false;
1644
- }
1645
-
1646
- function redistribute(ul) {
1647
- try {
1648
- if (!ul) return;
1649
-
1650
- // Step 1: wrap any direct child between DIVs into LI hosts (makes list stable)
1651
- ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1652
-
1653
- // Step 2: only act if we see pile-up (avoid touching infinite scroll during normal flow)
1654
- if (!detectPileUp(ul)) return;
1655
-
1656
- // Move each host to immediately after the closest previous topic LI at its current position.
1657
- var hosts = ul.querySelectorAll(':scope > li.' + HOST_CLASS);
1658
- hosts.forEach(function(host){
1659
- try {
1660
- var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1661
- if (!wrap) return;
1662
-
1663
- var anchor = previousTopicLi(host);
1664
- if (!anchor) return; // if none, don't move (prevents yanking to top/bottom)
1665
-
1666
- if (host.previousElementSibling !== anchor) {
1667
- anchor.insertAdjacentElement('afterend', host);
1668
- }
1669
- } catch (e) {}
1670
- });
1671
- } catch (e) {}
1672
- }
1673
-
1674
- function schedule(reason) {
1675
- var now = Date.now();
1676
- if (now - lastRun < COOLDOWN) return;
1677
- if (scheduled) return;
1678
- scheduled = true;
1679
- requestAnimationFrame(function () {
1680
- scheduled = false;
1681
- lastRun = Date.now();
1682
- try {
1683
- var ul = getTopicList();
1684
- if (!ul) return;
1685
- redistribute(ul);
1686
- } catch (e) {}
1687
- });
1688
- }
1689
-
1690
- function init() {
1691
- schedule('init');
1692
-
1693
- // Observe only the topic list once available
1694
- try {
1695
- if (typeof MutationObserver !== 'undefined') {
1696
- var observeList = function(ul){
1697
- if (!ul) return;
1698
- var mo = new MutationObserver(function(muts){
1699
- // schedule on any change; redistribute() itself is guarded by pile-up detection
1700
- schedule('mo');
1701
- });
1702
- mo.observe(ul, { childList: true, subtree: true });
1703
- };
1704
-
1705
- var ul = getTopicList();
1706
- if (ul) observeList(ul);
1707
- else {
1708
- var mo2 = new MutationObserver(function(){
1709
- var u2 = getTopicList();
1710
- if (u2) {
1711
- try { observeList(u2); } catch(e){}
1712
- try { mo2.disconnect(); } catch(e){}
1713
- }
1714
- });
1715
- mo2.observe(document.documentElement || document.body, { childList: true, subtree: true });
1716
- }
1717
- }
1718
- } catch (e) {}
1719
-
1720
- // NodeBB events: run after infinite scroll batches
1721
- if (window.jQuery) {
1722
- try {
1723
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1724
- setTimeout(function(){ schedule('event'); }, 50);
1725
- setTimeout(function(){ schedule('event2'); }, 400);
1726
- });
1727
- } catch (e) {}
1728
- }
1729
- }
1730
-
1731
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1732
- else init();
1733
- })();
1734
- // ===== /V17 =====
1735
-
package/public/style.css CHANGED
@@ -79,10 +79,3 @@
79
79
  position: static !important;
80
80
  top: auto !important;
81
81
  }
82
-
83
-
84
- /* ===== V17 host styling ===== */
85
- li.nodebb-ezoic-host { list-style: none; width: 100%; display: block; }
86
- li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; display: block; }
87
- /* ===== /V17 ===== */
88
-