nodebb-plugin-ezoic-infinite 1.8.6 → 1.8.7
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 +147 -344
- package/public/style.css +15 -0
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -77,18 +77,12 @@
|
|
|
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
|
+
const EMPTY_CHECK_MS = 20_000; // délai avant collapse d'un wrap vide post-show
|
|
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 = 4; // max showAds() simultanés
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const SHOW_RELEASE_MS = 700; // 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 = 40; // 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 = 120; // délai min entre deux déclenchements de burst
|
|
82
|
+
const MAX_INSERTS_RUN = 6; // max insertions par appel runCore
|
|
83
|
+
const MAX_INFLIGHT = 4; // max showAds() simultanés
|
|
84
|
+
const SHOW_THROTTLE_MS = 900; // anti-spam showAds() par id
|
|
85
|
+
const BURST_COOLDOWN_MS = 200; // délai min entre deux déclenchements de burst
|
|
92
86
|
|
|
93
87
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
94
88
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
@@ -132,23 +126,14 @@
|
|
|
132
126
|
inflight: 0, // showAds() en cours
|
|
133
127
|
pending: [], // ids en attente de slot inflight
|
|
134
128
|
pendingSet: new Set(),
|
|
135
|
-
showBatchTimer: 0,
|
|
136
|
-
destroyBatchTimer: 0,
|
|
137
|
-
destroyPending: [],
|
|
138
|
-
destroyPendingSet: new Set(),
|
|
139
|
-
sweepQueued: false,
|
|
140
129
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
141
|
-
ezActiveIds: new Set(), // ids actifs côté plugin (wrap présent / récemment show)
|
|
142
|
-
ezShownSinceDestroy: new Set(), // ids déjà show depuis le dernier destroy Ezoic
|
|
143
|
-
scrollDir: 1, // 1=bas, -1=haut
|
|
144
|
-
scrollSpeed: 0, // px/s approx (EMA)
|
|
145
|
-
lastScrollY: 0,
|
|
146
|
-
lastScrollTs: 0,
|
|
147
130
|
runQueued: false,
|
|
148
131
|
burstActive: false,
|
|
149
132
|
burstDeadline: 0,
|
|
150
133
|
burstCount: 0,
|
|
151
134
|
lastBurstTs: 0,
|
|
135
|
+
lastScrollY: 0,
|
|
136
|
+
scrollDir: 1, // 1=down, -1=up
|
|
152
137
|
};
|
|
153
138
|
|
|
154
139
|
let blockedUntil = 0;
|
|
@@ -159,106 +144,10 @@
|
|
|
159
144
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
160
145
|
const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
161
146
|
|
|
162
|
-
function phEl(id) {
|
|
163
|
-
return document.getElementById(`${PH_PREFIX}${id}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function hasSinglePlaceholder(id) {
|
|
167
|
-
try { return document.querySelectorAll(`#${PH_PREFIX}${id}`).length === 1; } catch (_) { return false; }
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function canShowPlaceholderId(id, now = ts()) {
|
|
171
|
-
const n = parseInt(id, 10);
|
|
172
|
-
if (!Number.isFinite(n) || n <= 0) return false;
|
|
173
|
-
if (now - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return false;
|
|
174
|
-
const ph = phEl(n);
|
|
175
|
-
if (!ph?.isConnected || isFilled(ph)) return false;
|
|
176
|
-
if (!hasSinglePlaceholder(n)) return false;
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function queueSweepDeadWraps() {
|
|
181
|
-
if (S.sweepQueued) return;
|
|
182
|
-
S.sweepQueued = true;
|
|
183
|
-
requestAnimationFrame(() => {
|
|
184
|
-
S.sweepQueued = false;
|
|
185
|
-
sweepDeadWraps();
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function getDynamicShowBatchMax() {
|
|
190
|
-
const speed = S.scrollSpeed || 0;
|
|
191
|
-
const pend = S.pending.length;
|
|
192
|
-
// Scroll très rapide => petits batches (réduit le churn/unused)
|
|
193
|
-
if (speed > 2600) return 2;
|
|
194
|
-
if (speed > 1400) return 3;
|
|
195
|
-
// Peu de candidats => flush plus vite, inutile d'attendre 4
|
|
196
|
-
if (pend <= 1) return 1;
|
|
197
|
-
if (pend <= 3) return 2;
|
|
198
|
-
// Par défaut compromis dynamique
|
|
199
|
-
return 3;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
147
|
function mutate(fn) {
|
|
203
148
|
S.mutGuard++;
|
|
204
149
|
try { fn(); } finally { S.mutGuard--; }
|
|
205
150
|
}
|
|
206
|
-
function scheduleDestroyFlush() {
|
|
207
|
-
if (S.destroyBatchTimer) return;
|
|
208
|
-
S.destroyBatchTimer = setTimeout(() => {
|
|
209
|
-
S.destroyBatchTimer = 0;
|
|
210
|
-
flushDestroyBatch();
|
|
211
|
-
}, DESTROY_FLUSH_MS);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function flushDestroyBatch() {
|
|
215
|
-
if (!S.destroyPending.length) return;
|
|
216
|
-
const ids = [];
|
|
217
|
-
while (S.destroyPending.length && ids.length < MAX_DESTROY_BATCH) {
|
|
218
|
-
const id = S.destroyPending.shift();
|
|
219
|
-
S.destroyPendingSet.delete(id);
|
|
220
|
-
if (!Number.isFinite(id) || id <= 0) continue;
|
|
221
|
-
ids.push(id);
|
|
222
|
-
}
|
|
223
|
-
if (ids.length) {
|
|
224
|
-
try {
|
|
225
|
-
const ez = window.ezstandalone;
|
|
226
|
-
const run = () => { try { ez?.destroyPlaceholders?.(ids); } catch (_) {} };
|
|
227
|
-
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
228
|
-
} catch (_) {}
|
|
229
|
-
}
|
|
230
|
-
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function destroyEzoicId(id) {
|
|
234
|
-
if (!Number.isFinite(id) || id <= 0) return;
|
|
235
|
-
if (!S.ezActiveIds.has(id)) return;
|
|
236
|
-
S.ezActiveIds.delete(id);
|
|
237
|
-
if (!S.destroyPendingSet.has(id)) {
|
|
238
|
-
S.destroyPending.push(id);
|
|
239
|
-
S.destroyPendingSet.add(id);
|
|
240
|
-
}
|
|
241
|
-
scheduleDestroyFlush();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function destroyBeforeReuse(ids) {
|
|
245
|
-
const out = [];
|
|
246
|
-
const toDestroy = [];
|
|
247
|
-
const seen = new Set();
|
|
248
|
-
for (const raw of (ids || [])) {
|
|
249
|
-
const id = parseInt(raw, 10);
|
|
250
|
-
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
251
|
-
seen.add(id);
|
|
252
|
-
out.push(id);
|
|
253
|
-
if (S.ezShownSinceDestroy.has(id)) toDestroy.push(id);
|
|
254
|
-
}
|
|
255
|
-
if (toDestroy.length) {
|
|
256
|
-
try { window.ezstandalone?.destroyPlaceholders?.(toDestroy); } catch (_) {}
|
|
257
|
-
for (const id of toDestroy) S.ezShownSinceDestroy.delete(id);
|
|
258
|
-
}
|
|
259
|
-
return out;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
151
|
|
|
263
152
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
264
153
|
|
|
@@ -413,25 +302,6 @@ function destroyBeforeReuse(ids) {
|
|
|
413
302
|
return null;
|
|
414
303
|
}
|
|
415
304
|
|
|
416
|
-
function sweepDeadWraps() {
|
|
417
|
-
// NodeBB peut retirer des nœuds sans passer par dropWrap() (virtualisation / rerender).
|
|
418
|
-
// On libère alors les IDs/keys fantômes pour éviter l'épuisement du pool.
|
|
419
|
-
for (const [key, wrap] of Array.from(S.wrapByKey.entries())) {
|
|
420
|
-
if (wrap?.isConnected) continue;
|
|
421
|
-
S.wrapByKey.delete(key);
|
|
422
|
-
const id = parseInt(wrap?.getAttribute?.(A_WRAPID), 10);
|
|
423
|
-
if (Number.isFinite(id)) {
|
|
424
|
-
S.mountedIds.delete(id);
|
|
425
|
-
S.pendingSet.delete(id);
|
|
426
|
-
S.lastShow.delete(id);
|
|
427
|
-
S.ezActiveIds.delete(id);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (S.pending.length) {
|
|
431
|
-
S.pending = S.pending.filter(id => S.pendingSet.has(id));
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
305
|
/**
|
|
436
306
|
* Pool épuisé : recycle un wrap loin au-dessus du viewport.
|
|
437
307
|
* Séquence avec délais (destroyPlaceholders est asynchrone) :
|
|
@@ -439,84 +309,83 @@ function destroyBeforeReuse(ids) {
|
|
|
439
309
|
* displayMore = API Ezoic prévue pour l'infinite scroll.
|
|
440
310
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
441
311
|
*/
|
|
442
|
-
function recycleAndMove(klass, targetEl, newKey) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (metric < bestPrefFilledMetric) { bestPrefFilledMetric = metric; bestPrefFilled = wrap; }
|
|
312
|
+
function recycleAndMove(klass, targetEl, newKey) {
|
|
313
|
+
const ez = window.ezstandalone;
|
|
314
|
+
if (typeof ez?.destroyPlaceholders !== 'function' ||
|
|
315
|
+
typeof ez?.define !== 'function' ||
|
|
316
|
+
typeof ez?.displayMore !== 'function') return null;
|
|
317
|
+
|
|
318
|
+
const vh = window.innerHeight || 800;
|
|
319
|
+
const targetRect = targetEl?.getBoundingClientRect?.() || { top: vh, bottom: vh };
|
|
320
|
+
|
|
321
|
+
// Recyclage bidirectionnel :
|
|
322
|
+
// - scroll vers le bas -> recycle préférentiellement des wraps loin au-dessus
|
|
323
|
+
// - scroll vers le haut -> recycle préférentiellement des wraps loin en-dessous
|
|
324
|
+
// Fallback sur l'autre côté si aucun candidat.
|
|
325
|
+
const aboveThreshold = -vh; // wrap entièrement/suffisamment au-dessus
|
|
326
|
+
const belowThreshold = vh * 2; // wrap loin sous le viewport
|
|
327
|
+
|
|
328
|
+
let aboveEmpty = null, aboveEmptyBottom = Infinity;
|
|
329
|
+
let aboveFilled = null, aboveFilledBottom = Infinity;
|
|
330
|
+
let belowEmpty = null, belowEmptyTop = -Infinity;
|
|
331
|
+
let belowFilled = null, belowFilledTop = -Infinity;
|
|
332
|
+
|
|
333
|
+
document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(wrap => {
|
|
334
|
+
try {
|
|
335
|
+
if (!wrap?.isConnected) return;
|
|
336
|
+
const rect = wrap.getBoundingClientRect();
|
|
337
|
+
|
|
338
|
+
if (rect.bottom < aboveThreshold) {
|
|
339
|
+
if (!isFilled(wrap)) {
|
|
340
|
+
if (rect.bottom < aboveEmptyBottom) { aboveEmptyBottom = rect.bottom; aboveEmpty = wrap; }
|
|
341
|
+
} else {
|
|
342
|
+
if (rect.bottom < aboveFilledBottom) { aboveFilledBottom = rect.bottom; aboveFilled = wrap; }
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
476
345
|
}
|
|
477
|
-
}
|
|
478
|
-
if (!filled) {
|
|
479
|
-
if (metric < bestAnyMetric) { bestAnyMetric = metric; bestAnyEmpty = wrap; }
|
|
480
|
-
} else {
|
|
481
|
-
if (metric < bestAnyFilledMetric) { bestAnyFilledMetric = metric; bestAnyFilled = wrap; }
|
|
482
|
-
}
|
|
483
|
-
} catch (_) {}
|
|
484
|
-
}
|
|
485
346
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
best.setAttribute(A_CREATED, String(ts()));
|
|
496
|
-
best.setAttribute(A_SHOWN, '0');
|
|
497
|
-
best.classList.remove('is-empty');
|
|
498
|
-
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
499
|
-
if (ph) ph.innerHTML = '';
|
|
500
|
-
targetEl.insertAdjacentElement('afterend', best);
|
|
501
|
-
});
|
|
502
|
-
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
503
|
-
S.wrapByKey.set(newKey, best);
|
|
504
|
-
|
|
505
|
-
const doDestroy = () => {
|
|
506
|
-
if (S.ezShownSinceDestroy.has(id)) {
|
|
507
|
-
try { ez.destroyPlaceholders([id]); } catch (_) {}
|
|
508
|
-
S.ezShownSinceDestroy.delete(id);
|
|
509
|
-
}
|
|
510
|
-
S.ezActiveIds.delete(id);
|
|
511
|
-
setTimeout(doDefine, 330);
|
|
512
|
-
};
|
|
513
|
-
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
514
|
-
const doDisplay = () => { try { ez.displayMore([id]); S.ezActiveIds.add(id); S.ezShownSinceDestroy.add(id); } catch (_) {} };
|
|
515
|
-
try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
347
|
+
if (rect.top > belowThreshold) {
|
|
348
|
+
if (!isFilled(wrap)) {
|
|
349
|
+
if (rect.top > belowEmptyTop) { belowEmptyTop = rect.top; belowEmpty = wrap; }
|
|
350
|
+
} else {
|
|
351
|
+
if (rect.top > belowFilledTop) { belowFilledTop = rect.top; belowFilled = wrap; }
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} catch (_) {}
|
|
355
|
+
});
|
|
516
356
|
|
|
517
|
-
|
|
518
|
-
|
|
357
|
+
const preferBelow = (S.scrollDir < 0) || (targetRect.top < vh * 0.5);
|
|
358
|
+
const pickAbove = () => aboveEmpty ?? aboveFilled;
|
|
359
|
+
const pickBelow = () => belowEmpty ?? belowFilled;
|
|
360
|
+
const best = preferBelow ? (pickBelow() ?? pickAbove()) : (pickAbove() ?? pickBelow());
|
|
361
|
+
if (!best) return null;
|
|
362
|
+
const id = parseInt(best.getAttribute(A_WRAPID), 10);
|
|
363
|
+
if (!Number.isFinite(id)) return null;
|
|
364
|
+
|
|
365
|
+
const oldKey = best.getAttribute(A_ANCHOR);
|
|
366
|
+
// Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
|
|
367
|
+
// parasite si le nœud était encore dans la zone IO_MARGIN.
|
|
368
|
+
try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
|
|
369
|
+
mutate(() => {
|
|
370
|
+
best.setAttribute(A_ANCHOR, newKey);
|
|
371
|
+
best.setAttribute(A_CREATED, String(ts()));
|
|
372
|
+
best.setAttribute(A_SHOWN, '0');
|
|
373
|
+
best.classList.remove('is-empty');
|
|
374
|
+
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
375
|
+
if (ph) ph.innerHTML = '';
|
|
376
|
+
targetEl.insertAdjacentElement('afterend', best);
|
|
377
|
+
});
|
|
378
|
+
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
379
|
+
S.wrapByKey.set(newKey, best);
|
|
519
380
|
|
|
381
|
+
// Délais requis : destroyPlaceholders est asynchrone en interne
|
|
382
|
+
const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
|
|
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 (_) {}
|
|
386
|
+
|
|
387
|
+
return { id, wrap: best };
|
|
388
|
+
}
|
|
520
389
|
|
|
521
390
|
// ── Wraps DOM — création / suppression ────────────────────────────────────
|
|
522
391
|
|
|
@@ -552,7 +421,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
552
421
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
553
422
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
554
423
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
555
|
-
if (Number.isFinite(id))
|
|
424
|
+
if (Number.isFinite(id)) S.mountedIds.delete(id);
|
|
556
425
|
const key = w.getAttribute(A_ANCHOR);
|
|
557
426
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
558
427
|
w.remove();
|
|
@@ -627,8 +496,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
627
496
|
const key = anchorKey(klass, el);
|
|
628
497
|
if (findWrap(key)) continue;
|
|
629
498
|
|
|
630
|
-
|
|
631
|
-
if (!id) { sweepDeadWraps(); id = pickId(poolKey); }
|
|
499
|
+
const id = pickId(poolKey);
|
|
632
500
|
if (id) {
|
|
633
501
|
const w = insertAfter(el, id, klass, key);
|
|
634
502
|
if (w) { observePh(id); inserted++; }
|
|
@@ -659,107 +527,76 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
659
527
|
}
|
|
660
528
|
|
|
661
529
|
function observePh(id) {
|
|
662
|
-
const ph =
|
|
530
|
+
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
663
531
|
if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
|
|
664
|
-
// Fast-path : si déjà proche viewport, ne pas attendre un callback IO complet
|
|
665
|
-
try {
|
|
666
|
-
if (!ph?.isConnected) return;
|
|
667
|
-
const rect = ph.getBoundingClientRect();
|
|
668
|
-
const vh = window.innerHeight || 800;
|
|
669
|
-
const preload = isMobile() ? 1400 : 1000;
|
|
670
|
-
if (rect.top <= vh + preload && rect.bottom >= -preload) enqueueShow(id);
|
|
671
|
-
} catch (_) {}
|
|
672
532
|
}
|
|
673
533
|
|
|
674
|
-
function enqueueShow(id) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
function scheduleDrainQueue() {
|
|
684
|
-
if (isBlocked()) return;
|
|
685
|
-
if (S.showBatchTimer) return;
|
|
686
|
-
S.showBatchTimer = setTimeout(() => {
|
|
687
|
-
S.showBatchTimer = 0;
|
|
688
|
-
drainQueue();
|
|
689
|
-
}, BATCH_FLUSH_MS);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
function drainQueue() {
|
|
693
|
-
if (isBlocked()) return;
|
|
694
|
-
const free = Math.max(0, MAX_INFLIGHT - S.inflight);
|
|
695
|
-
if (!free || !S.pending.length) return;
|
|
696
|
-
|
|
697
|
-
const picked = [];
|
|
698
|
-
const seen = new Set();
|
|
699
|
-
const batchCap = Math.max(1, Math.min(MAX_SHOW_BATCH, free, getDynamicShowBatchMax()));
|
|
700
|
-
while (S.pending.length && picked.length < batchCap) {
|
|
701
|
-
const id = S.pending.shift();
|
|
702
|
-
S.pendingSet.delete(id);
|
|
703
|
-
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
704
|
-
if (!phEl(id)?.isConnected) continue;
|
|
705
|
-
seen.add(id);
|
|
706
|
-
picked.push(id);
|
|
534
|
+
function enqueueShow(id) {
|
|
535
|
+
if (!id || isBlocked()) return;
|
|
536
|
+
if (ts() - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) return;
|
|
537
|
+
if (S.inflight >= MAX_INFLIGHT) {
|
|
538
|
+
if (!S.pendingSet.has(id)) { S.pending.push(id); S.pendingSet.add(id); }
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
startShow(id);
|
|
707
542
|
}
|
|
708
|
-
if (picked.length) startShowBatch(picked);
|
|
709
|
-
if (S.pending.length && (MAX_INFLIGHT - S.inflight) > 0) scheduleDrainQueue();
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function startShowBatch(ids) {
|
|
713
|
-
if (!ids?.length || isBlocked()) return;
|
|
714
|
-
const reserve = ids.length;
|
|
715
|
-
S.inflight += reserve;
|
|
716
|
-
|
|
717
|
-
let done = false;
|
|
718
|
-
const release = () => {
|
|
719
|
-
if (done) return;
|
|
720
|
-
done = true;
|
|
721
|
-
S.inflight = Math.max(0, S.inflight - reserve);
|
|
722
|
-
drainQueue();
|
|
723
|
-
};
|
|
724
|
-
const timer = setTimeout(release, SHOW_FAILSAFE_MS);
|
|
725
543
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
544
|
+
function drainQueue() {
|
|
545
|
+
if (isBlocked()) return;
|
|
546
|
+
while (S.inflight < MAX_INFLIGHT && S.pending.length) {
|
|
547
|
+
const id = S.pending.shift();
|
|
548
|
+
S.pendingSet.delete(id);
|
|
549
|
+
startShow(id);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
729
552
|
|
|
730
|
-
|
|
731
|
-
|
|
553
|
+
function startShow(id) {
|
|
554
|
+
if (!id || isBlocked()) return;
|
|
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);
|
|
732
564
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
if (
|
|
736
|
-
const ph =
|
|
737
|
-
if (!
|
|
565
|
+
requestAnimationFrame(() => {
|
|
566
|
+
try {
|
|
567
|
+
if (isBlocked()) { clearTimeout(timer); return release(); }
|
|
568
|
+
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
569
|
+
if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
|
|
738
570
|
|
|
571
|
+
const t = ts();
|
|
572
|
+
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
739
573
|
S.lastShow.set(id, t);
|
|
740
|
-
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
741
|
-
valid.push(id);
|
|
742
|
-
}
|
|
743
574
|
|
|
744
|
-
|
|
575
|
+
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
745
576
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
};
|
|
758
|
-
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
759
|
-
} catch (_) { clearTimeout(timer); release(); }
|
|
760
|
-
});
|
|
761
|
-
}
|
|
577
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
578
|
+
const ez = window.ezstandalone;
|
|
579
|
+
const doShow = () => {
|
|
580
|
+
try { ez.showAds(id); } catch (_) {}
|
|
581
|
+
scheduleEmptyCheck(id, t);
|
|
582
|
+
setTimeout(() => { clearTimeout(timer); release(); }, 700);
|
|
583
|
+
};
|
|
584
|
+
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
585
|
+
} catch (_) { clearTimeout(timer); release(); }
|
|
586
|
+
});
|
|
587
|
+
}
|
|
762
588
|
|
|
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
|
+
}
|
|
763
600
|
|
|
764
601
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
765
602
|
//
|
|
@@ -777,21 +614,14 @@ function startShowBatch(ids) {
|
|
|
777
614
|
const orig = ez.showAds.bind(ez);
|
|
778
615
|
ez.showAds = function (...args) {
|
|
779
616
|
if (isBlocked()) return;
|
|
780
|
-
const ids
|
|
781
|
-
const valid = [];
|
|
617
|
+
const ids = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
|
|
782
618
|
const seen = new Set();
|
|
783
619
|
for (const v of ids) {
|
|
784
620
|
const id = parseInt(v, 10);
|
|
785
621
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
786
|
-
if (!
|
|
622
|
+
if (!document.getElementById(`${PH_PREFIX}${id}`)?.isConnected) continue;
|
|
787
623
|
seen.add(id);
|
|
788
|
-
|
|
789
|
-
}
|
|
790
|
-
if (!valid.length) return;
|
|
791
|
-
try { orig(...valid); } catch (_) {
|
|
792
|
-
for (const id of valid) {
|
|
793
|
-
try { orig(id); } catch (_) {}
|
|
794
|
-
}
|
|
624
|
+
try { orig(id); } catch (_) {}
|
|
795
625
|
}
|
|
796
626
|
};
|
|
797
627
|
} catch (_) {}
|
|
@@ -808,7 +638,6 @@ function startShowBatch(ids) {
|
|
|
808
638
|
async function runCore() {
|
|
809
639
|
if (isBlocked()) return 0;
|
|
810
640
|
patchShowAds();
|
|
811
|
-
sweepDeadWraps();
|
|
812
641
|
|
|
813
642
|
const cfg = await fetchConfig();
|
|
814
643
|
if (!cfg || cfg.excluded) return 0;
|
|
@@ -875,7 +704,7 @@ function startShowBatch(ids) {
|
|
|
875
704
|
S.burstCount++;
|
|
876
705
|
scheduleRun(n => {
|
|
877
706
|
if (!n && !S.pending.length) { S.burstActive = false; return; }
|
|
878
|
-
setTimeout(step, n > 0 ?
|
|
707
|
+
setTimeout(step, n > 0 ? 150 : 300);
|
|
879
708
|
});
|
|
880
709
|
};
|
|
881
710
|
step();
|
|
@@ -893,21 +722,11 @@ function startShowBatch(ids) {
|
|
|
893
722
|
S.mountedIds.clear();
|
|
894
723
|
S.lastShow.clear();
|
|
895
724
|
S.wrapByKey.clear();
|
|
896
|
-
S.ezActiveIds.clear();
|
|
897
|
-
S.ezShownSinceDestroy.clear();
|
|
898
725
|
S.inflight = 0;
|
|
899
726
|
S.pending = [];
|
|
900
727
|
S.pendingSet.clear();
|
|
901
|
-
if (S.showBatchTimer) { clearTimeout(S.showBatchTimer); S.showBatchTimer = 0; }
|
|
902
|
-
if (S.destroyBatchTimer) { clearTimeout(S.destroyBatchTimer); S.destroyBatchTimer = 0; }
|
|
903
|
-
S.destroyPending = [];
|
|
904
|
-
S.destroyPendingSet.clear();
|
|
905
728
|
S.burstActive = false;
|
|
906
729
|
S.runQueued = false;
|
|
907
|
-
S.sweepQueued = false;
|
|
908
|
-
S.scrollSpeed = 0;
|
|
909
|
-
S.lastScrollY = 0;
|
|
910
|
-
S.lastScrollTs = 0;
|
|
911
730
|
}
|
|
912
731
|
|
|
913
732
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
@@ -918,14 +737,6 @@ function startShowBatch(ids) {
|
|
|
918
737
|
S.domObs = new MutationObserver(muts => {
|
|
919
738
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
920
739
|
for (const m of muts) {
|
|
921
|
-
let sawWrapRemoval = false;
|
|
922
|
-
for (const n of m.removedNodes) {
|
|
923
|
-
if (n.nodeType !== 1) continue;
|
|
924
|
-
if ((n.matches && n.matches(`.${WRAP_CLASS}`)) || (n.querySelector && n.querySelector(`.${WRAP_CLASS}`))) {
|
|
925
|
-
sawWrapRemoval = true;
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
929
740
|
for (const n of m.addedNodes) {
|
|
930
741
|
if (n.nodeType !== 1) continue;
|
|
931
742
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
@@ -1013,7 +824,7 @@ function startShowBatch(ids) {
|
|
|
1013
824
|
S.pageKey = pageKey();
|
|
1014
825
|
blockedUntil = 0;
|
|
1015
826
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
1016
|
-
patchShowAds(); getIO(); ensureDomObserver();
|
|
827
|
+
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
1017
828
|
});
|
|
1018
829
|
|
|
1019
830
|
const burstEvts = [
|
|
@@ -1035,21 +846,12 @@ function startShowBatch(ids) {
|
|
|
1035
846
|
|
|
1036
847
|
function bindScroll() {
|
|
1037
848
|
let ticking = false;
|
|
1038
|
-
try {
|
|
1039
|
-
S.lastScrollY = window.scrollY || window.pageYOffset || 0;
|
|
1040
|
-
S.lastScrollTs = ts();
|
|
1041
|
-
} catch (_) {}
|
|
1042
849
|
window.addEventListener('scroll', () => {
|
|
1043
850
|
try {
|
|
1044
851
|
const y = window.scrollY || window.pageYOffset || 0;
|
|
1045
|
-
const t = ts();
|
|
1046
852
|
const dy = y - (S.lastScrollY || 0);
|
|
1047
|
-
|
|
1048
|
-
if (Math.abs(dy) > 1) S.scrollDir = dy >= 0 ? 1 : -1;
|
|
1049
|
-
const inst = Math.abs(dy) * 1000 / dt;
|
|
1050
|
-
S.scrollSpeed = S.scrollSpeed ? (S.scrollSpeed * 0.7 + inst * 0.3) : inst;
|
|
853
|
+
if (Math.abs(dy) > 2) S.scrollDir = dy > 0 ? 1 : -1;
|
|
1051
854
|
S.lastScrollY = y;
|
|
1052
|
-
S.lastScrollTs = t;
|
|
1053
855
|
} catch (_) {}
|
|
1054
856
|
if (ticking) return;
|
|
1055
857
|
ticking = true;
|
|
@@ -1060,6 +862,7 @@ function startShowBatch(ids) {
|
|
|
1060
862
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
1061
863
|
|
|
1062
864
|
S.pageKey = pageKey();
|
|
865
|
+
try { S.lastScrollY = window.scrollY || window.pageYOffset || 0; } catch (_) { S.lastScrollY = 0; }
|
|
1063
866
|
muteConsole();
|
|
1064
867
|
ensureTcfLocator();
|
|
1065
868
|
warmNetwork();
|
package/public/style.css
CHANGED
|
@@ -56,6 +56,21 @@
|
|
|
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
|
+
|
|
59
74
|
/* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
|
|
60
75
|
.ezoic-ad {
|
|
61
76
|
margin: 0 !important;
|