nodebb-plugin-ezoic-infinite 1.7.33 → 1.7.35

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 +72 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.7.33",
3
+ "version": "1.7.35",
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
@@ -32,13 +32,16 @@
32
32
  *
33
33
  * v34 moveDistantWrap — voir v38.
34
34
  *
35
- * v39 Recyclage réel des slots via l'API Ezoic complète :
36
- * destroyPlaceholders([id])déplacement DOMdefine([id]) displayMore([id]).
37
- * destroyPlaceholders() efface le slot de la registry Ezoic, define() le
38
- * réenregistre comme neuf, displayMore() est l'API infinite-scroll d'Ezoic
39
- * (contrairement à showAds() qui est pour le chargement initial).
40
- * Priorité de recyclage : wraps vides en premier (pas de pub visible perdue),
41
- * puis wraps remplis si nécessaire. Seuil : 4 viewports au-dessus.
35
+ * v42 Seuil de recyclage aligné sur IO_MARGIN par device :
36
+ * desktop-(2500 + vh), mobile -(3500 + vh).
37
+ * Un wrap dans la zone IO_MARGIN peut être re-observé après recyclage
38
+ * conflit. On recycle uniquement au-delà de la marge d'observation.
39
+ *
40
+ * v41 Seuil -1vh (trop permissif sur mobile, ignorait IO_MARGIN).
41
+ *
42
+ * v40 Recyclage slots via destroyPlaceholders+define+displayMore avec délais.
43
+ * Séquence : destroy → 300ms → define → 300ms → displayMore.
44
+ * Testé manuellement : fonctionne. displayMore = API Ezoic infinite scroll.
42
45
  *
43
46
  * v38 Pool épuisé = fin de quota Ezoic par page-view. ez.refresh() interdit
44
47
  * sur la même page que ez.enable() — supprimé. moveDistantWrap supprimé :
@@ -294,6 +297,67 @@
294
297
  return null;
295
298
  }
296
299
 
300
+ /**
301
+ * Pool épuisé : recycle un wrap loin au-dessus du viewport.
302
+ * Séquence avec délais (destroyPlaceholders est asynchrone) :
303
+ * destroy([id]) → 300ms → define([id]) → 300ms → displayMore([id])
304
+ * displayMore = API Ezoic prévue pour l'infinite scroll.
305
+ * Priorité : wraps vides d'abord, remplis si nécessaire.
306
+ */
307
+ function recycleAndMove(klass, targetEl, newKey) {
308
+ const ez = window.ezstandalone;
309
+ if (typeof ez?.destroyPlaceholders !== 'function' ||
310
+ typeof ez?.define !== 'function' ||
311
+ typeof ez?.displayMore !== 'function') return null;
312
+
313
+ const vh = window.innerHeight || 800;
314
+ // Seuil : au-delà de l'IO_MARGIN + 1vh pour être sûr que l'observer
315
+ // ne peut plus déclencher de showAds sur ce wrap après recyclage.
316
+ const margin = isMobile() ? 3500 : 2500;
317
+ const threshold = -(margin + vh);
318
+ let bestEmpty = null, bestEmptyBottom = Infinity;
319
+ let bestFilled = null, bestFilledBottom = Infinity;
320
+
321
+ document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(wrap => {
322
+ try {
323
+ const rect = wrap.getBoundingClientRect();
324
+ if (rect.bottom > threshold) return;
325
+ if (!isFilled(wrap)) {
326
+ if (rect.bottom < bestEmptyBottom) { bestEmptyBottom = rect.bottom; bestEmpty = wrap; }
327
+ } else {
328
+ if (rect.bottom < bestFilledBottom) { bestFilledBottom = rect.bottom; bestFilled = wrap; }
329
+ }
330
+ } catch (_) {}
331
+ });
332
+
333
+ const best = bestEmpty ?? bestFilled;
334
+ if (!best) return null;
335
+ const id = parseInt(best.getAttribute(A_WRAPID), 10);
336
+ if (!Number.isFinite(id)) return null;
337
+
338
+ const oldKey = best.getAttribute(A_ANCHOR);
339
+ mutate(() => {
340
+ best.setAttribute(A_ANCHOR, newKey);
341
+ best.setAttribute(A_CREATED, String(ts()));
342
+ best.setAttribute(A_SHOWN, '0');
343
+ best.classList.remove('is-empty');
344
+ const ph = best.querySelector(`#${PH_PREFIX}${id}`);
345
+ if (ph) ph.innerHTML = '';
346
+ targetEl.insertAdjacentElement('afterend', best);
347
+ });
348
+ if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
349
+ S.wrapByKey.set(newKey, best);
350
+
351
+ // Délais requis : destroyPlaceholders est asynchrone en interne
352
+ const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
353
+ const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
354
+ const doDisplay = () => { try { ez.displayMore([id]); } catch (_) {} };
355
+ try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
356
+
357
+ return { id, wrap: best };
358
+ }
359
+
360
+ // ── Wraps DOM — création / suppression ────────────────────────────────────
297
361
 
298
362
  function makeWrap(id, klass, key) {
299
363
  const w = document.createElement('div');
@@ -407,9 +471,8 @@
407
471
  const w = insertAfter(el, id, klass, key);
408
472
  if (w) { observePh(id); inserted++; }
409
473
  } else {
410
- // Pool épuisé : recycler un wrap distant via destroyPlaceholders+define+displayMore
411
474
  const recycled = recycleAndMove(klass, el, key);
412
- if (!recycled) break; // Aucun wrap recyclable → arrêt
475
+ if (!recycled) break;
413
476
  inserted++;
414
477
  }
415
478
  }