nodebb-plugin-ezoic-infinite 1.4.65 → 1.4.67
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 +38 -55
- package/public/style.css +10 -3
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;
|
|
@@ -119,6 +116,11 @@
|
|
|
119
116
|
window.ezstandalone.destroyPlaceholders(filtered);
|
|
120
117
|
}
|
|
121
118
|
} catch (e) {}
|
|
119
|
+
|
|
120
|
+
// Recyclage: libérer IDs après 100ms
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
123
|
+
}, 100);
|
|
122
124
|
};
|
|
123
125
|
try {
|
|
124
126
|
window.ezstandalone = window.ezstandalone || {};
|
|
@@ -126,10 +128,37 @@
|
|
|
126
128
|
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
127
129
|
else window.ezstandalone.cmd.push(call);
|
|
128
130
|
} catch (e) {}
|
|
131
|
+
|
|
132
|
+
// Recyclage: libérer IDs après 100ms
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
135
|
+
}, 100);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Nettoyer éléments Ezoic invisibles qui créent espace vertical
|
|
139
|
+
function cleanupInvisibleEzoicElements() {
|
|
140
|
+
try {
|
|
141
|
+
document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
|
|
142
|
+
// Supprimer éléments invisibles après le placeholder
|
|
143
|
+
wrapper.querySelectorAll('*').forEach(el => {
|
|
144
|
+
if (el.id && el.id.startsWith('ezoic-pub-ad-placeholder-')) return;
|
|
145
|
+
|
|
146
|
+
const rect = el.getBoundingClientRect();
|
|
147
|
+
const computed = window.getComputedStyle(el);
|
|
148
|
+
|
|
149
|
+
// Élément invisible mais prend de l'espace
|
|
150
|
+
if (
|
|
151
|
+
(computed.display !== 'none' && computed.visibility !== 'hidden') &&
|
|
152
|
+
(rect.height === 0 || computed.opacity === '0') &&
|
|
153
|
+
el.children.length === 0
|
|
154
|
+
) {
|
|
155
|
+
el.remove();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
} catch (e) {}
|
|
129
160
|
}
|
|
130
161
|
|
|
131
|
-
|
|
132
|
-
// Nettoyer les wrappers vides (sans pub) pour éviter espaces verticaux
|
|
133
162
|
function cleanupEmptyWrappers() {
|
|
134
163
|
try {
|
|
135
164
|
document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
|
|
@@ -217,10 +246,8 @@
|
|
|
217
246
|
if (findWrap(kindClass, afterPos)) return null;
|
|
218
247
|
|
|
219
248
|
// 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
249
|
if (insertingIds.has(id)) return null;
|
|
222
250
|
|
|
223
|
-
// 2. Vérifier qu'aucun placeholder avec cet ID n'existe déjà dans le DOM
|
|
224
251
|
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
225
252
|
if (existingPh && existingPh.isConnected) return null;
|
|
226
253
|
|
|
@@ -233,7 +260,6 @@
|
|
|
233
260
|
attachFillObserver(wrap, id);
|
|
234
261
|
return wrap;
|
|
235
262
|
} finally {
|
|
236
|
-
// Libérer le lock après 100ms (le temps que le DOM soit stable)
|
|
237
263
|
setTimeout(() => insertingIds.delete(id), 50);
|
|
238
264
|
}
|
|
239
265
|
}
|
|
@@ -244,8 +270,6 @@
|
|
|
244
270
|
}
|
|
245
271
|
|
|
246
272
|
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
273
|
const applyPatch = () => {
|
|
250
274
|
try {
|
|
251
275
|
window.ezstandalone = window.ezstandalone || {}, ez = window.ezstandalone;
|
|
@@ -272,7 +296,6 @@
|
|
|
272
296
|
};
|
|
273
297
|
|
|
274
298
|
applyPatch();
|
|
275
|
-
// Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
|
|
276
299
|
if (!window.__nodebbEzoicPatched) {
|
|
277
300
|
try {
|
|
278
301
|
window.ezstandalone = window.ezstandalone || {};
|
|
@@ -285,7 +308,6 @@
|
|
|
285
308
|
function markFilled(wrap) {
|
|
286
309
|
try {
|
|
287
310
|
if (!wrap) return;
|
|
288
|
-
// Disconnect the fill observer first (no need to remove+re-add the attribute)
|
|
289
311
|
if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
|
|
290
312
|
wrap.setAttribute('data-ezoic-filled', '1');
|
|
291
313
|
} catch (e) {}
|
|
@@ -301,19 +323,18 @@
|
|
|
301
323
|
if (!ph) return;
|
|
302
324
|
// Already filled?
|
|
303
325
|
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
304
|
-
markFilled(wrap);
|
|
305
|
-
|
|
326
|
+
markFilled(wrap); // Afficher wrapper
|
|
327
|
+
sessionDefinedIds.add(id);
|
|
306
328
|
return;
|
|
307
329
|
}
|
|
308
330
|
const obs = new MutationObserver(() => {
|
|
309
331
|
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
310
|
-
markFilled(wrap);
|
|
311
|
-
try {
|
|
332
|
+
markFilled(wrap); // CRITIQUE: Afficher wrapper maintenant
|
|
333
|
+
try { sessionDefinedIds.add(id); } catch (e) {}
|
|
312
334
|
try { obs.disconnect(); } catch (e) {}
|
|
313
335
|
}
|
|
314
336
|
});
|
|
315
337
|
obs.observe(ph, { childList: true, subtree: true });
|
|
316
|
-
// Keep a weak reference on the wrapper so we can disconnect on recycle/remove
|
|
317
338
|
wrap.__ezoicFillObs = obs;
|
|
318
339
|
} catch (e) {}
|
|
319
340
|
}
|
|
@@ -333,15 +354,12 @@
|
|
|
333
354
|
return filled;
|
|
334
355
|
}
|
|
335
356
|
|
|
336
|
-
// Appeler showAds() en batch selon recommandations Ezoic
|
|
337
|
-
// Au lieu de showAds(id1), showAds(id2)... faire showAds(id1, id2, id3...)
|
|
338
357
|
let batchShowAdsTimer = null;
|
|
339
358
|
const pendingShowAdsIds = new Set();
|
|
340
359
|
|
|
341
360
|
function scheduleShowAdsBatch(id) {
|
|
342
361
|
if (!id) return;
|
|
343
362
|
|
|
344
|
-
// CRITIQUE: Si cet ID a déjà été défini (sessionDefinedIds), le détruire d'abord
|
|
345
363
|
if (sessionDefinedIds.has(id)) {
|
|
346
364
|
try {
|
|
347
365
|
destroyPlaceholderIds([id]);
|
|
@@ -356,7 +374,6 @@
|
|
|
356
374
|
// Ajouter à la batch
|
|
357
375
|
pendingShowAdsIds.add(id);
|
|
358
376
|
|
|
359
|
-
// Debounce: attendre 100ms pour collecter tous les IDs
|
|
360
377
|
clearTimeout(batchShowAdsTimer);
|
|
361
378
|
batchShowAdsTimer = setTimeout(() => {
|
|
362
379
|
if (pendingShowAdsIds.size === 0) return;
|
|
@@ -406,19 +423,15 @@
|
|
|
406
423
|
const startPageKey = state.pageKey;
|
|
407
424
|
let attempts = 0;
|
|
408
425
|
(function waitForPh() {
|
|
409
|
-
// Abort if the user navigated away since this showAds was scheduled
|
|
410
426
|
if (state.pageKey !== startPageKey) return;
|
|
411
|
-
// Abort if another concurrent call is already handling this id
|
|
412
427
|
if (state.pendingById.has(id)) return;
|
|
413
428
|
|
|
414
429
|
attempts += 1;
|
|
415
430
|
const el = document.getElementById(phId);
|
|
416
431
|
if (el && el.isConnected) {
|
|
417
|
-
// CRITIQUE: Vérifier que le placeholder est VISIBLE
|
|
418
432
|
|
|
419
433
|
// Si on arrive ici, soit visible, soit timeout
|
|
420
434
|
|
|
421
|
-
// Si doCall() réussit, Ezoic est chargé et showAds a été appelé → sortir
|
|
422
435
|
if (doCall()) {
|
|
423
436
|
state.pendingById.delete(id);
|
|
424
437
|
return;
|
|
@@ -484,7 +497,6 @@
|
|
|
484
497
|
const el = items[afterPos - 1];
|
|
485
498
|
if (!el || !el.isConnected) continue;
|
|
486
499
|
|
|
487
|
-
// Prevent adjacent ads (DOM-based, robust against virtualization)
|
|
488
500
|
if (isAdjacentAd(el) || isPrevAd(el)) {
|
|
489
501
|
continue;
|
|
490
502
|
}
|
|
@@ -501,17 +513,14 @@
|
|
|
501
513
|
|
|
502
514
|
let wrap = null;
|
|
503
515
|
if (pick.recycled && pick.recycled.wrap) {
|
|
504
|
-
// Only destroy if Ezoic has actually defined this placeholder before
|
|
505
516
|
if (sessionDefinedIds.has(id)) {
|
|
506
517
|
destroyPlaceholderIds([id]);
|
|
507
518
|
}
|
|
508
|
-
// Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
|
|
509
519
|
const oldWrap = pick.recycled.wrap;
|
|
510
520
|
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
511
521
|
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
512
522
|
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
513
523
|
if (!wrap) continue;
|
|
514
|
-
// Attendre que le wrapper soit dans le DOM puis appeler showAds
|
|
515
524
|
setTimeout(() => {
|
|
516
525
|
callShowAdsWhenReady(id);
|
|
517
526
|
}, 50);
|
|
@@ -525,12 +534,10 @@
|
|
|
525
534
|
}
|
|
526
535
|
|
|
527
536
|
liveArr.push({ id, wrap });
|
|
528
|
-
// If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
|
|
529
537
|
if (wrap && (
|
|
530
538
|
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
531
539
|
)) {
|
|
532
540
|
try { wrap.remove(); } catch (e) {}
|
|
533
|
-
// Put id back if it was newly consumed (not recycled)
|
|
534
541
|
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
535
542
|
try { kindPool.unshift(id); } catch (e) {}
|
|
536
543
|
usedSet.delete(id);
|
|
@@ -547,7 +554,6 @@
|
|
|
547
554
|
for (let i = 0; i < ads.length; i++) {
|
|
548
555
|
const ad = ads[i], prev = ad.previousElementSibling;
|
|
549
556
|
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
550
|
-
// Supprimer le wrapper adjacent au lieu de le cacher
|
|
551
557
|
try {
|
|
552
558
|
const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
553
559
|
if (ph) {
|
|
@@ -568,8 +574,6 @@
|
|
|
568
574
|
function cleanup() {
|
|
569
575
|
destroyUsedPlaceholders();
|
|
570
576
|
|
|
571
|
-
// CRITIQUE: Supprimer TOUS les wrappers .ezoic-ad du DOM
|
|
572
|
-
// Sinon ils restent et deviennent "unused" sur la nouvelle page
|
|
573
577
|
document.querySelectorAll('.ezoic-ad').forEach(el => {
|
|
574
578
|
try { el.remove(); } catch (e) {}
|
|
575
579
|
});
|
|
@@ -585,23 +589,14 @@
|
|
|
585
589
|
state.usedPosts.clear();
|
|
586
590
|
state.usedCategories.clear();
|
|
587
591
|
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
592
|
state.pendingById.clear();
|
|
591
593
|
state.definedIds.clear();
|
|
592
594
|
|
|
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
595
|
state.activeTimeouts.forEach(id => {
|
|
600
596
|
try { clearTimeout(id); } catch (e) {}
|
|
601
597
|
});
|
|
602
598
|
state.activeTimeouts.clear();
|
|
603
599
|
|
|
604
|
-
// Vider aussi pendingById pour annuler les showAds en attente
|
|
605
600
|
state.pendingById.clear();
|
|
606
601
|
|
|
607
602
|
if (state.obs) { state.obs.disconnect(); state.obs = null; }
|
|
@@ -618,7 +613,6 @@
|
|
|
618
613
|
}
|
|
619
614
|
|
|
620
615
|
async function runCore() {
|
|
621
|
-
// Attendre que canInsert soit true (protection race condition navigation)
|
|
622
616
|
if (!state.canShowAds) {
|
|
623
617
|
return;
|
|
624
618
|
}
|
|
@@ -661,7 +655,6 @@
|
|
|
661
655
|
|
|
662
656
|
enforceNoAdjacentAds();
|
|
663
657
|
|
|
664
|
-
// If nothing inserted and list isn't in DOM yet (first click), retry a bit
|
|
665
658
|
let count = 0;
|
|
666
659
|
if (kind === 'topic') count = getPostContainers().length;
|
|
667
660
|
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
@@ -673,12 +666,9 @@
|
|
|
673
666
|
}
|
|
674
667
|
|
|
675
668
|
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
676
|
-
// Plus d'insertions possibles ce cycle, continuer immédiatement
|
|
677
669
|
setTimeout(arguments[0], 50);
|
|
678
670
|
} else if (inserted === 0 && count > 0) {
|
|
679
671
|
// 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
672
|
if (state.poolWaitAttempts < 8) {
|
|
683
673
|
state.poolWaitAttempts += 1;
|
|
684
674
|
setTimeout(arguments[0], 50);
|
|
@@ -712,15 +702,11 @@
|
|
|
712
702
|
state.pageKey = getPageKey();
|
|
713
703
|
ensureObserver();
|
|
714
704
|
|
|
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
705
|
state.canShowAds = true;
|
|
719
706
|
});
|
|
720
707
|
|
|
721
708
|
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
722
709
|
ensureObserver();
|
|
723
|
-
// category.loaded = infinite scroll, Ezoic déjà chargé normalement
|
|
724
710
|
waitForContentThenRun();
|
|
725
711
|
});
|
|
726
712
|
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
@@ -750,7 +736,6 @@
|
|
|
750
736
|
window.requestAnimationFrame(() => {
|
|
751
737
|
ticking = false;
|
|
752
738
|
enforceNoAdjacentAds();
|
|
753
|
-
// Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
|
|
754
739
|
const now = Date.now();
|
|
755
740
|
if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
|
|
756
741
|
state.lastScrollRun = now;
|
|
@@ -760,7 +745,6 @@
|
|
|
760
745
|
}, { passive: true });
|
|
761
746
|
}
|
|
762
747
|
|
|
763
|
-
// Fonction qui attend que la page ait assez de contenu avant d'insérer les pubs
|
|
764
748
|
function waitForContentThenRun() {
|
|
765
749
|
const MIN_WORDS = 250;
|
|
766
750
|
let attempts = 0;
|
|
@@ -791,7 +775,6 @@
|
|
|
791
775
|
})();
|
|
792
776
|
}
|
|
793
777
|
|
|
794
|
-
// Fonction qui attend que Ezoic soit vraiment chargé
|
|
795
778
|
function waitForEzoicThenRun() {
|
|
796
779
|
let attempts = 0;
|
|
797
780
|
const maxAttempts = 50; // 50 × 200ms = 10s max
|
package/public/style.css
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
.ezoic-ad{
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
.ezoic-ad {
|
|
2
|
+
height: auto !important;
|
|
3
|
+
padding: 0 !important;
|
|
4
|
+
margin: 0 !important;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.ezoic-ad * {
|
|
8
|
+
margin: 0 !important;
|
|
9
|
+
padding: 0 !important;
|
|
10
|
+
}
|