nodebb-plugin-ezoic-infinite 1.5.59 → 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 +126 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.59",
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);
@@ -84,9 +112,8 @@ function acquirePlaceholder(id) {
84
112
  ph.id = domId;
85
113
  ph.setAttribute('data-ezoic-id', String(id));
86
114
  ensurePool().appendChild(ph);
87
- }
88
- // Detach from wherever it currently is (pool or a previous wrap)
89
- try { if (ph.parentNode) ph.parentNode.removeChild(ph); } catch (e) {}
115
+ }
116
+ // Note: appendChild will automatically move the node, no manual detach (avoids race gaps).
90
117
  // Clear request/defined flags when reusing
91
118
  try {
92
119
  if (ph.dataset) {
@@ -470,28 +497,65 @@ function withInternalDomChange(fn) {
470
497
  }
471
498
 
472
499
  function initPools(cfg) {
473
- if (!cfg) return;
474
- if (state.allTopics.length === 0) state.allTopics = parsePool(cfg.placeholderIds);
475
- if (state.allPosts.length === 0) state.allPosts = parsePool(cfg.messagePlaceholderIds);
476
- if (state.allCategories.length === 0) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
477
-
478
- // Keep placeholders alive even before they're injected.
479
- // This avoids Ezoic trying to touch ids that we haven't inserted yet.
480
- primePool(state.allTopics);
481
- primePool(state.allPosts);
482
- primePool(state.allCategories);
483
- }
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
+
484
532
 
485
533
  // ---------- insertion primitives ----------
486
534
 
487
535
  function isAdjacentAd(target) {
488
- if (!target) return false;
489
- const next = target.nextElementSibling;
490
- if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
491
- const prev = target.previousElementSibling;
492
- if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
493
- 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;
494
555
  }
556
+ return false;
557
+ }
558
+
495
559
 
496
560
 
497
561
  function getWrapIdFromWrap(wrap) {
@@ -571,41 +635,54 @@ function withInternalDomChange(fn) {
571
635
  }, 3500);
572
636
  }
573
637
 
574
- function buildWrap(id, kindClass, afterPos) {
575
- const wrap = document.createElement('div');
576
- wrap.className = `${WRAP_CLASS} ${kindClass}`;
577
- wrap.setAttribute('data-ezoic-after', String(afterPos));
578
- wrap.setAttribute('data-ezoic-wrapid', String(id));
579
- 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%';
580
645
 
581
- const ph = acquirePlaceholder(id);
582
- try { if (ph.dataset) ph.dataset.ezActive = '1'; } catch (e) {}
583
- wrap.appendChild(ph);
646
+ const ph = acquirePlaceholder(id);
647
+ try { if (ph.dataset) ph.dataset.ezActive = '1'; } catch (e) {}
648
+ wrap.appendChild(ph);
584
649
 
585
- return wrap;
586
- }
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
+ }
587
663
 
588
- function findWrap(kindClass, afterPos) {
589
- return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
590
- }
591
664
 
592
665
  function insertAfter(target, id, kindClass, afterPos) {
593
- if (!target || !target.insertAdjacentElement) return null;
594
- if (findWrap(kindClass, afterPos)) return null;
595
- 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;
596
670
 
597
- const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
598
- if (existingPh && isPlaceholderInUse(existingPh)) return null;
671
+ const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
672
+ if (existingPh && isPlaceholderInUse(existingPh)) return null;
599
673
 
600
- insertingIds.add(id);
601
- try {
602
- const wrap = buildWrap(id, kindClass, afterPos);
603
- target.insertAdjacentElement('afterend', wrap);
604
- return wrap;
605
- } finally {
606
- insertingIds.delete(id);
607
- }
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);
608
683
  }
684
+ }
685
+
609
686
 
610
687
  function pickIdFromAll(allIds, cursorKey) {
611
688
  const n = allIds.length;