nodebb-plugin-ezoic-infinite 1.5.60 → 1.5.61

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 +124 -46
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.60",
3
+ "version": "1.5.61",
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
@@ -27,7 +27,7 @@ function ensurePool() {
27
27
  if (pool) {
28
28
  // In rare cases (aggressive SPA navigation), the pool may get detached.
29
29
  if (!pool.isConnected) {
30
- try { (document.documentElement || document.body).appendChild(pool); } catch (e) {}
30
+ try { document.body.appendChild(pool); } catch (e) {}
31
31
  }
32
32
  return pool;
33
33
  }
@@ -41,7 +41,7 @@ function ensurePool() {
41
41
  pool.style.overflow = 'hidden';
42
42
  pool.setAttribute('aria-hidden', 'true');
43
43
  // Attach early (documentElement exists before body), so placeholders are always connected.
44
- try { (document.documentElement || document.body).appendChild(pool); } catch (e) {}
44
+ try { document.body.appendChild(pool); } catch (e) {}
45
45
  // If body exists later, move it under body (purely cosmetic).
46
46
  try {
47
47
  if (document.body && pool.parentNode !== document.body) {
@@ -76,6 +76,34 @@ function primePool(ids) {
76
76
  } catch (e) {}
77
77
  }
78
78
 
79
+
80
+ // Prime a continuous id range (inclusive) into the offscreen pool.
81
+ // Ezoic sometimes references ids outside our configured list (e.g. +N); a small buffer prevents "does not exist" spam.
82
+ function primePoolRange(minId, maxId) {
83
+ try {
84
+ minId = parseInt(minId, 10);
85
+ maxId = parseInt(maxId, 10);
86
+ if (!Number.isFinite(minId) || !Number.isFinite(maxId)) return;
87
+ if (minId <= 0 || maxId <= 0) return;
88
+ if (maxId < minId) { const t = minId; minId = maxId; maxId = t; }
89
+ const span = Math.min(400, Math.max(0, maxId - minId)); // cap
90
+ const pool = ensurePool();
91
+ for (let id = minId; id <= minId + span; id += 1) {
92
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
93
+ let ph = document.getElementById(domId);
94
+ if (!ph) {
95
+ ph = document.createElement('div');
96
+ ph.id = domId;
97
+ ph.setAttribute('data-ezoic-id', String(id));
98
+ pool.appendChild(ph);
99
+ } else if (!ph.isConnected) {
100
+ pool.appendChild(ph);
101
+ }
102
+ }
103
+ } catch (e) {}
104
+ }
105
+
106
+
79
107
  function acquirePlaceholder(id) {
80
108
  const domId = `${PLACEHOLDER_PREFIX}${id}`;
81
109
  let ph = document.getElementById(domId);
@@ -469,28 +497,65 @@ function withInternalDomChange(fn) {
469
497
  }
470
498
 
471
499
  function initPools(cfg) {
472
- if (!cfg) return;
473
- if (state.allTopics.length === 0) state.allTopics = parsePool(cfg.placeholderIds);
474
- if (state.allPosts.length === 0) state.allPosts = parsePool(cfg.messagePlaceholderIds);
475
- if (state.allCategories.length === 0) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
476
-
477
- // Keep placeholders alive even before they're injected.
478
- // This avoids Ezoic trying to touch ids that we haven't inserted yet.
479
- primePool(state.allTopics);
480
- primePool(state.allPosts);
481
- primePool(state.allCategories);
482
- }
500
+ if (!cfg) return;
501
+ if (state.allTopics.length === 0) state.allTopics = parsePool(cfg.placeholderIds);
502
+ if (state.allPosts.length === 0) state.allPosts = parsePool(cfg.messagePlaceholderIds);
503
+ if (state.allCategories.length === 0) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
504
+
505
+ // Keep placeholders alive even before they're injected.
506
+ // Ezoic can reference ids slightly outside our configured lists (often sequential),
507
+ // so we prime a small buffered range to prevent "does not exist" spam.
508
+ const primeBuffered = (ids) => {
509
+ try {
510
+ if (!ids || !ids.length) return;
511
+ primePool(ids);
512
+
513
+ let min = Infinity, max = -Infinity;
514
+ for (const v of ids) {
515
+ const n = parseInt(v, 10);
516
+ if (!Number.isFinite(n) || n <= 0) continue;
517
+ if (n < min) min = n;
518
+ if (n > max) max = n;
519
+ }
520
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return;
521
+
522
+ // small buffer in both directions; range prime is capped internally
523
+ primePoolRange(Math.max(1, min - 5), max + 120);
524
+ } catch (e) {}
525
+ };
526
+
527
+ primeBuffered(state.allTopics);
528
+ primeBuffered(state.allPosts);
529
+ primeBuffered(state.allCategories);
530
+ }
531
+
483
532
 
484
533
  // ---------- insertion primitives ----------
485
534
 
486
535
  function isAdjacentAd(target) {
487
- if (!target) return false;
488
- const next = target.nextElementSibling;
489
- if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
490
- const prev = target.previousElementSibling;
491
- if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
492
- return false;
536
+ if (!target) return false;
537
+ const isAdEl = (el) => !!(el && el.classList && el.classList.contains(WRAP_CLASS));
538
+ // Look a few siblings away, skipping spacer elements inserted by NodeBB/themes
539
+ let el = target;
540
+ for (let i = 0; i < 3; i += 1) {
541
+ el = el.nextElementSibling;
542
+ if (!el) break;
543
+ if (isAdEl(el)) return true;
544
+ // Skip elements that are effectively spacers/dividers
545
+ const h = el.getBoundingClientRect ? el.getBoundingClientRect().height : 0;
546
+ if (h > 8) break;
547
+ }
548
+ el = target;
549
+ for (let i = 0; i < 3; i += 1) {
550
+ el = el.previousElementSibling;
551
+ if (!el) break;
552
+ if (isAdEl(el)) return true;
553
+ const h = el.getBoundingClientRect ? el.getBoundingClientRect().height : 0;
554
+ if (h > 8) break;
493
555
  }
556
+ return false;
557
+ }
558
+
494
559
 
495
560
 
496
561
  function getWrapIdFromWrap(wrap) {
@@ -570,41 +635,54 @@ function withInternalDomChange(fn) {
570
635
  }, 3500);
571
636
  }
572
637
 
573
- function buildWrap(id, kindClass, afterPos) {
574
- const wrap = document.createElement('div');
575
- wrap.className = `${WRAP_CLASS} ${kindClass}`;
576
- wrap.setAttribute('data-ezoic-after', String(afterPos));
577
- wrap.setAttribute('data-ezoic-wrapid', String(id));
578
- wrap.style.width = '100%';
638
+ function buildWrap(id, kindClass, afterPos, afterPid) {
639
+ const wrap = document.createElement('div');
640
+ wrap.className = `${WRAP_CLASS} ${kindClass}`;
641
+ wrap.setAttribute('data-ezoic-after', String(afterPos));
642
+ if (afterPid) wrap.setAttribute('data-ezoic-after-pid', String(afterPid));
643
+ wrap.setAttribute('data-ezoic-wrapid', String(id));
644
+ wrap.style.width = '100%';
579
645
 
580
- const ph = acquirePlaceholder(id);
581
- try { if (ph.dataset) ph.dataset.ezActive = '1'; } catch (e) {}
582
- wrap.appendChild(ph);
646
+ const ph = acquirePlaceholder(id);
647
+ try { if (ph.dataset) ph.dataset.ezActive = '1'; } catch (e) {}
648
+ wrap.appendChild(ph);
583
649
 
584
- return wrap;
585
- }
650
+ return wrap;
651
+ }
652
+
653
+
654
+ function findWrap(kindClass, afterPos, afterPid) {
655
+ try {
656
+ if (afterPid) {
657
+ const w = document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after-pid="${afterPid}"]`);
658
+ if (w) return w;
659
+ }
660
+ } catch (e) {}
661
+ return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
662
+ }
586
663
 
587
- function findWrap(kindClass, afterPos) {
588
- return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
589
- }
590
664
 
591
665
  function insertAfter(target, id, kindClass, afterPos) {
592
- if (!target || !target.insertAdjacentElement) return null;
593
- if (findWrap(kindClass, afterPos)) return null;
594
- if (insertingIds.has(id)) return null;
666
+ if (!target || !target.insertAdjacentElement) return null;
667
+ const afterPid = target.getAttribute ? target.getAttribute('data-pid') : null;
668
+ if (findWrap(kindClass, afterPos, afterPid)) return null;
669
+ if (insertingIds.has(id)) return null;
595
670
 
596
- const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
597
- if (existingPh && isPlaceholderInUse(existingPh)) return null;
671
+ const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
672
+ if (existingPh && isPlaceholderInUse(existingPh)) return null;
598
673
 
599
- insertingIds.add(id);
600
- try {
601
- const wrap = buildWrap(id, kindClass, afterPos);
602
- target.insertAdjacentElement('afterend', wrap);
603
- return wrap;
604
- } finally {
605
- insertingIds.delete(id);
606
- }
674
+ insertingIds.add(id);
675
+ try {
676
+ const wrap = buildWrap(id, kindClass, afterPos, afterPid);
677
+ target.insertAdjacentElement('afterend', wrap);
678
+ // Mark post as processed anchor to stabilize 1/X across infinite scroll reflows
679
+ try { if (afterPid) target.setAttribute('data-ezoic-anchored', '1'); } catch (e) {}
680
+ return wrap;
681
+ } finally {
682
+ insertingIds.delete(id);
607
683
  }
684
+ }
685
+
608
686
 
609
687
  function pickIdFromAll(allIds, cursorKey) {
610
688
  const n = allIds.length;