nodebb-plugin-ezoic-infinite 1.6.36 → 1.6.37

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.36",
3
+ "version": "1.6.37",
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
@@ -1514,20 +1514,18 @@ function buildOrdinalMap(items) {
1514
1514
 
1515
1515
 
1516
1516
 
1517
- // ===== V17.7: Keep injection, anchor each between slot to topic TID and repair on DOM moves (minimal, targeted) =====
1517
+ // ===== V17.8: No pile-up by design keep "future" between slots pending/collapsed until their anchor topic exists =====
1518
1518
  (function () {
1519
1519
  var TOPIC_LI_SEL = 'li[component="category/topic"]';
1520
1520
  var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1521
1521
  var HOST_CLASS = 'nodebb-ezoic-host';
1522
- var ANCHOR_ATTR = 'data-ezoic-anchor-tid';
1522
+ var PENDING_CLASS = 'nodebb-ezoic-pending';
1523
1523
 
1524
1524
  var ul = null;
1525
1525
  var mo = null;
1526
-
1527
- var pending = false;
1528
- var queue = new Set();
1529
- var lastFlush = 0;
1530
- var FLUSH_COOLDOWN = 60;
1526
+ var scheduled = false;
1527
+ var lastRun = 0;
1528
+ var COOLDOWN = 80;
1531
1529
 
1532
1530
  function getTopicList() {
1533
1531
  try {
@@ -1543,12 +1541,14 @@ function buildOrdinalMap(items) {
1543
1541
  return !!(el && el.nodeType === 1 && el.tagName === 'LI' && el.classList && el.classList.contains(HOST_CLASS));
1544
1542
  }
1545
1543
 
1544
+ function isBetweenWrap(el) {
1545
+ try { return !!(el && el.nodeType === 1 && el.matches && el.matches(BETWEEN_WRAP_SEL)); } catch(e){ return false; }
1546
+ }
1547
+
1546
1548
  function ensureHostForWrap(wrap, ulEl) {
1547
- // Only wrap invalid ul>div cases (same idea as V17), no injection hook.
1549
+ // If Ezoic inserts ul > div..., wrap it into a LI to keep a valid list structure (less NodeBB churn).
1548
1550
  try {
1549
- if (!wrap || wrap.nodeType !== 1) return null;
1550
- if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1551
-
1551
+ if (!wrap || !isBetweenWrap(wrap)) return null;
1552
1552
  var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1553
1553
  if (host) return host;
1554
1554
 
@@ -1561,10 +1561,14 @@ function buildOrdinalMap(items) {
1561
1561
  host.setAttribute('role', 'listitem');
1562
1562
  host.style.listStyle = 'none';
1563
1563
  host.style.width = '100%';
1564
+
1564
1565
  try {
1565
1566
  var after = wrap.getAttribute('data-ezoic-after');
1566
1567
  if (after) host.setAttribute('data-ezoic-after', after);
1568
+ var pin = wrap.getAttribute('data-ezoic-pin');
1569
+ if (pin) host.setAttribute('data-ezoic-pin', pin);
1567
1570
  } catch (e) {}
1571
+
1568
1572
  ulEl.insertBefore(host, wrap);
1569
1573
  host.appendChild(wrap);
1570
1574
  try { wrap.style.width = '100%'; } catch (e) {}
@@ -1574,227 +1578,145 @@ function buildOrdinalMap(items) {
1574
1578
  return null;
1575
1579
  }
1576
1580
 
1577
- function getTidFromTopicLi(li) {
1581
+ function getAfter(el) {
1578
1582
  try {
1579
- if (!li) return null;
1580
- var tid = li.getAttribute('data-tid') || li.getAttribute('data-topic-id') || li.getAttribute('data-topicid') || li.getAttribute('data-id');
1581
- if (tid && /^\\d+$/.test(tid)) return tid;
1582
-
1583
- var a = li.querySelector && li.querySelector('a[href*="/topic/"]');
1584
- if (a) {
1585
- var href = a.getAttribute('href') || '';
1586
- var m = href.match(/\\/topic\\/(\\d+)(\\/|$)/);
1587
- if (m) return m[1];
1583
+ var v = null;
1584
+ if (el && el.getAttribute) v = el.getAttribute('data-ezoic-after');
1585
+ if (!v && el && el.querySelector) {
1586
+ var w = el.querySelector(BETWEEN_WRAP_SEL);
1587
+ if (w) v = w.getAttribute('data-ezoic-after');
1588
1588
  }
1589
- } catch (e) {}
1590
- return null;
1589
+ var n = parseInt(v, 10);
1590
+ return isNaN(n) ? null : n;
1591
+ } catch (e) { return null; }
1591
1592
  }
1592
1593
 
1593
- function closestPrevTopicLi(node) {
1594
- try {
1595
- var cur = node;
1596
- if (!cur) return null;
1597
- if (cur.closest) {
1598
- var h = cur.closest('li.' + HOST_CLASS);
1599
- if (h) cur = h;
1600
- }
1601
- var prev = cur.previousElementSibling;
1602
- while (prev) {
1603
- if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1604
- prev = prev.previousElementSibling;
1605
- }
1606
- } catch (e) {}
1607
- return null;
1594
+ function getTopics(ulEl) {
1595
+ try { return ulEl ? ulEl.querySelectorAll(TOPIC_LI_SEL) : []; } catch(e){ return []; }
1608
1596
  }
1609
1597
 
1610
- function topicLiByTid(ulEl, tid) {
1598
+ function lastTopic(ulEl, topics) {
1611
1599
  try {
1612
- if (!ulEl || !tid) return null;
1613
- var q = ulEl.querySelector(
1614
- TOPIC_LI_SEL + '[data-tid="' + tid + '"], ' +
1615
- TOPIC_LI_SEL + '[data-topic-id="' + tid + '"], ' +
1616
- TOPIC_LI_SEL + '[data-topicid="' + tid + '"]'
1617
- );
1618
- if (q) return q;
1619
-
1620
- var topics = ulEl.querySelectorAll(TOPIC_LI_SEL);
1621
- for (var i=0;i<topics.length;i++){
1622
- var t = getTidFromTopicLi(topics[i]);
1623
- if (t === tid) return topics[i];
1624
- }
1625
- } catch (e) {}
1626
- return null;
1600
+ topics = topics || getTopics(ulEl);
1601
+ return topics && topics.length ? topics[topics.length - 1] : null;
1602
+ } catch (e) { return null; }
1627
1603
  }
1628
1604
 
1629
- function rememberAnchor(el) {
1605
+ function moveAfter(node, anchor) {
1630
1606
  try {
1631
- if (!el) return;
1632
-
1633
- // normalize to host if wrap
1634
- if (el.matches && el.matches(BETWEEN_WRAP_SEL)) {
1635
- var h = el.closest ? el.closest('li.' + HOST_CLASS) : null;
1636
- if (h) el = h;
1637
- }
1638
- if (!isHost(el) && !(el.matches && el.matches(BETWEEN_WRAP_SEL))) return;
1639
-
1640
- if (el.getAttribute && el.getAttribute(ANCHOR_ATTR)) return;
1641
-
1642
- var prevTopic = closestPrevTopicLi(el);
1643
- if (!prevTopic) return;
1644
- var tid = getTidFromTopicLi(prevTopic);
1645
- if (!tid) return;
1646
-
1647
- el.setAttribute(ANCHOR_ATTR, tid);
1607
+ if (!node || !anchor || !anchor.insertAdjacentElement) return;
1608
+ if (node.previousElementSibling === anchor) return;
1609
+ anchor.insertAdjacentElement('afterend', node);
1648
1610
  } catch (e) {}
1649
1611
  }
1650
1612
 
1651
- function repairOne(node, ulEl) {
1613
+ function placeOrPend(host, ulEl, topics) {
1652
1614
  try {
1615
+ if (!host) return;
1653
1616
  ulEl = ulEl || ensureUL();
1654
1617
  if (!ulEl) return;
1655
1618
 
1656
- var el = node;
1657
-
1658
- if (el && el.nodeType === 1 && el.matches && el.matches(BETWEEN_WRAP_SEL)) {
1659
- var host = ensureHostForWrap(el, ulEl) || (el.closest ? el.closest('li.' + HOST_CLASS) : null);
1660
- if (host) el = host;
1661
- }
1619
+ topics = topics || getTopics(ulEl);
1662
1620
 
1663
- if (isHost(el)) {
1664
- rememberAnchor(el);
1665
- var tid = el.getAttribute(ANCHOR_ATTR);
1666
- if (!tid) return;
1667
- var anchorTopic = topicLiByTid(ulEl, tid);
1668
- if (!anchorTopic) return;
1669
- if (el.previousElementSibling !== anchorTopic) {
1670
- anchorTopic.insertAdjacentElement('afterend', el);
1671
- }
1621
+ var after = getAfter(host);
1622
+ if (!after) {
1623
+ var lt = lastTopic(ulEl, topics);
1624
+ if (lt) moveAfter(host, lt);
1625
+ host.classList && host.classList.remove(PENDING_CLASS);
1672
1626
  return;
1673
1627
  }
1674
1628
 
1675
- if (el && el.nodeType === 1 && el.matches && el.matches(BETWEEN_WRAP_SEL)) {
1676
- rememberAnchor(el);
1677
- var tid2 = el.getAttribute(ANCHOR_ATTR);
1678
- if (!tid2) return;
1679
- var anchor2 = topicLiByTid(ulEl, tid2);
1680
- if (!anchor2) return;
1681
- if (el.previousElementSibling !== anchor2) {
1682
- anchor2.insertAdjacentElement('afterend', el);
1683
- }
1684
- }
1685
- } catch (e) {}
1686
- }
1687
-
1688
- function enqueue(node) {
1689
- try {
1690
- if (!node || node.nodeType !== 1) return;
1691
-
1692
- if (isHost(node) || (node.matches && node.matches(BETWEEN_WRAP_SEL))) {
1693
- queue.add(node);
1694
- scheduleFlush();
1629
+ if (after > topics.length) {
1630
+ var lt2 = lastTopic(ulEl, topics);
1631
+ if (lt2) moveAfter(host, lt2);
1632
+ host.classList && host.classList.add(PENDING_CLASS);
1695
1633
  return;
1696
1634
  }
1697
- if (node.querySelectorAll) {
1698
- var wraps = node.querySelectorAll(BETWEEN_WRAP_SEL);
1699
- if (wraps && wraps.length) wraps.forEach(function(w){ queue.add(w); });
1700
- var hosts = node.querySelectorAll('li.' + HOST_CLASS);
1701
- if (hosts && hosts.length) hosts.forEach(function(h){ queue.add(h); });
1702
- if ((wraps && wraps.length) || (hosts && hosts.length)) scheduleFlush();
1703
- }
1635
+
1636
+ var anchor = topics[after - 1];
1637
+ if (anchor) moveAfter(host, anchor);
1638
+ host.classList && host.classList.remove(PENDING_CLASS);
1704
1639
  } catch (e) {}
1705
1640
  }
1706
1641
 
1707
- function flush() {
1708
- pending = false;
1642
+ function reconcile() {
1709
1643
  var ulEl = ensureUL();
1710
1644
  if (!ulEl) return;
1711
1645
 
1712
- // host invalid ul>div and capture anchors
1646
+ var topics = getTopics(ulEl);
1647
+
1713
1648
  try {
1714
- ulEl.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){
1715
- var host = ensureHostForWrap(w, ulEl);
1716
- if (host) queue.add(host);
1649
+ ulEl.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function (w) {
1650
+ var h = ensureHostForWrap(w, ulEl);
1651
+ if (h) placeOrPend(h, ulEl, topics);
1717
1652
  });
1718
- ulEl.querySelectorAll('li.' + HOST_CLASS).forEach(function(h){ rememberAnchor(h); });
1719
- ulEl.querySelectorAll(BETWEEN_WRAP_SEL).forEach(function(w){ rememberAnchor(w); });
1720
- } catch (e) {}
1721
1653
 
1722
- var n = 0;
1723
- for (var item of Array.from(queue)) {
1724
- queue.delete(item);
1725
- repairOne(item, ulEl);
1726
- n++;
1727
- if (n >= 10) break;
1728
- }
1729
- if (queue.size) scheduleFlush();
1654
+ ulEl.querySelectorAll('li.' + HOST_CLASS).forEach(function (h) {
1655
+ placeOrPend(h, ulEl, topics);
1656
+ });
1657
+
1658
+ ulEl.querySelectorAll(BETWEEN_WRAP_SEL).forEach(function (w) {
1659
+ var h = ensureHostForWrap(w, ulEl) || (w.closest ? w.closest('li.' + HOST_CLASS) : null);
1660
+ if (h) placeOrPend(h, ulEl, topics);
1661
+ });
1662
+ } catch (e) {}
1730
1663
  }
1731
1664
 
1732
- function scheduleFlush() {
1665
+ function scheduleReconcile() {
1733
1666
  var now = Date.now();
1734
- if (pending) return;
1735
- if (now - lastFlush < FLUSH_COOLDOWN) return;
1736
- pending = true;
1737
- lastFlush = now;
1738
- requestAnimationFrame(flush);
1667
+ if (scheduled) return;
1668
+ if (now - lastRun < COOLDOWN) return;
1669
+ scheduled = true;
1670
+ lastRun = now;
1671
+ requestAnimationFrame(function () {
1672
+ scheduled = false;
1673
+ reconcile();
1674
+ });
1739
1675
  }
1740
1676
 
1741
1677
  function initObserver() {
1742
1678
  var ulEl = ensureUL();
1743
1679
  if (!ulEl || mo) return;
1744
1680
 
1745
- // initial capture
1746
- try {
1747
- ulEl.querySelectorAll(BETWEEN_WRAP_SEL).forEach(function(w){
1748
- var host = ensureHostForWrap(w, ulEl) || (w.closest ? w.closest('li.' + HOST_CLASS) : null);
1749
- rememberAnchor(host || w);
1750
- });
1751
- } catch (e) {}
1752
-
1753
- mo = new MutationObserver(function(muts){
1754
- try {
1755
- for (var i=0;i<muts.length;i++){
1756
- var m = muts[i];
1757
- if (m.addedNodes && m.addedNodes.length) {
1758
- for (var j=0;j<m.addedNodes.length;j++) enqueue(m.addedNodes[j]);
1759
- }
1760
- if (m.target) enqueue(m.target);
1761
- }
1762
- } catch (e) {}
1681
+ mo = new MutationObserver(function () {
1682
+ scheduleReconcile();
1763
1683
  });
1764
- mo.observe(ulEl, { childList:true, subtree:true });
1684
+
1685
+ mo.observe(ulEl, { childList: true, subtree: true });
1765
1686
  }
1766
1687
 
1767
1688
  function init() {
1768
1689
  initObserver();
1769
- scheduleFlush();
1690
+ scheduleReconcile();
1770
1691
 
1771
1692
  if (window.jQuery) {
1772
1693
  try {
1773
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1694
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function () {
1774
1695
  ul = null;
1775
- try { if (mo) { mo.disconnect(); } } catch(e){}
1696
+ try { if (mo) mo.disconnect(); } catch (e) {}
1776
1697
  mo = null;
1777
1698
  initObserver();
1778
- setTimeout(function(){ scheduleFlush(); }, 80);
1779
- setTimeout(function(){ scheduleFlush(); }, 450);
1699
+ scheduleReconcile();
1700
+ setTimeout(scheduleReconcile, 120);
1701
+ setTimeout(scheduleReconcile, 500);
1780
1702
  });
1781
1703
  } catch (e) {}
1782
1704
  }
1783
1705
 
1784
1706
  var tries = 0;
1785
- var t = setInterval(function(){
1707
+ var t = setInterval(function () {
1786
1708
  tries++;
1787
1709
  if (ensureUL()) {
1788
1710
  clearInterval(t);
1789
1711
  initObserver();
1790
- scheduleFlush();
1712
+ scheduleReconcile();
1791
1713
  }
1792
- if (tries > 25) clearInterval(t);
1714
+ if (tries > 30) clearInterval(t);
1793
1715
  }, 200);
1794
1716
  }
1795
1717
 
1796
1718
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1797
1719
  else init();
1798
1720
  })();
1799
- // ===== /V17.7 =====
1721
+ // ===== /V17.8 =====
1800
1722
 
package/public/style.css CHANGED
@@ -88,8 +88,18 @@ li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; displa
88
88
 
89
89
 
90
90
 
91
- /* ===== V17.7 host styling ===== */
91
+ /* ===== V17.8 pending slots (prevents pile-up top/bottom) ===== */
92
92
  li.nodebb-ezoic-host { list-style:none; width:100%; display:block; }
93
93
  li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width:100%; display:block; }
94
- /* ===== /V17.7 ===== */
94
+
95
+ /* slots whose anchor topic isn't loaded yet */
96
+ li.nodebb-ezoic-host.nodebb-ezoic-pending{
97
+ max-height: 0 !important;
98
+ margin: 0 !important;
99
+ padding: 0 !important;
100
+ overflow: hidden !important;
101
+ opacity: 0 !important;
102
+ pointer-events: none !important;
103
+ }
104
+ /* ===== /V17.8 ===== */
95
105