nodebb-plugin-ezoic-infinite 1.8.27 → 1.8.29

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.8.27",
3
+ "version": "1.8.29",
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
@@ -136,6 +136,7 @@
136
136
  firstShown: false,
137
137
  wrapsByClass: new Map(),
138
138
  kind: null,
139
+ phState: new Map(), // id -> new|show-queued|shown|destroyed
139
140
  };
140
141
 
141
142
  let blockedUntil = 0;
@@ -144,13 +145,42 @@
144
145
  const isBlocked = () => ts() < blockedUntil;
145
146
  const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
146
147
  const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
147
- const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
148
+ const FILL_SEL = 'iframe, ins, img, video, [data-google-container-id], div[id$="__container__"]';
149
+ const isFilled = n => !!(n?.querySelector?.(FILL_SEL));
150
+ function placeholderLooksUsed(ph) {
151
+ try {
152
+ if (!ph?.isConnected) return false;
153
+ return !!ph.querySelector('ins.adsbygoogle, iframe, [data-google-container-id], div[id$="__container__"]');
154
+ } catch (_) { return false; }
155
+ }
156
+
148
157
 
149
158
  function mutate(fn) {
150
159
  S.mutGuard++;
151
160
  try { fn(); } finally { S.mutGuard--; }
152
161
  }
153
162
 
163
+
164
+ function clearEmptyIfFilled(wrap) {
165
+ try {
166
+ if (!wrap?.isConnected) return false;
167
+ const ph = wrap.querySelector(`[id^="${PH_PREFIX}"]`);
168
+ if (!ph) return false;
169
+ if (!isFilled(ph)) return false;
170
+ wrap.classList.remove('is-empty');
171
+ const id = parseInt(wrap.getAttribute(A_WRAPID) || '0', 10);
172
+ if (Number.isFinite(id) && id > 0) S.phState.set(id, 'shown');
173
+ return true;
174
+ } catch (_) { return false; }
175
+ }
176
+
177
+ function scheduleUncollapseChecksForWrap(wrap) {
178
+ if (!wrap) return;
179
+ for (const ms of [500, 1500, 3000, 7000, 15000]) {
180
+ setTimeout(() => { try { clearEmptyIfFilled(wrap); } catch (_) {} }, ms);
181
+ }
182
+ }
183
+
154
184
  // ── Config ─────────────────────────────────────────────────────────────────
155
185
 
156
186
  async function fetchConfig() {
@@ -370,14 +400,12 @@
370
400
  best.setAttribute(A_CREATED, String(ts()));
371
401
  best.setAttribute(A_SHOWN, '0');
372
402
  best.classList.remove('is-empty');
373
- const oldPh = best.querySelector(`#${PH_PREFIX}${id}`);
374
- if (oldPh) {
375
- const fresh = document.createElement('div');
376
- fresh.id = `${PH_PREFIX}${id}`;
377
- fresh.setAttribute('data-ezoic-id', String(id));
378
- fresh.style.minHeight = `${MIN_PLACEHOLDER_HEIGHT}px`;
379
- oldPh.replaceWith(fresh);
380
- }
403
+ best.replaceChildren();
404
+ const fresh = document.createElement('div');
405
+ fresh.id = `${PH_PREFIX}${id}`;
406
+ fresh.setAttribute('data-ezoic-id', String(id));
407
+ fresh.style.minHeight = `${MIN_PLACEHOLDER_HEIGHT}px`;
408
+ best.appendChild(fresh);
381
409
  targetEl.insertAdjacentElement('afterend', best);
382
410
  });
383
411
  if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
@@ -386,11 +414,13 @@
386
414
  // Recyclage Ezoic : détruire l'ancien placeholder avant de réutiliser le même ID.
387
415
  // Puis ré-observer + re-show (batché via patchShowAds) sur le placeholder recréé.
388
416
  const doDestroy = () => {
417
+ S.phState.set(id, 'destroyed');
389
418
  try { ez.destroyPlaceholders(id); } catch (_) {
390
419
  try { ez.destroyPlaceholders([id]); } catch (_) {}
391
420
  }
392
421
  setTimeout(() => {
393
422
  try { observePh(id); } catch (_) {}
423
+ S.phState.set(id, 'new');
394
424
  try { enqueueShow(id); } catch (_) {}
395
425
  }, 450);
396
426
  };
@@ -425,6 +455,7 @@
425
455
  const w = makeWrap(id, klass, key);
426
456
  mutate(() => el.insertAdjacentElement('afterend', w));
427
457
  S.mountedIds.add(id);
458
+ S.phState.set(id, 'new');
428
459
  S.wrapByKey.set(key, w);
429
460
  registerWrap(klass, w);
430
461
  return w;
@@ -435,7 +466,7 @@
435
466
  const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
436
467
  if (ph instanceof Element) S.io?.unobserve(ph);
437
468
  const id = parseInt(w.getAttribute(A_WRAPID), 10);
438
- if (Number.isFinite(id)) S.mountedIds.delete(id);
469
+ if (Number.isFinite(id)) { S.mountedIds.delete(id); S.phState.delete(id); }
439
470
  const key = w.getAttribute(A_ANCHOR);
440
471
  if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
441
472
  const klass = Array.from(w.classList || []).find(c => c !== WRAP_CLASS && c.startsWith('ezoic-ad-'));
@@ -554,11 +585,14 @@
554
585
 
555
586
  function enqueueShow(id) {
556
587
  if (!id || isBlocked()) return;
588
+ const st = S.phState.get(id);
589
+ if (st === 'show-queued' || st === 'shown') return;
557
590
  if (ts() - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) return;
558
591
  if (S.inflight >= MAX_INFLIGHT) {
559
- if (!S.pendingSet.has(id)) { S.pending.push(id); S.pendingSet.add(id); }
592
+ if (!S.pendingSet.has(id)) { S.pending.push(id); S.pendingSet.add(id); S.phState.set(id, 'show-queued'); }
560
593
  return;
561
594
  }
595
+ S.phState.set(id, 'show-queued');
562
596
  startShow(id);
563
597
  }
564
598
 
@@ -587,18 +621,23 @@
587
621
  try {
588
622
  if (isBlocked()) { clearTimeout(timer); return release(); }
589
623
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
590
- if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
624
+ if (!ph?.isConnected) { S.phState.delete(id); clearTimeout(timer); return release(); }
625
+ if (isFilled(ph) || placeholderLooksUsed(ph)) { S.phState.set(id, 'shown'); clearTimeout(timer); return release(); }
591
626
 
592
627
  const t = ts();
593
628
  if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
594
629
  S.lastShow.set(id, t);
595
630
 
596
631
  try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
632
+ S.phState.set(id, 'shown');
597
633
 
598
634
  window.ezstandalone = window.ezstandalone || {};
599
635
  const ez = window.ezstandalone;
600
636
  const doShow = () => {
637
+ let wrap = null;
638
+ try { wrap = ph.closest?.(`.${WRAP_CLASS}`) || null; } catch (_) {}
601
639
  try { ez.showAds(id); } catch (_) {}
640
+ if (wrap) scheduleUncollapseChecksForWrap(wrap);
602
641
  scheduleEmptyCheck(id, t);
603
642
  setTimeout(() => { clearTimeout(timer); release(); }, 700);
604
643
  };
@@ -614,7 +653,8 @@
614
653
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
615
654
  if (!wrap || !ph?.isConnected) return;
616
655
  if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
617
- wrap.classList.toggle('is-empty', !isFilled(ph));
656
+ if (clearEmptyIfFilled(wrap)) return;
657
+ wrap.classList.add('is-empty');
618
658
  } catch (_) {}
619
659
  }, EMPTY_CHECK_MS);
620
660
  }
@@ -642,7 +682,12 @@
642
682
  if (isBlocked() || !q.size) return;
643
683
  const ids = Array.from(q).sort((a, b) => a - b);
644
684
  q.clear();
645
- const valid = ids.filter(id => document.getElementById(`${PH_PREFIX}${id}`)?.isConnected);
685
+ const valid = ids.filter(id => {
686
+ const ph = document.getElementById(`${PH_PREFIX}${id}`);
687
+ if (!ph?.isConnected) { S.phState.delete(id); return false; }
688
+ if (placeholderLooksUsed(ph)) { S.phState.set(id, 'shown'); return false; }
689
+ return true;
690
+ });
646
691
  for (let i = 0; i < valid.length; i += BATCH_SIZE) {
647
692
  const chunk = valid.slice(i, i + BATCH_SIZE);
648
693
  try { orig(...chunk); } catch (_) {
@@ -656,7 +701,10 @@
656
701
  for (const v of ids) {
657
702
  const id = parseInt(v, 10);
658
703
  if (!Number.isFinite(id) || id <= 0) continue;
659
- if (!document.getElementById(`${PH_PREFIX}${id}`)?.isConnected) continue;
704
+ const ph = document.getElementById(`${PH_PREFIX}${id}`);
705
+ if (!ph?.isConnected) continue;
706
+ if (placeholderLooksUsed(ph)) { S.phState.set(id, 'shown'); continue; }
707
+ S.phState.set(id, 'show-queued');
660
708
  q.add(id);
661
709
  }
662
710
  if (!q.size) return;
@@ -762,6 +810,7 @@
762
810
  S.wrapByKey.clear();
763
811
  S.wrapsByClass.clear();
764
812
  S.kind = null;
813
+ S.phState.clear();
765
814
  S.inflight = 0;
766
815
  S.pending = [];
767
816
  S.pendingSet.clear();
@@ -776,6 +825,21 @@
776
825
  if (S.domObs) return;
777
826
  S.domObs = new MutationObserver(muts => {
778
827
  if (S.mutGuard > 0 || isBlocked()) return;
828
+ for (const m of muts) {
829
+ if (m.type !== 'childList') continue;
830
+ for (const node of m.addedNodes) {
831
+ if (!(node instanceof Element)) continue;
832
+ let hasAd = false;
833
+ try {
834
+ hasAd = !!(node.matches?.(FILL_SEL) || node.querySelector?.(FILL_SEL));
835
+ } catch (_) {}
836
+ if (!hasAd) continue;
837
+ try {
838
+ const wrap = node.closest?.(`.${WRAP_CLASS}.is-empty`) || m.target?.closest?.(`.${WRAP_CLASS}.is-empty`);
839
+ if (wrap) clearEmptyIfFilled(wrap);
840
+ } catch (_) {}
841
+ }
842
+ }
779
843
  const relevant = (() => {
780
844
  const k = getKind();
781
845
  if (k === 'topic') return [SEL.post];
package/public/style.css CHANGED
@@ -78,3 +78,14 @@
78
78
  margin: 0 !important;
79
79
  padding: 0 !important;
80
80
  }
81
+
82
+
83
+ /* Anti-faux-empty : si une pub est présente, annule le collapse */
84
+ .nodebb-ezoic-wrap.is-empty:has(iframe),
85
+ .nodebb-ezoic-wrap.is-empty:has([data-google-container-id]),
86
+ .nodebb-ezoic-wrap.is-empty:has(div[id$="__container__"]) {
87
+ height: auto !important;
88
+ min-height: 0 !important;
89
+ max-height: none !important;
90
+ overflow: visible !important;
91
+ }