nodebb-plugin-ezoic-infinite 1.7.82 → 1.7.83

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.7.82",
3
+ "version": "1.7.83",
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
@@ -132,6 +132,7 @@
132
132
  burstDeadline: 0,
133
133
  burstCount: 0,
134
134
  lastBurstTs: 0,
135
+ emptyChecks: new Map(), // id -> timeout ids[]
135
136
  };
136
137
 
137
138
  let blockedUntil = 0;
@@ -142,6 +143,44 @@
142
143
  const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
143
144
  const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
144
145
 
146
+ function clearEmptyChecks(id) {
147
+ const timers = S.emptyChecks.get(id);
148
+ if (!timers) return;
149
+ for (const t of timers) { try { clearTimeout(t); } catch (_) {} }
150
+ S.emptyChecks.delete(id);
151
+ }
152
+
153
+ function queueEmptyCheck(id, timerId) {
154
+ const arr = S.emptyChecks.get(id) || [];
155
+ arr.push(timerId);
156
+ S.emptyChecks.set(id, arr);
157
+ }
158
+
159
+ function uncollapseIfFilled(ph) {
160
+ try {
161
+ const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
162
+ if (!wrap) return false;
163
+ if (!isFilled(ph)) return false;
164
+ wrap.classList.remove('is-empty');
165
+ return true;
166
+ } catch (_) { return false; }
167
+ }
168
+
169
+ function watchPlaceholderFill(ph) {
170
+ if (!ph || ph.__nbbFillObs) return;
171
+ try {
172
+ const obs = new MutationObserver(() => { if (uncollapseIfFilled(ph)) return; });
173
+ obs.observe(ph, { childList: true, subtree: true, attributes: true });
174
+ ph.__nbbFillObs = obs;
175
+ } catch (_) {}
176
+ uncollapseIfFilled(ph);
177
+ }
178
+
179
+ function unwatchPlaceholderFill(ph) {
180
+ try { ph?.__nbbFillObs?.disconnect?.(); } catch (_) {}
181
+ try { if (ph) delete ph.__nbbFillObs; } catch (_) {}
182
+ }
183
+
145
184
  function mutate(fn) {
146
185
  S.mutGuard++;
147
186
  try { fn(); } finally { S.mutGuard--; }
@@ -341,13 +380,14 @@
341
380
  // Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
342
381
  // parasite si le nœud était encore dans la zone IO_MARGIN.
343
382
  try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
383
+ clearEmptyChecks(id);
344
384
  mutate(() => {
345
385
  best.setAttribute(A_ANCHOR, newKey);
346
386
  best.setAttribute(A_CREATED, String(ts()));
347
387
  best.setAttribute(A_SHOWN, '0');
348
388
  best.classList.remove('is-empty');
349
389
  const ph = best.querySelector(`#${PH_PREFIX}${id}`);
350
- if (ph) ph.innerHTML = '';
390
+ if (ph) { ph.innerHTML = ''; watchPlaceholderFill(ph); }
351
391
  targetEl.insertAdjacentElement('afterend', best);
352
392
  });
353
393
  if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
@@ -388,15 +428,16 @@
388
428
  mutate(() => el.insertAdjacentElement('afterend', w));
389
429
  S.mountedIds.add(id);
390
430
  S.wrapByKey.set(key, w);
431
+ try { watchPlaceholderFill(w.querySelector(`#${PH_PREFIX}${id}`)); } catch (_) {}
391
432
  return w;
392
433
  }
393
434
 
394
435
  function dropWrap(w) {
395
436
  try {
396
437
  const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
397
- if (ph instanceof Element) S.io?.unobserve(ph);
438
+ if (ph instanceof Element) { S.io?.unobserve(ph); unwatchPlaceholderFill(ph); }
398
439
  const id = parseInt(w.getAttribute(A_WRAPID), 10);
399
- if (Number.isFinite(id)) S.mountedIds.delete(id);
440
+ if (Number.isFinite(id)) { S.mountedIds.delete(id); clearEmptyChecks(id); }
400
441
  const key = w.getAttribute(A_ANCHOR);
401
442
  if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
402
443
  w.remove();
@@ -503,7 +544,9 @@
503
544
 
504
545
  function observePh(id) {
505
546
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
506
- if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
547
+ if (!ph?.isConnected) return;
548
+ watchPlaceholderFill(ph);
549
+ try { getIO()?.observe(ph); } catch (_) {}
507
550
  }
508
551
 
509
552
  function enqueueShow(id) {
@@ -542,6 +585,8 @@
542
585
  if (isBlocked()) { clearTimeout(timer); return release(); }
543
586
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
544
587
  if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
588
+ clearEmptyChecks(id);
589
+ try { ph.closest?.(`.${WRAP_CLASS}`)?.classList.remove('is-empty'); } catch (_) {}
545
590
 
546
591
  const t = ts();
547
592
  if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
@@ -562,15 +607,19 @@
562
607
  }
563
608
 
564
609
  function scheduleEmptyCheck(id, showTs) {
565
- setTimeout(() => {
610
+ clearEmptyChecks(id);
611
+ const delays = [EMPTY_CHECK_MS, EMPTY_CHECK_MS + 5000, EMPTY_CHECK_MS + 15000];
612
+ const runCheck = () => {
566
613
  try {
567
614
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
568
615
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
569
616
  if (!wrap || !ph?.isConnected) return;
570
617
  if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
571
- wrap.classList.toggle('is-empty', !isFilled(ph));
618
+ if (uncollapseIfFilled(ph)) return;
619
+ wrap.classList.add('is-empty');
572
620
  } catch (_) {}
573
- }, EMPTY_CHECK_MS);
621
+ };
622
+ for (const d of delays) queueEmptyCheck(id, setTimeout(runCheck, d));
574
623
  }
575
624
 
576
625
  // ── Patch Ezoic showAds ────────────────────────────────────────────────────
@@ -700,6 +749,8 @@
700
749
  S.inflight = 0;
701
750
  S.pending = [];
702
751
  S.pendingSet.clear();
752
+ for (const timers of S.emptyChecks.values()) for (const t of timers) { try { clearTimeout(t); } catch (_) {} }
753
+ S.emptyChecks.clear();
703
754
  S.burstActive = false;
704
755
  S.runQueued = false;
705
756
  }
package/public/style.css CHANGED
@@ -76,3 +76,12 @@
76
76
  margin: 0 !important;
77
77
  padding: 0 !important;
78
78
  }
79
+
80
+
81
+ /* Filet de sécurité : si Ezoic a rempli le wrap, annuler le collapse même si .is-empty est resté */
82
+ .nodebb-ezoic-wrap.is-empty:has(iframe, ins, img, video, [data-google-container-id]) {
83
+ height: auto !important;
84
+ min-height: 1px !important;
85
+ max-height: none !important;
86
+ overflow: visible !important;
87
+ }