nodebb-plugin-ezoic-infinite 1.6.24 → 1.6.26

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.24",
3
+ "version": "1.6.26",
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
@@ -1,6 +1,43 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
+ function ezoicInsertAfterTopicHost(target, wrap, kindClass) {
5
+ try {
6
+ if (!target || !wrap) return false;
7
+ if (kindClass !== 'ezoic-ad-between') return false;
8
+
9
+ // Ensure we insert a LI host that looks like a topic item so NodeBB list virtualization keeps ordering.
10
+ var anchorLi = target.closest ? (target.closest('li[component="category/topic"]') || target.closest('li')) : null;
11
+ if (!anchorLi && target.tagName === 'LI') anchorLi = target;
12
+ if (!anchorLi) return false;
13
+
14
+ var ul = anchorLi.parentElement;
15
+ if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return false;
16
+
17
+ // If wrap already hosted, do nothing
18
+ var existingHost = wrap.closest ? wrap.closest('li.nodebb-ezoic-host') : null;
19
+ if (existingHost) return true;
20
+
21
+ var host = document.createElement('li');
22
+ host.className = 'nodebb-ezoic-host';
23
+ // mimic topic list item so NodeBB doesn't relocate it
24
+ host.setAttribute('component', 'category/topic');
25
+ host.setAttribute('data-ezoic-host', 'between');
26
+ host.style.listStyle = 'none';
27
+ host.style.width = '100%';
28
+
29
+ // Insert host after anchorLi
30
+ if (anchorLi.insertAdjacentElement) anchorLi.insertAdjacentElement('afterend', host);
31
+ else ul.insertBefore(host, anchorLi.nextSibling);
32
+
33
+ host.appendChild(wrap);
34
+ try { wrap.style.width = '100%'; } catch (e) {}
35
+ return true;
36
+ } catch (e) {}
37
+ return false;
38
+ }
39
+
40
+
4
41
  // Track scroll direction to avoid aggressive recycling when the user scrolls upward.
5
42
  // Recycling while scrolling up is a common cause of ads "bunching" and a "disappearing too fast" feeling.
6
43
  let lastScrollY = 0;
@@ -668,7 +705,8 @@ function globalGapFixInit() {
668
705
  insertingIds.add(id);
669
706
  try {
670
707
  const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
671
- target.insertAdjacentElement('afterend', wrap);
708
+ if (ezoicInsertAfterTopicHost(target, wrap, kindClass)) return;
709
+ target.insertAdjacentElement('afterend', wrap);
672
710
 
673
711
  // If placeholder exists elsewhere (including pool), move it into the wrapper.
674
712
  if (existingPh) {
@@ -1160,7 +1198,8 @@ function buildOrdinalMap(items) {
1160
1198
  if (!anchorEl || !wrap || !wrap.isConnected) return null;
1161
1199
 
1162
1200
  wrap.setAttribute('data-ezoic-after', String(afterPos));
1163
- anchorEl.insertAdjacentElement('afterend', wrap);
1201
+ if (ezoicInsertAfterTopicHost(anchorEl, wrap, kindClass)) return;
1202
+ anchorEl.insertAdjacentElement('afterend', wrap);
1164
1203
 
1165
1204
  // Ensure minimal layout impact.
1166
1205
  try { wrap.style.contain = 'layout style paint'; } catch (e) {}
@@ -1511,227 +1550,27 @@ function buildOrdinalMap(items) {
1511
1550
 
1512
1551
 
1513
1552
 
1514
- // ===== V14.1.3: Mutation-based reconcile (no scroll listener), place by data-ezoic-after =====
1515
- (function () {
1516
- var WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1517
- var HOST_SEL = 'li.nodebb-ezoic-host';
1518
- var TOPIC_LI_SEL = 'li[component="category/topic"]';
1519
-
1520
- var freezeUntil = 0;
1521
- var pending = false;
1522
- var queue = new Set();
1523
-
1524
- function now() { return Date.now(); }
1525
-
1526
- function getTopicList() {
1527
- try {
1528
- var topic = document.querySelector(TOPIC_LI_SEL);
1529
- if (!topic) return null;
1530
- return topic.closest ? topic.closest('ul,ol') : null;
1531
- } catch (e) { return null; }
1532
- }
1533
-
1534
- function isLoading() {
1535
- try {
1536
- if (now() < freezeUntil) return true;
1537
- return !!document.querySelector('.infinite-loading, .infinite-scroll-loading, .loading-indicator, .spinner, .topic-loading');
1538
- } catch (e) { return false; }
1539
- }
1540
-
1541
- function ensureHostForWrap(wrap, ul) {
1542
- try {
1543
- if (!wrap || wrap.nodeType !== 1) return null;
1544
- if (!(wrap.matches && wrap.matches(WRAP_SEL))) return null;
1545
- var host = wrap.closest ? wrap.closest(HOST_SEL) : null;
1546
- if (host) return host;
1547
- ul = ul || (wrap.closest ? wrap.closest('ul,ol') : null);
1548
- if (!ul) return null;
1549
- if (wrap.parentElement === ul) {
1550
- host = document.createElement('li');
1551
- host.className = 'nodebb-ezoic-host';
1552
- host.setAttribute('role', 'listitem');
1553
- host.style.listStyle = 'none';
1554
- host.style.width = '100%';
1555
- ul.insertBefore(host, wrap);
1556
- host.appendChild(wrap);
1557
- try { wrap.style.width = '100%'; } catch (e) {}
1558
- return host;
1559
- }
1560
- } catch (e) {}
1561
- return null;
1562
- }
1563
-
1564
- function nthTopic(ul, n) {
1565
- try {
1566
- var topics = ul.querySelectorAll(TOPIC_LI_SEL);
1567
- if (!topics || !topics.length) return null;
1568
- if (n < 1) n = 1;
1569
- if (n > topics.length) n = topics.length;
1570
- return topics[n-1] || null;
1571
- } catch (e) { return null; }
1572
- }
1573
-
1574
- function previousTopicLi(host) {
1575
- try {
1576
- var prev = host.previousElementSibling;
1577
- while (prev) {
1578
- if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1579
- prev = prev.previousElementSibling;
1580
- }
1581
- } catch (e) {}
1582
- return null;
1583
- }
1584
-
1585
- function placeHost(host, ul) {
1586
- try {
1587
- if (!host || host.nodeType !== 1) return;
1588
- ul = ul || host.parentElement;
1589
- if (!ul) return;
1590
-
1591
- var wrap = host.querySelector && host.querySelector(WRAP_SEL);
1592
- if (!wrap) { host.remove(); return; }
1593
-
1594
- var after = wrap.getAttribute('data-ezoic-after');
1595
- var anchor = null;
1596
-
1597
- if (after) {
1598
- anchor = nthTopic(ul, parseInt(after, 10));
1599
- }
1600
- if (!anchor) {
1601
- anchor = previousTopicLi(host);
1602
- }
1603
- if (!anchor) {
1604
- // Can't place reliably; if host drifted to top area, remove to avoid pile-up.
1605
- // Otherwise keep as-is.
1606
- var firstTopic = nthTopic(ul, 1);
1607
- if (firstTopic && host !== firstTopic && host.compareDocumentPosition(firstTopic) & Node.DOCUMENT_POSITION_FOLLOWING) {
1608
- host.remove();
1609
- }
1610
- return;
1611
- }
1612
-
1613
- if (host.previousElementSibling !== anchor) {
1614
- anchor.insertAdjacentElement('afterend', host);
1615
- }
1616
- } catch (e) {}
1617
- }
1618
-
1619
- function drain() {
1620
- pending = false;
1621
- if (isLoading()) {
1622
- // retry later
1623
- scheduleDrain(250);
1624
- return;
1625
- }
1626
- var ul = getTopicList();
1627
- if (!ul) return;
1628
-
1629
- // repair invalid ul>div once
1630
- try {
1631
- ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){
1632
- var h = ensureHostForWrap(w, ul);
1633
- if (h) queue.add(h);
1634
- });
1635
- } catch (e) {}
1636
-
1637
- var count = 0;
1638
- for (var host of Array.from(queue)) {
1639
- queue.delete(host);
1640
- placeHost(host, ul);
1641
- count++;
1642
- if (count >= 8) break; // keep it light
1643
- }
1644
- if (queue.size) scheduleDrain(60);
1645
- }
1646
-
1647
- function scheduleDrain(delay) {
1648
- if (pending) return;
1649
- pending = true;
1650
- if (delay) {
1651
- setTimeout(function(){ requestAnimationFrame(drain); }, delay);
1652
- } else {
1653
- requestAnimationFrame(drain);
1654
- }
1655
- }
1656
-
1657
- function enqueueFromNode(node) {
1658
- try {
1659
- if (!node || node.nodeType !== 1) return;
1660
- var ul = getTopicList();
1661
- if (!ul) return;
1662
-
1663
- if (node.matches && node.matches(WRAP_SEL)) {
1664
- var h = ensureHostForWrap(node, ul) || (node.closest ? node.closest(HOST_SEL) : null);
1665
- if (h) queue.add(h);
1666
- } else if (node.matches && node.matches(HOST_SEL)) {
1667
- queue.add(node);
1668
- } else if (node.querySelectorAll) {
1669
- node.querySelectorAll(WRAP_SEL).forEach(function(w){
1670
- var h2 = ensureHostForWrap(w, ul) || (w.closest ? w.closest(HOST_SEL) : null);
1671
- if (h2) queue.add(h2);
1672
- });
1673
- node.querySelectorAll(HOST_SEL).forEach(function(h3){ queue.add(h3); });
1674
- }
1675
- } catch (e) {}
1676
- }
1677
-
1678
- function init() {
1679
- // initial scan (light)
1680
- try {
1681
- var ul = getTopicList();
1682
- if (ul) {
1683
- ul.querySelectorAll(WRAP_SEL).forEach(function(w){
1684
- var h = ensureHostForWrap(w, ul) || (w.closest ? w.closest(HOST_SEL) : null);
1685
- if (h) queue.add(h);
1686
- });
1687
- scheduleDrain(0);
1688
- }
1689
- } catch (e) {}
1690
-
1691
- // Observe ONLY the topic list container once available
1692
- try {
1693
- if (typeof MutationObserver !== 'undefined') {
1694
- var mo = new MutationObserver(function(muts){
1695
- for (var i=0;i<muts.length;i++){
1696
- var m = muts[i];
1697
- if (m.addedNodes && m.addedNodes.length) {
1698
- for (var j=0;j<m.addedNodes.length;j++){
1699
- enqueueFromNode(m.addedNodes[j]);
1700
- }
1701
- scheduleDrain(0);
1702
- }
1703
- }
1704
- });
1705
-
1706
- var ul = getTopicList();
1707
- if (ul) mo.observe(ul, { childList:true, subtree:true });
1708
- else {
1709
- // wait for ajaxify
1710
- var mo2 = new MutationObserver(function(){
1711
- var u2 = getTopicList();
1712
- if (u2) {
1713
- try { mo.observe(u2, { childList:true, subtree:true }); } catch(e){}
1714
- try { mo2.disconnect(); } catch(e){}
1715
- }
1716
- });
1717
- mo2.observe(document.documentElement || document.body, { childList:true, subtree:true });
1718
- }
1719
- }
1720
- } catch (e) {}
1721
-
1722
- if (window.jQuery) {
1553
+ // ===== V16 repair invalid UL children (between) =====
1554
+ function ezoicRepairBetweenHosts() {
1555
+ try {
1556
+ var bad = document.querySelectorAll('ul > div.nodebb-ezoic-wrap.ezoic-ad-between, ol > div.nodebb-ezoic-wrap.ezoic-ad-between');
1557
+ bad.forEach(function(wrap){
1723
1558
  try {
1724
- window.jQuery(window).on('action:infiniteScroll.loaded action:ajaxify.end', function(){
1725
- freezeUntil = now() + 900; // allow NodeBB to settle
1726
- // then reconcile any drift
1727
- setTimeout(function(){ scheduleDrain(0); }, 950);
1728
- });
1559
+ var prev = wrap.previousElementSibling;
1560
+ if (!prev) return;
1561
+ ezoicInsertAfterTopicHost(prev, wrap, 'ezoic-ad-between');
1729
1562
  } catch (e) {}
1730
- }
1731
- }
1732
-
1733
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1734
- else init();
1735
- })();
1736
- // ===== /V14.1.3 =====
1563
+ });
1564
+ } catch (e) {}
1565
+ }
1566
+ try { ezoicRepairBetweenHosts(); } catch (e) {}
1567
+ if (window.jQuery) {
1568
+ try {
1569
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1570
+ setTimeout(function(){ try { ezoicRepairBetweenHosts(); } catch(e) {} }, 0);
1571
+ setTimeout(function(){ try { ezoicRepairBetweenHosts(); } catch(e) {} }, 250);
1572
+ });
1573
+ } catch (e) {}
1574
+ }
1575
+ // ===== /V16 =====
1737
1576
 
package/public/style.css CHANGED
@@ -81,7 +81,14 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V14.1.3 host ===== */
85
- li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- /* ===== /V14.1.3 ===== */
84
+ /* ===== V16 host topic li ===== */
85
+ li.nodebb-ezoic-host[component="category/topic"]{
86
+ list-style:none;
87
+ width:100%;
88
+ }
89
+ /* ensure host doesn't inherit topic card layout */
90
+ li.nodebb-ezoic-host[component="category/topic"] > .nodebb-ezoic-wrap.ezoic-ad-between{
91
+ width:100%;
92
+ }
93
+ /* ===== /V16 ===== */
87
94