nodebb-plugin-ezoic-infinite 1.4.65 → 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 +0 -51
- package/public/style.css +15 -0
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,8 +125,6 @@
|
|
|
128
125
|
} catch (e) {}
|
|
129
126
|
}
|
|
130
127
|
|
|
131
|
-
|
|
132
|
-
// Nettoyer les wrappers vides (sans pub) pour éviter espaces verticaux
|
|
133
128
|
function cleanupEmptyWrappers() {
|
|
134
129
|
try {
|
|
135
130
|
document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
|
|
@@ -217,10 +212,8 @@
|
|
|
217
212
|
if (findWrap(kindClass, afterPos)) return null;
|
|
218
213
|
|
|
219
214
|
// CRITICAL: Double-lock pour éviter race conditions sur les doublons
|
|
220
|
-
// 1. Vérifier qu'aucun autre thread n'est en train d'insérer cet ID
|
|
221
215
|
if (insertingIds.has(id)) return null;
|
|
222
216
|
|
|
223
|
-
// 2. Vérifier qu'aucun placeholder avec cet ID n'existe déjà dans le DOM
|
|
224
217
|
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
225
218
|
if (existingPh && existingPh.isConnected) return null;
|
|
226
219
|
|
|
@@ -233,7 +226,6 @@
|
|
|
233
226
|
attachFillObserver(wrap, id);
|
|
234
227
|
return wrap;
|
|
235
228
|
} finally {
|
|
236
|
-
// Libérer le lock après 100ms (le temps que le DOM soit stable)
|
|
237
229
|
setTimeout(() => insertingIds.delete(id), 50);
|
|
238
230
|
}
|
|
239
231
|
}
|
|
@@ -244,8 +236,6 @@
|
|
|
244
236
|
}
|
|
245
237
|
|
|
246
238
|
function patchShowAds() {
|
|
247
|
-
// Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
|
|
248
|
-
// Also ensures the patch is applied even if Ezoic loads after our script.
|
|
249
239
|
const applyPatch = () => {
|
|
250
240
|
try {
|
|
251
241
|
window.ezstandalone = window.ezstandalone || {}, ez = window.ezstandalone;
|
|
@@ -272,7 +262,6 @@
|
|
|
272
262
|
};
|
|
273
263
|
|
|
274
264
|
applyPatch();
|
|
275
|
-
// Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
|
|
276
265
|
if (!window.__nodebbEzoicPatched) {
|
|
277
266
|
try {
|
|
278
267
|
window.ezstandalone = window.ezstandalone || {};
|
|
@@ -285,7 +274,6 @@
|
|
|
285
274
|
function markFilled(wrap) {
|
|
286
275
|
try {
|
|
287
276
|
if (!wrap) return;
|
|
288
|
-
// Disconnect the fill observer first (no need to remove+re-add the attribute)
|
|
289
277
|
if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
|
|
290
278
|
wrap.setAttribute('data-ezoic-filled', '1');
|
|
291
279
|
} catch (e) {}
|
|
@@ -313,7 +301,6 @@
|
|
|
313
301
|
}
|
|
314
302
|
});
|
|
315
303
|
obs.observe(ph, { childList: true, subtree: true });
|
|
316
|
-
// Keep a weak reference on the wrapper so we can disconnect on recycle/remove
|
|
317
304
|
wrap.__ezoicFillObs = obs;
|
|
318
305
|
} catch (e) {}
|
|
319
306
|
}
|
|
@@ -333,15 +320,12 @@
|
|
|
333
320
|
return filled;
|
|
334
321
|
}
|
|
335
322
|
|
|
336
|
-
// Appeler showAds() en batch selon recommandations Ezoic
|
|
337
|
-
// Au lieu de showAds(id1), showAds(id2)... faire showAds(id1, id2, id3...)
|
|
338
323
|
let batchShowAdsTimer = null;
|
|
339
324
|
const pendingShowAdsIds = new Set();
|
|
340
325
|
|
|
341
326
|
function scheduleShowAdsBatch(id) {
|
|
342
327
|
if (!id) return;
|
|
343
328
|
|
|
344
|
-
// CRITIQUE: Si cet ID a déjà été défini (sessionDefinedIds), le détruire d'abord
|
|
345
329
|
if (sessionDefinedIds.has(id)) {
|
|
346
330
|
try {
|
|
347
331
|
destroyPlaceholderIds([id]);
|
|
@@ -356,7 +340,6 @@
|
|
|
356
340
|
// Ajouter à la batch
|
|
357
341
|
pendingShowAdsIds.add(id);
|
|
358
342
|
|
|
359
|
-
// Debounce: attendre 100ms pour collecter tous les IDs
|
|
360
343
|
clearTimeout(batchShowAdsTimer);
|
|
361
344
|
batchShowAdsTimer = setTimeout(() => {
|
|
362
345
|
if (pendingShowAdsIds.size === 0) return;
|
|
@@ -406,19 +389,15 @@
|
|
|
406
389
|
const startPageKey = state.pageKey;
|
|
407
390
|
let attempts = 0;
|
|
408
391
|
(function waitForPh() {
|
|
409
|
-
// Abort if the user navigated away since this showAds was scheduled
|
|
410
392
|
if (state.pageKey !== startPageKey) return;
|
|
411
|
-
// Abort if another concurrent call is already handling this id
|
|
412
393
|
if (state.pendingById.has(id)) return;
|
|
413
394
|
|
|
414
395
|
attempts += 1;
|
|
415
396
|
const el = document.getElementById(phId);
|
|
416
397
|
if (el && el.isConnected) {
|
|
417
|
-
// CRITIQUE: Vérifier que le placeholder est VISIBLE
|
|
418
398
|
|
|
419
399
|
// Si on arrive ici, soit visible, soit timeout
|
|
420
400
|
|
|
421
|
-
// Si doCall() réussit, Ezoic est chargé et showAds a été appelé → sortir
|
|
422
401
|
if (doCall()) {
|
|
423
402
|
state.pendingById.delete(id);
|
|
424
403
|
return;
|
|
@@ -484,7 +463,6 @@
|
|
|
484
463
|
const el = items[afterPos - 1];
|
|
485
464
|
if (!el || !el.isConnected) continue;
|
|
486
465
|
|
|
487
|
-
// Prevent adjacent ads (DOM-based, robust against virtualization)
|
|
488
466
|
if (isAdjacentAd(el) || isPrevAd(el)) {
|
|
489
467
|
continue;
|
|
490
468
|
}
|
|
@@ -501,17 +479,14 @@
|
|
|
501
479
|
|
|
502
480
|
let wrap = null;
|
|
503
481
|
if (pick.recycled && pick.recycled.wrap) {
|
|
504
|
-
// Only destroy if Ezoic has actually defined this placeholder before
|
|
505
482
|
if (sessionDefinedIds.has(id)) {
|
|
506
483
|
destroyPlaceholderIds([id]);
|
|
507
484
|
}
|
|
508
|
-
// Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
|
|
509
485
|
const oldWrap = pick.recycled.wrap;
|
|
510
486
|
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
511
487
|
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
512
488
|
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
513
489
|
if (!wrap) continue;
|
|
514
|
-
// Attendre que le wrapper soit dans le DOM puis appeler showAds
|
|
515
490
|
setTimeout(() => {
|
|
516
491
|
callShowAdsWhenReady(id);
|
|
517
492
|
}, 50);
|
|
@@ -525,12 +500,10 @@
|
|
|
525
500
|
}
|
|
526
501
|
|
|
527
502
|
liveArr.push({ id, wrap });
|
|
528
|
-
// If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
|
|
529
503
|
if (wrap && (
|
|
530
504
|
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
531
505
|
)) {
|
|
532
506
|
try { wrap.remove(); } catch (e) {}
|
|
533
|
-
// Put id back if it was newly consumed (not recycled)
|
|
534
507
|
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
535
508
|
try { kindPool.unshift(id); } catch (e) {}
|
|
536
509
|
usedSet.delete(id);
|
|
@@ -547,7 +520,6 @@
|
|
|
547
520
|
for (let i = 0; i < ads.length; i++) {
|
|
548
521
|
const ad = ads[i], prev = ad.previousElementSibling;
|
|
549
522
|
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
550
|
-
// Supprimer le wrapper adjacent au lieu de le cacher
|
|
551
523
|
try {
|
|
552
524
|
const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
553
525
|
if (ph) {
|
|
@@ -568,8 +540,6 @@
|
|
|
568
540
|
function cleanup() {
|
|
569
541
|
destroyUsedPlaceholders();
|
|
570
542
|
|
|
571
|
-
// CRITIQUE: Supprimer TOUS les wrappers .ezoic-ad du DOM
|
|
572
|
-
// Sinon ils restent et deviennent "unused" sur la nouvelle page
|
|
573
543
|
document.querySelectorAll('.ezoic-ad').forEach(el => {
|
|
574
544
|
try { el.remove(); } catch (e) {}
|
|
575
545
|
});
|
|
@@ -585,23 +555,14 @@
|
|
|
585
555
|
state.usedPosts.clear();
|
|
586
556
|
state.usedCategories.clear();
|
|
587
557
|
state.lastShowById.clear();
|
|
588
|
-
// CRITIQUE: Vider pendingById pour annuler tous les showAds en cours
|
|
589
|
-
// Sinon Ezoic essaie d'accéder aux placeholders pendant que NodeBB vide le DOM
|
|
590
558
|
state.pendingById.clear();
|
|
591
559
|
state.definedIds.clear();
|
|
592
560
|
|
|
593
|
-
// NE PAS supprimer les wrappers Ezoic ici - ils seront supprimés naturellement
|
|
594
|
-
// quand NodeBB vide le DOM lors de la navigation ajaxify
|
|
595
|
-
// Les supprimer manuellement cause des problèmes avec l'état interne d'Ezoic
|
|
596
|
-
|
|
597
|
-
// CRITIQUE: Annuler TOUS les timeouts en cours pour éviter que les anciens
|
|
598
|
-
// showAds() continuent à s'exécuter après la navigation
|
|
599
561
|
state.activeTimeouts.forEach(id => {
|
|
600
562
|
try { clearTimeout(id); } catch (e) {}
|
|
601
563
|
});
|
|
602
564
|
state.activeTimeouts.clear();
|
|
603
565
|
|
|
604
|
-
// Vider aussi pendingById pour annuler les showAds en attente
|
|
605
566
|
state.pendingById.clear();
|
|
606
567
|
|
|
607
568
|
if (state.obs) { state.obs.disconnect(); state.obs = null; }
|
|
@@ -618,7 +579,6 @@
|
|
|
618
579
|
}
|
|
619
580
|
|
|
620
581
|
async function runCore() {
|
|
621
|
-
// Attendre que canInsert soit true (protection race condition navigation)
|
|
622
582
|
if (!state.canShowAds) {
|
|
623
583
|
return;
|
|
624
584
|
}
|
|
@@ -661,7 +621,6 @@
|
|
|
661
621
|
|
|
662
622
|
enforceNoAdjacentAds();
|
|
663
623
|
|
|
664
|
-
// If nothing inserted and list isn't in DOM yet (first click), retry a bit
|
|
665
624
|
let count = 0;
|
|
666
625
|
if (kind === 'topic') count = getPostContainers().length;
|
|
667
626
|
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
@@ -673,12 +632,9 @@
|
|
|
673
632
|
}
|
|
674
633
|
|
|
675
634
|
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
676
|
-
// Plus d'insertions possibles ce cycle, continuer immédiatement
|
|
677
635
|
setTimeout(arguments[0], 50);
|
|
678
636
|
} else if (inserted === 0 && count > 0) {
|
|
679
637
|
// Pool épuisé ou recyclage pas encore disponible.
|
|
680
|
-
// Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
|
|
681
|
-
// le temps de défiler hors écran et devenir recyclables.
|
|
682
638
|
if (state.poolWaitAttempts < 8) {
|
|
683
639
|
state.poolWaitAttempts += 1;
|
|
684
640
|
setTimeout(arguments[0], 50);
|
|
@@ -712,15 +668,11 @@
|
|
|
712
668
|
state.pageKey = getPageKey();
|
|
713
669
|
ensureObserver();
|
|
714
670
|
|
|
715
|
-
// CRITIQUE: Attendre 300ms avant de permettre l'insertion de nouveaux placeholders
|
|
716
|
-
// pour laisser les anciens showAds() (en cours) se terminer ou échouer proprement
|
|
717
|
-
// Sinon race condition: NodeBB vide le DOM pendant que Ezoic essaie d'accéder aux placeholders
|
|
718
671
|
state.canShowAds = true;
|
|
719
672
|
});
|
|
720
673
|
|
|
721
674
|
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
722
675
|
ensureObserver();
|
|
723
|
-
// category.loaded = infinite scroll, Ezoic déjà chargé normalement
|
|
724
676
|
waitForContentThenRun();
|
|
725
677
|
});
|
|
726
678
|
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
@@ -750,7 +702,6 @@
|
|
|
750
702
|
window.requestAnimationFrame(() => {
|
|
751
703
|
ticking = false;
|
|
752
704
|
enforceNoAdjacentAds();
|
|
753
|
-
// Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
|
|
754
705
|
const now = Date.now();
|
|
755
706
|
if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
|
|
756
707
|
state.lastScrollRun = now;
|
|
@@ -760,7 +711,6 @@
|
|
|
760
711
|
}, { passive: true });
|
|
761
712
|
}
|
|
762
713
|
|
|
763
|
-
// Fonction qui attend que la page ait assez de contenu avant d'insérer les pubs
|
|
764
714
|
function waitForContentThenRun() {
|
|
765
715
|
const MIN_WORDS = 250;
|
|
766
716
|
let attempts = 0;
|
|
@@ -791,7 +741,6 @@
|
|
|
791
741
|
})();
|
|
792
742
|
}
|
|
793
743
|
|
|
794
|
-
// Fonction qui attend que Ezoic soit vraiment chargé
|
|
795
744
|
function waitForEzoicThenRun() {
|
|
796
745
|
let attempts = 0;
|
|
797
746
|
const maxAttempts = 50; // 50 × 200ms = 10s max
|
package/public/style.css
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
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
|
+
}
|