nodebb-plugin-ezoic-infinite 1.4.95 → 1.4.97
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 +365 -130
- package/public/style.css +19 -3
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -7,8 +7,17 @@
|
|
|
7
7
|
categoryItem: 'li[component="categories/category"]',
|
|
8
8
|
}, WRAP_CLASS = 'ezoic-ad';
|
|
9
9
|
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-', MAX_INSERTS_PER_RUN = 3;
|
|
10
|
+
// Ultra-fast: preload ads before they enter viewport (especially above-the-fold)
|
|
11
|
+
const PRELOAD_ROOT_MARGIN = '1200px 0px';
|
|
12
|
+
const ABOVE_FOLD_MULT = 1.5; // render immediately if within 1.5× viewport height
|
|
13
|
+
const FAST_START_MS = 2500; // aggressive preload window after page load
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// FLAG GLOBAL: Bloquer Ezoic pendant navigation
|
|
17
|
+
let EZOIC_BLOCKED = false;
|
|
18
|
+
|
|
19
|
+
// DEBUG: Vérifier que le plugin charge
|
|
10
20
|
|
|
11
|
-
// Nécessaire pour savoir si on doit appeler destroyPlaceholders avant recyclage.
|
|
12
21
|
const sessionDefinedIds = new Set();
|
|
13
22
|
|
|
14
23
|
const insertingIds = new Set(), state = {
|
|
@@ -33,7 +42,43 @@
|
|
|
33
42
|
|
|
34
43
|
obs: null,
|
|
35
44
|
activeTimeouts: new Set(),
|
|
36
|
-
lastScrollRun: 0,
|
|
45
|
+
lastScrollRun: 0,
|
|
46
|
+
io: null,
|
|
47
|
+
fastStartUntil: 0,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// Track currently inserted ad wrappers for recycling
|
|
52
|
+
const liveArr = [];
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// Network warm-up: reduce latency to Ezoic/CDN on first above-the-fold render
|
|
56
|
+
const _warmLinksDone = new Set();
|
|
57
|
+
function warmUpNetwork() {
|
|
58
|
+
try {
|
|
59
|
+
const head = document.head || document.getElementsByTagName('head')[0];
|
|
60
|
+
if (!head) return;
|
|
61
|
+
const links = [
|
|
62
|
+
['preconnect', 'https://g.ezoic.net', true],
|
|
63
|
+
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
64
|
+
['preconnect', 'https://go.ezoic.net', true],
|
|
65
|
+
['dns-prefetch', 'https://go.ezoic.net', false],
|
|
66
|
+
['preconnect', 'https://www.ezoic.net', true],
|
|
67
|
+
['dns-prefetch', 'https://www.ezoic.net', false],
|
|
68
|
+
];
|
|
69
|
+
for (const [rel, href, cors] of links) {
|
|
70
|
+
const key = rel + '|' + href;
|
|
71
|
+
if (_warmLinksDone.has(key)) continue;
|
|
72
|
+
_warmLinksDone.add(key);
|
|
73
|
+
const link = document.createElement('link');
|
|
74
|
+
link.rel = rel;
|
|
75
|
+
link.href = href;
|
|
76
|
+
if (cors) link.crossOrigin = 'anonymous';
|
|
77
|
+
head.appendChild(link);
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
}
|
|
81
|
+
|
|
37
82
|
|
|
38
83
|
function normalizeBool(v) {
|
|
39
84
|
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
@@ -106,9 +151,7 @@
|
|
|
106
151
|
|
|
107
152
|
function destroyPlaceholderIds(ids) {
|
|
108
153
|
if (!ids || !ids.length) return;
|
|
109
|
-
// Only destroy ids that were actually "defined" (filled at least once) to avoid Ezoic warnings.
|
|
110
154
|
const filtered = ids.filter((id) => {
|
|
111
|
-
// Utiliser sessionDefinedIds (survit aux navigations) plutôt que state.definedIds
|
|
112
155
|
try { return sessionDefinedIds.has(id); } catch (e) { return true; }
|
|
113
156
|
});
|
|
114
157
|
if (!filtered.length) return;
|
|
@@ -119,6 +162,11 @@
|
|
|
119
162
|
window.ezstandalone.destroyPlaceholders(filtered);
|
|
120
163
|
}
|
|
121
164
|
} catch (e) {}
|
|
165
|
+
|
|
166
|
+
// Recyclage: libérer IDs après 100ms
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
169
|
+
}, 100);
|
|
122
170
|
};
|
|
123
171
|
try {
|
|
124
172
|
window.ezstandalone = window.ezstandalone || {};
|
|
@@ -126,10 +174,63 @@
|
|
|
126
174
|
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
127
175
|
else window.ezstandalone.cmd.push(call);
|
|
128
176
|
} catch (e) {}
|
|
177
|
+
|
|
178
|
+
// Recyclage: libérer IDs après 100ms
|
|
179
|
+
setTimeout(() => {
|
|
180
|
+
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
181
|
+
}, 100);
|
|
129
182
|
}
|
|
130
183
|
|
|
184
|
+
// Nettoyer éléments Ezoic invisibles qui créent espace vertical
|
|
185
|
+
function cleanupInvisibleEzoicElements() {
|
|
186
|
+
try {
|
|
187
|
+
document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
|
|
188
|
+
const ph = wrapper.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
|
|
189
|
+
if (!ph) return;
|
|
131
190
|
|
|
132
|
-
//
|
|
191
|
+
// ULTRA-AGRESSIF: Supprimer TOUT sauf placeholder
|
|
192
|
+
Array.from(wrapper.children).forEach(child => {
|
|
193
|
+
if (child === ph || child.contains(ph)) return;
|
|
194
|
+
if (child.id && child.id.startsWith('ezoic-pub-ad-placeholder-')) return;
|
|
195
|
+
child.remove();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Forcer wrapper collé
|
|
199
|
+
wrapper.style.height = 'auto';
|
|
200
|
+
wrapper.style.overflow = 'hidden';
|
|
201
|
+
wrapper.style.lineHeight = '0';
|
|
202
|
+
wrapper.style.fontSize = '0';
|
|
203
|
+
wrapper.style.margin = '0';
|
|
204
|
+
wrapper.style.padding = '0';
|
|
205
|
+
|
|
206
|
+
// CRITIQUE: Forcer suppression margin sur TOUS les .ezoic-ad enfants
|
|
207
|
+
// Pour overrider les margin-top/bottom:15px !important d'Ezoic
|
|
208
|
+
wrapper.querySelectorAll('.ezoic-ad, span.ezoic-ad').forEach(ezSpan => {
|
|
209
|
+
ezSpan.style.setProperty('margin-top', '0', 'important');
|
|
210
|
+
ezSpan.style.setProperty('margin-bottom', '0', 'important');
|
|
211
|
+
ezSpan.style.setProperty('margin-left', '0', 'important');
|
|
212
|
+
ezSpan.style.setProperty('margin-right', '0', 'important');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Nettoyer aussi DANS le placeholder
|
|
216
|
+
if (ph) {
|
|
217
|
+
Array.from(ph.children).forEach(child => {
|
|
218
|
+
// Garder seulement iframe, ins, img
|
|
219
|
+
const tag = child.tagName;
|
|
220
|
+
if (tag !== 'IFRAME' && tag !== 'INS' && tag !== 'IMG' && tag !== 'DIV') {
|
|
221
|
+
child.remove();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
} catch (e) {}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Lancer cleanup périodique toutes les 2s
|
|
230
|
+
setInterval(() => {
|
|
231
|
+
try { cleanupInvisibleEzoicElements(); } catch (e) {}
|
|
232
|
+
}, 2000);
|
|
233
|
+
|
|
133
234
|
function cleanupEmptyWrappers() {
|
|
134
235
|
try {
|
|
135
236
|
document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
|
|
@@ -137,11 +238,17 @@
|
|
|
137
238
|
if (ph && ph.children.length === 0) {
|
|
138
239
|
// Placeholder vide après 3s = pub non chargée
|
|
139
240
|
setTimeout(() => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
241
|
+
try {
|
|
242
|
+
// Give Ezoic more time on slower connections, and don't remove if we recently requested an ad.
|
|
243
|
+
const phId = ph && ph.id;
|
|
244
|
+
const id = phId ? parseInt(phId.replace(PLACEHOLDER_PREFIX, ''), 10) : 0;
|
|
245
|
+
const last = (id && state.lastShowById && state.lastShowById.get(id)) || 0;
|
|
246
|
+
if (ph.children.length === 0 && (!last || Date.now() - last > 10000)) {
|
|
247
|
+
wrapper.remove();
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {}
|
|
250
|
+
}, 8000);
|
|
251
|
+
}
|
|
145
252
|
});
|
|
146
253
|
} catch (e) {}
|
|
147
254
|
}
|
|
@@ -217,10 +324,8 @@
|
|
|
217
324
|
if (findWrap(kindClass, afterPos)) return null;
|
|
218
325
|
|
|
219
326
|
// CRITICAL: Double-lock pour éviter race conditions sur les doublons
|
|
220
|
-
// 1. Vérifier qu'aucun autre thread n'est en train d'insérer cet ID
|
|
221
327
|
if (insertingIds.has(id)) return null;
|
|
222
328
|
|
|
223
|
-
// 2. Vérifier qu'aucun placeholder avec cet ID n'existe déjà dans le DOM
|
|
224
329
|
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
225
330
|
if (existingPh && existingPh.isConnected) return null;
|
|
226
331
|
|
|
@@ -233,7 +338,6 @@
|
|
|
233
338
|
attachFillObserver(wrap, id);
|
|
234
339
|
return wrap;
|
|
235
340
|
} finally {
|
|
236
|
-
// Libérer le lock après 100ms (le temps que le DOM soit stable)
|
|
237
341
|
setTimeout(() => insertingIds.delete(id), 50);
|
|
238
342
|
}
|
|
239
343
|
}
|
|
@@ -244,48 +348,57 @@
|
|
|
244
348
|
}
|
|
245
349
|
|
|
246
350
|
function patchShowAds() {
|
|
247
|
-
// Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
|
|
248
|
-
// Also ensures the patch is applied even if Ezoic loads after our script.
|
|
249
351
|
const applyPatch = () => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
352
|
+
try {
|
|
353
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
354
|
+
const ez = window.ezstandalone;
|
|
355
|
+
if (window.__nodebbEzoicPatched) return;
|
|
356
|
+
if (typeof ez.showAds !== 'function') return;
|
|
357
|
+
|
|
358
|
+
window.__nodebbEzoicPatched = true;
|
|
359
|
+
const orig = ez.showAds;
|
|
360
|
+
|
|
361
|
+
// Ezoic's ez-standalone.js logs warnings when asked to render an ID
|
|
362
|
+
// that isn't present in the DOM anymore. In an infinite-scroll context
|
|
363
|
+
// we must filter and call per-id.
|
|
364
|
+
ez.showAds = function (...args) {
|
|
365
|
+
if (EZOIC_BLOCKED) return;
|
|
366
|
+
|
|
367
|
+
let ids = [];
|
|
368
|
+
if (args.length === 1 && Array.isArray(args[0])) {
|
|
369
|
+
ids = args[0];
|
|
370
|
+
} else {
|
|
371
|
+
ids = args;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const seen = new Set();
|
|
375
|
+
for (const v of ids) {
|
|
376
|
+
const id = parseInt(v, 10);
|
|
377
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
378
|
+
|
|
379
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
380
|
+
if (!ph || !ph.isConnected) continue;
|
|
381
|
+
|
|
382
|
+
seen.add(id);
|
|
383
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
} catch (e) {}
|
|
272
387
|
};
|
|
273
388
|
|
|
274
389
|
applyPatch();
|
|
275
|
-
// Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
|
|
276
390
|
if (!window.__nodebbEzoicPatched) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
391
|
+
try {
|
|
392
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
393
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
394
|
+
window.ezstandalone.cmd.push(applyPatch);
|
|
395
|
+
} catch (e) {}
|
|
283
396
|
}
|
|
397
|
+
}
|
|
284
398
|
|
|
285
399
|
function markFilled(wrap) {
|
|
286
400
|
try {
|
|
287
401
|
if (!wrap) return;
|
|
288
|
-
// Disconnect the fill observer first (no need to remove+re-add the attribute)
|
|
289
402
|
if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
|
|
290
403
|
wrap.setAttribute('data-ezoic-filled', '1');
|
|
291
404
|
} catch (e) {}
|
|
@@ -301,19 +414,18 @@
|
|
|
301
414
|
if (!ph) return;
|
|
302
415
|
// Already filled?
|
|
303
416
|
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
304
|
-
markFilled(wrap);
|
|
305
|
-
|
|
417
|
+
markFilled(wrap); // Afficher wrapper
|
|
418
|
+
sessionDefinedIds.add(id);
|
|
306
419
|
return;
|
|
307
420
|
}
|
|
308
421
|
const obs = new MutationObserver(() => {
|
|
309
422
|
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
310
|
-
markFilled(wrap);
|
|
311
|
-
try {
|
|
423
|
+
markFilled(wrap); // CRITIQUE: Afficher wrapper maintenant
|
|
424
|
+
try { sessionDefinedIds.add(id); } catch (e) {}
|
|
312
425
|
try { obs.disconnect(); } catch (e) {}
|
|
313
426
|
}
|
|
314
427
|
});
|
|
315
428
|
obs.observe(ph, { childList: true, subtree: true });
|
|
316
|
-
// Keep a weak reference on the wrapper so we can disconnect on recycle/remove
|
|
317
429
|
wrap.__ezoicFillObs = obs;
|
|
318
430
|
} catch (e) {}
|
|
319
431
|
}
|
|
@@ -333,15 +445,12 @@
|
|
|
333
445
|
return filled;
|
|
334
446
|
}
|
|
335
447
|
|
|
336
|
-
// Appeler showAds() en batch selon recommandations Ezoic
|
|
337
|
-
// Au lieu de showAds(id1), showAds(id2)... faire showAds(id1, id2, id3...)
|
|
338
448
|
let batchShowAdsTimer = null;
|
|
339
449
|
const pendingShowAdsIds = new Set();
|
|
340
450
|
|
|
341
451
|
function scheduleShowAdsBatch(id) {
|
|
342
452
|
if (!id) return;
|
|
343
453
|
|
|
344
|
-
// CRITIQUE: Si cet ID a déjà été défini (sessionDefinedIds), le détruire d'abord
|
|
345
454
|
if (sessionDefinedIds.has(id)) {
|
|
346
455
|
try {
|
|
347
456
|
destroyPlaceholderIds([id]);
|
|
@@ -356,84 +465,144 @@
|
|
|
356
465
|
// Ajouter à la batch
|
|
357
466
|
pendingShowAdsIds.add(id);
|
|
358
467
|
|
|
359
|
-
// Debounce: attendre 100ms pour collecter tous les IDs
|
|
360
468
|
clearTimeout(batchShowAdsTimer);
|
|
361
469
|
batchShowAdsTimer = setTimeout(() => {
|
|
470
|
+
// CRITIQUE: Vérifier que nous sommes toujours sur la même page
|
|
471
|
+
const currentPageKey = getPageKey();
|
|
472
|
+
if (state.pageKey && currentPageKey !== state.pageKey) {
|
|
473
|
+
pendingShowAdsIds.clear();
|
|
474
|
+
return; // Page a changé, annuler
|
|
475
|
+
}
|
|
476
|
+
|
|
362
477
|
if (pendingShowAdsIds.size === 0) return;
|
|
363
478
|
|
|
364
479
|
const idsArray = Array.from(pendingShowAdsIds);
|
|
365
480
|
pendingShowAdsIds.clear();
|
|
366
481
|
|
|
482
|
+
// CRITIQUE: Vérifier que placeholders existent encore
|
|
483
|
+
const validIds = idsArray.filter(id => {
|
|
484
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
485
|
+
return ph && ph.isConnected;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
if (validIds.length === 0) return;
|
|
489
|
+
|
|
367
490
|
// Appeler showAds avec TOUS les IDs en une fois
|
|
368
491
|
try {
|
|
492
|
+
// CRITIQUE: Re-patcher AVANT chaque appel pour être sûr
|
|
493
|
+
patchShowAds();
|
|
494
|
+
|
|
369
495
|
window.ezstandalone = window.ezstandalone || {};
|
|
370
496
|
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
371
497
|
window.ezstandalone.cmd.push(function() {
|
|
372
498
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
373
499
|
// Appel batch: showAds(id1, id2, id3...)
|
|
374
|
-
window.ezstandalone.showAds(
|
|
500
|
+
window.ezstandalone.showAds(validIds);
|
|
375
501
|
// Tracker tous les IDs
|
|
376
|
-
|
|
502
|
+
validIds.forEach(id => {
|
|
377
503
|
state.lastShowById.set(id, Date.now());
|
|
378
504
|
sessionDefinedIds.add(id);
|
|
379
505
|
});
|
|
380
506
|
}
|
|
381
507
|
});
|
|
382
508
|
} catch (e) {}
|
|
509
|
+
|
|
510
|
+
// CRITIQUE: Nettoyer éléments invisibles APRÈS que pubs soient chargées
|
|
511
|
+
setTimeout(() => {
|
|
512
|
+
cleanupInvisibleEzoicElements();
|
|
513
|
+
}, 1500); // 1.5s pour laisser Ezoic charger
|
|
383
514
|
}, 100);
|
|
384
515
|
}
|
|
385
516
|
|
|
386
|
-
|
|
517
|
+
|
|
518
|
+
function callShowAdsWhenReady(id) {
|
|
387
519
|
if (!id) return;
|
|
388
520
|
|
|
389
521
|
const now = Date.now(), last = state.lastShowById.get(id) || 0;
|
|
390
|
-
if (now - last <
|
|
391
|
-
|
|
392
|
-
const phId = `${PLACEHOLDER_PREFIX}${id}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
522
|
+
if (now - last < 2500) return;
|
|
523
|
+
|
|
524
|
+
const phId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
525
|
+
|
|
526
|
+
const tryShowOrQueue = () => {
|
|
527
|
+
if (EZOIC_BLOCKED) return false;
|
|
528
|
+
const el = document.getElementById(phId);
|
|
529
|
+
if (!el || !el.isConnected) return false;
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
533
|
+
const ez = window.ezstandalone;
|
|
534
|
+
|
|
535
|
+
if (typeof ez.showAds === 'function') {
|
|
536
|
+
state.lastShowById.set(id, Date.now());
|
|
537
|
+
ez.showAds(id);
|
|
538
|
+
sessionDefinedIds.add(id);
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Queue once to run as soon as Ezoic is ready
|
|
543
|
+
ez.cmd = ez.cmd || [];
|
|
544
|
+
if (!el.__ezoicQueued) {
|
|
545
|
+
el.__ezoicQueued = true;
|
|
546
|
+
ez.cmd.push(() => {
|
|
547
|
+
try {
|
|
548
|
+
if (EZOIC_BLOCKED) return;
|
|
549
|
+
const ph = document.getElementById(phId);
|
|
550
|
+
if (!ph || !ph.isConnected) return;
|
|
551
|
+
state.lastShowById.set(id, Date.now());
|
|
552
|
+
window.ezstandalone.showAds(id);
|
|
553
|
+
sessionDefinedIds.add(id);
|
|
554
|
+
} catch (e) {}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
} catch (e) {}
|
|
558
|
+
return false;
|
|
404
559
|
};
|
|
405
560
|
|
|
406
561
|
const startPageKey = state.pageKey;
|
|
407
562
|
let attempts = 0;
|
|
408
|
-
(function waitForPh() {
|
|
409
|
-
// Abort if the user navigated away since this showAds was scheduled
|
|
410
|
-
if (state.pageKey !== startPageKey) return;
|
|
411
|
-
// Abort if another concurrent call is already handling this id
|
|
412
|
-
if (state.pendingById.has(id)) return;
|
|
413
|
-
|
|
414
|
-
attempts += 1;
|
|
415
|
-
const el = document.getElementById(phId);
|
|
416
|
-
if (el && el.isConnected) {
|
|
417
|
-
// CRITIQUE: Vérifier que le placeholder est VISIBLE
|
|
418
563
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
564
|
+
(function waitForPh() {
|
|
565
|
+
if (state.pageKey !== startPageKey) return;
|
|
566
|
+
|
|
567
|
+
const el = document.getElementById(phId);
|
|
568
|
+
if (el && el.isConnected) {
|
|
569
|
+
// tag id for IO callback
|
|
570
|
+
try { el.setAttribute('data-ezoic-id', String(id)); } catch (e) {}
|
|
571
|
+
|
|
572
|
+
// If above-the-fold (or during the fast-start window), fire immediately.
|
|
573
|
+
const inFastWindow = state.fastStartUntil && Date.now() < state.fastStartUntil;
|
|
574
|
+
if (isAboveFold(el) || (inFastWindow && (el.getBoundingClientRect().top < window.innerHeight * 3))) {
|
|
575
|
+
if (tryShowOrQueue()) {
|
|
576
|
+
state.pendingById.delete(id);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Otherwise, preload via IntersectionObserver
|
|
582
|
+
if (!state.pendingById.has(id)) {
|
|
583
|
+
state.pendingById.add(id);
|
|
584
|
+
const io = ensurePreloadObserver();
|
|
585
|
+
try { io && io.observe(el); } catch (e) {}
|
|
586
|
+
|
|
587
|
+
// Safety fallback: if it still hasn't rendered after 6s, force attempt.
|
|
588
|
+
const t = setTimeout(() => {
|
|
589
|
+
tryShowOrQueue();
|
|
590
|
+
state.pendingById.delete(id);
|
|
591
|
+
}, 6000);
|
|
592
|
+
state.activeTimeouts.add(t);
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
attempts += 1;
|
|
598
|
+
if (attempts < 100) {
|
|
599
|
+
const timeoutId = setTimeout(waitForPh, 25);
|
|
600
|
+
state.activeTimeouts.add(timeoutId);
|
|
601
|
+
}
|
|
433
602
|
})();
|
|
434
|
-
|
|
603
|
+
}
|
|
435
604
|
|
|
436
|
-
|
|
605
|
+
async function fetchConfig() {
|
|
437
606
|
if (state.cfg) return state.cfg;
|
|
438
607
|
if (state.cfgPromise) return state.cfgPromise;
|
|
439
608
|
|
|
@@ -484,7 +653,6 @@
|
|
|
484
653
|
const el = items[afterPos - 1];
|
|
485
654
|
if (!el || !el.isConnected) continue;
|
|
486
655
|
|
|
487
|
-
// Prevent adjacent ads (DOM-based, robust against virtualization)
|
|
488
656
|
if (isAdjacentAd(el) || isPrevAd(el)) {
|
|
489
657
|
continue;
|
|
490
658
|
}
|
|
@@ -501,17 +669,14 @@
|
|
|
501
669
|
|
|
502
670
|
let wrap = null;
|
|
503
671
|
if (pick.recycled && pick.recycled.wrap) {
|
|
504
|
-
// Only destroy if Ezoic has actually defined this placeholder before
|
|
505
672
|
if (sessionDefinedIds.has(id)) {
|
|
506
673
|
destroyPlaceholderIds([id]);
|
|
507
674
|
}
|
|
508
|
-
// Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
|
|
509
675
|
const oldWrap = pick.recycled.wrap;
|
|
510
676
|
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
511
677
|
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
512
678
|
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
513
679
|
if (!wrap) continue;
|
|
514
|
-
// Attendre que le wrapper soit dans le DOM puis appeler showAds
|
|
515
680
|
setTimeout(() => {
|
|
516
681
|
callShowAdsWhenReady(id);
|
|
517
682
|
}, 50);
|
|
@@ -525,12 +690,10 @@
|
|
|
525
690
|
}
|
|
526
691
|
|
|
527
692
|
liveArr.push({ id, wrap });
|
|
528
|
-
// If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
|
|
529
693
|
if (wrap && (
|
|
530
694
|
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
531
695
|
)) {
|
|
532
696
|
try { wrap.remove(); } catch (e) {}
|
|
533
|
-
// Put id back if it was newly consumed (not recycled)
|
|
534
697
|
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
535
698
|
try { kindPool.unshift(id); } catch (e) {}
|
|
536
699
|
usedSet.delete(id);
|
|
@@ -547,7 +710,6 @@
|
|
|
547
710
|
for (let i = 0; i < ads.length; i++) {
|
|
548
711
|
const ad = ads[i], prev = ad.previousElementSibling;
|
|
549
712
|
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
550
|
-
// Supprimer le wrapper adjacent au lieu de le cacher
|
|
551
713
|
try {
|
|
552
714
|
const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
553
715
|
if (ph) {
|
|
@@ -566,11 +728,45 @@
|
|
|
566
728
|
}
|
|
567
729
|
|
|
568
730
|
function cleanup() {
|
|
569
|
-
|
|
731
|
+
// DEBUG: Vérifier que cleanup est appelé
|
|
732
|
+
// CRITIQUE: BLOQUER Ezoic immédiatement
|
|
733
|
+
EZOIC_BLOCKED = true;
|
|
734
|
+
|
|
735
|
+
// Détruire TOUS les placeholders Ezoic AVANT de supprimer DOM
|
|
736
|
+
const allWrappers = document.querySelectorAll('.ezoic-ad');
|
|
737
|
+
const allIds = [];
|
|
738
|
+
allWrappers.forEach(wrapper => {
|
|
739
|
+
const ph = wrapper.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
|
|
740
|
+
if (ph) {
|
|
741
|
+
const match = ph.id.match(/\d+/);
|
|
742
|
+
if (match) allIds.push(parseInt(match[0]));
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// CRITIQUE: Vider COMPLÈTEMENT sessionDefinedIds
|
|
747
|
+
// Pour éviter que d'anciens IDs soient encore en mémoire
|
|
748
|
+
|
|
749
|
+
// CRITIQUE: Détruire AUSSI tous les IDs tracés dans state
|
|
750
|
+
// Pour annuler les anciens IDs qu'Ezoic a en mémoire
|
|
751
|
+
const trackedIds = [
|
|
752
|
+
...Array.from(state.usedTopics),
|
|
753
|
+
...Array.from(state.usedPosts),
|
|
754
|
+
...Array.from(state.usedCategories)
|
|
755
|
+
];
|
|
756
|
+
|
|
757
|
+
const allIdsToDestroy = [...new Set([...allIds, ...trackedIds, ...Array.from(sessionDefinedIds)])];
|
|
758
|
+
sessionDefinedIds.clear(); // ✅ VIDER TOUT
|
|
759
|
+
|
|
760
|
+
if (allIdsToDestroy.length > 0) {
|
|
761
|
+
destroyPlaceholderIds(allIdsToDestroy);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Annuler batch showAds en attente
|
|
765
|
+
pendingShowAdsIds.clear();
|
|
766
|
+
clearTimeout(batchShowAdsTimer);
|
|
570
767
|
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
document.querySelectorAll('.ezoic-ad').forEach(el => {
|
|
768
|
+
// Maintenant supprimer DOM
|
|
769
|
+
allWrappers.forEach(el => {
|
|
574
770
|
try { el.remove(); } catch (e) {}
|
|
575
771
|
});
|
|
576
772
|
|
|
@@ -585,26 +781,18 @@
|
|
|
585
781
|
state.usedPosts.clear();
|
|
586
782
|
state.usedCategories.clear();
|
|
587
783
|
state.lastShowById.clear();
|
|
588
|
-
// CRITIQUE: Vider pendingById pour annuler tous les showAds en cours
|
|
589
|
-
// Sinon Ezoic essaie d'accéder aux placeholders pendant que NodeBB vide le DOM
|
|
590
784
|
state.pendingById.clear();
|
|
591
785
|
state.definedIds.clear();
|
|
592
786
|
|
|
593
|
-
// NE PAS supprimer les wrappers Ezoic ici - ils seront supprimés naturellement
|
|
594
|
-
// quand NodeBB vide le DOM lors de la navigation ajaxify
|
|
595
|
-
// Les supprimer manuellement cause des problèmes avec l'état interne d'Ezoic
|
|
596
|
-
|
|
597
|
-
// CRITIQUE: Annuler TOUS les timeouts en cours pour éviter que les anciens
|
|
598
|
-
// showAds() continuent à s'exécuter après la navigation
|
|
599
787
|
state.activeTimeouts.forEach(id => {
|
|
600
788
|
try { clearTimeout(id); } catch (e) {}
|
|
601
789
|
});
|
|
602
790
|
state.activeTimeouts.clear();
|
|
603
791
|
|
|
604
|
-
// Vider aussi pendingById pour annuler les showAds en attente
|
|
605
792
|
state.pendingById.clear();
|
|
606
793
|
|
|
607
794
|
if (state.obs) { state.obs.disconnect(); state.obs = null; }
|
|
795
|
+
if (state.io) { try { state.io.disconnect(); } catch (e) {} state.io = null; }
|
|
608
796
|
|
|
609
797
|
state.scheduled = false;
|
|
610
798
|
clearTimeout(state.timer);
|
|
@@ -617,8 +805,40 @@
|
|
|
617
805
|
try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
618
806
|
}
|
|
619
807
|
|
|
808
|
+
function ensurePreloadObserver() {
|
|
809
|
+
if (state.io) return state.io;
|
|
810
|
+
try {
|
|
811
|
+
state.io = new IntersectionObserver((entries) => {
|
|
812
|
+
for (const ent of entries) {
|
|
813
|
+
if (!ent.isIntersecting) continue;
|
|
814
|
+
const el = ent.target;
|
|
815
|
+
try { state.io && state.io.unobserve(el); } catch (e) {}
|
|
816
|
+
const idAttr = el && el.getAttribute && el.getAttribute('data-ezoic-id');
|
|
817
|
+
const id = parseInt(idAttr, 10);
|
|
818
|
+
if (Number.isFinite(id) && id > 0) {
|
|
819
|
+
// Try immediately; otherwise it will queue via ezstandalone.cmd
|
|
820
|
+
try { callShowAdsWhenReady(id); } catch (e) {}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}, { root: null, rootMargin: PRELOAD_ROOT_MARGIN, threshold: 0.01 });
|
|
824
|
+
} catch (e) { state.io = null; }
|
|
825
|
+
return state.io;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function isAboveFold(el) {
|
|
829
|
+
try {
|
|
830
|
+
if (!el || !el.getBoundingClientRect) return false;
|
|
831
|
+
const r = el.getBoundingClientRect();
|
|
832
|
+
return r.top < (window.innerHeight * ABOVE_FOLD_MULT) && r.bottom > -200;
|
|
833
|
+
} catch (e) { return false; }
|
|
834
|
+
}
|
|
835
|
+
|
|
620
836
|
async function runCore() {
|
|
621
|
-
//
|
|
837
|
+
// CRITIQUE: Ne rien insérer si navigation en cours
|
|
838
|
+
if (EZOIC_BLOCKED) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
622
842
|
if (!state.canShowAds) {
|
|
623
843
|
return;
|
|
624
844
|
}
|
|
@@ -661,7 +881,6 @@
|
|
|
661
881
|
|
|
662
882
|
enforceNoAdjacentAds();
|
|
663
883
|
|
|
664
|
-
// If nothing inserted and list isn't in DOM yet (first click), retry a bit
|
|
665
884
|
let count = 0;
|
|
666
885
|
if (kind === 'topic') count = getPostContainers().length;
|
|
667
886
|
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
@@ -673,12 +892,9 @@
|
|
|
673
892
|
}
|
|
674
893
|
|
|
675
894
|
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
676
|
-
// Plus d'insertions possibles ce cycle, continuer immédiatement
|
|
677
895
|
setTimeout(arguments[0], 50);
|
|
678
896
|
} else if (inserted === 0 && count > 0) {
|
|
679
897
|
// Pool épuisé ou recyclage pas encore disponible.
|
|
680
|
-
// Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
|
|
681
|
-
// le temps de défiler hors écran et devenir recyclables.
|
|
682
898
|
if (state.poolWaitAttempts < 8) {
|
|
683
899
|
state.poolWaitAttempts += 1;
|
|
684
900
|
setTimeout(arguments[0], 50);
|
|
@@ -708,19 +924,33 @@
|
|
|
708
924
|
|
|
709
925
|
$(window).on('action:ajaxify.start.ezoicInfinite', () => cleanup());
|
|
710
926
|
|
|
711
|
-
|
|
927
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
712
928
|
state.pageKey = getPageKey();
|
|
929
|
+
|
|
930
|
+
// Débloquer Ezoic IMMÉDIATEMENT pour la nouvelle page
|
|
931
|
+
EZOIC_BLOCKED = false;
|
|
932
|
+
|
|
933
|
+
// Warm-up réseau + runtime Ezoic dès que possible
|
|
934
|
+
warmUpNetwork();
|
|
935
|
+
patchShowAds();
|
|
936
|
+
|
|
937
|
+
// Ultra-fast preload window
|
|
938
|
+
state.fastStartUntil = Date.now() + FAST_START_MS;
|
|
939
|
+
|
|
713
940
|
ensureObserver();
|
|
941
|
+
ensurePreloadObserver();
|
|
714
942
|
|
|
715
|
-
// CRITIQUE: Attendre 300ms avant de permettre l'insertion de nouveaux placeholders
|
|
716
|
-
// pour laisser les anciens showAds() (en cours) se terminer ou échouer proprement
|
|
717
|
-
// Sinon race condition: NodeBB vide le DOM pendant que Ezoic essaie d'accéder aux placeholders
|
|
718
943
|
state.canShowAds = true;
|
|
944
|
+
|
|
945
|
+
// HERO slot ASAP (above-the-fold)
|
|
946
|
+
insertHeroAdEarly();
|
|
947
|
+
|
|
948
|
+
// Relancer l'insertion normale
|
|
949
|
+
scheduleRun();
|
|
719
950
|
});
|
|
720
951
|
|
|
721
952
|
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
722
953
|
ensureObserver();
|
|
723
|
-
// category.loaded = infinite scroll, Ezoic déjà chargé normalement
|
|
724
954
|
waitForContentThenRun();
|
|
725
955
|
});
|
|
726
956
|
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
@@ -750,7 +980,6 @@
|
|
|
750
980
|
window.requestAnimationFrame(() => {
|
|
751
981
|
ticking = false;
|
|
752
982
|
enforceNoAdjacentAds();
|
|
753
|
-
// Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
|
|
754
983
|
const now = Date.now();
|
|
755
984
|
if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
|
|
756
985
|
state.lastScrollRun = now;
|
|
@@ -760,7 +989,6 @@
|
|
|
760
989
|
}, { passive: true });
|
|
761
990
|
}
|
|
762
991
|
|
|
763
|
-
// Fonction qui attend que la page ait assez de contenu avant d'insérer les pubs
|
|
764
992
|
function waitForContentThenRun() {
|
|
765
993
|
const MIN_WORDS = 250;
|
|
766
994
|
let attempts = 0;
|
|
@@ -791,7 +1019,6 @@
|
|
|
791
1019
|
})();
|
|
792
1020
|
}
|
|
793
1021
|
|
|
794
|
-
// Fonction qui attend que Ezoic soit vraiment chargé
|
|
795
1022
|
function waitForEzoicThenRun() {
|
|
796
1023
|
let attempts = 0;
|
|
797
1024
|
const maxAttempts = 50; // 50 × 200ms = 10s max
|
|
@@ -820,8 +1047,16 @@
|
|
|
820
1047
|
bind();
|
|
821
1048
|
bindScroll();
|
|
822
1049
|
ensureObserver();
|
|
1050
|
+
ensurePreloadObserver();
|
|
823
1051
|
state.pageKey = getPageKey();
|
|
824
1052
|
|
|
1053
|
+
// Warm-up réseau dès le boot
|
|
1054
|
+
warmUpNetwork();
|
|
1055
|
+
patchShowAds();
|
|
1056
|
+
state.fastStartUntil = Date.now() + FAST_START_MS;
|
|
1057
|
+
// HERO slot ASAP au premier chargement
|
|
1058
|
+
insertHeroAdEarly();
|
|
1059
|
+
|
|
825
1060
|
// Attendre que Ezoic soit chargé avant d'insérer
|
|
826
1061
|
waitForEzoicThenRun();
|
|
827
1062
|
})();
|
package/public/style.css
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
.ezoic-ad
|
|
2
|
-
.ezoic-ad
|
|
3
|
-
.ezoic-ad
|
|
1
|
+
.ezoic-ad,
|
|
2
|
+
.ezoic-ad *,
|
|
3
|
+
span.ezoic-ad,
|
|
4
|
+
span[class*="ezoic"] {
|
|
5
|
+
min-height: 0 !important;
|
|
6
|
+
min-width: 0 !important;
|
|
7
|
+
}
|
|
8
|
+
/* Reduce layout shifts and kill extra spacing around Ezoic wrappers */
|
|
9
|
+
.ezoic-ad {
|
|
10
|
+
display: block;
|
|
11
|
+
width: 100%;
|
|
12
|
+
margin: 0 !important;
|
|
13
|
+
padding: 0 !important;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
.ezoic-ad > [id^="ezoic-pub-ad-placeholder-"] {
|
|
17
|
+
margin: 0 !important;
|
|
18
|
+
padding: 0 !important;
|
|
19
|
+
}
|