nodebb-plugin-ezoic-infinite 1.6.34 → 1.6.36

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.34",
3
+ "version": "1.6.36",
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,189 +1514,287 @@ function buildOrdinalMap(items) {
1514
1514
 
1515
1515
 
1516
1516
 
1517
- // ===== V17.3: keep injection; hide top pile on upscroll; rehome EMPTY hosts on downscroll =====
1518
- (function(){
1517
+ // ===== V17.7: Keep injection, anchor each between slot to topic TID and repair on DOM moves (minimal, targeted) =====
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 PILED_ATTR = 'data-ezoic-piled';
1522
+ var ANCHOR_ATTR = 'data-ezoic-anchor-tid';
1523
1523
 
1524
- var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1525
- var scheduled = false;
1526
- var lastRun = 0;
1527
- var COOLDOWN = 120;
1524
+ var ul = null;
1525
+ var mo = null;
1528
1526
 
1529
- function getY(){ return window.pageYOffset || document.documentElement.scrollTop || 0; }
1527
+ var pending = false;
1528
+ var queue = new Set();
1529
+ var lastFlush = 0;
1530
+ var FLUSH_COOLDOWN = 60;
1530
1531
 
1531
- function getTopicList(){
1532
+ function getTopicList() {
1532
1533
  try {
1533
1534
  var li = document.querySelector(TOPIC_LI_SEL);
1534
1535
  if (!li) return null;
1535
1536
  return li.closest ? li.closest('ul,ol') : null;
1536
- } catch(e){ return null; }
1537
+ } catch (e) { return null; }
1537
1538
  }
1538
1539
 
1539
- function ensureHostForWrap(wrap, ul){
1540
- try{
1541
- if (!wrap || wrap.nodeType!==1) return null;
1540
+ function ensureUL() { ul = ul || getTopicList(); return ul; }
1541
+
1542
+ function isHost(el) {
1543
+ return !!(el && el.nodeType === 1 && el.tagName === 'LI' && el.classList && el.classList.contains(HOST_CLASS));
1544
+ }
1545
+
1546
+ function ensureHostForWrap(wrap, ulEl) {
1547
+ // Only wrap invalid ul>div cases (same idea as V17), no injection hook.
1548
+ try {
1549
+ if (!wrap || wrap.nodeType !== 1) return null;
1542
1550
  if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1543
1551
 
1544
- var host = wrap.closest ? wrap.closest('li.'+HOST_CLASS) : null;
1552
+ var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1545
1553
  if (host) return host;
1546
1554
 
1547
- if (!ul) ul = wrap.closest ? wrap.closest('ul,ol') : null;
1548
- if (!ul || !(ul.tagName==='UL' || ul.tagName==='OL')) return null;
1555
+ ulEl = ulEl || (wrap.closest ? wrap.closest('ul,ol') : null);
1556
+ if (!ulEl || !(ulEl.tagName === 'UL' || ulEl.tagName === 'OL')) return null;
1549
1557
 
1550
- if (wrap.parentElement === ul){
1558
+ if (wrap.parentElement === ulEl) {
1551
1559
  host = document.createElement('li');
1552
1560
  host.className = HOST_CLASS;
1553
- host.setAttribute('role','listitem');
1554
- host.style.listStyle='none';
1555
- host.style.width='100%';
1556
- // preserve after
1561
+ host.setAttribute('role', 'listitem');
1562
+ host.style.listStyle = 'none';
1563
+ host.style.width = '100%';
1557
1564
  try {
1558
1565
  var after = wrap.getAttribute('data-ezoic-after');
1559
1566
  if (after) host.setAttribute('data-ezoic-after', after);
1560
- } catch(e){}
1561
- ul.insertBefore(host, wrap);
1567
+ } catch (e) {}
1568
+ ulEl.insertBefore(host, wrap);
1562
1569
  host.appendChild(wrap);
1563
- try { wrap.style.width='100%'; } catch(e){}
1570
+ try { wrap.style.width = '100%'; } catch (e) {}
1564
1571
  return host;
1565
1572
  }
1566
- }catch(e){}
1573
+ } catch (e) {}
1574
+ return null;
1575
+ }
1576
+
1577
+ function getTidFromTopicLi(li) {
1578
+ 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];
1588
+ }
1589
+ } catch (e) {}
1567
1590
  return null;
1568
1591
  }
1569
1592
 
1570
- function hostHasAdContent(host){
1571
- // iframes/amp ads mean it's already rendered; moving might reduce fill
1572
- try{
1573
- return !!(host.querySelector && host.querySelector('iframe, amp-ad, amp-iframe, .amp-ads, .ezoic-ad iframe'));
1574
- }catch(e){ return false; }
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;
1575
1608
  }
1576
1609
 
1577
- function nthTopic(ul, n){
1578
- try{
1579
- var topics = ul.querySelectorAll(TOPIC_LI_SEL);
1580
- if (!topics || !topics.length) return null;
1581
- if (n<1) n=1;
1582
- if (n>topics.length) n=topics.length;
1583
- return topics[n-1] || null;
1584
- }catch(e){ return null; }
1610
+ function topicLiByTid(ulEl, tid) {
1611
+ 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;
1585
1627
  }
1586
1628
 
1587
- function getAfter(host){
1588
- try{
1589
- var v = host.getAttribute('data-ezoic-after');
1590
- if (!v){
1591
- var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1592
- if (wrap) v = wrap.getAttribute('data-ezoic-after');
1629
+ function rememberAnchor(el) {
1630
+ 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;
1593
1637
  }
1594
- var n = parseInt(v,10);
1595
- return isNaN(n)? null : n;
1596
- }catch(e){ return null; }
1597
- }
1598
-
1599
- function detectAndHideTopPile(ul){
1600
- // Hide piled hosts near top so user doesn't see the stack.
1601
- try{
1602
- var kids = ul.children;
1603
- var limit = Math.min(kids.length, 60);
1604
- var run = 0;
1605
- for (var i=0;i<limit;i++){
1606
- var el = kids[i];
1607
- var isBetweenHost = (el.tagName==='LI' && el.classList && el.classList.contains(HOST_CLASS) && el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1608
- var isTopic = (el.matches && el.matches(TOPIC_LI_SEL));
1609
- if (isBetweenHost){
1610
- run++;
1611
- if (run>=2){
1612
- // mark piled beyond the first
1613
- el.setAttribute(PILED_ATTR,'1');
1614
- }
1615
- } else if (isTopic){
1616
- run=0;
1617
- } else {
1618
- // reset on any non-topic
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);
1648
+ } catch (e) {}
1649
+ }
1650
+
1651
+ function repairOne(node, ulEl) {
1652
+ try {
1653
+ ulEl = ulEl || ensureUL();
1654
+ if (!ulEl) return;
1655
+
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
+ }
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);
1619
1671
  }
1672
+ return;
1620
1673
  }
1621
- }catch(e){}
1674
+
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) {}
1622
1686
  }
1623
1687
 
1624
- function rehomePiledEmptyOnDownscroll(ul){
1625
- // On downscroll, try to move only EMPTY (not yet rendered) piled hosts to intended anchors and unhide them.
1626
- try{
1627
- var piled = ul.querySelectorAll('li.'+HOST_CLASS+'['+PILED_ATTR+'="1"]');
1628
- if (!piled.length) return;
1688
+ function enqueue(node) {
1689
+ try {
1690
+ if (!node || node.nodeType !== 1) return;
1629
1691
 
1630
- piled.forEach(function(host){
1631
- try{
1632
- // if it already has rendered ad, just unhide; do not move
1633
- if (hostHasAdContent(host)){
1634
- host.removeAttribute(PILED_ATTR);
1635
- return;
1636
- }
1637
- var after = getAfter(host);
1638
- var anchor = after!=null ? nthTopic(ul, after) : null;
1639
- if (anchor){
1640
- anchor.insertAdjacentElement('afterend', host);
1641
- }
1642
- host.removeAttribute(PILED_ATTR);
1643
- }catch(e){}
1692
+ if (isHost(node) || (node.matches && node.matches(BETWEEN_WRAP_SEL))) {
1693
+ queue.add(node);
1694
+ scheduleFlush();
1695
+ return;
1696
+ }
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
+ }
1704
+ } catch (e) {}
1705
+ }
1706
+
1707
+ function flush() {
1708
+ pending = false;
1709
+ var ulEl = ensureUL();
1710
+ if (!ulEl) return;
1711
+
1712
+ // host invalid ul>div and capture anchors
1713
+ try {
1714
+ ulEl.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){
1715
+ var host = ensureHostForWrap(w, ulEl);
1716
+ if (host) queue.add(host);
1644
1717
  });
1645
- }catch(e){}
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
+
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();
1646
1730
  }
1647
1731
 
1648
- function schedule(fn){
1732
+ function scheduleFlush() {
1649
1733
  var now = Date.now();
1650
- if (now-lastRun < COOLDOWN) return;
1651
- if (scheduled) return;
1652
- scheduled = true;
1653
- requestAnimationFrame(function(){
1654
- scheduled=false;
1655
- lastRun = Date.now();
1656
- fn();
1657
- });
1734
+ if (pending) return;
1735
+ if (now - lastFlush < FLUSH_COOLDOWN) return;
1736
+ pending = true;
1737
+ lastFlush = now;
1738
+ requestAnimationFrame(flush);
1658
1739
  }
1659
1740
 
1660
- function onScroll(){
1661
- var y = getY();
1662
- var dy = y - lastY;
1663
- lastY = y;
1664
- var ul = getTopicList();
1665
- if (!ul) return;
1741
+ function initObserver() {
1742
+ var ulEl = ensureUL();
1743
+ if (!ulEl || mo) return;
1666
1744
 
1667
- // always wrap invalid ul>div (cheap)
1668
- try { ul.querySelectorAll(':scope > '+BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); }); } catch(e){}
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) {}
1669
1752
 
1670
- if (dy < -8 && y < 900){
1671
- // upscroll near top: hide pile quickly (no moving)
1672
- schedule(function(){ detectAndHideTopPile(ul); });
1673
- } else if (dy > 8){
1674
- // downscroll: rehome only empty piled hosts (safe) and unhide
1675
- schedule(function(){ rehomePiledEmptyOnDownscroll(ul); });
1676
- }
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) {}
1763
+ });
1764
+ mo.observe(ulEl, { childList:true, subtree:true });
1677
1765
  }
1678
1766
 
1679
- function init(){
1680
- window.addEventListener('scroll', onScroll, { passive:true });
1767
+ function init() {
1768
+ initObserver();
1769
+ scheduleFlush();
1681
1770
 
1682
- if (window.jQuery){
1683
- try{
1771
+ if (window.jQuery) {
1772
+ try {
1684
1773
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1685
- // wrap invalids only
1686
- setTimeout(function(){
1687
- try{
1688
- var ul = getTopicList();
1689
- if (!ul) return;
1690
- ul.querySelectorAll(':scope > '+BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1691
- }catch(e){}
1692
- }, 50);
1774
+ ul = null;
1775
+ try { if (mo) { mo.disconnect(); } } catch(e){}
1776
+ mo = null;
1777
+ initObserver();
1778
+ setTimeout(function(){ scheduleFlush(); }, 80);
1779
+ setTimeout(function(){ scheduleFlush(); }, 450);
1693
1780
  });
1694
- }catch(e){}
1781
+ } catch (e) {}
1695
1782
  }
1783
+
1784
+ var tries = 0;
1785
+ var t = setInterval(function(){
1786
+ tries++;
1787
+ if (ensureUL()) {
1788
+ clearInterval(t);
1789
+ initObserver();
1790
+ scheduleFlush();
1791
+ }
1792
+ if (tries > 25) clearInterval(t);
1793
+ }, 200);
1696
1794
  }
1697
1795
 
1698
1796
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1699
1797
  else init();
1700
1798
  })();
1701
- // ===== /V17.3 =====
1799
+ // ===== /V17.7 =====
1702
1800
 
package/public/style.css CHANGED
@@ -88,10 +88,8 @@ li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; displa
88
88
 
89
89
 
90
90
 
91
- /* ===== V17.3 piled hide ===== */
92
- li.nodebb-ezoic-host[data-ezoic-piled="1"] { display: none !important; }
93
- /* keep host stable */
91
+ /* ===== V17.7 host styling ===== */
94
92
  li.nodebb-ezoic-host { list-style:none; width:100%; display:block; }
95
93
  li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width:100%; display:block; }
96
- /* ===== /V17.3 ===== */
94
+ /* ===== /V17.7 ===== */
97
95