nodebb-plugin-ezoic-infinite 1.7.88 → 1.7.89

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/client.js +41 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.7.88",
3
+ "version": "1.7.89",
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
@@ -282,6 +282,28 @@
282
282
  return (w?.isConnected) ? w : null;
283
283
  }
284
284
 
285
+ /**
286
+ * Libère les ids de wraps supprimés du DOM (virtualisation NodeBB / rerender).
287
+ * Sans ce sweep, mountedIds peut conserver des ids fantômes → pool épuisé
288
+ * après long scroll alors qu'aucun wrap recyclable n'existe encore en DOM.
289
+ */
290
+ function sweepDeadWraps() {
291
+ for (const [key, w] of S.wrapByKey.entries()) {
292
+ if (w?.isConnected) continue;
293
+ const id = parseInt(w?.getAttribute?.(A_WRAPID), 10);
294
+ if (Number.isFinite(id)) {
295
+ S.mountedIds.delete(id);
296
+ S.lastShow.delete(id);
297
+ S.pendingSet.delete(id);
298
+ }
299
+ S.wrapByKey.delete(key);
300
+ }
301
+ if (S.pending.length) {
302
+ S.pending = S.pending.filter(id => !S.pendingSet.has(id) || document.getElementById(`${PH_PREFIX}${id}`)?.isConnected);
303
+ S.pendingSet = new Set(S.pending);
304
+ }
305
+ }
306
+
285
307
  // ── Pool ───────────────────────────────────────────────────────────────────
286
308
 
287
309
  /**
@@ -471,7 +493,13 @@
471
493
  const key = anchorKey(klass, el);
472
494
  if (findWrap(key)) continue;
473
495
 
474
- const id = pickId(poolKey);
496
+ let id = pickId(poolKey);
497
+ if (!id) {
498
+ // Réessaie après sweep : des wraps ont pu être retirés du DOM (virtualisation)
499
+ // sans passer par dropWrap, laissant des ids fantômes dans mountedIds.
500
+ sweepDeadWraps();
501
+ id = pickId(poolKey);
502
+ }
475
503
  if (id) {
476
504
  const w = insertAfter(el, id, klass, key);
477
505
  if (w) { observePh(id); inserted++; }
@@ -613,6 +641,7 @@
613
641
  async function runCore() {
614
642
  if (isBlocked()) return 0;
615
643
  patchShowAds();
644
+ sweepDeadWraps();
616
645
 
617
646
  const cfg = await fetchConfig();
618
647
  if (!cfg || cfg.excluded) return 0;
@@ -711,16 +740,26 @@
711
740
  const allSel = [SEL.post, SEL.topic, SEL.category];
712
741
  S.domObs = new MutationObserver(muts => {
713
742
  if (S.mutGuard > 0 || isBlocked()) return;
743
+ let sawRelevantAdd = false;
744
+ let sawWrapRemoval = false;
714
745
  for (const m of muts) {
746
+ for (const n of m.removedNodes) {
747
+ if (n.nodeType !== 1) continue;
748
+ if ((n.matches && n.matches(`.${WRAP_CLASS}`)) || n.querySelector?.(`.${WRAP_CLASS}`)) {
749
+ sawWrapRemoval = true;
750
+ }
751
+ }
715
752
  for (const n of m.addedNodes) {
716
753
  if (n.nodeType !== 1) continue;
717
754
  // matches() d'abord (O(1)), querySelector() seulement si nécessaire
718
755
  if (allSel.some(s => { try { return n.matches(s); } catch(_){return false;} }) ||
719
756
  allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
720
- requestBurst(); return;
757
+ sawRelevantAdd = true;
721
758
  }
722
759
  }
723
760
  }
761
+ if (sawWrapRemoval) sweepDeadWraps();
762
+ if (sawRelevantAdd) requestBurst();
724
763
  });
725
764
  try { S.domObs.observe(document.body, { childList: true, subtree: true }); } catch (_) {}
726
765
  }