nodebb-plugin-ezoic-infinite 1.6.33 → 1.6.34

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.33",
3
+ "version": "1.6.34",
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,110 +1514,144 @@ function buildOrdinalMap(items) {
1514
1514
 
1515
1515
 
1516
1516
 
1517
- // ===== V17.5: Viewport-based pile masking (no moves, keeps injection intact) =====
1517
+ // ===== V17.3: keep injection; hide top pile on upscroll; rehome EMPTY hosts on downscroll =====
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;
1524
1525
  var scheduled = false;
1525
1526
  var lastRun = 0;
1526
- var COOLDOWN = 70;
1527
-
1528
- var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1527
+ var COOLDOWN = 120;
1529
1528
 
1530
1529
  function getY(){ return window.pageYOffset || document.documentElement.scrollTop || 0; }
1531
1530
 
1532
1531
  function getTopicList(){
1533
- try{
1532
+ try {
1534
1533
  var li = document.querySelector(TOPIC_LI_SEL);
1535
1534
  if (!li) return null;
1536
1535
  return li.closest ? li.closest('ul,ol') : null;
1537
- }catch(e){ return null; }
1536
+ } catch(e){ return null; }
1538
1537
  }
1539
1538
 
1540
- function isBetweenNode(el){
1539
+ function ensureHostForWrap(wrap, ul){
1541
1540
  try{
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));
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;
1546
1565
  }
1547
1566
  }catch(e){}
1548
- return false;
1567
+ return null;
1549
1568
  }
1550
1569
 
1551
- function isTopicNode(el){
1552
- try{ return !!(el && el.nodeType===1 && el.matches && el.matches(TOPIC_LI_SEL)); }catch(e){ return false; }
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; }
1553
1575
  }
1554
1576
 
1555
- function setPiled(el, piled){
1577
+ function nthTopic(ul, n){
1556
1578
  try{
1557
- if (!el) return;
1558
- if (piled) el.setAttribute(PILED_ATTR,'1');
1559
- else el.removeAttribute(PILED_ATTR);
1560
- }catch(e){}
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; }
1561
1585
  }
1562
1586
 
1563
- function clearAll(ul){
1587
+ function getAfter(host){
1564
1588
  try{
1565
- if (!ul) return;
1566
- ul.querySelectorAll('['+PILED_ATTR+'="1"]').forEach(function(n){ setPiled(n,false); });
1567
- }catch(e){}
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; }
1568
1597
  }
1569
1598
 
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.
1599
+ function detectAndHideTopPile(ul){
1600
+ // Hide piled hosts near top so user doesn't see the stack.
1573
1601
  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
1580
1602
  var kids = ul.children;
1581
- var candidates = [];
1582
- for (var i=0;i<kids.length;i++){
1583
- var el = kids[i];
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;
1591
- }
1592
- if (r.bottom < bandTop) continue;
1593
- if (r.top > bandBottom) continue;
1594
-
1595
- candidates.push(el);
1596
- }
1597
-
1598
- // Now walk candidates and hide consecutive between nodes (keep first between in a run)
1603
+ var limit = Math.min(kids.length, 60);
1599
1604
  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)){
1605
+ for (var i=0;i<limit;i++){
1606
+ 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){
1607
1610
  run++;
1608
- setPiled(el2, run >= 2);
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
1609
1619
  }
1610
1620
  }
1611
1621
  }catch(e){}
1612
1622
  }
1613
1623
 
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
+ });
1645
+ }catch(e){}
1646
+ }
1647
+
1614
1648
  function schedule(fn){
1615
1649
  var now = Date.now();
1616
1650
  if (now-lastRun < COOLDOWN) return;
1617
1651
  if (scheduled) return;
1618
1652
  scheduled = true;
1619
1653
  requestAnimationFrame(function(){
1620
- scheduled = false;
1654
+ scheduled=false;
1621
1655
  lastRun = Date.now();
1622
1656
  fn();
1623
1657
  });
@@ -1627,49 +1661,42 @@ function buildOrdinalMap(items) {
1627
1661
  var y = getY();
1628
1662
  var dy = y - lastY;
1629
1663
  lastY = y;
1630
-
1631
1664
  var ul = getTopicList();
1632
1665
  if (!ul) return;
1633
1666
 
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); });
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); });
1640
1676
  }
1641
1677
  }
1642
1678
 
1643
1679
  function init(){
1644
1680
  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 });
1650
1681
 
1651
1682
  if (window.jQuery){
1652
1683
  try{
1653
1684
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1685
+ // wrap invalids only
1654
1686
  setTimeout(function(){
1655
- var ul = getTopicList();
1656
- if (!ul) return;
1657
- schedule(function(){ maskViewportPile(ul); });
1658
- }, 120);
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);
1659
1693
  });
1660
1694
  }catch(e){}
1661
1695
  }
1662
-
1663
- // initial
1664
- setTimeout(function(){
1665
- var ul = getTopicList();
1666
- if (!ul) return;
1667
- schedule(function(){ maskViewportPile(ul); });
1668
- }, 250);
1669
1696
  }
1670
1697
 
1671
1698
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1672
1699
  else init();
1673
1700
  })();
1674
- // ===== /V17.5 =====
1701
+ // ===== /V17.3 =====
1675
1702
 
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.5 pile hide ===== */
92
- [data-ezoic-piled="1"] { display: none !important; }
91
+ /* ===== V17.3 piled hide ===== */
92
+ li.nodebb-ezoic-host[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.5 ===== */
96
+ /* ===== /V17.3 ===== */
97
97