nodebb-plugin-ezoic-infinite 1.5.81 → 1.5.83

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.81",
3
+ "version": "1.5.83",
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
@@ -749,24 +749,30 @@ function globalGapFixInit() {
749
749
  if (created && (now() - created) < keepEmptyWrapMs()) return;
750
750
  } catch (e) {}
751
751
 
752
- if (hasNearbyItem(wrap)) return;
752
+ if (hasNearbyItem(wrap)) {
753
+ try { wrap.classList && wrap.classList.remove('ez-orphan-hidden'); wrap.style && (wrap.style.display = ''); } catch (e) {}
754
+ return;
755
+ }
753
756
 
754
- // For message ads: only prune if far offscreen to avoid perceived "vanishing".
755
- if (isMessage) {
756
- try {
757
- const r = wrap.getBoundingClientRect();
758
- const vh = Math.max(1, window.innerHeight || 1);
759
- const farAbove = r.bottom < -vh * 2;
760
- const farBelow = r.top > vh * 4;
761
- if (!farAbove && !farBelow) return;
762
- } catch (e) {
763
- // If we can't measure, be conservative.
764
- return;
765
- }
766
- }
757
+ // If the anchor item is no longer in the DOM (virtualized), hide the wrap so ads never "stack"
758
+ // back-to-back while scrolling. We'll recycle it when its anchor comes back.
759
+ try { wrap.classList && wrap.classList.add('ez-orphan-hidden'); wrap.style && (wrap.style.display = 'none'); } catch (e) {}
767
760
 
768
- withInternalDomChange(() => releaseWrapNode(wrap));
769
- removed++;
761
+ // For message ads: only release if far offscreen to avoid perceived "vanishing" during fast scroll.
762
+ if (isMessage) {
763
+ try {
764
+ const r = wrap.getBoundingClientRect();
765
+ const vh = Math.max(1, window.innerHeight || 1);
766
+ const farAbove = r.bottom < -vh * 2;
767
+ const farBelow = r.top > vh * 4;
768
+ if (!farAbove && !farBelow) return;
769
+ } catch (e) {
770
+ return;
771
+ }
772
+ }
773
+
774
+ withInternalDomChange(() => releaseWrapNode(wrap));
775
+ removed++;
770
776
  });
771
777
 
772
778
  return removed;
@@ -1031,6 +1037,36 @@ function globalGapFixInit() {
1031
1037
 
1032
1038
  // ---------------- core injection ----------------
1033
1039
 
1040
+ function getItemOrdinal(el, fallbackIndex) {
1041
+ try {
1042
+ if (!el) return fallbackIndex + 1;
1043
+ const di = el.getAttribute('data-index') || (el.dataset && (el.dataset.index || el.dataset.postIndex));
1044
+ if (di !== null && di !== undefined && di !== '' && !isNaN(di)) {
1045
+ const n = parseInt(di, 10);
1046
+ if (Number.isFinite(n) && n >= 0) return n + 1;
1047
+ }
1048
+ const d1 = el.getAttribute('data-idx') || el.getAttribute('data-position') || (el.dataset && (el.dataset.idx || el.dataset.position));
1049
+ if (d1 !== null && d1 !== undefined && d1 !== '' && !isNaN(d1)) {
1050
+ const n = parseInt(d1, 10);
1051
+ if (Number.isFinite(n) && n > 0) return n;
1052
+ }
1053
+ } catch (e) {}
1054
+ return fallbackIndex + 1;
1055
+ }
1056
+
1057
+ function buildOrdinalMap(items) {
1058
+ const map = new Map();
1059
+ let max = 0;
1060
+ for (let i = 0; i < items.length; i++) {
1061
+ const el = items[i];
1062
+ const ord = getItemOrdinal(el, i);
1063
+ map.set(ord, el);
1064
+ if (ord > max) max = ord;
1065
+ }
1066
+ return { map, max };
1067
+ }
1068
+
1069
+
1034
1070
  function computeTargets(count, interval, showFirst) {
1035
1071
  const out = [];
1036
1072
  if (count <= 0) return out;
@@ -1045,14 +1081,15 @@ function globalGapFixInit() {
1045
1081
  function injectBetween(kindClass, items, interval, showFirst, allIds, cursorKey) {
1046
1082
  if (!items.length) return 0;
1047
1083
 
1048
- const targets = computeTargets(items.length, interval, showFirst);
1084
+ const { map: ordinalMap, max: maxOrdinal } = buildOrdinalMap(items);
1085
+ const targets = computeTargets(maxOrdinal, interval, showFirst);
1049
1086
  let inserted = 0;
1050
1087
  const maxInserts = MAX_INSERTS_PER_RUN + (isBoosted() ? 1 : 0);
1051
1088
 
1052
1089
  for (const afterPos of targets) {
1053
1090
  if (inserted >= maxInserts) break;
1054
-
1055
- const el = items[afterPos - 1];
1091
+ const el = ordinalMap.get(afterPos);
1092
+ if (!el) continue;
1056
1093
  if (!el || !el.isConnected) continue;
1057
1094
  if (isAdjacentAd(el)) continue;
1058
1095
  if (findWrap(kindClass, afterPos)) continue;
@@ -0,0 +1 @@
1
+ hi