nodebb-plugin-ezoic-infinite 1.6.16 → 1.6.17

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.16",
3
+ "version": "1.6.17",
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,116 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
+ function ezoicAnchorToken() {
5
+ // cheap unique-ish token
6
+ try { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } catch (e) { return String(Date.now()); }
7
+ }
8
+
9
+ function ezoicInsertAfterWithToken(target, wrap, kindClass) {
10
+ try {
11
+ if (!target || !wrap) return;
12
+
13
+ // Tag the anchor LI (topic item) with a stable token the first time we attach a between ad to it.
14
+ // If NodeBB re-renders/virtualizes and recreates the LI, this tag disappears => we can drop orphan ads.
15
+ var anchorLi = null;
16
+ try { anchorLi = target.closest ? target.closest('li') : null; } catch (e) {}
17
+ if (!anchorLi && target.tagName === 'LI') anchorLi = target;
18
+
19
+ if (kindClass === 'ezoic-ad-between' && anchorLi) {
20
+ var token = anchorLi.getAttribute('data-ezoic-anchor-token');
21
+ if (!token) {
22
+ token = ezoicAnchorToken();
23
+ try { anchorLi.setAttribute('data-ezoic-anchor-token', token); } catch (e) {}
24
+ }
25
+
26
+ // If inside UL/OL, keep valid DOM with LI host, but keep wrap as DIV (compat).
27
+ var p = anchorLi.parentElement;
28
+ if (p && (p.tagName === 'UL' || p.tagName === 'OL')) {
29
+ var host = document.createElement('li');
30
+ host.className = 'nodebb-ezoic-host';
31
+ host.setAttribute('role', 'listitem');
32
+ host.style.listStyle = 'none';
33
+ host.style.width = '100%';
34
+ try { host.setAttribute('data-ezoic-anchor-token', token); } catch (e) {}
35
+
36
+ if (anchorLi.insertAdjacentElement) anchorLi.insertAdjacentElement('afterend', host);
37
+ else if (p.insertBefore) p.insertBefore(host, anchorLi.nextSibling);
38
+
39
+ try { host.appendChild(wrap); } catch (e) {}
40
+ try { wrap.style.width = '100%'; } catch (e) {}
41
+ return;
42
+ }
43
+
44
+ // Non-list parent: just insert sibling but still tag wrap so we can reconcile
45
+ try { wrap.setAttribute('data-ezoic-anchor-token', token); } catch (e) {}
46
+ }
47
+
48
+ if (target.insertAdjacentElement) ezoicInsertAfterWithToken(target, wrap, kindClass);
49
+ else if (target.parentNode) target.parentNode.insertBefore(wrap, target.nextSibling);
50
+ } catch (e) {}
51
+ }
52
+
53
+ function ezoicRepairBetweenWrapsToHost() {
54
+ // Repair any invalid UL children and ensure hosts/wraps carry token if possible.
55
+ try {
56
+ var bad = document.querySelectorAll('ul > div.nodebb-ezoic-wrap.ezoic-ad-between, ol > div.nodebb-ezoic-wrap.ezoic-ad-between');
57
+ bad.forEach(function (wrap) {
58
+ try {
59
+ var ul = wrap.parentElement;
60
+ if (!ul) return;
61
+ var prevLi = wrap.previousElementSibling && wrap.previousElementSibling.tagName === 'LI' ? wrap.previousElementSibling : null;
62
+ var token = prevLi ? prevLi.getAttribute('data-ezoic-anchor-token') : null;
63
+ if (!token && prevLi) {
64
+ token = ezoicAnchorToken();
65
+ prevLi.setAttribute('data-ezoic-anchor-token', token);
66
+ }
67
+
68
+ var host = document.createElement('li');
69
+ host.className = 'nodebb-ezoic-host';
70
+ host.setAttribute('role', 'listitem');
71
+ host.style.listStyle = 'none';
72
+ host.style.width = '100%';
73
+ if (token) host.setAttribute('data-ezoic-anchor-token', token);
74
+
75
+ ul.insertBefore(host, wrap);
76
+ host.appendChild(wrap);
77
+ wrap.style.width = '100%';
78
+ } catch (e) {}
79
+ });
80
+ } catch (e) {}
81
+ }
82
+
83
+ function ezoicReconcileOrDropHostsByToken(scope) {
84
+ // Core fix: if NodeBB virtualizes and anchors disappear, DROP hosts so they can't pile up at top.
85
+ // If anchor exists, ensure host sits right after it.
86
+ try {
87
+ var root = scope || document;
88
+ var hosts = root.querySelectorAll('li.nodebb-ezoic-host[data-ezoic-anchor-token]');
89
+ if (!hosts || !hosts.length) return;
90
+
91
+ hosts.forEach(function(host){
92
+ try {
93
+ var token = host.getAttribute('data-ezoic-anchor-token');
94
+ if (!token) return;
95
+ var listEl = host.parentElement;
96
+ if (!listEl) return;
97
+
98
+ // Find the exact anchor LI that still carries the token.
99
+ var anchor = listEl.querySelector('li[data-ezoic-anchor-token="' + token + '"]');
100
+ if (!anchor) {
101
+ // anchor not in DOM => virtualized/re-rendered: remove host to avoid stacking
102
+ host.remove();
103
+ return;
104
+ }
105
+ if (host.previousElementSibling !== anchor) {
106
+ anchor.insertAdjacentElement('afterend', host);
107
+ }
108
+ } catch (e) {}
109
+ });
110
+ } catch (e) {}
111
+ }
112
+
113
+
4
114
  // Track scroll direction to avoid aggressive recycling when the user scrolls upward.
5
115
  // Recycling while scrolling up is a common cause of ads "bunching" and a "disappearing too fast" feeling.
6
116
  let lastScrollY = 0;
@@ -668,7 +778,7 @@ function globalGapFixInit() {
668
778
  insertingIds.add(id);
669
779
  try {
670
780
  const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
671
- target.insertAdjacentElement('afterend', wrap);
781
+ ezoicInsertAfterWithToken(target, wrap, kindClass);
672
782
 
673
783
  // If placeholder exists elsewhere (including pool), move it into the wrapper.
674
784
  if (existingPh) {
@@ -1160,7 +1270,7 @@ function buildOrdinalMap(items) {
1160
1270
  if (!anchorEl || !wrap || !wrap.isConnected) return null;
1161
1271
 
1162
1272
  wrap.setAttribute('data-ezoic-after', String(afterPos));
1163
- anchorEl.insertAdjacentElement('afterend', wrap);
1273
+ ezoicInsertAfterWithToken(anchorEl, wrap, kindClass);
1164
1274
 
1165
1275
  // Ensure minimal layout impact.
1166
1276
  try { wrap.style.contain = 'layout style paint'; } catch (e) {}
@@ -1510,104 +1620,49 @@ function buildOrdinalMap(items) {
1510
1620
  })();
1511
1621
 
1512
1622
 
1513
-
1514
- // ===== V13 sticky-killer for Ezoic/AMP in list ads =====
1515
- (function () {
1516
- // Hypothesis: the "pile at top" is a visual effect from multiple sticky intradiv ads
1517
- // (ezads-sticky-intradiv with inline !important). We neutralize sticky continuously.
1518
-
1519
- function killStickyIn(root) {
1520
- try {
1521
- var scope = root || document;
1522
- var nodes = scope.querySelectorAll('.nodebb-ezoic-wrap .ezads-sticky-intradiv, .nodebb-ezoic-wrap [style*="position: sticky"], .nodebb-ezoic-wrap [style*="position:sticky"]');
1523
- nodes.forEach(function(n){
1524
- try {
1525
- n.style.setProperty('position', 'static', 'important');
1526
- n.style.setProperty('top', 'auto', 'important');
1527
- n.style.setProperty('bottom', 'auto', 'important');
1528
- n.style.setProperty('left', 'auto', 'important');
1529
- n.style.setProperty('right', 'auto', 'important');
1530
- // if they force translate/transform for sticky, remove it
1531
- n.style.setProperty('transform', 'none', 'important');
1532
- n.style.setProperty('will-change', 'auto', 'important');
1533
- // also remove class to reduce re-application
1534
- if (n.classList) n.classList.remove('ezads-sticky-intradiv');
1535
- } catch (e) {}
1536
- // If inline style string contains "sticky", scrub it (some libs reparse cssText)
1537
- try {
1538
- var st = n.getAttribute && n.getAttribute('style');
1539
- if (st && (st.indexOf('sticky') !== -1)) {
1540
- var cleaned = st.replace(/position\s*:\s*sticky\s*!important\s*;?/gi, 'position: static !important;')
1541
- .replace(/position\s*:\s*sticky\s*;?/gi, 'position: static;')
1542
- .replace(/top\s*:\s*0px\s*!important\s*;?/gi, 'top: auto !important;')
1543
- .replace(/top\s*:\s*0px\s*;?/gi, 'top: auto;');
1544
- n.setAttribute('style', cleaned);
1545
- }
1546
- } catch (e) {}
1547
- });
1548
- } catch (e) {}
1623
+ // ===== V14 token reconcile scheduler =====
1624
+ (function(){
1625
+ var pending=false, last=0, COOLDOWN=140;
1626
+ function schedule(scope){
1627
+ var now=Date.now();
1628
+ if (now-last<COOLDOWN) return;
1629
+ if (pending) return;
1630
+ pending=true;
1631
+ requestAnimationFrame(function(){
1632
+ pending=false; last=Date.now();
1633
+ try { ezoicRepairBetweenWrapsToHost(); } catch(e) {}
1634
+ try { ezoicReconcileOrDropHostsByToken(scope); } catch(e) {}
1635
+ });
1549
1636
  }
1550
1637
 
1551
- function install() {
1552
- // initial pass
1553
- killStickyIn(document);
1554
-
1555
- // Throttled scroll pass (in case scripts reapply on scroll)
1556
- var t = null;
1557
- window.addEventListener('scroll', function(){
1558
- if (t) return;
1559
- t = setTimeout(function(){ t = null; killStickyIn(document); }, 120);
1560
- }, { passive: true });
1638
+ window.addEventListener('scroll', function(){ schedule(document); }, { passive:true });
1639
+ window.addEventListener('resize', function(){ schedule(document); }, { passive:true });
1561
1640
 
1562
- // MutationObserver to catch late injections / style changes
1563
- try {
1564
- if (typeof MutationObserver !== 'undefined') {
1565
- var mo = new MutationObserver(function(muts){
1566
- try {
1567
- for (var i=0;i<muts.length;i++){
1568
- var m = muts[i];
1569
- if (m.type === 'attributes') {
1570
- // style/class changed
1571
- if (m.attributeName === 'style' || m.attributeName === 'class') {
1572
- killStickyIn(m.target && m.target.closest ? (m.target.closest('.nodebb-ezoic-wrap') || document) : document);
1573
- }
1574
- } else if (m.addedNodes && m.addedNodes.length) {
1575
- for (var j=0;j<m.addedNodes.length;j++){
1576
- var n = m.addedNodes[j];
1577
- if (!n || n.nodeType !== 1) continue;
1578
- if (n.matches && (n.matches('.nodebb-ezoic-wrap') || n.matches('.ezads-sticky-intradiv'))) {
1579
- killStickyIn(n);
1580
- } else if (n.querySelector) {
1581
- if (n.querySelector('.nodebb-ezoic-wrap, .ezads-sticky-intradiv')) killStickyIn(n);
1582
- }
1583
- }
1584
- }
1585
- }
1586
- } catch (e) {}
1587
- });
1588
- mo.observe(document.documentElement || document.body, {
1589
- subtree: true,
1590
- childList: true,
1591
- attributes: true,
1592
- attributeFilter: ['style','class']
1593
- });
1594
- }
1595
- } catch (e) {}
1641
+ if (window.jQuery) {
1642
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1643
+ setTimeout(function(){ schedule(document); }, 0);
1644
+ setTimeout(function(){ schedule(document); }, 220);
1645
+ setTimeout(function(){ schedule(document); }, 800);
1646
+ });
1647
+ }
1596
1648
 
1597
- // NodeBB events
1598
- if (window.jQuery) {
1599
- try {
1600
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1601
- setTimeout(function(){ killStickyIn(document); }, 0);
1602
- setTimeout(function(){ killStickyIn(document); }, 250);
1603
- setTimeout(function(){ killStickyIn(document); }, 900);
1604
- });
1605
- } catch (e) {}
1649
+ try {
1650
+ if (typeof MutationObserver !== 'undefined') {
1651
+ var mo = new MutationObserver(function(muts){
1652
+ for (var i=0;i<muts.length;i++){
1653
+ var m=muts[i];
1654
+ if ((m.addedNodes && m.addedNodes.length) || (m.removedNodes && m.removedNodes.length)) {
1655
+ schedule(m.target);
1656
+ break;
1657
+ }
1658
+ }
1659
+ });
1660
+ document.querySelectorAll('ul,ol').forEach(function(list){
1661
+ try { mo.observe(list, {childList:true}); } catch(e) {}
1662
+ });
1606
1663
  }
1607
- }
1664
+ } catch(e){}
1608
1665
 
1609
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', install);
1610
- else install();
1666
+ setTimeout(function(){ schedule(document); }, 0);
1611
1667
  })();
1612
- // ===== /V13 =====
1613
-
1668
+ // ===== /V14 =====
package/public/style.css CHANGED
@@ -81,11 +81,7 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V13 sticky-killer CSS fallback ===== */
85
- .nodebb-ezoic-wrap .ezads-sticky-intradiv {
86
- position: static !important;
87
- top: auto !important;
88
- transform: none !important;
89
- }
90
- /* ===== /V13 ===== */
84
+ /* ===== V14 token host styles ===== */
85
+ li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
+ /* ===== /V14 ===== */
91
87