nodebb-plugin-ezoic-infinite 1.8.15 → 1.8.17

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/library.js CHANGED
@@ -21,34 +21,21 @@ function parseBool(v, def = false) {
21
21
  }
22
22
 
23
23
  async function getAllGroups() {
24
- const now = Date.now();
25
- if (_groupsCache && (now - _groupsCacheAt) < GROUPS_TTL) return _groupsCache;
26
-
27
24
  let names = await db.getSortedSetRange('groups:createtime', 0, -1);
28
25
  if (!names || !names.length) {
29
26
  names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
30
27
  }
31
28
  const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
32
29
  const data = await groups.getGroupsData(filtered);
33
- const valid = (data || []).filter(g => g && g.name);
30
+ // Filter out nulls (groups deleted between the sorted-set read and getGroupsData)
31
+ const valid = data.filter(g => g && g.name);
34
32
  valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
35
-
36
- _groupsCacheAt = now;
37
- _groupsCache = valid;
38
33
  return valid;
39
34
  }
40
-
41
35
  let _settingsCache = null;
42
36
  let _settingsCacheAt = 0;
43
37
  const SETTINGS_TTL = 30000; // 30s
44
38
 
45
- let _groupsCache = null;
46
- let _groupsCacheAt = 0;
47
- const GROUPS_TTL = 5 * 60 * 1000; // 5min
48
-
49
- const _excludedCache = new Map(); // uid -> { at, excluded }
50
- const EXCLUDED_TTL = 30 * 1000; // 30s
51
-
52
39
  async function getSettings() {
53
40
  const now = Date.now();
54
41
  if (_settingsCache && (now - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
@@ -80,27 +67,14 @@ async function getSettings() {
80
67
 
81
68
  async function isUserExcluded(uid, excludedGroups) {
82
69
  if (!uid || !excludedGroups.length) return false;
83
-
84
- const now = Date.now();
85
- const cached = _excludedCache.get(uid);
86
- if (cached && (now - cached.at) < EXCLUDED_TTL) return cached.excluded;
87
-
88
70
  const userGroups = await groups.getUserGroups([uid]);
89
- const excluded = (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
90
-
91
- _excludedCache.set(uid, { at: now, excluded });
92
- // petite hygiene : éviter une map qui grossit sans limite
93
- if (_excludedCache.size > 5000) _excludedCache.clear();
94
-
95
- return excluded;
71
+ return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
96
72
  }
97
73
 
98
-
99
74
  plugin.onSettingsSet = function (data) {
100
75
  // Invalider le cache dès que les settings de ce plugin sont sauvegardés via l'ACP
101
76
  if (data && data.hash === SETTINGS_KEY) {
102
77
  _settingsCache = null;
103
- _excludedCache.clear();
104
78
  }
105
79
  };
106
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.8.15",
3
+ "version": "1.8.17",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
@@ -18,4 +18,4 @@
18
18
  "compatibility": "^4.0.0"
19
19
  },
20
20
  "private": false
21
- }
21
+ }
package/public/client.js CHANGED
@@ -80,7 +80,6 @@
80
80
  const S = {
81
81
  pageKey: null,
82
82
  cfg: null,
83
- poolSig: null,
84
83
 
85
84
  pools: { topics: [], posts: [], categories: [] },
86
85
  cursors: { topics: 0, posts: 0, categories: 0 },
@@ -88,7 +87,6 @@
88
87
  lastShow: new Map(), // id → timestamp dernier show
89
88
 
90
89
  io: null,
91
- ioMargin: null,
92
90
  domObs: null,
93
91
  mutGuard: 0, // compteur internalMutation
94
92
 
@@ -124,31 +122,14 @@
124
122
  }
125
123
 
126
124
  function initPools(cfg) {
127
- // (Perf) Ne reparse les pools que si les strings ont changé.
128
- const sig = `${cfg.placeholderIds || ''}§${cfg.messagePlaceholderIds || ''}§${cfg.categoryPlaceholderIds || ''}`;
129
- if (S.poolSig === sig) return;
130
- S.poolSig = sig;
131
-
132
125
  S.pools.topics = parseIds(cfg.placeholderIds);
133
126
  S.pools.posts = parseIds(cfg.messagePlaceholderIds);
134
127
  S.pools.categories = parseIds(cfg.categoryPlaceholderIds);
135
-
136
- // Réinitialise les curseurs si une pool change
137
- S.cursors.topics = 0;
138
- S.cursors.posts = 0;
139
- S.cursors.categories = 0;
140
128
  }
141
129
 
142
130
  function parseIds(raw) {
143
- // Accepte : un ID par ligne, ou séparés par virgules/espaces (ACP le mentionne).
144
131
  const out = [], seen = new Set();
145
- const parts = String(raw || '')
146
- .replace(/\r/g, '\n')
147
- .split(/[\n\s,]+/)
148
- .map(s => s.trim())
149
- .filter(Boolean);
150
-
151
- for (const v of parts) {
132
+ for (const v of String(raw || '').split(/\r?\n/).map(s => s.trim()).filter(Boolean)) {
152
133
  const n = parseInt(v, 10);
153
134
  if (n > 0 && Number.isFinite(n) && !seen.has(n)) { seen.add(n); out.push(n); }
154
135
  }
@@ -312,26 +293,23 @@
312
293
  const meta = KIND[klass];
313
294
  if (!meta) return;
314
295
 
315
- // (Perf) Construire 1 set d'ancres présentes, au lieu de querySelector par wrap.
316
- const present = new Set();
317
- try {
318
- document.querySelectorAll(meta.sel).forEach(el => {
319
- const v = el.getAttribute(meta.anchorAttr);
320
- if (v !== null && v !== '') present.add(String(v));
321
- });
322
- } catch (_) {}
296
+ const baseTag = meta.sel.split('[')[0]; // ex: "li", "[component=..." "" (géré)
323
297
 
324
298
  document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(w => {
325
299
  if (ts() - parseInt(w.getAttribute(A_CREATED) || '0', 10) < MIN_PRUNE_AGE_MS) return;
326
300
 
327
301
  const key = w.getAttribute(A_ANCHOR) ?? '';
328
- const sid = key.slice(klass.length + 1); // après "kindClass:"
302
+ const sid = key.slice(klass.length + 1); // extrait la partie après "kindClass:"
329
303
  if (!sid) { mutate(() => dropWrap(w)); return; }
330
304
 
331
- if (!present.has(String(sid))) mutate(() => dropWrap(w));
305
+ const anchorEl = document.querySelector(
306
+ `${baseTag}[${meta.anchorAttr}="${sid.replace(/"/g, '\\"')}"]`
307
+ );
308
+ if (!anchorEl || !anchorEl.isConnected) mutate(() => dropWrap(w));
332
309
  });
333
310
  }
334
- // ── Decluster ──────────────────────────────────────────────────────────────
311
+
312
+ // ── Decluster ──────────────────────────────────────────────────────────────
335
313
 
336
314
  /**
337
315
  * Deux wraps adjacents = situation anormale → supprimer le moins prioritaire.
@@ -411,14 +389,8 @@
411
389
  // ── IntersectionObserver & Show ────────────────────────────────────────────
412
390
 
413
391
  function getIO() {
392
+ if (S.io) return S.io;
414
393
  const margin = isMobile() ? IO_MARGIN_MOBILE : IO_MARGIN_DESKTOP;
415
- if (S.io && S.ioMargin === margin) return S.io;
416
- // Si la marge doit changer (resize/orientation), on recrée l'observer.
417
- if (S.io && S.ioMargin !== margin) {
418
- try { S.io.disconnect(); } catch (_) {}
419
- S.io = null;
420
- }
421
- S.ioMargin = margin;
422
394
  try {
423
395
  S.io = new IntersectionObserver(entries => {
424
396
  for (const e of entries) {
@@ -622,7 +594,6 @@
622
594
  blockedUntil = ts() + 1500;
623
595
  mutate(() => document.querySelectorAll(`.${WRAP_CLASS}`).forEach(dropWrap));
624
596
  S.cfg = null;
625
- S.poolSig = null;
626
597
  S.pools = { topics: [], posts: [], categories: [] };
627
598
  S.cursors = { topics: 0, posts: 0, categories: 0 };
628
599
  S.mountedIds.clear();
@@ -756,22 +727,6 @@
756
727
  } catch (_) {}
757
728
  }
758
729
 
759
-
760
- function bindResize() {
761
- let t = null;
762
- window.addEventListener('resize', () => {
763
- clearTimeout(t);
764
- t = setTimeout(() => {
765
- try { getIO(); } catch (_) {}
766
- // Ré-observer les placeholders existants (si IO recréé)
767
- try {
768
- document.querySelectorAll(`.${WRAP_CLASS} [id^="${PH_PREFIX}"]`).forEach(ph => {
769
- if (ph instanceof Element) { try { S.io?.observe(ph); } catch (_) {} }
770
- });
771
- } catch (_) {}
772
- }, 200);
773
- }, { passive: true });
774
- }
775
730
  function bindScroll() {
776
731
  let ticking = false;
777
732
  window.addEventListener('scroll', () => {
@@ -791,7 +746,6 @@
791
746
  getIO();
792
747
  ensureDomObserver();
793
748
  bindNodeBB();
794
- bindResize();
795
749
  bindScroll();
796
750
  blockedUntil = 0;
797
751
  requestBurst();