nodebb-plugin-ezoic-infinite 1.8.99 → 1.9.1

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
@@ -243,6 +243,8 @@ plugin.injectEzoicHead = async (data) => {
243
243
  };
244
244
 
245
245
  plugin.init = async ({ router, middleware }) => {
246
+ getSettings().catch(() => {});
247
+
246
248
  async function render(req, res) {
247
249
  const settings = await getSettings();
248
250
  const allGroups = await getAllGroups();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.8.99",
3
+ "version": "1.9.1",
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
@@ -58,6 +58,7 @@
58
58
  };
59
59
 
60
60
  const FILL_SEL = 'iframe, ins, img, video, [data-google-container-id], div[id$="__container__"]';
61
+ const SUPPORTS_HAS = typeof CSS !== 'undefined' && !!CSS.supports?.('selector(:has(*))');
61
62
 
62
63
  // ── Utility ────────────────────────────────────────────────────────────────
63
64
 
@@ -101,16 +102,15 @@
101
102
  burstDeadline: 0,
102
103
  burstCount: 0,
103
104
  lastBurstTs: 0,
105
+ cleaningUp: false,
104
106
  };
105
107
 
106
108
  const isBlocked = () => now() < S.blockedUntil;
107
109
 
108
110
  // ── DOM caches (burst-scoped) ──────────────────────────────────────────────
109
111
 
110
- let _postsCache = null, _postsCacheTs = 0;
111
- let _topicsCache = null, _topicsCacheTs = 0;
112
- let _catsCache = null, _catsCacheTs = 0;
113
112
  const POSTS_CACHE_MS = 200;
113
+ const _dc = { posts: null, postsAt: 0, topics: null, topicsAt: 0, cats: null, catsAt: 0 };
114
114
 
115
115
  function mutate(fn) {
116
116
  S.mutGuard++;
@@ -121,10 +121,8 @@
121
121
 
122
122
  async function fetchConfig() {
123
123
  if (S.cfg) return S.cfg;
124
- try {
125
- const inline = window.__nbbEzoicCfg;
126
- if (inline && typeof inline === 'object') { S.cfg = inline; return S.cfg; }
127
- } catch (_) {}
124
+ const inline = window.__nbbEzoicCfg;
125
+ if (inline && typeof inline === 'object') { S.cfg = inline; return S.cfg; }
128
126
  try {
129
127
  const ctrl = new AbortController();
130
128
  const t = setTimeout(() => ctrl.abort(), 5_000);
@@ -184,37 +182,41 @@
184
182
 
185
183
  function getPosts() {
186
184
  const t = now();
187
- if (_postsCache && t - _postsCacheTs < POSTS_CACHE_MS) return _postsCache;
188
- const all = document.querySelectorAll(SEL.post);
185
+ if (_dc.posts && t - _dc.postsAt < POSTS_CACHE_MS) return _dc.posts;
186
+ const all = SUPPORTS_HAS
187
+ ? document.querySelectorAll('[component="post"][data-pid]:has([component="post/content"])')
188
+ : document.querySelectorAll(SEL.post);
189
189
  const out = [];
190
190
  for (let i = 0; i < all.length; i++) {
191
191
  const el = all[i];
192
192
  if (!el.isConnected) continue;
193
- if (el.childElementCount === 0) continue;
194
- if (!el.querySelector('[component="post/content"]')) continue;
193
+ if (!SUPPORTS_HAS) {
194
+ if (el.childElementCount === 0) continue;
195
+ if (!el.querySelector('[component="post/content"]')) continue;
196
+ }
195
197
  const parent = el.parentElement?.closest(SEL.post);
196
198
  if (parent && parent !== el) continue;
197
199
  if (el.getAttribute('component') === 'post/parent') continue;
198
200
  out.push(el);
199
201
  }
200
- _postsCache = out;
201
- _postsCacheTs = t;
202
+ _dc.posts = out;
203
+ _dc.postsAt = t;
202
204
  return out;
203
205
  }
204
206
 
205
207
  function getTopics() {
206
208
  const t = now();
207
- if (_topicsCache && t - _topicsCacheTs < POSTS_CACHE_MS) return _topicsCache;
208
- _topicsCache = document.querySelectorAll(SEL.topic);
209
- _topicsCacheTs = t;
210
- return _topicsCache;
209
+ if (_dc.topics && t - _dc.topicsAt < POSTS_CACHE_MS) return _dc.topics;
210
+ _dc.topics = Array.from(document.querySelectorAll(SEL.topic));
211
+ _dc.topicsAt = t;
212
+ return _dc.topics;
211
213
  }
212
214
  function getCategories() {
213
215
  const t = now();
214
- if (_catsCache && t - _catsCacheTs < POSTS_CACHE_MS) return _catsCache;
215
- _catsCache = document.querySelectorAll(SEL.category);
216
- _catsCacheTs = t;
217
- return _catsCache;
216
+ if (_dc.cats && t - _dc.catsAt < POSTS_CACHE_MS) return _dc.cats;
217
+ _dc.cats = Array.from(document.querySelectorAll(SEL.category));
218
+ _dc.catsAt = t;
219
+ return _dc.cats;
218
220
  }
219
221
 
220
222
  // ── Anchor keys & wrap registry ────────────────────────────────────────────
@@ -271,22 +273,7 @@
271
273
  function wrapIsLive(wrap) {
272
274
  if (!wrap?.classList?.contains(WRAP_CLASS)) return false;
273
275
  const key = wrap.getAttribute(ATTR.ANCHOR);
274
- if (!key) return false;
275
- if (S.wrapByKey.get(key) === wrap) return wrap.isConnected;
276
- const colonIdx = key.indexOf(':');
277
- const klass = key.slice(0, colonIdx);
278
- const anchorId = key.slice(colonIdx + 1);
279
- const cfg = KIND[klass];
280
- if (!cfg) return false;
281
- const parent = wrap.parentElement;
282
- if (!parent) return false;
283
- const sel = `${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`;
284
- for (const sib of parent.children) {
285
- if (sib !== wrap) {
286
- try { if (sib.matches(sel)) return sib.isConnected; } catch (_) {}
287
- }
288
- }
289
- return false;
276
+ return !!key && S.wrapByKey.get(key) === wrap && wrap.isConnected;
290
277
  }
291
278
 
292
279
  const adjacentWrap = el => wrapIsLive(el.nextElementSibling) || wrapIsLive(el.previousElementSibling);
@@ -435,15 +422,16 @@
435
422
  if (ph instanceof Element) S.io?.unobserve(ph);
436
423
  const id = parseInt(w.getAttribute(ATTR.WRAPID), 10);
437
424
  if (Number.isFinite(id)) {
438
- S.mountedIds.delete(id);
439
- S.lastShow.delete(id);
440
425
  const timers = S.wrapTimers.get(id);
441
426
  if (timers) { for (const t of timers) clearTimeout(t); S.wrapTimers.delete(id); }
442
427
  }
443
- const key = w.getAttribute(ATTR.ANCHOR);
444
- if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
445
- const colonIdx = key?.indexOf(':') ?? -1;
446
- if (colonIdx > 0) S.wrapsByClass.get(key.slice(0, colonIdx))?.delete(w);
428
+ if (!S.cleaningUp) {
429
+ if (Number.isFinite(id)) { S.mountedIds.delete(id); S.lastShow.delete(id); }
430
+ const key = w.getAttribute(ATTR.ANCHOR);
431
+ if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
432
+ const colonIdx = key?.indexOf(':') ?? -1;
433
+ if (colonIdx > 0) S.wrapsByClass.get(key.slice(0, colonIdx))?.delete(w);
434
+ }
447
435
  w.remove();
448
436
  } catch (_) {}
449
437
  }
@@ -722,16 +710,19 @@
722
710
  const ez = window.ezstandalone;
723
711
  if (typeof ez?.destroyPlaceholders === 'function') ez.destroyPlaceholders();
724
712
  } catch (_) {}
713
+ S.cleaningUp = true;
725
714
  mutate(() => {
726
715
  for (const w of document.querySelectorAll(`.${WRAP_CLASS}`)) dropWrap(w);
727
716
  });
717
+ S.cleaningUp = false;
718
+ // Safety net: wrapTimers should be empty after dropWrap loop, but clear any orphans.
728
719
  for (const timers of S.wrapTimers.values()) { for (const t of timers) clearTimeout(t); }
729
720
  S.wrapTimers.clear();
730
721
  S.domObs?.disconnect(); S.domObs = null;
731
722
  S.io?.disconnect(); S.io = null; _ioMobile = null;
732
- _postsCache = null; _postsCacheTs = 0;
733
- _topicsCache = null; _topicsCacheTs = 0;
734
- _catsCache = null; _catsCacheTs = 0;
723
+ _dc.posts = null; _dc.postsAt = 0;
724
+ _dc.topics = null; _dc.topicsAt = 0;
725
+ _dc.cats = null; _dc.catsAt = 0;
735
726
  // cfg/opts/pools are static for the session (window.__nbbEzoicCfg is set once
736
727
  // by the server and never changes between SPA navigations) — preserve them.
737
728
  S.cursors = { topics: 0, posts: 0, categories: 0 };
@@ -796,7 +787,7 @@
796
787
  }
797
788
  }
798
789
  }
799
- if (needsBurst) { _postsCacheTs = 0; _topicsCacheTs = 0; _catsCacheTs = 0; requestBurst(); }
790
+ if (needsBurst) { _dc.postsAt = 0; _dc.topicsAt = 0; _dc.catsAt = 0; requestBurst(); }
800
791
  });
801
792
  try {
802
793
  const target = document.getElementById('content') || document.body;