nodebb-plugin-ezoic-infinite 1.6.28 → 1.6.30

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.28",
3
+ "version": "1.6.30",
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,16 +1511,23 @@ function buildOrdinalMap(items) {
1511
1511
 
1512
1512
 
1513
1513
 
1514
- // ===== V17 minimal pile-fix (no insert hooks) =====
1514
+
1515
+
1516
+
1517
+ // ===== V17.2: Up-scroll "rehome" by data-ezoic-after (keeps injection intact) =====
1515
1518
  (function () {
1516
- // Goal: keep ad injection intact. Only repair when we detect "pile-up" of between wraps.
1517
1519
  var TOPIC_LI_SEL = 'li[component="category/topic"]';
1518
1520
  var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1519
1521
  var HOST_CLASS = 'nodebb-ezoic-host';
1520
1522
 
1523
+ var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1521
1524
  var scheduled = false;
1522
1525
  var lastRun = 0;
1523
- var COOLDOWN = 180;
1526
+ var COOLDOWN = 160;
1527
+
1528
+ function getY() {
1529
+ return window.pageYOffset || document.documentElement.scrollTop || 0;
1530
+ }
1524
1531
 
1525
1532
  function getTopicList() {
1526
1533
  try {
@@ -1530,10 +1537,6 @@ function buildOrdinalMap(items) {
1530
1537
  } catch (e) { return null; }
1531
1538
  }
1532
1539
 
1533
- function isHost(node) {
1534
- return !!(node && node.nodeType === 1 && node.tagName === 'LI' && node.classList && node.classList.contains(HOST_CLASS));
1535
- }
1536
-
1537
1540
  function ensureHostForWrap(wrap, ul) {
1538
1541
  try {
1539
1542
  if (!wrap || wrap.nodeType !== 1) return null;
@@ -1545,13 +1548,16 @@ function buildOrdinalMap(items) {
1545
1548
  if (!ul) ul = wrap.closest ? wrap.closest('ul,ol') : null;
1546
1549
  if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return null;
1547
1550
 
1548
- // Only wrap if direct child of list (invalid / fragile)
1549
1551
  if (wrap.parentElement === ul) {
1550
1552
  host = document.createElement('li');
1551
1553
  host.className = HOST_CLASS;
1552
1554
  host.setAttribute('role', 'listitem');
1553
1555
  host.style.listStyle = 'none';
1554
1556
  host.style.width = '100%';
1557
+ try {
1558
+ var after = wrap.getAttribute('data-ezoic-after');
1559
+ if (after) host.setAttribute('data-ezoic-after', after);
1560
+ } catch (e) {}
1555
1561
  ul.insertBefore(host, wrap);
1556
1562
  host.appendChild(wrap);
1557
1563
  try { wrap.style.width = '100%'; } catch (e) {}
@@ -1561,66 +1567,98 @@ function buildOrdinalMap(items) {
1561
1567
  return null;
1562
1568
  }
1563
1569
 
1564
- function previousTopicLi(node) {
1570
+ function nthTopic(ul, n) {
1565
1571
  try {
1566
- var prev = node.previousElementSibling;
1567
- while (prev) {
1568
- if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1569
- // skip other hosts/wraps
1570
- prev = prev.previousElementSibling;
1572
+ var topics = ul.querySelectorAll(TOPIC_LI_SEL);
1573
+ if (!topics || !topics.length) return null;
1574
+ if (n < 1) n = 1;
1575
+ if (n > topics.length) n = topics.length;
1576
+ return topics[n-1] || null;
1577
+ } catch (e) {}
1578
+ return null;
1579
+ }
1580
+
1581
+ function getAfterValue(host) {
1582
+ try {
1583
+ var v = host.getAttribute('data-ezoic-after');
1584
+ if (!v) {
1585
+ var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1586
+ if (wrap) v = wrap.getAttribute('data-ezoic-after');
1571
1587
  }
1588
+ var n = parseInt(v, 10);
1589
+ return isNaN(n) ? null : n;
1572
1590
  } catch (e) {}
1573
1591
  return null;
1574
1592
  }
1575
1593
 
1576
- function detectPileUp(ul) {
1577
- // Pile-up signature: 2+ between wraps/hosts adjacent with no topics between, often near top.
1594
+ function isTopPiled(ul) {
1578
1595
  try {
1579
1596
  var kids = ul.children;
1580
- var run = 0;
1581
- var maxRun = 0;
1582
- for (var i = 0; i < kids.length; i++) {
1597
+ var limit = Math.min(kids.length, 50);
1598
+ var run = 0, maxRun = 0;
1599
+ for (var i=0;i<limit;i++){
1583
1600
  var el = kids[i];
1584
- var isBetween = false;
1585
- if (isHost(el)) {
1586
- isBetween = !!(el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1601
+ var isAd = false;
1602
+ if (el.tagName === 'LI' && el.classList && el.classList.contains(HOST_CLASS)) {
1603
+ isAd = !!(el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1587
1604
  } else if (el.matches && el.matches(BETWEEN_WRAP_SEL)) {
1588
- isBetween = true;
1589
- }
1590
- if (isBetween) {
1591
- run++;
1592
- if (run > maxRun) maxRun = run;
1593
- } else if (el.matches && el.matches(TOPIC_LI_SEL)) {
1594
- run = 0;
1595
- } else {
1596
- // other nodes reset lightly
1597
- run = 0;
1605
+ isAd = true;
1598
1606
  }
1607
+
1608
+ if (isAd) { run++; if (run>maxRun) maxRun=run; }
1609
+ else if (el.matches && el.matches(TOPIC_LI_SEL)) { run = 0; }
1610
+ else { run = 0; }
1599
1611
  }
1600
1612
  return maxRun >= 2;
1601
1613
  } catch (e) {}
1602
1614
  return false;
1603
1615
  }
1604
1616
 
1605
- function redistribute(ul) {
1617
+ function rehomeByAfter(ul) {
1606
1618
  try {
1607
1619
  if (!ul) return;
1608
1620
 
1609
- // Step 1: wrap any direct child between DIVs into LI hosts (makes list stable)
1610
- ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1621
+ try {
1622
+ ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1623
+ } catch (e) {}
1611
1624
 
1612
- // Step 2: only act if we see pile-up (avoid touching infinite scroll during normal flow)
1613
- if (!detectPileUp(ul)) return;
1625
+ if (!isTopPiled(ul)) return;
1614
1626
 
1615
- // Move each host to immediately after the closest previous topic LI at its current position.
1616
- var hosts = ul.querySelectorAll(':scope > li.' + HOST_CLASS);
1617
- hosts.forEach(function(host){
1618
- try {
1619
- var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1620
- if (!wrap) return;
1627
+ var kids = ul.children;
1628
+ var limit = Math.min(kids.length, 80);
1629
+ var topHosts = [];
1630
+ for (var i=0;i<limit;i++){
1631
+ var el = kids[i];
1632
+ if (el.tagName === 'LI' && el.classList && el.classList.contains(HOST_CLASS)) {
1633
+ var wrap = el.querySelector && el.querySelector(BETWEEN_WRAP_SEL);
1634
+ if (wrap) topHosts.push(el);
1635
+ }
1636
+ }
1637
+ if (!topHosts.length) return;
1638
+
1639
+ topHosts.sort(function(a,b){
1640
+ var aa = getAfterValue(a);
1641
+ var bb = getAfterValue(b);
1642
+ if (aa == null && bb == null) return 0;
1643
+ if (aa == null) return 1;
1644
+ if (bb == null) return -1;
1645
+ return aa - bb;
1646
+ });
1621
1647
 
1622
- var anchor = previousTopicLi(host);
1623
- if (!anchor) return; // if none, don't move (prevents yanking to top/bottom)
1648
+ topHosts.forEach(function(host){
1649
+ try {
1650
+ var after = getAfterValue(host);
1651
+ var anchor = null;
1652
+ if (after != null) anchor = nthTopic(ul, after);
1653
+
1654
+ if (!anchor) {
1655
+ var prev = host.previousElementSibling;
1656
+ while (prev) {
1657
+ if (prev.matches && prev.matches(TOPIC_LI_SEL)) { anchor = prev; break; }
1658
+ prev = prev.previousElementSibling;
1659
+ }
1660
+ }
1661
+ if (!anchor) return;
1624
1662
 
1625
1663
  if (host.previousElementSibling !== anchor) {
1626
1664
  anchor.insertAdjacentElement('afterend', host);
@@ -1630,58 +1668,44 @@ function buildOrdinalMap(items) {
1630
1668
  } catch (e) {}
1631
1669
  }
1632
1670
 
1633
- function schedule(reason) {
1671
+ function schedule() {
1634
1672
  var now = Date.now();
1635
1673
  if (now - lastRun < COOLDOWN) return;
1636
1674
  if (scheduled) return;
1637
1675
  scheduled = true;
1638
- requestAnimationFrame(function () {
1676
+ requestAnimationFrame(function(){
1639
1677
  scheduled = false;
1640
1678
  lastRun = Date.now();
1641
- try {
1642
- var ul = getTopicList();
1643
- if (!ul) return;
1644
- redistribute(ul);
1645
- } catch (e) {}
1679
+ var ul = getTopicList();
1680
+ if (!ul) return;
1681
+ rehomeByAfter(ul);
1646
1682
  });
1647
1683
  }
1648
1684
 
1649
- function init() {
1650
- schedule('init');
1685
+ function onScroll() {
1686
+ var y = getY();
1687
+ var dy = y - lastY;
1688
+ lastY = y;
1689
+ if (dy < -8 && y < 900) schedule();
1690
+ }
1651
1691
 
1652
- // Observe only the topic list once available
1692
+ function init() {
1653
1693
  try {
1654
- if (typeof MutationObserver !== 'undefined') {
1655
- var observeList = function(ul){
1656
- if (!ul) return;
1657
- var mo = new MutationObserver(function(muts){
1658
- // schedule on any change; redistribute() itself is guarded by pile-up detection
1659
- schedule('mo');
1660
- });
1661
- mo.observe(ul, { childList: true, subtree: true });
1662
- };
1663
-
1664
- var ul = getTopicList();
1665
- if (ul) observeList(ul);
1666
- else {
1667
- var mo2 = new MutationObserver(function(){
1668
- var u2 = getTopicList();
1669
- if (u2) {
1670
- try { observeList(u2); } catch(e){}
1671
- try { mo2.disconnect(); } catch(e){}
1672
- }
1673
- });
1674
- mo2.observe(document.documentElement || document.body, { childList: true, subtree: true });
1675
- }
1676
- }
1694
+ var ul = getTopicList();
1695
+ if (ul) ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1677
1696
  } catch (e) {}
1678
1697
 
1679
- // NodeBB events: run after infinite scroll batches
1698
+ window.addEventListener('scroll', onScroll, { passive:true });
1699
+
1680
1700
  if (window.jQuery) {
1681
1701
  try {
1682
1702
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1683
- setTimeout(function(){ schedule('event'); }, 50);
1684
- setTimeout(function(){ schedule('event2'); }, 400);
1703
+ setTimeout(function(){
1704
+ try {
1705
+ var ul = getTopicList();
1706
+ if (ul) ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1707
+ } catch(e){}
1708
+ }, 50);
1685
1709
  });
1686
1710
  } catch (e) {}
1687
1711
  }
@@ -1690,5 +1714,5 @@ function buildOrdinalMap(items) {
1690
1714
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1691
1715
  else init();
1692
1716
  })();
1693
- // ===== /V17 =====
1717
+ // ===== /V17.2 =====
1694
1718
 
package/public/style.css CHANGED
@@ -86,3 +86,10 @@ li.nodebb-ezoic-host { list-style: none; width: 100%; display: block; }
86
86
  li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; display: block; }
87
87
  /* ===== /V17 ===== */
88
88
 
89
+
90
+
91
+ /* ===== V17.2 host styling ===== */
92
+ li.nodebb-ezoic-host { list-style:none; width:100%; display:block; }
93
+ li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width:100%; display:block; }
94
+ /* ===== /V17.2 ===== */
95
+