nodebb-plugin-ezoic-infinite 1.6.11 → 1.6.13
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 +1 -1
- package/public/client.js +216 -25
- package/public/style.css +2 -3
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,34 +1,127 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
function
|
|
5
|
-
// For <ul>/<ol> we MUST insert a <li> to keep valid DOM.
|
|
6
|
-
// But for compatibility with existing selectors (often 'div.nodebb-ezoic-wrap'),
|
|
7
|
-
// we keep the actual wrap as a DIV inside the LI.
|
|
8
|
-
var host = null;
|
|
4
|
+
function ezoicAnchorKeyFromEl(el) {
|
|
9
5
|
try {
|
|
6
|
+
if (!el || !el.getAttribute) return '';
|
|
7
|
+
var tid = el.getAttribute('data-tid') || el.getAttribute('data-topic-id');
|
|
8
|
+
if (tid) return 'tid:' + String(tid);
|
|
9
|
+
var pid = el.getAttribute('data-pid') || el.getAttribute('data-id');
|
|
10
|
+
if (pid) return 'pid:' + String(pid);
|
|
11
|
+
var uid = el.getAttribute('data-uid');
|
|
12
|
+
if (uid) return 'uid:' + String(uid);
|
|
13
|
+
} catch (e) {}
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function ezoicFindAnchorEl(anchorKey, scope) {
|
|
18
|
+
try {
|
|
19
|
+
if (!anchorKey) return null;
|
|
20
|
+
var root = scope || document;
|
|
21
|
+
if (anchorKey.indexOf('tid:') === 0) {
|
|
22
|
+
var v = anchorKey.slice(4);
|
|
23
|
+
return root.querySelector('[data-tid="' + v + '"], [data-topic-id="' + v + '"]');
|
|
24
|
+
}
|
|
25
|
+
if (anchorKey.indexOf('pid:') === 0) {
|
|
26
|
+
var p = anchorKey.slice(4);
|
|
27
|
+
return root.querySelector('[data-pid="' + p + '"], [data-id="' + p + '"]');
|
|
28
|
+
}
|
|
29
|
+
if (anchorKey.indexOf('uid:') === 0) {
|
|
30
|
+
var u = anchorKey.slice(4);
|
|
31
|
+
return root.querySelector('[data-uid="' + u + '"]');
|
|
32
|
+
}
|
|
33
|
+
} catch (e) {}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ezoicReconcileHosts(scope) {
|
|
38
|
+
// Ensure each li.nodebb-ezoic-host stays directly after its anchor <li>.
|
|
39
|
+
// Prevents "all hosts under first topic" after NodeBB re-render/virtualize.
|
|
40
|
+
try {
|
|
41
|
+
var root = scope || document;
|
|
42
|
+
var hosts = root.querySelectorAll('li.nodebb-ezoic-host[data-ezoic-anchor]');
|
|
43
|
+
if (!hosts || !hosts.length) return;
|
|
44
|
+
|
|
45
|
+
hosts.forEach(function (host) {
|
|
46
|
+
try {
|
|
47
|
+
var anchorKey = host.getAttribute('data-ezoic-anchor') || '';
|
|
48
|
+
if (!anchorKey) return;
|
|
49
|
+
var ul = host.parentElement;
|
|
50
|
+
if (!ul) return;
|
|
51
|
+
|
|
52
|
+
var anchor = ezoicFindAnchorEl(anchorKey, ul) || ezoicFindAnchorEl(anchorKey, document);
|
|
53
|
+
if (!anchor) return;
|
|
54
|
+
|
|
55
|
+
// Anchor is often inside the <li> topic; normalize to list item
|
|
56
|
+
var anchorLi = anchor.closest ? (anchor.closest('li') || anchor) : anchor;
|
|
57
|
+
if (!anchorLi || !anchorLi.parentElement) return;
|
|
58
|
+
|
|
59
|
+
if (host.previousElementSibling !== anchorLi) {
|
|
60
|
+
anchorLi.insertAdjacentElement('afterend', host);
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {}
|
|
63
|
+
});
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
' + '';
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
function ezoicInsertAfterWithUlGuard(target, wrap, kindClass) {
|
|
71
|
+
try {
|
|
72
|
+
if (!target || !wrap) return;
|
|
73
|
+
// Only guard for BETWEEN placements (known to be inserted between <li> items inside <ul>/<ol>)
|
|
10
74
|
if (kindClass === 'ezoic-ad-between') {
|
|
11
|
-
var p =
|
|
75
|
+
var p = target.parentElement;
|
|
12
76
|
if (p && (p.tagName === 'UL' || p.tagName === 'OL')) {
|
|
13
|
-
|
|
77
|
+
// Ensure wrap is not a direct child of UL/OL (invalid HTML); wrap it in an LI host.
|
|
78
|
+
var host = document.createElement('li');
|
|
14
79
|
host.className = 'nodebb-ezoic-host';
|
|
15
80
|
host.setAttribute('role', 'listitem');
|
|
16
81
|
host.style.listStyle = 'none';
|
|
17
82
|
host.style.width = '100%';
|
|
83
|
+
try { var ak = ezoicAnchorKeyFromEl(target); if (ak) host.setAttribute('data-ezoic-anchor', ak); } catch(e) {}
|
|
84
|
+
|
|
85
|
+
// Insert host after the target listitem, then move the wrap inside.
|
|
86
|
+
if (target.insertAdjacentElement) {
|
|
87
|
+
target.insertAdjacentElement('afterend', host);
|
|
88
|
+
} else if (p.insertBefore) {
|
|
89
|
+
p.insertBefore(host, target.nextSibling);
|
|
90
|
+
}
|
|
91
|
+
try { host.appendChild(wrap); } catch (e) {}
|
|
92
|
+
|
|
93
|
+
// Keep wrap full width
|
|
94
|
+
try { wrap.style.width = '100%'; } catch (e) {}
|
|
95
|
+
return;
|
|
18
96
|
}
|
|
19
97
|
}
|
|
20
|
-
} catch (e) {}
|
|
21
98
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
99
|
+
// Default behavior
|
|
100
|
+
if (target.insertAdjacentElement) ezoicInsertAfterWithUlGuard(target, wrap, kindClass);
|
|
101
|
+
else if (target.parentNode) target.parentNode.insertBefore(wrap, target.nextSibling);
|
|
102
|
+
} catch (e) {}
|
|
103
|
+
}
|
|
26
104
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
105
|
+
function ezoicRepairInvalidBetweenWraps() {
|
|
106
|
+
// If any legacy DIV wraps were already inserted directly under UL/OL, repair them once.
|
|
107
|
+
try {
|
|
108
|
+
var bad = document.querySelectorAll('ul > div.nodebb-ezoic-wrap.ezoic-ad-between, ol > div.nodebb-ezoic-wrap.ezoic-ad-between');
|
|
109
|
+
bad.forEach(function (wrap) {
|
|
110
|
+
try {
|
|
111
|
+
var p = wrap.parentElement;
|
|
112
|
+
if (!p) return;
|
|
113
|
+
var host = document.createElement('li');
|
|
114
|
+
host.className = 'nodebb-ezoic-host';
|
|
115
|
+
host.setAttribute('role', 'listitem');
|
|
116
|
+
host.style.listStyle = 'none';
|
|
117
|
+
host.style.width = '100%';
|
|
118
|
+
try { var ak = ezoicAnchorKeyFromEl(target); if (ak) host.setAttribute('data-ezoic-anchor', ak); } catch(e) {}
|
|
119
|
+
p.insertBefore(host, wrap);
|
|
120
|
+
host.appendChild(wrap);
|
|
121
|
+
try { wrap.style.width = '100%'; } catch (e) {}
|
|
122
|
+
} catch (e) {}
|
|
123
|
+
});
|
|
124
|
+
} catch (e) {}
|
|
32
125
|
}
|
|
33
126
|
|
|
34
127
|
|
|
@@ -668,9 +761,7 @@ function globalGapFixInit() {
|
|
|
668
761
|
// ---------------- insertion primitives ----------------
|
|
669
762
|
|
|
670
763
|
function buildWrap(id, kindClass, afterPos, createPlaceholder) {
|
|
671
|
-
const
|
|
672
|
-
const host = pair.hostEl;
|
|
673
|
-
const wrap = pair.wrapEl;
|
|
764
|
+
const wrap = document.createElement('div');
|
|
674
765
|
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
675
766
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
676
767
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
@@ -701,7 +792,7 @@ function globalGapFixInit() {
|
|
|
701
792
|
insertingIds.add(id);
|
|
702
793
|
try {
|
|
703
794
|
const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
|
|
704
|
-
target
|
|
795
|
+
ezoicInsertAfterWithUlGuard(target, wrap, kindClass);
|
|
705
796
|
|
|
706
797
|
// If placeholder exists elsewhere (including pool), move it into the wrapper.
|
|
707
798
|
if (existingPh) {
|
|
@@ -1161,12 +1252,48 @@ function buildOrdinalMap(items) {
|
|
|
1161
1252
|
}
|
|
1162
1253
|
|
|
1163
1254
|
function pickRecyclableWrap(kindClass) {
|
|
1164
|
-
|
|
1165
|
-
|
|
1255
|
+
// Only recycle wrappers that are well above the viewport to avoid visible "disappearing".
|
|
1256
|
+
// With very small id pools (e.g. 6 ids), recycling is the only way to keep ads appearing
|
|
1257
|
+
// on long topics without redefining placeholders.
|
|
1258
|
+
const wraps = document.querySelectorAll('.' + kindClass + '[data-ezoic-wrapid]');
|
|
1259
|
+
if (!wraps || !wraps.length) return null;
|
|
1260
|
+
|
|
1261
|
+
const vh = Math.max(300, window.innerHeight || 800);
|
|
1262
|
+
// Recycle only when the wrapper is far above the viewport.
|
|
1263
|
+
// This keeps ads on-screen longer (especially on mobile) and reduces "blink".
|
|
1264
|
+
const threshold = -Math.min(9000, Math.round(vh * 6));
|
|
1265
|
+
|
|
1266
|
+
let best = null;
|
|
1267
|
+
let bestBottom = Infinity;
|
|
1268
|
+
for (const w of wraps) {
|
|
1269
|
+
if (!w || !w.isConnected) continue;
|
|
1270
|
+
if (w.getAttribute && w.getAttribute('data-ezoic-pin') === '1') continue;
|
|
1271
|
+
const rect = w.getBoundingClientRect();
|
|
1272
|
+
if (rect.bottom < threshold) {
|
|
1273
|
+
if (rect.bottom < bestBottom) {
|
|
1274
|
+
bestBottom = rect.bottom;
|
|
1275
|
+
best = w;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return best;
|
|
1280
|
+
}
|
|
1166
1281
|
|
|
1167
1282
|
function moveWrapAfter(anchorEl, wrap, kindClass, afterPos) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1283
|
+
try {
|
|
1284
|
+
if (!anchorEl || !wrap || !wrap.isConnected) return null;
|
|
1285
|
+
|
|
1286
|
+
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
1287
|
+
ezoicInsertAfterWithUlGuard(anchorEl, wrap, kindClass);
|
|
1288
|
+
|
|
1289
|
+
// Ensure minimal layout impact.
|
|
1290
|
+
try { wrap.style.contain = 'layout style paint'; } catch (e) {}
|
|
1291
|
+
try { tightenStickyIn(wrap); } catch (e) {}
|
|
1292
|
+
return wrap;
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1170
1297
|
|
|
1171
1298
|
async function runCore() {
|
|
1172
1299
|
if (isBlocked()) return 0;
|
|
@@ -1505,3 +1632,67 @@ function buildOrdinalMap(items) {
|
|
|
1505
1632
|
insertHeroAdEarly().catch(() => {});
|
|
1506
1633
|
requestBurst();
|
|
1507
1634
|
})();
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
// ===== V12.2: run UL/OL repair =====
|
|
1638
|
+
try { ezoicRepairInvalidBetweenWraps(); } catch (e) {}
|
|
1639
|
+
if (window.jQuery) {
|
|
1640
|
+
try {
|
|
1641
|
+
window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function () {
|
|
1642
|
+
setTimeout(function(){ try { ezoicRepairInvalidBetweenWraps(); } catch(e) {} }, 0);
|
|
1643
|
+
setTimeout(function(){ try { ezoicRepairInvalidBetweenWraps(); } catch(e) {} }, 200);
|
|
1644
|
+
});
|
|
1645
|
+
} catch (e) {}
|
|
1646
|
+
}
|
|
1647
|
+
// ===== /V12.2 =====
|
|
1648
|
+
|
|
1649
|
+
|
|
1650
|
+
// ===== V12.3 reconcile scheduler =====
|
|
1651
|
+
(function(){
|
|
1652
|
+
var pending = false;
|
|
1653
|
+
var last = 0;
|
|
1654
|
+
var COOLDOWN = 180;
|
|
1655
|
+
|
|
1656
|
+
function schedule(scope){
|
|
1657
|
+
var now = Date.now();
|
|
1658
|
+
if (now - last < COOLDOWN) return;
|
|
1659
|
+
if (pending) return;
|
|
1660
|
+
pending = true;
|
|
1661
|
+
requestAnimationFrame(function(){
|
|
1662
|
+
pending = false;
|
|
1663
|
+
last = Date.now();
|
|
1664
|
+
try { ezoicReconcileHosts(scope); } catch(e) {}
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
window.addEventListener('scroll', function(){ schedule(document); }, { passive: true });
|
|
1669
|
+
window.addEventListener('resize', function(){ schedule(document); }, { passive: true });
|
|
1670
|
+
|
|
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); }, 600);
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// MutationObserver lightweight: only watch ULs for child list changes
|
|
1680
|
+
try {
|
|
1681
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
1682
|
+
var mo = new MutationObserver(function(muts){
|
|
1683
|
+
// if any li hosts are moved/added, reconcile
|
|
1684
|
+
for (var i=0;i<muts.length;i++){
|
|
1685
|
+
var m = muts[i];
|
|
1686
|
+
if (m.addedNodes && m.addedNodes.length) { schedule(m.target); break; }
|
|
1687
|
+
if (m.removedNodes && m.removedNodes.length) { schedule(m.target); break; }
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
document.querySelectorAll('ul,ol').forEach(function(list){
|
|
1691
|
+
try { mo.observe(list, {childList:true}); } catch(e) {}
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
} catch(e){}
|
|
1695
|
+
|
|
1696
|
+
setTimeout(function(){ try { schedule(document); } catch(e) {} }, 0);
|
|
1697
|
+
})();
|
|
1698
|
+
// ===== /V12.3 =====
|
package/public/style.css
CHANGED
|
@@ -81,9 +81,8 @@
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
/* ===== V12.
|
|
84
|
+
/* ===== V12.2 UL host for between wraps ===== */
|
|
85
85
|
li.nodebb-ezoic-host { list-style: none; width: 100%; }
|
|
86
|
-
/* keep inner wrap full width */
|
|
87
86
|
li.nodebb-ezoic-host > .nodebb-ezoic-wrap { width: 100%; }
|
|
88
|
-
/* ===== /V12.
|
|
87
|
+
/* ===== /V12.2 ===== */
|
|
89
88
|
|