nodebb-plugin-ezoic-infinite 1.6.20 → 1.6.22

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.20",
3
+ "version": "1.6.22",
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,40 +1511,55 @@ function buildOrdinalMap(items) {
1511
1511
 
1512
1512
 
1513
1513
 
1514
- // ===== V14.3 reconcile by data-ezoic-after (upscroll only, no DOM scanning on downscroll) =====
1514
+ // ===== V14.1.2 IO reconcile (visible only) minimal impact on infinite scroll =====
1515
1515
  (function () {
1516
- var HOST_CLASS = 'nodebb-ezoic-host';
1517
1516
  var WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1518
- var TOPIC_LI_SEL = 'li[component="category/topic"]';
1519
- var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1517
+ var HOST_CLASS = 'nodebb-ezoic-host';
1518
+ var io = null;
1520
1519
 
1521
- function getY() {
1522
- return window.pageYOffset || document.documentElement.scrollTop || 0;
1520
+ function getListContainer(node) {
1521
+ 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;
1523
1531
  }
1524
1532
 
1525
- function getTopicList() {
1533
+ function closestNonHostLi(node) {
1526
1534
  try {
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; }
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;
1532
1549
  }
1533
1550
 
1534
- function ensureValidHostForBetween(wrap) {
1535
- // Ensure ul/ol does not directly contain div wraps: wrap them in LI host.
1551
+ function ensureHostForWrap(wrap) {
1552
+ // Only repairs invalid UL>DIV. Does NOT move around the list except wrapping in-place.
1536
1553
  try {
1537
1554
  if (!wrap || wrap.nodeType !== 1) return null;
1538
1555
  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
1556
 
1543
- // If already inside host, return host
1544
1557
  var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1545
1558
  if (host) return host;
1546
1559
 
1547
- // If direct child of UL/OL, create host and move wrap into it
1560
+ var ul = getListContainer(wrap);
1561
+ if (!ul) return null;
1562
+
1548
1563
  if (wrap.parentElement === ul) {
1549
1564
  host = document.createElement('li');
1550
1565
  host.className = HOST_CLASS;
@@ -1560,98 +1575,105 @@ function buildOrdinalMap(items) {
1560
1575
  return null;
1561
1576
  }
1562
1577
 
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.
1578
+ function reconcileHostPosition(host) {
1579
+ // Moves ONLY the one host we are currently handling (visible or just inserted).
1566
1580
  try {
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; }
1576
- }
1581
+ if (!host || host.nodeType !== 1) return;
1582
+ var ul = getListContainer(host);
1583
+ if (!ul) return;
1577
1584
 
1578
- function reconcileHostsUpScroll() {
1579
- var ul = getTopicList();
1580
- if (!ul) return;
1585
+ var anchorLi = closestNonHostLi(host);
1586
+ if (!anchorLi) return;
1587
+
1588
+ if (host.previousElementSibling !== anchorLi) {
1589
+ anchorLi.insertAdjacentElement('afterend', host);
1590
+ }
1591
+ } catch (e) {}
1592
+ }
1581
1593
 
1582
- // 1) Repair invalid wraps (cheap)
1594
+ function observeHost(host) {
1583
1595
  try {
1584
- ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){ ensureValidHostForBetween(w); });
1596
+ if (!host) return;
1597
+ if (!io) return;
1598
+ io.observe(host);
1585
1599
  } catch (e) {}
1600
+ }
1586
1601
 
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;
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 });
1613
+ }
1592
1614
 
1615
+ function processWrap(wrap) {
1593
1616
  try {
1594
- var hosts = ul.querySelectorAll('li.' + HOST_CLASS);
1595
- hosts.forEach(function(host){
1596
- try {
1597
- var wrap = host.querySelector && host.querySelector(WRAP_SEL);
1598
- if (!wrap) { host.remove(); return; }
1599
-
1600
- var afterNum = wrap.getAttribute('data-ezoic-after') || host.getAttribute('data-ezoic-after') || '';
1601
- if (!afterNum) return;
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);
1622
+ }
1623
+ } catch (e) {}
1624
+ }
1602
1625
 
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
- }
1626
+ function scanInitial() {
1627
+ try {
1628
+ document.querySelectorAll(WRAP_SEL).forEach(processWrap);
1629
+ } catch (e) {}
1630
+ }
1609
1631
 
1610
- if (host.previousElementSibling !== anchor) {
1611
- anchor.insertAdjacentElement('afterend', host);
1632
+ function installMO() {
1633
+ 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++) {
1638
+ 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);
1650
+ }
1651
+ }
1612
1652
  }
1613
1653
  } catch (e) {}
1614
1654
  });
1655
+ mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
1615
1656
  } catch (e) {}
1616
1657
  }
1617
1658
 
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
- });
1633
- }
1634
-
1635
1659
  function init() {
1636
- // initial repair only
1637
- try {
1638
- var ul = getTopicList();
1639
- if (ul) ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){ ensureValidHostForBetween(w); });
1640
- } catch (e) {}
1641
-
1642
- window.addEventListener('scroll', schedule, { passive: true });
1643
- window.addEventListener('resize', schedule, { passive: true });
1660
+ initIO();
1661
+ scanInitial();
1662
+ installMO();
1644
1663
 
1645
1664
  if (window.jQuery) {
1646
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1647
- setTimeout(schedule, 0);
1648
- setTimeout(schedule, 300);
1649
- });
1665
+ 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);
1670
+ });
1671
+ } catch (e) {}
1650
1672
  }
1651
1673
  }
1652
1674
 
1653
1675
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1654
1676
  else init();
1655
1677
  })();
1656
- // ===== /V14.3 =====
1678
+ // ===== /V14.1.2 =====
1657
1679
 
package/public/style.css CHANGED
@@ -81,7 +81,7 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V14.3 host ===== */
84
+ /* ===== V14.1.2 host ===== */
85
85
  li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- /* ===== /V14.3 ===== */
86
+ /* ===== /V14.1.2 ===== */
87
87