nodebb-plugin-ezoic-infinite 1.4.64 → 1.4.66
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 +16 -49
- package/public/style.css +16 -1
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
}, WRAP_CLASS = 'ezoic-ad';
|
|
9
9
|
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-', MAX_INSERTS_PER_RUN = 3;
|
|
10
10
|
|
|
11
|
-
// Nécessaire pour savoir si on doit appeler destroyPlaceholders avant recyclage.
|
|
12
11
|
const sessionDefinedIds = new Set();
|
|
13
12
|
|
|
14
13
|
const insertingIds = new Set(), state = {
|
|
@@ -106,9 +105,7 @@
|
|
|
106
105
|
|
|
107
106
|
function destroyPlaceholderIds(ids) {
|
|
108
107
|
if (!ids || !ids.length) return;
|
|
109
|
-
// Only destroy ids that were actually "defined" (filled at least once) to avoid Ezoic warnings.
|
|
110
108
|
const filtered = ids.filter((id) => {
|
|
111
|
-
// Utiliser sessionDefinedIds (survit aux navigations) plutôt que state.definedIds
|
|
112
109
|
try { return sessionDefinedIds.has(id); } catch (e) { return true; }
|
|
113
110
|
});
|
|
114
111
|
if (!filtered.length) return;
|
|
@@ -128,6 +125,22 @@
|
|
|
128
125
|
} catch (e) {}
|
|
129
126
|
}
|
|
130
127
|
|
|
128
|
+
function cleanupEmptyWrappers() {
|
|
129
|
+
try {
|
|
130
|
+
document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
|
|
131
|
+
const ph = wrapper.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
|
|
132
|
+
if (ph && ph.children.length === 0) {
|
|
133
|
+
// Placeholder vide après 3s = pub non chargée
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
if (ph.children.length === 0) {
|
|
136
|
+
wrapper.remove();
|
|
137
|
+
}
|
|
138
|
+
}, 3000);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
} catch (e) {}
|
|
142
|
+
}
|
|
143
|
+
|
|
131
144
|
function getRecyclable(liveArr) {
|
|
132
145
|
const margin = 600;
|
|
133
146
|
for (let i = 0; i < liveArr.length; i++) {
|
|
@@ -199,10 +212,8 @@
|
|
|
199
212
|
if (findWrap(kindClass, afterPos)) return null;
|
|
200
213
|
|
|
201
214
|
// CRITICAL: Double-lock pour éviter race conditions sur les doublons
|
|
202
|
-
// 1. Vérifier qu'aucun autre thread n'est en train d'insérer cet ID
|
|
203
215
|
if (insertingIds.has(id)) return null;
|
|
204
216
|
|
|
205
|
-
// 2. Vérifier qu'aucun placeholder avec cet ID n'existe déjà dans le DOM
|
|
206
217
|
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
207
218
|
if (existingPh && existingPh.isConnected) return null;
|
|
208
219
|
|
|
@@ -215,7 +226,6 @@
|
|
|
215
226
|
attachFillObserver(wrap, id);
|
|
216
227
|
return wrap;
|
|
217
228
|
} finally {
|
|
218
|
-
// Libérer le lock après 100ms (le temps que le DOM soit stable)
|
|
219
229
|
setTimeout(() => insertingIds.delete(id), 50);
|
|
220
230
|
}
|
|
221
231
|
}
|
|
@@ -226,8 +236,6 @@
|
|
|
226
236
|
}
|
|
227
237
|
|
|
228
238
|
function patchShowAds() {
|
|
229
|
-
// Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
|
|
230
|
-
// Also ensures the patch is applied even if Ezoic loads after our script.
|
|
231
239
|
const applyPatch = () => {
|
|
232
240
|
try {
|
|
233
241
|
window.ezstandalone = window.ezstandalone || {}, ez = window.ezstandalone;
|
|
@@ -254,7 +262,6 @@
|
|
|
254
262
|
};
|
|
255
263
|
|
|
256
264
|
applyPatch();
|
|
257
|
-
// Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
|
|
258
265
|
if (!window.__nodebbEzoicPatched) {
|
|
259
266
|
try {
|
|
260
267
|
window.ezstandalone = window.ezstandalone || {};
|
|
@@ -267,7 +274,6 @@
|
|
|
267
274
|
function markFilled(wrap) {
|
|
268
275
|
try {
|
|
269
276
|
if (!wrap) return;
|
|
270
|
-
// Disconnect the fill observer first (no need to remove+re-add the attribute)
|
|
271
277
|
if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
|
|
272
278
|
wrap.setAttribute('data-ezoic-filled', '1');
|
|
273
279
|
} catch (e) {}
|
|
@@ -295,7 +301,6 @@
|
|
|
295
301
|
}
|
|
296
302
|
});
|
|
297
303
|
obs.observe(ph, { childList: true, subtree: true });
|
|
298
|
-
// Keep a weak reference on the wrapper so we can disconnect on recycle/remove
|
|
299
304
|
wrap.__ezoicFillObs = obs;
|
|
300
305
|
} catch (e) {}
|
|
301
306
|
}
|
|
@@ -315,15 +320,12 @@
|
|
|
315
320
|
return filled;
|
|
316
321
|
}
|
|
317
322
|
|
|
318
|
-
// Appeler showAds() en batch selon recommandations Ezoic
|
|
319
|
-
// Au lieu de showAds(id1), showAds(id2)... faire showAds(id1, id2, id3...)
|
|
320
323
|
let batchShowAdsTimer = null;
|
|
321
324
|
const pendingShowAdsIds = new Set();
|
|
322
325
|
|
|
323
326
|
function scheduleShowAdsBatch(id) {
|
|
324
327
|
if (!id) return;
|
|
325
328
|
|
|
326
|
-
// CRITIQUE: Si cet ID a déjà été défini (sessionDefinedIds), le détruire d'abord
|
|
327
329
|
if (sessionDefinedIds.has(id)) {
|
|
328
330
|
try {
|
|
329
331
|
destroyPlaceholderIds([id]);
|
|
@@ -338,7 +340,6 @@
|
|
|
338
340
|
// Ajouter à la batch
|
|
339
341
|
pendingShowAdsIds.add(id);
|
|
340
342
|
|
|
341
|
-
// Debounce: attendre 100ms pour collecter tous les IDs
|
|
342
343
|
clearTimeout(batchShowAdsTimer);
|
|
343
344
|
batchShowAdsTimer = setTimeout(() => {
|
|
344
345
|
if (pendingShowAdsIds.size === 0) return;
|
|
@@ -388,19 +389,15 @@
|
|
|
388
389
|
const startPageKey = state.pageKey;
|
|
389
390
|
let attempts = 0;
|
|
390
391
|
(function waitForPh() {
|
|
391
|
-
// Abort if the user navigated away since this showAds was scheduled
|
|
392
392
|
if (state.pageKey !== startPageKey) return;
|
|
393
|
-
// Abort if another concurrent call is already handling this id
|
|
394
393
|
if (state.pendingById.has(id)) return;
|
|
395
394
|
|
|
396
395
|
attempts += 1;
|
|
397
396
|
const el = document.getElementById(phId);
|
|
398
397
|
if (el && el.isConnected) {
|
|
399
|
-
// CRITIQUE: Vérifier que le placeholder est VISIBLE
|
|
400
398
|
|
|
401
399
|
// Si on arrive ici, soit visible, soit timeout
|
|
402
400
|
|
|
403
|
-
// Si doCall() réussit, Ezoic est chargé et showAds a été appelé → sortir
|
|
404
401
|
if (doCall()) {
|
|
405
402
|
state.pendingById.delete(id);
|
|
406
403
|
return;
|
|
@@ -466,7 +463,6 @@
|
|
|
466
463
|
const el = items[afterPos - 1];
|
|
467
464
|
if (!el || !el.isConnected) continue;
|
|
468
465
|
|
|
469
|
-
// Prevent adjacent ads (DOM-based, robust against virtualization)
|
|
470
466
|
if (isAdjacentAd(el) || isPrevAd(el)) {
|
|
471
467
|
continue;
|
|
472
468
|
}
|
|
@@ -483,17 +479,14 @@
|
|
|
483
479
|
|
|
484
480
|
let wrap = null;
|
|
485
481
|
if (pick.recycled && pick.recycled.wrap) {
|
|
486
|
-
// Only destroy if Ezoic has actually defined this placeholder before
|
|
487
482
|
if (sessionDefinedIds.has(id)) {
|
|
488
483
|
destroyPlaceholderIds([id]);
|
|
489
484
|
}
|
|
490
|
-
// Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
|
|
491
485
|
const oldWrap = pick.recycled.wrap;
|
|
492
486
|
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
493
487
|
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
494
488
|
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
495
489
|
if (!wrap) continue;
|
|
496
|
-
// Attendre que le wrapper soit dans le DOM puis appeler showAds
|
|
497
490
|
setTimeout(() => {
|
|
498
491
|
callShowAdsWhenReady(id);
|
|
499
492
|
}, 50);
|
|
@@ -507,12 +500,10 @@
|
|
|
507
500
|
}
|
|
508
501
|
|
|
509
502
|
liveArr.push({ id, wrap });
|
|
510
|
-
// If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
|
|
511
503
|
if (wrap && (
|
|
512
504
|
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
513
505
|
)) {
|
|
514
506
|
try { wrap.remove(); } catch (e) {}
|
|
515
|
-
// Put id back if it was newly consumed (not recycled)
|
|
516
507
|
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
517
508
|
try { kindPool.unshift(id); } catch (e) {}
|
|
518
509
|
usedSet.delete(id);
|
|
@@ -529,7 +520,6 @@
|
|
|
529
520
|
for (let i = 0; i < ads.length; i++) {
|
|
530
521
|
const ad = ads[i], prev = ad.previousElementSibling;
|
|
531
522
|
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
532
|
-
// Supprimer le wrapper adjacent au lieu de le cacher
|
|
533
523
|
try {
|
|
534
524
|
const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
535
525
|
if (ph) {
|
|
@@ -550,8 +540,6 @@
|
|
|
550
540
|
function cleanup() {
|
|
551
541
|
destroyUsedPlaceholders();
|
|
552
542
|
|
|
553
|
-
// CRITIQUE: Supprimer TOUS les wrappers .ezoic-ad du DOM
|
|
554
|
-
// Sinon ils restent et deviennent "unused" sur la nouvelle page
|
|
555
543
|
document.querySelectorAll('.ezoic-ad').forEach(el => {
|
|
556
544
|
try { el.remove(); } catch (e) {}
|
|
557
545
|
});
|
|
@@ -567,23 +555,14 @@
|
|
|
567
555
|
state.usedPosts.clear();
|
|
568
556
|
state.usedCategories.clear();
|
|
569
557
|
state.lastShowById.clear();
|
|
570
|
-
// CRITIQUE: Vider pendingById pour annuler tous les showAds en cours
|
|
571
|
-
// Sinon Ezoic essaie d'accéder aux placeholders pendant que NodeBB vide le DOM
|
|
572
558
|
state.pendingById.clear();
|
|
573
559
|
state.definedIds.clear();
|
|
574
560
|
|
|
575
|
-
// NE PAS supprimer les wrappers Ezoic ici - ils seront supprimés naturellement
|
|
576
|
-
// quand NodeBB vide le DOM lors de la navigation ajaxify
|
|
577
|
-
// Les supprimer manuellement cause des problèmes avec l'état interne d'Ezoic
|
|
578
|
-
|
|
579
|
-
// CRITIQUE: Annuler TOUS les timeouts en cours pour éviter que les anciens
|
|
580
|
-
// showAds() continuent à s'exécuter après la navigation
|
|
581
561
|
state.activeTimeouts.forEach(id => {
|
|
582
562
|
try { clearTimeout(id); } catch (e) {}
|
|
583
563
|
});
|
|
584
564
|
state.activeTimeouts.clear();
|
|
585
565
|
|
|
586
|
-
// Vider aussi pendingById pour annuler les showAds en attente
|
|
587
566
|
state.pendingById.clear();
|
|
588
567
|
|
|
589
568
|
if (state.obs) { state.obs.disconnect(); state.obs = null; }
|
|
@@ -600,7 +579,6 @@
|
|
|
600
579
|
}
|
|
601
580
|
|
|
602
581
|
async function runCore() {
|
|
603
|
-
// Attendre que canInsert soit true (protection race condition navigation)
|
|
604
582
|
if (!state.canShowAds) {
|
|
605
583
|
return;
|
|
606
584
|
}
|
|
@@ -643,7 +621,6 @@
|
|
|
643
621
|
|
|
644
622
|
enforceNoAdjacentAds();
|
|
645
623
|
|
|
646
|
-
// If nothing inserted and list isn't in DOM yet (first click), retry a bit
|
|
647
624
|
let count = 0;
|
|
648
625
|
if (kind === 'topic') count = getPostContainers().length;
|
|
649
626
|
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
@@ -655,12 +632,9 @@
|
|
|
655
632
|
}
|
|
656
633
|
|
|
657
634
|
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
658
|
-
// Plus d'insertions possibles ce cycle, continuer immédiatement
|
|
659
635
|
setTimeout(arguments[0], 50);
|
|
660
636
|
} else if (inserted === 0 && count > 0) {
|
|
661
637
|
// Pool épuisé ou recyclage pas encore disponible.
|
|
662
|
-
// Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
|
|
663
|
-
// le temps de défiler hors écran et devenir recyclables.
|
|
664
638
|
if (state.poolWaitAttempts < 8) {
|
|
665
639
|
state.poolWaitAttempts += 1;
|
|
666
640
|
setTimeout(arguments[0], 50);
|
|
@@ -694,15 +668,11 @@
|
|
|
694
668
|
state.pageKey = getPageKey();
|
|
695
669
|
ensureObserver();
|
|
696
670
|
|
|
697
|
-
// CRITIQUE: Attendre 300ms avant de permettre l'insertion de nouveaux placeholders
|
|
698
|
-
// pour laisser les anciens showAds() (en cours) se terminer ou échouer proprement
|
|
699
|
-
// Sinon race condition: NodeBB vide le DOM pendant que Ezoic essaie d'accéder aux placeholders
|
|
700
671
|
state.canShowAds = true;
|
|
701
672
|
});
|
|
702
673
|
|
|
703
674
|
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
704
675
|
ensureObserver();
|
|
705
|
-
// category.loaded = infinite scroll, Ezoic déjà chargé normalement
|
|
706
676
|
waitForContentThenRun();
|
|
707
677
|
});
|
|
708
678
|
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
@@ -732,7 +702,6 @@
|
|
|
732
702
|
window.requestAnimationFrame(() => {
|
|
733
703
|
ticking = false;
|
|
734
704
|
enforceNoAdjacentAds();
|
|
735
|
-
// Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
|
|
736
705
|
const now = Date.now();
|
|
737
706
|
if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
|
|
738
707
|
state.lastScrollRun = now;
|
|
@@ -742,7 +711,6 @@
|
|
|
742
711
|
}, { passive: true });
|
|
743
712
|
}
|
|
744
713
|
|
|
745
|
-
// Fonction qui attend que la page ait assez de contenu avant d'insérer les pubs
|
|
746
714
|
function waitForContentThenRun() {
|
|
747
715
|
const MIN_WORDS = 250;
|
|
748
716
|
let attempts = 0;
|
|
@@ -773,7 +741,6 @@
|
|
|
773
741
|
})();
|
|
774
742
|
}
|
|
775
743
|
|
|
776
|
-
// Fonction qui attend que Ezoic soit vraiment chargé
|
|
777
744
|
function waitForEzoicThenRun() {
|
|
778
745
|
let attempts = 0;
|
|
779
746
|
const maxAttempts = 50; // 50 × 200ms = 10s max
|
package/public/style.css
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
-
.ezoic-ad{height:auto !important;
|
|
1
|
+
.ezoic-ad{height:auto !important; padding:0 !important; margin: 0.25rem 0;}
|
|
2
2
|
.ezoic-ad .ezoic-ad-inner{padding:0;margin:0;}
|
|
3
3
|
.ezoic-ad .ezoic-ad-inner > div{padding:0;margin:0;}
|
|
4
|
+
|
|
5
|
+
/* Cacher placeholder vide pour éviter espace réservé */
|
|
6
|
+
.ezoic-ad [id^="ezoic-pub-ad-placeholder-"]:empty {
|
|
7
|
+
display: none !important;
|
|
8
|
+
height: 0 !important;
|
|
9
|
+
margin: 0 !important;
|
|
10
|
+
padding: 0 !important;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Wrapper vide aussi caché */
|
|
14
|
+
.ezoic-ad:has([id^="ezoic-pub-ad-placeholder-"]:empty) {
|
|
15
|
+
display: none !important;
|
|
16
|
+
height: 0 !important;
|
|
17
|
+
margin: 0 !important;
|
|
18
|
+
}
|