nodebb-plugin-ezoic-infinite 1.7.12 → 1.7.14
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 +131 -124
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,38 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — client.js v24
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* Historique des corrections majeures
|
|
5
|
+
* ────────────────────────────────────
|
|
6
|
+
* v18 Ancrage stable par data-pid/data-index au lieu d'ordinalMap fragile.
|
|
7
|
+
* Suppression du recyclage de wraps (moveWrapAfter). Cleanup complet navigation.
|
|
8
|
+
*
|
|
9
|
+
* v19 Intervalle global basé sur l'ordinal absolu (data-index) et non sur
|
|
10
|
+
* la position dans le batch courant.
|
|
11
|
+
*
|
|
12
|
+
* v20 Table KIND : anchorAttr/ordinalAttr/baseTag explicites par kindClass.
|
|
13
|
+
* Fix fatal catégories : data-cid au lieu de data-index inexistant.
|
|
14
|
+
* Fix posts : baseTag vide → ordinal fallback cassé → pubs jamais injectées.
|
|
15
|
+
* IO fixe (une instance, jamais recréée). Burst cooldown 200 ms.
|
|
16
|
+
* Fix unobserve(null) → corruption IO → pubads error au scroll retour.
|
|
17
|
+
* Fix TCF locator : MutationObserver recrée l'iframe si ajaxify la retire.
|
|
18
|
+
*
|
|
19
|
+
* v21 Suppression de toute la logique wyvern.js (pause/destroy avant remove) :
|
|
20
|
+
* les erreurs wyvern viennent du SDK Ezoic lui-même lors de ses propres
|
|
21
|
+
* refreshes internes, pas de nos suppressions. Nos wraps filled ne sont
|
|
22
|
+
* de toute façon jamais supprimés (règle pruneOrphans/decluster).
|
|
23
|
+
* Refactorisation finale prod-ready : code unifié, zéro duplication,
|
|
24
|
+
* commentaires essentiels uniquement.
|
|
12
25
|
*/
|
|
13
26
|
(function () {
|
|
14
27
|
'use strict';
|
|
15
28
|
|
|
16
29
|
// ── Constantes ─────────────────────────────────────────────────────────────
|
|
17
30
|
|
|
18
|
-
const WRAP_CLASS
|
|
19
|
-
const PH_PREFIX
|
|
20
|
-
const A_ANCHOR
|
|
21
|
-
const A_WRAPID
|
|
22
|
-
const A_CREATED
|
|
23
|
-
const A_SHOWN
|
|
24
|
-
|
|
25
|
-
const MIN_PRUNE_AGE_MS
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
const WRAP_CLASS = 'nodebb-ezoic-wrap';
|
|
32
|
+
const PH_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
33
|
+
const A_ANCHOR = 'data-ezoic-anchor'; // "kindClass:stableId"
|
|
34
|
+
const A_WRAPID = 'data-ezoic-wrapid'; // id Ezoic
|
|
35
|
+
const A_CREATED = 'data-ezoic-created'; // timestamp création ms
|
|
36
|
+
const A_SHOWN = 'data-ezoic-shown'; // timestamp dernier showAds ms
|
|
37
|
+
|
|
38
|
+
const MIN_PRUNE_AGE_MS = 8_000; // garde-fou post-batch NodeBB
|
|
39
|
+
const PRUNE_STABLE_MS = 45_000; // délai avant qu'un wrap vide puisse être purgé
|
|
40
|
+
// (évite la suppression lors du scroll up / virtualisation NodeBB)
|
|
41
|
+
const FILL_GRACE_MS = 25_000; // fenêtre fill async Ezoic (SSP auction)
|
|
42
|
+
const EMPTY_CHECK_MS = 20_000; // délai collapse wrap vide post-show
|
|
43
|
+
const MAX_INSERTS_RUN = 6;
|
|
44
|
+
const MAX_INFLIGHT = 4;
|
|
45
|
+
const SHOW_THROTTLE_MS = 900;
|
|
46
|
+
const BURST_COOLDOWN_MS = 200;
|
|
47
|
+
|
|
48
|
+
// IO : marges larges fixes — une seule instance, jamais recréée
|
|
49
|
+
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
50
|
+
const IO_MARGIN_MOBILE = '3500px 0px 3500px 0px';
|
|
36
51
|
|
|
37
52
|
const SEL = {
|
|
38
53
|
post: '[component="post"][data-pid]',
|
|
@@ -43,46 +58,46 @@
|
|
|
43
58
|
/**
|
|
44
59
|
* Table KIND — source de vérité par kindClass.
|
|
45
60
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
61
|
+
* sel : sélecteur CSS complet
|
|
62
|
+
* baseTag : préfixe tag pour les querySelector de recherche d'ancre
|
|
63
|
+
* (vide pour posts car leur sélecteur commence par '[')
|
|
64
|
+
* anchorAttr : attribut DOM STABLE → clé unique du wrap, permanent
|
|
65
|
+
* data-pid posts (id message, immuable)
|
|
66
|
+
* data-index topics (index dans la liste)
|
|
67
|
+
* data-cid catégories (id catégorie, immuable)
|
|
68
|
+
* ordinalAttr: attribut 0-based pour le calcul de l'intervalle
|
|
69
|
+
* data-index posts + topics (fourni par NodeBB)
|
|
70
|
+
* null catégories (page statique → fallback positionnel)
|
|
52
71
|
*/
|
|
53
72
|
const KIND = {
|
|
54
|
-
'ezoic-ad-message': { sel: SEL.post, anchorAttr: 'data-pid',
|
|
55
|
-
'ezoic-ad-between': { sel: SEL.topic, anchorAttr: 'data-index',
|
|
56
|
-
'ezoic-ad-categories': { sel: SEL.category, anchorAttr: 'data-cid',
|
|
73
|
+
'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index' },
|
|
74
|
+
'ezoic-ad-between': { sel: SEL.topic, baseTag: 'li', anchorAttr: 'data-index', ordinalAttr: 'data-index' },
|
|
75
|
+
'ezoic-ad-categories': { sel: SEL.category, baseTag: 'li', anchorAttr: 'data-cid', ordinalAttr: null },
|
|
57
76
|
};
|
|
58
77
|
|
|
59
78
|
// ── État ───────────────────────────────────────────────────────────────────
|
|
60
79
|
|
|
61
80
|
const S = {
|
|
62
|
-
pageKey:
|
|
63
|
-
cfg:
|
|
64
|
-
pools:
|
|
65
|
-
cursors:
|
|
66
|
-
mountedIds:
|
|
67
|
-
lastShow:
|
|
68
|
-
io:
|
|
69
|
-
domObs:
|
|
70
|
-
mutGuard:
|
|
71
|
-
inflight:
|
|
72
|
-
pending:
|
|
73
|
-
pendingSet:
|
|
74
|
-
runQueued:
|
|
75
|
-
burstActive:
|
|
81
|
+
pageKey: null,
|
|
82
|
+
cfg: null,
|
|
83
|
+
pools: { topics: [], posts: [], categories: [] },
|
|
84
|
+
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
85
|
+
mountedIds: new Set(), // IDs Ezoic montés dans le DOM
|
|
86
|
+
lastShow: new Map(), // id → timestamp dernier show
|
|
87
|
+
io: null,
|
|
88
|
+
domObs: null,
|
|
89
|
+
mutGuard: 0,
|
|
90
|
+
inflight: 0,
|
|
91
|
+
pending: [],
|
|
92
|
+
pendingSet: new Set(),
|
|
93
|
+
runQueued: false,
|
|
94
|
+
burstActive: false,
|
|
76
95
|
burstDeadline: 0,
|
|
77
|
-
burstCount:
|
|
78
|
-
lastBurstTs:
|
|
96
|
+
burstCount: 0,
|
|
97
|
+
lastBurstTs: 0,
|
|
79
98
|
};
|
|
80
99
|
|
|
81
|
-
|
|
82
|
-
const wrapByKey = new Map();
|
|
83
|
-
let blockedUntil = 0;
|
|
84
|
-
let poolsReady = false; // initPools une seule fois par page
|
|
85
|
-
|
|
100
|
+
let blockedUntil = 0;
|
|
86
101
|
const ts = () => Date.now();
|
|
87
102
|
const isBlocked = () => ts() < blockedUntil;
|
|
88
103
|
const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
|
|
@@ -115,11 +130,9 @@
|
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
function initPools(cfg) {
|
|
118
|
-
if (poolsReady) return;
|
|
119
133
|
S.pools.topics = parseIds(cfg.placeholderIds);
|
|
120
134
|
S.pools.posts = parseIds(cfg.messagePlaceholderIds);
|
|
121
135
|
S.pools.categories = parseIds(cfg.categoryPlaceholderIds);
|
|
122
|
-
poolsReady = true;
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
// ── Page identity ──────────────────────────────────────────────────────────
|
|
@@ -182,13 +195,14 @@
|
|
|
182
195
|
return 'i0';
|
|
183
196
|
}
|
|
184
197
|
|
|
185
|
-
const
|
|
198
|
+
const anchorKey = (klass, el) => `${klass}:${stableId(klass, el)}`;
|
|
186
199
|
|
|
187
200
|
function findWrap(key) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
try {
|
|
202
|
+
return document.querySelector(
|
|
203
|
+
`.${WRAP_CLASS}[${A_ANCHOR}="${key.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"]`
|
|
204
|
+
);
|
|
205
|
+
} catch (_) { return null; }
|
|
192
206
|
}
|
|
193
207
|
|
|
194
208
|
// ── Pool ───────────────────────────────────────────────────────────────────
|
|
@@ -207,7 +221,7 @@
|
|
|
207
221
|
// ── Wraps DOM ──────────────────────────────────────────────────────────────
|
|
208
222
|
|
|
209
223
|
function makeWrap(id, klass, key) {
|
|
210
|
-
const w
|
|
224
|
+
const w = document.createElement('div');
|
|
211
225
|
w.className = `${WRAP_CLASS} ${klass}`;
|
|
212
226
|
w.setAttribute(A_ANCHOR, key);
|
|
213
227
|
w.setAttribute(A_WRAPID, String(id));
|
|
@@ -221,28 +235,24 @@
|
|
|
221
235
|
}
|
|
222
236
|
|
|
223
237
|
function insertAfter(el, id, klass, key) {
|
|
224
|
-
if (!el?.insertAdjacentElement)
|
|
225
|
-
if (findWrap(key))
|
|
226
|
-
if (S.mountedIds.has(id))
|
|
227
|
-
if (document.getElementById(`${PH_PREFIX}${id}`)?.isConnected)
|
|
238
|
+
if (!el?.insertAdjacentElement) return null;
|
|
239
|
+
if (findWrap(key)) return null;
|
|
240
|
+
if (S.mountedIds.has(id)) return null;
|
|
241
|
+
if (document.getElementById(`${PH_PREFIX}${id}`)?.isConnected) return null;
|
|
228
242
|
const w = makeWrap(id, klass, key);
|
|
229
243
|
mutate(() => el.insertAdjacentElement('afterend', w));
|
|
230
244
|
S.mountedIds.add(id);
|
|
231
|
-
wrapByKey.set(key, w);
|
|
232
245
|
return w;
|
|
233
246
|
}
|
|
234
247
|
|
|
235
248
|
function dropWrap(w) {
|
|
236
249
|
try {
|
|
250
|
+
// Unobserve avant remove — guard instanceof évite unobserve(null)
|
|
251
|
+
// qui corrompt l'état interne de l'IO (pubads error au scroll suivant)
|
|
252
|
+
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
253
|
+
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
237
254
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
238
255
|
if (Number.isFinite(id)) S.mountedIds.delete(id);
|
|
239
|
-
const key = w.getAttribute(A_ANCHOR);
|
|
240
|
-
if (key) wrapByKey.delete(key);
|
|
241
|
-
// unobserve avant remove — guard instanceof (unobserve(null) corrompt l'IO pubads)
|
|
242
|
-
try {
|
|
243
|
-
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
244
|
-
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
245
|
-
} catch (_) {}
|
|
246
256
|
w.remove();
|
|
247
257
|
} catch (_) {}
|
|
248
258
|
}
|
|
@@ -252,20 +262,24 @@
|
|
|
252
262
|
/**
|
|
253
263
|
* Supprime les wraps VIDES dont l'ancre a disparu du DOM.
|
|
254
264
|
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
265
|
+
* On ne supprime JAMAIS un wrap rempli (filled) :
|
|
266
|
+
* - Les wraps remplis peuvent être temporairement orphelins lors d'une
|
|
267
|
+
* virtualisation NodeBB — l'ancre reviendra.
|
|
268
|
+
* - Le SDK Ezoic (wyvern, GAM) exécute des callbacks async sur le contenu ;
|
|
269
|
+
* retirer le nœud sous ses pieds génère des erreurs non critiques mais
|
|
270
|
+
* inutiles. Le cleanup de navigation gère la suppression définitive.
|
|
259
271
|
*/
|
|
260
272
|
function pruneOrphans(klass) {
|
|
261
273
|
const meta = KIND[klass];
|
|
262
274
|
if (!meta) return;
|
|
263
275
|
|
|
264
276
|
for (const w of document.querySelectorAll(`.${WRAP_CLASS}.${klass}`)) {
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
277
|
+
// Ne jamais supprimer un wrap filled
|
|
278
|
+
if (isFilled(w)) continue;
|
|
279
|
+
// Attendre PRUNE_STABLE_MS depuis la création : pendant ce délai, l'ancre
|
|
280
|
+
// peut avoir temporairement disparu du DOM par virtualisation NodeBB au
|
|
281
|
+
// scroll up — ce n'est pas un vrai orphelin.
|
|
282
|
+
if (ts() - parseInt(w.getAttribute(A_CREATED) || '0', 10) < PRUNE_STABLE_MS) continue;
|
|
269
283
|
|
|
270
284
|
const key = w.getAttribute(A_ANCHOR) ?? '';
|
|
271
285
|
const sid = key.slice(klass.length + 1);
|
|
@@ -281,18 +295,15 @@
|
|
|
281
295
|
|
|
282
296
|
/**
|
|
283
297
|
* Deux wraps adjacents → supprimer le moins prioritaire.
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* - Jamais supprimer un wrap filled
|
|
287
|
-
* - Jamais supprimer un wrap < INJECT_GRACE_MS (fill Ezoic potentiellement en cours)
|
|
288
|
-
* - Jamais supprimer un wrap < FILL_GRACE_MS depuis le dernier showAds
|
|
289
|
-
* - Si les deux wraps adjacents sont vides et hors grâce → supprimer le courant
|
|
298
|
+
* Priorité : filled > en grâce de fill > vide.
|
|
299
|
+
* Jamais de suppression d'un wrap rempli (même raison que pruneOrphans).
|
|
290
300
|
*/
|
|
291
301
|
function decluster(klass) {
|
|
292
302
|
for (const w of document.querySelectorAll(`.${WRAP_CLASS}.${klass}`)) {
|
|
303
|
+
// Ne jamais toucher un wrap filled
|
|
293
304
|
if (isFilled(w)) continue;
|
|
294
|
-
|
|
295
|
-
if (
|
|
305
|
+
// Protéger par A_CREATED : un wrap récent attend encore son showAds async
|
|
306
|
+
if (ts() - parseInt(w.getAttribute(A_CREATED) || '0', 10) < FILL_GRACE_MS) continue;
|
|
296
307
|
const wShown = parseInt(w.getAttribute(A_SHOWN) || '0', 10);
|
|
297
308
|
if (wShown && ts() - wShown < FILL_GRACE_MS) continue;
|
|
298
309
|
|
|
@@ -300,8 +311,7 @@
|
|
|
300
311
|
while (prev && steps++ < 3) {
|
|
301
312
|
if (!prev.classList?.contains(WRAP_CLASS)) { prev = prev.previousElementSibling; continue; }
|
|
302
313
|
if (isFilled(prev)) break;
|
|
303
|
-
|
|
304
|
-
if (pAge < INJECT_GRACE_MS) break;
|
|
314
|
+
if (ts() - parseInt(prev.getAttribute(A_CREATED) || '0', 10) < FILL_GRACE_MS) break;
|
|
305
315
|
const pShown = parseInt(prev.getAttribute(A_SHOWN) || '0', 10);
|
|
306
316
|
if (pShown && ts() - pShown < FILL_GRACE_MS) break;
|
|
307
317
|
|
|
@@ -316,13 +326,16 @@
|
|
|
316
326
|
|
|
317
327
|
/**
|
|
318
328
|
* Ordinal 0-based pour le calcul de l'intervalle.
|
|
319
|
-
* Posts
|
|
320
|
-
* Catégories
|
|
329
|
+
* Posts et topics : data-index (NodeBB 4.x, stable entre les batches).
|
|
330
|
+
* Catégories : fallback positionnel (page statique, pas d'infinite scroll).
|
|
321
331
|
*/
|
|
322
332
|
function ordinal(klass, el) {
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
325
|
-
|
|
333
|
+
const attr = KIND[klass]?.ordinalAttr;
|
|
334
|
+
if (attr) {
|
|
335
|
+
const v = el.getAttribute(attr);
|
|
336
|
+
if (v !== null && v !== '' && !isNaN(v)) return parseInt(v, 10);
|
|
337
|
+
}
|
|
338
|
+
// Fallback positionnel — compte uniquement les éléments du même type
|
|
326
339
|
const fullSel = KIND[klass]?.sel ?? '';
|
|
327
340
|
let i = 0;
|
|
328
341
|
for (const s of el.parentElement?.children ?? []) {
|
|
@@ -337,15 +350,14 @@
|
|
|
337
350
|
let inserted = 0;
|
|
338
351
|
|
|
339
352
|
for (const el of items) {
|
|
340
|
-
if (inserted >=
|
|
353
|
+
if (inserted >= MAX_INSERTS_RUN) break;
|
|
341
354
|
if (!el?.isConnected) continue;
|
|
342
355
|
|
|
343
356
|
const ord = ordinal(klass, el);
|
|
344
357
|
if (!(showFirst && ord === 0) && (ord + 1) % interval !== 0) continue;
|
|
345
|
-
|
|
346
358
|
if (adjacentWrap(el)) continue;
|
|
347
359
|
|
|
348
|
-
const key =
|
|
360
|
+
const key = anchorKey(klass, el);
|
|
349
361
|
if (findWrap(key)) continue;
|
|
350
362
|
|
|
351
363
|
const id = pickId(poolKey);
|
|
@@ -481,6 +493,7 @@
|
|
|
481
493
|
|
|
482
494
|
async function runCore() {
|
|
483
495
|
if (isBlocked()) return 0;
|
|
496
|
+
patchShowAds();
|
|
484
497
|
|
|
485
498
|
const cfg = await fetchConfig();
|
|
486
499
|
if (!cfg || cfg.excluded) return 0;
|
|
@@ -506,14 +519,13 @@
|
|
|
506
519
|
'ezoic-ad-between', getTopics,
|
|
507
520
|
cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics'
|
|
508
521
|
);
|
|
509
|
-
|
|
522
|
+
return exec(
|
|
510
523
|
'ezoic-ad-categories', getCategories,
|
|
511
524
|
cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories'
|
|
512
525
|
);
|
|
513
|
-
return 0;
|
|
514
526
|
}
|
|
515
527
|
|
|
516
|
-
// ── Scheduler
|
|
528
|
+
// ── Scheduler ──────────────────────────────────────────────────────────────
|
|
517
529
|
|
|
518
530
|
function scheduleRun(cb) {
|
|
519
531
|
if (S.runQueued) return;
|
|
@@ -523,7 +535,7 @@
|
|
|
523
535
|
if (S.pageKey && pageKey() !== S.pageKey) return;
|
|
524
536
|
let n = 0;
|
|
525
537
|
try { n = await runCore(); } catch (_) {}
|
|
526
|
-
|
|
538
|
+
cb?.(n);
|
|
527
539
|
});
|
|
528
540
|
}
|
|
529
541
|
|
|
@@ -531,10 +543,8 @@
|
|
|
531
543
|
if (isBlocked()) return;
|
|
532
544
|
const t = ts();
|
|
533
545
|
if (t - S.lastBurstTs < BURST_COOLDOWN_MS) return;
|
|
534
|
-
S.lastBurstTs
|
|
535
|
-
|
|
536
|
-
const pk = pageKey();
|
|
537
|
-
S.pageKey = pk;
|
|
546
|
+
S.lastBurstTs = t;
|
|
547
|
+
S.pageKey = pageKey();
|
|
538
548
|
S.burstDeadline = t + 2000;
|
|
539
549
|
|
|
540
550
|
if (S.burstActive) return;
|
|
@@ -542,7 +552,7 @@
|
|
|
542
552
|
S.burstCount = 0;
|
|
543
553
|
|
|
544
554
|
const step = () => {
|
|
545
|
-
if (pageKey() !==
|
|
555
|
+
if (pageKey() !== S.pageKey || isBlocked() || ts() > S.burstDeadline || S.burstCount >= 8) {
|
|
546
556
|
S.burstActive = false; return;
|
|
547
557
|
}
|
|
548
558
|
S.burstCount++;
|
|
@@ -558,9 +568,7 @@
|
|
|
558
568
|
|
|
559
569
|
function cleanup() {
|
|
560
570
|
blockedUntil = ts() + 1500;
|
|
561
|
-
poolsReady = false;
|
|
562
571
|
mutate(() => document.querySelectorAll(`.${WRAP_CLASS}`).forEach(dropWrap));
|
|
563
|
-
wrapByKey.clear();
|
|
564
572
|
S.cfg = null;
|
|
565
573
|
S.pools = { topics: [], posts: [], categories: [] };
|
|
566
574
|
S.cursors = { topics: 0, posts: 0, categories: 0 };
|
|
@@ -573,7 +581,7 @@
|
|
|
573
581
|
S.runQueued = false;
|
|
574
582
|
}
|
|
575
583
|
|
|
576
|
-
// ──
|
|
584
|
+
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
577
585
|
|
|
578
586
|
function ensureDomObserver() {
|
|
579
587
|
if (S.domObs) return;
|
|
@@ -581,7 +589,6 @@
|
|
|
581
589
|
S.domObs = new MutationObserver(muts => {
|
|
582
590
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
583
591
|
for (const m of muts) {
|
|
584
|
-
if (!m.addedNodes?.length) continue;
|
|
585
592
|
for (const n of m.addedNodes) {
|
|
586
593
|
if (n.nodeType !== 1) continue;
|
|
587
594
|
if (allSel.some(s => n.matches?.(s) || n.querySelector?.(s))) {
|
|
@@ -611,7 +618,8 @@
|
|
|
611
618
|
|
|
612
619
|
function ensureTcfLocator() {
|
|
613
620
|
// L'iframe __tcfapiLocator route les appels postMessage du CMP.
|
|
614
|
-
// En navigation ajaxify, NodeBB peut la retirer → erreurs CMP.
|
|
621
|
+
// En navigation ajaxify, NodeBB peut la retirer du DOM → erreurs CMP.
|
|
622
|
+
// Un MutationObserver la recrée dès qu'elle disparaît.
|
|
615
623
|
try {
|
|
616
624
|
if (!window.__tcfapi && !window.__cmp) return;
|
|
617
625
|
const inject = () => {
|
|
@@ -650,7 +658,7 @@
|
|
|
650
658
|
}
|
|
651
659
|
}
|
|
652
660
|
|
|
653
|
-
// ── Bindings
|
|
661
|
+
// ── Bindings ───────────────────────────────────────────────────────────────
|
|
654
662
|
|
|
655
663
|
function bindNodeBB() {
|
|
656
664
|
const $ = window.jQuery;
|
|
@@ -661,16 +669,15 @@
|
|
|
661
669
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
662
670
|
S.pageKey = pageKey();
|
|
663
671
|
blockedUntil = 0;
|
|
664
|
-
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
665
|
-
getIO(); ensureDomObserver(); requestBurst();
|
|
672
|
+
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
673
|
+
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
666
674
|
});
|
|
667
675
|
|
|
668
|
-
const
|
|
669
|
-
'action:ajaxify.contentLoaded',
|
|
670
|
-
'action:posts.loaded', 'action:topics.loaded',
|
|
676
|
+
const burstEvts = [
|
|
677
|
+
'action:ajaxify.contentLoaded', 'action:posts.loaded', 'action:topics.loaded',
|
|
671
678
|
'action:categories.loaded', 'action:category.loaded', 'action:topic.loaded',
|
|
672
679
|
].map(e => `${e}.nbbEzoic`).join(' ');
|
|
673
|
-
$(window).on(
|
|
680
|
+
$(window).on(burstEvts, () => { if (!isBlocked()) requestBurst(); });
|
|
674
681
|
|
|
675
682
|
try {
|
|
676
683
|
require(['hooks'], hooks => {
|