nodebb-plugin-ezoic-infinite 1.7.49 → 1.7.51
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 +91 -206
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,50 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — client.js v57
|
|
3
3
|
*
|
|
4
|
-
* Historique
|
|
5
|
-
*
|
|
6
|
-
* v18 Ancrage stable par data-pid / data-index
|
|
7
|
-
*
|
|
8
|
-
* v20 Table KIND : anchorAttr / ordinalAttr / baseTag par kindClass.
|
|
9
|
-
* IO fixe (une instance, jamais recréée). Fix TCF locator.
|
|
4
|
+
* Historique
|
|
5
|
+
* ──────────
|
|
6
|
+
* v18 Ancrage stable par data-pid / data-index.
|
|
7
|
+
* v20 Table KIND. IO fixe. Fix TCF locator.
|
|
10
8
|
* v25 Fix scroll-up / virtualisation NodeBB.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* Toujours désactivé pour posts (virtualisation → faux-orphelins).
|
|
17
|
-
* v35 initPools protégé (S.poolsReady). muteConsole élargi.
|
|
18
|
-
* v36 S.wrapByKey Map O(1). wrapIsLive allégé. MutationObserver optimisé.
|
|
19
|
-
* v38 ez.refresh() interdit → supprimé. Pool épuisé → break propre.
|
|
20
|
-
* v40 Recyclage via destroyPlaceholders+define+displayMore (délais 300ms).
|
|
9
|
+
* v28 Wraps persistants pendant la session.
|
|
10
|
+
* v35 S.poolsReady. muteConsole élargi.
|
|
11
|
+
* v36 S.wrapByKey Map O(1). MutationObserver optimisé.
|
|
12
|
+
* v38 ez.refresh() supprimé. Pool épuisé → break propre.
|
|
13
|
+
* v40 Recyclage destroyPlaceholders+define+displayMore (délais 300ms).
|
|
21
14
|
* v43 Seuil recyclage -1vh + unobserve avant déplacement.
|
|
22
|
-
* v49 Fix normalizeExcludedGroups
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
15
|
+
* v49 Fix normalizeExcludedGroups (JSON.parse tableau NodeBB).
|
|
16
|
+
* v51 fetchConfig backoff 10s. IO recrée au resize. tcfObs dans S.
|
|
17
|
+
* v52 pruneOrphansBetween supprimé (NodeBB virtualise aussi les topics).
|
|
18
|
+
* v53 S.recycling garde double-recyclage. pickId early-exit. cleanup complet.
|
|
19
|
+
* v54 ensureTcfLocator rappelé à chaque ajaxify.end.
|
|
20
|
+
* v56 scheduleEmptyCheck / is-empty supprimés (collapse prématuré).
|
|
21
|
+
* v57 Nettoyage prod : A_SHOWN/A_CREATED supprimés, normBool Set O(1),
|
|
22
|
+
* muteConsole étend aux erreurs CMP getTCData, code nettoyé.
|
|
28
23
|
*/
|
|
29
24
|
(function nbbEzoicInfinite() {
|
|
30
25
|
'use strict';
|
|
31
26
|
|
|
32
27
|
// ── Constantes ─────────────────────────────────────────────────────────────
|
|
33
28
|
|
|
34
|
-
const WRAP_CLASS
|
|
35
|
-
const PH_PREFIX
|
|
36
|
-
const A_ANCHOR
|
|
37
|
-
const A_WRAPID
|
|
38
|
-
const A_CREATED = 'data-ezoic-created'; // timestamp création ms
|
|
39
|
-
const A_SHOWN = 'data-ezoic-shown'; // timestamp dernier showAds ms
|
|
29
|
+
const WRAP_CLASS = 'nodebb-ezoic-wrap';
|
|
30
|
+
const PH_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
31
|
+
const A_ANCHOR = 'data-ezoic-anchor'; // "kindClass:stableId"
|
|
32
|
+
const A_WRAPID = 'data-ezoic-wrapid'; // id Ezoic
|
|
40
33
|
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const BURST_COOLDOWN_MS = 200; // délai min entre deux déclenchements de burst
|
|
34
|
+
const MAX_INSERTS_RUN = 6; // insertions max par appel runCore
|
|
35
|
+
const MAX_INFLIGHT = 4; // showAds() simultanés max
|
|
36
|
+
const SHOW_THROTTLE_MS = 900; // anti-spam showAds() par id
|
|
37
|
+
const BURST_COOLDOWN_MS = 200; // délai min entre deux bursts
|
|
46
38
|
|
|
47
|
-
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
48
39
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
49
40
|
const IO_MARGIN_MOBILE = '3500px 0px 3500px 0px';
|
|
50
41
|
|
|
@@ -56,13 +47,10 @@
|
|
|
56
47
|
|
|
57
48
|
/**
|
|
58
49
|
* Table KIND — source de vérité par kindClass.
|
|
59
|
-
*
|
|
60
|
-
* sel sélecteur CSS complet des éléments cibles
|
|
50
|
+
* sel sélecteur CSS des éléments cibles
|
|
61
51
|
* baseTag préfixe tag pour querySelector d'ancre
|
|
62
|
-
* (vide pour posts : le sélecteur commence par '[')
|
|
63
52
|
* anchorAttr attribut DOM stable → clé unique du wrap
|
|
64
|
-
* ordinalAttr attribut 0-based pour le calcul de l'intervalle
|
|
65
|
-
* null → fallback positionnel (catégories)
|
|
53
|
+
* ordinalAttr attribut 0-based pour le calcul de l'intervalle (null = fallback positionnel)
|
|
66
54
|
*/
|
|
67
55
|
const KIND = {
|
|
68
56
|
'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index' },
|
|
@@ -73,36 +61,39 @@
|
|
|
73
61
|
// ── État global ────────────────────────────────────────────────────────────
|
|
74
62
|
|
|
75
63
|
const S = {
|
|
76
|
-
pageKey:
|
|
77
|
-
cfg:
|
|
78
|
-
poolsReady:
|
|
79
|
-
pools:
|
|
80
|
-
cursors:
|
|
81
|
-
mountedIds:
|
|
82
|
-
lastShow:
|
|
83
|
-
io:
|
|
84
|
-
domObs:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
runQueued:
|
|
93
|
-
burstActive:
|
|
64
|
+
pageKey: null,
|
|
65
|
+
cfg: null,
|
|
66
|
+
poolsReady: false,
|
|
67
|
+
pools: { topics: [], posts: [], categories: [] },
|
|
68
|
+
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
69
|
+
mountedIds: new Set(),
|
|
70
|
+
lastShow: new Map(),
|
|
71
|
+
io: null,
|
|
72
|
+
domObs: null,
|
|
73
|
+
tcfObs: null,
|
|
74
|
+
mutGuard: 0,
|
|
75
|
+
inflight: 0,
|
|
76
|
+
pending: [],
|
|
77
|
+
pendingSet: new Set(),
|
|
78
|
+
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
79
|
+
recycling: new Set(), // ids en cours de séquence destroy→define→displayMore
|
|
80
|
+
runQueued: false,
|
|
81
|
+
burstActive: false,
|
|
94
82
|
burstDeadline: 0,
|
|
95
|
-
burstCount:
|
|
96
|
-
lastBurstTs:
|
|
83
|
+
burstCount: 0,
|
|
84
|
+
lastBurstTs: 0,
|
|
97
85
|
};
|
|
98
86
|
|
|
99
|
-
let blockedUntil
|
|
87
|
+
let blockedUntil = 0;
|
|
88
|
+
let _cfgErrorUntil = 0;
|
|
89
|
+
let _ioMobile = null;
|
|
100
90
|
|
|
101
|
-
const ts
|
|
102
|
-
const isBlocked
|
|
103
|
-
const isMobile
|
|
104
|
-
const
|
|
105
|
-
const
|
|
91
|
+
const ts = () => Date.now();
|
|
92
|
+
const isBlocked = () => ts() < blockedUntil;
|
|
93
|
+
const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
|
|
94
|
+
const _BOOL_TRUE = new Set([true, 'true', 1, '1', 'on']);
|
|
95
|
+
const normBool = v => _BOOL_TRUE.has(v);
|
|
96
|
+
const isFilled = n => !!(n?.querySelector('iframe, ins, img, video, [data-google-container-id]'));
|
|
106
97
|
|
|
107
98
|
function mutate(fn) {
|
|
108
99
|
S.mutGuard++;
|
|
@@ -111,9 +102,6 @@
|
|
|
111
102
|
|
|
112
103
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
113
104
|
|
|
114
|
-
// Fix #2 : backoff 10s sur échec pour éviter de spammer l'API
|
|
115
|
-
// Fix #3 : _cfgErrorUntil au scope IIFE pour être réinitialisé dans cleanup()
|
|
116
|
-
let _cfgErrorUntil = 0;
|
|
117
105
|
async function fetchConfig() {
|
|
118
106
|
if (S.cfg) return S.cfg;
|
|
119
107
|
if (Date.now() < _cfgErrorUntil) return null;
|
|
@@ -181,53 +169,37 @@
|
|
|
181
169
|
|
|
182
170
|
// ── Wraps — détection ──────────────────────────────────────────────────────
|
|
183
171
|
|
|
184
|
-
/**
|
|
185
|
-
* Vérifie qu'un wrap a encore son ancre dans le DOM.
|
|
186
|
-
* Utilisé par adjacentWrap pour ignorer les wraps orphelins.
|
|
187
|
-
*/
|
|
188
172
|
function wrapIsLive(wrap) {
|
|
189
173
|
if (!wrap?.classList?.contains(WRAP_CLASS)) return false;
|
|
190
174
|
const key = wrap.getAttribute(A_ANCHOR);
|
|
191
175
|
if (!key) return false;
|
|
192
|
-
// Lookup O(1) dans le registre — vrai si le wrap EST encore dans le registre
|
|
193
|
-
// et connecté au DOM (le registre est tenu à jour par insertAfter/dropWrap).
|
|
194
176
|
if (S.wrapByKey.get(key) === wrap) return wrap.isConnected;
|
|
195
|
-
// Fallback : registre pas encore à jour ou wrap non enregistré.
|
|
196
177
|
const colonIdx = key.indexOf(':');
|
|
197
178
|
const klass = key.slice(0, colonIdx);
|
|
198
179
|
const anchorId = key.slice(colonIdx + 1);
|
|
199
180
|
const cfg = KIND[klass];
|
|
200
181
|
if (!cfg) return false;
|
|
201
|
-
// Optimisation : si l'ancre est un frère direct du wrap, pas besoin
|
|
202
|
-
// de querySelector global — on cherche parmi les voisins immédiats.
|
|
203
182
|
const parent = wrap.parentElement;
|
|
204
183
|
if (parent) {
|
|
205
184
|
for (const sib of parent.children) {
|
|
206
185
|
if (sib === wrap) continue;
|
|
207
186
|
try {
|
|
208
|
-
if (sib.matches(`${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`))
|
|
187
|
+
if (sib.matches(`${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`))
|
|
209
188
|
return sib.isConnected;
|
|
210
|
-
}
|
|
211
189
|
} catch (_) {}
|
|
212
190
|
}
|
|
213
191
|
}
|
|
214
|
-
// Dernier recours : querySelector global
|
|
215
192
|
try {
|
|
216
193
|
const found = document.querySelector(`${cfg.sel}[${cfg.anchorAttr}="${anchorId}"]`);
|
|
217
194
|
return !!(found?.isConnected);
|
|
218
195
|
} catch (_) { return false; }
|
|
219
196
|
}
|
|
220
197
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
198
|
+
const adjacentWrap = el =>
|
|
199
|
+
wrapIsLive(el.nextElementSibling) || wrapIsLive(el.previousElementSibling);
|
|
224
200
|
|
|
225
201
|
// ── Ancres stables ─────────────────────────────────────────────────────────
|
|
226
202
|
|
|
227
|
-
/**
|
|
228
|
-
* Retourne la valeur de l'attribut stable pour cet élément,
|
|
229
|
-
* ou un fallback positionnel si l'attribut est absent.
|
|
230
|
-
*/
|
|
231
203
|
function stableId(klass, el) {
|
|
232
204
|
const attr = KIND[klass]?.anchorAttr;
|
|
233
205
|
if (attr) {
|
|
@@ -246,21 +218,15 @@
|
|
|
246
218
|
|
|
247
219
|
function findWrap(key) {
|
|
248
220
|
const w = S.wrapByKey.get(key);
|
|
249
|
-
return
|
|
221
|
+
return w?.isConnected ? w : null;
|
|
250
222
|
}
|
|
251
223
|
|
|
252
224
|
// ── Pool ───────────────────────────────────────────────────────────────────
|
|
253
225
|
|
|
254
|
-
/**
|
|
255
|
-
* Retourne le prochain id disponible dans le pool (round-robin),
|
|
256
|
-
* ou null si tous les ids sont montés.
|
|
257
|
-
*/
|
|
258
226
|
function pickId(poolKey) {
|
|
259
227
|
const pool = S.pools[poolKey];
|
|
260
228
|
if (!pool.length) return null;
|
|
261
|
-
|
|
262
|
-
if (S.mountedIds.size >= pool.length &&
|
|
263
|
-
pool.every(id => S.mountedIds.has(id))) return null;
|
|
229
|
+
if (S.mountedIds.size >= pool.length && pool.every(id => S.mountedIds.has(id))) return null;
|
|
264
230
|
for (let t = 0; t < pool.length; t++) {
|
|
265
231
|
const i = S.cursors[poolKey] % pool.length;
|
|
266
232
|
S.cursors[poolKey] = (S.cursors[poolKey] + 1) % pool.length;
|
|
@@ -272,9 +238,7 @@
|
|
|
272
238
|
|
|
273
239
|
/**
|
|
274
240
|
* Pool épuisé : recycle un wrap loin au-dessus du viewport.
|
|
275
|
-
* Séquence
|
|
276
|
-
* destroy([id]) → 300ms → define([id]) → 300ms → displayMore([id])
|
|
277
|
-
* displayMore = API Ezoic prévue pour l'infinite scroll.
|
|
241
|
+
* Séquence : destroy([id]) → 300ms → define([id]) → 300ms → displayMore([id])
|
|
278
242
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
279
243
|
*/
|
|
280
244
|
function recycleAndMove(klass, targetEl, newKey) {
|
|
@@ -283,10 +247,7 @@
|
|
|
283
247
|
typeof ez?.define !== 'function' ||
|
|
284
248
|
typeof ez?.displayMore !== 'function') return null;
|
|
285
249
|
|
|
286
|
-
const
|
|
287
|
-
// Seuil : -1vh (hors viewport visible). On appelle unobserve(ph) juste
|
|
288
|
-
// après pour neutraliser l'IO — plus de showAds parasite possible.
|
|
289
|
-
const threshold = -vh;
|
|
250
|
+
const threshold = -(window.innerHeight || 800);
|
|
290
251
|
let bestEmpty = null, bestEmptyBottom = Infinity;
|
|
291
252
|
let bestFilled = null, bestFilledBottom = Infinity;
|
|
292
253
|
|
|
@@ -306,19 +267,13 @@
|
|
|
306
267
|
if (!best) return null;
|
|
307
268
|
const id = parseInt(best.getAttribute(A_WRAPID), 10);
|
|
308
269
|
if (!Number.isFinite(id)) return null;
|
|
309
|
-
// Fix #1 : éviter de recycler un slot dont la séquence Ezoic est en cours
|
|
310
270
|
if (S.recycling.has(id)) return null;
|
|
311
271
|
S.recycling.add(id);
|
|
312
272
|
|
|
313
273
|
const oldKey = best.getAttribute(A_ANCHOR);
|
|
314
|
-
// Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
|
|
315
|
-
// parasite si le nœud était encore dans la zone IO_MARGIN.
|
|
316
274
|
try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
|
|
317
275
|
mutate(() => {
|
|
318
|
-
best.setAttribute(A_ANCHOR,
|
|
319
|
-
best.setAttribute(A_CREATED, String(ts()));
|
|
320
|
-
best.setAttribute(A_SHOWN, '0');
|
|
321
|
-
best.classList.remove('is-empty');
|
|
276
|
+
best.setAttribute(A_ANCHOR, newKey);
|
|
322
277
|
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
323
278
|
if (ph) ph.innerHTML = '';
|
|
324
279
|
targetEl.insertAdjacentElement('afterend', best);
|
|
@@ -326,20 +281,14 @@
|
|
|
326
281
|
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
327
282
|
S.wrapByKey.set(newKey, best);
|
|
328
283
|
|
|
329
|
-
// Délais requis : destroyPlaceholders est asynchrone en interne
|
|
330
284
|
const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
|
|
331
285
|
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
332
|
-
// Fix #4 : re-observer le ph après displayMore pour déclencher scheduleEmptyCheck
|
|
333
|
-
// si la pub ne charge pas (détection wrap vide).
|
|
334
286
|
const doDisplay = () => {
|
|
335
287
|
try { ez.displayMore([id]); } catch (_) {}
|
|
336
|
-
S.recycling.delete(id);
|
|
288
|
+
S.recycling.delete(id);
|
|
337
289
|
observePh(id);
|
|
338
|
-
const t = ts();
|
|
339
|
-
try { best.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
340
|
-
scheduleEmptyCheck(id, t);
|
|
341
290
|
};
|
|
342
|
-
try {
|
|
291
|
+
try { typeof ez.cmd?.push === 'function' ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
343
292
|
|
|
344
293
|
return { id, wrap: best };
|
|
345
294
|
}
|
|
@@ -349,10 +298,8 @@
|
|
|
349
298
|
function makeWrap(id, klass, key) {
|
|
350
299
|
const w = document.createElement('div');
|
|
351
300
|
w.className = `${WRAP_CLASS} ${klass}`;
|
|
352
|
-
w.setAttribute(A_ANCHOR,
|
|
353
|
-
w.setAttribute(A_WRAPID,
|
|
354
|
-
w.setAttribute(A_CREATED, String(ts()));
|
|
355
|
-
w.setAttribute(A_SHOWN, '0');
|
|
301
|
+
w.setAttribute(A_ANCHOR, key);
|
|
302
|
+
w.setAttribute(A_WRAPID, String(id));
|
|
356
303
|
w.style.cssText = 'width:100%;display:block;';
|
|
357
304
|
const ph = document.createElement('div');
|
|
358
305
|
ph.id = `${PH_PREFIX}${id}`;
|
|
@@ -385,13 +332,8 @@
|
|
|
385
332
|
} catch (_) {}
|
|
386
333
|
}
|
|
387
334
|
|
|
388
|
-
|
|
389
335
|
// ── Injection ──────────────────────────────────────────────────────────────
|
|
390
336
|
|
|
391
|
-
/**
|
|
392
|
-
* Ordinal 0-based pour le calcul de l'intervalle d'injection.
|
|
393
|
-
* Utilise ordinalAttr si défini, sinon compte les frères dans le parent.
|
|
394
|
-
*/
|
|
395
337
|
function ordinal(klass, el) {
|
|
396
338
|
const attr = KIND[klass]?.ordinalAttr;
|
|
397
339
|
if (attr) {
|
|
@@ -410,18 +352,14 @@
|
|
|
410
352
|
function injectBetween(klass, items, interval, showFirst, poolKey) {
|
|
411
353
|
if (!items.length) return 0;
|
|
412
354
|
let inserted = 0;
|
|
413
|
-
|
|
414
355
|
for (const el of items) {
|
|
415
356
|
if (inserted >= MAX_INSERTS_RUN) break;
|
|
416
357
|
if (!el?.isConnected) continue;
|
|
417
|
-
|
|
418
358
|
const ord = ordinal(klass, el);
|
|
419
359
|
if (!(showFirst && ord === 0) && (ord + 1) % interval !== 0) continue;
|
|
420
360
|
if (adjacentWrap(el)) continue;
|
|
421
|
-
|
|
422
361
|
const key = anchorKey(klass, el);
|
|
423
362
|
if (findWrap(key)) continue;
|
|
424
|
-
|
|
425
363
|
const id = pickId(poolKey);
|
|
426
364
|
if (id) {
|
|
427
365
|
const w = insertAfter(el, id, klass, key);
|
|
@@ -437,13 +375,9 @@
|
|
|
437
375
|
|
|
438
376
|
// ── IntersectionObserver & Show ────────────────────────────────────────────
|
|
439
377
|
|
|
440
|
-
// Fix #6 : recréer l'observer si le type d'écran change (rotation, resize).
|
|
441
|
-
// isMobile() est évalué à chaque appel pour détecter un changement.
|
|
442
|
-
let _ioMobile = null; // dernier état mobile/desktop pour lequel l'IO a été créé
|
|
443
378
|
function getIO() {
|
|
444
379
|
const mobile = isMobile();
|
|
445
380
|
if (S.io && _ioMobile === mobile) return S.io;
|
|
446
|
-
// Type d'écran changé ou première création : (re)créer l'observer
|
|
447
381
|
if (S.io) { try { S.io.disconnect(); } catch (_) {} S.io = null; }
|
|
448
382
|
_ioMobile = mobile;
|
|
449
383
|
try {
|
|
@@ -494,24 +428,18 @@
|
|
|
494
428
|
drainQueue();
|
|
495
429
|
};
|
|
496
430
|
const timer = setTimeout(release, 7000);
|
|
497
|
-
|
|
498
431
|
requestAnimationFrame(() => {
|
|
499
432
|
try {
|
|
500
433
|
if (isBlocked()) { clearTimeout(timer); return release(); }
|
|
501
434
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
502
435
|
if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
|
|
503
|
-
|
|
504
436
|
const t = ts();
|
|
505
437
|
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
506
438
|
S.lastShow.set(id, t);
|
|
507
|
-
|
|
508
|
-
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
509
|
-
|
|
510
439
|
window.ezstandalone = window.ezstandalone || {};
|
|
511
440
|
const ez = window.ezstandalone;
|
|
512
441
|
const doShow = () => {
|
|
513
442
|
try { ez.showAds(id); } catch (_) {}
|
|
514
|
-
scheduleEmptyCheck(id, t);
|
|
515
443
|
setTimeout(() => { clearTimeout(timer); release(); }, 700);
|
|
516
444
|
};
|
|
517
445
|
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
@@ -519,26 +447,10 @@
|
|
|
519
447
|
});
|
|
520
448
|
}
|
|
521
449
|
|
|
522
|
-
function scheduleEmptyCheck(id, showTs) {
|
|
523
|
-
setTimeout(() => {
|
|
524
|
-
try {
|
|
525
|
-
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
526
|
-
const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
|
|
527
|
-
if (!wrap || !ph?.isConnected) return;
|
|
528
|
-
if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
|
|
529
|
-
// is-empty désactivé pour ezoic-ad-between : Ezoic peut prendre >20s
|
|
530
|
-
// à charger sur liste de catégorie — collapse prématuré des wraps.
|
|
531
|
-
if (wrap.classList.contains('ezoic-ad-between')) return;
|
|
532
|
-
wrap.classList.toggle('is-empty', !isFilled(ph));
|
|
533
|
-
} catch (_) {}
|
|
534
|
-
}, EMPTY_CHECK_MS);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
450
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
538
451
|
//
|
|
539
|
-
// Intercepte ez.showAds() pour
|
|
540
|
-
//
|
|
541
|
-
// – filtrer les ids dont le placeholder n'est pas en DOM
|
|
452
|
+
// Intercepte ez.showAds() pour ignorer les appels pendant blockedUntil
|
|
453
|
+
// et filtrer les ids dont le placeholder n'est pas connecté au DOM.
|
|
542
454
|
|
|
543
455
|
function patchShowAds() {
|
|
544
456
|
const apply = () => {
|
|
@@ -574,36 +486,19 @@
|
|
|
574
486
|
async function runCore() {
|
|
575
487
|
if (isBlocked()) return 0;
|
|
576
488
|
patchShowAds();
|
|
577
|
-
|
|
578
489
|
const cfg = await fetchConfig();
|
|
579
490
|
if (!cfg || cfg.excluded) return 0;
|
|
580
491
|
initPools(cfg);
|
|
581
|
-
|
|
582
492
|
const kind = getKind();
|
|
583
493
|
if (kind === 'other') return 0;
|
|
584
|
-
|
|
585
494
|
const exec = (klass, getItems, cfgEnable, cfgInterval, cfgShowFirst, poolKey) => {
|
|
586
495
|
if (!normBool(cfgEnable)) return 0;
|
|
587
496
|
const interval = Math.max(1, parseInt(cfgInterval, 10) || 3);
|
|
588
497
|
return injectBetween(klass, getItems(), interval, normBool(cfgShowFirst), poolKey);
|
|
589
498
|
};
|
|
590
|
-
|
|
591
|
-
if (kind === '
|
|
592
|
-
|
|
593
|
-
cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts'
|
|
594
|
-
);
|
|
595
|
-
|
|
596
|
-
if (kind === 'categoryTopics') {
|
|
597
|
-
return exec(
|
|
598
|
-
'ezoic-ad-between', getTopics,
|
|
599
|
-
cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics'
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
return exec(
|
|
604
|
-
'ezoic-ad-categories', getCategories,
|
|
605
|
-
cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories'
|
|
606
|
-
);
|
|
499
|
+
if (kind === 'topic') return exec('ezoic-ad-message', getPosts, cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts');
|
|
500
|
+
if (kind === 'categoryTopics') return exec('ezoic-ad-between', getTopics, cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics');
|
|
501
|
+
return exec('ezoic-ad-categories', getCategories, cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories');
|
|
607
502
|
}
|
|
608
503
|
|
|
609
504
|
// ── Scheduler ──────────────────────────────────────────────────────────────
|
|
@@ -627,11 +522,9 @@
|
|
|
627
522
|
S.lastBurstTs = t;
|
|
628
523
|
S.pageKey = pageKey();
|
|
629
524
|
S.burstDeadline = t + 2000;
|
|
630
|
-
|
|
631
525
|
if (S.burstActive) return;
|
|
632
526
|
S.burstActive = true;
|
|
633
527
|
S.burstCount = 0;
|
|
634
|
-
|
|
635
528
|
const step = () => {
|
|
636
529
|
if (pageKey() !== S.pageKey || isBlocked() || ts() > S.burstDeadline || S.burstCount >= 8) {
|
|
637
530
|
S.burstActive = false; return;
|
|
@@ -650,25 +543,25 @@
|
|
|
650
543
|
function cleanup() {
|
|
651
544
|
blockedUntil = ts() + 1500;
|
|
652
545
|
mutate(() => document.querySelectorAll(`.${WRAP_CLASS}`).forEach(dropWrap));
|
|
653
|
-
S.cfg
|
|
654
|
-
_cfgErrorUntil = 0;
|
|
655
|
-
S.poolsReady
|
|
656
|
-
S.pools
|
|
657
|
-
S.cursors
|
|
546
|
+
S.cfg = null;
|
|
547
|
+
_cfgErrorUntil = 0;
|
|
548
|
+
S.poolsReady = false;
|
|
549
|
+
S.pools = { topics: [], posts: [], categories: [] };
|
|
550
|
+
S.cursors = { topics: 0, posts: 0, categories: 0 };
|
|
658
551
|
S.mountedIds.clear();
|
|
659
552
|
S.lastShow.clear();
|
|
660
553
|
S.wrapByKey.clear();
|
|
661
|
-
S.inflight = 0;
|
|
662
|
-
S.pending = [];
|
|
663
|
-
S.pendingSet.clear();
|
|
664
|
-
S.burstActive = false;
|
|
665
|
-
S.runQueued = false;
|
|
666
554
|
S.recycling.clear();
|
|
555
|
+
S.inflight = 0;
|
|
556
|
+
S.pending = [];
|
|
557
|
+
S.pendingSet.clear();
|
|
558
|
+
S.burstActive = false;
|
|
559
|
+
S.runQueued = false;
|
|
667
560
|
if (S.domObs) { S.domObs.disconnect(); S.domObs = null; }
|
|
668
561
|
if (S.tcfObs) { S.tcfObs.disconnect(); S.tcfObs = null; }
|
|
669
562
|
}
|
|
670
563
|
|
|
671
|
-
// ── MutationObserver
|
|
564
|
+
// ── MutationObserver DOM ───────────────────────────────────────────────────
|
|
672
565
|
|
|
673
566
|
function ensureDomObserver() {
|
|
674
567
|
if (S.domObs) return;
|
|
@@ -678,9 +571,8 @@
|
|
|
678
571
|
for (const m of muts) {
|
|
679
572
|
for (const n of m.addedNodes) {
|
|
680
573
|
if (n.nodeType !== 1) continue;
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
|
|
574
|
+
if (allSel.some(s => { try { return n.matches(s); } catch (_) { return false; } }) ||
|
|
575
|
+
allSel.some(s => { try { return !!n.querySelector(s); } catch (_) { return false; } })) {
|
|
684
576
|
requestBurst(); return;
|
|
685
577
|
}
|
|
686
578
|
}
|
|
@@ -700,6 +592,7 @@
|
|
|
700
592
|
'cannot call refresh on the same page',
|
|
701
593
|
'no placeholders are currently defined in Refresh',
|
|
702
594
|
'Debugger iframe already exists',
|
|
595
|
+
'[CMP] Error in custom getTCData',
|
|
703
596
|
`with id ${PH_PREFIX}`,
|
|
704
597
|
];
|
|
705
598
|
for (const m of ['log', 'info', 'warn', 'error']) {
|
|
@@ -722,7 +615,6 @@
|
|
|
722
615
|
(document.body || document.documentElement).appendChild(f);
|
|
723
616
|
};
|
|
724
617
|
inject();
|
|
725
|
-
// Fix #5 : ref stockée dans S pour pouvoir déconnecter au cleanup
|
|
726
618
|
if (!S.tcfObs) {
|
|
727
619
|
S.tcfObs = new MutationObserver(inject);
|
|
728
620
|
S.tcfObs.observe(document.documentElement, { childList: true, subtree: true });
|
|
@@ -757,25 +649,19 @@
|
|
|
757
649
|
function bindNodeBB() {
|
|
758
650
|
const $ = window.jQuery;
|
|
759
651
|
if (!$) return;
|
|
760
|
-
|
|
761
652
|
$(window).off('.nbbEzoic');
|
|
762
653
|
$(window).on('action:ajaxify.start.nbbEzoic', cleanup);
|
|
763
654
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
764
655
|
S.pageKey = pageKey();
|
|
765
656
|
blockedUntil = 0;
|
|
766
|
-
// muteConsole et warmNetwork sont idempotents — appelés au boot seulement.
|
|
767
|
-
// ensureTcfLocator doit être rappelé : ajaxify retire l'iframe __tcfapiLocator
|
|
768
|
-
// du DOM à chaque navigation, le CMP lève une erreur postMessage sans elle.
|
|
769
657
|
muteConsole(); ensureTcfLocator();
|
|
770
658
|
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
771
659
|
});
|
|
772
|
-
|
|
773
660
|
const burstEvts = [
|
|
774
|
-
'action:ajaxify.contentLoaded', 'action:posts.loaded',
|
|
775
|
-
'action:categories.loaded',
|
|
661
|
+
'action:ajaxify.contentLoaded', 'action:posts.loaded', 'action:topics.loaded',
|
|
662
|
+
'action:categories.loaded', 'action:category.loaded', 'action:topic.loaded',
|
|
776
663
|
].map(e => `${e}.nbbEzoic`).join(' ');
|
|
777
664
|
$(window).on(burstEvts, () => { if (!isBlocked()) requestBurst(); });
|
|
778
|
-
|
|
779
665
|
try {
|
|
780
666
|
require(['hooks'], hooks => {
|
|
781
667
|
if (typeof hooks?.on !== 'function') return;
|
|
@@ -794,11 +680,10 @@
|
|
|
794
680
|
ticking = true;
|
|
795
681
|
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
796
682
|
}, { passive: true });
|
|
797
|
-
// Fix #6 : détecter rotation/resize pour recréer l'IO avec la bonne marge
|
|
798
683
|
let resizeTimer = 0;
|
|
799
684
|
window.addEventListener('resize', () => {
|
|
800
685
|
clearTimeout(resizeTimer);
|
|
801
|
-
resizeTimer = setTimeout(
|
|
686
|
+
resizeTimer = setTimeout(getIO, 500);
|
|
802
687
|
}, { passive: true });
|
|
803
688
|
}
|
|
804
689
|
|