nodebb-plugin-ezoic-infinite 1.8.10 → 1.8.11
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/library.js +26 -18
- package/package.json +2 -2
- package/public/client.js +168 -58
- package/public/style.css +5 -8
package/library.js
CHANGED
|
@@ -71,8 +71,9 @@ async function getSettings() {
|
|
|
71
71
|
|
|
72
72
|
async function isUserExcluded(uid, excludedGroups) {
|
|
73
73
|
if (!uid || !excludedGroups.length) return false;
|
|
74
|
+
const excluded = new Set((excludedGroups || []).map(String));
|
|
74
75
|
const userGroups = await groups.getUserGroups([uid]);
|
|
75
|
-
return (userGroups[0] || []).some(g =>
|
|
76
|
+
return (userGroups[0] || []).some(g => excluded.has(String(g.name)));
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// ── Scripts Ezoic ──────────────────────────────────────────────────────────
|
|
@@ -113,13 +114,34 @@ plugin.injectEzoicHead = async (data) => {
|
|
|
113
114
|
const uid = data.req?.uid ?? 0;
|
|
114
115
|
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
115
116
|
if (!excluded) {
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
const html = data.templateData.customHTML || '';
|
|
118
|
+
if (!html.includes('data-nbb-ezoic-head="1"')) {
|
|
119
|
+
data.templateData.customHTML = `<meta data-nbb-ezoic-head="1">` + EZOIC_SCRIPTS + html;
|
|
120
|
+
}
|
|
118
121
|
}
|
|
119
122
|
} catch (_) {}
|
|
120
123
|
return data;
|
|
121
124
|
};
|
|
122
125
|
|
|
126
|
+
|
|
127
|
+
function publicConfigPayload(settings, excluded) {
|
|
128
|
+
return {
|
|
129
|
+
excluded,
|
|
130
|
+
enableBetweenAds: settings.enableBetweenAds,
|
|
131
|
+
showFirstTopicAd: settings.showFirstTopicAd,
|
|
132
|
+
placeholderIds: settings.placeholderIds,
|
|
133
|
+
intervalPosts: settings.intervalPosts,
|
|
134
|
+
enableCategoryAds: settings.enableCategoryAds,
|
|
135
|
+
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
136
|
+
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
137
|
+
intervalCategories: settings.intervalCategories,
|
|
138
|
+
enableMessageAds: settings.enableMessageAds,
|
|
139
|
+
showFirstMessageAd: settings.showFirstMessageAd,
|
|
140
|
+
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
141
|
+
messageIntervalPosts: settings.messageIntervalPosts,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
plugin.init = async ({ router, middleware }) => {
|
|
124
146
|
async function render(req, res) {
|
|
125
147
|
const settings = await getSettings();
|
|
@@ -139,21 +161,7 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
139
161
|
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
140
162
|
const settings = await getSettings();
|
|
141
163
|
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
142
|
-
res.json(
|
|
143
|
-
excluded,
|
|
144
|
-
enableBetweenAds: settings.enableBetweenAds,
|
|
145
|
-
showFirstTopicAd: settings.showFirstTopicAd,
|
|
146
|
-
placeholderIds: settings.placeholderIds,
|
|
147
|
-
intervalPosts: settings.intervalPosts,
|
|
148
|
-
enableCategoryAds: settings.enableCategoryAds,
|
|
149
|
-
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
150
|
-
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
151
|
-
intervalCategories: settings.intervalCategories,
|
|
152
|
-
enableMessageAds: settings.enableMessageAds,
|
|
153
|
-
showFirstMessageAd: settings.showFirstMessageAd,
|
|
154
|
-
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
155
|
-
messageIntervalPosts: settings.messageIntervalPosts,
|
|
156
|
-
});
|
|
164
|
+
res.json(publicConfigPayload(settings, excluded));
|
|
157
165
|
});
|
|
158
166
|
};
|
|
159
167
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-ezoic-infinite",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.11",
|
|
4
4
|
"description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,4 +18,4 @@
|
|
|
18
18
|
"compatibility": "^4.0.0"
|
|
19
19
|
},
|
|
20
20
|
"private": false
|
|
21
|
-
}
|
|
21
|
+
}
|
package/public/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — client.js v52
|
|
3
3
|
*
|
|
4
4
|
* Historique des corrections majeures
|
|
5
5
|
* ────────────────────────────────────
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
*
|
|
33
33
|
* v34 moveDistantWrap — voir v38.
|
|
34
34
|
*
|
|
35
|
+
* v52 Clean architecture interne (modules logiques, cache DOM, bridge Ezoic).
|
|
36
|
+
*
|
|
35
37
|
* v50 Suppression de bindLoginCheck() : NodeBB fait un rechargement complet
|
|
36
38
|
* après login — filter:middleware.renderHeader re-évalue l'exclusion au
|
|
37
39
|
* rechargement. Redondant depuis le fix normalizeExcludedGroups (v49).
|
|
@@ -81,11 +83,11 @@
|
|
|
81
83
|
const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
|
|
82
84
|
const MAX_INSERTS_RUN = 10; // plus réactif si NodeBB injecte en rafale
|
|
83
85
|
const MAX_INFLIGHT = 2; // ids max simultanés en vol (garde-fou)
|
|
84
|
-
const MAX_SHOW_BATCH =
|
|
86
|
+
const MAX_SHOW_BATCH = 4; // ids max par appel showAds(...ids)
|
|
85
87
|
const SHOW_THROTTLE_MS = 500; // anti-spam showAds() par id (plus réactif)
|
|
86
88
|
const SHOW_RELEASE_MS = 300; // relâche inflight après showAds() batché
|
|
87
89
|
const SHOW_FAILSAFE_MS = 7000; // relâche forcée si stack pub lente
|
|
88
|
-
const BATCH_FLUSH_MS =
|
|
90
|
+
const BATCH_FLUSH_MS = 30; // micro-buffer pour regrouper les ids proches
|
|
89
91
|
const MAX_DESTROY_BATCH = 4; // ids max par destroyPlaceholders(...ids)
|
|
90
92
|
const DESTROY_FLUSH_MS = 30; // micro-buffer destroy pour lisser les rafales
|
|
91
93
|
const BURST_COOLDOWN_MS = 100; // délai min entre deux déclenchements de burst
|
|
@@ -149,6 +151,9 @@
|
|
|
149
151
|
burstDeadline: 0,
|
|
150
152
|
burstCount: 0,
|
|
151
153
|
lastBurstTs: 0,
|
|
154
|
+
ioViewportMode: isMobile() ? 'm' : 'd',
|
|
155
|
+
domRev: 0,
|
|
156
|
+
domCache: new Map(),
|
|
152
157
|
};
|
|
153
158
|
|
|
154
159
|
let blockedUntil = 0;
|
|
@@ -157,7 +162,61 @@
|
|
|
157
162
|
const isBlocked = () => ts() < blockedUntil;
|
|
158
163
|
const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
|
|
159
164
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
160
|
-
const
|
|
165
|
+
const DEBUG = (() => { try { return localStorage.getItem('nbbEzDebug') === '1'; } catch (_) { return false; } })();
|
|
166
|
+
const dbg = (...a) => { if (DEBUG) { try { console.debug('[nbb-ezoic]', ...a); } catch (_) {} } };
|
|
167
|
+
const isFilled = n => {
|
|
168
|
+
try {
|
|
169
|
+
if (!n?.querySelector) return false;
|
|
170
|
+
if (n.querySelector('iframe, ins, img, video, [data-google-container-id], .ezoic-ad, [id$="__container__"]')) return true;
|
|
171
|
+
const r = n.getBoundingClientRect?.();
|
|
172
|
+
return !!(r && r.height > 8);
|
|
173
|
+
} catch (_) { return false; }
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const Arch = {
|
|
177
|
+
cache: {
|
|
178
|
+
invalidate(reason) {
|
|
179
|
+
S.domRev++;
|
|
180
|
+
S.domCache.clear();
|
|
181
|
+
dbg('dom-cache.invalidate', reason || '');
|
|
182
|
+
},
|
|
183
|
+
queryAll(key, selector, mapFn) {
|
|
184
|
+
const cacheKey = `${S.domRev}:${key}`;
|
|
185
|
+
if (S.domCache.has(cacheKey)) return S.domCache.get(cacheKey);
|
|
186
|
+
const arr = Array.from(document.querySelectorAll(selector));
|
|
187
|
+
const out = typeof mapFn === 'function' ? arr.filter(Boolean).filter(mapFn) : arr;
|
|
188
|
+
S.domCache.set(cacheKey, out);
|
|
189
|
+
return out;
|
|
190
|
+
},
|
|
191
|
+
get(key, buildFn) {
|
|
192
|
+
const cacheKey = `${S.domRev}:${key}`;
|
|
193
|
+
if (S.domCache.has(cacheKey)) return S.domCache.get(cacheKey);
|
|
194
|
+
const v = buildFn();
|
|
195
|
+
S.domCache.set(cacheKey, v);
|
|
196
|
+
return v;
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
ezoic: {
|
|
200
|
+
ensure() {
|
|
201
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
202
|
+
return window.ezstandalone;
|
|
203
|
+
},
|
|
204
|
+
run(fn) {
|
|
205
|
+
const ez = this.ensure();
|
|
206
|
+
try { return (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(fn) : fn(); } catch (_) {}
|
|
207
|
+
},
|
|
208
|
+
call(method, ...args) {
|
|
209
|
+
const ez = this.ensure();
|
|
210
|
+
const fn = ez?.[method];
|
|
211
|
+
if (typeof fn !== 'function') return;
|
|
212
|
+
try { return fn.apply(ez, args); } catch (_) {}
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
page: {
|
|
216
|
+
kind() { return getKind(); },
|
|
217
|
+
key() { return pageKey(); },
|
|
218
|
+
},
|
|
219
|
+
};
|
|
161
220
|
|
|
162
221
|
function healFalseEmpty(root = document) {
|
|
163
222
|
try {
|
|
@@ -215,7 +274,8 @@
|
|
|
215
274
|
|
|
216
275
|
function mutate(fn) {
|
|
217
276
|
S.mutGuard++;
|
|
218
|
-
try { fn(); }
|
|
277
|
+
try { fn(); }
|
|
278
|
+
finally { S.mutGuard--; Arch.cache.invalidate('mutate'); }
|
|
219
279
|
}
|
|
220
280
|
function scheduleDestroyFlush() {
|
|
221
281
|
if (S.destroyBatchTimer) return;
|
|
@@ -235,11 +295,7 @@ function flushDestroyBatch() {
|
|
|
235
295
|
ids.push(id);
|
|
236
296
|
}
|
|
237
297
|
if (ids.length) {
|
|
238
|
-
try {
|
|
239
|
-
const ez = window.ezstandalone;
|
|
240
|
-
const run = () => { try { ez?.destroyPlaceholders?.(ids); } catch (_) {} };
|
|
241
|
-
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
242
|
-
} catch (_) {}
|
|
298
|
+
try { Arch.ezoic.run(() => Arch.ezoic.call('destroyPlaceholders', ids)); } catch (_) {}
|
|
243
299
|
}
|
|
244
300
|
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
245
301
|
}
|
|
@@ -255,20 +311,28 @@ function destroyEzoicId(id) {
|
|
|
255
311
|
scheduleDestroyFlush();
|
|
256
312
|
}
|
|
257
313
|
|
|
258
|
-
function
|
|
314
|
+
function queueDestroyIds(ids) {
|
|
315
|
+
for (const raw of (ids || [])) {
|
|
316
|
+
const id = parseInt(raw, 10);
|
|
317
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
318
|
+
if (!S.ezActiveIds.has(id) && !S.ezShownSinceDestroy.has(id)) continue;
|
|
319
|
+
if (S.destroyPendingSet.has(id)) continue;
|
|
320
|
+
S.destroyPending.push(id);
|
|
321
|
+
S.destroyPendingSet.add(id);
|
|
322
|
+
S.ezActiveIds.delete(id);
|
|
323
|
+
S.ezShownSinceDestroy.delete(id);
|
|
324
|
+
}
|
|
325
|
+
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function prepareIdsForShow(ids) {
|
|
259
329
|
const out = [];
|
|
260
|
-
const toDestroy = [];
|
|
261
330
|
const seen = new Set();
|
|
262
331
|
for (const raw of (ids || [])) {
|
|
263
332
|
const id = parseInt(raw, 10);
|
|
264
333
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
265
334
|
seen.add(id);
|
|
266
335
|
out.push(id);
|
|
267
|
-
if (S.ezShownSinceDestroy.has(id)) toDestroy.push(id);
|
|
268
|
-
}
|
|
269
|
-
if (toDestroy.length) {
|
|
270
|
-
try { window.ezstandalone?.destroyPlaceholders?.(toDestroy); } catch (_) {}
|
|
271
|
-
for (const id of toDestroy) S.ezShownSinceDestroy.delete(id);
|
|
272
336
|
}
|
|
273
337
|
return out;
|
|
274
338
|
}
|
|
@@ -314,6 +378,7 @@ function destroyBeforeReuse(ids) {
|
|
|
314
378
|
}
|
|
315
379
|
|
|
316
380
|
function getKind() {
|
|
381
|
+
return Arch.cache.get('kind', () => {
|
|
317
382
|
const p = location.pathname;
|
|
318
383
|
if (/^\/topic\//.test(p)) return 'topic';
|
|
319
384
|
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
@@ -322,12 +387,13 @@ function destroyBeforeReuse(ids) {
|
|
|
322
387
|
if (document.querySelector(SEL.post)) return 'topic';
|
|
323
388
|
if (document.querySelector(SEL.topic)) return 'categoryTopics';
|
|
324
389
|
return 'other';
|
|
390
|
+
});
|
|
325
391
|
}
|
|
326
392
|
|
|
327
393
|
// ── Items DOM ──────────────────────────────────────────────────────────────
|
|
328
394
|
|
|
329
395
|
function getPosts() {
|
|
330
|
-
return
|
|
396
|
+
return Arch.cache.queryAll('posts', SEL.post, el => {
|
|
331
397
|
if (!el.isConnected) return false;
|
|
332
398
|
if (!el.querySelector('[component="post/content"]')) return false;
|
|
333
399
|
const p = el.parentElement?.closest(SEL.post);
|
|
@@ -336,8 +402,8 @@ function destroyBeforeReuse(ids) {
|
|
|
336
402
|
});
|
|
337
403
|
}
|
|
338
404
|
|
|
339
|
-
const getTopics = () =>
|
|
340
|
-
const getCategories = () =>
|
|
405
|
+
const getTopics = () => Arch.cache.queryAll('topics', SEL.topic);
|
|
406
|
+
const getCategories = () => Arch.cache.queryAll('categories', SEL.category);
|
|
341
407
|
|
|
342
408
|
// ── Wraps — détection ──────────────────────────────────────────────────────
|
|
343
409
|
|
|
@@ -438,6 +504,7 @@ function destroyBeforeReuse(ids) {
|
|
|
438
504
|
S.mountedIds.delete(id);
|
|
439
505
|
S.pendingSet.delete(id);
|
|
440
506
|
S.lastShow.delete(id);
|
|
507
|
+
queueDestroyIds([id]);
|
|
441
508
|
S.ezActiveIds.delete(id);
|
|
442
509
|
}
|
|
443
510
|
}
|
|
@@ -454,7 +521,7 @@ function destroyBeforeReuse(ids) {
|
|
|
454
521
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
455
522
|
*/
|
|
456
523
|
function recycleAndMove(klass, targetEl, newKey) {
|
|
457
|
-
const ez =
|
|
524
|
+
const ez = Arch.ezoic.ensure();
|
|
458
525
|
if (typeof ez?.destroyPlaceholders !== 'function' ||
|
|
459
526
|
typeof ez?.define !== 'function' ||
|
|
460
527
|
typeof ez?.displayMore !== 'function') return null;
|
|
@@ -526,7 +593,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
526
593
|
};
|
|
527
594
|
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
528
595
|
const doDisplay = () => { try { ez.displayMore([id]); S.ezActiveIds.add(id); S.ezShownSinceDestroy.add(id); } catch (_) {} };
|
|
529
|
-
try {
|
|
596
|
+
try { Arch.ezoic.run(doDestroy); } catch (_) {}
|
|
530
597
|
|
|
531
598
|
return { id, wrap: best };
|
|
532
599
|
}
|
|
@@ -566,7 +633,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
566
633
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
567
634
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
568
635
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
569
|
-
if (Number.isFinite(id)) { S.ezActiveIds.delete(id); S.mountedIds.delete(id); }
|
|
636
|
+
if (Number.isFinite(id)) { queueDestroyIds([id]); S.ezActiveIds.delete(id); S.mountedIds.delete(id); }
|
|
570
637
|
const key = w.getAttribute(A_ANCHOR);
|
|
571
638
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
572
639
|
w.remove();
|
|
@@ -589,19 +656,18 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
589
656
|
// supprimerait les wraps, et provoquerait une réinjection en haut.
|
|
590
657
|
|
|
591
658
|
function pruneOrphansBetween() {
|
|
659
|
+
if (S.burstActive || (S.scrollSpeed || 0) > 1800) return;
|
|
592
660
|
const klass = 'ezoic-ad-between';
|
|
593
661
|
const cfg = KIND[klass];
|
|
662
|
+
const liveAnchors = new Set(Arch.cache.queryAll('prune-between-anchors', cfg.sel).map(el => el.getAttribute(cfg.anchorAttr)).filter(Boolean));
|
|
594
663
|
|
|
595
664
|
document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(w => {
|
|
596
665
|
const created = parseInt(w.getAttribute(A_CREATED) || '0', 10);
|
|
597
|
-
if (ts() - created < MIN_PRUNE_AGE_MS) return;
|
|
598
|
-
|
|
666
|
+
if (ts() - created < MIN_PRUNE_AGE_MS) return;
|
|
599
667
|
const key = w.getAttribute(A_ANCHOR) ?? '';
|
|
600
|
-
const sid = key.slice(klass.length + 1);
|
|
668
|
+
const sid = key.slice(klass.length + 1);
|
|
601
669
|
if (!sid) { mutate(() => dropWrap(w)); return; }
|
|
602
|
-
|
|
603
|
-
const anchorEl = document.querySelector(`${cfg.baseTag}[${cfg.anchorAttr}="${sid}"]`);
|
|
604
|
-
if (!anchorEl?.isConnected) mutate(() => dropWrap(w));
|
|
670
|
+
if (!liveAnchors.has(sid)) mutate(() => dropWrap(w));
|
|
605
671
|
});
|
|
606
672
|
}
|
|
607
673
|
|
|
@@ -647,6 +713,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
647
713
|
const w = insertAfter(el, id, klass, key);
|
|
648
714
|
if (w) { observePh(id); inserted++; }
|
|
649
715
|
} else {
|
|
716
|
+
// Recyclage agressif = source fréquente de churn visuel (surtout sticky/fixed)
|
|
717
|
+
if (klass === 'ezoic-ad-message') break;
|
|
650
718
|
const recycled = recycleAndMove(klass, el, key);
|
|
651
719
|
if (!recycled) break;
|
|
652
720
|
inserted++;
|
|
@@ -657,15 +725,31 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
657
725
|
|
|
658
726
|
// ── IntersectionObserver & Show ────────────────────────────────────────────
|
|
659
727
|
|
|
728
|
+
function ensureIOViewportMode() {
|
|
729
|
+
const mode = isMobile() ? 'm' : 'd';
|
|
730
|
+
if (S.io && S.ioViewportMode !== mode) {
|
|
731
|
+
try { S.io.disconnect(); } catch (_) {}
|
|
732
|
+
S.io = null;
|
|
733
|
+
}
|
|
734
|
+
S.ioViewportMode = mode;
|
|
735
|
+
}
|
|
736
|
+
|
|
660
737
|
function getIO() {
|
|
738
|
+
ensureIOViewportMode();
|
|
661
739
|
if (S.io) return S.io;
|
|
662
740
|
try {
|
|
663
741
|
S.io = new IntersectionObserver(entries => {
|
|
664
742
|
for (const e of entries) {
|
|
665
743
|
if (!e.isIntersecting) continue;
|
|
666
|
-
|
|
667
|
-
const id = parseInt(
|
|
668
|
-
if (Number.isFinite(id)
|
|
744
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
745
|
+
const id = parseInt(target?.getAttribute('data-ezoic-id'), 10);
|
|
746
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
747
|
+
const accepted = enqueueShow(id);
|
|
748
|
+
if (accepted) {
|
|
749
|
+
try { S.io?.unobserve(target); } catch (_) {}
|
|
750
|
+
} else if (target?.isConnected) {
|
|
751
|
+
setTimeout(() => { try { if (target.isConnected) S.io?.observe(target); } catch (_) {} }, 300);
|
|
752
|
+
}
|
|
669
753
|
}
|
|
670
754
|
}, { root: null, rootMargin: isMobile() ? IO_MARGIN_MOBILE : IO_MARGIN_DESKTOP, threshold: 0 });
|
|
671
755
|
} catch (_) { S.io = null; }
|
|
@@ -686,12 +770,15 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
686
770
|
}
|
|
687
771
|
|
|
688
772
|
function enqueueShow(id) {
|
|
689
|
-
if (!id || isBlocked()) return;
|
|
773
|
+
if (!id || isBlocked()) return false;
|
|
690
774
|
const n = parseInt(id, 10);
|
|
691
|
-
if (!Number.isFinite(n) || n <= 0) return;
|
|
692
|
-
if (ts() - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return;
|
|
775
|
+
if (!Number.isFinite(n) || n <= 0) return false;
|
|
776
|
+
if (ts() - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return false;
|
|
777
|
+
const ph = phEl(n);
|
|
778
|
+
if (!ph?.isConnected || isFilled(ph) || !hasSinglePlaceholder(n)) return false;
|
|
693
779
|
if (!S.pendingSet.has(n)) { S.pending.push(n); S.pendingSet.add(n); }
|
|
694
780
|
scheduleDrainQueue();
|
|
781
|
+
return true;
|
|
695
782
|
}
|
|
696
783
|
|
|
697
784
|
function scheduleDrainQueue() {
|
|
@@ -757,19 +844,18 @@ function startShowBatch(ids) {
|
|
|
757
844
|
|
|
758
845
|
if (!valid.length) { clearTimeout(timer); return release(); }
|
|
759
846
|
|
|
760
|
-
|
|
761
|
-
const ez = window.ezstandalone;
|
|
847
|
+
const ez = Arch.ezoic.ensure();
|
|
762
848
|
const doShow = () => {
|
|
763
|
-
const prepared =
|
|
849
|
+
const prepared = prepareIdsForShow(valid);
|
|
764
850
|
if (!prepared.length) { setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS); return; }
|
|
765
|
-
try { ez.showAds(...prepared); } catch (_) {}
|
|
851
|
+
try { ez.showAds(...prepared); } catch (_) { dbg('showAds failed', prepared); }
|
|
766
852
|
for (const id of prepared) {
|
|
767
853
|
S.ezActiveIds.add(id);
|
|
768
854
|
S.ezShownSinceDestroy.add(id);
|
|
769
855
|
}
|
|
770
856
|
setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS);
|
|
771
857
|
};
|
|
772
|
-
|
|
858
|
+
Arch.ezoic.run(doShow);
|
|
773
859
|
} catch (_) { clearTimeout(timer); release(); }
|
|
774
860
|
});
|
|
775
861
|
}
|
|
@@ -784,8 +870,7 @@ function startShowBatch(ids) {
|
|
|
784
870
|
function patchShowAds() {
|
|
785
871
|
const apply = () => {
|
|
786
872
|
try {
|
|
787
|
-
|
|
788
|
-
const ez = window.ezstandalone;
|
|
873
|
+
const ez = Arch.ezoic.ensure();
|
|
789
874
|
if (window.__nbbEzPatched || typeof ez.showAds !== 'function') return;
|
|
790
875
|
window.__nbbEzPatched = true;
|
|
791
876
|
const orig = ez.showAds.bind(ez);
|
|
@@ -812,8 +897,7 @@ function startShowBatch(ids) {
|
|
|
812
897
|
};
|
|
813
898
|
apply();
|
|
814
899
|
if (!window.__nbbEzPatched) {
|
|
815
|
-
|
|
816
|
-
(window.ezstandalone.cmd = window.ezstandalone.cmd || []).push(apply);
|
|
900
|
+
Arch.ezoic.run(apply);
|
|
817
901
|
}
|
|
818
902
|
}
|
|
819
903
|
|
|
@@ -922,6 +1006,7 @@ function startShowBatch(ids) {
|
|
|
922
1006
|
S.scrollSpeed = 0;
|
|
923
1007
|
S.lastScrollY = 0;
|
|
924
1008
|
S.lastScrollTs = 0;
|
|
1009
|
+
Arch.cache.invalidate('cleanup');
|
|
925
1010
|
}
|
|
926
1011
|
|
|
927
1012
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
@@ -939,14 +1024,14 @@ function startShowBatch(ids) {
|
|
|
939
1024
|
sawWrapRemoval = true;
|
|
940
1025
|
}
|
|
941
1026
|
}
|
|
942
|
-
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
1027
|
+
if (sawWrapRemoval) { Arch.cache.invalidate('wrap-removed'); queueSweepDeadWraps(); }
|
|
943
1028
|
for (const n of m.addedNodes) {
|
|
944
1029
|
if (n.nodeType !== 1) continue;
|
|
945
1030
|
try { healFalseEmpty(n); } catch (_) {}
|
|
946
1031
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
947
1032
|
if (allSel.some(s => { try { return n.matches(s); } catch(_){return false;} }) ||
|
|
948
1033
|
allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
|
|
949
|
-
requestBurst(); return;
|
|
1034
|
+
Arch.cache.invalidate('dom-added'); requestBurst(); return;
|
|
950
1035
|
}
|
|
951
1036
|
}
|
|
952
1037
|
}
|
|
@@ -1025,7 +1110,8 @@ function startShowBatch(ids) {
|
|
|
1025
1110
|
$(window).off('.nbbEzoic');
|
|
1026
1111
|
$(window).on('action:ajaxify.start.nbbEzoic', cleanup);
|
|
1027
1112
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
1028
|
-
S.pageKey =
|
|
1113
|
+
S.pageKey = Arch.page.key();
|
|
1114
|
+
Arch.cache.invalidate('ajaxify.end');
|
|
1029
1115
|
blockedUntil = 0;
|
|
1030
1116
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
1031
1117
|
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
@@ -1048,6 +1134,17 @@ function startShowBatch(ids) {
|
|
|
1048
1134
|
} catch (_) {}
|
|
1049
1135
|
}
|
|
1050
1136
|
|
|
1137
|
+
function bindResize() {
|
|
1138
|
+
let rT = 0;
|
|
1139
|
+
window.addEventListener('resize', () => {
|
|
1140
|
+
if (rT) clearTimeout(rT);
|
|
1141
|
+
rT = setTimeout(() => {
|
|
1142
|
+
ensureIOViewportMode();
|
|
1143
|
+
requestBurst();
|
|
1144
|
+
}, 120);
|
|
1145
|
+
}, { passive: true });
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1051
1148
|
function bindScroll() {
|
|
1052
1149
|
let ticking = false;
|
|
1053
1150
|
try {
|
|
@@ -1074,16 +1171,29 @@ function startShowBatch(ids) {
|
|
|
1074
1171
|
|
|
1075
1172
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
1076
1173
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1174
|
+
const App = {
|
|
1175
|
+
modules: {
|
|
1176
|
+
infra: { muteConsole, ensureTcfLocator, warmNetwork, patchShowAds },
|
|
1177
|
+
lifecycle: { ensureDomObserver, bindNodeBB, cleanup },
|
|
1178
|
+
viewport: { getIO, bindResize, bindScroll },
|
|
1179
|
+
scheduler: { requestBurst },
|
|
1180
|
+
},
|
|
1181
|
+
boot() {
|
|
1182
|
+
this.modules.infra.muteConsole();
|
|
1183
|
+
this.modules.infra.ensureTcfLocator();
|
|
1184
|
+
this.modules.infra.warmNetwork();
|
|
1185
|
+
this.modules.infra.patchShowAds();
|
|
1186
|
+
this.modules.viewport.getIO();
|
|
1187
|
+
this.modules.lifecycle.ensureDomObserver();
|
|
1188
|
+
this.modules.lifecycle.bindNodeBB();
|
|
1189
|
+
this.modules.viewport.bindResize();
|
|
1190
|
+
this.modules.viewport.bindScroll();
|
|
1191
|
+
blockedUntil = 0;
|
|
1192
|
+
this.modules.scheduler.requestBurst();
|
|
1193
|
+
},
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
S.pageKey = Arch.page.key();
|
|
1197
|
+
App.boot();
|
|
1088
1198
|
|
|
1089
1199
|
})();
|
package/public/style.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — style.css (
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — style.css (v21)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/* ── Wrapper ──────────────────────────────────────────────────────────────── */
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
width: 100%;
|
|
9
9
|
margin: 0 !important;
|
|
10
10
|
padding: 0 !important;
|
|
11
|
-
overflow:
|
|
12
|
-
contain:
|
|
11
|
+
overflow: visible;
|
|
12
|
+
contain: none;
|
|
13
|
+
position: relative;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
/* Placeholder : 1px minimum pour rester visible par l'IntersectionObserver */
|
|
@@ -50,11 +51,7 @@
|
|
|
50
51
|
position: absolute !important;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
/*
|
|
54
|
-
.nodebb-ezoic-wrap .ezads-sticky-intradiv {
|
|
55
|
-
position: static !important;
|
|
56
|
-
top: auto !important;
|
|
57
|
-
}
|
|
54
|
+
/* Compat sticky/fixed Ezoic : ne pas neutraliser globalement ici. */
|
|
58
55
|
|
|
59
56
|
/* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
|
|
60
57
|
.ezoic-ad {
|