nodebb-plugin-ezoic-infinite 1.7.89 → 1.7.91
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 +103 -56
- package/public/style.css +0 -14
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
const A_CREATED = 'data-ezoic-created'; // timestamp création ms
|
|
78
78
|
const A_SHOWN = 'data-ezoic-shown'; // timestamp dernier showAds ms
|
|
79
79
|
|
|
80
|
-
const EMPTY_CHECK_MS = 20_000; // délai avant collapse d'un wrap vide post-show
|
|
81
80
|
const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
|
|
82
81
|
const MAX_INSERTS_RUN = 6; // max insertions par appel runCore
|
|
83
82
|
const MAX_INFLIGHT = 4; // max showAds() simultanés
|
|
@@ -127,11 +126,14 @@
|
|
|
127
126
|
pending: [], // ids en attente de slot inflight
|
|
128
127
|
pendingSet: new Set(),
|
|
129
128
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
129
|
+
fillObsById: new Map(), // id -> MutationObserver
|
|
130
130
|
runQueued: false,
|
|
131
131
|
burstActive: false,
|
|
132
132
|
burstDeadline: 0,
|
|
133
133
|
burstCount: 0,
|
|
134
134
|
lastBurstTs: 0,
|
|
135
|
+
lastScrollY: 0,
|
|
136
|
+
scrollDir: 1, // 1=down, -1=up
|
|
135
137
|
};
|
|
136
138
|
|
|
137
139
|
let blockedUntil = 0;
|
|
@@ -142,6 +144,33 @@
|
|
|
142
144
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
143
145
|
const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
144
146
|
|
|
147
|
+
|
|
148
|
+
function unwatchPlaceholderFillById(id) {
|
|
149
|
+
const obs = S.fillObsById.get(id);
|
|
150
|
+
if (obs) { try { obs.disconnect(); } catch (_) {} S.fillObsById.delete(id); }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function uncollapseIfFilled(ph) {
|
|
154
|
+
try {
|
|
155
|
+
const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
|
|
156
|
+
if (!wrap) return false;
|
|
157
|
+
if (!isFilled(ph)) return false;
|
|
158
|
+
wrap.classList.remove('is-empty');
|
|
159
|
+
return true;
|
|
160
|
+
} catch (_) { return false; }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function watchPlaceholderFill(id) {
|
|
164
|
+
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
165
|
+
if (!ph?.isConnected || S.fillObsById.has(id)) return;
|
|
166
|
+
try {
|
|
167
|
+
const obs = new MutationObserver(() => { try { uncollapseIfFilled(ph); } catch (_) {} });
|
|
168
|
+
obs.observe(ph, { childList: true, subtree: true, attributes: true });
|
|
169
|
+
S.fillObsById.set(id, obs);
|
|
170
|
+
uncollapseIfFilled(ph);
|
|
171
|
+
} catch (_) {}
|
|
172
|
+
}
|
|
173
|
+
|
|
145
174
|
function mutate(fn) {
|
|
146
175
|
S.mutGuard++;
|
|
147
176
|
try { fn(); } finally { S.mutGuard--; }
|
|
@@ -282,26 +311,24 @@
|
|
|
282
311
|
return (w?.isConnected) ? w : null;
|
|
283
312
|
}
|
|
284
313
|
|
|
285
|
-
/**
|
|
286
|
-
* Libère les ids de wraps supprimés du DOM (virtualisation NodeBB / rerender).
|
|
287
|
-
* Sans ce sweep, mountedIds peut conserver des ids fantômes → pool épuisé
|
|
288
|
-
* après long scroll alors qu'aucun wrap recyclable n'existe encore en DOM.
|
|
289
|
-
*/
|
|
290
314
|
function sweepDeadWraps() {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
315
|
+
let freed = 0;
|
|
316
|
+
for (const [key, wrap] of Array.from(S.wrapByKey.entries())) {
|
|
317
|
+
if (wrap?.isConnected) continue;
|
|
318
|
+
const id = parseInt(wrap?.getAttribute?.(A_WRAPID) || '0', 10);
|
|
319
|
+
if (Number.isFinite(id) && id > 0) {
|
|
295
320
|
S.mountedIds.delete(id);
|
|
321
|
+
unwatchPlaceholderFillById(id);
|
|
296
322
|
S.lastShow.delete(id);
|
|
297
323
|
S.pendingSet.delete(id);
|
|
298
324
|
}
|
|
299
325
|
S.wrapByKey.delete(key);
|
|
326
|
+
freed++;
|
|
300
327
|
}
|
|
301
|
-
if (S.pending.length) {
|
|
302
|
-
S.pending = S.pending.filter(id => !S.
|
|
303
|
-
S.pendingSet = new Set(S.pending);
|
|
328
|
+
if (freed && S.pending.length) {
|
|
329
|
+
S.pending = S.pending.filter(id => !S.mountedIds.has(id));
|
|
304
330
|
}
|
|
331
|
+
return freed;
|
|
305
332
|
}
|
|
306
333
|
|
|
307
334
|
// ── Pool ───────────────────────────────────────────────────────────────────
|
|
@@ -335,26 +362,50 @@
|
|
|
335
362
|
typeof ez?.define !== 'function' ||
|
|
336
363
|
typeof ez?.displayMore !== 'function') return null;
|
|
337
364
|
|
|
338
|
-
const vh
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
365
|
+
const vh = window.innerHeight || 800;
|
|
366
|
+
const targetRect = targetEl?.getBoundingClientRect?.() || { top: vh, bottom: vh };
|
|
367
|
+
|
|
368
|
+
// Recyclage bidirectionnel :
|
|
369
|
+
// - scroll vers le bas -> recycle préférentiellement des wraps loin au-dessus
|
|
370
|
+
// - scroll vers le haut -> recycle préférentiellement des wraps loin en-dessous
|
|
371
|
+
// Fallback sur l'autre côté si aucun candidat.
|
|
372
|
+
const aboveThreshold = -vh; // wrap entièrement/suffisamment au-dessus
|
|
373
|
+
const belowThreshold = vh * 2; // wrap loin sous le viewport
|
|
374
|
+
|
|
375
|
+
let aboveEmpty = null, aboveEmptyBottom = Infinity;
|
|
376
|
+
let aboveFilled = null, aboveFilledBottom = Infinity;
|
|
377
|
+
let belowEmpty = null, belowEmptyTop = -Infinity;
|
|
378
|
+
let belowFilled = null, belowFilledTop = -Infinity;
|
|
344
379
|
|
|
345
|
-
|
|
380
|
+
for (const wrap of S.wrapByKey.values()) {
|
|
381
|
+
if (!wrap?.classList?.contains?.(klass)) continue;
|
|
346
382
|
try {
|
|
383
|
+
if (!wrap?.isConnected) continue;
|
|
347
384
|
const rect = wrap.getBoundingClientRect();
|
|
348
|
-
|
|
349
|
-
if (
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
385
|
+
|
|
386
|
+
if (rect.bottom < aboveThreshold) {
|
|
387
|
+
if (!isFilled(wrap)) {
|
|
388
|
+
if (rect.bottom < aboveEmptyBottom) { aboveEmptyBottom = rect.bottom; aboveEmpty = wrap; }
|
|
389
|
+
} else {
|
|
390
|
+
if (rect.bottom < aboveFilledBottom) { aboveFilledBottom = rect.bottom; aboveFilled = wrap; }
|
|
391
|
+
}
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (rect.top > belowThreshold) {
|
|
396
|
+
if (!isFilled(wrap)) {
|
|
397
|
+
if (rect.top > belowEmptyTop) { belowEmptyTop = rect.top; belowEmpty = wrap; }
|
|
398
|
+
} else {
|
|
399
|
+
if (rect.top > belowFilledTop) { belowFilledTop = rect.top; belowFilled = wrap; }
|
|
400
|
+
}
|
|
353
401
|
}
|
|
354
402
|
} catch (_) {}
|
|
355
|
-
}
|
|
403
|
+
}
|
|
356
404
|
|
|
357
|
-
const
|
|
405
|
+
const preferBelow = (S.scrollDir < 0) || (targetRect.top < vh * 0.5);
|
|
406
|
+
const pickAbove = () => aboveEmpty ?? aboveFilled;
|
|
407
|
+
const pickBelow = () => belowEmpty ?? belowFilled;
|
|
408
|
+
const best = preferBelow ? (pickBelow() ?? pickAbove()) : (pickAbove() ?? pickBelow());
|
|
358
409
|
if (!best) return null;
|
|
359
410
|
const id = parseInt(best.getAttribute(A_WRAPID), 10);
|
|
360
411
|
if (!Number.isFinite(id)) return null;
|
|
@@ -410,6 +461,7 @@
|
|
|
410
461
|
mutate(() => el.insertAdjacentElement('afterend', w));
|
|
411
462
|
S.mountedIds.add(id);
|
|
412
463
|
S.wrapByKey.set(key, w);
|
|
464
|
+
watchPlaceholderFill(id);
|
|
413
465
|
return w;
|
|
414
466
|
}
|
|
415
467
|
|
|
@@ -418,7 +470,7 @@
|
|
|
418
470
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
419
471
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
420
472
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
421
|
-
if (Number.isFinite(id)) S.mountedIds.delete(id);
|
|
473
|
+
if (Number.isFinite(id)) { S.mountedIds.delete(id); unwatchPlaceholderFillById(id); S.lastShow.delete(id); S.pendingSet.delete(id); }
|
|
422
474
|
const key = w.getAttribute(A_ANCHOR);
|
|
423
475
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
424
476
|
w.remove();
|
|
@@ -494,12 +546,7 @@
|
|
|
494
546
|
if (findWrap(key)) continue;
|
|
495
547
|
|
|
496
548
|
let id = pickId(poolKey);
|
|
497
|
-
if (!id) {
|
|
498
|
-
// Réessaie après sweep : des wraps ont pu être retirés du DOM (virtualisation)
|
|
499
|
-
// sans passer par dropWrap, laissant des ids fantômes dans mountedIds.
|
|
500
|
-
sweepDeadWraps();
|
|
501
|
-
id = pickId(poolKey);
|
|
502
|
-
}
|
|
549
|
+
if (!id) { sweepDeadWraps(); id = pickId(poolKey); }
|
|
503
550
|
if (id) {
|
|
504
551
|
const w = insertAfter(el, id, klass, key);
|
|
505
552
|
if (w) { observePh(id); inserted++; }
|
|
@@ -531,7 +578,9 @@
|
|
|
531
578
|
|
|
532
579
|
function observePh(id) {
|
|
533
580
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
534
|
-
if (ph?.isConnected)
|
|
581
|
+
if (!ph?.isConnected) return;
|
|
582
|
+
watchPlaceholderFill(id);
|
|
583
|
+
try { getIO()?.observe(ph); } catch (_) {}
|
|
535
584
|
}
|
|
536
585
|
|
|
537
586
|
function enqueueShow(id) {
|
|
@@ -571,6 +620,8 @@
|
|
|
571
620
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
572
621
|
if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
|
|
573
622
|
|
|
623
|
+
try { ph.closest?.(`.${WRAP_CLASS}`)?.classList.remove('is-empty'); } catch (_) {}
|
|
624
|
+
|
|
574
625
|
const t = ts();
|
|
575
626
|
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
576
627
|
S.lastShow.set(id, t);
|
|
@@ -581,7 +632,6 @@
|
|
|
581
632
|
const ez = window.ezstandalone;
|
|
582
633
|
const doShow = () => {
|
|
583
634
|
try { ez.showAds(id); } catch (_) {}
|
|
584
|
-
scheduleEmptyCheck(id, t);
|
|
585
635
|
setTimeout(() => { clearTimeout(timer); release(); }, 700);
|
|
586
636
|
};
|
|
587
637
|
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
@@ -589,17 +639,6 @@
|
|
|
589
639
|
});
|
|
590
640
|
}
|
|
591
641
|
|
|
592
|
-
function scheduleEmptyCheck(id, showTs) {
|
|
593
|
-
setTimeout(() => {
|
|
594
|
-
try {
|
|
595
|
-
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
596
|
-
const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
|
|
597
|
-
if (!wrap || !ph?.isConnected) return;
|
|
598
|
-
if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
|
|
599
|
-
wrap.classList.toggle('is-empty', !isFilled(ph));
|
|
600
|
-
} catch (_) {}
|
|
601
|
-
}, EMPTY_CHECK_MS);
|
|
602
|
-
}
|
|
603
642
|
|
|
604
643
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
605
644
|
//
|
|
@@ -726,6 +765,8 @@
|
|
|
726
765
|
S.mountedIds.clear();
|
|
727
766
|
S.lastShow.clear();
|
|
728
767
|
S.wrapByKey.clear();
|
|
768
|
+
for (const obs of S.fillObsById.values()) { try { obs.disconnect(); } catch (_) {} }
|
|
769
|
+
S.fillObsById.clear();
|
|
729
770
|
S.inflight = 0;
|
|
730
771
|
S.pending = [];
|
|
731
772
|
S.pendingSet.clear();
|
|
@@ -740,26 +781,25 @@
|
|
|
740
781
|
const allSel = [SEL.post, SEL.topic, SEL.category];
|
|
741
782
|
S.domObs = new MutationObserver(muts => {
|
|
742
783
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
743
|
-
let sawRelevantAdd = false;
|
|
744
|
-
let sawWrapRemoval = false;
|
|
745
784
|
for (const m of muts) {
|
|
746
|
-
|
|
785
|
+
let sawWrapRemoval = false;
|
|
786
|
+
for (const n of m.removedNodes || []) {
|
|
747
787
|
if (n.nodeType !== 1) continue;
|
|
748
|
-
|
|
749
|
-
sawWrapRemoval = true;
|
|
750
|
-
}
|
|
788
|
+
try {
|
|
789
|
+
if (n.matches?.(`.${WRAP_CLASS}`) || n.querySelector?.(`.${WRAP_CLASS}`)) { sawWrapRemoval = true; }
|
|
790
|
+
} catch (_) {}
|
|
751
791
|
}
|
|
792
|
+
if (sawWrapRemoval) sweepDeadWraps();
|
|
793
|
+
|
|
752
794
|
for (const n of m.addedNodes) {
|
|
753
795
|
if (n.nodeType !== 1) continue;
|
|
754
796
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
755
797
|
if (allSel.some(s => { try { return n.matches(s); } catch(_){return false;} }) ||
|
|
756
798
|
allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
|
|
757
|
-
|
|
799
|
+
requestBurst(); return;
|
|
758
800
|
}
|
|
759
801
|
}
|
|
760
802
|
}
|
|
761
|
-
if (sawWrapRemoval) sweepDeadWraps();
|
|
762
|
-
if (sawRelevantAdd) requestBurst();
|
|
763
803
|
});
|
|
764
804
|
try { S.domObs.observe(document.body, { childList: true, subtree: true }); } catch (_) {}
|
|
765
805
|
}
|
|
@@ -861,6 +901,12 @@
|
|
|
861
901
|
function bindScroll() {
|
|
862
902
|
let ticking = false;
|
|
863
903
|
window.addEventListener('scroll', () => {
|
|
904
|
+
try {
|
|
905
|
+
const y = window.scrollY || window.pageYOffset || 0;
|
|
906
|
+
const dy = y - (S.lastScrollY || 0);
|
|
907
|
+
if (Math.abs(dy) > 2) S.scrollDir = dy > 0 ? 1 : -1;
|
|
908
|
+
S.lastScrollY = y;
|
|
909
|
+
} catch (_) {}
|
|
864
910
|
if (ticking) return;
|
|
865
911
|
ticking = true;
|
|
866
912
|
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
@@ -870,6 +916,7 @@
|
|
|
870
916
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
871
917
|
|
|
872
918
|
S.pageKey = pageKey();
|
|
919
|
+
try { S.lastScrollY = window.scrollY || window.pageYOffset || 0; } catch (_) { S.lastScrollY = 0; }
|
|
873
920
|
muteConsole();
|
|
874
921
|
ensureTcfLocator();
|
|
875
922
|
warmNetwork();
|
package/public/style.css
CHANGED
|
@@ -56,20 +56,6 @@
|
|
|
56
56
|
top: auto !important;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/* ── État vide ────────────────────────────────────────────────────────────── */
|
|
60
|
-
/*
|
|
61
|
-
Ajouté 20s après showAds si aucun fill détecté.
|
|
62
|
-
Collapse à 1px (pas 0) : reste observable par l'IO si le fill arrive tard.
|
|
63
|
-
*/
|
|
64
|
-
.nodebb-ezoic-wrap.is-empty {
|
|
65
|
-
display: block !important;
|
|
66
|
-
height: 1px !important;
|
|
67
|
-
min-height: 1px !important;
|
|
68
|
-
max-height: 1px !important;
|
|
69
|
-
margin: 0 !important;
|
|
70
|
-
padding: 0 !important;
|
|
71
|
-
overflow: hidden !important;
|
|
72
|
-
}
|
|
73
59
|
|
|
74
60
|
/* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
|
|
75
61
|
.ezoic-ad {
|