nodebb-plugin-ezoic-infinite 1.7.50 → 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 -186
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,49 +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 MAX_INSERTS_RUN = 6;
|
|
42
|
-
const MAX_INFLIGHT = 4;
|
|
43
|
-
const SHOW_THROTTLE_MS = 900;
|
|
44
|
-
const BURST_COOLDOWN_MS = 200;
|
|
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
|
|
45
38
|
|
|
46
|
-
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
47
39
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
48
40
|
const IO_MARGIN_MOBILE = '3500px 0px 3500px 0px';
|
|
49
41
|
|
|
@@ -55,13 +47,10 @@
|
|
|
55
47
|
|
|
56
48
|
/**
|
|
57
49
|
* Table KIND — source de vérité par kindClass.
|
|
58
|
-
*
|
|
59
|
-
* sel sélecteur CSS complet des éléments cibles
|
|
50
|
+
* sel sélecteur CSS des éléments cibles
|
|
60
51
|
* baseTag préfixe tag pour querySelector d'ancre
|
|
61
|
-
* (vide pour posts : le sélecteur commence par '[')
|
|
62
52
|
* 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)
|
|
53
|
+
* ordinalAttr attribut 0-based pour le calcul de l'intervalle (null = fallback positionnel)
|
|
65
54
|
*/
|
|
66
55
|
const KIND = {
|
|
67
56
|
'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index' },
|
|
@@ -72,36 +61,39 @@
|
|
|
72
61
|
// ── État global ────────────────────────────────────────────────────────────
|
|
73
62
|
|
|
74
63
|
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:
|
|
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,
|
|
93
82
|
burstDeadline: 0,
|
|
94
|
-
burstCount:
|
|
95
|
-
lastBurstTs:
|
|
83
|
+
burstCount: 0,
|
|
84
|
+
lastBurstTs: 0,
|
|
96
85
|
};
|
|
97
86
|
|
|
98
|
-
let blockedUntil
|
|
87
|
+
let blockedUntil = 0;
|
|
88
|
+
let _cfgErrorUntil = 0;
|
|
89
|
+
let _ioMobile = null;
|
|
99
90
|
|
|
100
|
-
const ts
|
|
101
|
-
const isBlocked
|
|
102
|
-
const isMobile
|
|
103
|
-
const
|
|
104
|
-
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]'));
|
|
105
97
|
|
|
106
98
|
function mutate(fn) {
|
|
107
99
|
S.mutGuard++;
|
|
@@ -110,9 +102,6 @@
|
|
|
110
102
|
|
|
111
103
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
112
104
|
|
|
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
105
|
async function fetchConfig() {
|
|
117
106
|
if (S.cfg) return S.cfg;
|
|
118
107
|
if (Date.now() < _cfgErrorUntil) return null;
|
|
@@ -180,53 +169,37 @@
|
|
|
180
169
|
|
|
181
170
|
// ── Wraps — détection ──────────────────────────────────────────────────────
|
|
182
171
|
|
|
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
172
|
function wrapIsLive(wrap) {
|
|
188
173
|
if (!wrap?.classList?.contains(WRAP_CLASS)) return false;
|
|
189
174
|
const key = wrap.getAttribute(A_ANCHOR);
|
|
190
175
|
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
176
|
if (S.wrapByKey.get(key) === wrap) return wrap.isConnected;
|
|
194
|
-
// Fallback : registre pas encore à jour ou wrap non enregistré.
|
|
195
177
|
const colonIdx = key.indexOf(':');
|
|
196
178
|
const klass = key.slice(0, colonIdx);
|
|
197
179
|
const anchorId = key.slice(colonIdx + 1);
|
|
198
180
|
const cfg = KIND[klass];
|
|
199
181
|
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
182
|
const parent = wrap.parentElement;
|
|
203
183
|
if (parent) {
|
|
204
184
|
for (const sib of parent.children) {
|
|
205
185
|
if (sib === wrap) continue;
|
|
206
186
|
try {
|
|
207
|
-
if (sib.matches(`${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`))
|
|
187
|
+
if (sib.matches(`${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`))
|
|
208
188
|
return sib.isConnected;
|
|
209
|
-
}
|
|
210
189
|
} catch (_) {}
|
|
211
190
|
}
|
|
212
191
|
}
|
|
213
|
-
// Dernier recours : querySelector global
|
|
214
192
|
try {
|
|
215
193
|
const found = document.querySelector(`${cfg.sel}[${cfg.anchorAttr}="${anchorId}"]`);
|
|
216
194
|
return !!(found?.isConnected);
|
|
217
195
|
} catch (_) { return false; }
|
|
218
196
|
}
|
|
219
197
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
198
|
+
const adjacentWrap = el =>
|
|
199
|
+
wrapIsLive(el.nextElementSibling) || wrapIsLive(el.previousElementSibling);
|
|
223
200
|
|
|
224
201
|
// ── Ancres stables ─────────────────────────────────────────────────────────
|
|
225
202
|
|
|
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
203
|
function stableId(klass, el) {
|
|
231
204
|
const attr = KIND[klass]?.anchorAttr;
|
|
232
205
|
if (attr) {
|
|
@@ -245,21 +218,15 @@
|
|
|
245
218
|
|
|
246
219
|
function findWrap(key) {
|
|
247
220
|
const w = S.wrapByKey.get(key);
|
|
248
|
-
return
|
|
221
|
+
return w?.isConnected ? w : null;
|
|
249
222
|
}
|
|
250
223
|
|
|
251
224
|
// ── Pool ───────────────────────────────────────────────────────────────────
|
|
252
225
|
|
|
253
|
-
/**
|
|
254
|
-
* Retourne le prochain id disponible dans le pool (round-robin),
|
|
255
|
-
* ou null si tous les ids sont montés.
|
|
256
|
-
*/
|
|
257
226
|
function pickId(poolKey) {
|
|
258
227
|
const pool = S.pools[poolKey];
|
|
259
228
|
if (!pool.length) return null;
|
|
260
|
-
|
|
261
|
-
if (S.mountedIds.size >= pool.length &&
|
|
262
|
-
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;
|
|
263
230
|
for (let t = 0; t < pool.length; t++) {
|
|
264
231
|
const i = S.cursors[poolKey] % pool.length;
|
|
265
232
|
S.cursors[poolKey] = (S.cursors[poolKey] + 1) % pool.length;
|
|
@@ -271,9 +238,7 @@
|
|
|
271
238
|
|
|
272
239
|
/**
|
|
273
240
|
* 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.
|
|
241
|
+
* Séquence : destroy([id]) → 300ms → define([id]) → 300ms → displayMore([id])
|
|
277
242
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
278
243
|
*/
|
|
279
244
|
function recycleAndMove(klass, targetEl, newKey) {
|
|
@@ -282,10 +247,7 @@
|
|
|
282
247
|
typeof ez?.define !== 'function' ||
|
|
283
248
|
typeof ez?.displayMore !== 'function') return null;
|
|
284
249
|
|
|
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;
|
|
250
|
+
const threshold = -(window.innerHeight || 800);
|
|
289
251
|
let bestEmpty = null, bestEmptyBottom = Infinity;
|
|
290
252
|
let bestFilled = null, bestFilledBottom = Infinity;
|
|
291
253
|
|
|
@@ -305,19 +267,13 @@
|
|
|
305
267
|
if (!best) return null;
|
|
306
268
|
const id = parseInt(best.getAttribute(A_WRAPID), 10);
|
|
307
269
|
if (!Number.isFinite(id)) return null;
|
|
308
|
-
// Fix #1 : éviter de recycler un slot dont la séquence Ezoic est en cours
|
|
309
270
|
if (S.recycling.has(id)) return null;
|
|
310
271
|
S.recycling.add(id);
|
|
311
272
|
|
|
312
273
|
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
274
|
try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
|
|
316
275
|
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');
|
|
276
|
+
best.setAttribute(A_ANCHOR, newKey);
|
|
321
277
|
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
322
278
|
if (ph) ph.innerHTML = '';
|
|
323
279
|
targetEl.insertAdjacentElement('afterend', best);
|
|
@@ -325,16 +281,14 @@
|
|
|
325
281
|
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
326
282
|
S.wrapByKey.set(newKey, best);
|
|
327
283
|
|
|
328
|
-
// Délais requis : destroyPlaceholders est asynchrone en interne
|
|
329
284
|
const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
|
|
330
285
|
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
331
286
|
const doDisplay = () => {
|
|
332
287
|
try { ez.displayMore([id]); } catch (_) {}
|
|
333
|
-
S.recycling.delete(id);
|
|
288
|
+
S.recycling.delete(id);
|
|
334
289
|
observePh(id);
|
|
335
|
-
try { best.setAttribute(A_SHOWN, String(ts())); } catch (_) {}
|
|
336
290
|
};
|
|
337
|
-
try {
|
|
291
|
+
try { typeof ez.cmd?.push === 'function' ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
338
292
|
|
|
339
293
|
return { id, wrap: best };
|
|
340
294
|
}
|
|
@@ -344,10 +298,8 @@
|
|
|
344
298
|
function makeWrap(id, klass, key) {
|
|
345
299
|
const w = document.createElement('div');
|
|
346
300
|
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');
|
|
301
|
+
w.setAttribute(A_ANCHOR, key);
|
|
302
|
+
w.setAttribute(A_WRAPID, String(id));
|
|
351
303
|
w.style.cssText = 'width:100%;display:block;';
|
|
352
304
|
const ph = document.createElement('div');
|
|
353
305
|
ph.id = `${PH_PREFIX}${id}`;
|
|
@@ -380,13 +332,8 @@
|
|
|
380
332
|
} catch (_) {}
|
|
381
333
|
}
|
|
382
334
|
|
|
383
|
-
|
|
384
335
|
// ── Injection ──────────────────────────────────────────────────────────────
|
|
385
336
|
|
|
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
337
|
function ordinal(klass, el) {
|
|
391
338
|
const attr = KIND[klass]?.ordinalAttr;
|
|
392
339
|
if (attr) {
|
|
@@ -405,18 +352,14 @@
|
|
|
405
352
|
function injectBetween(klass, items, interval, showFirst, poolKey) {
|
|
406
353
|
if (!items.length) return 0;
|
|
407
354
|
let inserted = 0;
|
|
408
|
-
|
|
409
355
|
for (const el of items) {
|
|
410
356
|
if (inserted >= MAX_INSERTS_RUN) break;
|
|
411
357
|
if (!el?.isConnected) continue;
|
|
412
|
-
|
|
413
358
|
const ord = ordinal(klass, el);
|
|
414
359
|
if (!(showFirst && ord === 0) && (ord + 1) % interval !== 0) continue;
|
|
415
360
|
if (adjacentWrap(el)) continue;
|
|
416
|
-
|
|
417
361
|
const key = anchorKey(klass, el);
|
|
418
362
|
if (findWrap(key)) continue;
|
|
419
|
-
|
|
420
363
|
const id = pickId(poolKey);
|
|
421
364
|
if (id) {
|
|
422
365
|
const w = insertAfter(el, id, klass, key);
|
|
@@ -432,13 +375,9 @@
|
|
|
432
375
|
|
|
433
376
|
// ── IntersectionObserver & Show ────────────────────────────────────────────
|
|
434
377
|
|
|
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
378
|
function getIO() {
|
|
439
379
|
const mobile = isMobile();
|
|
440
380
|
if (S.io && _ioMobile === mobile) return S.io;
|
|
441
|
-
// Type d'écran changé ou première création : (re)créer l'observer
|
|
442
381
|
if (S.io) { try { S.io.disconnect(); } catch (_) {} S.io = null; }
|
|
443
382
|
_ioMobile = mobile;
|
|
444
383
|
try {
|
|
@@ -489,19 +428,14 @@
|
|
|
489
428
|
drainQueue();
|
|
490
429
|
};
|
|
491
430
|
const timer = setTimeout(release, 7000);
|
|
492
|
-
|
|
493
431
|
requestAnimationFrame(() => {
|
|
494
432
|
try {
|
|
495
433
|
if (isBlocked()) { clearTimeout(timer); return release(); }
|
|
496
434
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
497
435
|
if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
|
|
498
|
-
|
|
499
436
|
const t = ts();
|
|
500
437
|
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
501
438
|
S.lastShow.set(id, t);
|
|
502
|
-
|
|
503
|
-
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
504
|
-
|
|
505
439
|
window.ezstandalone = window.ezstandalone || {};
|
|
506
440
|
const ez = window.ezstandalone;
|
|
507
441
|
const doShow = () => {
|
|
@@ -513,12 +447,10 @@
|
|
|
513
447
|
});
|
|
514
448
|
}
|
|
515
449
|
|
|
516
|
-
|
|
517
450
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
518
451
|
//
|
|
519
|
-
// Intercepte ez.showAds() pour
|
|
520
|
-
//
|
|
521
|
-
// – 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.
|
|
522
454
|
|
|
523
455
|
function patchShowAds() {
|
|
524
456
|
const apply = () => {
|
|
@@ -554,36 +486,19 @@
|
|
|
554
486
|
async function runCore() {
|
|
555
487
|
if (isBlocked()) return 0;
|
|
556
488
|
patchShowAds();
|
|
557
|
-
|
|
558
489
|
const cfg = await fetchConfig();
|
|
559
490
|
if (!cfg || cfg.excluded) return 0;
|
|
560
491
|
initPools(cfg);
|
|
561
|
-
|
|
562
492
|
const kind = getKind();
|
|
563
493
|
if (kind === 'other') return 0;
|
|
564
|
-
|
|
565
494
|
const exec = (klass, getItems, cfgEnable, cfgInterval, cfgShowFirst, poolKey) => {
|
|
566
495
|
if (!normBool(cfgEnable)) return 0;
|
|
567
496
|
const interval = Math.max(1, parseInt(cfgInterval, 10) || 3);
|
|
568
497
|
return injectBetween(klass, getItems(), interval, normBool(cfgShowFirst), poolKey);
|
|
569
498
|
};
|
|
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
|
-
);
|
|
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');
|
|
587
502
|
}
|
|
588
503
|
|
|
589
504
|
// ── Scheduler ──────────────────────────────────────────────────────────────
|
|
@@ -607,11 +522,9 @@
|
|
|
607
522
|
S.lastBurstTs = t;
|
|
608
523
|
S.pageKey = pageKey();
|
|
609
524
|
S.burstDeadline = t + 2000;
|
|
610
|
-
|
|
611
525
|
if (S.burstActive) return;
|
|
612
526
|
S.burstActive = true;
|
|
613
527
|
S.burstCount = 0;
|
|
614
|
-
|
|
615
528
|
const step = () => {
|
|
616
529
|
if (pageKey() !== S.pageKey || isBlocked() || ts() > S.burstDeadline || S.burstCount >= 8) {
|
|
617
530
|
S.burstActive = false; return;
|
|
@@ -630,25 +543,25 @@
|
|
|
630
543
|
function cleanup() {
|
|
631
544
|
blockedUntil = ts() + 1500;
|
|
632
545
|
mutate(() => document.querySelectorAll(`.${WRAP_CLASS}`).forEach(dropWrap));
|
|
633
|
-
S.cfg
|
|
634
|
-
_cfgErrorUntil = 0;
|
|
635
|
-
S.poolsReady
|
|
636
|
-
S.pools
|
|
637
|
-
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 };
|
|
638
551
|
S.mountedIds.clear();
|
|
639
552
|
S.lastShow.clear();
|
|
640
553
|
S.wrapByKey.clear();
|
|
641
|
-
S.inflight = 0;
|
|
642
|
-
S.pending = [];
|
|
643
|
-
S.pendingSet.clear();
|
|
644
|
-
S.burstActive = false;
|
|
645
|
-
S.runQueued = false;
|
|
646
554
|
S.recycling.clear();
|
|
555
|
+
S.inflight = 0;
|
|
556
|
+
S.pending = [];
|
|
557
|
+
S.pendingSet.clear();
|
|
558
|
+
S.burstActive = false;
|
|
559
|
+
S.runQueued = false;
|
|
647
560
|
if (S.domObs) { S.domObs.disconnect(); S.domObs = null; }
|
|
648
561
|
if (S.tcfObs) { S.tcfObs.disconnect(); S.tcfObs = null; }
|
|
649
562
|
}
|
|
650
563
|
|
|
651
|
-
// ── MutationObserver
|
|
564
|
+
// ── MutationObserver DOM ───────────────────────────────────────────────────
|
|
652
565
|
|
|
653
566
|
function ensureDomObserver() {
|
|
654
567
|
if (S.domObs) return;
|
|
@@ -658,9 +571,8 @@
|
|
|
658
571
|
for (const m of muts) {
|
|
659
572
|
for (const n of m.addedNodes) {
|
|
660
573
|
if (n.nodeType !== 1) continue;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
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; } })) {
|
|
664
576
|
requestBurst(); return;
|
|
665
577
|
}
|
|
666
578
|
}
|
|
@@ -680,6 +592,7 @@
|
|
|
680
592
|
'cannot call refresh on the same page',
|
|
681
593
|
'no placeholders are currently defined in Refresh',
|
|
682
594
|
'Debugger iframe already exists',
|
|
595
|
+
'[CMP] Error in custom getTCData',
|
|
683
596
|
`with id ${PH_PREFIX}`,
|
|
684
597
|
];
|
|
685
598
|
for (const m of ['log', 'info', 'warn', 'error']) {
|
|
@@ -702,7 +615,6 @@
|
|
|
702
615
|
(document.body || document.documentElement).appendChild(f);
|
|
703
616
|
};
|
|
704
617
|
inject();
|
|
705
|
-
// Fix #5 : ref stockée dans S pour pouvoir déconnecter au cleanup
|
|
706
618
|
if (!S.tcfObs) {
|
|
707
619
|
S.tcfObs = new MutationObserver(inject);
|
|
708
620
|
S.tcfObs.observe(document.documentElement, { childList: true, subtree: true });
|
|
@@ -737,25 +649,19 @@
|
|
|
737
649
|
function bindNodeBB() {
|
|
738
650
|
const $ = window.jQuery;
|
|
739
651
|
if (!$) return;
|
|
740
|
-
|
|
741
652
|
$(window).off('.nbbEzoic');
|
|
742
653
|
$(window).on('action:ajaxify.start.nbbEzoic', cleanup);
|
|
743
654
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
744
655
|
S.pageKey = pageKey();
|
|
745
656
|
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
657
|
muteConsole(); ensureTcfLocator();
|
|
750
658
|
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
751
659
|
});
|
|
752
|
-
|
|
753
660
|
const burstEvts = [
|
|
754
|
-
'action:ajaxify.contentLoaded', 'action:posts.loaded',
|
|
755
|
-
'action:categories.loaded',
|
|
661
|
+
'action:ajaxify.contentLoaded', 'action:posts.loaded', 'action:topics.loaded',
|
|
662
|
+
'action:categories.loaded', 'action:category.loaded', 'action:topic.loaded',
|
|
756
663
|
].map(e => `${e}.nbbEzoic`).join(' ');
|
|
757
664
|
$(window).on(burstEvts, () => { if (!isBlocked()) requestBurst(); });
|
|
758
|
-
|
|
759
665
|
try {
|
|
760
666
|
require(['hooks'], hooks => {
|
|
761
667
|
if (typeof hooks?.on !== 'function') return;
|
|
@@ -774,11 +680,10 @@
|
|
|
774
680
|
ticking = true;
|
|
775
681
|
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
776
682
|
}, { passive: true });
|
|
777
|
-
// Fix #6 : détecter rotation/resize pour recréer l'IO avec la bonne marge
|
|
778
683
|
let resizeTimer = 0;
|
|
779
684
|
window.addEventListener('resize', () => {
|
|
780
685
|
clearTimeout(resizeTimer);
|
|
781
|
-
resizeTimer = setTimeout(
|
|
686
|
+
resizeTimer = setTimeout(getIO, 500);
|
|
782
687
|
}, { passive: true });
|
|
783
688
|
}
|
|
784
689
|
|