nodebb-plugin-ezoic-infinite 1.6.18 → 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.18",
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,172 +1511,147 @@ function buildOrdinalMap(items) {
1511
1511
 
1512
1512
 
1513
1513
 
1514
- // ===== V14.1 Hook any between-wrap and tether to nearest topic LI =====
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';
1519
- var pendingSweep = false;
1520
- var lastSweep = 0;
1521
- var COOLDOWN = 120;
1517
+ 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;
1522
1520
 
1523
- function token() {
1524
- try { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } catch (e) { return String(Date.now()); }
1521
+ function getY() {
1522
+ return window.pageYOffset || document.documentElement.scrollTop || 0;
1525
1523
  }
1526
1524
 
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.
1525
+ function getTopicList() {
1530
1526
  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
- }
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;
1542
-
1543
- // Start from current element position within ul
1544
- var prev = cur.previousElementSibling;
1545
- while (prev) {
1546
- if (prev.tagName === 'LI' && !(prev.classList && prev.classList.contains(HOST_CLASS))) return prev;
1547
- prev = prev.previousElementSibling;
1548
- }
1549
- } catch (e) {}
1550
- 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; }
1551
1532
  }
1552
1533
 
1553
- function ensureHostForWrap(wrap) {
1534
+ function ensureValidHostForBetween(wrap) {
1535
+ // Ensure ul/ol does not directly contain div wraps: wrap them in LI host.
1554
1536
  try {
1555
- if (!wrap || wrap.nodeType !== 1) return;
1556
- if (!(wrap.matches && wrap.matches(BETWEEN_SEL))) return;
1557
-
1558
- // Already in a host?
1559
- var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1537
+ if (!wrap || wrap.nodeType !== 1) return null;
1538
+ if (!(wrap.matches && wrap.matches(WRAP_SEL))) return null;
1560
1539
  var ul = wrap.parentElement;
1561
1540
  while (ul && !(ul.tagName === 'UL' || ul.tagName === 'OL')) ul = ul.parentElement;
1562
- if (!ul) return;
1541
+ if (!ul) return null;
1563
1542
 
1564
- // Ensure wrap is inside UL only via LI host (never direct ul>div)
1565
- if (!host) {
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) {
1566
1549
  host = document.createElement('li');
1567
1550
  host.className = HOST_CLASS;
1568
1551
  host.setAttribute('role', 'listitem');
1569
1552
  host.style.listStyle = 'none';
1570
1553
  host.style.width = '100%';
1571
- // Insert host at wrap position
1572
1554
  ul.insertBefore(host, wrap);
1573
1555
  host.appendChild(wrap);
1574
- } else if (host.parentElement !== ul) {
1575
- // move host into correct list container if needed
1576
- ul.appendChild(host);
1577
- }
1578
-
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
1594
- host.setAttribute('data-ezoic-orphan', '1');
1556
+ try { wrap.style.width = '100%'; } catch (e) {}
1557
+ return host;
1595
1558
  }
1596
1559
  } catch (e) {}
1560
+ return null;
1597
1561
  }
1598
1562
 
1599
- function sweepAll() {
1600
- var now = Date.now();
1601
- if (now - lastSweep < COOLDOWN) return;
1602
- lastSweep = now;
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.
1566
+ 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
+ }
1603
1577
 
1578
+ function reconcileHostsUpScroll() {
1579
+ var ul = getTopicList();
1580
+ if (!ul) return;
1581
+
1582
+ // 1) Repair invalid wraps (cheap)
1604
1583
  try {
1605
- // 1) repair direct ul>div wraps
1606
- var bad = document.querySelectorAll('ul > ' + BETWEEN_SEL + ', ol > ' + BETWEEN_SEL);
1607
- bad.forEach(ensureHostForWrap);
1584
+ ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){ ensureValidHostForBetween(w); });
1585
+ } catch (e) {}
1608
1586
 
1609
- // 2) ensure any between wrap anywhere is hosted+tethered
1610
- var wraps = document.querySelectorAll(BETWEEN_SEL);
1611
- wraps.forEach(ensureHostForWrap);
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;
1612
1592
 
1613
- // 3) reconcile/drop hosts that lost their anchor (virtualized)
1614
- var hosts = document.querySelectorAll('li.' + HOST_CLASS + '[' + TOKEN_ATTR + ']');
1593
+ try {
1594
+ var hosts = ul.querySelectorAll('li.' + HOST_CLASS);
1615
1595
  hosts.forEach(function(host){
1616
1596
  try {
1617
- var ul = host.parentElement;
1618
- if (!ul) { host.remove(); return; }
1619
- var t = host.getAttribute(TOKEN_ATTR);
1620
- if (!t) { host.remove(); return; }
1621
- var anchor = ul.querySelector('li[' + TOKEN_ATTR + '="' + t + '"]');
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;
1602
+
1603
+ var anchor = desiredAnchorByAfter(ul, afterNum);
1622
1604
  if (!anchor) {
1605
+ // if we can't anchor, remove to prevent piling
1623
1606
  host.remove();
1624
1607
  return;
1625
1608
  }
1609
+
1626
1610
  if (host.previousElementSibling !== anchor) {
1627
1611
  anchor.insertAdjacentElement('afterend', host);
1628
1612
  }
1629
1613
  } catch (e) {}
1630
1614
  });
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
1615
  } catch (e) {}
1636
1616
  }
1637
1617
 
1638
- function scheduleSweep() {
1639
- if (pendingSweep) return;
1640
- pendingSweep = true;
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;
1641
1628
  requestAnimationFrame(function(){
1642
- pendingSweep = false;
1643
- sweepAll();
1629
+ pending = false;
1630
+ lastRun = Date.now();
1631
+ reconcileHostsUpScroll();
1644
1632
  });
1645
1633
  }
1646
1634
 
1647
- function installObservers() {
1648
- // Observe the document for any between-wrap insertion/moves
1635
+ function init() {
1636
+ // initial repair only
1649
1637
  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; }
1656
- }
1657
- });
1658
- mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
1659
- }
1638
+ var ul = getTopicList();
1639
+ if (ul) ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){ ensureValidHostForBetween(w); });
1660
1640
  } catch (e) {}
1661
- }
1662
1641
 
1663
- function init() {
1664
- scheduleSweep();
1665
- window.addEventListener('scroll', scheduleSweep, { passive: true });
1666
- window.addEventListener('resize', scheduleSweep, { passive: true });
1642
+ window.addEventListener('scroll', schedule, { passive: true });
1643
+ window.addEventListener('resize', schedule, { passive: true });
1667
1644
 
1668
1645
  if (window.jQuery) {
1669
1646
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1670
- setTimeout(scheduleSweep, 0);
1671
- setTimeout(scheduleSweep, 250);
1672
- setTimeout(scheduleSweep, 900);
1647
+ setTimeout(schedule, 0);
1648
+ setTimeout(schedule, 300);
1673
1649
  });
1674
1650
  }
1675
- installObservers();
1676
1651
  }
1677
1652
 
1678
1653
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1679
1654
  else init();
1680
1655
  })();
1681
- // ===== /V14.1 =====
1656
+ // ===== /V14.3 =====
1682
1657
 
package/public/style.css CHANGED
@@ -81,7 +81,7 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V14.1 host ===== */
84
+ /* ===== V14.3 host ===== */
85
85
  li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- /* ===== /V14.1 ===== */
86
+ /* ===== /V14.3 ===== */
87
87