nodebb-plugin-ezoic-infinite 1.6.31 → 1.6.33

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.31",
3
+ "version": "1.6.33",
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
@@ -1514,134 +1514,100 @@ function buildOrdinalMap(items) {
1514
1514
 
1515
1515
 
1516
1516
 
1517
- // ===== V17.3: keep injection; hide top pile on upscroll; rehome EMPTY hosts on downscroll =====
1517
+ // ===== V17.5: Viewport-based pile masking (no moves, keeps injection intact) =====
1518
1518
  (function(){
1519
1519
  var TOPIC_LI_SEL = 'li[component="category/topic"]';
1520
1520
  var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1521
1521
  var HOST_CLASS = 'nodebb-ezoic-host';
1522
1522
  var PILED_ATTR = 'data-ezoic-piled';
1523
1523
 
1524
- var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1525
1524
  var scheduled = false;
1526
1525
  var lastRun = 0;
1527
- var COOLDOWN = 120;
1526
+ var COOLDOWN = 70;
1527
+
1528
+ var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1528
1529
 
1529
1530
  function getY(){ return window.pageYOffset || document.documentElement.scrollTop || 0; }
1530
1531
 
1531
1532
  function getTopicList(){
1532
- try {
1533
+ try{
1533
1534
  var li = document.querySelector(TOPIC_LI_SEL);
1534
1535
  if (!li) return null;
1535
1536
  return li.closest ? li.closest('ul,ol') : null;
1536
- } catch(e){ return null; }
1537
+ }catch(e){ return null; }
1537
1538
  }
1538
1539
 
1539
- function ensureHostForWrap(wrap, ul){
1540
+ function isBetweenNode(el){
1540
1541
  try{
1541
- if (!wrap || wrap.nodeType!==1) return null;
1542
- if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1543
-
1544
- var host = wrap.closest ? wrap.closest('li.'+HOST_CLASS) : null;
1545
- if (host) return host;
1546
-
1547
- if (!ul) ul = wrap.closest ? wrap.closest('ul,ol') : null;
1548
- if (!ul || !(ul.tagName==='UL' || ul.tagName==='OL')) return null;
1549
-
1550
- if (wrap.parentElement === ul){
1551
- host = document.createElement('li');
1552
- host.className = HOST_CLASS;
1553
- host.setAttribute('role','listitem');
1554
- host.style.listStyle='none';
1555
- host.style.width='100%';
1556
- // preserve after
1557
- try {
1558
- var after = wrap.getAttribute('data-ezoic-after');
1559
- if (after) host.setAttribute('data-ezoic-after', after);
1560
- } catch(e){}
1561
- ul.insertBefore(host, wrap);
1562
- host.appendChild(wrap);
1563
- try { wrap.style.width='100%'; } catch(e){}
1564
- return host;
1542
+ if (!el || el.nodeType!==1) return false;
1543
+ if (el.matches && el.matches(BETWEEN_WRAP_SEL)) return true; // ul>div
1544
+ if (el.tagName==='LI' && el.classList && el.classList.contains(HOST_CLASS)) {
1545
+ return !!(el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1565
1546
  }
1566
1547
  }catch(e){}
1567
- return null;
1548
+ return false;
1568
1549
  }
1569
1550
 
1570
- function hostHasAdContent(host){
1571
- // iframes/amp ads mean it's already rendered; moving might reduce fill
1572
- try{
1573
- return !!(host.querySelector && host.querySelector('iframe, amp-ad, amp-iframe, .amp-ads, .ezoic-ad iframe'));
1574
- }catch(e){ return false; }
1551
+ function isTopicNode(el){
1552
+ try{ return !!(el && el.nodeType===1 && el.matches && el.matches(TOPIC_LI_SEL)); }catch(e){ return false; }
1575
1553
  }
1576
1554
 
1577
- function nthTopic(ul, n){
1555
+ function setPiled(el, piled){
1578
1556
  try{
1579
- var topics = ul.querySelectorAll(TOPIC_LI_SEL);
1580
- if (!topics || !topics.length) return null;
1581
- if (n<1) n=1;
1582
- if (n>topics.length) n=topics.length;
1583
- return topics[n-1] || null;
1584
- }catch(e){ return null; }
1557
+ if (!el) return;
1558
+ if (piled) el.setAttribute(PILED_ATTR,'1');
1559
+ else el.removeAttribute(PILED_ATTR);
1560
+ }catch(e){}
1585
1561
  }
1586
1562
 
1587
- function getAfter(host){
1563
+ function clearAll(ul){
1588
1564
  try{
1589
- var v = host.getAttribute('data-ezoic-after');
1590
- if (!v){
1591
- var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1592
- if (wrap) v = wrap.getAttribute('data-ezoic-after');
1593
- }
1594
- var n = parseInt(v,10);
1595
- return isNaN(n)? null : n;
1596
- }catch(e){ return null; }
1565
+ if (!ul) return;
1566
+ ul.querySelectorAll('['+PILED_ATTR+'="1"]').forEach(function(n){ setPiled(n,false); });
1567
+ }catch(e){}
1597
1568
  }
1598
1569
 
1599
- function detectAndHideTopPile(ul){
1600
- // Hide piled hosts near top so user doesn't see the stack.
1570
+ function maskViewportPile(ul){
1571
+ // We mask piled ads based on what is actually near/in the viewport, not just first N children.
1572
+ // This catches cases where virtualization moves some ads but not others.
1601
1573
  try{
1574
+ if (!ul) return;
1575
+ var vh = window.innerHeight || 800;
1576
+ var bandTop = -200; // slightly above viewport
1577
+ var bandBottom = vh * 1.6; // covers top area user sees when returning up
1578
+
1579
+ // collect candidates in band in DOM order
1602
1580
  var kids = ul.children;
1603
- var limit = Math.min(kids.length, 60);
1604
- var run = 0;
1605
- for (var i=0;i<limit;i++){
1581
+ var candidates = [];
1582
+ for (var i=0;i<kids.length;i++){
1606
1583
  var el = kids[i];
1607
- var isBetweenHost = (el.tagName==='LI' && el.classList && el.classList.contains(HOST_CLASS) && el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1608
- var isTopic = (el.matches && el.matches(TOPIC_LI_SEL));
1609
- if (isBetweenHost){
1610
- run++;
1611
- if (run>=2){
1612
- // mark piled beyond the first
1613
- el.setAttribute(PILED_ATTR,'1');
1614
- }
1615
- } else if (isTopic){
1616
- run=0;
1617
- } else {
1618
- // reset on any non-topic
1584
+ if (!isBetweenNode(el) && !isTopicNode(el)) continue;
1585
+ // compute rect (cheap enough on limited band; stop after we've passed far below band)
1586
+ var r = el.getBoundingClientRect ? el.getBoundingClientRect() : null;
1587
+ if (!r) continue;
1588
+ if (r.top > bandBottom && candidates.length > 0) {
1589
+ // Once we've started collecting and we are past band, we can stop scanning further.
1590
+ break;
1619
1591
  }
1592
+ if (r.bottom < bandTop) continue;
1593
+ if (r.top > bandBottom) continue;
1594
+
1595
+ candidates.push(el);
1620
1596
  }
1621
- }catch(e){}
1622
- }
1623
1597
 
1624
- function rehomePiledEmptyOnDownscroll(ul){
1625
- // On downscroll, try to move only EMPTY (not yet rendered) piled hosts to intended anchors and unhide them.
1626
- try{
1627
- var piled = ul.querySelectorAll('li.'+HOST_CLASS+'['+PILED_ATTR+'="1"]');
1628
- if (!piled.length) return;
1629
-
1630
- piled.forEach(function(host){
1631
- try{
1632
- // if it already has rendered ad, just unhide; do not move
1633
- if (hostHasAdContent(host)){
1634
- host.removeAttribute(PILED_ATTR);
1635
- return;
1636
- }
1637
- var after = getAfter(host);
1638
- var anchor = after!=null ? nthTopic(ul, after) : null;
1639
- if (anchor){
1640
- anchor.insertAdjacentElement('afterend', host);
1641
- }
1642
- host.removeAttribute(PILED_ATTR);
1643
- }catch(e){}
1644
- });
1598
+ // Now walk candidates and hide consecutive between nodes (keep first between in a run)
1599
+ var run = 0;
1600
+ for (var j=0;j<candidates.length;j++){
1601
+ var el2 = candidates[j];
1602
+ if (isTopicNode(el2)){
1603
+ run = 0;
1604
+ continue;
1605
+ }
1606
+ if (isBetweenNode(el2)){
1607
+ run++;
1608
+ setPiled(el2, run >= 2);
1609
+ }
1610
+ }
1645
1611
  }catch(e){}
1646
1612
  }
1647
1613
 
@@ -1651,7 +1617,7 @@ function buildOrdinalMap(items) {
1651
1617
  if (scheduled) return;
1652
1618
  scheduled = true;
1653
1619
  requestAnimationFrame(function(){
1654
- scheduled=false;
1620
+ scheduled = false;
1655
1621
  lastRun = Date.now();
1656
1622
  fn();
1657
1623
  });
@@ -1661,42 +1627,49 @@ function buildOrdinalMap(items) {
1661
1627
  var y = getY();
1662
1628
  var dy = y - lastY;
1663
1629
  lastY = y;
1630
+
1664
1631
  var ul = getTopicList();
1665
1632
  if (!ul) return;
1666
1633
 
1667
- // always wrap invalid ul>div (cheap)
1668
- try { ul.querySelectorAll(':scope > '+BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); }); } catch(e){}
1669
-
1670
- if (dy < -8 && y < 900){
1671
- // upscroll near top: hide pile quickly (no moving)
1672
- schedule(function(){ detectAndHideTopPile(ul); });
1673
- } else if (dy > 8){
1674
- // downscroll: rehome only empty piled hosts (safe) and unhide
1675
- schedule(function(){ rehomePiledEmptyOnDownscroll(ul); });
1634
+ // Only manage pile masking when user is heading upward OR is near top-ish.
1635
+ if (dy < -6 || y < 1100){
1636
+ schedule(function(){ maskViewportPile(ul); });
1637
+ } else if (dy > 12 && y > 1400){
1638
+ // when leaving the top area, restore visibility
1639
+ schedule(function(){ clearAll(ul); });
1676
1640
  }
1677
1641
  }
1678
1642
 
1679
1643
  function init(){
1680
1644
  window.addEventListener('scroll', onScroll, { passive:true });
1645
+ window.addEventListener('resize', function(){
1646
+ var ul = getTopicList();
1647
+ if (!ul) return;
1648
+ schedule(function(){ maskViewportPile(ul); });
1649
+ }, { passive:true });
1681
1650
 
1682
1651
  if (window.jQuery){
1683
1652
  try{
1684
1653
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1685
- // wrap invalids only
1686
1654
  setTimeout(function(){
1687
- try{
1688
- var ul = getTopicList();
1689
- if (!ul) return;
1690
- ul.querySelectorAll(':scope > '+BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1691
- }catch(e){}
1692
- }, 50);
1655
+ var ul = getTopicList();
1656
+ if (!ul) return;
1657
+ schedule(function(){ maskViewportPile(ul); });
1658
+ }, 120);
1693
1659
  });
1694
1660
  }catch(e){}
1695
1661
  }
1662
+
1663
+ // initial
1664
+ setTimeout(function(){
1665
+ var ul = getTopicList();
1666
+ if (!ul) return;
1667
+ schedule(function(){ maskViewportPile(ul); });
1668
+ }, 250);
1696
1669
  }
1697
1670
 
1698
1671
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1699
1672
  else init();
1700
1673
  })();
1701
- // ===== /V17.3 =====
1674
+ // ===== /V17.5 =====
1702
1675
 
package/public/style.css CHANGED
@@ -88,10 +88,10 @@ li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; displa
88
88
 
89
89
 
90
90
 
91
- /* ===== V17.3 piled hide ===== */
92
- li.nodebb-ezoic-host[data-ezoic-piled="1"] { display: none !important; }
91
+ /* ===== V17.5 pile hide ===== */
92
+ [data-ezoic-piled="1"] { display: none !important; }
93
93
  /* keep host stable */
94
94
  li.nodebb-ezoic-host { list-style:none; width:100%; display:block; }
95
95
  li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width:100%; display:block; }
96
- /* ===== /V17.3 ===== */
96
+ /* ===== /V17.5 ===== */
97
97