nodebb-plugin-ezoic-infinite 1.6.15 → 1.6.16

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.15",
3
+ "version": "1.6.16",
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,133 +1,6 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- function ezoicTopicIdFromLi(li) {
5
- try {
6
- if (!li) return '';
7
- // try explicit attrs first
8
- var tid = li.getAttribute && (li.getAttribute('data-tid') || li.getAttribute('data-topic-id'));
9
- if (tid) return String(tid);
10
- // try finding a topic permalink like /topic/13789/...
11
- var a = li.querySelector && li.querySelector('a[href*="/topic/"]');
12
- if (!a) return '';
13
- var href = a.getAttribute('href') || '';
14
- var m = href.match(/\/topic\/(\d+)\b/);
15
- if (m && m[1]) return m[1];
16
- } catch (e) {}
17
- return '';
18
- }
19
-
20
- function ezoicAnchorKeyFromTarget(target) {
21
- try {
22
- if (!target) return '';
23
- var li = target.closest ? target.closest('li') : null;
24
- if (!li) li = target.tagName === 'LI' ? target : null;
25
- if (!li) return '';
26
- var tid = ezoicTopicIdFromLi(li);
27
- if (tid) return 'topic:' + tid;
28
- } catch (e) {}
29
- return '';
30
- }
31
-
32
- function ezoicFindAnchorLiByKey(anchorKey, scope) {
33
- try {
34
- if (!anchorKey) return null;
35
- if (anchorKey.indexOf('topic:') !== 0) return null;
36
- var tid = anchorKey.slice(6);
37
- var root = scope || document;
38
- // locate any link to this topic and climb to li
39
- var link = root.querySelector('a[href^="/topic/' + tid + '"], a[href*="/topic/' + tid + '/"]');
40
- if (!link) return null;
41
- return link.closest ? link.closest('li') : null;
42
- } catch (e) {}
43
- return null;
44
- }
45
-
46
- function ezoicReconcileOrPurgeByTopic(scope) {
47
- try {
48
- var root = scope || document;
49
- var hosts = root.querySelectorAll('li.nodebb-ezoic-host[data-ezoic-anchor]');
50
- if (!hosts || !hosts.length) return;
51
-
52
- hosts.forEach(function(host){
53
- try {
54
- var listEl = host.parentElement;
55
- if (!listEl) return;
56
- var key = host.getAttribute('data-ezoic-anchor') || '';
57
- if (!key) return;
58
-
59
- var anchorLi = ezoicFindAnchorLiByKey(key, listEl) || ezoicFindAnchorLiByKey(key, document);
60
- if (!anchorLi) {
61
- host.remove();
62
- return;
63
- }
64
- if (host.previousElementSibling !== anchorLi) {
65
- anchorLi.insertAdjacentElement('afterend', host);
66
- }
67
- } catch (e) {}
68
- });
69
- } catch (e) {}
70
- }
71
-
72
-
73
- function ezoicInsertAfterWithUlGuard(target, wrap, kindClass) {
74
- try {
75
- if (!target || !wrap) return;
76
- // Only guard for BETWEEN placements (known to be inserted between <li> items inside <ul>/<ol>)
77
- if (kindClass === 'ezoic-ad-between') {
78
- var p = target.parentElement;
79
- if (p && (p.tagName === 'UL' || p.tagName === 'OL')) {
80
- // Ensure wrap is not a direct child of UL/OL (invalid HTML); wrap it in an LI host.
81
- var host = document.createElement('li');
82
- host.className = 'nodebb-ezoic-host';
83
- host.setAttribute('role', 'listitem');
84
- host.style.listStyle = 'none';
85
- host.style.width = '100%';
86
- try { var ak = ezoicAnchorKeyFromTarget(target); if (ak) host.setAttribute('data-ezoic-anchor', ak); } catch(e) {}
87
-
88
- // Insert host after the target listitem, then move the wrap inside.
89
- if (target.insertAdjacentElement) {
90
- target.insertAdjacentElement('afterend', host);
91
- } else if (p.insertBefore) {
92
- p.insertBefore(host, target.nextSibling);
93
- }
94
- try { host.appendChild(wrap); } catch (e) {}
95
-
96
- // Keep wrap full width
97
- try { wrap.style.width = '100%'; } catch (e) {}
98
- return;
99
- }
100
- }
101
-
102
- // Default behavior
103
- if (target.insertAdjacentElement) ezoicInsertAfterWithUlGuard(target, wrap, kindClass);
104
- else if (target.parentNode) target.parentNode.insertBefore(wrap, target.nextSibling);
105
- } catch (e) {}
106
- }
107
-
108
- function ezoicRepairInvalidBetweenWraps() {
109
- // If any legacy DIV wraps were already inserted directly under UL/OL, repair them once.
110
- try {
111
- var bad = document.querySelectorAll('ul > div.nodebb-ezoic-wrap.ezoic-ad-between, ol > div.nodebb-ezoic-wrap.ezoic-ad-between');
112
- bad.forEach(function (wrap) {
113
- try {
114
- var p = wrap.parentElement;
115
- if (!p) return;
116
- var host = document.createElement('li');
117
- host.className = 'nodebb-ezoic-host';
118
- host.setAttribute('role', 'listitem');
119
- host.style.listStyle = 'none';
120
- host.style.width = '100%';
121
- try { var prevLi = wrap.previousElementSibling; var ak2 = ezoicAnchorKeyFromTarget(prevLi || wrap); if (ak2) host.setAttribute('data-ezoic-anchor', ak2); } catch(e) {}
122
- p.insertBefore(host, wrap);
123
- host.appendChild(wrap);
124
- try { wrap.style.width = '100%'; } catch (e) {}
125
- } catch (e) {}
126
- });
127
- } catch (e) {}
128
- }
129
-
130
-
131
4
  // Track scroll direction to avoid aggressive recycling when the user scrolls upward.
132
5
  // Recycling while scrolling up is a common cause of ads "bunching" and a "disappearing too fast" feeling.
133
6
  let lastScrollY = 0;
@@ -795,7 +668,7 @@ function globalGapFixInit() {
795
668
  insertingIds.add(id);
796
669
  try {
797
670
  const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
798
- ezoicInsertAfterWithUlGuard(target, wrap, kindClass);
671
+ target.insertAdjacentElement('afterend', wrap);
799
672
 
800
673
  // If placeholder exists elsewhere (including pool), move it into the wrapper.
801
674
  if (existingPh) {
@@ -1287,7 +1160,7 @@ function buildOrdinalMap(items) {
1287
1160
  if (!anchorEl || !wrap || !wrap.isConnected) return null;
1288
1161
 
1289
1162
  wrap.setAttribute('data-ezoic-after', String(afterPos));
1290
- ezoicInsertAfterWithUlGuard(anchorEl, wrap, kindClass);
1163
+ anchorEl.insertAdjacentElement('afterend', wrap);
1291
1164
 
1292
1165
  // Ensure minimal layout impact.
1293
1166
  try { wrap.style.contain = 'layout style paint'; } catch (e) {}
@@ -1637,63 +1510,104 @@ function buildOrdinalMap(items) {
1637
1510
  })();
1638
1511
 
1639
1512
 
1640
- // ===== V12.2: run UL/OL repair =====
1641
- try { ezoicRepairInvalidBetweenWraps(); } catch (e) {}
1642
- if (window.jQuery) {
1643
- try {
1644
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function () {
1645
- setTimeout(function(){ try { ezoicRepairInvalidBetweenWraps(); } catch(e) {} }, 0);
1646
- setTimeout(function(){ try { ezoicRepairInvalidBetweenWraps(); } catch(e) {} }, 200);
1647
- });
1648
- } catch (e) {}
1649
- }
1650
- // ===== /V12.2 =====
1651
-
1652
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.
1653
1518
 
1654
- // ===== V12.5 topic-anchor reconcile =====
1655
- (function(){
1656
- var pending=false, last=0, COOLDOWN=140;
1657
- function schedule(scope){
1658
- var now=Date.now();
1659
- if (now-last<COOLDOWN) return;
1660
- if (pending) return;
1661
- pending=true;
1662
- requestAnimationFrame(function(){
1663
- pending=false; last=Date.now();
1664
- try { ezoicReconcileOrPurgeByTopic(scope); } catch(e) {}
1665
- });
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) {}
1666
1549
  }
1667
1550
 
1668
- window.addEventListener('scroll', function(){ schedule(document); }, { passive:true });
1669
- window.addEventListener('resize', function(){ schedule(document); }, { passive:true });
1551
+ function install() {
1552
+ // initial pass
1553
+ killStickyIn(document);
1670
1554
 
1671
- if (window.jQuery) {
1672
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1673
- setTimeout(function(){ schedule(document); }, 0);
1674
- setTimeout(function(){ schedule(document); }, 220);
1675
- setTimeout(function(){ schedule(document); }, 700);
1676
- });
1677
- }
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 });
1678
1561
 
1679
- try {
1680
- if (typeof MutationObserver !== 'undefined') {
1681
- var mo = new MutationObserver(function(muts){
1682
- for (var i=0;i<muts.length;i++){
1683
- var m=muts[i];
1684
- if ((m.addedNodes && m.addedNodes.length) || (m.removedNodes && m.removedNodes.length)) {
1685
- schedule(m.target);
1686
- break;
1687
- }
1688
- }
1689
- });
1690
- document.querySelectorAll('ul,ol').forEach(function(list){
1691
- try { mo.observe(list, {childList:true, subtree:false}); } catch(e) {}
1692
- });
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) {}
1596
+
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) {}
1693
1606
  }
1694
- } catch(e){}
1607
+ }
1695
1608
 
1696
- setTimeout(function(){ schedule(document); }, 0);
1609
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', install);
1610
+ else install();
1697
1611
  })();
1698
- // ===== /V12.5 =====
1612
+ // ===== /V13 =====
1699
1613
 
package/public/style.css CHANGED
@@ -81,8 +81,11 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V12.2 UL host for between wraps ===== */
85
- li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- li.nodebb-ezoic-host > .nodebb-ezoic-wrap { width: 100%; }
87
- /* ===== /V12.2 ===== */
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 ===== */
88
91