nodebb-plugin-ezoic-infinite 1.6.19 → 1.6.21

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.19",
3
+ "version": "1.6.21",
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,86 +1511,95 @@ function buildOrdinalMap(items) {
1511
1511
 
1512
1512
 
1513
1513
 
1514
- // ===== V14.2 targeted hook (topic list only) + upscroll reconcile =====
1514
+ // ===== V14.1.1 Hook between-wrap (safe): host repair always, reconcile ONLY on upscroll, never append host to bottom =====
1515
1515
  (function () {
1516
1516
  var BETWEEN_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1517
1517
  var HOST_CLASS = 'nodebb-ezoic-host';
1518
1518
  var TOKEN_ATTR = 'data-ezoic-anchor-token';
1519
- var TOPIC_LI_SEL = 'li[component="category/topic"]';
1520
1519
 
1521
1520
  var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1521
+ var pending = false;
1522
+ var lastRun = 0;
1523
+ var COOLDOWN = 140;
1522
1524
 
1523
- function token() {
1524
- try { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } catch (e) { return String(Date.now()); }
1525
- }
1526
-
1527
- function getScrollY() {
1525
+ function getY() {
1528
1526
  return window.pageYOffset || document.documentElement.scrollTop || 0;
1529
1527
  }
1530
1528
 
1531
- function isTopicList(ul) {
1532
- try { return !!(ul && ul.querySelector && ul.querySelector(TOPIC_LI_SEL)); } catch (e) { return false; }
1529
+ function token() {
1530
+ try { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } catch (e) { return String(Date.now()); }
1533
1531
  }
1534
1532
 
1535
- function closestTopicList(node) {
1533
+ function getListContainer(node) {
1536
1534
  try {
1537
1535
  var ul = node && node.closest ? node.closest('ul,ol') : null;
1538
- if (ul && isTopicList(ul)) return ul;
1539
- // sometimes wrap is outside: search up
1536
+ if (ul && (ul.tagName === 'UL' || ul.tagName === 'OL')) return ul;
1540
1537
  var p = node && node.parentElement;
1541
1538
  while (p) {
1542
- if ((p.tagName === 'UL' || p.tagName === 'OL') && isTopicList(p)) return p;
1539
+ if (p.tagName === 'UL' || p.tagName === 'OL') return p;
1543
1540
  p = p.parentElement;
1544
1541
  }
1545
1542
  } catch (e) {}
1546
1543
  return null;
1547
1544
  }
1548
1545
 
1549
- function closestAnchorTopicLi(hostOrWrap, ul) {
1546
+ function closestNonHostLi(node) {
1550
1547
  try {
1551
- var cur = hostOrWrap;
1548
+ var cur = node;
1552
1549
  if (!cur) return null;
1553
- // if wrap inside host li, start from host
1554
1550
  if (cur.closest) {
1555
- var h = cur.closest('li.' + HOST_CLASS);
1556
- if (h) cur = h;
1551
+ var host = cur.closest('li.' + HOST_CLASS);
1552
+ if (host) cur = host;
1557
1553
  }
1558
1554
  var prev = cur.previousElementSibling;
1559
1555
  while (prev) {
1560
- if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1556
+ if (prev.tagName === 'LI' && !(prev.classList && prev.classList.contains(HOST_CLASS))) return prev;
1561
1557
  prev = prev.previousElementSibling;
1562
1558
  }
1563
1559
  } catch (e) {}
1564
1560
  return null;
1565
1561
  }
1566
1562
 
1567
- function ensureHostAndTokenForWrap(wrap) {
1563
+ function ensureHost(wrap) {
1564
+ // Always ensure UL/OL doesn't directly contain DIV wraps.
1568
1565
  try {
1569
- if (!wrap || wrap.nodeType !== 1) return;
1570
- if (!(wrap.matches && wrap.matches(BETWEEN_SEL))) return;
1566
+ if (!wrap || wrap.nodeType !== 1) return null;
1567
+ if (!(wrap.matches && wrap.matches(BETWEEN_SEL))) return null;
1571
1568
 
1572
- var ul = closestTopicList(wrap);
1573
- if (!ul) return;
1569
+ var ul = getListContainer(wrap);
1570
+ if (!ul) return null;
1574
1571
 
1575
- // Ensure wrap is not direct child of UL/OL
1576
1572
  var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1577
- if (!host) {
1578
- if (wrap.parentElement === ul) {
1579
- host = document.createElement('li');
1580
- host.className = HOST_CLASS;
1581
- host.setAttribute('role', 'listitem');
1582
- host.style.listStyle = 'none';
1583
- host.style.width = '100%';
1584
- ul.insertBefore(host, wrap);
1585
- host.appendChild(wrap);
1586
- }
1573
+ if (host) return host;
1574
+
1575
+ // If direct child of UL/OL, create host in-place (does not affect infinite scroll measurements much)
1576
+ if (wrap.parentElement === ul) {
1577
+ host = document.createElement('li');
1578
+ host.className = HOST_CLASS;
1579
+ host.setAttribute('role', 'listitem');
1580
+ host.style.listStyle = 'none';
1581
+ host.style.width = '100%';
1582
+
1583
+ ul.insertBefore(host, wrap);
1584
+ host.appendChild(wrap);
1585
+ try { wrap.style.width = '100%'; } catch (e) {}
1586
+ return host;
1587
1587
  }
1588
+ } catch (e) {}
1589
+ return null;
1590
+ }
1588
1591
 
1592
+ function tetherHostToAnchor(host, ul) {
1593
+ // Only called on upscroll to avoid breaking load logic.
1594
+ try {
1589
1595
  if (!host) return;
1596
+ var wrap = host.querySelector && host.querySelector(BETWEEN_SEL);
1597
+ if (!wrap) { host.remove(); return; }
1590
1598
 
1591
- // Anchor = previous topic LI only
1592
- var anchorLi = closestAnchorTopicLi(host, ul);
1599
+ // Determine anchor = nearest previous non-host li
1600
+ var anchorLi = closestNonHostLi(host);
1593
1601
  if (!anchorLi) {
1602
+ // No anchor in DOM -> mark orphan (handled on upscroll only)
1594
1603
  host.setAttribute('data-ezoic-orphan', '1');
1595
1604
  return;
1596
1605
  }
@@ -1602,114 +1611,96 @@ function buildOrdinalMap(items) {
1602
1611
  }
1603
1612
  host.setAttribute(TOKEN_ATTR, t);
1604
1613
  host.removeAttribute('data-ezoic-orphan');
1614
+
1615
+ // If host drifted, put it back right after anchor
1616
+ if (host.previousElementSibling !== anchorLi) {
1617
+ anchorLi.insertAdjacentElement('afterend', host);
1618
+ }
1605
1619
  } catch (e) {}
1606
1620
  }
1607
1621
 
1608
- function repairInvalidUlChildren(ul) {
1622
+ function reconcileUpScroll() {
1623
+ var y = getY();
1624
+ var dy = y - lastY;
1625
+ lastY = y;
1626
+
1627
+ // ALWAYS: repair invalid ul>div occurrences (cheap) but don't reorder
1609
1628
  try {
1610
- if (!ul) return;
1611
- // Fix ul>div wraps
1612
- var bad = ul.querySelectorAll(':scope > ' + BETWEEN_SEL);
1613
- bad.forEach(function(wrap){ ensureHostAndTokenForWrap(wrap); });
1629
+ var bad = document.querySelectorAll('ul > ' + BETWEEN_SEL + ', ol > ' + BETWEEN_SEL);
1630
+ bad.forEach(function(w){ ensureHost(w); });
1614
1631
  } catch (e) {}
1615
- }
1616
1632
 
1617
- function cleanupEmptyHosts(ul) {
1633
+ // If NOT upscroll, do nothing else (prevents "ads accumulate at bottom" and avoids scroll blocking)
1634
+ if (dy > -8) return;
1635
+
1636
+ // Up-scroll: tether/reconcile and drop orphan hosts (they cause pile-ups)
1618
1637
  try {
1619
- if (!ul) return;
1620
- var hosts = ul.querySelectorAll('li.' + HOST_CLASS);
1621
- hosts.forEach(function(host){
1622
- try {
1623
- var hasWrap = host.querySelector && host.querySelector(BETWEEN_SEL);
1624
- if (!hasWrap) host.remove();
1625
- } catch (e) {}
1638
+ var wraps = document.querySelectorAll(BETWEEN_SEL);
1639
+ wraps.forEach(function(w){
1640
+ var host = ensureHost(w);
1641
+ if (!host) host = w.closest ? w.closest('li.' + HOST_CLASS) : null;
1642
+ var ul = getListContainer(host || w);
1643
+ if (host && ul) tetherHostToAnchor(host, ul);
1626
1644
  });
1627
- } catch (e) {}
1628
- }
1629
1645
 
1630
- function reconcileUpScroll(ul) {
1631
- // Only reconcile (move/drop) on upscroll to avoid breaking infinite scroll load calculations.
1632
- try {
1633
- if (!ul) return;
1634
- var y = getScrollY();
1635
- var dy = y - lastY;
1636
- lastY = y;
1637
- if (dy >= -10) return; // not an upscroll
1638
-
1639
- // Reconcile / drop
1640
- var hosts = ul.querySelectorAll('li.' + HOST_CLASS + '[' + TOKEN_ATTR + '], li.' + HOST_CLASS + '[data-ezoic-orphan="1"]');
1641
- hosts.forEach(function(host){
1642
- try {
1643
- if (host.getAttribute('data-ezoic-orphan') === '1') { host.remove(); return; }
1644
- var t = host.getAttribute(TOKEN_ATTR);
1645
- if (!t) { host.remove(); return; }
1646
- var anchor = ul.querySelector(TOPIC_LI_SEL + '[' + TOKEN_ATTR + '="' + t + '"]');
1647
- if (!anchor) { host.remove(); return; }
1648
- if (host.previousElementSibling !== anchor) anchor.insertAdjacentElement('afterend', host);
1649
- } catch (e) {}
1646
+ // Drop orphans only on upscroll
1647
+ var orphans = document.querySelectorAll('li.' + HOST_CLASS + '[data-ezoic-orphan="1"]');
1648
+ orphans.forEach(function(h){ try { h.remove(); } catch(e) {} });
1649
+
1650
+ // Drop empty hosts
1651
+ var empties = document.querySelectorAll('li.' + HOST_CLASS);
1652
+ empties.forEach(function(h){
1653
+ try { if (!(h.querySelector && h.querySelector(BETWEEN_SEL))) h.remove(); } catch(e) {}
1650
1654
  });
1651
1655
  } catch (e) {}
1652
1656
  }
1653
1657
 
1654
- function sweepAll(reasonNode) {
1655
- try {
1656
- var ul = closestTopicList(reasonNode || document.querySelector(TOPIC_LI_SEL));
1657
- if (!ul) return;
1658
- repairInvalidUlChildren(ul);
1659
-
1660
- // Tokenize any new wraps within this list
1661
- ul.querySelectorAll(BETWEEN_SEL).forEach(function(wrap){ ensureHostAndTokenForWrap(wrap); });
1662
-
1663
- // Don't move on downscroll; only clean empties
1664
- cleanupEmptyHosts(ul);
1665
- reconcileUpScroll(ul);
1666
- } catch (e) {}
1658
+ function schedule() {
1659
+ var now = Date.now();
1660
+ if (now - lastRun < COOLDOWN) return;
1661
+ if (pending) return;
1662
+ pending = true;
1663
+ requestAnimationFrame(function(){
1664
+ pending = false;
1665
+ lastRun = Date.now();
1666
+ reconcileUpScroll();
1667
+ });
1667
1668
  }
1668
1669
 
1669
- // Process mutations incrementally (no full-document scan)
1670
- function installMO() {
1670
+ function init() {
1671
+ // Initial repair only
1671
1672
  try {
1672
- if (typeof MutationObserver === 'undefined') return;
1673
- var mo = new MutationObserver(function(muts){
1674
- for (var i=0;i<muts.length;i++){
1675
- var m = muts[i];
1676
- if (m.addedNodes && m.addedNodes.length) {
1677
- for (var j=0;j<m.addedNodes.length;j++){
1678
- var n = m.addedNodes[j];
1679
- if (!n || n.nodeType !== 1) continue;
1680
- if (n.matches && n.matches(BETWEEN_SEL)) {
1681
- ensureHostAndTokenForWrap(n);
1682
- } else if (n.querySelector && n.querySelector(BETWEEN_SEL)) {
1683
- n.querySelectorAll(BETWEEN_SEL).forEach(function(w){ ensureHostAndTokenForWrap(w); });
1684
- }
1685
- }
1686
- // light sweep on the container only
1687
- sweepAll(m.target);
1688
- break;
1689
- }
1690
- }
1691
- });
1692
- mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
1673
+ var bad = document.querySelectorAll('ul > ' + BETWEEN_SEL + ', ol > ' + BETWEEN_SEL);
1674
+ bad.forEach(function(w){ ensureHost(w); });
1693
1675
  } catch (e) {}
1694
- }
1695
1676
 
1696
- function init() {
1697
- // initial tokenizing/repair
1698
- sweepAll(document);
1699
- window.addEventListener('scroll', function(){ sweepAll(document); }, { passive: true });
1700
- window.addEventListener('resize', function(){ sweepAll(document); }, { passive: true });
1677
+ window.addEventListener('scroll', schedule, { passive: true });
1678
+ window.addEventListener('resize', schedule, { passive: true });
1701
1679
 
1702
1680
  if (window.jQuery) {
1703
1681
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1704
- setTimeout(function(){ sweepAll(document); }, 0);
1705
- setTimeout(function(){ sweepAll(document); }, 250);
1682
+ setTimeout(schedule, 0);
1683
+ setTimeout(schedule, 250);
1684
+ setTimeout(schedule, 900);
1706
1685
  });
1707
1686
  }
1708
- installMO();
1687
+
1688
+ // MutationObserver: only schedule (no immediate heavy work)
1689
+ try {
1690
+ if (typeof MutationObserver !== 'undefined') {
1691
+ var mo = new MutationObserver(function(muts){
1692
+ for (var i=0;i<muts.length;i++){
1693
+ var m=muts[i];
1694
+ if ((m.addedNodes && m.addedNodes.length) || (m.removedNodes && m.removedNodes.length)) { schedule(); break; }
1695
+ }
1696
+ });
1697
+ mo.observe(document.documentElement || document.body, { childList:true, subtree:true });
1698
+ }
1699
+ } catch (e) {}
1709
1700
  }
1710
1701
 
1711
1702
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1712
1703
  else init();
1713
1704
  })();
1714
- // ===== /V14.2 =====
1705
+ // ===== /V14.1.1 =====
1715
1706
 
package/public/style.css CHANGED
@@ -81,7 +81,7 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V14.2 host ===== */
84
+ /* ===== V14.1.1 host ===== */
85
85
  li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- /* ===== /V14.2 ===== */
86
+ /* ===== /V14.1.1 ===== */
87
87