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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.4.65",
3
+ "version": "1.4.67",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
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
- state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id);
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 { state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id); } catch (e) {}
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{height:auto !important; padding:0 !important; margin: 0.25rem 0;}
2
- .ezoic-ad .ezoic-ad-inner{padding:0;margin:0;}
3
- .ezoic-ad .ezoic-ad-inner > div{padding:0;margin:0;}
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
+ }