nodebb-plugin-ezoic-infinite 1.7.19 → 1.7.20

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 +17 -85
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.7.19",
3
+ "version": "1.7.20",
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
@@ -1,10 +1,10 @@
1
1
  /**
2
- * NodeBB Ezoic Infinite Ads — client.js v25
2
+ * NodeBB Ezoic Infinite Ads — client.js v26
3
3
  *
4
4
  * Historique des corrections majeures
5
5
  * ────────────────────────────────────
6
6
  * v18 Ancrage stable par data-pid/data-index au lieu d'ordinalMap fragile.
7
- * Suppression du recyclage de wraps (moveWrapAfter). Cleanup complet navigation.
7
+ * Suppression du recyclage de wraps. Cleanup complet navigation.
8
8
  *
9
9
  * v19 Intervalle global basé sur l'ordinal absolu (data-index) et non sur
10
10
  * la position dans le batch courant.
@@ -16,17 +16,17 @@
16
16
  * Fix unobserve(null) → corruption IO → pubads error au scroll retour.
17
17
  * Fix TCF locator : MutationObserver recrée l'iframe si ajaxify la retire.
18
18
  *
19
- * v25 Base v20.1 avec :
20
- * Fix scroll-up / virtualisation NodeBB :
19
+ * v25 Table KIND unifiée avec baseTag + ordinalAttr.
20
+ * Fix scroll-up / virtualisation NodeBB :
21
21
  * – pruneOrphans : PRUNE_STABLE_MS = 45 s, isFilled guard en premier.
22
22
  * – decluster : isFilled en premier, A_CREATED grace period (FILL_GRACE_MS).
23
- * Recyclage d'id (pool épuisé en infinite scroll) :
24
- * pickRecyclableWrap() : sélectionne le wrap vide le plus loin au-dessus
25
- * du viewport (seuil -6 × vh), jamais pour ezoic-ad-message.
26
- * moveWrapAfter() : déplace le wrap vers sa nouvelle ancre.
27
- * scrollDir tracking pour n'autoriser le recyclage qu'en scroll down.
28
- * Table KIND unifiée avec baseTag + ordinalAttr + recyclable flag.
29
- * ordinal() : utilise KIND[klass].ordinalAttr, fallback positionnel propre.
23
+ * Tentative recyclage d'id (v25) cause exactement le même bug (wraps
24
+ * déplacés laissent les positions originales libres réinjection en haut).
25
+ *
26
+ * v26 Suppression définitive du recyclage d'id.
27
+ * Pool épuisé = on attend que pruneOrphans libère des ids (> 45 s hors DOM).
28
+ * Suppression de scrollDir, pickRecyclableWrap, moveWrapAfter.
29
+ * KIND simplifié : retrait du flag recyclable inutile.
30
30
  */
31
31
  (function () {
32
32
  'use strict';
@@ -43,7 +43,6 @@
43
43
  const PRUNE_STABLE_MS = 45_000; // délai avant pruning (évite faux-orphelins scroll-up)
44
44
  const FILL_GRACE_MS = 25_000; // fenêtre fill async Ezoic (SSP auction)
45
45
  const EMPTY_CHECK_MS = 20_000; // délai collapse wrap vide post-show
46
- const RECYCLE_THRESHOLD = 6; // nb de viewports au-dessus du seuil de recyclage
47
46
  const MAX_INSERTS_RUN = 6;
48
47
  const MAX_INFLIGHT = 4;
49
48
  const SHOW_THROTTLE_MS = 900;
@@ -69,13 +68,11 @@
69
68
  * data-pid posts / data-index topics / data-cid catégories
70
69
  * ordinalAttr: attribut 0-based pour calcul de l'intervalle
71
70
  * null → fallback positionnel (catégories)
72
- * recyclable : autoriser le recyclage d'id quand le pool est épuisé
73
- * false pour ezoic-ad-message (sauts visuels indésirables)
74
71
  */
75
72
  const KIND = {
76
- 'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index', recyclable: false },
77
- 'ezoic-ad-between': { sel: SEL.topic, baseTag: 'li', anchorAttr: 'data-index', ordinalAttr: 'data-index', recyclable: true },
78
- 'ezoic-ad-categories': { sel: SEL.category, baseTag: 'li', anchorAttr: 'data-cid', ordinalAttr: null, recyclable: true },
73
+ 'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index' },
74
+ 'ezoic-ad-between': { sel: SEL.topic, baseTag: 'li', anchorAttr: 'data-index', ordinalAttr: 'data-index' },
75
+ 'ezoic-ad-categories': { sel: SEL.category, baseTag: 'li', anchorAttr: 'data-cid', ordinalAttr: null },
79
76
  };
80
77
 
81
78
  // ── État ───────────────────────────────────────────────────────────────────
@@ -98,8 +95,6 @@
98
95
  burstDeadline: 0,
99
96
  burstCount: 0,
100
97
  lastBurstTs: 0,
101
- scrollDir: 1, // 1 = down, -1 = up
102
- lastScrollY: 0,
103
98
  };
104
99
 
105
100
  let blockedUntil = 0;
@@ -223,49 +218,6 @@
223
218
  return null;
224
219
  }
225
220
 
226
- // ── Recyclage d'id ─────────────────────────────────────────────────────────
227
-
228
- /**
229
- * Sélectionne le wrap vide le plus éloigné au-dessus du viewport.
230
- * Conditions : kindClass.recyclable = true, scroll vers le bas,
231
- * wrap vide (non filled), rect.bottom < -(RECYCLE_THRESHOLD × vh).
232
- */
233
- function pickRecyclableWrap(klass) {
234
- if (!KIND[klass]?.recyclable) return null;
235
- if (S.scrollDir < 0) return null;
236
-
237
- const vh = Math.max(300, window.innerHeight || 800);
238
- const threshold = -(vh * RECYCLE_THRESHOLD);
239
- let best = null, bestBottom = Infinity;
240
-
241
- for (const w of document.querySelectorAll(`.${WRAP_CLASS}.${klass}`)) {
242
- if (!w.isConnected || isFilled(w)) continue;
243
- try {
244
- const rect = w.getBoundingClientRect();
245
- if (rect.bottom < threshold && rect.bottom < bestBottom) {
246
- bestBottom = rect.bottom;
247
- best = w;
248
- }
249
- } catch (_) {}
250
- }
251
- return best;
252
- }
253
-
254
- /**
255
- * Déplace un wrap recyclé vers sa nouvelle ancre el.
256
- * Réinitialise A_ANCHOR, A_CREATED, supprime A_SHOWN.
257
- */
258
- function moveWrapAfter(el, wrap, newKey) {
259
- try {
260
- if (!el || !wrap?.isConnected) return null;
261
- wrap.setAttribute(A_ANCHOR, newKey);
262
- wrap.setAttribute(A_CREATED, String(ts()));
263
- wrap.removeAttribute(A_SHOWN);
264
- mutate(() => el.insertAdjacentElement('afterend', wrap));
265
- return wrap;
266
- } catch (_) { return null; }
267
- }
268
-
269
221
  // ── Wraps DOM ──────────────────────────────────────────────────────────────
270
222
 
271
223
  function makeWrap(id, klass, key) {
@@ -401,23 +353,11 @@
401
353
  const key = anchorKey(klass, el);
402
354
  if (findWrap(key)) continue;
403
355
 
404
- // 1. Tentative pool normal
405
356
  const id = pickId(poolKey);
406
- if (id) {
407
- const w = insertAfter(el, id, klass, key);
408
- if (w) { observePh(id); inserted++; }
409
- continue;
410
- }
357
+ if (!id) continue; // pool épuisé : on attend que pruneOrphans libère des ids
411
358
 
412
- // 2. Pool épuisé tentative de recyclage
413
- const recyclable = pickRecyclableWrap(klass);
414
- if (recyclable) {
415
- const rid = parseInt(recyclable.getAttribute(A_WRAPID), 10);
416
- const w = moveWrapAfter(el, recyclable, key);
417
- if (w && Number.isFinite(rid)) { observePh(rid); inserted++; }
418
- }
419
- // Pool épuisé et pas de recyclage : on continue (items suivants peuvent
420
- // avoir un wrap existant via findWrap, on ne break pas)
359
+ const w = insertAfter(el, id, klass, key);
360
+ if (w) { observePh(id); inserted++; }
421
361
  }
422
362
  return inserted;
423
363
  }
@@ -743,13 +683,6 @@
743
683
  function bindScroll() {
744
684
  let ticking = false;
745
685
  window.addEventListener('scroll', () => {
746
- // Suivi direction du scroll (nécessaire pour le recyclage conditionnel)
747
- try {
748
- const y = window.scrollY || window.pageYOffset || 0;
749
- const d = y - S.lastScrollY;
750
- if (Math.abs(d) > 4) { S.scrollDir = d > 0 ? 1 : -1; S.lastScrollY = y; }
751
- } catch (_) {}
752
-
753
686
  if (ticking) return;
754
687
  ticking = true;
755
688
  requestAnimationFrame(() => { ticking = false; requestBurst(); });
@@ -759,7 +692,6 @@
759
692
  // ── Boot ───────────────────────────────────────────────────────────────────
760
693
 
761
694
  S.pageKey = pageKey();
762
- try { S.lastScrollY = window.scrollY || window.pageYOffset || 0; } catch (_) {}
763
695
  muteConsole();
764
696
  ensureTcfLocator();
765
697
  warmNetwork();