nodebb-plugin-ezoic-infinite 1.8.0 → 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 -24
- package/public/style.css +0 -6
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -77,16 +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
|
+
// Tunables (stables en prod)
|
|
80
81
|
const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
|
|
81
|
-
const MAX_INSERTS_RUN =
|
|
82
|
-
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)
|
|
83
84
|
const MAX_SHOW_BATCH = 4; // ids max par appel showAds(...ids)
|
|
84
|
-
const SHOW_THROTTLE_MS =
|
|
85
|
-
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
|
|
86
90
|
|
|
87
91
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
88
|
-
const IO_MARGIN_DESKTOP = '
|
|
89
|
-
const IO_MARGIN_MOBILE = '
|
|
92
|
+
const IO_MARGIN_DESKTOP = '3000px 0px 3000px 0px';
|
|
93
|
+
const IO_MARGIN_MOBILE = '2000px 0px 2000px 0px';
|
|
90
94
|
|
|
91
95
|
const SEL = {
|
|
92
96
|
post: '[component="post"][data-pid]',
|
|
@@ -126,6 +130,8 @@
|
|
|
126
130
|
inflight: 0, // showAds() en cours
|
|
127
131
|
pending: [], // ids en attente de slot inflight
|
|
128
132
|
pendingSet: new Set(),
|
|
133
|
+
showBatchTimer: 0,
|
|
134
|
+
sweepQueued: false,
|
|
129
135
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
130
136
|
ezActiveIds: new Set(), // ids déjà passés à showAds/displayMore
|
|
131
137
|
scrollDir: 1, // 1=bas, -1=haut
|
|
@@ -147,6 +153,33 @@
|
|
|
147
153
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
148
154
|
const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
149
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
|
+
|
|
150
183
|
function getDynamicShowBatchMax() {
|
|
151
184
|
const speed = S.scrollSpeed || 0;
|
|
152
185
|
const pend = S.pending.length;
|
|
@@ -329,6 +362,25 @@ function destroyEzoicId(id) {
|
|
|
329
362
|
return null;
|
|
330
363
|
}
|
|
331
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
|
+
|
|
332
384
|
/**
|
|
333
385
|
* Pool épuisé : recycle un wrap loin au-dessus du viewport.
|
|
334
386
|
* Séquence avec délais (destroyPlaceholders est asynchrone) :
|
|
@@ -352,13 +404,14 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
352
404
|
let bestAnyEmpty = null, bestAnyMetric = Infinity;
|
|
353
405
|
let bestAnyFilled = null, bestAnyFilledMetric = Infinity;
|
|
354
406
|
|
|
355
|
-
|
|
407
|
+
for (const wrap of S.wrapByKey.values()) {
|
|
408
|
+
if (!wrap?.isConnected || !wrap.classList?.contains(WRAP_CLASS) || !wrap.classList.contains(klass)) continue;
|
|
356
409
|
try {
|
|
357
410
|
const rect = wrap.getBoundingClientRect();
|
|
358
411
|
const isAbove = rect.bottom <= farAbove;
|
|
359
412
|
const isBelow = rect.top >= farBelow;
|
|
360
413
|
const anyFar = isAbove || isBelow;
|
|
361
|
-
if (!anyFar)
|
|
414
|
+
if (!anyFar) continue;
|
|
362
415
|
|
|
363
416
|
const qualifies = preferAbove ? isAbove : isBelow;
|
|
364
417
|
const metric = preferAbove ? Math.abs(rect.bottom) : Math.abs(rect.top - vh);
|
|
@@ -377,7 +430,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
377
430
|
if (metric < bestAnyFilledMetric) { bestAnyFilledMetric = metric; bestAnyFilled = wrap; }
|
|
378
431
|
}
|
|
379
432
|
} catch (_) {}
|
|
380
|
-
}
|
|
433
|
+
}
|
|
381
434
|
|
|
382
435
|
const best = bestPrefEmpty ?? bestPrefFilled ?? bestAnyEmpty ?? bestAnyFilled;
|
|
383
436
|
if (!best) return null;
|
|
@@ -522,7 +575,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
522
575
|
const key = anchorKey(klass, el);
|
|
523
576
|
if (findWrap(key)) continue;
|
|
524
577
|
|
|
525
|
-
|
|
578
|
+
let id = pickId(poolKey);
|
|
579
|
+
if (!id) { sweepDeadWraps(); id = pickId(poolKey); }
|
|
526
580
|
if (id) {
|
|
527
581
|
const w = insertAfter(el, id, klass, key);
|
|
528
582
|
if (w) { observePh(id); inserted++; }
|
|
@@ -553,15 +607,34 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
553
607
|
}
|
|
554
608
|
|
|
555
609
|
function observePh(id) {
|
|
556
|
-
const ph =
|
|
610
|
+
const ph = phEl(id);
|
|
557
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 (_) {}
|
|
558
620
|
}
|
|
559
621
|
|
|
560
622
|
function enqueueShow(id) {
|
|
561
623
|
if (!id || isBlocked()) return;
|
|
562
|
-
|
|
563
|
-
if (!
|
|
564
|
-
|
|
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);
|
|
565
638
|
}
|
|
566
639
|
|
|
567
640
|
function drainQueue() {
|
|
@@ -576,10 +649,12 @@ function drainQueue() {
|
|
|
576
649
|
const id = S.pending.shift();
|
|
577
650
|
S.pendingSet.delete(id);
|
|
578
651
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
652
|
+
if (!phEl(id)?.isConnected) continue;
|
|
579
653
|
seen.add(id);
|
|
580
654
|
picked.push(id);
|
|
581
655
|
}
|
|
582
656
|
if (picked.length) startShowBatch(picked);
|
|
657
|
+
if (S.pending.length && (MAX_INFLIGHT - S.inflight) > 0) scheduleDrainQueue();
|
|
583
658
|
}
|
|
584
659
|
|
|
585
660
|
function startShowBatch(ids) {
|
|
@@ -594,7 +669,7 @@ function startShowBatch(ids) {
|
|
|
594
669
|
S.inflight = Math.max(0, S.inflight - reserve);
|
|
595
670
|
drainQueue();
|
|
596
671
|
};
|
|
597
|
-
const timer = setTimeout(release,
|
|
672
|
+
const timer = setTimeout(release, SHOW_FAILSAFE_MS);
|
|
598
673
|
|
|
599
674
|
requestAnimationFrame(() => {
|
|
600
675
|
try {
|
|
@@ -606,10 +681,8 @@ function startShowBatch(ids) {
|
|
|
606
681
|
for (const raw of ids) {
|
|
607
682
|
const id = parseInt(raw, 10);
|
|
608
683
|
if (!Number.isFinite(id) || id <= 0) continue;
|
|
609
|
-
const ph =
|
|
610
|
-
if (!
|
|
611
|
-
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) continue;
|
|
612
|
-
if (document.querySelectorAll(`#${PH_PREFIX}${id}`).length !== 1) continue;
|
|
684
|
+
const ph = phEl(id);
|
|
685
|
+
if (!canShowPlaceholderId(id, t)) continue;
|
|
613
686
|
|
|
614
687
|
S.lastShow.set(id, t);
|
|
615
688
|
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
@@ -625,7 +698,7 @@ function startShowBatch(ids) {
|
|
|
625
698
|
for (const id of valid) {
|
|
626
699
|
S.ezActiveIds.add(id);
|
|
627
700
|
}
|
|
628
|
-
setTimeout(() => { clearTimeout(timer); release(); },
|
|
701
|
+
setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS);
|
|
629
702
|
};
|
|
630
703
|
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
631
704
|
} catch (_) { clearTimeout(timer); release(); }
|
|
@@ -655,8 +728,7 @@ function startShowBatch(ids) {
|
|
|
655
728
|
for (const v of ids) {
|
|
656
729
|
const id = parseInt(v, 10);
|
|
657
730
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
658
|
-
if (!
|
|
659
|
-
if (document.querySelectorAll(`#${PH_PREFIX}${id}`).length !== 1) continue;
|
|
731
|
+
if (!phEl(id)?.isConnected || !hasSinglePlaceholder(id)) continue;
|
|
660
732
|
seen.add(id);
|
|
661
733
|
valid.push(id);
|
|
662
734
|
}
|
|
@@ -681,6 +753,7 @@ function startShowBatch(ids) {
|
|
|
681
753
|
async function runCore() {
|
|
682
754
|
if (isBlocked()) return 0;
|
|
683
755
|
patchShowAds();
|
|
756
|
+
sweepDeadWraps();
|
|
684
757
|
|
|
685
758
|
const cfg = await fetchConfig();
|
|
686
759
|
if (!cfg || cfg.excluded) return 0;
|
|
@@ -747,7 +820,7 @@ function startShowBatch(ids) {
|
|
|
747
820
|
S.burstCount++;
|
|
748
821
|
scheduleRun(n => {
|
|
749
822
|
if (!n && !S.pending.length) { S.burstActive = false; return; }
|
|
750
|
-
setTimeout(step, n > 0 ?
|
|
823
|
+
setTimeout(step, n > 0 ? 80 : 180);
|
|
751
824
|
});
|
|
752
825
|
};
|
|
753
826
|
step();
|
|
@@ -769,8 +842,10 @@ function startShowBatch(ids) {
|
|
|
769
842
|
S.inflight = 0;
|
|
770
843
|
S.pending = [];
|
|
771
844
|
S.pendingSet.clear();
|
|
845
|
+
if (S.showBatchTimer) { clearTimeout(S.showBatchTimer); S.showBatchTimer = 0; }
|
|
772
846
|
S.burstActive = false;
|
|
773
847
|
S.runQueued = false;
|
|
848
|
+
S.sweepQueued = false;
|
|
774
849
|
S.scrollSpeed = 0;
|
|
775
850
|
S.lastScrollY = 0;
|
|
776
851
|
S.lastScrollTs = 0;
|
|
@@ -784,6 +859,14 @@ function startShowBatch(ids) {
|
|
|
784
859
|
S.domObs = new MutationObserver(muts => {
|
|
785
860
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
786
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();
|
|
787
870
|
for (const n of m.addedNodes) {
|
|
788
871
|
if (n.nodeType !== 1) continue;
|
|
789
872
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
@@ -871,7 +954,7 @@ function startShowBatch(ids) {
|
|
|
871
954
|
S.pageKey = pageKey();
|
|
872
955
|
blockedUntil = 0;
|
|
873
956
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
874
|
-
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
957
|
+
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
875
958
|
});
|
|
876
959
|
|
|
877
960
|
const burstEvts = [
|
package/public/style.css
CHANGED
|
@@ -56,12 +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
|
-
|
|
65
59
|
/* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
|
|
66
60
|
.ezoic-ad {
|
|
67
61
|
margin: 0 !important;
|