nodebb-plugin-ezoic-infinite 1.6.36 → 1.6.38

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.38",
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
@@ -498,6 +498,25 @@ function globalGapFixInit() {
498
498
  return el;
499
499
  }
500
500
 
501
+ // Best-effort refresh for Ezoic standalone when we defer placement.
502
+ // Some stacks won't fill ads if a placeholder is rendered while collapsed/0px.
503
+ let _refreshTimer = null;
504
+ function refreshAds(ids) {
505
+ if (!ids || !ids.length) return;
506
+ clearTimeout(_refreshTimer);
507
+ _refreshTimer = setTimeout(() => {
508
+ try {
509
+ if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
510
+ window.ezstandalone.showAds(ids);
511
+ return;
512
+ }
513
+ if (window.ez && typeof window.ez.showAds === 'function') {
514
+ window.ez.showAds(ids);
515
+ }
516
+ } catch (e) {}
517
+ }, 50);
518
+ }
519
+
501
520
  function primePlaceholderPool(allIds) {
502
521
  try {
503
522
  if (!Array.isArray(allIds) || !allIds.length) return;
@@ -1514,20 +1533,18 @@ function buildOrdinalMap(items) {
1514
1533
 
1515
1534
 
1516
1535
 
1517
- // ===== V17.7: Keep injection, anchor each between slot to topic TID and repair on DOM moves (minimal, targeted) =====
1536
+ // ===== V17.8: No pile-up by design keep "future" between slots pending/collapsed until their anchor topic exists =====
1518
1537
  (function () {
1519
1538
  var TOPIC_LI_SEL = 'li[component="category/topic"]';
1520
1539
  var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1521
1540
  var HOST_CLASS = 'nodebb-ezoic-host';
1522
- var ANCHOR_ATTR = 'data-ezoic-anchor-tid';
1541
+ var PENDING_CLASS = 'nodebb-ezoic-pending';
1523
1542
 
1524
1543
  var ul = null;
1525
1544
  var mo = null;
1526
-
1527
- var pending = false;
1528
- var queue = new Set();
1529
- var lastFlush = 0;
1530
- var FLUSH_COOLDOWN = 60;
1545
+ var scheduled = false;
1546
+ var lastRun = 0;
1547
+ var COOLDOWN = 80;
1531
1548
 
1532
1549
  function getTopicList() {
1533
1550
  try {
@@ -1543,12 +1560,14 @@ function buildOrdinalMap(items) {
1543
1560
  return !!(el && el.nodeType === 1 && el.tagName === 'LI' && el.classList && el.classList.contains(HOST_CLASS));
1544
1561
  }
1545
1562
 
1563
+ function isBetweenWrap(el) {
1564
+ try { return !!(el && el.nodeType === 1 && el.matches && el.matches(BETWEEN_WRAP_SEL)); } catch(e){ return false; }
1565
+ }
1566
+
1546
1567
  function ensureHostForWrap(wrap, ulEl) {
1547
- // Only wrap invalid ul>div cases (same idea as V17), no injection hook.
1568
+ // If Ezoic inserts ul > div..., wrap it into a LI to keep a valid list structure (less NodeBB churn).
1548
1569
  try {
1549
- if (!wrap || wrap.nodeType !== 1) return null;
1550
- if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1551
-
1570
+ if (!wrap || !isBetweenWrap(wrap)) return null;
1552
1571
  var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1553
1572
  if (host) return host;
1554
1573
 
@@ -1561,10 +1580,14 @@ function buildOrdinalMap(items) {
1561
1580
  host.setAttribute('role', 'listitem');
1562
1581
  host.style.listStyle = 'none';
1563
1582
  host.style.width = '100%';
1583
+
1564
1584
  try {
1565
1585
  var after = wrap.getAttribute('data-ezoic-after');
1566
1586
  if (after) host.setAttribute('data-ezoic-after', after);
1587
+ var pin = wrap.getAttribute('data-ezoic-pin');
1588
+ if (pin) host.setAttribute('data-ezoic-pin', pin);
1567
1589
  } catch (e) {}
1590
+
1568
1591
  ulEl.insertBefore(host, wrap);
1569
1592
  host.appendChild(wrap);
1570
1593
  try { wrap.style.width = '100%'; } catch (e) {}
@@ -1574,227 +1597,151 @@ function buildOrdinalMap(items) {
1574
1597
  return null;
1575
1598
  }
1576
1599
 
1577
- function getTidFromTopicLi(li) {
1600
+ function getAfter(el) {
1578
1601
  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];
1602
+ var v = null;
1603
+ if (el && el.getAttribute) v = el.getAttribute('data-ezoic-after');
1604
+ if (!v && el && el.querySelector) {
1605
+ var w = el.querySelector(BETWEEN_WRAP_SEL);
1606
+ if (w) v = w.getAttribute('data-ezoic-after');
1588
1607
  }
1589
- } catch (e) {}
1590
- return null;
1608
+ var n = parseInt(v, 10);
1609
+ return isNaN(n) ? null : n;
1610
+ } catch (e) { return null; }
1591
1611
  }
1592
1612
 
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;
1613
+ function getTopics(ulEl) {
1614
+ try { return ulEl ? ulEl.querySelectorAll(TOPIC_LI_SEL) : []; } catch(e){ return []; }
1608
1615
  }
1609
1616
 
1610
- function topicLiByTid(ulEl, tid) {
1617
+ function lastTopic(ulEl, topics) {
1611
1618
  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;
1619
+ topics = topics || getTopics(ulEl);
1620
+ return topics && topics.length ? topics[topics.length - 1] : null;
1621
+ } catch (e) { return null; }
1627
1622
  }
1628
1623
 
1629
- function rememberAnchor(el) {
1624
+ function moveAfter(node, anchor) {
1630
1625
  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);
1626
+ if (!node || !anchor || !anchor.insertAdjacentElement) return;
1627
+ if (node.previousElementSibling === anchor) return;
1628
+ anchor.insertAdjacentElement('afterend', node);
1648
1629
  } catch (e) {}
1649
1630
  }
1650
1631
 
1651
- function repairOne(node, ulEl) {
1632
+ function placeOrPend(host, ulEl, topics) {
1652
1633
  try {
1634
+ if (!host) return;
1653
1635
  ulEl = ulEl || ensureUL();
1654
1636
  if (!ulEl) return;
1655
1637
 
1656
- var el = node;
1638
+ topics = topics || getTopics(ulEl);
1657
1639
 
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
- }
1662
-
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
- }
1640
+ var after = getAfter(host);
1641
+ if (!after) {
1642
+ var lt = lastTopic(ulEl, topics);
1643
+ if (lt) moveAfter(host, lt);
1644
+ host.classList && host.classList.remove(PENDING_CLASS);
1672
1645
  return;
1673
1646
  }
1674
1647
 
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();
1648
+ if (after > topics.length) {
1649
+ // Defer placement until the anchor exists.
1650
+ // Keeping a placeholder in a collapsed/0px container can prevent ad fill on some stacks.
1651
+ host.classList && host.classList.add(PENDING_CLASS);
1652
+ try {
1653
+ getPoolEl().appendChild(host);
1654
+ } catch (e) {}
1695
1655
  return;
1696
1656
  }
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();
1657
+
1658
+ var anchor = topics[after - 1];
1659
+ if (anchor) moveAfter(host, anchor);
1660
+ if (host.classList && host.classList.contains(PENDING_CLASS)) {
1661
+ host.classList.remove(PENDING_CLASS);
1662
+ if (host.dataset && host.dataset.ezoicId) refreshAds([host.dataset.ezoicId]);
1703
1663
  }
1704
1664
  } catch (e) {}
1705
1665
  }
1706
1666
 
1707
- function flush() {
1708
- pending = false;
1667
+ function reconcile() {
1709
1668
  var ulEl = ensureUL();
1710
1669
  if (!ulEl) return;
1711
1670
 
1712
- // host invalid ul>div and capture anchors
1671
+ var topics = getTopics(ulEl);
1672
+
1713
1673
  try {
1714
- ulEl.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){
1715
- var host = ensureHostForWrap(w, ulEl);
1716
- if (host) queue.add(host);
1674
+ ulEl.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function (w) {
1675
+ var h = ensureHostForWrap(w, ulEl);
1676
+ if (h) placeOrPend(h, ulEl, topics);
1717
1677
  });
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
1678
 
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();
1679
+ ulEl.querySelectorAll('li.' + HOST_CLASS).forEach(function (h) {
1680
+ placeOrPend(h, ulEl, topics);
1681
+ });
1682
+
1683
+ ulEl.querySelectorAll(BETWEEN_WRAP_SEL).forEach(function (w) {
1684
+ var h = ensureHostForWrap(w, ulEl) || (w.closest ? w.closest('li.' + HOST_CLASS) : null);
1685
+ if (h) placeOrPend(h, ulEl, topics);
1686
+ });
1687
+ } catch (e) {}
1730
1688
  }
1731
1689
 
1732
- function scheduleFlush() {
1690
+ function scheduleReconcile() {
1733
1691
  var now = Date.now();
1734
- if (pending) return;
1735
- if (now - lastFlush < FLUSH_COOLDOWN) return;
1736
- pending = true;
1737
- lastFlush = now;
1738
- requestAnimationFrame(flush);
1692
+ if (scheduled) return;
1693
+ if (now - lastRun < COOLDOWN) return;
1694
+ scheduled = true;
1695
+ lastRun = now;
1696
+ requestAnimationFrame(function () {
1697
+ scheduled = false;
1698
+ reconcile();
1699
+ });
1739
1700
  }
1740
1701
 
1741
1702
  function initObserver() {
1742
1703
  var ulEl = ensureUL();
1743
1704
  if (!ulEl || mo) return;
1744
1705
 
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) {}
1706
+ mo = new MutationObserver(function () {
1707
+ scheduleReconcile();
1763
1708
  });
1764
- mo.observe(ulEl, { childList:true, subtree:true });
1709
+
1710
+ mo.observe(ulEl, { childList: true, subtree: true });
1765
1711
  }
1766
1712
 
1767
1713
  function init() {
1768
1714
  initObserver();
1769
- scheduleFlush();
1715
+ scheduleReconcile();
1770
1716
 
1771
1717
  if (window.jQuery) {
1772
1718
  try {
1773
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1719
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function () {
1774
1720
  ul = null;
1775
- try { if (mo) { mo.disconnect(); } } catch(e){}
1721
+ try { if (mo) mo.disconnect(); } catch (e) {}
1776
1722
  mo = null;
1777
1723
  initObserver();
1778
- setTimeout(function(){ scheduleFlush(); }, 80);
1779
- setTimeout(function(){ scheduleFlush(); }, 450);
1724
+ scheduleReconcile();
1725
+ setTimeout(scheduleReconcile, 120);
1726
+ setTimeout(scheduleReconcile, 500);
1780
1727
  });
1781
1728
  } catch (e) {}
1782
1729
  }
1783
1730
 
1784
1731
  var tries = 0;
1785
- var t = setInterval(function(){
1732
+ var t = setInterval(function () {
1786
1733
  tries++;
1787
1734
  if (ensureUL()) {
1788
1735
  clearInterval(t);
1789
1736
  initObserver();
1790
- scheduleFlush();
1737
+ scheduleReconcile();
1791
1738
  }
1792
- if (tries > 25) clearInterval(t);
1739
+ if (tries > 30) clearInterval(t);
1793
1740
  }, 200);
1794
1741
  }
1795
1742
 
1796
1743
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1797
1744
  else init();
1798
1745
  })();
1799
- // ===== /V17.7 =====
1746
+ // ===== /V17.8 =====
1800
1747
 
package/public/style.css CHANGED
@@ -88,8 +88,13 @@ 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
+ display: none !important;
98
+ }
99
+ /* ===== /V17.8 ===== */
95
100