nodebb-plugin-ezoic-infinite 1.6.18 → 1.6.19

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.18",
3
+ "version": "1.6.19",
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,172 +1511,205 @@ function buildOrdinalMap(items) {
1511
1511
 
1512
1512
 
1513
1513
 
1514
- // ===== V14.1 Hook any between-wrap and tether to nearest topic LI =====
1514
+ // ===== V14.2 targeted hook (topic list only) + upscroll reconcile =====
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 pendingSweep = false;
1520
- var lastSweep = 0;
1521
- var COOLDOWN = 120;
1519
+ var TOPIC_LI_SEL = 'li[component="category/topic"]';
1520
+
1521
+ var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1522
1522
 
1523
1523
  function token() {
1524
1524
  try { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } catch (e) { return String(Date.now()); }
1525
1525
  }
1526
1526
 
1527
- function closestTopicLi(node) {
1528
- // Find the topic <li> that this wrap should be associated with:
1529
- // prefer previous sibling <li> that is NOT a host, else climb to nearest previous in the list.
1527
+ function getScrollY() {
1528
+ return window.pageYOffset || document.documentElement.scrollTop || 0;
1529
+ }
1530
+
1531
+ function isTopicList(ul) {
1532
+ try { return !!(ul && ul.querySelector && ul.querySelector(TOPIC_LI_SEL)); } catch (e) { return false; }
1533
+ }
1534
+
1535
+ function closestTopicList(node) {
1530
1536
  try {
1531
- if (!node) return null;
1532
- var cur = node;
1533
- // If wrap is inside a host li, start from host
1534
- if (cur.closest) {
1535
- var host = cur.closest('li.' + HOST_CLASS);
1536
- if (host) cur = host;
1537
+ var ul = node && node.closest ? node.closest('ul,ol') : null;
1538
+ if (ul && isTopicList(ul)) return ul;
1539
+ // sometimes wrap is outside: search up
1540
+ var p = node && node.parentElement;
1541
+ while (p) {
1542
+ if ((p.tagName === 'UL' || p.tagName === 'OL') && isTopicList(p)) return p;
1543
+ p = p.parentElement;
1537
1544
  }
1538
- // Find list container
1539
- var ul = cur.parentElement;
1540
- while (ul && !(ul.tagName === 'UL' || ul.tagName === 'OL')) ul = ul.parentElement;
1541
- if (!ul) return null;
1545
+ } catch (e) {}
1546
+ return null;
1547
+ }
1542
1548
 
1543
- // Start from current element position within ul
1549
+ function closestAnchorTopicLi(hostOrWrap, ul) {
1550
+ try {
1551
+ var cur = hostOrWrap;
1552
+ if (!cur) return null;
1553
+ // if wrap inside host li, start from host
1554
+ if (cur.closest) {
1555
+ var h = cur.closest('li.' + HOST_CLASS);
1556
+ if (h) cur = h;
1557
+ }
1544
1558
  var prev = cur.previousElementSibling;
1545
1559
  while (prev) {
1546
- if (prev.tagName === 'LI' && !(prev.classList && prev.classList.contains(HOST_CLASS))) return prev;
1560
+ if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1547
1561
  prev = prev.previousElementSibling;
1548
1562
  }
1549
1563
  } catch (e) {}
1550
1564
  return null;
1551
1565
  }
1552
1566
 
1553
- function ensureHostForWrap(wrap) {
1567
+ function ensureHostAndTokenForWrap(wrap) {
1554
1568
  try {
1555
1569
  if (!wrap || wrap.nodeType !== 1) return;
1556
1570
  if (!(wrap.matches && wrap.matches(BETWEEN_SEL))) return;
1557
1571
 
1558
- // Already in a host?
1559
- var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1560
- var ul = wrap.parentElement;
1561
- while (ul && !(ul.tagName === 'UL' || ul.tagName === 'OL')) ul = ul.parentElement;
1572
+ var ul = closestTopicList(wrap);
1562
1573
  if (!ul) return;
1563
1574
 
1564
- // Ensure wrap is inside UL only via LI host (never direct ul>div)
1575
+ // Ensure wrap is not direct child of UL/OL
1576
+ var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1565
1577
  if (!host) {
1566
- host = document.createElement('li');
1567
- host.className = HOST_CLASS;
1568
- host.setAttribute('role', 'listitem');
1569
- host.style.listStyle = 'none';
1570
- host.style.width = '100%';
1571
- // Insert host at wrap position
1572
- ul.insertBefore(host, wrap);
1573
- host.appendChild(wrap);
1574
- } else if (host.parentElement !== ul) {
1575
- // move host into correct list container if needed
1576
- ul.appendChild(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
+ }
1577
1587
  }
1578
1588
 
1579
- // Tether to nearest topic li and tag both with same token
1580
- var anchorLi = closestTopicLi(host) || closestTopicLi(wrap);
1581
- if (anchorLi) {
1582
- var t = anchorLi.getAttribute(TOKEN_ATTR);
1583
- if (!t) {
1584
- t = token();
1585
- anchorLi.setAttribute(TOKEN_ATTR, t);
1586
- }
1587
- host.setAttribute(TOKEN_ATTR, t);
1588
- // keep host just after anchor
1589
- if (host.previousElementSibling !== anchorLi) {
1590
- anchorLi.insertAdjacentElement('afterend', host);
1591
- }
1592
- } else {
1593
- // No anchor -> it's dangerous; mark host so we can drop it later
1589
+ if (!host) return;
1590
+
1591
+ // Anchor = previous topic LI only
1592
+ var anchorLi = closestAnchorTopicLi(host, ul);
1593
+ if (!anchorLi) {
1594
1594
  host.setAttribute('data-ezoic-orphan', '1');
1595
+ return;
1596
+ }
1597
+
1598
+ var t = anchorLi.getAttribute(TOKEN_ATTR);
1599
+ if (!t) {
1600
+ t = token();
1601
+ anchorLi.setAttribute(TOKEN_ATTR, t);
1595
1602
  }
1603
+ host.setAttribute(TOKEN_ATTR, t);
1604
+ host.removeAttribute('data-ezoic-orphan');
1596
1605
  } catch (e) {}
1597
1606
  }
1598
1607
 
1599
- function sweepAll() {
1600
- var now = Date.now();
1601
- if (now - lastSweep < COOLDOWN) return;
1602
- lastSweep = now;
1608
+ function repairInvalidUlChildren(ul) {
1609
+ 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); });
1614
+ } catch (e) {}
1615
+ }
1603
1616
 
1617
+ function cleanupEmptyHosts(ul) {
1604
1618
  try {
1605
- // 1) repair direct ul>div wraps
1606
- var bad = document.querySelectorAll('ul > ' + BETWEEN_SEL + ', ol > ' + BETWEEN_SEL);
1607
- bad.forEach(ensureHostForWrap);
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) {}
1626
+ });
1627
+ } catch (e) {}
1628
+ }
1608
1629
 
1609
- // 2) ensure any between wrap anywhere is hosted+tethered
1610
- var wraps = document.querySelectorAll(BETWEEN_SEL);
1611
- wraps.forEach(ensureHostForWrap);
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
1612
1638
 
1613
- // 3) reconcile/drop hosts that lost their anchor (virtualized)
1614
- var hosts = document.querySelectorAll('li.' + HOST_CLASS + '[' + TOKEN_ATTR + ']');
1639
+ // Reconcile / drop
1640
+ var hosts = ul.querySelectorAll('li.' + HOST_CLASS + '[' + TOKEN_ATTR + '], li.' + HOST_CLASS + '[data-ezoic-orphan="1"]');
1615
1641
  hosts.forEach(function(host){
1616
1642
  try {
1617
- var ul = host.parentElement;
1618
- if (!ul) { host.remove(); return; }
1643
+ if (host.getAttribute('data-ezoic-orphan') === '1') { host.remove(); return; }
1619
1644
  var t = host.getAttribute(TOKEN_ATTR);
1620
1645
  if (!t) { host.remove(); return; }
1621
- var anchor = ul.querySelector('li[' + TOKEN_ATTR + '="' + t + '"]');
1622
- if (!anchor) {
1623
- host.remove();
1624
- return;
1625
- }
1626
- if (host.previousElementSibling !== anchor) {
1627
- anchor.insertAdjacentElement('afterend', host);
1628
- }
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);
1629
1649
  } catch (e) {}
1630
1650
  });
1631
-
1632
- // 4) drop explicit orphans
1633
- var orphans = document.querySelectorAll('li.' + HOST_CLASS + '[data-ezoic-orphan="1"]');
1634
- orphans.forEach(function(h){ try { h.remove(); } catch(e) {} });
1635
1651
  } catch (e) {}
1636
1652
  }
1637
1653
 
1638
- function scheduleSweep() {
1639
- if (pendingSweep) return;
1640
- pendingSweep = true;
1641
- requestAnimationFrame(function(){
1642
- pendingSweep = false;
1643
- sweepAll();
1644
- });
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) {}
1645
1667
  }
1646
1668
 
1647
- function installObservers() {
1648
- // Observe the document for any between-wrap insertion/moves
1669
+ // Process mutations incrementally (no full-document scan)
1670
+ function installMO() {
1649
1671
  try {
1650
- if (typeof MutationObserver !== 'undefined') {
1651
- var mo = new MutationObserver(function(muts){
1652
- for (var i=0;i<muts.length;i++){
1653
- var m = muts[i];
1654
- if (m.addedNodes && m.addedNodes.length) { scheduleSweep(); break; }
1655
- if (m.removedNodes && m.removedNodes.length) { scheduleSweep(); break; }
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;
1656
1689
  }
1657
- });
1658
- mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
1659
- }
1690
+ }
1691
+ });
1692
+ mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
1660
1693
  } catch (e) {}
1661
1694
  }
1662
1695
 
1663
1696
  function init() {
1664
- scheduleSweep();
1665
- window.addEventListener('scroll', scheduleSweep, { passive: true });
1666
- window.addEventListener('resize', scheduleSweep, { passive: true });
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 });
1667
1701
 
1668
1702
  if (window.jQuery) {
1669
1703
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1670
- setTimeout(scheduleSweep, 0);
1671
- setTimeout(scheduleSweep, 250);
1672
- setTimeout(scheduleSweep, 900);
1704
+ setTimeout(function(){ sweepAll(document); }, 0);
1705
+ setTimeout(function(){ sweepAll(document); }, 250);
1673
1706
  });
1674
1707
  }
1675
- installObservers();
1708
+ installMO();
1676
1709
  }
1677
1710
 
1678
1711
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1679
1712
  else init();
1680
1713
  })();
1681
- // ===== /V14.1 =====
1714
+ // ===== /V14.2 =====
1682
1715
 
package/public/style.css CHANGED
@@ -81,7 +81,7 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V14.1 host ===== */
84
+ /* ===== V14.2 host ===== */
85
85
  li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- /* ===== /V14.1 ===== */
86
+ /* ===== /V14.2 ===== */
87
87