nodebb-plugin-ezoic-infinite 1.6.22 → 1.6.24

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.22",
3
+ "version": "1.6.24",
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,58 +1511,44 @@ function buildOrdinalMap(items) {
1511
1511
 
1512
1512
 
1513
1513
 
1514
- // ===== V14.1.2 IO reconcile (visible only) minimal impact on infinite scroll =====
1514
+ // ===== V14.1.3: Mutation-based reconcile (no scroll listener), place by data-ezoic-after =====
1515
1515
  (function () {
1516
1516
  var WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1517
- var HOST_CLASS = 'nodebb-ezoic-host';
1518
- var io = null;
1517
+ var HOST_SEL = 'li.nodebb-ezoic-host';
1518
+ var TOPIC_LI_SEL = 'li[component="category/topic"]';
1519
1519
 
1520
- function getListContainer(node) {
1520
+ var freezeUntil = 0;
1521
+ var pending = false;
1522
+ var queue = new Set();
1523
+
1524
+ function now() { return Date.now(); }
1525
+
1526
+ function getTopicList() {
1521
1527
  try {
1522
- var ul = node && node.closest ? node.closest('ul,ol') : null;
1523
- if (ul && (ul.tagName === 'UL' || ul.tagName === 'OL')) return ul;
1524
- var p = node && node.parentElement;
1525
- while (p) {
1526
- if (p.tagName === 'UL' || p.tagName === 'OL') return p;
1527
- p = p.parentElement;
1528
- }
1529
- } catch (e) {}
1530
- return null;
1528
+ var topic = document.querySelector(TOPIC_LI_SEL);
1529
+ if (!topic) return null;
1530
+ return topic.closest ? topic.closest('ul,ol') : null;
1531
+ } catch (e) { return null; }
1531
1532
  }
1532
1533
 
1533
- function closestNonHostLi(node) {
1534
+ function isLoading() {
1534
1535
  try {
1535
- var cur = node;
1536
- if (!cur) return null;
1537
- // If inside host, start from host
1538
- if (cur.closest) {
1539
- var host = cur.closest('li.' + HOST_CLASS);
1540
- if (host) cur = host;
1541
- }
1542
- var prev = cur.previousElementSibling;
1543
- while (prev) {
1544
- if (prev.tagName === 'LI' && !(prev.classList && prev.classList.contains(HOST_CLASS))) return prev;
1545
- prev = prev.previousElementSibling;
1546
- }
1547
- } catch (e) {}
1548
- return null;
1536
+ if (now() < freezeUntil) return true;
1537
+ return !!document.querySelector('.infinite-loading, .infinite-scroll-loading, .loading-indicator, .spinner, .topic-loading');
1538
+ } catch (e) { return false; }
1549
1539
  }
1550
1540
 
1551
- function ensureHostForWrap(wrap) {
1552
- // Only repairs invalid UL>DIV. Does NOT move around the list except wrapping in-place.
1541
+ function ensureHostForWrap(wrap, ul) {
1553
1542
  try {
1554
1543
  if (!wrap || wrap.nodeType !== 1) return null;
1555
1544
  if (!(wrap.matches && wrap.matches(WRAP_SEL))) return null;
1556
-
1557
- var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1545
+ var host = wrap.closest ? wrap.closest(HOST_SEL) : null;
1558
1546
  if (host) return host;
1559
-
1560
- var ul = getListContainer(wrap);
1547
+ ul = ul || (wrap.closest ? wrap.closest('ul,ol') : null);
1561
1548
  if (!ul) return null;
1562
-
1563
1549
  if (wrap.parentElement === ul) {
1564
1550
  host = document.createElement('li');
1565
- host.className = HOST_CLASS;
1551
+ host.className = 'nodebb-ezoic-host';
1566
1552
  host.setAttribute('role', 'listitem');
1567
1553
  host.style.listStyle = 'none';
1568
1554
  host.style.width = '100%';
@@ -1575,98 +1561,170 @@ function buildOrdinalMap(items) {
1575
1561
  return null;
1576
1562
  }
1577
1563
 
1578
- function reconcileHostPosition(host) {
1579
- // Moves ONLY the one host we are currently handling (visible or just inserted).
1564
+ function nthTopic(ul, n) {
1565
+ try {
1566
+ var topics = ul.querySelectorAll(TOPIC_LI_SEL);
1567
+ if (!topics || !topics.length) return null;
1568
+ if (n < 1) n = 1;
1569
+ if (n > topics.length) n = topics.length;
1570
+ return topics[n-1] || null;
1571
+ } catch (e) { return null; }
1572
+ }
1573
+
1574
+ function previousTopicLi(host) {
1575
+ try {
1576
+ var prev = host.previousElementSibling;
1577
+ while (prev) {
1578
+ if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1579
+ prev = prev.previousElementSibling;
1580
+ }
1581
+ } catch (e) {}
1582
+ return null;
1583
+ }
1584
+
1585
+ function placeHost(host, ul) {
1580
1586
  try {
1581
1587
  if (!host || host.nodeType !== 1) return;
1582
- var ul = getListContainer(host);
1588
+ ul = ul || host.parentElement;
1583
1589
  if (!ul) return;
1584
1590
 
1585
- var anchorLi = closestNonHostLi(host);
1586
- if (!anchorLi) return;
1591
+ var wrap = host.querySelector && host.querySelector(WRAP_SEL);
1592
+ if (!wrap) { host.remove(); return; }
1593
+
1594
+ var after = wrap.getAttribute('data-ezoic-after');
1595
+ var anchor = null;
1596
+
1597
+ if (after) {
1598
+ anchor = nthTopic(ul, parseInt(after, 10));
1599
+ }
1600
+ if (!anchor) {
1601
+ anchor = previousTopicLi(host);
1602
+ }
1603
+ if (!anchor) {
1604
+ // Can't place reliably; if host drifted to top area, remove to avoid pile-up.
1605
+ // Otherwise keep as-is.
1606
+ var firstTopic = nthTopic(ul, 1);
1607
+ if (firstTopic && host !== firstTopic && host.compareDocumentPosition(firstTopic) & Node.DOCUMENT_POSITION_FOLLOWING) {
1608
+ host.remove();
1609
+ }
1610
+ return;
1611
+ }
1587
1612
 
1588
- if (host.previousElementSibling !== anchorLi) {
1589
- anchorLi.insertAdjacentElement('afterend', host);
1613
+ if (host.previousElementSibling !== anchor) {
1614
+ anchor.insertAdjacentElement('afterend', host);
1590
1615
  }
1591
1616
  } catch (e) {}
1592
1617
  }
1593
1618
 
1594
- function observeHost(host) {
1619
+ function drain() {
1620
+ pending = false;
1621
+ if (isLoading()) {
1622
+ // retry later
1623
+ scheduleDrain(250);
1624
+ return;
1625
+ }
1626
+ var ul = getTopicList();
1627
+ if (!ul) return;
1628
+
1629
+ // repair invalid ul>div once
1595
1630
  try {
1596
- if (!host) return;
1597
- if (!io) return;
1598
- io.observe(host);
1631
+ ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){
1632
+ var h = ensureHostForWrap(w, ul);
1633
+ if (h) queue.add(h);
1634
+ });
1599
1635
  } catch (e) {}
1636
+
1637
+ var count = 0;
1638
+ for (var host of Array.from(queue)) {
1639
+ queue.delete(host);
1640
+ placeHost(host, ul);
1641
+ count++;
1642
+ if (count >= 8) break; // keep it light
1643
+ }
1644
+ if (queue.size) scheduleDrain(60);
1600
1645
  }
1601
1646
 
1602
- function initIO() {
1603
- if (io || typeof IntersectionObserver === 'undefined') return;
1604
- io = new IntersectionObserver(function (entries) {
1605
- try {
1606
- entries.forEach(function (e) {
1607
- if (!e || !e.isIntersecting || !e.target) return;
1608
- // When host is near viewport, reconcile once.
1609
- reconcileHostPosition(e.target);
1610
- });
1611
- } catch (e) {}
1612
- }, { root: null, rootMargin: '800px 0px 800px 0px', threshold: 0.01 });
1647
+ function scheduleDrain(delay) {
1648
+ if (pending) return;
1649
+ pending = true;
1650
+ if (delay) {
1651
+ setTimeout(function(){ requestAnimationFrame(drain); }, delay);
1652
+ } else {
1653
+ requestAnimationFrame(drain);
1654
+ }
1613
1655
  }
1614
1656
 
1615
- function processWrap(wrap) {
1657
+ function enqueueFromNode(node) {
1616
1658
  try {
1617
- var host = ensureHostForWrap(wrap) || (wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null);
1618
- if (host) {
1619
- // reconcile immediately only for the newly processed node (cheap)
1620
- reconcileHostPosition(host);
1621
- observeHost(host);
1659
+ if (!node || node.nodeType !== 1) return;
1660
+ var ul = getTopicList();
1661
+ if (!ul) return;
1662
+
1663
+ if (node.matches && node.matches(WRAP_SEL)) {
1664
+ var h = ensureHostForWrap(node, ul) || (node.closest ? node.closest(HOST_SEL) : null);
1665
+ if (h) queue.add(h);
1666
+ } else if (node.matches && node.matches(HOST_SEL)) {
1667
+ queue.add(node);
1668
+ } else if (node.querySelectorAll) {
1669
+ node.querySelectorAll(WRAP_SEL).forEach(function(w){
1670
+ var h2 = ensureHostForWrap(w, ul) || (w.closest ? w.closest(HOST_SEL) : null);
1671
+ if (h2) queue.add(h2);
1672
+ });
1673
+ node.querySelectorAll(HOST_SEL).forEach(function(h3){ queue.add(h3); });
1622
1674
  }
1623
1675
  } catch (e) {}
1624
1676
  }
1625
1677
 
1626
- function scanInitial() {
1678
+ function init() {
1679
+ // initial scan (light)
1627
1680
  try {
1628
- document.querySelectorAll(WRAP_SEL).forEach(processWrap);
1681
+ var ul = getTopicList();
1682
+ if (ul) {
1683
+ ul.querySelectorAll(WRAP_SEL).forEach(function(w){
1684
+ var h = ensureHostForWrap(w, ul) || (w.closest ? w.closest(HOST_SEL) : null);
1685
+ if (h) queue.add(h);
1686
+ });
1687
+ scheduleDrain(0);
1688
+ }
1629
1689
  } catch (e) {}
1630
- }
1631
1690
 
1632
- function installMO() {
1691
+ // Observe ONLY the topic list container once available
1633
1692
  try {
1634
- if (typeof MutationObserver === 'undefined') return;
1635
- var mo = new MutationObserver(function (muts) {
1636
- try {
1637
- for (var i = 0; i < muts.length; i++) {
1693
+ if (typeof MutationObserver !== 'undefined') {
1694
+ var mo = new MutationObserver(function(muts){
1695
+ for (var i=0;i<muts.length;i++){
1638
1696
  var m = muts[i];
1639
- if (!m.addedNodes || !m.addedNodes.length) continue;
1640
-
1641
- for (var j = 0; j < m.addedNodes.length; j++) {
1642
- var n = m.addedNodes[j];
1643
- if (!n || n.nodeType !== 1) continue;
1644
-
1645
- if (n.matches && n.matches(WRAP_SEL)) {
1646
- processWrap(n);
1647
- } else if (n.querySelectorAll) {
1648
- var inner = n.querySelectorAll(WRAP_SEL);
1649
- if (inner && inner.length) inner.forEach(processWrap);
1697
+ if (m.addedNodes && m.addedNodes.length) {
1698
+ for (var j=0;j<m.addedNodes.length;j++){
1699
+ enqueueFromNode(m.addedNodes[j]);
1650
1700
  }
1701
+ scheduleDrain(0);
1651
1702
  }
1652
1703
  }
1653
- } catch (e) {}
1654
- });
1655
- mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
1656
- } catch (e) {}
1657
- }
1704
+ });
1658
1705
 
1659
- function init() {
1660
- initIO();
1661
- scanInitial();
1662
- installMO();
1706
+ var ul = getTopicList();
1707
+ if (ul) mo.observe(ul, { childList:true, subtree:true });
1708
+ else {
1709
+ // wait for ajaxify
1710
+ var mo2 = new MutationObserver(function(){
1711
+ var u2 = getTopicList();
1712
+ if (u2) {
1713
+ try { mo.observe(u2, { childList:true, subtree:true }); } catch(e){}
1714
+ try { mo2.disconnect(); } catch(e){}
1715
+ }
1716
+ });
1717
+ mo2.observe(document.documentElement || document.body, { childList:true, subtree:true });
1718
+ }
1719
+ }
1720
+ } catch (e) {}
1663
1721
 
1664
1722
  if (window.jQuery) {
1665
1723
  try {
1666
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function () {
1667
- // do a light rescan (no scroll listeners)
1668
- setTimeout(scanInitial, 0);
1669
- setTimeout(scanInitial, 250);
1724
+ window.jQuery(window).on('action:infiniteScroll.loaded action:ajaxify.end', function(){
1725
+ freezeUntil = now() + 900; // allow NodeBB to settle
1726
+ // then reconcile any drift
1727
+ setTimeout(function(){ scheduleDrain(0); }, 950);
1670
1728
  });
1671
1729
  } catch (e) {}
1672
1730
  }
@@ -1675,5 +1733,5 @@ function buildOrdinalMap(items) {
1675
1733
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1676
1734
  else init();
1677
1735
  })();
1678
- // ===== /V14.1.2 =====
1736
+ // ===== /V14.1.3 =====
1679
1737
 
package/public/style.css CHANGED
@@ -81,7 +81,7 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V14.1.2 host ===== */
84
+ /* ===== V14.1.3 host ===== */
85
85
  li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- /* ===== /V14.1.2 ===== */
86
+ /* ===== /V14.1.3 ===== */
87
87