nodebb-plugin-ezoic-infinite 1.6.15 → 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.15",
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,127 +1,110 @@
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 '';
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()); }
18
7
  }
19
8
 
20
- function ezoicAnchorKeyFromTarget(target) {
9
+ function ezoicInsertAfterWithToken(target, wrap, kindClass) {
21
10
  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
- }
11
+ if (!target || !wrap) return;
71
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
+ }
72
25
 
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;
26
+ // If inside UL/OL, keep valid DOM with LI host, but keep wrap as DIV (compat).
27
+ var p = anchorLi.parentElement;
79
28
  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
29
  var host = document.createElement('li');
82
30
  host.className = 'nodebb-ezoic-host';
83
31
  host.setAttribute('role', 'listitem');
84
32
  host.style.listStyle = 'none';
85
33
  host.style.width = '100%';
86
- try { var ak = ezoicAnchorKeyFromTarget(target); if (ak) host.setAttribute('data-ezoic-anchor', ak); } catch(e) {}
34
+ try { host.setAttribute('data-ezoic-anchor-token', token); } catch (e) {}
87
35
 
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) {}
36
+ if (anchorLi.insertAdjacentElement) anchorLi.insertAdjacentElement('afterend', host);
37
+ else if (p.insertBefore) p.insertBefore(host, anchorLi.nextSibling);
95
38
 
96
- // Keep wrap full width
39
+ try { host.appendChild(wrap); } catch (e) {}
97
40
  try { wrap.style.width = '100%'; } catch (e) {}
98
41
  return;
99
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) {}
100
46
  }
101
47
 
102
- // Default behavior
103
- if (target.insertAdjacentElement) ezoicInsertAfterWithUlGuard(target, wrap, kindClass);
48
+ if (target.insertAdjacentElement) ezoicInsertAfterWithToken(target, wrap, kindClass);
104
49
  else if (target.parentNode) target.parentNode.insertBefore(wrap, target.nextSibling);
105
50
  } catch (e) {}
106
51
  }
107
52
 
108
- function ezoicRepairInvalidBetweenWraps() {
109
- // If any legacy DIV wraps were already inserted directly under UL/OL, repair them once.
53
+ function ezoicRepairBetweenWrapsToHost() {
54
+ // Repair any invalid UL children and ensure hosts/wraps carry token if possible.
110
55
  try {
111
56
  var bad = document.querySelectorAll('ul > div.nodebb-ezoic-wrap.ezoic-ad-between, ol > div.nodebb-ezoic-wrap.ezoic-ad-between');
112
57
  bad.forEach(function (wrap) {
113
58
  try {
114
- var p = wrap.parentElement;
115
- if (!p) return;
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
+
116
68
  var host = document.createElement('li');
117
69
  host.className = 'nodebb-ezoic-host';
118
70
  host.setAttribute('role', 'listitem');
119
71
  host.style.listStyle = 'none';
120
72
  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);
73
+ if (token) host.setAttribute('data-ezoic-anchor-token', token);
74
+
75
+ ul.insertBefore(host, wrap);
123
76
  host.appendChild(wrap);
124
- try { wrap.style.width = '100%'; } catch (e) {}
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
+ }
125
108
  } catch (e) {}
126
109
  });
127
110
  } catch (e) {}
@@ -795,7 +778,7 @@ function globalGapFixInit() {
795
778
  insertingIds.add(id);
796
779
  try {
797
780
  const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
798
- ezoicInsertAfterWithUlGuard(target, wrap, kindClass);
781
+ ezoicInsertAfterWithToken(target, wrap, kindClass);
799
782
 
800
783
  // If placeholder exists elsewhere (including pool), move it into the wrapper.
801
784
  if (existingPh) {
@@ -1287,7 +1270,7 @@ function buildOrdinalMap(items) {
1287
1270
  if (!anchorEl || !wrap || !wrap.isConnected) return null;
1288
1271
 
1289
1272
  wrap.setAttribute('data-ezoic-after', String(afterPos));
1290
- ezoicInsertAfterWithUlGuard(anchorEl, wrap, kindClass);
1273
+ ezoicInsertAfterWithToken(anchorEl, wrap, kindClass);
1291
1274
 
1292
1275
  // Ensure minimal layout impact.
1293
1276
  try { wrap.style.contain = 'layout style paint'; } catch (e) {}
@@ -1637,21 +1620,7 @@ function buildOrdinalMap(items) {
1637
1620
  })();
1638
1621
 
1639
1622
 
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
-
1653
-
1654
- // ===== V12.5 topic-anchor reconcile =====
1623
+ // ===== V14 token reconcile scheduler =====
1655
1624
  (function(){
1656
1625
  var pending=false, last=0, COOLDOWN=140;
1657
1626
  function schedule(scope){
@@ -1661,7 +1630,8 @@ if (window.jQuery) {
1661
1630
  pending=true;
1662
1631
  requestAnimationFrame(function(){
1663
1632
  pending=false; last=Date.now();
1664
- try { ezoicReconcileOrPurgeByTopic(scope); } catch(e) {}
1633
+ try { ezoicRepairBetweenWrapsToHost(); } catch(e) {}
1634
+ try { ezoicReconcileOrDropHostsByToken(scope); } catch(e) {}
1665
1635
  });
1666
1636
  }
1667
1637
 
@@ -1672,7 +1642,7 @@ if (window.jQuery) {
1672
1642
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1673
1643
  setTimeout(function(){ schedule(document); }, 0);
1674
1644
  setTimeout(function(){ schedule(document); }, 220);
1675
- setTimeout(function(){ schedule(document); }, 700);
1645
+ setTimeout(function(){ schedule(document); }, 800);
1676
1646
  });
1677
1647
  }
1678
1648
 
@@ -1688,12 +1658,11 @@ if (window.jQuery) {
1688
1658
  }
1689
1659
  });
1690
1660
  document.querySelectorAll('ul,ol').forEach(function(list){
1691
- try { mo.observe(list, {childList:true, subtree:false}); } catch(e) {}
1661
+ try { mo.observe(list, {childList:true}); } catch(e) {}
1692
1662
  });
1693
1663
  }
1694
1664
  } catch(e){}
1695
1665
 
1696
1666
  setTimeout(function(){ schedule(document); }, 0);
1697
1667
  })();
1698
- // ===== /V12.5 =====
1699
-
1668
+ // ===== /V14 =====
package/public/style.css CHANGED
@@ -81,8 +81,7 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V12.2 UL host for between wraps ===== */
84
+ /* ===== V14 token host styles ===== */
85
85
  li.nodebb-ezoic-host { list-style: none; width: 100%; }
86
- li.nodebb-ezoic-host > .nodebb-ezoic-wrap { width: 100%; }
87
- /* ===== /V12.2 ===== */
86
+ /* ===== /V14 ===== */
88
87