nodebb-plugin-ezoic-infinite 1.6.73 → 1.6.75

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 +74 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.73",
3
+ "version": "1.6.75",
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
@@ -220,6 +220,42 @@ function tightenEzoicMinHeight(wrap) {
220
220
  } catch (e) {}
221
221
  }
222
222
 
223
+ // If a placement never fills, Ezoic may still leave an inline `min-height:400px !important`
224
+ // on nested containers, creating large "blank" blocks. On SPA/infinite scroll, some
225
+ // auctions can fail (network/CMP/adblock). We collapse those gaps after a grace period.
226
+ function collapseEmptyGap(wrap) {
227
+ try {
228
+ if (!wrap || !wrap.isConnected) return;
229
+ if (isFilledNode(wrap)) return;
230
+
231
+ const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
232
+ if (!created) return;
233
+ // Give plenty of time for late fill (CMP / header bidding).
234
+ if ((now() - created) < 12000) return;
235
+
236
+ // Find the nested node that carries the 400px min-height.
237
+ const nodes = wrap.querySelectorAll('.ezoic-ad, .ezoic-ad-adaptive');
238
+ nodes.forEach((n) => {
239
+ const st = (n.getAttribute('style') || '').toLowerCase();
240
+ if (st.includes('min-height:400')) {
241
+ try { n.style.setProperty('min-height', '1px', 'important'); } catch (e) { n.style.minHeight = '1px'; }
242
+ try { n.style.setProperty('height', 'auto', 'important'); } catch (e) {}
243
+ }
244
+ });
245
+
246
+ // Also collapse our wrapper's reserved height.
247
+ try { wrap.style.setProperty('min-height', '1px', 'important'); } catch (e) { wrap.style.minHeight = '1px'; }
248
+ try { wrap.style.setProperty('height', 'auto', 'important'); } catch (e) {}
249
+ } catch (e) {}
250
+ }
251
+
252
+ function collapseEmptyGaps(kindClass) {
253
+ try {
254
+ const wraps = document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`);
255
+ wraps.forEach((w) => collapseEmptyGap(w));
256
+ } catch (e) {}
257
+ }
258
+
223
259
  function watchWrapForFill(wrap) {
224
260
  try {
225
261
  if (!wrap || wrap.__ezFillObs) return;
@@ -738,17 +774,16 @@ function globalGapFixInit() {
738
774
  if (wrap.getAttribute('data-ezoic-pin') === '1') return;
739
775
  } catch (e) {}
740
776
 
741
- // For message pages AND category topic lists, NodeBB may virtualize/recycle items.
742
- // If we never hide/prune filled wraps, they can "pile up" when their anchor items
743
- // temporarily leave the DOM (especially on /category/... lists).
777
+ // Virtualized lists can remove anchors from the DOM while keeping our wraps.
778
+ // If we keep *filled* wraps alive when their anchors disappear, they will visually
779
+ // "stack" (back-to-back) when the user scrolls back up.
744
780
  //
745
- // Strategy:
746
- // - always hide orphan wraps (filled or empty) when their anchor item is gone
747
- // - only *release* (remove) them if they are far offscreen, to avoid "vanishing" feel
781
+ // Topic list pages (categoryTopics) are also virtualized in some themes/plugins,
782
+ // so we must treat between-ads similarly to message-ads.
748
783
  const isMessage = (kindClass === 'ezoic-ad-message');
749
- const isTopicList = (kindClass === 'ezoic-ad-between');
750
- const allowOrphanForFilled = (isMessage || isTopicList);
751
- if (!allowOrphanForFilled && isFilled(wrap)) return; // keep filled ads for non-virtualized lists
784
+ const isBetween = (kindClass === 'ezoic-ad-between');
785
+ const isVirtualizedList = isMessage || isBetween;
786
+ if (!isVirtualizedList && isFilled(wrap)) return; // keep filled ads for non-virtualized lists
752
787
 
753
788
  // Never prune a fresh wrap: it may fill late.
754
789
  try {
@@ -756,30 +791,34 @@ function globalGapFixInit() {
756
791
  if (created && (now() - created) < keepEmptyWrapMs()) return;
757
792
  } catch (e) {}
758
793
 
759
- if (hasNearbyItem(wrap)) {
760
- try { wrap.classList && wrap.classList.remove('ez-orphan-hidden'); wrap.style && (wrap.style.display = ''); } catch (e) {}
761
- return;
762
- }
794
+ if (hasNearbyItem(wrap)) {
795
+ try {
796
+ wrap.classList && wrap.classList.remove('ez-orphan-hidden');
797
+ wrap.style && (wrap.style.display = '');
798
+ } catch (e) {}
799
+ return;
800
+ }
763
801
 
764
- // If the anchor item is no longer in the DOM (virtualized), hide the wrap so ads never "stack"
765
- // back-to-back while scrolling. We'll recycle it when its anchor comes back.
766
- try { wrap.classList && wrap.classList.add('ez-orphan-hidden'); wrap.style && (wrap.style.display = 'none'); } catch (e) {}
802
+ // If the anchor item is no longer in the DOM (virtualized), hide the wrap so ads never "stack"
803
+ // back-to-back while scrolling. When the anchor comes back, the wrap can be shown again.
804
+ try {
805
+ wrap.classList && wrap.classList.add('ez-orphan-hidden');
806
+ wrap.style && (wrap.style.display = 'none');
807
+ } catch (e) {}
767
808
 
768
- // For message ads and topic-list ads: only release if far offscreen to avoid perceived "vanishing" during fast scroll.
769
- if (isMessage || isTopicList) {
770
- try {
771
- const r = wrap.getBoundingClientRect();
772
- const vh = Math.max(1, window.innerHeight || 1);
773
- const farAbove = r.bottom < -vh * 2;
774
- const farBelow = r.top > vh * 4;
775
- if (!farAbove && !farBelow) return;
776
- } catch (e) {
777
- return;
778
- }
779
- }
809
+ // Release only if far offscreen (keep UX stable). Use looser thresholds for topic lists.
810
+ try {
811
+ const r = wrap.getBoundingClientRect();
812
+ const vh = Math.max(1, window.innerHeight || 1);
813
+ const farAbove = r.bottom < -vh * (isBetween ? 4 : 2);
814
+ const farBelow = r.top > vh * (isBetween ? 8 : 4);
815
+ if (!farAbove && !farBelow) return;
816
+ } catch (e) {
817
+ return;
818
+ }
780
819
 
781
- withInternalDomChange(() => releaseWrapNode(wrap));
782
- removed++;
820
+ withInternalDomChange(() => releaseWrapNode(wrap));
821
+ removed++;
783
822
  });
784
823
 
785
824
  return removed;
@@ -805,6 +844,7 @@ removed++;
805
844
  }
806
845
  };
807
846
 
847
+ const lookback = (kindClass === 'ezoic-ad-between') ? 12 : 3;
808
848
  let removed = 0;
809
849
  for (const w of wraps) {
810
850
  // Never decluster pinned placements.
@@ -813,7 +853,7 @@ removed++;
813
853
  } catch (e) {}
814
854
 
815
855
  let prev = w.previousElementSibling;
816
- for (let i = 0; i < 3 && prev; i++) {
856
+ for (let i = 0; i < lookback && prev; i++) {
817
857
  if (isWrap(prev)) {
818
858
  // If the previous wrap is pinned, keep this one (spacing is intentional).
819
859
  try {
@@ -1203,6 +1243,7 @@ function buildOrdinalMap(items) {
1203
1243
  'curPosts'
1204
1244
  );
1205
1245
  decluster('ezoic-ad-message');
1246
+ collapseEmptyGaps('ezoic-ad-message');
1206
1247
  }
1207
1248
  } else if (kind === 'categoryTopics') {
1208
1249
  if (normalizeBool(cfg.enableBetweenAds)) {
@@ -1217,6 +1258,7 @@ function buildOrdinalMap(items) {
1217
1258
  'curTopics'
1218
1259
  );
1219
1260
  decluster('ezoic-ad-between');
1261
+ collapseEmptyGaps('ezoic-ad-between');
1220
1262
  }
1221
1263
  } else if (kind === 'categories') {
1222
1264
  if (normalizeBool(cfg.enableCategoryAds)) {
@@ -1231,6 +1273,7 @@ function buildOrdinalMap(items) {
1231
1273
  'curCategories'
1232
1274
  );
1233
1275
  decluster('ezoic-ad-categories');
1276
+ collapseEmptyGaps('ezoic-ad-categories');
1234
1277
  }
1235
1278
  }
1236
1279