nodebb-plugin-ezoic-infinite 1.6.27 → 1.6.29

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.27",
3
+ "version": "1.6.29",
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,93 +1,6 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- function ezoicEnsureBetweenHost(anchorLi, wrap) {
5
- try {
6
- if (!anchorLi || !wrap) return null;
7
- var ul = anchorLi.parentElement;
8
- if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return null;
9
-
10
- // already hosted?
11
- var host = wrap.closest ? wrap.closest('li.nodebb-ezoic-host') : null;
12
- if (host) return host;
13
-
14
- host = document.createElement('li');
15
- host.className = 'nodebb-ezoic-host';
16
- host.setAttribute('role', 'listitem');
17
- host.style.listStyle = 'none';
18
- host.style.width = '100%';
19
-
20
- // copy after position for later repairs
21
- try {
22
- var after = wrap.getAttribute && wrap.getAttribute('data-ezoic-after');
23
- if (after) host.setAttribute('data-ezoic-after', after);
24
- } catch (e) {}
25
-
26
- anchorLi.insertAdjacentElement('afterend', host);
27
- host.appendChild(wrap);
28
- try { wrap.style.width = '100%'; } catch (e) {}
29
- return host;
30
- } catch (e) {}
31
- return null;
32
- }
33
-
34
- function ezoicDesiredAnchorForAfter(ul, afterNum) {
35
- try {
36
- var n = parseInt(afterNum, 10);
37
- if (!n || n < 1) return null;
38
- var topics = ul.querySelectorAll('li[component="category/topic"]');
39
- if (!topics || !topics.length) return null;
40
- if (n > topics.length) n = topics.length;
41
- return topics[n-1] || null;
42
- } catch (e) {}
43
- return null;
44
- }
45
-
46
- function ezoicRepairBetweenHosts() {
47
- // Reposition between hosts if NodeBB moved them (e.g., to the top/bottom) after virtualized rerender.
48
- try {
49
- var ul = document.querySelector('li[component="category/topic"]')?.closest('ul,ol');
50
- if (!ul) return;
51
-
52
- // 1) fix invalid ul>div wraps
53
- ul.querySelectorAll(':scope > div.nodebb-ezoic-wrap.ezoic-ad-between').forEach(function(wrap){
54
- try {
55
- var prev = wrap.previousElementSibling;
56
- if (prev && prev.tagName === 'LI') {
57
- ezoicEnsureBetweenHost(prev, wrap);
58
- }
59
- } catch(e){}
60
- });
61
-
62
- // 2) ensure every between wrap is hosted (but don't scan whole document)
63
- ul.querySelectorAll('div.nodebb-ezoic-wrap.ezoic-ad-between').forEach(function(wrap){
64
- try {
65
- var host = wrap.closest ? wrap.closest('li.nodebb-ezoic-host') : null;
66
- if (!host) {
67
- var prev = wrap.closest ? wrap.closest('li') : null;
68
- if (prev) ezoicEnsureBetweenHost(prev, wrap);
69
- }
70
- } catch(e){}
71
- });
72
-
73
- // 3) reposition hosts based on data-ezoic-after (stable intent)
74
- ul.querySelectorAll('li.nodebb-ezoic-host').forEach(function(host){
75
- try {
76
- var wrap = host.querySelector && host.querySelector('div.nodebb-ezoic-wrap.ezoic-ad-between');
77
- if (!wrap) { host.remove(); return; }
78
- var after = host.getAttribute('data-ezoic-after') || wrap.getAttribute('data-ezoic-after');
79
- if (!after) return;
80
- var anchor = ezoicDesiredAnchorForAfter(ul, after);
81
- if (!anchor) return;
82
- if (host.previousElementSibling !== anchor) {
83
- anchor.insertAdjacentElement('afterend', host);
84
- }
85
- } catch(e){}
86
- });
87
- } catch (e) {}
88
- }
89
-
90
-
91
4
  // Track scroll direction to avoid aggressive recycling when the user scrolls upward.
92
5
  // Recycling while scrolling up is a common cause of ads "bunching" and a "disappearing too fast" feeling.
93
6
  let lastScrollY = 0;
@@ -755,11 +668,7 @@ function globalGapFixInit() {
755
668
  insertingIds.add(id);
756
669
  try {
757
670
  const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
758
- if (kindClass === 'ezoic-ad-between') {
759
- var aLi = (target.closest ? (target.closest('li[component="category/topic"]') || target.closest('li')) : null) || (target.tagName==='LI'?target:null);
760
- if (aLi) { ezoicEnsureBetweenHost(aLi, wrap); return; }
761
- }
762
- target.insertAdjacentElement('afterend', wrap);
671
+ target.insertAdjacentElement('afterend', wrap);
763
672
 
764
673
  // If placeholder exists elsewhere (including pool), move it into the wrapper.
765
674
  if (existingPh) {
@@ -1251,11 +1160,7 @@ function buildOrdinalMap(items) {
1251
1160
  if (!anchorEl || !wrap || !wrap.isConnected) return null;
1252
1161
 
1253
1162
  wrap.setAttribute('data-ezoic-after', String(afterPos));
1254
- if (kindClass === 'ezoic-ad-between') {
1255
- var aLi = (anchorEl.closest ? (anchorEl.closest('li[component="category/topic"]') || anchorEl.closest('li')) : null) || (anchorEl.tagName==='LI'?anchorEl:null);
1256
- if (aLi) { ezoicEnsureBetweenHost(aLi, wrap); return; }
1257
- }
1258
- anchorEl.insertAdjacentElement('afterend', wrap);
1163
+ anchorEl.insertAdjacentElement('afterend', wrap);
1259
1164
 
1260
1165
  // Ensure minimal layout impact.
1261
1166
  try { wrap.style.contain = 'layout style paint'; } catch (e) {}
@@ -1606,16 +1511,176 @@ function buildOrdinalMap(items) {
1606
1511
 
1607
1512
 
1608
1513
 
1609
- // ===== V16.1 event-based repair (no scroll, no heavy observers) =====
1610
- try { ezoicRepairBetweenHosts(); } catch(e) {}
1611
- if (window.jQuery) {
1612
- try {
1613
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1614
- // Let NodeBB settle before repositioning
1615
- setTimeout(function(){ try { ezoicRepairBetweenHosts(); } catch(e) {} }, 150);
1616
- setTimeout(function(){ try { ezoicRepairBetweenHosts(); } catch(e) {} }, 700);
1514
+ // ===== V17.1 upscroll-only top pile-up fixer (keeps injection intact) =====
1515
+ (function () {
1516
+ var TOPIC_LI_SEL = 'li[component="category/topic"]';
1517
+ var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1518
+ var HOST_CLASS = 'nodebb-ezoic-host';
1519
+
1520
+ var scheduled = false;
1521
+ var lastRun = 0;
1522
+ var COOLDOWN = 140;
1523
+
1524
+ var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
1525
+
1526
+ function getY() {
1527
+ return window.pageYOffset || document.documentElement.scrollTop || 0;
1528
+ }
1529
+
1530
+ function getTopicList() {
1531
+ try {
1532
+ var li = document.querySelector(TOPIC_LI_SEL);
1533
+ if (!li) return null;
1534
+ return li.closest ? li.closest('ul,ol') : null;
1535
+ } catch (e) { return null; }
1536
+ }
1537
+
1538
+ function isHost(node) {
1539
+ return !!(node && node.nodeType === 1 && node.tagName === 'LI' && node.classList && node.classList.contains(HOST_CLASS));
1540
+ }
1541
+
1542
+ function ensureHostForWrap(wrap, ul) {
1543
+ // only wrap invalid ul>div cases; do not interfere with normal injection
1544
+ try {
1545
+ if (!wrap || wrap.nodeType !== 1) return null;
1546
+ if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1547
+
1548
+ var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1549
+ if (host) return host;
1550
+
1551
+ if (!ul) ul = wrap.closest ? wrap.closest('ul,ol') : null;
1552
+ if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return null;
1553
+
1554
+ if (wrap.parentElement === ul) {
1555
+ host = document.createElement('li');
1556
+ host.className = HOST_CLASS;
1557
+ host.setAttribute('role', 'listitem');
1558
+ host.style.listStyle = 'none';
1559
+ host.style.width = '100%';
1560
+ ul.insertBefore(host, wrap);
1561
+ host.appendChild(wrap);
1562
+ try { wrap.style.width = '100%'; } catch (e) {}
1563
+ return host;
1564
+ }
1565
+ } catch (e) {}
1566
+ return null;
1567
+ }
1568
+
1569
+ function previousTopicLi(node) {
1570
+ try {
1571
+ var prev = node.previousElementSibling;
1572
+ while (prev) {
1573
+ if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1574
+ prev = prev.previousElementSibling;
1575
+ }
1576
+ } catch (e) {}
1577
+ return null;
1578
+ }
1579
+
1580
+ function detectTopPileUp(ul) {
1581
+ // Detect a pile-up near the top: within first ~40 children, find 2+ between ads without topics between.
1582
+ try {
1583
+ var kids = ul.children;
1584
+ var limit = Math.min(kids.length, 40);
1585
+ var run = 0;
1586
+ var maxRun = 0;
1587
+ for (var i=0;i<limit;i++){
1588
+ var el = kids[i];
1589
+ var isBetween = false;
1590
+ if (isHost(el)) isBetween = !!(el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1591
+ else if (el.matches && el.matches(BETWEEN_WRAP_SEL)) isBetween = true;
1592
+
1593
+ if (isBetween) { run++; if (run>maxRun) maxRun=run; }
1594
+ else if (el.matches && el.matches(TOPIC_LI_SEL)) { run=0; }
1595
+ else { run=0; }
1596
+ }
1597
+ return maxRun >= 2;
1598
+ } catch (e) {}
1599
+ return false;
1600
+ }
1601
+
1602
+ function redistributeTopPile(ul) {
1603
+ try {
1604
+ if (!ul) return;
1605
+
1606
+ // Step 0: wrap invalid top-level between divs
1607
+ try { ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); }); } catch(e){}
1608
+
1609
+ if (!detectTopPileUp(ul)) return;
1610
+
1611
+ // Only move hosts that are currently in the top region (first 60 list children).
1612
+ var kids = ul.children;
1613
+ var limit = Math.min(kids.length, 60);
1614
+ for (var i=0;i<limit;i++){
1615
+ var el = kids[i];
1616
+ if (!isHost(el)) continue;
1617
+ var host = el;
1618
+ var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1619
+ if (!wrap) continue;
1620
+
1621
+ var anchor = previousTopicLi(host);
1622
+ if (!anchor) continue;
1623
+ if (host.previousElementSibling !== anchor) {
1624
+ anchor.insertAdjacentElement('afterend', host);
1625
+ }
1626
+ }
1627
+ } catch (e) {}
1628
+ }
1629
+
1630
+ function schedule(reason) {
1631
+ var now = Date.now();
1632
+ if (now - lastRun < COOLDOWN) return;
1633
+ if (scheduled) return;
1634
+ scheduled = true;
1635
+ requestAnimationFrame(function(){
1636
+ scheduled = false;
1637
+ lastRun = Date.now();
1638
+ try {
1639
+ var ul = getTopicList();
1640
+ if (!ul) return;
1641
+ redistributeTopPile(ul);
1642
+ } catch (e) {}
1617
1643
  });
1618
- } catch(e) {}
1619
- }
1620
- // ===== /V16.1 =====
1644
+ }
1645
+
1646
+ function onScroll() {
1647
+ var y = getY();
1648
+ var dy = y - lastY;
1649
+ lastY = y;
1650
+
1651
+ // Only act on upscroll (when the pile-up manifests)
1652
+ if (dy < -6) schedule('upscroll');
1653
+ }
1654
+
1655
+ function init() {
1656
+ // Initial repair does nothing unless pile-up already present.
1657
+ schedule('init');
1658
+
1659
+ window.addEventListener('scroll', onScroll, { passive: true });
1660
+
1661
+ // MutationObserver on list (guarded; redistribution only runs on upscroll)
1662
+ try {
1663
+ if (typeof MutationObserver !== 'undefined') {
1664
+ var ul = getTopicList();
1665
+ if (ul) {
1666
+ var mo = new MutationObserver(function(){ /* no immediate redistribution */ });
1667
+ mo.observe(ul, { childList:true, subtree:true });
1668
+ }
1669
+ }
1670
+ } catch (e) {}
1671
+
1672
+ if (window.jQuery) {
1673
+ try {
1674
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1675
+ // don't touch during downscroll load; just re-evaluate when user scrolls up
1676
+ setTimeout(function(){ schedule('event'); }, 0);
1677
+ });
1678
+ } catch(e){}
1679
+ }
1680
+ }
1681
+
1682
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1683
+ else init();
1684
+ })();
1685
+ // ===== /V17.1 =====
1621
1686
 
package/public/style.css CHANGED
@@ -81,8 +81,8 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V16.1 host li ===== */
85
- li.nodebb-ezoic-host { list-style: none; width: 100%; display:block; }
86
- li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; display:block; }
87
- /* ===== /V16.1 ===== */
84
+ /* ===== V17.1 host styling ===== */
85
+ li.nodebb-ezoic-host { list-style:none; width:100%; display:block; }
86
+ li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width:100%; display:block; }
87
+ /* ===== /V17.1 ===== */
88
88