nodebb-plugin-ezoic-infinite 1.8.7 → 1.8.9
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 +364 -147
- package/public/style.css +9 -15
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -77,12 +77,19 @@
|
|
|
77
77
|
const A_CREATED = 'data-ezoic-created'; // timestamp création ms
|
|
78
78
|
const A_SHOWN = 'data-ezoic-shown'; // timestamp dernier showAds ms
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// Tunables (stables en prod)
|
|
81
81
|
const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
|
|
82
|
-
const MAX_INSERTS_RUN =
|
|
83
|
-
const MAX_INFLIGHT =
|
|
84
|
-
const
|
|
85
|
-
const
|
|
82
|
+
const MAX_INSERTS_RUN = 10; // plus réactif si NodeBB injecte en rafale
|
|
83
|
+
const MAX_INFLIGHT = 2; // ids max simultanés en vol (garde-fou)
|
|
84
|
+
const MAX_SHOW_BATCH = 4; // ids max par appel showAds(...ids)
|
|
85
|
+
const SHOW_THROTTLE_MS = 500; // anti-spam showAds() par id (plus réactif)
|
|
86
|
+
const SHOW_RELEASE_MS = 300; // relâche inflight après showAds() batché
|
|
87
|
+
const SHOW_FAILSAFE_MS = 7000; // relâche forcée si stack pub lente
|
|
88
|
+
const BATCH_FLUSH_MS = 30; // micro-buffer pour regrouper les ids proches
|
|
89
|
+
const MAX_DESTROY_BATCH = 4; // ids max par destroyPlaceholders(...ids)
|
|
90
|
+
const DESTROY_FLUSH_MS = 30; // micro-buffer destroy pour lisser les rafales
|
|
91
|
+
const BURST_COOLDOWN_MS = 100; // délai min entre deux déclenchements de burst
|
|
92
|
+
const MIN_RECYCLE_AGE_DESKTOP_MS = 3000; // évite le flash show→recycle trop rapide sur PC
|
|
86
93
|
|
|
87
94
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
88
95
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
@@ -126,14 +133,23 @@
|
|
|
126
133
|
inflight: 0, // showAds() en cours
|
|
127
134
|
pending: [], // ids en attente de slot inflight
|
|
128
135
|
pendingSet: new Set(),
|
|
136
|
+
showBatchTimer: 0,
|
|
137
|
+
destroyBatchTimer: 0,
|
|
138
|
+
destroyPending: [],
|
|
139
|
+
destroyPendingSet: new Set(),
|
|
140
|
+
sweepQueued: false,
|
|
129
141
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
142
|
+
ezActiveIds: new Set(), // ids actifs côté plugin (wrap présent / récemment show)
|
|
143
|
+
ezShownSinceDestroy: new Set(), // ids déjà show depuis le dernier destroy Ezoic
|
|
144
|
+
scrollDir: 1, // 1=bas, -1=haut
|
|
145
|
+
scrollSpeed: 0, // px/s approx (EMA)
|
|
146
|
+
lastScrollY: 0,
|
|
147
|
+
lastScrollTs: 0,
|
|
130
148
|
runQueued: false,
|
|
131
149
|
burstActive: false,
|
|
132
150
|
burstDeadline: 0,
|
|
133
151
|
burstCount: 0,
|
|
134
152
|
lastBurstTs: 0,
|
|
135
|
-
lastScrollY: 0,
|
|
136
|
-
scrollDir: 1, // 1=down, -1=up
|
|
137
153
|
};
|
|
138
154
|
|
|
139
155
|
let blockedUntil = 0;
|
|
@@ -144,10 +160,120 @@
|
|
|
144
160
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
145
161
|
const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
146
162
|
|
|
163
|
+
function healFalseEmpty(root = document) {
|
|
164
|
+
try {
|
|
165
|
+
const list = [];
|
|
166
|
+
if (root instanceof Element && root.classList?.contains(WRAP_CLASS)) list.push(root);
|
|
167
|
+
const found = root.querySelectorAll ? root.querySelectorAll(`.${WRAP_CLASS}.is-empty`) : [];
|
|
168
|
+
for (const w of found) list.push(w);
|
|
169
|
+
for (const w of list) {
|
|
170
|
+
if (!w?.classList?.contains('is-empty')) continue;
|
|
171
|
+
if (isFilled(w)) w.classList.remove('is-empty');
|
|
172
|
+
}
|
|
173
|
+
} catch (_) {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function phEl(id) {
|
|
177
|
+
return document.getElementById(`${PH_PREFIX}${id}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function hasSinglePlaceholder(id) {
|
|
181
|
+
try { return document.querySelectorAll(`#${PH_PREFIX}${id}`).length === 1; } catch (_) { return false; }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function canShowPlaceholderId(id, now = ts()) {
|
|
185
|
+
const n = parseInt(id, 10);
|
|
186
|
+
if (!Number.isFinite(n) || n <= 0) return false;
|
|
187
|
+
if (now - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return false;
|
|
188
|
+
const ph = phEl(n);
|
|
189
|
+
if (!ph?.isConnected || isFilled(ph)) return false;
|
|
190
|
+
if (!hasSinglePlaceholder(n)) return false;
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function queueSweepDeadWraps() {
|
|
195
|
+
if (S.sweepQueued) return;
|
|
196
|
+
S.sweepQueued = true;
|
|
197
|
+
requestAnimationFrame(() => {
|
|
198
|
+
S.sweepQueued = false;
|
|
199
|
+
sweepDeadWraps();
|
|
200
|
+
healFalseEmpty();
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getDynamicShowBatchMax() {
|
|
205
|
+
const speed = S.scrollSpeed || 0;
|
|
206
|
+
const pend = S.pending.length;
|
|
207
|
+
// Scroll très rapide => petits batches (réduit le churn/unused)
|
|
208
|
+
if (speed > 2600) return 2;
|
|
209
|
+
if (speed > 1400) return 3;
|
|
210
|
+
// Peu de candidats => flush plus vite, inutile d'attendre 4
|
|
211
|
+
if (pend <= 1) return 1;
|
|
212
|
+
if (pend <= 3) return 2;
|
|
213
|
+
// Par défaut compromis dynamique
|
|
214
|
+
return 3;
|
|
215
|
+
}
|
|
216
|
+
|
|
147
217
|
function mutate(fn) {
|
|
148
218
|
S.mutGuard++;
|
|
149
219
|
try { fn(); } finally { S.mutGuard--; }
|
|
150
220
|
}
|
|
221
|
+
function scheduleDestroyFlush() {
|
|
222
|
+
if (S.destroyBatchTimer) return;
|
|
223
|
+
S.destroyBatchTimer = setTimeout(() => {
|
|
224
|
+
S.destroyBatchTimer = 0;
|
|
225
|
+
flushDestroyBatch();
|
|
226
|
+
}, DESTROY_FLUSH_MS);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function flushDestroyBatch() {
|
|
230
|
+
if (!S.destroyPending.length) return;
|
|
231
|
+
const ids = [];
|
|
232
|
+
while (S.destroyPending.length && ids.length < MAX_DESTROY_BATCH) {
|
|
233
|
+
const id = S.destroyPending.shift();
|
|
234
|
+
S.destroyPendingSet.delete(id);
|
|
235
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
236
|
+
ids.push(id);
|
|
237
|
+
}
|
|
238
|
+
if (ids.length) {
|
|
239
|
+
try {
|
|
240
|
+
const ez = window.ezstandalone;
|
|
241
|
+
const run = () => { try { ez?.destroyPlaceholders?.(ids); } catch (_) {} };
|
|
242
|
+
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
243
|
+
} catch (_) {}
|
|
244
|
+
}
|
|
245
|
+
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function destroyEzoicId(id) {
|
|
249
|
+
if (!Number.isFinite(id) || id <= 0) return;
|
|
250
|
+
if (!S.ezActiveIds.has(id)) return;
|
|
251
|
+
S.ezActiveIds.delete(id);
|
|
252
|
+
if (!S.destroyPendingSet.has(id)) {
|
|
253
|
+
S.destroyPending.push(id);
|
|
254
|
+
S.destroyPendingSet.add(id);
|
|
255
|
+
}
|
|
256
|
+
scheduleDestroyFlush();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function destroyBeforeReuse(ids) {
|
|
260
|
+
const out = [];
|
|
261
|
+
const toDestroy = [];
|
|
262
|
+
const seen = new Set();
|
|
263
|
+
for (const raw of (ids || [])) {
|
|
264
|
+
const id = parseInt(raw, 10);
|
|
265
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
266
|
+
seen.add(id);
|
|
267
|
+
out.push(id);
|
|
268
|
+
if (S.ezShownSinceDestroy.has(id)) toDestroy.push(id);
|
|
269
|
+
}
|
|
270
|
+
if (toDestroy.length) {
|
|
271
|
+
try { window.ezstandalone?.destroyPlaceholders?.(toDestroy); } catch (_) {}
|
|
272
|
+
for (const id of toDestroy) S.ezShownSinceDestroy.delete(id);
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
|
|
151
277
|
|
|
152
278
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
153
279
|
|
|
@@ -302,6 +428,25 @@
|
|
|
302
428
|
return null;
|
|
303
429
|
}
|
|
304
430
|
|
|
431
|
+
function sweepDeadWraps() {
|
|
432
|
+
// NodeBB peut retirer des nœuds sans passer par dropWrap() (virtualisation / rerender).
|
|
433
|
+
// On libère alors les IDs/keys fantômes pour éviter l'épuisement du pool.
|
|
434
|
+
for (const [key, wrap] of Array.from(S.wrapByKey.entries())) {
|
|
435
|
+
if (wrap?.isConnected) continue;
|
|
436
|
+
S.wrapByKey.delete(key);
|
|
437
|
+
const id = parseInt(wrap?.getAttribute?.(A_WRAPID), 10);
|
|
438
|
+
if (Number.isFinite(id)) {
|
|
439
|
+
S.mountedIds.delete(id);
|
|
440
|
+
S.pendingSet.delete(id);
|
|
441
|
+
S.lastShow.delete(id);
|
|
442
|
+
S.ezActiveIds.delete(id);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (S.pending.length) {
|
|
446
|
+
S.pending = S.pending.filter(id => S.pendingSet.has(id));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
305
450
|
/**
|
|
306
451
|
* Pool épuisé : recycle un wrap loin au-dessus du viewport.
|
|
307
452
|
* Séquence avec délais (destroyPlaceholders est asynchrone) :
|
|
@@ -309,83 +454,88 @@
|
|
|
309
454
|
* displayMore = API Ezoic prévue pour l'infinite scroll.
|
|
310
455
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
311
456
|
*/
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
} else {
|
|
351
|
-
if (rect.top > belowFilledTop) { belowFilledTop = rect.top; belowFilled = wrap; }
|
|
352
|
-
}
|
|
457
|
+
function recycleAndMove(klass, targetEl, newKey) {
|
|
458
|
+
const ez = window.ezstandalone;
|
|
459
|
+
if (typeof ez?.destroyPlaceholders !== 'function' ||
|
|
460
|
+
typeof ez?.define !== 'function' ||
|
|
461
|
+
typeof ez?.displayMore !== 'function') return null;
|
|
462
|
+
|
|
463
|
+
const vh = window.innerHeight || 800;
|
|
464
|
+
const preferAbove = S.scrollDir >= 0; // scroll bas => recycle en haut
|
|
465
|
+
const farAbove = -vh;
|
|
466
|
+
const farBelow = vh * 2;
|
|
467
|
+
|
|
468
|
+
let bestPrefEmpty = null, bestPrefMetric = Infinity;
|
|
469
|
+
let bestPrefFilled = null, bestPrefFilledMetric = Infinity;
|
|
470
|
+
let bestAnyEmpty = null, bestAnyMetric = Infinity;
|
|
471
|
+
let bestAnyFilled = null, bestAnyFilledMetric = Infinity;
|
|
472
|
+
|
|
473
|
+
for (const wrap of S.wrapByKey.values()) {
|
|
474
|
+
if (!wrap?.isConnected || !wrap.classList?.contains(WRAP_CLASS) || !wrap.classList.contains(klass)) continue;
|
|
475
|
+
try {
|
|
476
|
+
if (!isMobile()) {
|
|
477
|
+
const shownTs = parseInt(wrap.getAttribute(A_SHOWN) || '0', 10);
|
|
478
|
+
if (shownTs > 0 && (ts() - shownTs) < MIN_RECYCLE_AGE_DESKTOP_MS) continue;
|
|
479
|
+
}
|
|
480
|
+
const rect = wrap.getBoundingClientRect();
|
|
481
|
+
const isAbove = rect.bottom <= farAbove;
|
|
482
|
+
const isBelow = rect.top >= farBelow;
|
|
483
|
+
const anyFar = isAbove || isBelow;
|
|
484
|
+
if (!anyFar) continue;
|
|
485
|
+
|
|
486
|
+
const qualifies = preferAbove ? isAbove : isBelow;
|
|
487
|
+
const metric = preferAbove ? Math.abs(rect.bottom) : Math.abs(rect.top - vh);
|
|
488
|
+
const filled = isFilled(wrap);
|
|
489
|
+
|
|
490
|
+
if (qualifies) {
|
|
491
|
+
if (!filled) {
|
|
492
|
+
if (metric < bestPrefMetric) { bestPrefMetric = metric; bestPrefEmpty = wrap; }
|
|
493
|
+
} else {
|
|
494
|
+
if (metric < bestPrefFilledMetric) { bestPrefFilledMetric = metric; bestPrefFilled = wrap; }
|
|
353
495
|
}
|
|
354
|
-
}
|
|
355
|
-
|
|
496
|
+
}
|
|
497
|
+
if (!filled) {
|
|
498
|
+
if (metric < bestAnyMetric) { bestAnyMetric = metric; bestAnyEmpty = wrap; }
|
|
499
|
+
} else {
|
|
500
|
+
if (metric < bestAnyFilledMetric) { bestAnyFilledMetric = metric; bestAnyFilled = wrap; }
|
|
501
|
+
}
|
|
502
|
+
} catch (_) {}
|
|
503
|
+
}
|
|
356
504
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
505
|
+
const best = bestPrefEmpty ?? bestPrefFilled ?? bestAnyEmpty ?? bestAnyFilled;
|
|
506
|
+
if (!best) return null;
|
|
507
|
+
const id = parseInt(best.getAttribute(A_WRAPID), 10);
|
|
508
|
+
if (!Number.isFinite(id)) return null;
|
|
509
|
+
|
|
510
|
+
const oldKey = best.getAttribute(A_ANCHOR);
|
|
511
|
+
try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
|
|
512
|
+
mutate(() => {
|
|
513
|
+
best.setAttribute(A_ANCHOR, newKey);
|
|
514
|
+
best.setAttribute(A_CREATED, String(ts()));
|
|
515
|
+
best.setAttribute(A_SHOWN, '0');
|
|
516
|
+
best.classList.remove('is-empty');
|
|
517
|
+
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
518
|
+
if (ph) ph.innerHTML = '';
|
|
519
|
+
targetEl.insertAdjacentElement('afterend', best);
|
|
520
|
+
});
|
|
521
|
+
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
522
|
+
S.wrapByKey.set(newKey, best);
|
|
523
|
+
|
|
524
|
+
const doDestroy = () => {
|
|
525
|
+
if (S.ezShownSinceDestroy.has(id)) {
|
|
526
|
+
try { ez.destroyPlaceholders([id]); } catch (_) {}
|
|
527
|
+
S.ezShownSinceDestroy.delete(id);
|
|
528
|
+
}
|
|
529
|
+
S.ezActiveIds.delete(id);
|
|
530
|
+
setTimeout(doDefine, 330);
|
|
531
|
+
};
|
|
532
|
+
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
533
|
+
const doDisplay = () => { try { ez.displayMore([id]); S.ezActiveIds.add(id); S.ezShownSinceDestroy.add(id); } catch (_) {} };
|
|
534
|
+
try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
380
535
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
384
|
-
const doDisplay = () => { try { ez.displayMore([id]); } catch (_) {} };
|
|
385
|
-
try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
536
|
+
return { id, wrap: best };
|
|
537
|
+
}
|
|
386
538
|
|
|
387
|
-
return { id, wrap: best };
|
|
388
|
-
}
|
|
389
539
|
|
|
390
540
|
// ── Wraps DOM — création / suppression ────────────────────────────────────
|
|
391
541
|
|
|
@@ -421,7 +571,7 @@
|
|
|
421
571
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
422
572
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
423
573
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
424
|
-
if (Number.isFinite(id)) S.mountedIds.delete(id);
|
|
574
|
+
if (Number.isFinite(id)) { S.ezActiveIds.delete(id); S.mountedIds.delete(id); }
|
|
425
575
|
const key = w.getAttribute(A_ANCHOR);
|
|
426
576
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
427
577
|
w.remove();
|
|
@@ -496,7 +646,8 @@
|
|
|
496
646
|
const key = anchorKey(klass, el);
|
|
497
647
|
if (findWrap(key)) continue;
|
|
498
648
|
|
|
499
|
-
|
|
649
|
+
let id = pickId(poolKey);
|
|
650
|
+
if (!id) { sweepDeadWraps(); id = pickId(poolKey); }
|
|
500
651
|
if (id) {
|
|
501
652
|
const w = insertAfter(el, id, klass, key);
|
|
502
653
|
if (w) { observePh(id); inserted++; }
|
|
@@ -527,76 +678,107 @@
|
|
|
527
678
|
}
|
|
528
679
|
|
|
529
680
|
function observePh(id) {
|
|
530
|
-
const ph =
|
|
681
|
+
const ph = phEl(id);
|
|
531
682
|
if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
|
|
683
|
+
// Fast-path : si déjà proche viewport, ne pas attendre un callback IO complet
|
|
684
|
+
try {
|
|
685
|
+
if (!ph?.isConnected) return;
|
|
686
|
+
const rect = ph.getBoundingClientRect();
|
|
687
|
+
const vh = window.innerHeight || 800;
|
|
688
|
+
const preload = isMobile() ? 1400 : 1000;
|
|
689
|
+
if (rect.top <= vh + preload && rect.bottom >= -preload) enqueueShow(id);
|
|
690
|
+
} catch (_) {}
|
|
532
691
|
}
|
|
533
692
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
693
|
+
function enqueueShow(id) {
|
|
694
|
+
if (!id || isBlocked()) return;
|
|
695
|
+
const n = parseInt(id, 10);
|
|
696
|
+
if (!Number.isFinite(n) || n <= 0) return;
|
|
697
|
+
if (ts() - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return;
|
|
698
|
+
if (!S.pendingSet.has(n)) { S.pending.push(n); S.pendingSet.add(n); }
|
|
699
|
+
scheduleDrainQueue();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function scheduleDrainQueue() {
|
|
703
|
+
if (isBlocked()) return;
|
|
704
|
+
if (S.showBatchTimer) return;
|
|
705
|
+
S.showBatchTimer = setTimeout(() => {
|
|
706
|
+
S.showBatchTimer = 0;
|
|
707
|
+
drainQueue();
|
|
708
|
+
}, BATCH_FLUSH_MS);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function drainQueue() {
|
|
712
|
+
if (isBlocked()) return;
|
|
713
|
+
const free = Math.max(0, MAX_INFLIGHT - S.inflight);
|
|
714
|
+
if (!free || !S.pending.length) return;
|
|
715
|
+
|
|
716
|
+
const picked = [];
|
|
717
|
+
const seen = new Set();
|
|
718
|
+
const batchCap = Math.max(1, Math.min(MAX_SHOW_BATCH, free, getDynamicShowBatchMax()));
|
|
719
|
+
while (S.pending.length && picked.length < batchCap) {
|
|
720
|
+
const id = S.pending.shift();
|
|
721
|
+
S.pendingSet.delete(id);
|
|
722
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
723
|
+
if (!phEl(id)?.isConnected) continue;
|
|
724
|
+
seen.add(id);
|
|
725
|
+
picked.push(id);
|
|
542
726
|
}
|
|
727
|
+
if (picked.length) startShowBatch(picked);
|
|
728
|
+
if (S.pending.length && (MAX_INFLIGHT - S.inflight) > 0) scheduleDrainQueue();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function startShowBatch(ids) {
|
|
732
|
+
if (!ids?.length || isBlocked()) return;
|
|
733
|
+
const reserve = ids.length;
|
|
734
|
+
S.inflight += reserve;
|
|
735
|
+
|
|
736
|
+
let done = false;
|
|
737
|
+
const release = () => {
|
|
738
|
+
if (done) return;
|
|
739
|
+
done = true;
|
|
740
|
+
S.inflight = Math.max(0, S.inflight - reserve);
|
|
741
|
+
drainQueue();
|
|
742
|
+
};
|
|
743
|
+
const timer = setTimeout(release, SHOW_FAILSAFE_MS);
|
|
543
744
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const id = S.pending.shift();
|
|
548
|
-
S.pendingSet.delete(id);
|
|
549
|
-
startShow(id);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
745
|
+
requestAnimationFrame(() => {
|
|
746
|
+
try {
|
|
747
|
+
if (isBlocked()) { clearTimeout(timer); return release(); }
|
|
552
748
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
S.inflight++;
|
|
556
|
-
let done = false;
|
|
557
|
-
const release = () => {
|
|
558
|
-
if (done) return;
|
|
559
|
-
done = true;
|
|
560
|
-
S.inflight = Math.max(0, S.inflight - 1);
|
|
561
|
-
drainQueue();
|
|
562
|
-
};
|
|
563
|
-
const timer = setTimeout(release, 7000);
|
|
749
|
+
const valid = [];
|
|
750
|
+
const t = ts();
|
|
564
751
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (
|
|
568
|
-
const ph =
|
|
569
|
-
if (!
|
|
752
|
+
for (const raw of ids) {
|
|
753
|
+
const id = parseInt(raw, 10);
|
|
754
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
755
|
+
const ph = phEl(id);
|
|
756
|
+
if (!canShowPlaceholderId(id, t)) continue;
|
|
570
757
|
|
|
571
|
-
const t = ts();
|
|
572
|
-
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
573
758
|
S.lastShow.set(id, t);
|
|
574
|
-
|
|
575
759
|
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
760
|
+
valid.push(id);
|
|
761
|
+
}
|
|
576
762
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
763
|
+
if (!valid.length) { clearTimeout(timer); return release(); }
|
|
764
|
+
|
|
765
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
766
|
+
const ez = window.ezstandalone;
|
|
767
|
+
const doShow = () => {
|
|
768
|
+
const prepared = destroyBeforeReuse(valid);
|
|
769
|
+
if (!prepared.length) { setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS); return; }
|
|
770
|
+
try { ez.showAds(...prepared); } catch (_) {}
|
|
771
|
+
for (const id of prepared) {
|
|
772
|
+
S.ezActiveIds.add(id);
|
|
773
|
+
S.ezShownSinceDestroy.add(id);
|
|
774
|
+
}
|
|
775
|
+
setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS);
|
|
776
|
+
};
|
|
777
|
+
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
778
|
+
} catch (_) { clearTimeout(timer); release(); }
|
|
779
|
+
});
|
|
780
|
+
}
|
|
588
781
|
|
|
589
|
-
function scheduleEmptyCheck(id, showTs) {
|
|
590
|
-
setTimeout(() => {
|
|
591
|
-
try {
|
|
592
|
-
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
593
|
-
const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
|
|
594
|
-
if (!wrap || !ph?.isConnected) return;
|
|
595
|
-
if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
|
|
596
|
-
wrap.classList.toggle('is-empty', !isFilled(ph));
|
|
597
|
-
} catch (_) {}
|
|
598
|
-
}, EMPTY_CHECK_MS);
|
|
599
|
-
}
|
|
600
782
|
|
|
601
783
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
602
784
|
//
|
|
@@ -614,14 +796,21 @@
|
|
|
614
796
|
const orig = ez.showAds.bind(ez);
|
|
615
797
|
ez.showAds = function (...args) {
|
|
616
798
|
if (isBlocked()) return;
|
|
617
|
-
const ids
|
|
799
|
+
const ids = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
|
|
800
|
+
const valid = [];
|
|
618
801
|
const seen = new Set();
|
|
619
802
|
for (const v of ids) {
|
|
620
803
|
const id = parseInt(v, 10);
|
|
621
804
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
622
|
-
if (!
|
|
805
|
+
if (!phEl(id)?.isConnected || !hasSinglePlaceholder(id)) continue;
|
|
623
806
|
seen.add(id);
|
|
624
|
-
|
|
807
|
+
valid.push(id);
|
|
808
|
+
}
|
|
809
|
+
if (!valid.length) return;
|
|
810
|
+
try { orig(...valid); } catch (_) {
|
|
811
|
+
for (const id of valid) {
|
|
812
|
+
try { orig(id); } catch (_) {}
|
|
813
|
+
}
|
|
625
814
|
}
|
|
626
815
|
};
|
|
627
816
|
} catch (_) {}
|
|
@@ -638,6 +827,7 @@
|
|
|
638
827
|
async function runCore() {
|
|
639
828
|
if (isBlocked()) return 0;
|
|
640
829
|
patchShowAds();
|
|
830
|
+
sweepDeadWraps();
|
|
641
831
|
|
|
642
832
|
const cfg = await fetchConfig();
|
|
643
833
|
if (!cfg || cfg.excluded) return 0;
|
|
@@ -704,7 +894,7 @@
|
|
|
704
894
|
S.burstCount++;
|
|
705
895
|
scheduleRun(n => {
|
|
706
896
|
if (!n && !S.pending.length) { S.burstActive = false; return; }
|
|
707
|
-
setTimeout(step, n > 0 ?
|
|
897
|
+
setTimeout(step, n > 0 ? 80 : 180);
|
|
708
898
|
});
|
|
709
899
|
};
|
|
710
900
|
step();
|
|
@@ -722,11 +912,21 @@
|
|
|
722
912
|
S.mountedIds.clear();
|
|
723
913
|
S.lastShow.clear();
|
|
724
914
|
S.wrapByKey.clear();
|
|
915
|
+
S.ezActiveIds.clear();
|
|
916
|
+
S.ezShownSinceDestroy.clear();
|
|
725
917
|
S.inflight = 0;
|
|
726
918
|
S.pending = [];
|
|
727
919
|
S.pendingSet.clear();
|
|
920
|
+
if (S.showBatchTimer) { clearTimeout(S.showBatchTimer); S.showBatchTimer = 0; }
|
|
921
|
+
if (S.destroyBatchTimer) { clearTimeout(S.destroyBatchTimer); S.destroyBatchTimer = 0; }
|
|
922
|
+
S.destroyPending = [];
|
|
923
|
+
S.destroyPendingSet.clear();
|
|
728
924
|
S.burstActive = false;
|
|
729
925
|
S.runQueued = false;
|
|
926
|
+
S.sweepQueued = false;
|
|
927
|
+
S.scrollSpeed = 0;
|
|
928
|
+
S.lastScrollY = 0;
|
|
929
|
+
S.lastScrollTs = 0;
|
|
730
930
|
}
|
|
731
931
|
|
|
732
932
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
@@ -737,8 +937,17 @@
|
|
|
737
937
|
S.domObs = new MutationObserver(muts => {
|
|
738
938
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
739
939
|
for (const m of muts) {
|
|
940
|
+
let sawWrapRemoval = false;
|
|
941
|
+
for (const n of m.removedNodes) {
|
|
942
|
+
if (n.nodeType !== 1) continue;
|
|
943
|
+
if ((n.matches && n.matches(`.${WRAP_CLASS}`)) || (n.querySelector && n.querySelector(`.${WRAP_CLASS}`))) {
|
|
944
|
+
sawWrapRemoval = true;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
740
948
|
for (const n of m.addedNodes) {
|
|
741
949
|
if (n.nodeType !== 1) continue;
|
|
950
|
+
try { healFalseEmpty(n); } catch (_) {}
|
|
742
951
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
743
952
|
if (allSel.some(s => { try { return n.matches(s); } catch(_){return false;} }) ||
|
|
744
953
|
allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
|
|
@@ -824,7 +1033,7 @@
|
|
|
824
1033
|
S.pageKey = pageKey();
|
|
825
1034
|
blockedUntil = 0;
|
|
826
1035
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
827
|
-
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
1036
|
+
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
828
1037
|
});
|
|
829
1038
|
|
|
830
1039
|
const burstEvts = [
|
|
@@ -846,12 +1055,21 @@
|
|
|
846
1055
|
|
|
847
1056
|
function bindScroll() {
|
|
848
1057
|
let ticking = false;
|
|
1058
|
+
try {
|
|
1059
|
+
S.lastScrollY = window.scrollY || window.pageYOffset || 0;
|
|
1060
|
+
S.lastScrollTs = ts();
|
|
1061
|
+
} catch (_) {}
|
|
849
1062
|
window.addEventListener('scroll', () => {
|
|
850
1063
|
try {
|
|
851
1064
|
const y = window.scrollY || window.pageYOffset || 0;
|
|
1065
|
+
const t = ts();
|
|
852
1066
|
const dy = y - (S.lastScrollY || 0);
|
|
853
|
-
|
|
1067
|
+
const dt = Math.max(1, t - (S.lastScrollTs || t));
|
|
1068
|
+
if (Math.abs(dy) > 1) S.scrollDir = dy >= 0 ? 1 : -1;
|
|
1069
|
+
const inst = Math.abs(dy) * 1000 / dt;
|
|
1070
|
+
S.scrollSpeed = S.scrollSpeed ? (S.scrollSpeed * 0.7 + inst * 0.3) : inst;
|
|
854
1071
|
S.lastScrollY = y;
|
|
1072
|
+
S.lastScrollTs = t;
|
|
855
1073
|
} catch (_) {}
|
|
856
1074
|
if (ticking) return;
|
|
857
1075
|
ticking = true;
|
|
@@ -862,7 +1080,6 @@
|
|
|
862
1080
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
863
1081
|
|
|
864
1082
|
S.pageKey = pageKey();
|
|
865
|
-
try { S.lastScrollY = window.scrollY || window.pageYOffset || 0; } catch (_) { S.lastScrollY = 0; }
|
|
866
1083
|
muteConsole();
|
|
867
1084
|
ensureTcfLocator();
|
|
868
1085
|
warmNetwork();
|
package/public/style.css
CHANGED
|
@@ -56,23 +56,17 @@
|
|
|
56
56
|
top: auto !important;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/* ── État vide ────────────────────────────────────────────────────────────── */
|
|
60
|
-
/*
|
|
61
|
-
Ajouté 20s après showAds si aucun fill détecté.
|
|
62
|
-
Collapse à 1px (pas 0) : reste observable par l'IO si le fill arrive tard.
|
|
63
|
-
*/
|
|
64
|
-
.nodebb-ezoic-wrap.is-empty {
|
|
65
|
-
display: block !important;
|
|
66
|
-
height: 1px !important;
|
|
67
|
-
min-height: 1px !important;
|
|
68
|
-
max-height: 1px !important;
|
|
69
|
-
margin: 0 !important;
|
|
70
|
-
padding: 0 !important;
|
|
71
|
-
overflow: hidden !important;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
59
|
/* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
|
|
75
60
|
.ezoic-ad {
|
|
76
61
|
margin: 0 !important;
|
|
77
62
|
padding: 0 !important;
|
|
78
63
|
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/* Filet anti faux-empty : si la pub est rendue, ne pas laisser le wrap replié */
|
|
67
|
+
.nodebb-ezoic-wrap.is-empty:has(iframe, [data-google-container-id], [id^="google_ads_iframe_"]) {
|
|
68
|
+
height: auto !important;
|
|
69
|
+
min-height: 1px !important;
|
|
70
|
+
max-height: none !important;
|
|
71
|
+
overflow: visible !important;
|
|
72
|
+
}
|