nodebb-plugin-ezoic-infinite 1.7.50 → 1.7.52
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 +98 -187
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,49 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — client.js v58
|
|
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
|
+
* v58 tcfObs survit aux navigations : ne plus déconnecter dans cleanup().
|
|
22
|
+
* L'iframe __tcfapiLocator doit exister en permanence pour le CMP —
|
|
23
|
+
* la fenêtre entre cleanup() et ajaxify.end causait des erreurs
|
|
24
|
+
* "Cannot read properties of null (postMessage)" et disparition des pubs.
|
|
25
|
+
* v57 Nettoyage prod : A_SHOWN/A_CREATED supprimés, normBool Set O(1),
|
|
26
|
+
* muteConsole étend aux erreurs CMP getTCData, code nettoyé.
|
|
28
27
|
*/
|
|
29
28
|
(function nbbEzoicInfinite() {
|
|
30
29
|
'use strict';
|
|
31
30
|
|
|
32
31
|
// ── Constantes ─────────────────────────────────────────────────────────────
|
|
33
32
|
|
|
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
|
|
33
|
+
const WRAP_CLASS = 'nodebb-ezoic-wrap';
|
|
34
|
+
const PH_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
35
|
+
const A_ANCHOR = 'data-ezoic-anchor'; // "kindClass:stableId"
|
|
36
|
+
const A_WRAPID = 'data-ezoic-wrapid'; // id Ezoic
|
|
40
37
|
|
|
41
|
-
const MAX_INSERTS_RUN = 6;
|
|
42
|
-
const MAX_INFLIGHT = 4;
|
|
43
|
-
const SHOW_THROTTLE_MS = 900;
|
|
44
|
-
const BURST_COOLDOWN_MS = 200;
|
|
38
|
+
const MAX_INSERTS_RUN = 6; // insertions max par appel runCore
|
|
39
|
+
const MAX_INFLIGHT = 4; // showAds() simultanés max
|
|
40
|
+
const SHOW_THROTTLE_MS = 900; // anti-spam showAds() par id
|
|
41
|
+
const BURST_COOLDOWN_MS = 200; // délai min entre deux bursts
|
|
45
42
|
|
|
46
|
-
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
47
43
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
48
44
|
const IO_MARGIN_MOBILE = '3500px 0px 3500px 0px';
|
|
49
45
|
|
|
@@ -55,13 +51,10 @@
|
|
|
55
51
|
|
|
56
52
|
/**
|
|
57
53
|
* Table KIND — source de vérité par kindClass.
|
|
58
|
-
*
|
|
59
|
-
* sel sélecteur CSS complet des éléments cibles
|
|
54
|
+
* sel sélecteur CSS des éléments cibles
|
|
60
55
|
* baseTag préfixe tag pour querySelector d'ancre
|
|
61
|
-
* (vide pour posts : le sélecteur commence par '[')
|
|
62
56
|
* anchorAttr attribut DOM stable → clé unique du wrap
|
|
63
|
-
* ordinalAttr attribut 0-based pour le calcul de l'intervalle
|
|
64
|
-
* null → fallback positionnel (catégories)
|
|
57
|
+
* ordinalAttr attribut 0-based pour le calcul de l'intervalle (null = fallback positionnel)
|
|
65
58
|
*/
|
|
66
59
|
const KIND = {
|
|
67
60
|
'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index' },
|
|
@@ -72,36 +65,39 @@
|
|
|
72
65
|
// ── État global ────────────────────────────────────────────────────────────
|
|
73
66
|
|
|
74
67
|
const S = {
|
|
75
|
-
pageKey:
|
|
76
|
-
cfg:
|
|
77
|
-
poolsReady:
|
|
78
|
-
pools:
|
|
79
|
-
cursors:
|
|
80
|
-
mountedIds:
|
|
81
|
-
lastShow:
|
|
82
|
-
io:
|
|
83
|
-
domObs:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
runQueued:
|
|
92
|
-
burstActive:
|
|
68
|
+
pageKey: null,
|
|
69
|
+
cfg: null,
|
|
70
|
+
poolsReady: false,
|
|
71
|
+
pools: { topics: [], posts: [], categories: [] },
|
|
72
|
+
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
73
|
+
mountedIds: new Set(),
|
|
74
|
+
lastShow: new Map(),
|
|
75
|
+
io: null,
|
|
76
|
+
domObs: null,
|
|
77
|
+
tcfObs: null,
|
|
78
|
+
mutGuard: 0,
|
|
79
|
+
inflight: 0,
|
|
80
|
+
pending: [],
|
|
81
|
+
pendingSet: new Set(),
|
|
82
|
+
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
83
|
+
recycling: new Set(), // ids en cours de séquence destroy→define→displayMore
|
|
84
|
+
runQueued: false,
|
|
85
|
+
burstActive: false,
|
|
93
86
|
burstDeadline: 0,
|
|
94
|
-
burstCount:
|
|
95
|
-
lastBurstTs:
|
|
87
|
+
burstCount: 0,
|
|
88
|
+
lastBurstTs: 0,
|
|
96
89
|
};
|
|
97
90
|
|
|
98
|
-
let blockedUntil
|
|
91
|
+
let blockedUntil = 0;
|
|
92
|
+
let _cfgErrorUntil = 0;
|
|
93
|
+
let _ioMobile = null;
|
|
99
94
|
|
|
100
|
-
const ts
|
|
101
|
-
const isBlocked
|
|
102
|
-
const isMobile
|
|
103
|
-
const
|
|
104
|
-
const
|
|
95
|
+
const ts = () => Date.now();
|
|
96
|
+
const isBlocked = () => ts() < blockedUntil;
|
|
97
|
+
const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
|
|
98
|
+
const _BOOL_TRUE = new Set([true, 'true', 1, '1', 'on']);
|
|
99
|
+
const normBool = v => _BOOL_TRUE.has(v);
|
|
100
|
+
const isFilled = n => !!(n?.querySelector('iframe, ins, img, video, [data-google-container-id]'));
|
|
105
101
|
|
|
106
102
|
function mutate(fn) {
|
|
107
103
|
S.mutGuard++;
|
|
@@ -110,9 +106,6 @@
|
|
|
110
106
|
|
|
111
107
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
112
108
|
|
|
113
|
-
// Fix #2 : backoff 10s sur échec pour éviter de spammer l'API
|
|
114
|
-
// Fix #3 : _cfgErrorUntil au scope IIFE pour être réinitialisé dans cleanup()
|
|
115
|
-
let _cfgErrorUntil = 0;
|
|
116
109
|
async function fetchConfig() {
|
|
117
110
|
if (S.cfg) return S.cfg;
|
|
118
111
|
if (Date.now() < _cfgErrorUntil) return null;
|
|
@@ -180,53 +173,37 @@
|
|
|
180
173
|
|
|
181
174
|
// ── Wraps — détection ──────────────────────────────────────────────────────
|
|
182
175
|
|
|
183
|
-
/**
|
|
184
|
-
* Vérifie qu'un wrap a encore son ancre dans le DOM.
|
|
185
|
-
* Utilisé par adjacentWrap pour ignorer les wraps orphelins.
|
|
186
|
-
*/
|
|
187
176
|
function wrapIsLive(wrap) {
|
|
188
177
|
if (!wrap?.classList?.contains(WRAP_CLASS)) return false;
|
|
189
178
|
const key = wrap.getAttribute(A_ANCHOR);
|
|
190
179
|
if (!key) return false;
|
|
191
|
-
// Lookup O(1) dans le registre — vrai si le wrap EST encore dans le registre
|
|
192
|
-
// et connecté au DOM (le registre est tenu à jour par insertAfter/dropWrap).
|
|
193
180
|
if (S.wrapByKey.get(key) === wrap) return wrap.isConnected;
|
|
194
|
-
// Fallback : registre pas encore à jour ou wrap non enregistré.
|
|
195
181
|
const colonIdx = key.indexOf(':');
|
|
196
182
|
const klass = key.slice(0, colonIdx);
|
|
197
183
|
const anchorId = key.slice(colonIdx + 1);
|
|
198
184
|
const cfg = KIND[klass];
|
|
199
185
|
if (!cfg) return false;
|
|
200
|
-
// Optimisation : si l'ancre est un frère direct du wrap, pas besoin
|
|
201
|
-
// de querySelector global — on cherche parmi les voisins immédiats.
|
|
202
186
|
const parent = wrap.parentElement;
|
|
203
187
|
if (parent) {
|
|
204
188
|
for (const sib of parent.children) {
|
|
205
189
|
if (sib === wrap) continue;
|
|
206
190
|
try {
|
|
207
|
-
if (sib.matches(`${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`))
|
|
191
|
+
if (sib.matches(`${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`))
|
|
208
192
|
return sib.isConnected;
|
|
209
|
-
}
|
|
210
193
|
} catch (_) {}
|
|
211
194
|
}
|
|
212
195
|
}
|
|
213
|
-
// Dernier recours : querySelector global
|
|
214
196
|
try {
|
|
215
197
|
const found = document.querySelector(`${cfg.sel}[${cfg.anchorAttr}="${anchorId}"]`);
|
|
216
198
|
return !!(found?.isConnected);
|
|
217
199
|
} catch (_) { return false; }
|
|
218
200
|
}
|
|
219
201
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
202
|
+
const adjacentWrap = el =>
|
|
203
|
+
wrapIsLive(el.nextElementSibling) || wrapIsLive(el.previousElementSibling);
|
|
223
204
|
|
|
224
205
|
// ── Ancres stables ─────────────────────────────────────────────────────────
|
|
225
206
|
|
|
226
|
-
/**
|
|
227
|
-
* Retourne la valeur de l'attribut stable pour cet élément,
|
|
228
|
-
* ou un fallback positionnel si l'attribut est absent.
|
|
229
|
-
*/
|
|
230
207
|
function stableId(klass, el) {
|
|
231
208
|
const attr = KIND[klass]?.anchorAttr;
|
|
232
209
|
if (attr) {
|
|
@@ -245,21 +222,15 @@
|
|
|
245
222
|
|
|
246
223
|
function findWrap(key) {
|
|
247
224
|
const w = S.wrapByKey.get(key);
|
|
248
|
-
return
|
|
225
|
+
return w?.isConnected ? w : null;
|
|
249
226
|
}
|
|
250
227
|
|
|
251
228
|
// ── Pool ───────────────────────────────────────────────────────────────────
|
|
252
229
|
|
|
253
|
-
/**
|
|
254
|
-
* Retourne le prochain id disponible dans le pool (round-robin),
|
|
255
|
-
* ou null si tous les ids sont montés.
|
|
256
|
-
*/
|
|
257
230
|
function pickId(poolKey) {
|
|
258
231
|
const pool = S.pools[poolKey];
|
|
259
232
|
if (!pool.length) return null;
|
|
260
|
-
|
|
261
|
-
if (S.mountedIds.size >= pool.length &&
|
|
262
|
-
pool.every(id => S.mountedIds.has(id))) return null;
|
|
233
|
+
if (S.mountedIds.size >= pool.length && pool.every(id => S.mountedIds.has(id))) return null;
|
|
263
234
|
for (let t = 0; t < pool.length; t++) {
|
|
264
235
|
const i = S.cursors[poolKey] % pool.length;
|
|
265
236
|
S.cursors[poolKey] = (S.cursors[poolKey] + 1) % pool.length;
|
|
@@ -271,9 +242,7 @@
|
|
|
271
242
|
|
|
272
243
|
/**
|
|
273
244
|
* Pool épuisé : recycle un wrap loin au-dessus du viewport.
|
|
274
|
-
* Séquence
|
|
275
|
-
* destroy([id]) → 300ms → define([id]) → 300ms → displayMore([id])
|
|
276
|
-
* displayMore = API Ezoic prévue pour l'infinite scroll.
|
|
245
|
+
* Séquence : destroy([id]) → 300ms → define([id]) → 300ms → displayMore([id])
|
|
277
246
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
278
247
|
*/
|
|
279
248
|
function recycleAndMove(klass, targetEl, newKey) {
|
|
@@ -282,10 +251,7 @@
|
|
|
282
251
|
typeof ez?.define !== 'function' ||
|
|
283
252
|
typeof ez?.displayMore !== 'function') return null;
|
|
284
253
|
|
|
285
|
-
const
|
|
286
|
-
// Seuil : -1vh (hors viewport visible). On appelle unobserve(ph) juste
|
|
287
|
-
// après pour neutraliser l'IO — plus de showAds parasite possible.
|
|
288
|
-
const threshold = -vh;
|
|
254
|
+
const threshold = -(window.innerHeight || 800);
|
|
289
255
|
let bestEmpty = null, bestEmptyBottom = Infinity;
|
|
290
256
|
let bestFilled = null, bestFilledBottom = Infinity;
|
|
291
257
|
|
|
@@ -305,19 +271,13 @@
|
|
|
305
271
|
if (!best) return null;
|
|
306
272
|
const id = parseInt(best.getAttribute(A_WRAPID), 10);
|
|
307
273
|
if (!Number.isFinite(id)) return null;
|
|
308
|
-
// Fix #1 : éviter de recycler un slot dont la séquence Ezoic est en cours
|
|
309
274
|
if (S.recycling.has(id)) return null;
|
|
310
275
|
S.recycling.add(id);
|
|
311
276
|
|
|
312
277
|
const oldKey = best.getAttribute(A_ANCHOR);
|
|
313
|
-
// Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
|
|
314
|
-
// parasite si le nœud était encore dans la zone IO_MARGIN.
|
|
315
278
|
try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
|
|
316
279
|
mutate(() => {
|
|
317
|
-
best.setAttribute(A_ANCHOR,
|
|
318
|
-
best.setAttribute(A_CREATED, String(ts()));
|
|
319
|
-
best.setAttribute(A_SHOWN, '0');
|
|
320
|
-
best.classList.remove('is-empty');
|
|
280
|
+
best.setAttribute(A_ANCHOR, newKey);
|
|
321
281
|
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
322
282
|
if (ph) ph.innerHTML = '';
|
|
323
283
|
targetEl.insertAdjacentElement('afterend', best);
|
|
@@ -325,16 +285,14 @@
|
|
|
325
285
|
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
326
286
|
S.wrapByKey.set(newKey, best);
|
|
327
287
|
|
|
328
|
-
// Délais requis : destroyPlaceholders est asynchrone en interne
|
|
329
288
|
const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
|
|
330
289
|
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
331
290
|
const doDisplay = () => {
|
|
332
291
|
try { ez.displayMore([id]); } catch (_) {}
|
|
333
|
-
S.recycling.delete(id);
|
|
292
|
+
S.recycling.delete(id);
|
|
334
293
|
observePh(id);
|
|
335
|
-
try { best.setAttribute(A_SHOWN, String(ts())); } catch (_) {}
|
|
336
294
|
};
|
|
337
|
-
try {
|
|
295
|
+
try { typeof ez.cmd?.push === 'function' ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
338
296
|
|
|
339
297
|
return { id, wrap: best };
|
|
340
298
|
}
|
|
@@ -344,10 +302,8 @@
|
|
|
344
302
|
function makeWrap(id, klass, key) {
|
|
345
303
|
const w = document.createElement('div');
|
|
346
304
|
w.className = `${WRAP_CLASS} ${klass}`;
|
|
347
|
-
w.setAttribute(A_ANCHOR,
|
|
348
|
-
w.setAttribute(A_WRAPID,
|
|
349
|
-
w.setAttribute(A_CREATED, String(ts()));
|
|
350
|
-
w.setAttribute(A_SHOWN, '0');
|
|
305
|
+
w.setAttribute(A_ANCHOR, key);
|
|
306
|
+
w.setAttribute(A_WRAPID, String(id));
|
|
351
307
|
w.style.cssText = 'width:100%;display:block;';
|
|
352
308
|
const ph = document.createElement('div');
|
|
353
309
|
ph.id = `${PH_PREFIX}${id}`;
|
|
@@ -380,13 +336,8 @@
|
|
|
380
336
|
} catch (_) {}
|
|
381
337
|
}
|
|
382
338
|
|
|
383
|
-
|
|
384
339
|
// ── Injection ──────────────────────────────────────────────────────────────
|
|
385
340
|
|
|
386
|
-
/**
|
|
387
|
-
* Ordinal 0-based pour le calcul de l'intervalle d'injection.
|
|
388
|
-
* Utilise ordinalAttr si défini, sinon compte les frères dans le parent.
|
|
389
|
-
*/
|
|
390
341
|
function ordinal(klass, el) {
|
|
391
342
|
const attr = KIND[klass]?.ordinalAttr;
|
|
392
343
|
if (attr) {
|
|
@@ -405,18 +356,14 @@
|
|
|
405
356
|
function injectBetween(klass, items, interval, showFirst, poolKey) {
|
|
406
357
|
if (!items.length) return 0;
|
|
407
358
|
let inserted = 0;
|
|
408
|
-
|
|
409
359
|
for (const el of items) {
|
|
410
360
|
if (inserted >= MAX_INSERTS_RUN) break;
|
|
411
361
|
if (!el?.isConnected) continue;
|
|
412
|
-
|
|
413
362
|
const ord = ordinal(klass, el);
|
|
414
363
|
if (!(showFirst && ord === 0) && (ord + 1) % interval !== 0) continue;
|
|
415
364
|
if (adjacentWrap(el)) continue;
|
|
416
|
-
|
|
417
365
|
const key = anchorKey(klass, el);
|
|
418
366
|
if (findWrap(key)) continue;
|
|
419
|
-
|
|
420
367
|
const id = pickId(poolKey);
|
|
421
368
|
if (id) {
|
|
422
369
|
const w = insertAfter(el, id, klass, key);
|
|
@@ -432,13 +379,9 @@
|
|
|
432
379
|
|
|
433
380
|
// ── IntersectionObserver & Show ────────────────────────────────────────────
|
|
434
381
|
|
|
435
|
-
// Fix #6 : recréer l'observer si le type d'écran change (rotation, resize).
|
|
436
|
-
// isMobile() est évalué à chaque appel pour détecter un changement.
|
|
437
|
-
let _ioMobile = null; // dernier état mobile/desktop pour lequel l'IO a été créé
|
|
438
382
|
function getIO() {
|
|
439
383
|
const mobile = isMobile();
|
|
440
384
|
if (S.io && _ioMobile === mobile) return S.io;
|
|
441
|
-
// Type d'écran changé ou première création : (re)créer l'observer
|
|
442
385
|
if (S.io) { try { S.io.disconnect(); } catch (_) {} S.io = null; }
|
|
443
386
|
_ioMobile = mobile;
|
|
444
387
|
try {
|
|
@@ -489,19 +432,14 @@
|
|
|
489
432
|
drainQueue();
|
|
490
433
|
};
|
|
491
434
|
const timer = setTimeout(release, 7000);
|
|
492
|
-
|
|
493
435
|
requestAnimationFrame(() => {
|
|
494
436
|
try {
|
|
495
437
|
if (isBlocked()) { clearTimeout(timer); return release(); }
|
|
496
438
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
497
439
|
if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
|
|
498
|
-
|
|
499
440
|
const t = ts();
|
|
500
441
|
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
501
442
|
S.lastShow.set(id, t);
|
|
502
|
-
|
|
503
|
-
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
504
|
-
|
|
505
443
|
window.ezstandalone = window.ezstandalone || {};
|
|
506
444
|
const ez = window.ezstandalone;
|
|
507
445
|
const doShow = () => {
|
|
@@ -513,12 +451,10 @@
|
|
|
513
451
|
});
|
|
514
452
|
}
|
|
515
453
|
|
|
516
|
-
|
|
517
454
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
518
455
|
//
|
|
519
|
-
// Intercepte ez.showAds() pour
|
|
520
|
-
//
|
|
521
|
-
// – filtrer les ids dont le placeholder n'est pas en DOM
|
|
456
|
+
// Intercepte ez.showAds() pour ignorer les appels pendant blockedUntil
|
|
457
|
+
// et filtrer les ids dont le placeholder n'est pas connecté au DOM.
|
|
522
458
|
|
|
523
459
|
function patchShowAds() {
|
|
524
460
|
const apply = () => {
|
|
@@ -554,36 +490,19 @@
|
|
|
554
490
|
async function runCore() {
|
|
555
491
|
if (isBlocked()) return 0;
|
|
556
492
|
patchShowAds();
|
|
557
|
-
|
|
558
493
|
const cfg = await fetchConfig();
|
|
559
494
|
if (!cfg || cfg.excluded) return 0;
|
|
560
495
|
initPools(cfg);
|
|
561
|
-
|
|
562
496
|
const kind = getKind();
|
|
563
497
|
if (kind === 'other') return 0;
|
|
564
|
-
|
|
565
498
|
const exec = (klass, getItems, cfgEnable, cfgInterval, cfgShowFirst, poolKey) => {
|
|
566
499
|
if (!normBool(cfgEnable)) return 0;
|
|
567
500
|
const interval = Math.max(1, parseInt(cfgInterval, 10) || 3);
|
|
568
501
|
return injectBetween(klass, getItems(), interval, normBool(cfgShowFirst), poolKey);
|
|
569
502
|
};
|
|
570
|
-
|
|
571
|
-
if (kind === '
|
|
572
|
-
|
|
573
|
-
cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts'
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
if (kind === 'categoryTopics') {
|
|
577
|
-
return exec(
|
|
578
|
-
'ezoic-ad-between', getTopics,
|
|
579
|
-
cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics'
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
return exec(
|
|
584
|
-
'ezoic-ad-categories', getCategories,
|
|
585
|
-
cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories'
|
|
586
|
-
);
|
|
503
|
+
if (kind === 'topic') return exec('ezoic-ad-message', getPosts, cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts');
|
|
504
|
+
if (kind === 'categoryTopics') return exec('ezoic-ad-between', getTopics, cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics');
|
|
505
|
+
return exec('ezoic-ad-categories', getCategories, cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories');
|
|
587
506
|
}
|
|
588
507
|
|
|
589
508
|
// ── Scheduler ──────────────────────────────────────────────────────────────
|
|
@@ -607,11 +526,9 @@
|
|
|
607
526
|
S.lastBurstTs = t;
|
|
608
527
|
S.pageKey = pageKey();
|
|
609
528
|
S.burstDeadline = t + 2000;
|
|
610
|
-
|
|
611
529
|
if (S.burstActive) return;
|
|
612
530
|
S.burstActive = true;
|
|
613
531
|
S.burstCount = 0;
|
|
614
|
-
|
|
615
532
|
const step = () => {
|
|
616
533
|
if (pageKey() !== S.pageKey || isBlocked() || ts() > S.burstDeadline || S.burstCount >= 8) {
|
|
617
534
|
S.burstActive = false; return;
|
|
@@ -630,25 +547,27 @@
|
|
|
630
547
|
function cleanup() {
|
|
631
548
|
blockedUntil = ts() + 1500;
|
|
632
549
|
mutate(() => document.querySelectorAll(`.${WRAP_CLASS}`).forEach(dropWrap));
|
|
633
|
-
S.cfg
|
|
634
|
-
_cfgErrorUntil = 0;
|
|
635
|
-
S.poolsReady
|
|
636
|
-
S.pools
|
|
637
|
-
S.cursors
|
|
550
|
+
S.cfg = null;
|
|
551
|
+
_cfgErrorUntil = 0;
|
|
552
|
+
S.poolsReady = false;
|
|
553
|
+
S.pools = { topics: [], posts: [], categories: [] };
|
|
554
|
+
S.cursors = { topics: 0, posts: 0, categories: 0 };
|
|
638
555
|
S.mountedIds.clear();
|
|
639
556
|
S.lastShow.clear();
|
|
640
557
|
S.wrapByKey.clear();
|
|
641
|
-
S.inflight = 0;
|
|
642
|
-
S.pending = [];
|
|
643
|
-
S.pendingSet.clear();
|
|
644
|
-
S.burstActive = false;
|
|
645
|
-
S.runQueued = false;
|
|
646
558
|
S.recycling.clear();
|
|
559
|
+
S.inflight = 0;
|
|
560
|
+
S.pending = [];
|
|
561
|
+
S.pendingSet.clear();
|
|
562
|
+
S.burstActive = false;
|
|
563
|
+
S.runQueued = false;
|
|
647
564
|
if (S.domObs) { S.domObs.disconnect(); S.domObs = null; }
|
|
648
|
-
|
|
565
|
+
// tcfObs intentionnellement préservé : l'iframe __tcfapiLocator doit
|
|
566
|
+
// exister en permanence — la déconnecter pendant la navigation cause
|
|
567
|
+
// des erreurs CMP postMessage et la disparition des pubs.
|
|
649
568
|
}
|
|
650
569
|
|
|
651
|
-
// ── MutationObserver
|
|
570
|
+
// ── MutationObserver DOM ───────────────────────────────────────────────────
|
|
652
571
|
|
|
653
572
|
function ensureDomObserver() {
|
|
654
573
|
if (S.domObs) return;
|
|
@@ -658,9 +577,8 @@
|
|
|
658
577
|
for (const m of muts) {
|
|
659
578
|
for (const n of m.addedNodes) {
|
|
660
579
|
if (n.nodeType !== 1) continue;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
|
|
580
|
+
if (allSel.some(s => { try { return n.matches(s); } catch (_) { return false; } }) ||
|
|
581
|
+
allSel.some(s => { try { return !!n.querySelector(s); } catch (_) { return false; } })) {
|
|
664
582
|
requestBurst(); return;
|
|
665
583
|
}
|
|
666
584
|
}
|
|
@@ -680,6 +598,7 @@
|
|
|
680
598
|
'cannot call refresh on the same page',
|
|
681
599
|
'no placeholders are currently defined in Refresh',
|
|
682
600
|
'Debugger iframe already exists',
|
|
601
|
+
'[CMP] Error in custom getTCData',
|
|
683
602
|
`with id ${PH_PREFIX}`,
|
|
684
603
|
];
|
|
685
604
|
for (const m of ['log', 'info', 'warn', 'error']) {
|
|
@@ -702,7 +621,6 @@
|
|
|
702
621
|
(document.body || document.documentElement).appendChild(f);
|
|
703
622
|
};
|
|
704
623
|
inject();
|
|
705
|
-
// Fix #5 : ref stockée dans S pour pouvoir déconnecter au cleanup
|
|
706
624
|
if (!S.tcfObs) {
|
|
707
625
|
S.tcfObs = new MutationObserver(inject);
|
|
708
626
|
S.tcfObs.observe(document.documentElement, { childList: true, subtree: true });
|
|
@@ -737,25 +655,19 @@
|
|
|
737
655
|
function bindNodeBB() {
|
|
738
656
|
const $ = window.jQuery;
|
|
739
657
|
if (!$) return;
|
|
740
|
-
|
|
741
658
|
$(window).off('.nbbEzoic');
|
|
742
659
|
$(window).on('action:ajaxify.start.nbbEzoic', cleanup);
|
|
743
660
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
744
661
|
S.pageKey = pageKey();
|
|
745
662
|
blockedUntil = 0;
|
|
746
|
-
// muteConsole et warmNetwork sont idempotents — appelés au boot seulement.
|
|
747
|
-
// ensureTcfLocator doit être rappelé : ajaxify retire l'iframe __tcfapiLocator
|
|
748
|
-
// du DOM à chaque navigation, le CMP lève une erreur postMessage sans elle.
|
|
749
663
|
muteConsole(); ensureTcfLocator();
|
|
750
664
|
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
751
665
|
});
|
|
752
|
-
|
|
753
666
|
const burstEvts = [
|
|
754
|
-
'action:ajaxify.contentLoaded', 'action:posts.loaded',
|
|
755
|
-
'action:categories.loaded',
|
|
667
|
+
'action:ajaxify.contentLoaded', 'action:posts.loaded', 'action:topics.loaded',
|
|
668
|
+
'action:categories.loaded', 'action:category.loaded', 'action:topic.loaded',
|
|
756
669
|
].map(e => `${e}.nbbEzoic`).join(' ');
|
|
757
670
|
$(window).on(burstEvts, () => { if (!isBlocked()) requestBurst(); });
|
|
758
|
-
|
|
759
671
|
try {
|
|
760
672
|
require(['hooks'], hooks => {
|
|
761
673
|
if (typeof hooks?.on !== 'function') return;
|
|
@@ -774,11 +686,10 @@
|
|
|
774
686
|
ticking = true;
|
|
775
687
|
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
776
688
|
}, { passive: true });
|
|
777
|
-
// Fix #6 : détecter rotation/resize pour recréer l'IO avec la bonne marge
|
|
778
689
|
let resizeTimer = 0;
|
|
779
690
|
window.addEventListener('resize', () => {
|
|
780
691
|
clearTimeout(resizeTimer);
|
|
781
|
-
resizeTimer = setTimeout(
|
|
692
|
+
resizeTimer = setTimeout(getIO, 500);
|
|
782
693
|
}, { passive: true });
|
|
783
694
|
}
|
|
784
695
|
|