nodebb-plugin-ezoic-infinite 1.5.32 → 1.5.33

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 +68 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.32",
3
+ "version": "1.5.33",
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
@@ -14,19 +14,32 @@
14
14
  const PRELOAD_MARGIN_DESKTOP = '2600px 0px 2600px 0px';
15
15
  const PRELOAD_MARGIN_MOBILE = '1500px 0px 1500px 0px';
16
16
 
17
+ // When the user scrolls very fast, temporarily preload more aggressively.
18
+ // This helps ensure ads are already in-flight before the user reaches them.
19
+ const PRELOAD_MARGIN_DESKTOP_BOOST = '4200px 0px 4200px 0px';
20
+ const PRELOAD_MARGIN_MOBILE_BOOST = '2500px 0px 2500px 0px';
21
+ const BOOST_DURATION_MS = 2500;
22
+ const BOOST_SPEED_PX_PER_MS = 2.2; // ~2200px/s
23
+
17
24
  const MAX_INFLIGHT_DESKTOP = 4;
18
25
  const MAX_INFLIGHT_MOBILE = 3;
19
26
 
27
+ function isBoosted() {
28
+ try { return Date.now() < (state.scrollBoostUntil || 0); } catch (e) { return false; }
29
+ }
30
+
20
31
  function isMobile() {
21
32
  try { return window && window.innerWidth && window.innerWidth < 768; } catch (e) { return false; }
22
33
  }
23
34
 
24
35
  function getPreloadRootMargin() {
25
- return isMobile() ? PRELOAD_MARGIN_MOBILE : PRELOAD_MARGIN_DESKTOP;
36
+ if (isMobile()) return isBoosted() ? PRELOAD_MARGIN_MOBILE_BOOST : PRELOAD_MARGIN_MOBILE;
37
+ return isBoosted() ? PRELOAD_MARGIN_DESKTOP_BOOST : PRELOAD_MARGIN_DESKTOP;
26
38
  }
27
39
 
28
40
  function getMaxInflight() {
29
- return isMobile() ? MAX_INFLIGHT_MOBILE : MAX_INFLIGHT_DESKTOP;
41
+ const base = isMobile() ? MAX_INFLIGHT_MOBILE : MAX_INFLIGHT_DESKTOP;
42
+ return base + (isBoosted() ? 1 : 0);
30
43
  }
31
44
 
32
45
  const SELECTORS = {
@@ -71,13 +84,18 @@
71
84
  pending: [],
72
85
  pendingSet: new Set(),
73
86
 
87
+ // fast scroll boosting
88
+ scrollBoostUntil: 0,
89
+ lastScrollY: 0,
90
+ lastScrollTs: 0,
91
+ ioMargin: null,
92
+
74
93
  // hero)
75
94
  heroDoneForPage: false,
76
95
  };
77
96
 
78
97
  const insertingIds = new Set();
79
98
 
80
- }
81
99
 
82
100
  function markEmptyWrapper(id) {
83
101
  try {
@@ -532,7 +550,14 @@ function startShow(id) {
532
550
  // ---------- preload / above-the-fold ----------
533
551
 
534
552
  function ensurePreloadObserver() {
535
- if (state.io) return state.io;
553
+ const desiredMargin = getPreloadRootMargin();
554
+ if (state.io && state.ioMargin === desiredMargin) return state.io;
555
+
556
+ // Rebuild IO if margin changed (e.g., scroll boost toggled)
557
+ if (state.io) {
558
+ try { state.io.disconnect(); } catch (e) {}
559
+ state.io = null;
560
+ }
536
561
  try {
537
562
  state.io = new IntersectionObserver((entries) => {
538
563
  for (const ent of entries) {
@@ -544,10 +569,20 @@ function startShow(id) {
544
569
  const id = parseInt(idAttr, 10);
545
570
  if (Number.isFinite(id) && id > 0) enqueueShow(id);
546
571
  }
547
- }, { root: null, rootMargin: getPreloadRootMargin(), threshold: 0 });
572
+ }, { root: null, rootMargin: desiredMargin, threshold: 0 });
573
+ state.ioMargin = desiredMargin;
548
574
  } catch (e) {
549
575
  state.io = null;
576
+ state.ioMargin = null;
550
577
  }
578
+
579
+ // If we rebuilt the observer, re-observe existing placeholders so we don't miss them.
580
+ try {
581
+ if (state.io) {
582
+ const nodes = document.querySelectorAll(`[id^="${PLACEHOLDER_PREFIX}"]`);
583
+ nodes.forEach((n) => { try { state.io.observe(n); } catch (e) {} });
584
+ }
585
+ } catch (e) {}
551
586
  return state.io;
552
587
  }
553
588
 
@@ -560,7 +595,9 @@ function startShow(id) {
560
595
  // If already above fold, fire immediately
561
596
  try {
562
597
  const r = ph.getBoundingClientRect();
563
- if (r.top < window.innerHeight * 3.0 && r.bottom > -800) enqueueShow(id);
598
+ const screens = isBoosted() ? 5.0 : 3.0;
599
+ const minBottom = isBoosted() ? -1500 : -800;
600
+ if (r.top < window.innerHeight * screens && r.bottom > minBottom) enqueueShow(id);
564
601
  } catch (e) {}
565
602
  }
566
603
 
@@ -581,9 +618,10 @@ function startShow(id) {
581
618
 
582
619
  const targets = computeTargets(items.length, interval, showFirst);
583
620
  let inserted = 0;
621
+ const maxInserts = MAX_INSERTS_PER_RUN + (isBoosted() ? 1 : 0);
584
622
 
585
623
  for (const afterPos of targets) {
586
- if (inserted >= MAX_INSERTS_PER_RUN) break;
624
+ if (inserted >= maxInserts) break;
587
625
 
588
626
  const el = items[afterPos - 1];
589
627
  if (!el || !el.isConnected) continue;
@@ -818,6 +856,29 @@ function startShow(id) {
818
856
  function bindScroll() {
819
857
  let ticking = false;
820
858
  window.addEventListener('scroll', () => {
859
+ // Detect very fast scrolling and temporarily boost preload/parallelism.
860
+ try {
861
+ const now = Date.now();
862
+ const y = window.scrollY || window.pageYOffset || 0;
863
+ if (state.lastScrollTs) {
864
+ const dt = now - state.lastScrollTs;
865
+ const dy = Math.abs(y - (state.lastScrollY || 0));
866
+ if (dt > 0) {
867
+ const speed = dy / dt; // px/ms
868
+ if (speed >= BOOST_SPEED_PX_PER_MS) {
869
+ const wasBoosted = isBoosted();
870
+ state.scrollBoostUntil = Math.max(state.scrollBoostUntil || 0, now + BOOST_DURATION_MS);
871
+ if (!wasBoosted) {
872
+ // margin changed -> rebuild IO so existing placeholders get earlier preload
873
+ ensurePreloadObserver();
874
+ }
875
+ }
876
+ }
877
+ }
878
+ state.lastScrollY = y;
879
+ state.lastScrollTs = now;
880
+ } catch (e) {}
881
+
821
882
  if (ticking) return;
822
883
  ticking = true;
823
884
  window.requestAnimationFrame(() => {