nodebb-plugin-ezoic-infinite 1.6.29 → 1.6.31

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.29",
3
+ "version": "1.6.31",
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
@@ -1511,176 +1511,192 @@ function buildOrdinalMap(items) {
1511
1511
 
1512
1512
 
1513
1513
 
1514
- // ===== V17.1 upscroll-only top pile-up fixer (keeps injection intact) =====
1515
- (function () {
1514
+
1515
+
1516
+
1517
+ // ===== V17.3: keep injection; hide top pile on upscroll; rehome EMPTY hosts on downscroll =====
1518
+ (function(){
1516
1519
  var TOPIC_LI_SEL = 'li[component="category/topic"]';
1517
1520
  var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1518
1521
  var HOST_CLASS = 'nodebb-ezoic-host';
1522
+ var PILED_ATTR = 'data-ezoic-piled';
1519
1523
 
1524
+ var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1520
1525
  var scheduled = false;
1521
1526
  var lastRun = 0;
1522
- var COOLDOWN = 140;
1523
-
1524
- var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1527
+ var COOLDOWN = 120;
1525
1528
 
1526
- function getY() {
1527
- return window.pageYOffset || document.documentElement.scrollTop || 0;
1528
- }
1529
+ function getY(){ return window.pageYOffset || document.documentElement.scrollTop || 0; }
1529
1530
 
1530
- function getTopicList() {
1531
+ function getTopicList(){
1531
1532
  try {
1532
1533
  var li = document.querySelector(TOPIC_LI_SEL);
1533
1534
  if (!li) return null;
1534
1535
  return li.closest ? li.closest('ul,ol') : null;
1535
- } catch (e) { return null; }
1536
- }
1537
-
1538
- function isHost(node) {
1539
- return !!(node && node.nodeType === 1 && node.tagName === 'LI' && node.classList && node.classList.contains(HOST_CLASS));
1536
+ } catch(e){ return null; }
1540
1537
  }
1541
1538
 
1542
- function ensureHostForWrap(wrap, ul) {
1543
- // only wrap invalid ul>div cases; do not interfere with normal injection
1544
- try {
1545
- if (!wrap || wrap.nodeType !== 1) return null;
1539
+ function ensureHostForWrap(wrap, ul){
1540
+ try{
1541
+ if (!wrap || wrap.nodeType!==1) return null;
1546
1542
  if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1547
1543
 
1548
- var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1544
+ var host = wrap.closest ? wrap.closest('li.'+HOST_CLASS) : null;
1549
1545
  if (host) return host;
1550
1546
 
1551
1547
  if (!ul) ul = wrap.closest ? wrap.closest('ul,ol') : null;
1552
- if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return null;
1548
+ if (!ul || !(ul.tagName==='UL' || ul.tagName==='OL')) return null;
1553
1549
 
1554
- if (wrap.parentElement === ul) {
1550
+ if (wrap.parentElement === ul){
1555
1551
  host = document.createElement('li');
1556
1552
  host.className = HOST_CLASS;
1557
- host.setAttribute('role', 'listitem');
1558
- host.style.listStyle = 'none';
1559
- host.style.width = '100%';
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){}
1560
1561
  ul.insertBefore(host, wrap);
1561
1562
  host.appendChild(wrap);
1562
- try { wrap.style.width = '100%'; } catch (e) {}
1563
+ try { wrap.style.width='100%'; } catch(e){}
1563
1564
  return host;
1564
1565
  }
1565
- } catch (e) {}
1566
+ }catch(e){}
1566
1567
  return null;
1567
1568
  }
1568
1569
 
1569
- function previousTopicLi(node) {
1570
- try {
1571
- var prev = node.previousElementSibling;
1572
- while (prev) {
1573
- if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1574
- prev = prev.previousElementSibling;
1575
- }
1576
- } catch (e) {}
1577
- return null;
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; }
1578
1575
  }
1579
1576
 
1580
- function detectTopPileUp(ul) {
1581
- // Detect a pile-up near the top: within first ~40 children, find 2+ between ads without topics between.
1582
- try {
1583
- var kids = ul.children;
1584
- var limit = Math.min(kids.length, 40);
1585
- var run = 0;
1586
- var maxRun = 0;
1587
- for (var i=0;i<limit;i++){
1588
- var el = kids[i];
1589
- var isBetween = false;
1590
- if (isHost(el)) isBetween = !!(el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1591
- else if (el.matches && el.matches(BETWEEN_WRAP_SEL)) isBetween = true;
1577
+ function nthTopic(ul, n){
1578
+ 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; }
1585
+ }
1592
1586
 
1593
- if (isBetween) { run++; if (run>maxRun) maxRun=run; }
1594
- else if (el.matches && el.matches(TOPIC_LI_SEL)) { run=0; }
1595
- else { run=0; }
1587
+ function getAfter(host){
1588
+ 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');
1596
1593
  }
1597
- return maxRun >= 2;
1598
- } catch (e) {}
1599
- return false;
1594
+ var n = parseInt(v,10);
1595
+ return isNaN(n)? null : n;
1596
+ }catch(e){ return null; }
1600
1597
  }
1601
1598
 
1602
- function redistributeTopPile(ul) {
1603
- try {
1604
- if (!ul) return;
1605
-
1606
- // Step 0: wrap invalid top-level between divs
1607
- try { ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); }); } catch(e){}
1608
-
1609
- if (!detectTopPileUp(ul)) return;
1610
-
1611
- // Only move hosts that are currently in the top region (first 60 list children).
1599
+ function detectAndHideTopPile(ul){
1600
+ // Hide piled hosts near top so user doesn't see the stack.
1601
+ try{
1612
1602
  var kids = ul.children;
1613
1603
  var limit = Math.min(kids.length, 60);
1604
+ var run = 0;
1614
1605
  for (var i=0;i<limit;i++){
1615
1606
  var el = kids[i];
1616
- if (!isHost(el)) continue;
1617
- var host = el;
1618
- var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1619
- if (!wrap) continue;
1620
-
1621
- var anchor = previousTopicLi(host);
1622
- if (!anchor) continue;
1623
- if (host.previousElementSibling !== anchor) {
1624
- anchor.insertAdjacentElement('afterend', host);
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
1625
1619
  }
1626
1620
  }
1627
- } catch (e) {}
1621
+ }catch(e){}
1628
1622
  }
1629
1623
 
1630
- function schedule(reason) {
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
+
1648
+ function schedule(fn){
1631
1649
  var now = Date.now();
1632
- if (now - lastRun < COOLDOWN) return;
1650
+ if (now-lastRun < COOLDOWN) return;
1633
1651
  if (scheduled) return;
1634
1652
  scheduled = true;
1635
1653
  requestAnimationFrame(function(){
1636
- scheduled = false;
1654
+ scheduled=false;
1637
1655
  lastRun = Date.now();
1638
- try {
1639
- var ul = getTopicList();
1640
- if (!ul) return;
1641
- redistributeTopPile(ul);
1642
- } catch (e) {}
1656
+ fn();
1643
1657
  });
1644
1658
  }
1645
1659
 
1646
- function onScroll() {
1660
+ function onScroll(){
1647
1661
  var y = getY();
1648
1662
  var dy = y - lastY;
1649
1663
  lastY = y;
1650
-
1651
- // Only act on upscroll (when the pile-up manifests)
1652
- if (dy < -6) schedule('upscroll');
1664
+ var ul = getTopicList();
1665
+ if (!ul) return;
1666
+
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); });
1676
+ }
1653
1677
  }
1654
1678
 
1655
- function init() {
1656
- // Initial repair does nothing unless pile-up already present.
1657
- schedule('init');
1658
-
1659
- window.addEventListener('scroll', onScroll, { passive: true });
1679
+ function init(){
1680
+ window.addEventListener('scroll', onScroll, { passive:true });
1660
1681
 
1661
- // MutationObserver on list (guarded; redistribution only runs on upscroll)
1662
- try {
1663
- if (typeof MutationObserver !== 'undefined') {
1664
- var ul = getTopicList();
1665
- if (ul) {
1666
- var mo = new MutationObserver(function(){ /* no immediate redistribution */ });
1667
- mo.observe(ul, { childList:true, subtree:true });
1668
- }
1669
- }
1670
- } catch (e) {}
1671
-
1672
- if (window.jQuery) {
1673
- try {
1682
+ if (window.jQuery){
1683
+ try{
1674
1684
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1675
- // don't touch during downscroll load; just re-evaluate when user scrolls up
1676
- setTimeout(function(){ schedule('event'); }, 0);
1685
+ // wrap invalids only
1686
+ 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);
1677
1693
  });
1678
- } catch(e){}
1694
+ }catch(e){}
1679
1695
  }
1680
1696
  }
1681
1697
 
1682
1698
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1683
1699
  else init();
1684
1700
  })();
1685
- // ===== /V17.1 =====
1701
+ // ===== /V17.3 =====
1686
1702
 
package/public/style.css CHANGED
@@ -81,8 +81,17 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V17.1 host styling ===== */
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
+
89
+
90
+
91
+ /* ===== V17.3 piled hide ===== */
92
+ li.nodebb-ezoic-host[data-ezoic-piled="1"] { display: none !important; }
93
+ /* keep host stable */
85
94
  li.nodebb-ezoic-host { list-style:none; width:100%; display:block; }
86
95
  li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width:100%; display:block; }
87
- /* ===== /V17.1 ===== */
96
+ /* ===== /V17.3 ===== */
88
97