nodebb-plugin-ezoic-infinite 1.6.19 → 1.6.20

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.20",
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,205 +1511,147 @@ function buildOrdinalMap(items) {
1511
1511
 
1512
1512
 
1513
1513
 
1514
- // ===== V14.2 targeted hook (topic list only) + upscroll reconcile =====
1514
+ // ===== V14.3 reconcile by data-ezoic-after (upscroll only, no DOM scanning on downscroll) =====
1515
1515
  (function () {
1516
- var BETWEEN_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1517
1516
  var HOST_CLASS = 'nodebb-ezoic-host';
1518
- var TOKEN_ATTR = 'data-ezoic-anchor-token';
1517
+ var WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1519
1518
  var TOPIC_LI_SEL = 'li[component="category/topic"]';
1520
-
1521
1519
  var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1522
1520
 
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() {
1521
+ function getY() {
1528
1522
  return window.pageYOffset || document.documentElement.scrollTop || 0;
1529
1523
  }
1530
1524
 
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) {
1525
+ function getTopicList() {
1536
1526
  try {
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;
1544
- }
1545
- } catch (e) {}
1546
- return null;
1527
+ var topicLi = document.querySelector(TOPIC_LI_SEL);
1528
+ if (!topicLi) return null;
1529
+ var ul = topicLi.closest ? topicLi.closest('ul,ol') : null;
1530
+ return ul;
1531
+ } catch (e) { return null; }
1547
1532
  }
1548
1533
 
1549
- function closestAnchorTopicLi(hostOrWrap, ul) {
1534
+ function ensureValidHostForBetween(wrap) {
1535
+ // Ensure ul/ol does not directly contain div wraps: wrap them in LI host.
1550
1536
  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
- }
1558
- var prev = cur.previousElementSibling;
1559
- while (prev) {
1560
- if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1561
- prev = prev.previousElementSibling;
1537
+ if (!wrap || wrap.nodeType !== 1) return null;
1538
+ if (!(wrap.matches && wrap.matches(WRAP_SEL))) return null;
1539
+ var ul = wrap.parentElement;
1540
+ while (ul && !(ul.tagName === 'UL' || ul.tagName === 'OL')) ul = ul.parentElement;
1541
+ if (!ul) return null;
1542
+
1543
+ // If already inside host, return host
1544
+ var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1545
+ if (host) return host;
1546
+
1547
+ // If direct child of UL/OL, create host and move wrap into it
1548
+ if (wrap.parentElement === ul) {
1549
+ host = document.createElement('li');
1550
+ host.className = HOST_CLASS;
1551
+ host.setAttribute('role', 'listitem');
1552
+ host.style.listStyle = 'none';
1553
+ host.style.width = '100%';
1554
+ ul.insertBefore(host, wrap);
1555
+ host.appendChild(wrap);
1556
+ try { wrap.style.width = '100%'; } catch (e) {}
1557
+ return host;
1562
1558
  }
1563
1559
  } catch (e) {}
1564
1560
  return null;
1565
1561
  }
1566
1562
 
1567
- function ensureHostAndTokenForWrap(wrap) {
1563
+ function desiredAnchorByAfter(ul, afterNum) {
1564
+ // afterNum is like 6/12/18 etc. It means "after nth topic".
1565
+ // We'll locate the nth topic LI currently in DOM (1-indexed), then anchor after it.
1568
1566
  try {
1569
- if (!wrap || wrap.nodeType !== 1) return;
1570
- if (!(wrap.matches && wrap.matches(BETWEEN_SEL))) return;
1571
-
1572
- var ul = closestTopicList(wrap);
1573
- if (!ul) return;
1574
-
1575
- // Ensure wrap is not direct child of UL/OL
1576
- 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
- }
1587
- }
1588
-
1589
- if (!host) return;
1590
-
1591
- // Anchor = previous topic LI only
1592
- var anchorLi = closestAnchorTopicLi(host, ul);
1593
- if (!anchorLi) {
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);
1602
- }
1603
- host.setAttribute(TOKEN_ATTR, t);
1604
- host.removeAttribute('data-ezoic-orphan');
1605
- } catch (e) {}
1567
+ if (!ul || !afterNum) return null;
1568
+ var n = parseInt(afterNum, 10);
1569
+ if (!n || n < 1) return null;
1570
+ var topics = ul.querySelectorAll(TOPIC_LI_SEL);
1571
+ if (!topics || !topics.length) return null;
1572
+ // clamp to range
1573
+ if (n > topics.length) n = topics.length;
1574
+ return topics[n - 1];
1575
+ } catch (e) { return null; }
1606
1576
  }
1607
1577
 
1608
- function repairInvalidUlChildren(ul) {
1578
+ function reconcileHostsUpScroll() {
1579
+ var ul = getTopicList();
1580
+ if (!ul) return;
1581
+
1582
+ // 1) Repair invalid wraps (cheap)
1609
1583
  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); });
1584
+ ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){ ensureValidHostForBetween(w); });
1614
1585
  } catch (e) {}
1615
- }
1616
1586
 
1617
- function cleanupEmptyHosts(ul) {
1587
+ // 2) Only on upscroll: move hosts back to their after-index positions if they drifted.
1588
+ var y = getY();
1589
+ var dy = y - lastY;
1590
+ lastY = y;
1591
+ if (dy >= -10) return;
1592
+
1618
1593
  try {
1619
- if (!ul) return;
1620
1594
  var hosts = ul.querySelectorAll('li.' + HOST_CLASS);
1621
1595
  hosts.forEach(function(host){
1622
1596
  try {
1623
- var hasWrap = host.querySelector && host.querySelector(BETWEEN_SEL);
1624
- if (!hasWrap) host.remove();
1625
- } catch (e) {}
1626
- });
1627
- } catch (e) {}
1628
- }
1597
+ var wrap = host.querySelector && host.querySelector(WRAP_SEL);
1598
+ if (!wrap) { host.remove(); return; }
1629
1599
 
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);
1600
+ var afterNum = wrap.getAttribute('data-ezoic-after') || host.getAttribute('data-ezoic-after') || '';
1601
+ if (!afterNum) return;
1602
+
1603
+ var anchor = desiredAnchorByAfter(ul, afterNum);
1604
+ if (!anchor) {
1605
+ // if we can't anchor, remove to prevent piling
1606
+ host.remove();
1607
+ return;
1608
+ }
1609
+
1610
+ if (host.previousElementSibling !== anchor) {
1611
+ anchor.insertAdjacentElement('afterend', host);
1612
+ }
1649
1613
  } catch (e) {}
1650
1614
  });
1651
1615
  } catch (e) {}
1652
1616
  }
1653
1617
 
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) {}
1618
+ // Throttled scheduler (scroll)
1619
+ var pending = false;
1620
+ var lastRun = 0;
1621
+ var COOLDOWN = 160;
1622
+
1623
+ function schedule() {
1624
+ var now = Date.now();
1625
+ if (now - lastRun < COOLDOWN) return;
1626
+ if (pending) return;
1627
+ pending = true;
1628
+ requestAnimationFrame(function(){
1629
+ pending = false;
1630
+ lastRun = Date.now();
1631
+ reconcileHostsUpScroll();
1632
+ });
1667
1633
  }
1668
1634
 
1669
- // Process mutations incrementally (no full-document scan)
1670
- function installMO() {
1635
+ function init() {
1636
+ // initial repair only
1671
1637
  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 });
1638
+ var ul = getTopicList();
1639
+ if (ul) ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){ ensureValidHostForBetween(w); });
1693
1640
  } catch (e) {}
1694
- }
1695
1641
 
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 });
1642
+ window.addEventListener('scroll', schedule, { passive: true });
1643
+ window.addEventListener('resize', schedule, { passive: true });
1701
1644
 
1702
1645
  if (window.jQuery) {
1703
1646
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1704
- setTimeout(function(){ sweepAll(document); }, 0);
1705
- setTimeout(function(){ sweepAll(document); }, 250);
1647
+ setTimeout(schedule, 0);
1648
+ setTimeout(schedule, 300);
1706
1649
  });
1707
1650
  }
1708
- installMO();
1709
1651
  }
1710
1652
 
1711
1653
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1712
1654
  else init();
1713
1655
  })();
1714
- // ===== /V14.2 =====
1656
+ // ===== /V14.3 =====
1715
1657
 
package/public/style.css CHANGED
@@ -81,7 +81,7 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V14.2 host ===== */
84
+ /* ===== V14.3 host ===== */
85
85
  li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- /* ===== /V14.2 ===== */
86
+ /* ===== /V14.3 ===== */
87
87