nodebb-plugin-ezoic-infinite 1.8.10 → 1.8.12
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 +188 -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.12",
|
|
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.1
|
|
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,10 @@
|
|
|
149
151
|
burstDeadline: 0,
|
|
150
152
|
burstCount: 0,
|
|
151
153
|
lastBurstTs: 0,
|
|
154
|
+
ioViewportMode: isMobile() ? 'm' : 'd',
|
|
155
|
+
domRev: 0,
|
|
156
|
+
domCache: new Map(),
|
|
157
|
+
cacheFrameArmed: false,
|
|
152
158
|
};
|
|
153
159
|
|
|
154
160
|
let blockedUntil = 0;
|
|
@@ -157,7 +163,81 @@
|
|
|
157
163
|
const isBlocked = () => ts() < blockedUntil;
|
|
158
164
|
const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
|
|
159
165
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
160
|
-
const
|
|
166
|
+
const DEBUG = (() => { try { return localStorage.getItem('nbbEzDebug') === '1'; } catch (_) { return false; } })();
|
|
167
|
+
const dbg = (...a) => { if (DEBUG) { try { console.debug('[nbb-ezoic]', ...a); } catch (_) {} } };
|
|
168
|
+
const isFilled = n => {
|
|
169
|
+
try {
|
|
170
|
+
if (!n?.querySelector) return false;
|
|
171
|
+
if (n.querySelector('iframe, ins, img, video, [data-google-container-id], .ezoic-ad, [id$="__container__"]')) return true;
|
|
172
|
+
const r = n.getBoundingClientRect?.();
|
|
173
|
+
return !!(r && r.height > 8);
|
|
174
|
+
} catch (_) { return false; }
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const Arch = {
|
|
178
|
+
cache: {
|
|
179
|
+
// v53: frame-only micro-cache (safe for NodeBB progressive renders).
|
|
180
|
+
// We only cache DOM query results until the next animation frame, and any mutation invalidates immediately.
|
|
181
|
+
_armFrameClear() {
|
|
182
|
+
if (S.cacheFrameArmed) return;
|
|
183
|
+
S.cacheFrameArmed = true;
|
|
184
|
+
requestAnimationFrame(() => {
|
|
185
|
+
S.cacheFrameArmed = false;
|
|
186
|
+
try { S.domCache.clear(); } catch (_) {}
|
|
187
|
+
dbg('dom-cache.clear', 'raf');
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
invalidate(reason) {
|
|
191
|
+
try { S.domRev++; S.domCache.clear(); } catch (_) {}
|
|
192
|
+
dbg('dom-cache.invalidate', reason || '');
|
|
193
|
+
},
|
|
194
|
+
queryAll(key, selector, filterFn) {
|
|
195
|
+
const k = `q:${S.domRev}:${key}:${selector}`;
|
|
196
|
+
try {
|
|
197
|
+
if (S.domCache.has(k)) return S.domCache.get(k);
|
|
198
|
+
} catch (_) {}
|
|
199
|
+
const arr = Array.from(document.querySelectorAll(selector));
|
|
200
|
+
const out = typeof filterFn === 'function' ? arr.filter(Boolean).filter(filterFn) : arr;
|
|
201
|
+
try {
|
|
202
|
+
this._armFrameClear();
|
|
203
|
+
S.domCache.set(k, out);
|
|
204
|
+
} catch (_) {}
|
|
205
|
+
return out;
|
|
206
|
+
},
|
|
207
|
+
get(key, buildFn) {
|
|
208
|
+
const k = `g:${S.domRev}:${key}`;
|
|
209
|
+
try {
|
|
210
|
+
if (S.domCache.has(k)) return S.domCache.get(k);
|
|
211
|
+
} catch (_) {}
|
|
212
|
+
const out = buildFn();
|
|
213
|
+
try {
|
|
214
|
+
this._armFrameClear();
|
|
215
|
+
S.domCache.set(k, out);
|
|
216
|
+
} catch (_) {}
|
|
217
|
+
return out;
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
ezoic: {
|
|
221
|
+
ensure() {
|
|
222
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
223
|
+
return window.ezstandalone;
|
|
224
|
+
},
|
|
225
|
+
run(fn) {
|
|
226
|
+
const ez = this.ensure();
|
|
227
|
+
try { return (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(fn) : fn(); } catch (_) {}
|
|
228
|
+
},
|
|
229
|
+
call(method, ...args) {
|
|
230
|
+
const ez = this.ensure();
|
|
231
|
+
const fn = ez?.[method];
|
|
232
|
+
if (typeof fn !== 'function') return;
|
|
233
|
+
try { return fn.apply(ez, args); } catch (_) {}
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
page: {
|
|
237
|
+
kind() { return Arch.cache.get('kind', getKind); },
|
|
238
|
+
key() { return pageKey(); },
|
|
239
|
+
},
|
|
240
|
+
};
|
|
161
241
|
|
|
162
242
|
function healFalseEmpty(root = document) {
|
|
163
243
|
try {
|
|
@@ -215,7 +295,8 @@
|
|
|
215
295
|
|
|
216
296
|
function mutate(fn) {
|
|
217
297
|
S.mutGuard++;
|
|
218
|
-
try { fn(); }
|
|
298
|
+
try { fn(); }
|
|
299
|
+
finally { S.mutGuard--; Arch.cache.invalidate('mutate'); }
|
|
219
300
|
}
|
|
220
301
|
function scheduleDestroyFlush() {
|
|
221
302
|
if (S.destroyBatchTimer) return;
|
|
@@ -235,11 +316,7 @@ function flushDestroyBatch() {
|
|
|
235
316
|
ids.push(id);
|
|
236
317
|
}
|
|
237
318
|
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 (_) {}
|
|
319
|
+
try { Arch.ezoic.run(() => Arch.ezoic.call('destroyPlaceholders', ids)); } catch (_) {}
|
|
243
320
|
}
|
|
244
321
|
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
245
322
|
}
|
|
@@ -255,20 +332,28 @@ function destroyEzoicId(id) {
|
|
|
255
332
|
scheduleDestroyFlush();
|
|
256
333
|
}
|
|
257
334
|
|
|
258
|
-
function
|
|
335
|
+
function queueDestroyIds(ids) {
|
|
336
|
+
for (const raw of (ids || [])) {
|
|
337
|
+
const id = parseInt(raw, 10);
|
|
338
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
339
|
+
if (!S.ezActiveIds.has(id) && !S.ezShownSinceDestroy.has(id)) continue;
|
|
340
|
+
if (S.destroyPendingSet.has(id)) continue;
|
|
341
|
+
S.destroyPending.push(id);
|
|
342
|
+
S.destroyPendingSet.add(id);
|
|
343
|
+
S.ezActiveIds.delete(id);
|
|
344
|
+
S.ezShownSinceDestroy.delete(id);
|
|
345
|
+
}
|
|
346
|
+
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function prepareIdsForShow(ids) {
|
|
259
350
|
const out = [];
|
|
260
|
-
const toDestroy = [];
|
|
261
351
|
const seen = new Set();
|
|
262
352
|
for (const raw of (ids || [])) {
|
|
263
353
|
const id = parseInt(raw, 10);
|
|
264
354
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
265
355
|
seen.add(id);
|
|
266
356
|
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
357
|
}
|
|
273
358
|
return out;
|
|
274
359
|
}
|
|
@@ -327,7 +412,7 @@ function destroyBeforeReuse(ids) {
|
|
|
327
412
|
// ── Items DOM ──────────────────────────────────────────────────────────────
|
|
328
413
|
|
|
329
414
|
function getPosts() {
|
|
330
|
-
return
|
|
415
|
+
return Arch.cache.queryAll('posts', SEL.post, el => {
|
|
331
416
|
if (!el.isConnected) return false;
|
|
332
417
|
if (!el.querySelector('[component="post/content"]')) return false;
|
|
333
418
|
const p = el.parentElement?.closest(SEL.post);
|
|
@@ -336,8 +421,8 @@ function destroyBeforeReuse(ids) {
|
|
|
336
421
|
});
|
|
337
422
|
}
|
|
338
423
|
|
|
339
|
-
const getTopics = () =>
|
|
340
|
-
const getCategories = () =>
|
|
424
|
+
const getTopics = () => Arch.cache.queryAll('topics', SEL.topic);
|
|
425
|
+
const getCategories = () => Arch.cache.queryAll('categories', SEL.category);
|
|
341
426
|
|
|
342
427
|
// ── Wraps — détection ──────────────────────────────────────────────────────
|
|
343
428
|
|
|
@@ -438,6 +523,7 @@ function destroyBeforeReuse(ids) {
|
|
|
438
523
|
S.mountedIds.delete(id);
|
|
439
524
|
S.pendingSet.delete(id);
|
|
440
525
|
S.lastShow.delete(id);
|
|
526
|
+
queueDestroyIds([id]);
|
|
441
527
|
S.ezActiveIds.delete(id);
|
|
442
528
|
}
|
|
443
529
|
}
|
|
@@ -454,7 +540,7 @@ function destroyBeforeReuse(ids) {
|
|
|
454
540
|
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
455
541
|
*/
|
|
456
542
|
function recycleAndMove(klass, targetEl, newKey) {
|
|
457
|
-
const ez =
|
|
543
|
+
const ez = Arch.ezoic.ensure();
|
|
458
544
|
if (typeof ez?.destroyPlaceholders !== 'function' ||
|
|
459
545
|
typeof ez?.define !== 'function' ||
|
|
460
546
|
typeof ez?.displayMore !== 'function') return null;
|
|
@@ -526,7 +612,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
526
612
|
};
|
|
527
613
|
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
528
614
|
const doDisplay = () => { try { ez.displayMore([id]); S.ezActiveIds.add(id); S.ezShownSinceDestroy.add(id); } catch (_) {} };
|
|
529
|
-
try {
|
|
615
|
+
try { Arch.ezoic.run(doDestroy); } catch (_) {}
|
|
530
616
|
|
|
531
617
|
return { id, wrap: best };
|
|
532
618
|
}
|
|
@@ -566,7 +652,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
566
652
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
567
653
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
568
654
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
569
|
-
if (Number.isFinite(id)) { S.ezActiveIds.delete(id); S.mountedIds.delete(id); }
|
|
655
|
+
if (Number.isFinite(id)) { queueDestroyIds([id]); S.ezActiveIds.delete(id); S.mountedIds.delete(id); }
|
|
570
656
|
const key = w.getAttribute(A_ANCHOR);
|
|
571
657
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
572
658
|
w.remove();
|
|
@@ -589,19 +675,18 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
589
675
|
// supprimerait les wraps, et provoquerait une réinjection en haut.
|
|
590
676
|
|
|
591
677
|
function pruneOrphansBetween() {
|
|
678
|
+
if (S.burstActive || (S.scrollSpeed || 0) > 1800) return;
|
|
592
679
|
const klass = 'ezoic-ad-between';
|
|
593
680
|
const cfg = KIND[klass];
|
|
681
|
+
const liveAnchors = new Set(Arch.cache.queryAll('prune-between-anchors', cfg.sel).map(el => el.getAttribute(cfg.anchorAttr)).filter(Boolean));
|
|
594
682
|
|
|
595
683
|
document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(w => {
|
|
596
684
|
const created = parseInt(w.getAttribute(A_CREATED) || '0', 10);
|
|
597
|
-
if (ts() - created < MIN_PRUNE_AGE_MS) return;
|
|
598
|
-
|
|
685
|
+
if (ts() - created < MIN_PRUNE_AGE_MS) return;
|
|
599
686
|
const key = w.getAttribute(A_ANCHOR) ?? '';
|
|
600
|
-
const sid = key.slice(klass.length + 1);
|
|
687
|
+
const sid = key.slice(klass.length + 1);
|
|
601
688
|
if (!sid) { mutate(() => dropWrap(w)); return; }
|
|
602
|
-
|
|
603
|
-
const anchorEl = document.querySelector(`${cfg.baseTag}[${cfg.anchorAttr}="${sid}"]`);
|
|
604
|
-
if (!anchorEl?.isConnected) mutate(() => dropWrap(w));
|
|
689
|
+
if (!liveAnchors.has(sid)) mutate(() => dropWrap(w));
|
|
605
690
|
});
|
|
606
691
|
}
|
|
607
692
|
|
|
@@ -647,6 +732,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
647
732
|
const w = insertAfter(el, id, klass, key);
|
|
648
733
|
if (w) { observePh(id); inserted++; }
|
|
649
734
|
} else {
|
|
735
|
+
// Recyclage agressif = source fréquente de churn visuel (surtout sticky/fixed)
|
|
736
|
+
if (klass === 'ezoic-ad-message') break;
|
|
650
737
|
const recycled = recycleAndMove(klass, el, key);
|
|
651
738
|
if (!recycled) break;
|
|
652
739
|
inserted++;
|
|
@@ -657,15 +744,31 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
657
744
|
|
|
658
745
|
// ── IntersectionObserver & Show ────────────────────────────────────────────
|
|
659
746
|
|
|
747
|
+
function ensureIOViewportMode() {
|
|
748
|
+
const mode = isMobile() ? 'm' : 'd';
|
|
749
|
+
if (S.io && S.ioViewportMode !== mode) {
|
|
750
|
+
try { S.io.disconnect(); } catch (_) {}
|
|
751
|
+
S.io = null;
|
|
752
|
+
}
|
|
753
|
+
S.ioViewportMode = mode;
|
|
754
|
+
}
|
|
755
|
+
|
|
660
756
|
function getIO() {
|
|
757
|
+
ensureIOViewportMode();
|
|
661
758
|
if (S.io) return S.io;
|
|
662
759
|
try {
|
|
663
760
|
S.io = new IntersectionObserver(entries => {
|
|
664
761
|
for (const e of entries) {
|
|
665
762
|
if (!e.isIntersecting) continue;
|
|
666
|
-
|
|
667
|
-
const id = parseInt(
|
|
668
|
-
if (Number.isFinite(id)
|
|
763
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
764
|
+
const id = parseInt(target?.getAttribute('data-ezoic-id'), 10);
|
|
765
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
766
|
+
const accepted = enqueueShow(id);
|
|
767
|
+
if (accepted) {
|
|
768
|
+
try { S.io?.unobserve(target); } catch (_) {}
|
|
769
|
+
} else if (target?.isConnected) {
|
|
770
|
+
setTimeout(() => { try { if (target.isConnected) S.io?.observe(target); } catch (_) {} }, 300);
|
|
771
|
+
}
|
|
669
772
|
}
|
|
670
773
|
}, { root: null, rootMargin: isMobile() ? IO_MARGIN_MOBILE : IO_MARGIN_DESKTOP, threshold: 0 });
|
|
671
774
|
} catch (_) { S.io = null; }
|
|
@@ -686,12 +789,15 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
686
789
|
}
|
|
687
790
|
|
|
688
791
|
function enqueueShow(id) {
|
|
689
|
-
if (!id || isBlocked()) return;
|
|
792
|
+
if (!id || isBlocked()) return false;
|
|
690
793
|
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;
|
|
794
|
+
if (!Number.isFinite(n) || n <= 0) return false;
|
|
795
|
+
if (ts() - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return false;
|
|
796
|
+
const ph = phEl(n);
|
|
797
|
+
if (!ph?.isConnected || isFilled(ph) || !hasSinglePlaceholder(n)) return false;
|
|
693
798
|
if (!S.pendingSet.has(n)) { S.pending.push(n); S.pendingSet.add(n); }
|
|
694
799
|
scheduleDrainQueue();
|
|
800
|
+
return true;
|
|
695
801
|
}
|
|
696
802
|
|
|
697
803
|
function scheduleDrainQueue() {
|
|
@@ -757,19 +863,18 @@ function startShowBatch(ids) {
|
|
|
757
863
|
|
|
758
864
|
if (!valid.length) { clearTimeout(timer); return release(); }
|
|
759
865
|
|
|
760
|
-
|
|
761
|
-
const ez = window.ezstandalone;
|
|
866
|
+
const ez = Arch.ezoic.ensure();
|
|
762
867
|
const doShow = () => {
|
|
763
|
-
const prepared =
|
|
868
|
+
const prepared = prepareIdsForShow(valid);
|
|
764
869
|
if (!prepared.length) { setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS); return; }
|
|
765
|
-
try { ez.showAds(...prepared); } catch (_) {}
|
|
870
|
+
try { ez.showAds(...prepared); } catch (_) { dbg('showAds failed', prepared); }
|
|
766
871
|
for (const id of prepared) {
|
|
767
872
|
S.ezActiveIds.add(id);
|
|
768
873
|
S.ezShownSinceDestroy.add(id);
|
|
769
874
|
}
|
|
770
875
|
setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS);
|
|
771
876
|
};
|
|
772
|
-
|
|
877
|
+
Arch.ezoic.run(doShow);
|
|
773
878
|
} catch (_) { clearTimeout(timer); release(); }
|
|
774
879
|
});
|
|
775
880
|
}
|
|
@@ -784,8 +889,7 @@ function startShowBatch(ids) {
|
|
|
784
889
|
function patchShowAds() {
|
|
785
890
|
const apply = () => {
|
|
786
891
|
try {
|
|
787
|
-
|
|
788
|
-
const ez = window.ezstandalone;
|
|
892
|
+
const ez = Arch.ezoic.ensure();
|
|
789
893
|
if (window.__nbbEzPatched || typeof ez.showAds !== 'function') return;
|
|
790
894
|
window.__nbbEzPatched = true;
|
|
791
895
|
const orig = ez.showAds.bind(ez);
|
|
@@ -812,8 +916,7 @@ function startShowBatch(ids) {
|
|
|
812
916
|
};
|
|
813
917
|
apply();
|
|
814
918
|
if (!window.__nbbEzPatched) {
|
|
815
|
-
|
|
816
|
-
(window.ezstandalone.cmd = window.ezstandalone.cmd || []).push(apply);
|
|
919
|
+
Arch.ezoic.run(apply);
|
|
817
920
|
}
|
|
818
921
|
}
|
|
819
922
|
|
|
@@ -920,8 +1023,10 @@ function startShowBatch(ids) {
|
|
|
920
1023
|
S.runQueued = false;
|
|
921
1024
|
S.sweepQueued = false;
|
|
922
1025
|
S.scrollSpeed = 0;
|
|
1026
|
+
S.cacheFrameArmed = false;
|
|
923
1027
|
S.lastScrollY = 0;
|
|
924
1028
|
S.lastScrollTs = 0;
|
|
1029
|
+
Arch.cache.invalidate('cleanup');
|
|
925
1030
|
}
|
|
926
1031
|
|
|
927
1032
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
@@ -939,14 +1044,14 @@ function startShowBatch(ids) {
|
|
|
939
1044
|
sawWrapRemoval = true;
|
|
940
1045
|
}
|
|
941
1046
|
}
|
|
942
|
-
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
1047
|
+
if (sawWrapRemoval) { Arch.cache.invalidate('wrap-removed'); queueSweepDeadWraps(); }
|
|
943
1048
|
for (const n of m.addedNodes) {
|
|
944
1049
|
if (n.nodeType !== 1) continue;
|
|
945
1050
|
try { healFalseEmpty(n); } catch (_) {}
|
|
946
1051
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
947
1052
|
if (allSel.some(s => { try { return n.matches(s); } catch(_){return false;} }) ||
|
|
948
1053
|
allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
|
|
949
|
-
requestBurst(); return;
|
|
1054
|
+
Arch.cache.invalidate('dom-added'); requestBurst(); return;
|
|
950
1055
|
}
|
|
951
1056
|
}
|
|
952
1057
|
}
|
|
@@ -1025,7 +1130,8 @@ function startShowBatch(ids) {
|
|
|
1025
1130
|
$(window).off('.nbbEzoic');
|
|
1026
1131
|
$(window).on('action:ajaxify.start.nbbEzoic', cleanup);
|
|
1027
1132
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
1028
|
-
S.pageKey =
|
|
1133
|
+
S.pageKey = Arch.page.key();
|
|
1134
|
+
Arch.cache.invalidate('ajaxify.end');
|
|
1029
1135
|
blockedUntil = 0;
|
|
1030
1136
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
1031
1137
|
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
@@ -1048,6 +1154,17 @@ function startShowBatch(ids) {
|
|
|
1048
1154
|
} catch (_) {}
|
|
1049
1155
|
}
|
|
1050
1156
|
|
|
1157
|
+
function bindResize() {
|
|
1158
|
+
let rT = 0;
|
|
1159
|
+
window.addEventListener('resize', () => {
|
|
1160
|
+
if (rT) clearTimeout(rT);
|
|
1161
|
+
rT = setTimeout(() => {
|
|
1162
|
+
ensureIOViewportMode();
|
|
1163
|
+
requestBurst();
|
|
1164
|
+
}, 120);
|
|
1165
|
+
}, { passive: true });
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1051
1168
|
function bindScroll() {
|
|
1052
1169
|
let ticking = false;
|
|
1053
1170
|
try {
|
|
@@ -1074,16 +1191,29 @@ function startShowBatch(ids) {
|
|
|
1074
1191
|
|
|
1075
1192
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
1076
1193
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1194
|
+
const App = {
|
|
1195
|
+
modules: {
|
|
1196
|
+
infra: { muteConsole, ensureTcfLocator, warmNetwork, patchShowAds },
|
|
1197
|
+
lifecycle: { ensureDomObserver, bindNodeBB, cleanup },
|
|
1198
|
+
viewport: { getIO, bindResize, bindScroll },
|
|
1199
|
+
scheduler: { requestBurst },
|
|
1200
|
+
},
|
|
1201
|
+
boot() {
|
|
1202
|
+
this.modules.infra.muteConsole();
|
|
1203
|
+
this.modules.infra.ensureTcfLocator();
|
|
1204
|
+
this.modules.infra.warmNetwork();
|
|
1205
|
+
this.modules.infra.patchShowAds();
|
|
1206
|
+
this.modules.viewport.getIO();
|
|
1207
|
+
this.modules.lifecycle.ensureDomObserver();
|
|
1208
|
+
this.modules.lifecycle.bindNodeBB();
|
|
1209
|
+
this.modules.viewport.bindResize();
|
|
1210
|
+
this.modules.viewport.bindScroll();
|
|
1211
|
+
blockedUntil = 0;
|
|
1212
|
+
this.modules.scheduler.requestBurst();
|
|
1213
|
+
},
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
S.pageKey = Arch.page.key();
|
|
1217
|
+
App.boot();
|
|
1088
1218
|
|
|
1089
1219
|
})();
|
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 {
|