nodebb-plugin-ezoic-infinite 1.8.5 → 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 -434
- package/public/style.css +15 -0
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -77,23 +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
|
|
92
|
-
const PAGE_WARMUP_MS = 700; // délai minimal avant premiers showAds
|
|
93
|
-
const PAGE_SETTLE_GATE_MS = 5000; // fenêtre où l'on surveille la croissance réelle du contenu
|
|
94
|
-
const CONTENT_SETTLE_WINDOW_MS = 500;
|
|
95
|
-
const CONTENT_SETTLE_MAX_DELAY_MS = 2500;
|
|
96
|
-
const CONTENT_GROWTH_THRESHOLD_PX = 40;
|
|
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
|
|
97
86
|
|
|
98
87
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
99
88
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
@@ -137,29 +126,14 @@
|
|
|
137
126
|
inflight: 0, // showAds() en cours
|
|
138
127
|
pending: [], // ids en attente de slot inflight
|
|
139
128
|
pendingSet: new Set(),
|
|
140
|
-
showBatchTimer: 0,
|
|
141
|
-
destroyBatchTimer: 0,
|
|
142
|
-
destroyPending: [],
|
|
143
|
-
destroyPendingSet: new Set(),
|
|
144
|
-
sweepQueued: false,
|
|
145
129
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
146
|
-
ezActiveIds: new Set(), // ids actifs côté plugin (wrap présent / récemment show)
|
|
147
|
-
ezShownSinceDestroy: new Set(), // ids déjà show depuis le dernier destroy Ezoic
|
|
148
|
-
scrollDir: 1, // 1=bas, -1=haut
|
|
149
|
-
scrollSpeed: 0, // px/s approx (EMA)
|
|
150
|
-
lastScrollY: 0,
|
|
151
|
-
lastScrollTs: 0,
|
|
152
130
|
runQueued: false,
|
|
153
131
|
burstActive: false,
|
|
154
132
|
burstDeadline: 0,
|
|
155
133
|
burstCount: 0,
|
|
156
134
|
lastBurstTs: 0,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
settleDelaySince: 0,
|
|
160
|
-
contentStableStreak: 0,
|
|
161
|
-
contentSampleH: 0,
|
|
162
|
-
contentSampleTs: 0,
|
|
135
|
+
lastScrollY: 0,
|
|
136
|
+
scrollDir: 1, // 1=down, -1=up
|
|
163
137
|
};
|
|
164
138
|
|
|
165
139
|
let blockedUntil = 0;
|
|
@@ -170,166 +144,10 @@
|
|
|
170
144
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
171
145
|
const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
172
146
|
|
|
173
|
-
function phEl(id) {
|
|
174
|
-
return document.getElementById(`${PH_PREFIX}${id}`);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function hasSinglePlaceholder(id) {
|
|
178
|
-
try { return document.querySelectorAll(`#${PH_PREFIX}${id}`).length === 1; } catch (_) { return false; }
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function canShowPlaceholderId(id, now = ts()) {
|
|
182
|
-
const n = parseInt(id, 10);
|
|
183
|
-
if (!Number.isFinite(n) || n <= 0) return false;
|
|
184
|
-
if (now - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return false;
|
|
185
|
-
const ph = phEl(n);
|
|
186
|
-
if (!ph?.isConnected || isFilled(ph)) return false;
|
|
187
|
-
if (!hasSinglePlaceholder(n)) return false;
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function queueSweepDeadWraps() {
|
|
192
|
-
if (S.sweepQueued) return;
|
|
193
|
-
S.sweepQueued = true;
|
|
194
|
-
requestAnimationFrame(() => {
|
|
195
|
-
S.sweepQueued = false;
|
|
196
|
-
sweepDeadWraps();
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function getDynamicShowBatchMax() {
|
|
201
|
-
const speed = S.scrollSpeed || 0;
|
|
202
|
-
const pend = S.pending.length;
|
|
203
|
-
// Scroll très rapide => petits batches (réduit le churn/unused)
|
|
204
|
-
if (speed > 2600) return 2;
|
|
205
|
-
if (speed > 1400) return 3;
|
|
206
|
-
// Peu de candidats => flush plus vite, inutile d'attendre 4
|
|
207
|
-
if (pend <= 1) return 1;
|
|
208
|
-
if (pend <= 3) return 2;
|
|
209
|
-
// Par défaut compromis dynamique
|
|
210
|
-
return 3;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
147
|
function mutate(fn) {
|
|
214
148
|
S.mutGuard++;
|
|
215
149
|
try { fn(); } finally { S.mutGuard--; }
|
|
216
150
|
}
|
|
217
|
-
function scheduleDestroyFlush() {
|
|
218
|
-
if (S.destroyBatchTimer) return;
|
|
219
|
-
S.destroyBatchTimer = setTimeout(() => {
|
|
220
|
-
S.destroyBatchTimer = 0;
|
|
221
|
-
flushDestroyBatch();
|
|
222
|
-
}, DESTROY_FLUSH_MS);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function flushDestroyBatch() {
|
|
226
|
-
if (!S.destroyPending.length) return;
|
|
227
|
-
const ids = [];
|
|
228
|
-
while (S.destroyPending.length && ids.length < MAX_DESTROY_BATCH) {
|
|
229
|
-
const id = S.destroyPending.shift();
|
|
230
|
-
S.destroyPendingSet.delete(id);
|
|
231
|
-
if (!Number.isFinite(id) || id <= 0) continue;
|
|
232
|
-
ids.push(id);
|
|
233
|
-
}
|
|
234
|
-
if (ids.length) {
|
|
235
|
-
try {
|
|
236
|
-
const ez = window.ezstandalone;
|
|
237
|
-
const run = () => { try { ez?.destroyPlaceholders?.(ids); } catch (_) {} };
|
|
238
|
-
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
239
|
-
} catch (_) {}
|
|
240
|
-
}
|
|
241
|
-
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function destroyEzoicId(id) {
|
|
245
|
-
if (!Number.isFinite(id) || id <= 0) return;
|
|
246
|
-
if (!S.ezActiveIds.has(id)) return;
|
|
247
|
-
S.ezActiveIds.delete(id);
|
|
248
|
-
if (!S.destroyPendingSet.has(id)) {
|
|
249
|
-
S.destroyPending.push(id);
|
|
250
|
-
S.destroyPendingSet.add(id);
|
|
251
|
-
}
|
|
252
|
-
scheduleDestroyFlush();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function destroyBeforeReuse(ids) {
|
|
256
|
-
const out = [];
|
|
257
|
-
const toDestroy = [];
|
|
258
|
-
const seen = new Set();
|
|
259
|
-
for (const raw of (ids || [])) {
|
|
260
|
-
const id = parseInt(raw, 10);
|
|
261
|
-
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
262
|
-
seen.add(id);
|
|
263
|
-
out.push(id);
|
|
264
|
-
if (S.ezShownSinceDestroy.has(id)) toDestroy.push(id);
|
|
265
|
-
}
|
|
266
|
-
if (toDestroy.length) {
|
|
267
|
-
try { window.ezstandalone?.destroyPlaceholders?.(toDestroy); } catch (_) {}
|
|
268
|
-
for (const id of toDestroy) S.ezShownSinceDestroy.delete(id);
|
|
269
|
-
}
|
|
270
|
-
return out;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
function getContentHeight() {
|
|
275
|
-
try {
|
|
276
|
-
const c = document.getElementById('content') || document.querySelector('main#panel') || document.body;
|
|
277
|
-
if (!c) return 0;
|
|
278
|
-
return Math.max(c.scrollHeight || 0, c.offsetHeight || 0);
|
|
279
|
-
} catch (_) { return 0; }
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function getShowSettleDelayMs(now = ts()) {
|
|
283
|
-
try {
|
|
284
|
-
// Warmup minimal fixe (évite de tirer des pubs avant le 1er rendu NodeBB)
|
|
285
|
-
if (S.pageWarmUntil && now < S.pageWarmUntil) {
|
|
286
|
-
return Math.max(0, Math.min(250, S.pageWarmUntil - now));
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// En dehors de la fenêtre "page en train de se construire", pas de gate.
|
|
290
|
-
if (!(S.pageSettleUntil && now < S.pageSettleUntil)) return 0;
|
|
291
|
-
|
|
292
|
-
const h = getContentHeight();
|
|
293
|
-
const prevH = S.contentSampleH || 0;
|
|
294
|
-
const prevTs = S.contentSampleTs || 0;
|
|
295
|
-
const dt = prevTs ? (now - prevTs) : 0;
|
|
296
|
-
const grew = h - prevH;
|
|
297
|
-
|
|
298
|
-
S.contentSampleH = h;
|
|
299
|
-
S.contentSampleTs = now;
|
|
300
|
-
|
|
301
|
-
if (!prevTs || h <= 0) {
|
|
302
|
-
S.contentStableStreak = 0;
|
|
303
|
-
return CONTENT_SETTLE_WINDOW_MS;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Si les samples sont trop rapprochés, on attend une vraie fenêtre d'observation.
|
|
307
|
-
if (dt < Math.max(120, CONTENT_SETTLE_WINDOW_MS * 0.6)) {
|
|
308
|
-
return Math.max(80, CONTENT_SETTLE_WINDOW_MS - dt);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const significantGrowth = grew >= CONTENT_GROWTH_THRESHOLD_PX;
|
|
312
|
-
if (significantGrowth) {
|
|
313
|
-
if (!S.settleDelaySince) S.settleDelaySince = now;
|
|
314
|
-
S.contentStableStreak = 0;
|
|
315
|
-
if ((now - S.settleDelaySince) < CONTENT_SETTLE_MAX_DELAY_MS) {
|
|
316
|
-
return CONTENT_SETTLE_WINDOW_MS;
|
|
317
|
-
}
|
|
318
|
-
// plafond atteint : on laisse passer pour ne pas bloquer indéfiniment
|
|
319
|
-
S.settleDelaySince = 0;
|
|
320
|
-
return 0;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Nécessite 2 fenêtres stables de suite avant de laisser partir le batch (début de page)
|
|
324
|
-
S.contentStableStreak = (S.contentStableStreak || 0) + 1;
|
|
325
|
-
if (S.contentStableStreak < 2) return Math.min(200, CONTENT_SETTLE_WINDOW_MS);
|
|
326
|
-
|
|
327
|
-
S.settleDelaySince = 0;
|
|
328
|
-
return 0;
|
|
329
|
-
} catch (_) {}
|
|
330
|
-
return 0;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
151
|
|
|
334
152
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
335
153
|
|
|
@@ -484,25 +302,6 @@ function destroyBeforeReuse(ids) {
|
|
|
484
302
|
return null;
|
|
485
303
|
}
|
|
486
304
|
|
|
487
|
-
function sweepDeadWraps() {
|
|
488
|
-
// NodeBB peut retirer des nœuds sans passer par dropWrap() (virtualisation / rerender).
|
|
489
|
-
// On libère alors les IDs/keys fantômes pour éviter l'épuisement du pool.
|
|
490
|
-
for (const [key, wrap] of Array.from(S.wrapByKey.entries())) {
|
|
491
|
-
if (wrap?.isConnected) continue;
|
|
492
|
-
S.wrapByKey.delete(key);
|
|
493
|
-
const id = parseInt(wrap?.getAttribute?.(A_WRAPID), 10);
|
|
494
|
-
if (Number.isFinite(id)) {
|
|
495
|
-
S.mountedIds.delete(id);
|
|
496
|
-
S.pendingSet.delete(id);
|
|
497
|
-
S.lastShow.delete(id);
|
|
498
|
-
S.ezActiveIds.delete(id);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
if (S.pending.length) {
|
|
502
|
-
S.pending = S.pending.filter(id => S.pendingSet.has(id));
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
305
|
/**
|
|
507
306
|
* Pool épuisé : recycle un wrap loin au-dessus du viewport.
|
|
508
307
|
* Séquence avec délais (destroyPlaceholders est asynchrone) :
|
|
@@ -510,84 +309,83 @@ function destroyBeforeReuse(ids) {
|
|
|
510
309
|
* displayMore = API Ezoic prévue pour l'infinite scroll.
|
|
511
310
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
512
311
|
*/
|
|
513
|
-
function recycleAndMove(klass, targetEl, newKey) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
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;
|
|
547
345
|
}
|
|
548
|
-
}
|
|
549
|
-
if (!filled) {
|
|
550
|
-
if (metric < bestAnyMetric) { bestAnyMetric = metric; bestAnyEmpty = wrap; }
|
|
551
|
-
} else {
|
|
552
|
-
if (metric < bestAnyFilledMetric) { bestAnyFilledMetric = metric; bestAnyFilled = wrap; }
|
|
553
|
-
}
|
|
554
|
-
} catch (_) {}
|
|
555
|
-
}
|
|
556
346
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
best.setAttribute(A_CREATED, String(ts()));
|
|
567
|
-
best.setAttribute(A_SHOWN, '0');
|
|
568
|
-
best.classList.remove('is-empty');
|
|
569
|
-
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
570
|
-
if (ph) ph.innerHTML = '';
|
|
571
|
-
targetEl.insertAdjacentElement('afterend', best);
|
|
572
|
-
});
|
|
573
|
-
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
574
|
-
S.wrapByKey.set(newKey, best);
|
|
575
|
-
|
|
576
|
-
const doDestroy = () => {
|
|
577
|
-
if (S.ezShownSinceDestroy.has(id)) {
|
|
578
|
-
try { ez.destroyPlaceholders([id]); } catch (_) {}
|
|
579
|
-
S.ezShownSinceDestroy.delete(id);
|
|
580
|
-
}
|
|
581
|
-
S.ezActiveIds.delete(id);
|
|
582
|
-
setTimeout(doDefine, 330);
|
|
583
|
-
};
|
|
584
|
-
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
585
|
-
const doDisplay = () => { try { ez.displayMore([id]); S.ezActiveIds.add(id); S.ezShownSinceDestroy.add(id); } catch (_) {} };
|
|
586
|
-
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
|
+
});
|
|
587
356
|
|
|
588
|
-
|
|
589
|
-
|
|
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);
|
|
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 (_) {}
|
|
590
386
|
|
|
387
|
+
return { id, wrap: best };
|
|
388
|
+
}
|
|
591
389
|
|
|
592
390
|
// ── Wraps DOM — création / suppression ────────────────────────────────────
|
|
593
391
|
|
|
@@ -623,7 +421,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
623
421
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
624
422
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
625
423
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
626
|
-
if (Number.isFinite(id))
|
|
424
|
+
if (Number.isFinite(id)) S.mountedIds.delete(id);
|
|
627
425
|
const key = w.getAttribute(A_ANCHOR);
|
|
628
426
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
629
427
|
w.remove();
|
|
@@ -698,8 +496,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
698
496
|
const key = anchorKey(klass, el);
|
|
699
497
|
if (findWrap(key)) continue;
|
|
700
498
|
|
|
701
|
-
|
|
702
|
-
if (!id) { sweepDeadWraps(); id = pickId(poolKey); }
|
|
499
|
+
const id = pickId(poolKey);
|
|
703
500
|
if (id) {
|
|
704
501
|
const w = insertAfter(el, id, klass, key);
|
|
705
502
|
if (w) { observePh(id); inserted++; }
|
|
@@ -730,109 +527,76 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
730
527
|
}
|
|
731
528
|
|
|
732
529
|
function observePh(id) {
|
|
733
|
-
const ph =
|
|
530
|
+
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
734
531
|
if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
|
|
735
|
-
// Fast-path : si déjà proche viewport, ne pas attendre un callback IO complet
|
|
736
|
-
try {
|
|
737
|
-
if (!ph?.isConnected) return;
|
|
738
|
-
const rect = ph.getBoundingClientRect();
|
|
739
|
-
const vh = window.innerHeight || 800;
|
|
740
|
-
const preload = isMobile() ? 1400 : 1000;
|
|
741
|
-
if (rect.top <= vh + preload && rect.bottom >= -preload) enqueueShow(id);
|
|
742
|
-
} catch (_) {}
|
|
743
532
|
}
|
|
744
533
|
|
|
745
|
-
function enqueueShow(id) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
function scheduleDrainQueue(delayMs = BATCH_FLUSH_MS) {
|
|
755
|
-
if (isBlocked()) return;
|
|
756
|
-
if (S.showBatchTimer) return;
|
|
757
|
-
S.showBatchTimer = setTimeout(() => {
|
|
758
|
-
S.showBatchTimer = 0;
|
|
759
|
-
drainQueue();
|
|
760
|
-
}, Math.max(0, delayMs|0));
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function drainQueue() {
|
|
764
|
-
if (isBlocked()) return;
|
|
765
|
-
const settleDelay = getShowSettleDelayMs();
|
|
766
|
-
if (settleDelay > 0) { scheduleDrainQueue(Math.max(BATCH_FLUSH_MS, settleDelay)); return; }
|
|
767
|
-
const free = Math.max(0, MAX_INFLIGHT - S.inflight);
|
|
768
|
-
if (!free || !S.pending.length) return;
|
|
769
|
-
|
|
770
|
-
const picked = [];
|
|
771
|
-
const seen = new Set();
|
|
772
|
-
const batchCap = Math.max(1, Math.min(MAX_SHOW_BATCH, free, getDynamicShowBatchMax()));
|
|
773
|
-
while (S.pending.length && picked.length < batchCap) {
|
|
774
|
-
const id = S.pending.shift();
|
|
775
|
-
S.pendingSet.delete(id);
|
|
776
|
-
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
777
|
-
if (!phEl(id)?.isConnected) continue;
|
|
778
|
-
seen.add(id);
|
|
779
|
-
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);
|
|
780
542
|
}
|
|
781
|
-
if (picked.length) startShowBatch(picked);
|
|
782
|
-
if (S.pending.length && (MAX_INFLIGHT - S.inflight) > 0) scheduleDrainQueue();
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function startShowBatch(ids) {
|
|
786
|
-
if (!ids?.length || isBlocked()) return;
|
|
787
|
-
const reserve = ids.length;
|
|
788
|
-
S.inflight += reserve;
|
|
789
|
-
|
|
790
|
-
let done = false;
|
|
791
|
-
const release = () => {
|
|
792
|
-
if (done) return;
|
|
793
|
-
done = true;
|
|
794
|
-
S.inflight = Math.max(0, S.inflight - reserve);
|
|
795
|
-
drainQueue();
|
|
796
|
-
};
|
|
797
|
-
const timer = setTimeout(release, SHOW_FAILSAFE_MS);
|
|
798
543
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
+
}
|
|
802
552
|
|
|
803
|
-
|
|
804
|
-
|
|
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);
|
|
805
564
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
if (
|
|
809
|
-
const ph =
|
|
810
|
-
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(); }
|
|
811
570
|
|
|
571
|
+
const t = ts();
|
|
572
|
+
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
812
573
|
S.lastShow.set(id, t);
|
|
813
|
-
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
814
|
-
valid.push(id);
|
|
815
|
-
}
|
|
816
574
|
|
|
817
|
-
|
|
575
|
+
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
818
576
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
};
|
|
831
|
-
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
832
|
-
} catch (_) { clearTimeout(timer); release(); }
|
|
833
|
-
});
|
|
834
|
-
}
|
|
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
|
+
}
|
|
835
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
|
+
}
|
|
836
600
|
|
|
837
601
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
838
602
|
//
|
|
@@ -850,21 +614,14 @@ function startShowBatch(ids) {
|
|
|
850
614
|
const orig = ez.showAds.bind(ez);
|
|
851
615
|
ez.showAds = function (...args) {
|
|
852
616
|
if (isBlocked()) return;
|
|
853
|
-
const ids
|
|
854
|
-
const valid = [];
|
|
617
|
+
const ids = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
|
|
855
618
|
const seen = new Set();
|
|
856
619
|
for (const v of ids) {
|
|
857
620
|
const id = parseInt(v, 10);
|
|
858
621
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
859
|
-
if (!
|
|
622
|
+
if (!document.getElementById(`${PH_PREFIX}${id}`)?.isConnected) continue;
|
|
860
623
|
seen.add(id);
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
if (!valid.length) return;
|
|
864
|
-
try { orig(...valid); } catch (_) {
|
|
865
|
-
for (const id of valid) {
|
|
866
|
-
try { orig(id); } catch (_) {}
|
|
867
|
-
}
|
|
624
|
+
try { orig(id); } catch (_) {}
|
|
868
625
|
}
|
|
869
626
|
};
|
|
870
627
|
} catch (_) {}
|
|
@@ -881,7 +638,6 @@ function startShowBatch(ids) {
|
|
|
881
638
|
async function runCore() {
|
|
882
639
|
if (isBlocked()) return 0;
|
|
883
640
|
patchShowAds();
|
|
884
|
-
sweepDeadWraps();
|
|
885
641
|
|
|
886
642
|
const cfg = await fetchConfig();
|
|
887
643
|
if (!cfg || cfg.excluded) return 0;
|
|
@@ -948,7 +704,7 @@ function startShowBatch(ids) {
|
|
|
948
704
|
S.burstCount++;
|
|
949
705
|
scheduleRun(n => {
|
|
950
706
|
if (!n && !S.pending.length) { S.burstActive = false; return; }
|
|
951
|
-
setTimeout(step, n > 0 ?
|
|
707
|
+
setTimeout(step, n > 0 ? 150 : 300);
|
|
952
708
|
});
|
|
953
709
|
};
|
|
954
710
|
step();
|
|
@@ -966,27 +722,11 @@ function startShowBatch(ids) {
|
|
|
966
722
|
S.mountedIds.clear();
|
|
967
723
|
S.lastShow.clear();
|
|
968
724
|
S.wrapByKey.clear();
|
|
969
|
-
S.ezActiveIds.clear();
|
|
970
|
-
S.ezShownSinceDestroy.clear();
|
|
971
725
|
S.inflight = 0;
|
|
972
726
|
S.pending = [];
|
|
973
727
|
S.pendingSet.clear();
|
|
974
|
-
if (S.showBatchTimer) { clearTimeout(S.showBatchTimer); S.showBatchTimer = 0; }
|
|
975
|
-
if (S.destroyBatchTimer) { clearTimeout(S.destroyBatchTimer); S.destroyBatchTimer = 0; }
|
|
976
|
-
S.destroyPending = [];
|
|
977
|
-
S.destroyPendingSet.clear();
|
|
978
728
|
S.burstActive = false;
|
|
979
729
|
S.runQueued = false;
|
|
980
|
-
S.sweepQueued = false;
|
|
981
|
-
S.scrollSpeed = 0;
|
|
982
|
-
S.lastScrollY = 0;
|
|
983
|
-
S.lastScrollTs = 0;
|
|
984
|
-
S.pageWarmUntil = 0;
|
|
985
|
-
S.pageSettleUntil = 0;
|
|
986
|
-
S.contentStableStreak = 0;
|
|
987
|
-
S.settleDelaySince = 0;
|
|
988
|
-
S.contentSampleH = 0;
|
|
989
|
-
S.contentSampleTs = 0;
|
|
990
730
|
}
|
|
991
731
|
|
|
992
732
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
@@ -997,14 +737,6 @@ function startShowBatch(ids) {
|
|
|
997
737
|
S.domObs = new MutationObserver(muts => {
|
|
998
738
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
999
739
|
for (const m of muts) {
|
|
1000
|
-
let sawWrapRemoval = false;
|
|
1001
|
-
for (const n of m.removedNodes) {
|
|
1002
|
-
if (n.nodeType !== 1) continue;
|
|
1003
|
-
if ((n.matches && n.matches(`.${WRAP_CLASS}`)) || (n.querySelector && n.querySelector(`.${WRAP_CLASS}`))) {
|
|
1004
|
-
sawWrapRemoval = true;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
1008
740
|
for (const n of m.addedNodes) {
|
|
1009
741
|
if (n.nodeType !== 1) continue;
|
|
1010
742
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
@@ -1092,13 +824,7 @@ function startShowBatch(ids) {
|
|
|
1092
824
|
S.pageKey = pageKey();
|
|
1093
825
|
blockedUntil = 0;
|
|
1094
826
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
1095
|
-
|
|
1096
|
-
S.pageSettleUntil = ts() + PAGE_SETTLE_GATE_MS;
|
|
1097
|
-
S.settleDelaySince = 0;
|
|
1098
|
-
S.contentStableStreak = 0;
|
|
1099
|
-
S.contentSampleH = getContentHeight();
|
|
1100
|
-
S.contentSampleTs = ts();
|
|
1101
|
-
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
827
|
+
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
1102
828
|
});
|
|
1103
829
|
|
|
1104
830
|
const burstEvts = [
|
|
@@ -1120,21 +846,12 @@ function startShowBatch(ids) {
|
|
|
1120
846
|
|
|
1121
847
|
function bindScroll() {
|
|
1122
848
|
let ticking = false;
|
|
1123
|
-
try {
|
|
1124
|
-
S.lastScrollY = window.scrollY || window.pageYOffset || 0;
|
|
1125
|
-
S.lastScrollTs = ts();
|
|
1126
|
-
} catch (_) {}
|
|
1127
849
|
window.addEventListener('scroll', () => {
|
|
1128
850
|
try {
|
|
1129
851
|
const y = window.scrollY || window.pageYOffset || 0;
|
|
1130
|
-
const t = ts();
|
|
1131
852
|
const dy = y - (S.lastScrollY || 0);
|
|
1132
|
-
|
|
1133
|
-
if (Math.abs(dy) > 1) S.scrollDir = dy >= 0 ? 1 : -1;
|
|
1134
|
-
const inst = Math.abs(dy) * 1000 / dt;
|
|
1135
|
-
S.scrollSpeed = S.scrollSpeed ? (S.scrollSpeed * 0.7 + inst * 0.3) : inst;
|
|
853
|
+
if (Math.abs(dy) > 2) S.scrollDir = dy > 0 ? 1 : -1;
|
|
1136
854
|
S.lastScrollY = y;
|
|
1137
|
-
S.lastScrollTs = t;
|
|
1138
855
|
} catch (_) {}
|
|
1139
856
|
if (ticking) return;
|
|
1140
857
|
ticking = true;
|
|
@@ -1145,6 +862,7 @@ function startShowBatch(ids) {
|
|
|
1145
862
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
1146
863
|
|
|
1147
864
|
S.pageKey = pageKey();
|
|
865
|
+
try { S.lastScrollY = window.scrollY || window.pageYOffset || 0; } catch (_) { S.lastScrollY = 0; }
|
|
1148
866
|
muteConsole();
|
|
1149
867
|
ensureTcfLocator();
|
|
1150
868
|
warmNetwork();
|
|
@@ -1153,11 +871,6 @@ function startShowBatch(ids) {
|
|
|
1153
871
|
ensureDomObserver();
|
|
1154
872
|
bindNodeBB();
|
|
1155
873
|
bindScroll();
|
|
1156
|
-
S.pageWarmUntil = ts() + PAGE_WARMUP_MS;
|
|
1157
|
-
S.pageSettleUntil = ts() + PAGE_SETTLE_GATE_MS;
|
|
1158
|
-
S.contentStableStreak = 0;
|
|
1159
|
-
S.contentSampleH = getContentHeight();
|
|
1160
|
-
S.contentSampleTs = ts();
|
|
1161
874
|
blockedUntil = 0;
|
|
1162
875
|
requestBurst();
|
|
1163
876
|
|
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;
|