nodebb-plugin-ezoic-infinite 1.7.99 → 1.8.1
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 +107 -38
- package/public/style.css +0 -15
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -77,17 +77,20 @@
|
|
|
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
|
-
|
|
80
|
+
// Tunables (stables en prod)
|
|
81
81
|
const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
|
|
82
|
-
const MAX_INSERTS_RUN =
|
|
83
|
-
const MAX_INFLIGHT =
|
|
82
|
+
const MAX_INSERTS_RUN = 10; // plus réactif si NodeBB injecte en rafale
|
|
83
|
+
const MAX_INFLIGHT = 2; // max showAds() simultanés (garde-fou)
|
|
84
84
|
const MAX_SHOW_BATCH = 4; // ids max par appel showAds(...ids)
|
|
85
|
-
const SHOW_THROTTLE_MS =
|
|
86
|
-
const
|
|
85
|
+
const SHOW_THROTTLE_MS = 500; // anti-spam showAds() par id (plus réactif)
|
|
86
|
+
const SHOW_RELEASE_MS = 300; // relâche inflight après showAds() batché
|
|
87
|
+
const SHOW_FAILSAFE_MS = 7000; // relâche forcée si stack pub lente
|
|
88
|
+
const BATCH_FLUSH_MS = 30; // micro-buffer pour regrouper les ids proches
|
|
89
|
+
const BURST_COOLDOWN_MS = 100; // délai min entre deux déclenchements de burst
|
|
87
90
|
|
|
88
91
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
89
|
-
const IO_MARGIN_DESKTOP = '
|
|
90
|
-
const IO_MARGIN_MOBILE = '
|
|
92
|
+
const IO_MARGIN_DESKTOP = '3000px 0px 3000px 0px';
|
|
93
|
+
const IO_MARGIN_MOBILE = '2000px 0px 2000px 0px';
|
|
91
94
|
|
|
92
95
|
const SEL = {
|
|
93
96
|
post: '[component="post"][data-pid]',
|
|
@@ -127,6 +130,8 @@
|
|
|
127
130
|
inflight: 0, // showAds() en cours
|
|
128
131
|
pending: [], // ids en attente de slot inflight
|
|
129
132
|
pendingSet: new Set(),
|
|
133
|
+
showBatchTimer: 0,
|
|
134
|
+
sweepQueued: false,
|
|
130
135
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
131
136
|
ezActiveIds: new Set(), // ids déjà passés à showAds/displayMore
|
|
132
137
|
scrollDir: 1, // 1=bas, -1=haut
|
|
@@ -148,6 +153,33 @@
|
|
|
148
153
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
149
154
|
const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
150
155
|
|
|
156
|
+
function phEl(id) {
|
|
157
|
+
return document.getElementById(`${PH_PREFIX}${id}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function hasSinglePlaceholder(id) {
|
|
161
|
+
try { return document.querySelectorAll(`#${PH_PREFIX}${id}`).length === 1; } catch (_) { return false; }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function canShowPlaceholderId(id, now = ts()) {
|
|
165
|
+
const n = parseInt(id, 10);
|
|
166
|
+
if (!Number.isFinite(n) || n <= 0) return false;
|
|
167
|
+
if (now - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return false;
|
|
168
|
+
const ph = phEl(n);
|
|
169
|
+
if (!ph?.isConnected || isFilled(ph)) return false;
|
|
170
|
+
if (!hasSinglePlaceholder(n)) return false;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function queueSweepDeadWraps() {
|
|
175
|
+
if (S.sweepQueued) return;
|
|
176
|
+
S.sweepQueued = true;
|
|
177
|
+
requestAnimationFrame(() => {
|
|
178
|
+
S.sweepQueued = false;
|
|
179
|
+
sweepDeadWraps();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
151
183
|
function getDynamicShowBatchMax() {
|
|
152
184
|
const speed = S.scrollSpeed || 0;
|
|
153
185
|
const pend = S.pending.length;
|
|
@@ -330,6 +362,25 @@ function destroyEzoicId(id) {
|
|
|
330
362
|
return null;
|
|
331
363
|
}
|
|
332
364
|
|
|
365
|
+
function sweepDeadWraps() {
|
|
366
|
+
// NodeBB peut retirer des nœuds sans passer par dropWrap() (virtualisation / rerender).
|
|
367
|
+
// On libère alors les IDs/keys fantômes pour éviter l'épuisement du pool.
|
|
368
|
+
for (const [key, wrap] of Array.from(S.wrapByKey.entries())) {
|
|
369
|
+
if (wrap?.isConnected) continue;
|
|
370
|
+
S.wrapByKey.delete(key);
|
|
371
|
+
const id = parseInt(wrap?.getAttribute?.(A_WRAPID), 10);
|
|
372
|
+
if (Number.isFinite(id)) {
|
|
373
|
+
S.mountedIds.delete(id);
|
|
374
|
+
S.pendingSet.delete(id);
|
|
375
|
+
S.lastShow.delete(id);
|
|
376
|
+
S.ezActiveIds.delete(id);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (S.pending.length) {
|
|
380
|
+
S.pending = S.pending.filter(id => S.pendingSet.has(id));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
333
384
|
/**
|
|
334
385
|
* Pool épuisé : recycle un wrap loin au-dessus du viewport.
|
|
335
386
|
* Séquence avec délais (destroyPlaceholders est asynchrone) :
|
|
@@ -353,13 +404,14 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
353
404
|
let bestAnyEmpty = null, bestAnyMetric = Infinity;
|
|
354
405
|
let bestAnyFilled = null, bestAnyFilledMetric = Infinity;
|
|
355
406
|
|
|
356
|
-
|
|
407
|
+
for (const wrap of S.wrapByKey.values()) {
|
|
408
|
+
if (!wrap?.isConnected || !wrap.classList?.contains(WRAP_CLASS) || !wrap.classList.contains(klass)) continue;
|
|
357
409
|
try {
|
|
358
410
|
const rect = wrap.getBoundingClientRect();
|
|
359
411
|
const isAbove = rect.bottom <= farAbove;
|
|
360
412
|
const isBelow = rect.top >= farBelow;
|
|
361
413
|
const anyFar = isAbove || isBelow;
|
|
362
|
-
if (!anyFar)
|
|
414
|
+
if (!anyFar) continue;
|
|
363
415
|
|
|
364
416
|
const qualifies = preferAbove ? isAbove : isBelow;
|
|
365
417
|
const metric = preferAbove ? Math.abs(rect.bottom) : Math.abs(rect.top - vh);
|
|
@@ -378,7 +430,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
378
430
|
if (metric < bestAnyFilledMetric) { bestAnyFilledMetric = metric; bestAnyFilled = wrap; }
|
|
379
431
|
}
|
|
380
432
|
} catch (_) {}
|
|
381
|
-
}
|
|
433
|
+
}
|
|
382
434
|
|
|
383
435
|
const best = bestPrefEmpty ?? bestPrefFilled ?? bestAnyEmpty ?? bestAnyFilled;
|
|
384
436
|
if (!best) return null;
|
|
@@ -523,7 +575,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
523
575
|
const key = anchorKey(klass, el);
|
|
524
576
|
if (findWrap(key)) continue;
|
|
525
577
|
|
|
526
|
-
|
|
578
|
+
let id = pickId(poolKey);
|
|
579
|
+
if (!id) { sweepDeadWraps(); id = pickId(poolKey); }
|
|
527
580
|
if (id) {
|
|
528
581
|
const w = insertAfter(el, id, klass, key);
|
|
529
582
|
if (w) { observePh(id); inserted++; }
|
|
@@ -554,15 +607,34 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
554
607
|
}
|
|
555
608
|
|
|
556
609
|
function observePh(id) {
|
|
557
|
-
const ph =
|
|
610
|
+
const ph = phEl(id);
|
|
558
611
|
if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
|
|
612
|
+
// Fast-path : si déjà proche viewport, ne pas attendre un callback IO complet
|
|
613
|
+
try {
|
|
614
|
+
if (!ph?.isConnected) return;
|
|
615
|
+
const rect = ph.getBoundingClientRect();
|
|
616
|
+
const vh = window.innerHeight || 800;
|
|
617
|
+
const preload = isMobile() ? 1400 : 1000;
|
|
618
|
+
if (rect.top <= vh + preload && rect.bottom >= -preload) enqueueShow(id);
|
|
619
|
+
} catch (_) {}
|
|
559
620
|
}
|
|
560
621
|
|
|
561
622
|
function enqueueShow(id) {
|
|
562
623
|
if (!id || isBlocked()) return;
|
|
563
|
-
|
|
564
|
-
if (!
|
|
565
|
-
|
|
624
|
+
const n = parseInt(id, 10);
|
|
625
|
+
if (!Number.isFinite(n) || n <= 0) return;
|
|
626
|
+
if (ts() - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return;
|
|
627
|
+
if (!S.pendingSet.has(n)) { S.pending.push(n); S.pendingSet.add(n); }
|
|
628
|
+
scheduleDrainQueue();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function scheduleDrainQueue() {
|
|
632
|
+
if (isBlocked()) return;
|
|
633
|
+
if (S.showBatchTimer) return;
|
|
634
|
+
S.showBatchTimer = setTimeout(() => {
|
|
635
|
+
S.showBatchTimer = 0;
|
|
636
|
+
drainQueue();
|
|
637
|
+
}, BATCH_FLUSH_MS);
|
|
566
638
|
}
|
|
567
639
|
|
|
568
640
|
function drainQueue() {
|
|
@@ -577,10 +649,12 @@ function drainQueue() {
|
|
|
577
649
|
const id = S.pending.shift();
|
|
578
650
|
S.pendingSet.delete(id);
|
|
579
651
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
652
|
+
if (!phEl(id)?.isConnected) continue;
|
|
580
653
|
seen.add(id);
|
|
581
654
|
picked.push(id);
|
|
582
655
|
}
|
|
583
656
|
if (picked.length) startShowBatch(picked);
|
|
657
|
+
if (S.pending.length && (MAX_INFLIGHT - S.inflight) > 0) scheduleDrainQueue();
|
|
584
658
|
}
|
|
585
659
|
|
|
586
660
|
function startShowBatch(ids) {
|
|
@@ -595,7 +669,7 @@ function startShowBatch(ids) {
|
|
|
595
669
|
S.inflight = Math.max(0, S.inflight - reserve);
|
|
596
670
|
drainQueue();
|
|
597
671
|
};
|
|
598
|
-
const timer = setTimeout(release,
|
|
672
|
+
const timer = setTimeout(release, SHOW_FAILSAFE_MS);
|
|
599
673
|
|
|
600
674
|
requestAnimationFrame(() => {
|
|
601
675
|
try {
|
|
@@ -607,10 +681,8 @@ function startShowBatch(ids) {
|
|
|
607
681
|
for (const raw of ids) {
|
|
608
682
|
const id = parseInt(raw, 10);
|
|
609
683
|
if (!Number.isFinite(id) || id <= 0) continue;
|
|
610
|
-
const ph =
|
|
611
|
-
if (!
|
|
612
|
-
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) continue;
|
|
613
|
-
if (document.querySelectorAll(`#${PH_PREFIX}${id}`).length !== 1) continue;
|
|
684
|
+
const ph = phEl(id);
|
|
685
|
+
if (!canShowPlaceholderId(id, t)) continue;
|
|
614
686
|
|
|
615
687
|
S.lastShow.set(id, t);
|
|
616
688
|
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
@@ -625,9 +697,8 @@ function startShowBatch(ids) {
|
|
|
625
697
|
try { ez.showAds(...valid); } catch (_) {}
|
|
626
698
|
for (const id of valid) {
|
|
627
699
|
S.ezActiveIds.add(id);
|
|
628
|
-
scheduleEmptyCheck(id, t);
|
|
629
700
|
}
|
|
630
|
-
setTimeout(() => { clearTimeout(timer); release(); },
|
|
701
|
+
setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS);
|
|
631
702
|
};
|
|
632
703
|
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
633
704
|
} catch (_) { clearTimeout(timer); release(); }
|
|
@@ -635,18 +706,6 @@ function startShowBatch(ids) {
|
|
|
635
706
|
}
|
|
636
707
|
|
|
637
708
|
|
|
638
|
-
function scheduleEmptyCheck(id, showTs) {
|
|
639
|
-
setTimeout(() => {
|
|
640
|
-
try {
|
|
641
|
-
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
642
|
-
const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
|
|
643
|
-
if (!wrap || !ph?.isConnected) return;
|
|
644
|
-
if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
|
|
645
|
-
wrap.classList.toggle('is-empty', !isFilled(ph));
|
|
646
|
-
} catch (_) {}
|
|
647
|
-
}, EMPTY_CHECK_MS);
|
|
648
|
-
}
|
|
649
|
-
|
|
650
709
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
651
710
|
//
|
|
652
711
|
// Intercepte ez.showAds() pour :
|
|
@@ -669,8 +728,7 @@ function startShowBatch(ids) {
|
|
|
669
728
|
for (const v of ids) {
|
|
670
729
|
const id = parseInt(v, 10);
|
|
671
730
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
672
|
-
if (!
|
|
673
|
-
if (document.querySelectorAll(`#${PH_PREFIX}${id}`).length !== 1) continue;
|
|
731
|
+
if (!phEl(id)?.isConnected || !hasSinglePlaceholder(id)) continue;
|
|
674
732
|
seen.add(id);
|
|
675
733
|
valid.push(id);
|
|
676
734
|
}
|
|
@@ -695,6 +753,7 @@ function startShowBatch(ids) {
|
|
|
695
753
|
async function runCore() {
|
|
696
754
|
if (isBlocked()) return 0;
|
|
697
755
|
patchShowAds();
|
|
756
|
+
sweepDeadWraps();
|
|
698
757
|
|
|
699
758
|
const cfg = await fetchConfig();
|
|
700
759
|
if (!cfg || cfg.excluded) return 0;
|
|
@@ -761,7 +820,7 @@ function startShowBatch(ids) {
|
|
|
761
820
|
S.burstCount++;
|
|
762
821
|
scheduleRun(n => {
|
|
763
822
|
if (!n && !S.pending.length) { S.burstActive = false; return; }
|
|
764
|
-
setTimeout(step, n > 0 ?
|
|
823
|
+
setTimeout(step, n > 0 ? 80 : 180);
|
|
765
824
|
});
|
|
766
825
|
};
|
|
767
826
|
step();
|
|
@@ -783,8 +842,10 @@ function startShowBatch(ids) {
|
|
|
783
842
|
S.inflight = 0;
|
|
784
843
|
S.pending = [];
|
|
785
844
|
S.pendingSet.clear();
|
|
845
|
+
if (S.showBatchTimer) { clearTimeout(S.showBatchTimer); S.showBatchTimer = 0; }
|
|
786
846
|
S.burstActive = false;
|
|
787
847
|
S.runQueued = false;
|
|
848
|
+
S.sweepQueued = false;
|
|
788
849
|
S.scrollSpeed = 0;
|
|
789
850
|
S.lastScrollY = 0;
|
|
790
851
|
S.lastScrollTs = 0;
|
|
@@ -798,6 +859,14 @@ function startShowBatch(ids) {
|
|
|
798
859
|
S.domObs = new MutationObserver(muts => {
|
|
799
860
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
800
861
|
for (const m of muts) {
|
|
862
|
+
let sawWrapRemoval = false;
|
|
863
|
+
for (const n of m.removedNodes) {
|
|
864
|
+
if (n.nodeType !== 1) continue;
|
|
865
|
+
if ((n.matches && n.matches(`.${WRAP_CLASS}`)) || (n.querySelector && n.querySelector(`.${WRAP_CLASS}`))) {
|
|
866
|
+
sawWrapRemoval = true;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
801
870
|
for (const n of m.addedNodes) {
|
|
802
871
|
if (n.nodeType !== 1) continue;
|
|
803
872
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
@@ -885,7 +954,7 @@ function startShowBatch(ids) {
|
|
|
885
954
|
S.pageKey = pageKey();
|
|
886
955
|
blockedUntil = 0;
|
|
887
956
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
888
|
-
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
957
|
+
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
889
958
|
});
|
|
890
959
|
|
|
891
960
|
const burstEvts = [
|
package/public/style.css
CHANGED
|
@@ -56,21 +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
|
-
|
|
74
59
|
/* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
|
|
75
60
|
.ezoic-ad {
|
|
76
61
|
margin: 0 !important;
|