nodebb-plugin-ezoic-infinite 1.8.20 → 1.8.21
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 +135 -3
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
const MAX_DESTROY_BATCH = 4; // ids max par destroyPlaceholders(...ids)
|
|
23
23
|
const DESTROY_FLUSH_MS = 30; // micro-buffer destroy pour lisser les rafales
|
|
24
24
|
const BURST_COOLDOWN_MS = 100; // délai min entre deux déclenchements de burst
|
|
25
|
+
const CLEANUP_GRACE_MS = 3_500; // délai mini avant cleanup d'un wrap candidat
|
|
26
|
+
const SPECIAL_GRACE_MS = 30_000; // délai allongé pour sticky/fixed/adhesion
|
|
27
|
+
const RECENT_WRAP_ACTIVITY_MS = 5_000; // protège un wrap récemment muté/rafraîchi
|
|
28
|
+
const VIEWPORT_BUFFER_DESKTOP = 500;
|
|
29
|
+
const VIEWPORT_BUFFER_MOBILE = 250;
|
|
25
30
|
|
|
26
31
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
27
32
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
@@ -76,6 +81,8 @@
|
|
|
76
81
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
77
82
|
ezActiveIds: new Set(), // ids actifs côté plugin (wrap présent / récemment show)
|
|
78
83
|
ezShownSinceDestroy: new Set(), // ids déjà show depuis le dernier destroy Ezoic
|
|
84
|
+
wrapActivityAt: new Map(), // key/id -> ts activité DOM récente
|
|
85
|
+
lastWrapHeightByClass: new Map(),
|
|
79
86
|
scrollDir: 1, // 1=bas, -1=haut
|
|
80
87
|
scrollSpeed: 0, // px/s approx (EMA)
|
|
81
88
|
lastScrollY: 0,
|
|
@@ -95,6 +102,102 @@
|
|
|
95
102
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
96
103
|
const isFilled = n => !!(n?.querySelector?.(FILL_SEL));
|
|
97
104
|
|
|
105
|
+
function viewportBufferPx() {
|
|
106
|
+
return isMobile() ? VIEWPORT_BUFFER_MOBILE : VIEWPORT_BUFFER_DESKTOP;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function textSig(el) {
|
|
110
|
+
if (!(el instanceof Element)) return '';
|
|
111
|
+
const parts = [el.id || '', String(el.className || ''), el.getAttribute?.('name') || ''];
|
|
112
|
+
try {
|
|
113
|
+
for (const a of ['data-google-query-id', 'data-google-container-id', 'data-slot', 'data-ad-slot']) {
|
|
114
|
+
const v = el.getAttribute?.(a);
|
|
115
|
+
if (v) parts.push(v);
|
|
116
|
+
}
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
return parts.join(' ').toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isSpecialSlotLike(el) {
|
|
122
|
+
const sig = textSig(el);
|
|
123
|
+
return /adhesion|interstitial|anchor|sticky|outofpage/.test(sig);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function hasSpecialSlotMarkers(root) {
|
|
127
|
+
if (!(root instanceof Element)) return false;
|
|
128
|
+
if (isSpecialSlotLike(root)) return true;
|
|
129
|
+
try {
|
|
130
|
+
const nodes = root.querySelectorAll('[id],[class],[name],[data-slot],[data-ad-slot]');
|
|
131
|
+
for (const n of nodes) if (isSpecialSlotLike(n)) return true;
|
|
132
|
+
} catch (_) {}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function hasFixedLikeNode(root, maxScan = 24) {
|
|
137
|
+
if (!(root instanceof Element)) return false;
|
|
138
|
+
const q = [root];
|
|
139
|
+
let seen = 0;
|
|
140
|
+
while (q.length && seen < maxScan) {
|
|
141
|
+
const n = q.shift();
|
|
142
|
+
seen++;
|
|
143
|
+
try {
|
|
144
|
+
const cs = window.getComputedStyle(n);
|
|
145
|
+
if (cs.position === 'fixed' || cs.position === 'sticky') return true;
|
|
146
|
+
} catch (_) {}
|
|
147
|
+
for (const c of n.children || []) q.push(c);
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function markWrapActivity(wrapOrId) {
|
|
153
|
+
const t = ts();
|
|
154
|
+
try {
|
|
155
|
+
if (wrapOrId instanceof Element) {
|
|
156
|
+
const key = wrapOrId.getAttribute(A_ANCHOR);
|
|
157
|
+
const id = parseInt(wrapOrId.getAttribute(A_WRAPID), 10);
|
|
158
|
+
if (key) S.wrapActivityAt.set(`k:${key}`, t);
|
|
159
|
+
if (Number.isFinite(id)) S.wrapActivityAt.set(`i:${id}`, t);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const id = parseInt(wrapOrId, 10);
|
|
163
|
+
if (Number.isFinite(id)) S.wrapActivityAt.set(`i:${id}`, t);
|
|
164
|
+
} catch (_) {}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function wrapRecentActivity(w) {
|
|
168
|
+
try {
|
|
169
|
+
const key = w?.getAttribute?.(A_ANCHOR);
|
|
170
|
+
const id = parseInt(w?.getAttribute?.(A_WRAPID), 10);
|
|
171
|
+
const t1 = key ? (S.wrapActivityAt.get(`k:${key}`) || 0) : 0;
|
|
172
|
+
const t2 = Number.isFinite(id) ? (S.wrapActivityAt.get(`i:${id}`) || 0) : 0;
|
|
173
|
+
const t3 = parseInt(w?.getAttribute?.(A_SHOWN) || '0', 10) || 0;
|
|
174
|
+
return (ts() - Math.max(t1, t2, t3)) < RECENT_WRAP_ACTIVITY_MS;
|
|
175
|
+
} catch (_) { return false; }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function wrapCleanupGraceMs(w) {
|
|
179
|
+
return (hasSpecialSlotMarkers(w) || hasFixedLikeNode(w)) ? SPECIAL_GRACE_MS : CLEANUP_GRACE_MS;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function wrapNearViewport(w) {
|
|
183
|
+
try {
|
|
184
|
+
const r = w.getBoundingClientRect();
|
|
185
|
+
const b = viewportBufferPx();
|
|
186
|
+
const vh = window.innerHeight || 800;
|
|
187
|
+
return r.bottom > -b && r.top < vh + b;
|
|
188
|
+
} catch (_) { return true; }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function rememberWrapHeight(w) {
|
|
192
|
+
try {
|
|
193
|
+
if (!(w instanceof Element)) return;
|
|
194
|
+
const klass = [...(w.classList || [])].find(c => c.startsWith('ezoic-ad-'));
|
|
195
|
+
if (!klass) return;
|
|
196
|
+
const h = Math.round(w.getBoundingClientRect().height || 0);
|
|
197
|
+
if (h >= 40) S.lastWrapHeightByClass.set(klass, h);
|
|
198
|
+
} catch (_) {}
|
|
199
|
+
}
|
|
200
|
+
|
|
98
201
|
function healFalseEmpty(root = document) {
|
|
99
202
|
try {
|
|
100
203
|
const list = [];
|
|
@@ -200,10 +303,20 @@ function destroyBeforeReuse(ids) {
|
|
|
200
303
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
201
304
|
seen.add(id);
|
|
202
305
|
out.push(id);
|
|
306
|
+
try {
|
|
307
|
+
const wrap = phEl(id)?.closest?.(WRAP_SEL) || null;
|
|
308
|
+
if (wrap && (wrapRecentActivity(wrap) || hasSpecialSlotMarkers(wrap) || hasFixedLikeNode(wrap))) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
} catch (_) {}
|
|
203
312
|
if (S.ezShownSinceDestroy.has(id)) toDestroy.push(id);
|
|
204
313
|
}
|
|
205
314
|
if (toDestroy.length) {
|
|
206
|
-
try {
|
|
315
|
+
try {
|
|
316
|
+
const ez = window.ezstandalone;
|
|
317
|
+
const run = () => { try { ez?.destroyPlaceholders?.(toDestroy); } catch (_) {} };
|
|
318
|
+
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
319
|
+
} catch (_) {}
|
|
207
320
|
for (const id of toDestroy) S.ezShownSinceDestroy.delete(id);
|
|
208
321
|
}
|
|
209
322
|
return out;
|
|
@@ -408,6 +521,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
408
521
|
|
|
409
522
|
for (const wrap of S.wrapByKey.values()) {
|
|
410
523
|
if (!wrap?.isConnected || !wrap.classList?.contains(WRAP_CLASS) || !wrap.classList.contains(klass)) continue;
|
|
524
|
+
if (wrapRecentActivity(wrap)) continue;
|
|
525
|
+
if (hasSpecialSlotMarkers(wrap) || hasFixedLikeNode(wrap)) continue;
|
|
411
526
|
try {
|
|
412
527
|
const rect = wrap.getBoundingClientRect();
|
|
413
528
|
const isAbove = rect.bottom <= farAbove;
|
|
@@ -442,6 +557,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
442
557
|
const oldKey = best.getAttribute(A_ANCHOR);
|
|
443
558
|
try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
|
|
444
559
|
mutate(() => {
|
|
560
|
+
rememberWrapHeight(best);
|
|
445
561
|
best.setAttribute(A_ANCHOR, newKey);
|
|
446
562
|
best.setAttribute(A_CREATED, String(ts()));
|
|
447
563
|
best.setAttribute(A_SHOWN, '0');
|
|
@@ -478,6 +594,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
478
594
|
w.setAttribute(A_CREATED, String(ts()));
|
|
479
595
|
w.setAttribute(A_SHOWN, '0');
|
|
480
596
|
w.style.cssText = 'width:100%;display:block;';
|
|
597
|
+
const cachedH = S.lastWrapHeightByClass.get(klass);
|
|
598
|
+
if (Number.isFinite(cachedH) && cachedH > 0) w.style.minHeight = `${cachedH}px`;
|
|
481
599
|
const ph = document.createElement('div');
|
|
482
600
|
ph.id = `${PH_PREFIX}${id}`;
|
|
483
601
|
ph.setAttribute('data-ezoic-id', String(id));
|
|
@@ -499,11 +617,14 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
499
617
|
|
|
500
618
|
function dropWrap(w) {
|
|
501
619
|
try {
|
|
620
|
+
rememberWrapHeight(w);
|
|
502
621
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
503
622
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
504
623
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
505
624
|
if (Number.isFinite(id)) { S.ezActiveIds.delete(id); S.mountedIds.delete(id); }
|
|
506
625
|
const key = w.getAttribute(A_ANCHOR);
|
|
626
|
+
if (key) S.wrapActivityAt.delete(`k:${key}`);
|
|
627
|
+
if (Number.isFinite(id)) S.wrapActivityAt.delete(`i:${id}`);
|
|
507
628
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
508
629
|
w.remove();
|
|
509
630
|
} catch (_) {}
|
|
@@ -530,7 +651,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
530
651
|
|
|
531
652
|
document.querySelectorAll(`${WRAP_SEL}.${klass}`).forEach(w => {
|
|
532
653
|
const created = parseInt(w.getAttribute(A_CREATED) || '0', 10);
|
|
533
|
-
if (ts() - created < MIN_PRUNE_AGE_MS) return;
|
|
654
|
+
if (ts() - created < Math.max(MIN_PRUNE_AGE_MS, wrapCleanupGraceMs(w))) return;
|
|
655
|
+
if (wrapRecentActivity(w) || wrapNearViewport(w)) return;
|
|
534
656
|
|
|
535
657
|
const key = w.getAttribute(A_ANCHOR) ?? '';
|
|
536
658
|
const sid = key.slice(klass.length + 1); // après "ezoic-ad-between:"
|
|
@@ -687,7 +809,7 @@ function startShowBatch(ids) {
|
|
|
687
809
|
if (!canShowPlaceholderId(id, t)) continue;
|
|
688
810
|
|
|
689
811
|
S.lastShow.set(id, t);
|
|
690
|
-
try { ph.closest?.(WRAP_SEL)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
812
|
+
try { const wrap = ph.closest?.(WRAP_SEL); wrap?.setAttribute(A_SHOWN, String(t)); if (wrap) markWrapActivity(wrap); } catch (_) {}
|
|
691
813
|
valid.push(id);
|
|
692
814
|
}
|
|
693
815
|
|
|
@@ -844,6 +966,7 @@ function startShowBatch(ids) {
|
|
|
844
966
|
S.wrapByKey.clear();
|
|
845
967
|
S.ezActiveIds.clear();
|
|
846
968
|
S.ezShownSinceDestroy.clear();
|
|
969
|
+
S.wrapActivityAt.clear();
|
|
847
970
|
S.inflight = 0;
|
|
848
971
|
S.pending = [];
|
|
849
972
|
S.pendingSet.clear();
|
|
@@ -887,12 +1010,21 @@ function startShowBatch(ids) {
|
|
|
887
1010
|
if (n.nodeType !== 1) continue;
|
|
888
1011
|
if (nodeMatchesAny(n, [WRAP_SEL]) || nodeContainsAny(n, [WRAP_SEL])) {
|
|
889
1012
|
sawWrapRemoval = true;
|
|
1013
|
+
try {
|
|
1014
|
+
if (n instanceof Element && n.classList?.contains(WRAP_CLASS)) markWrapActivity(n);
|
|
1015
|
+
else if (n instanceof Element) { const inner = n.querySelector?.(WRAP_SEL); if (inner) markWrapActivity(inner); }
|
|
1016
|
+
} catch (_) {}
|
|
890
1017
|
}
|
|
891
1018
|
}
|
|
892
1019
|
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
893
1020
|
for (const n of m.addedNodes) {
|
|
894
1021
|
if (n.nodeType !== 1) continue;
|
|
895
1022
|
try { healFalseEmpty(n); } catch (_) {}
|
|
1023
|
+
try {
|
|
1024
|
+
const w = (n instanceof Element && (n.classList?.contains(WRAP_CLASS) ? n : n.closest?.(WRAP_SEL))) || null;
|
|
1025
|
+
if (w) markWrapActivity(w);
|
|
1026
|
+
else if (n instanceof Element) { const inner = n.querySelector?.(WRAP_SEL); if (inner) markWrapActivity(inner); }
|
|
1027
|
+
} catch (_) {}
|
|
896
1028
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
897
1029
|
if (nodeMatchesAny(n, CONTENT_SEL_LIST) || nodeContainsAny(n, CONTENT_SEL_LIST)) {
|
|
898
1030
|
requestBurst(); return;
|