nodebb-plugin-ezoic-infinite 1.8.9 → 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 +166 -61
- 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).
|
|
@@ -89,7 +91,6 @@
|
|
|
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
|
|
92
|
-
const MIN_RECYCLE_AGE_DESKTOP_MS = 3000; // évite le flash show→recycle trop rapide sur PC
|
|
93
94
|
|
|
94
95
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
95
96
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
@@ -150,6 +151,9 @@
|
|
|
150
151
|
burstDeadline: 0,
|
|
151
152
|
burstCount: 0,
|
|
152
153
|
lastBurstTs: 0,
|
|
154
|
+
ioViewportMode: isMobile() ? 'm' : 'd',
|
|
155
|
+
domRev: 0,
|
|
156
|
+
domCache: new Map(),
|
|
153
157
|
};
|
|
154
158
|
|
|
155
159
|
let blockedUntil = 0;
|
|
@@ -158,7 +162,61 @@
|
|
|
158
162
|
const isBlocked = () => ts() < blockedUntil;
|
|
159
163
|
const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
|
|
160
164
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
161
|
-
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
|
+
};
|
|
162
220
|
|
|
163
221
|
function healFalseEmpty(root = document) {
|
|
164
222
|
try {
|
|
@@ -216,7 +274,8 @@
|
|
|
216
274
|
|
|
217
275
|
function mutate(fn) {
|
|
218
276
|
S.mutGuard++;
|
|
219
|
-
try { fn(); }
|
|
277
|
+
try { fn(); }
|
|
278
|
+
finally { S.mutGuard--; Arch.cache.invalidate('mutate'); }
|
|
220
279
|
}
|
|
221
280
|
function scheduleDestroyFlush() {
|
|
222
281
|
if (S.destroyBatchTimer) return;
|
|
@@ -236,11 +295,7 @@ function flushDestroyBatch() {
|
|
|
236
295
|
ids.push(id);
|
|
237
296
|
}
|
|
238
297
|
if (ids.length) {
|
|
239
|
-
try {
|
|
240
|
-
const ez = window.ezstandalone;
|
|
241
|
-
const run = () => { try { ez?.destroyPlaceholders?.(ids); } catch (_) {} };
|
|
242
|
-
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
243
|
-
} catch (_) {}
|
|
298
|
+
try { Arch.ezoic.run(() => Arch.ezoic.call('destroyPlaceholders', ids)); } catch (_) {}
|
|
244
299
|
}
|
|
245
300
|
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
246
301
|
}
|
|
@@ -256,20 +311,28 @@ function destroyEzoicId(id) {
|
|
|
256
311
|
scheduleDestroyFlush();
|
|
257
312
|
}
|
|
258
313
|
|
|
259
|
-
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) {
|
|
260
329
|
const out = [];
|
|
261
|
-
const toDestroy = [];
|
|
262
330
|
const seen = new Set();
|
|
263
331
|
for (const raw of (ids || [])) {
|
|
264
332
|
const id = parseInt(raw, 10);
|
|
265
333
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
266
334
|
seen.add(id);
|
|
267
335
|
out.push(id);
|
|
268
|
-
if (S.ezShownSinceDestroy.has(id)) toDestroy.push(id);
|
|
269
|
-
}
|
|
270
|
-
if (toDestroy.length) {
|
|
271
|
-
try { window.ezstandalone?.destroyPlaceholders?.(toDestroy); } catch (_) {}
|
|
272
|
-
for (const id of toDestroy) S.ezShownSinceDestroy.delete(id);
|
|
273
336
|
}
|
|
274
337
|
return out;
|
|
275
338
|
}
|
|
@@ -315,6 +378,7 @@ function destroyBeforeReuse(ids) {
|
|
|
315
378
|
}
|
|
316
379
|
|
|
317
380
|
function getKind() {
|
|
381
|
+
return Arch.cache.get('kind', () => {
|
|
318
382
|
const p = location.pathname;
|
|
319
383
|
if (/^\/topic\//.test(p)) return 'topic';
|
|
320
384
|
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
@@ -323,12 +387,13 @@ function destroyBeforeReuse(ids) {
|
|
|
323
387
|
if (document.querySelector(SEL.post)) return 'topic';
|
|
324
388
|
if (document.querySelector(SEL.topic)) return 'categoryTopics';
|
|
325
389
|
return 'other';
|
|
390
|
+
});
|
|
326
391
|
}
|
|
327
392
|
|
|
328
393
|
// ── Items DOM ──────────────────────────────────────────────────────────────
|
|
329
394
|
|
|
330
395
|
function getPosts() {
|
|
331
|
-
return
|
|
396
|
+
return Arch.cache.queryAll('posts', SEL.post, el => {
|
|
332
397
|
if (!el.isConnected) return false;
|
|
333
398
|
if (!el.querySelector('[component="post/content"]')) return false;
|
|
334
399
|
const p = el.parentElement?.closest(SEL.post);
|
|
@@ -337,8 +402,8 @@ function destroyBeforeReuse(ids) {
|
|
|
337
402
|
});
|
|
338
403
|
}
|
|
339
404
|
|
|
340
|
-
const getTopics = () =>
|
|
341
|
-
const getCategories = () =>
|
|
405
|
+
const getTopics = () => Arch.cache.queryAll('topics', SEL.topic);
|
|
406
|
+
const getCategories = () => Arch.cache.queryAll('categories', SEL.category);
|
|
342
407
|
|
|
343
408
|
// ── Wraps — détection ──────────────────────────────────────────────────────
|
|
344
409
|
|
|
@@ -439,6 +504,7 @@ function destroyBeforeReuse(ids) {
|
|
|
439
504
|
S.mountedIds.delete(id);
|
|
440
505
|
S.pendingSet.delete(id);
|
|
441
506
|
S.lastShow.delete(id);
|
|
507
|
+
queueDestroyIds([id]);
|
|
442
508
|
S.ezActiveIds.delete(id);
|
|
443
509
|
}
|
|
444
510
|
}
|
|
@@ -455,7 +521,7 @@ function destroyBeforeReuse(ids) {
|
|
|
455
521
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
456
522
|
*/
|
|
457
523
|
function recycleAndMove(klass, targetEl, newKey) {
|
|
458
|
-
const ez =
|
|
524
|
+
const ez = Arch.ezoic.ensure();
|
|
459
525
|
if (typeof ez?.destroyPlaceholders !== 'function' ||
|
|
460
526
|
typeof ez?.define !== 'function' ||
|
|
461
527
|
typeof ez?.displayMore !== 'function') return null;
|
|
@@ -473,10 +539,6 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
473
539
|
for (const wrap of S.wrapByKey.values()) {
|
|
474
540
|
if (!wrap?.isConnected || !wrap.classList?.contains(WRAP_CLASS) || !wrap.classList.contains(klass)) continue;
|
|
475
541
|
try {
|
|
476
|
-
if (!isMobile()) {
|
|
477
|
-
const shownTs = parseInt(wrap.getAttribute(A_SHOWN) || '0', 10);
|
|
478
|
-
if (shownTs > 0 && (ts() - shownTs) < MIN_RECYCLE_AGE_DESKTOP_MS) continue;
|
|
479
|
-
}
|
|
480
542
|
const rect = wrap.getBoundingClientRect();
|
|
481
543
|
const isAbove = rect.bottom <= farAbove;
|
|
482
544
|
const isBelow = rect.top >= farBelow;
|
|
@@ -531,7 +593,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
531
593
|
};
|
|
532
594
|
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
533
595
|
const doDisplay = () => { try { ez.displayMore([id]); S.ezActiveIds.add(id); S.ezShownSinceDestroy.add(id); } catch (_) {} };
|
|
534
|
-
try {
|
|
596
|
+
try { Arch.ezoic.run(doDestroy); } catch (_) {}
|
|
535
597
|
|
|
536
598
|
return { id, wrap: best };
|
|
537
599
|
}
|
|
@@ -571,7 +633,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
571
633
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
572
634
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
573
635
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
574
|
-
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); }
|
|
575
637
|
const key = w.getAttribute(A_ANCHOR);
|
|
576
638
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
577
639
|
w.remove();
|
|
@@ -594,19 +656,18 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
594
656
|
// supprimerait les wraps, et provoquerait une réinjection en haut.
|
|
595
657
|
|
|
596
658
|
function pruneOrphansBetween() {
|
|
659
|
+
if (S.burstActive || (S.scrollSpeed || 0) > 1800) return;
|
|
597
660
|
const klass = 'ezoic-ad-between';
|
|
598
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));
|
|
599
663
|
|
|
600
664
|
document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(w => {
|
|
601
665
|
const created = parseInt(w.getAttribute(A_CREATED) || '0', 10);
|
|
602
|
-
if (ts() - created < MIN_PRUNE_AGE_MS) return;
|
|
603
|
-
|
|
666
|
+
if (ts() - created < MIN_PRUNE_AGE_MS) return;
|
|
604
667
|
const key = w.getAttribute(A_ANCHOR) ?? '';
|
|
605
|
-
const sid = key.slice(klass.length + 1);
|
|
668
|
+
const sid = key.slice(klass.length + 1);
|
|
606
669
|
if (!sid) { mutate(() => dropWrap(w)); return; }
|
|
607
|
-
|
|
608
|
-
const anchorEl = document.querySelector(`${cfg.baseTag}[${cfg.anchorAttr}="${sid}"]`);
|
|
609
|
-
if (!anchorEl?.isConnected) mutate(() => dropWrap(w));
|
|
670
|
+
if (!liveAnchors.has(sid)) mutate(() => dropWrap(w));
|
|
610
671
|
});
|
|
611
672
|
}
|
|
612
673
|
|
|
@@ -652,6 +713,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
652
713
|
const w = insertAfter(el, id, klass, key);
|
|
653
714
|
if (w) { observePh(id); inserted++; }
|
|
654
715
|
} else {
|
|
716
|
+
// Recyclage agressif = source fréquente de churn visuel (surtout sticky/fixed)
|
|
717
|
+
if (klass === 'ezoic-ad-message') break;
|
|
655
718
|
const recycled = recycleAndMove(klass, el, key);
|
|
656
719
|
if (!recycled) break;
|
|
657
720
|
inserted++;
|
|
@@ -662,15 +725,31 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
662
725
|
|
|
663
726
|
// ── IntersectionObserver & Show ────────────────────────────────────────────
|
|
664
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
|
+
|
|
665
737
|
function getIO() {
|
|
738
|
+
ensureIOViewportMode();
|
|
666
739
|
if (S.io) return S.io;
|
|
667
740
|
try {
|
|
668
741
|
S.io = new IntersectionObserver(entries => {
|
|
669
742
|
for (const e of entries) {
|
|
670
743
|
if (!e.isIntersecting) continue;
|
|
671
|
-
|
|
672
|
-
const id = parseInt(
|
|
673
|
-
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
|
+
}
|
|
674
753
|
}
|
|
675
754
|
}, { root: null, rootMargin: isMobile() ? IO_MARGIN_MOBILE : IO_MARGIN_DESKTOP, threshold: 0 });
|
|
676
755
|
} catch (_) { S.io = null; }
|
|
@@ -691,12 +770,15 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
691
770
|
}
|
|
692
771
|
|
|
693
772
|
function enqueueShow(id) {
|
|
694
|
-
if (!id || isBlocked()) return;
|
|
773
|
+
if (!id || isBlocked()) return false;
|
|
695
774
|
const n = parseInt(id, 10);
|
|
696
|
-
if (!Number.isFinite(n) || n <= 0) return;
|
|
697
|
-
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;
|
|
698
779
|
if (!S.pendingSet.has(n)) { S.pending.push(n); S.pendingSet.add(n); }
|
|
699
780
|
scheduleDrainQueue();
|
|
781
|
+
return true;
|
|
700
782
|
}
|
|
701
783
|
|
|
702
784
|
function scheduleDrainQueue() {
|
|
@@ -762,19 +844,18 @@ function startShowBatch(ids) {
|
|
|
762
844
|
|
|
763
845
|
if (!valid.length) { clearTimeout(timer); return release(); }
|
|
764
846
|
|
|
765
|
-
|
|
766
|
-
const ez = window.ezstandalone;
|
|
847
|
+
const ez = Arch.ezoic.ensure();
|
|
767
848
|
const doShow = () => {
|
|
768
|
-
const prepared =
|
|
849
|
+
const prepared = prepareIdsForShow(valid);
|
|
769
850
|
if (!prepared.length) { setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS); return; }
|
|
770
|
-
try { ez.showAds(...prepared); } catch (_) {}
|
|
851
|
+
try { ez.showAds(...prepared); } catch (_) { dbg('showAds failed', prepared); }
|
|
771
852
|
for (const id of prepared) {
|
|
772
853
|
S.ezActiveIds.add(id);
|
|
773
854
|
S.ezShownSinceDestroy.add(id);
|
|
774
855
|
}
|
|
775
856
|
setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS);
|
|
776
857
|
};
|
|
777
|
-
|
|
858
|
+
Arch.ezoic.run(doShow);
|
|
778
859
|
} catch (_) { clearTimeout(timer); release(); }
|
|
779
860
|
});
|
|
780
861
|
}
|
|
@@ -789,8 +870,7 @@ function startShowBatch(ids) {
|
|
|
789
870
|
function patchShowAds() {
|
|
790
871
|
const apply = () => {
|
|
791
872
|
try {
|
|
792
|
-
|
|
793
|
-
const ez = window.ezstandalone;
|
|
873
|
+
const ez = Arch.ezoic.ensure();
|
|
794
874
|
if (window.__nbbEzPatched || typeof ez.showAds !== 'function') return;
|
|
795
875
|
window.__nbbEzPatched = true;
|
|
796
876
|
const orig = ez.showAds.bind(ez);
|
|
@@ -817,8 +897,7 @@ function startShowBatch(ids) {
|
|
|
817
897
|
};
|
|
818
898
|
apply();
|
|
819
899
|
if (!window.__nbbEzPatched) {
|
|
820
|
-
|
|
821
|
-
(window.ezstandalone.cmd = window.ezstandalone.cmd || []).push(apply);
|
|
900
|
+
Arch.ezoic.run(apply);
|
|
822
901
|
}
|
|
823
902
|
}
|
|
824
903
|
|
|
@@ -927,6 +1006,7 @@ function startShowBatch(ids) {
|
|
|
927
1006
|
S.scrollSpeed = 0;
|
|
928
1007
|
S.lastScrollY = 0;
|
|
929
1008
|
S.lastScrollTs = 0;
|
|
1009
|
+
Arch.cache.invalidate('cleanup');
|
|
930
1010
|
}
|
|
931
1011
|
|
|
932
1012
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
@@ -944,14 +1024,14 @@ function startShowBatch(ids) {
|
|
|
944
1024
|
sawWrapRemoval = true;
|
|
945
1025
|
}
|
|
946
1026
|
}
|
|
947
|
-
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
1027
|
+
if (sawWrapRemoval) { Arch.cache.invalidate('wrap-removed'); queueSweepDeadWraps(); }
|
|
948
1028
|
for (const n of m.addedNodes) {
|
|
949
1029
|
if (n.nodeType !== 1) continue;
|
|
950
1030
|
try { healFalseEmpty(n); } catch (_) {}
|
|
951
1031
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
952
1032
|
if (allSel.some(s => { try { return n.matches(s); } catch(_){return false;} }) ||
|
|
953
1033
|
allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
|
|
954
|
-
requestBurst(); return;
|
|
1034
|
+
Arch.cache.invalidate('dom-added'); requestBurst(); return;
|
|
955
1035
|
}
|
|
956
1036
|
}
|
|
957
1037
|
}
|
|
@@ -1030,7 +1110,8 @@ function startShowBatch(ids) {
|
|
|
1030
1110
|
$(window).off('.nbbEzoic');
|
|
1031
1111
|
$(window).on('action:ajaxify.start.nbbEzoic', cleanup);
|
|
1032
1112
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
1033
|
-
S.pageKey =
|
|
1113
|
+
S.pageKey = Arch.page.key();
|
|
1114
|
+
Arch.cache.invalidate('ajaxify.end');
|
|
1034
1115
|
blockedUntil = 0;
|
|
1035
1116
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
1036
1117
|
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
@@ -1053,6 +1134,17 @@ function startShowBatch(ids) {
|
|
|
1053
1134
|
} catch (_) {}
|
|
1054
1135
|
}
|
|
1055
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
|
+
|
|
1056
1148
|
function bindScroll() {
|
|
1057
1149
|
let ticking = false;
|
|
1058
1150
|
try {
|
|
@@ -1079,16 +1171,29 @@ function startShowBatch(ids) {
|
|
|
1079
1171
|
|
|
1080
1172
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
1081
1173
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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();
|
|
1093
1198
|
|
|
1094
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 {
|