nodebb-plugin-ezoic-infinite 1.4.2 → 1.4.4

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 +104 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
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
@@ -35,6 +35,11 @@
35
35
 
36
36
  lastShowById: new Map(),
37
37
  pendingById: new Set(),
38
+ retryById: new Map(),
39
+ retryTimer: null,
40
+ retryQueue: [],
41
+ retryQueueSet: new Set(),
42
+ retryQueueRunning: false,
38
43
 
39
44
  scheduled: false,
40
45
  timer: null,
@@ -234,6 +239,77 @@
234
239
  } catch (e) {}
235
240
  }
236
241
 
242
+
243
+ function isPlaceholderFilled(id) {
244
+ const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
245
+ if (!ph || !ph.isConnected) return false;
246
+ if (ph.childNodes && ph.childNodes.length > 0) return true;
247
+ const wrap = ph.parentElement;
248
+ if (wrap && wrap.querySelector && wrap.querySelector('iframe, ins, [id^="ezslot_"], [class*="ez"]')) return true;
249
+ return false;
250
+ }
251
+
252
+ function scheduleRefill(delay = 350) {
253
+ clearTimeout(state.retryTimer);
254
+ state.retryTimer = setTimeout(refillUnfilled, delay);
255
+ }
256
+
257
+ function enqueueRetry(id) {
258
+ if (!id) return;
259
+ if (state.retryQueueSet.has(id)) return;
260
+ state.retryQueueSet.add(id);
261
+ state.retryQueue.push(id);
262
+ processRetryQueue();
263
+ }
264
+
265
+ function processRetryQueue() {
266
+ if (state.retryQueueRunning) return;
267
+ state.retryQueueRunning = true;
268
+
269
+ const step = () => {
270
+ const id = state.retryQueue.shift();
271
+ if (!id) {
272
+ state.retryQueueRunning = false;
273
+ return;
274
+ }
275
+ state.retryQueueSet.delete(id);
276
+ callShowAdsWhenReady(id);
277
+ setTimeout(step, 950);
278
+ };
279
+
280
+ step();
281
+ }
282
+
283
+
284
+ function refillUnfilled() {
285
+ const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}`));
286
+ let scheduledAny = false;
287
+
288
+ for (const wrap of wraps) {
289
+ const ph = wrap.querySelector && wrap.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
290
+ if (!ph) continue;
291
+ const id = parseInt(ph.id.replace(PLACEHOLDER_PREFIX, ''), 10);
292
+ if (!Number.isFinite(id) || id <= 0) continue;
293
+
294
+ if (isPlaceholderFilled(id)) {
295
+ state.retryById.delete(id);
296
+ continue;
297
+ }
298
+
299
+ const tries = (state.retryById.get(id) || 0);
300
+ if (tries >= 8) continue;
301
+
302
+ const r = safeRect(wrap);
303
+ if (r && (r.top > window.innerHeight + 1200 || r.bottom < -1200)) continue;
304
+
305
+ state.retryById.set(id, tries + 1);
306
+ enqueueRetry(id);
307
+ scheduledAny = true;
308
+ }
309
+
310
+ if (scheduledAny) scheduleRefill(700);
311
+ }
312
+
237
313
  function callShowAdsWhenReady(id) {
238
314
  if (!id) return;
239
315
 
@@ -346,6 +422,12 @@
346
422
  const el = items[afterPos - 1];
347
423
  if (!el || !el.isConnected) continue;
348
424
 
425
+ // Prevent adjacent ads (DOM-based, robust against virtualization)
426
+ const nextSibling = el.nextElementSibling;
427
+ if (nextSibling && nextSibling.classList && nextSibling.classList.contains(WRAP_CLASS)) {
428
+ continue;
429
+ }
430
+
349
431
  // Prevent back-to-back at load
350
432
  const prevWrap = findWrap(kindClass, afterPos - 1);
351
433
  if (prevWrap) continue;
@@ -365,6 +447,7 @@
365
447
  destroyPlaceholderIds([id]);
366
448
  wrap = pick.recycled.wrap;
367
449
  if (!moveWrapAfter(wrap, el, kindClass, afterPos)) continue;
450
+ setTimeout(() => { enqueueRetry(id); }, 250);
368
451
  } else {
369
452
  usedSet.add(id);
370
453
  wrap = insertAfter(el, id, kindClass, afterPos);
@@ -372,7 +455,9 @@
372
455
  }
373
456
 
374
457
  liveArr.push({ id, wrap });
375
- callShowAdsWhenReady(id);
458
+ if (!(pick.recycled && pick.recycled.wrap)) {
459
+ callShowAdsWhenReady(id);
460
+ }
376
461
  inserted += 1;
377
462
  }
378
463
  return inserted;
@@ -465,6 +550,7 @@
465
550
  }
466
551
 
467
552
  enforceNoAdjacentAds();
553
+ scheduleRefill(250);
468
554
 
469
555
  // If nothing inserted and list isn't in DOM yet (first click), retry a bit
470
556
  let count = 0;
@@ -540,4 +626,20 @@
540
626
  state.pageKey = getPageKey();
541
627
  scheduleRun();
542
628
  setTimeout(scheduleRun, 250);
543
- })();
629
+ })()
630
+ function bindScroll() {
631
+ if (state.__scrollBound) return;
632
+ state.__scrollBound = true;
633
+ let ticking = false;
634
+ window.addEventListener('scroll', () => {
635
+ if (ticking) return;
636
+ ticking = true;
637
+ window.requestAnimationFrame(() => {
638
+ ticking = false;
639
+ enforceNoAdjacentAds();
640
+ scheduleRefill(200);
641
+ });
642
+ }, { passive: true });
643
+ }
644
+
645
+ ;