nodebb-plugin-ezoic-infinite 1.6.27 → 1.6.28

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.28",
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,184 @@ 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 minimal pile-fix (no insert hooks) =====
1515
+ (function () {
1516
+ // Goal: keep ad injection intact. Only repair when we detect "pile-up" of between wraps.
1517
+ var TOPIC_LI_SEL = 'li[component="category/topic"]';
1518
+ var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1519
+ var HOST_CLASS = 'nodebb-ezoic-host';
1520
+
1521
+ var scheduled = false;
1522
+ var lastRun = 0;
1523
+ var COOLDOWN = 180;
1524
+
1525
+ function getTopicList() {
1526
+ try {
1527
+ var li = document.querySelector(TOPIC_LI_SEL);
1528
+ if (!li) return null;
1529
+ return li.closest ? li.closest('ul,ol') : null;
1530
+ } catch (e) { return null; }
1531
+ }
1532
+
1533
+ function isHost(node) {
1534
+ return !!(node && node.nodeType === 1 && node.tagName === 'LI' && node.classList && node.classList.contains(HOST_CLASS));
1535
+ }
1536
+
1537
+ function ensureHostForWrap(wrap, ul) {
1538
+ try {
1539
+ if (!wrap || wrap.nodeType !== 1) return null;
1540
+ if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1541
+
1542
+ var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1543
+ if (host) return host;
1544
+
1545
+ if (!ul) ul = wrap.closest ? wrap.closest('ul,ol') : null;
1546
+ if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return null;
1547
+
1548
+ // Only wrap if direct child of list (invalid / fragile)
1549
+ if (wrap.parentElement === ul) {
1550
+ host = document.createElement('li');
1551
+ host.className = HOST_CLASS;
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 previousTopicLi(node) {
1565
+ try {
1566
+ var prev = node.previousElementSibling;
1567
+ while (prev) {
1568
+ if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1569
+ // skip other hosts/wraps
1570
+ prev = prev.previousElementSibling;
1571
+ }
1572
+ } catch (e) {}
1573
+ return null;
1574
+ }
1575
+
1576
+ function detectPileUp(ul) {
1577
+ // Pile-up signature: 2+ between wraps/hosts adjacent with no topics between, often near top.
1578
+ try {
1579
+ var kids = ul.children;
1580
+ var run = 0;
1581
+ var maxRun = 0;
1582
+ for (var i = 0; i < kids.length; i++) {
1583
+ var el = kids[i];
1584
+ var isBetween = false;
1585
+ if (isHost(el)) {
1586
+ isBetween = !!(el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1587
+ } else if (el.matches && el.matches(BETWEEN_WRAP_SEL)) {
1588
+ isBetween = true;
1589
+ }
1590
+ if (isBetween) {
1591
+ run++;
1592
+ if (run > maxRun) maxRun = run;
1593
+ } else if (el.matches && el.matches(TOPIC_LI_SEL)) {
1594
+ run = 0;
1595
+ } else {
1596
+ // other nodes reset lightly
1597
+ run = 0;
1598
+ }
1599
+ }
1600
+ return maxRun >= 2;
1601
+ } catch (e) {}
1602
+ return false;
1603
+ }
1604
+
1605
+ function redistribute(ul) {
1606
+ try {
1607
+ if (!ul) return;
1608
+
1609
+ // Step 1: wrap any direct child between DIVs into LI hosts (makes list stable)
1610
+ ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1611
+
1612
+ // Step 2: only act if we see pile-up (avoid touching infinite scroll during normal flow)
1613
+ if (!detectPileUp(ul)) return;
1614
+
1615
+ // Move each host to immediately after the closest previous topic LI at its current position.
1616
+ var hosts = ul.querySelectorAll(':scope > li.' + HOST_CLASS);
1617
+ hosts.forEach(function(host){
1618
+ try {
1619
+ var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1620
+ if (!wrap) return;
1621
+
1622
+ var anchor = previousTopicLi(host);
1623
+ if (!anchor) return; // if none, don't move (prevents yanking to top/bottom)
1624
+
1625
+ if (host.previousElementSibling !== anchor) {
1626
+ anchor.insertAdjacentElement('afterend', host);
1627
+ }
1628
+ } catch (e) {}
1629
+ });
1630
+ } catch (e) {}
1631
+ }
1632
+
1633
+ function schedule(reason) {
1634
+ var now = Date.now();
1635
+ if (now - lastRun < COOLDOWN) return;
1636
+ if (scheduled) return;
1637
+ scheduled = true;
1638
+ requestAnimationFrame(function () {
1639
+ scheduled = false;
1640
+ lastRun = Date.now();
1641
+ try {
1642
+ var ul = getTopicList();
1643
+ if (!ul) return;
1644
+ redistribute(ul);
1645
+ } catch (e) {}
1617
1646
  });
1618
- } catch(e) {}
1619
- }
1620
- // ===== /V16.1 =====
1647
+ }
1648
+
1649
+ function init() {
1650
+ schedule('init');
1651
+
1652
+ // Observe only the topic list once available
1653
+ try {
1654
+ if (typeof MutationObserver !== 'undefined') {
1655
+ var observeList = function(ul){
1656
+ if (!ul) return;
1657
+ var mo = new MutationObserver(function(muts){
1658
+ // schedule on any change; redistribute() itself is guarded by pile-up detection
1659
+ schedule('mo');
1660
+ });
1661
+ mo.observe(ul, { childList: true, subtree: true });
1662
+ };
1663
+
1664
+ var ul = getTopicList();
1665
+ if (ul) observeList(ul);
1666
+ else {
1667
+ var mo2 = new MutationObserver(function(){
1668
+ var u2 = getTopicList();
1669
+ if (u2) {
1670
+ try { observeList(u2); } catch(e){}
1671
+ try { mo2.disconnect(); } catch(e){}
1672
+ }
1673
+ });
1674
+ mo2.observe(document.documentElement || document.body, { childList: true, subtree: true });
1675
+ }
1676
+ }
1677
+ } catch (e) {}
1678
+
1679
+ // NodeBB events: run after infinite scroll batches
1680
+ if (window.jQuery) {
1681
+ try {
1682
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1683
+ setTimeout(function(){ schedule('event'); }, 50);
1684
+ setTimeout(function(){ schedule('event2'); }, 400);
1685
+ });
1686
+ } catch (e) {}
1687
+ }
1688
+ }
1689
+
1690
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1691
+ else init();
1692
+ })();
1693
+ // ===== /V17 =====
1621
1694
 
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 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 ===== */
88
88