nodebb-plugin-ezoic-infinite 1.5.75 → 1.5.76

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 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.75",
3
+ "version": "1.5.76",
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
@@ -38,6 +38,46 @@
38
38
  // Production build: debug disabled
39
39
  function dbg() {}
40
40
 
41
+ // Ezoic (and some partner scripts) can be very noisy in console on SPA/Ajaxify setups.
42
+ // These warnings are not actionable for end-users and can flood the console.
43
+ // We selectively silence the known spam patterns while keeping other warnings intact.
44
+ function muteNoisyConsole() {
45
+ try {
46
+ if (window.__nodebbEzoicConsoleMuted) return;
47
+ window.__nodebbEzoicConsoleMuted = true;
48
+
49
+ const shouldMute = (args) => {
50
+ try {
51
+ if (!args || !args.length) return false;
52
+ const s0 = typeof args[0] === 'string' ? args[0] : '';
53
+ // Duplicate placeholder definition spam (common when reusing ids in SPA/Ajaxify).
54
+ if (s0.includes('[EzoicAds JS]: Placeholder Id') && s0.includes('has already been defined')) return true;
55
+ // Ezoic debugger iframe spam.
56
+ if (s0.includes('Debugger iframe already exists')) return true;
57
+ // Missing placeholder spam (we already guard showAds; Ezoic still logs sometimes).
58
+ if (s0.includes('HTML element with id ezoic-pub-ad-placeholder-') && s0.includes('does not exist')) return true;
59
+ return false;
60
+ } catch (e) {
61
+ return false;
62
+ }
63
+ };
64
+
65
+ const wrap = (method) => {
66
+ const orig = console[method];
67
+ if (typeof orig !== 'function') return;
68
+ console[method] = function (...args) {
69
+ if (shouldMute(args)) return;
70
+ return orig.apply(console, args);
71
+ };
72
+ };
73
+
74
+ wrap('log');
75
+ wrap('info');
76
+ wrap('warn');
77
+ wrap('error');
78
+ } catch (e) {}
79
+ }
80
+
41
81
 
42
82
 
43
83
  function isFilledNode(node) {
@@ -546,6 +586,10 @@ function globalGapFixInit() {
546
586
  wrap.setAttribute('data-ezoic-after', String(afterPos));
547
587
  wrap.setAttribute('data-ezoic-wrapid', String(id));
548
588
  wrap.setAttribute('data-created', String(now()));
589
+ // "Pinned" placements (after the first item) should remain stable.
590
+ if (afterPos === 1) {
591
+ wrap.setAttribute('data-ezoic-pin', '1');
592
+ }
549
593
  wrap.style.width = '100%';
550
594
 
551
595
  if (createPlaceholder) {
@@ -604,8 +648,9 @@ function globalGapFixInit() {
604
648
  }
605
649
 
606
650
  function pruneOrphanWraps(kindClass, items) {
607
- // On mobile topics/messages, keep wraps alive longer to avoid 'disappearing' ads.
608
- if (kindClass === 'ezoic-ad-message' && isMobile()) return 0;
651
+ // Message ads should remain stable (desktop + mobile). Pruning them can make it look like
652
+ // ads vanish on scroll and can reduce fill on long topics.
653
+ if (kindClass === 'ezoic-ad-message') return 0;
609
654
  if (!items || !items.length) return 0;
610
655
  const itemSet = new Set(items);
611
656
  const wraps = document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`);
@@ -631,6 +676,11 @@ function globalGapFixInit() {
631
676
  };
632
677
 
633
678
  wraps.forEach((wrap) => {
679
+ // Never prune pinned placements.
680
+ try {
681
+ if (wrap.getAttribute('data-ezoic-pin') === '1') return;
682
+ } catch (e) {}
683
+
634
684
  if (isFilled(wrap)) return; // never prune filled ads
635
685
 
636
686
  // Never prune a fresh wrap: it may fill late.
@@ -670,9 +720,19 @@ function globalGapFixInit() {
670
720
 
671
721
  let removed = 0;
672
722
  for (const w of wraps) {
723
+ // Never decluster pinned placements.
724
+ try {
725
+ if (w.getAttribute && w.getAttribute('data-ezoic-pin') === '1') continue;
726
+ } catch (e) {}
727
+
673
728
  let prev = w.previousElementSibling;
674
729
  for (let i = 0; i < 3 && prev; i++) {
675
730
  if (isWrap(prev)) {
731
+ // If the previous wrap is pinned, keep this one (spacing is intentional).
732
+ try {
733
+ if (prev.getAttribute && prev.getAttribute('data-ezoic-pin') === '1') break;
734
+ } catch (e) {}
735
+
676
736
  // Don't decluster two "fresh" empty wraps — it can reduce fill on slow auctions.
677
737
  // Only decluster when at least one is filled, or when the newer one is stale.
678
738
  const prevFilled = isFilled(prev);
@@ -1020,6 +1080,10 @@ function globalGapFixInit() {
1020
1080
 
1021
1081
  state.heroDoneForPage = true;
1022
1082
  observePlaceholder(id);
1083
+ // Hero placement is expected to be visible right away (after first item),
1084
+ // so kick a fill request immediately instead of waiting only for IO callbacks.
1085
+ enqueueShow(id);
1086
+ startShowQueue();
1023
1087
  }
1024
1088
 
1025
1089
  // ---------------- scheduler ----------------
@@ -1154,6 +1218,7 @@ function globalGapFixInit() {
1154
1218
  state.pageKey = getPageKey();
1155
1219
  blockedUntil = 0;
1156
1220
 
1221
+ muteNoisyConsole();
1157
1222
  warmUpNetwork();
1158
1223
  patchShowAds();
1159
1224
  globalGapFixInit();
@@ -1208,6 +1273,7 @@ function globalGapFixInit() {
1208
1273
  // ---------------- boot ----------------
1209
1274
 
1210
1275
  state.pageKey = getPageKey();
1276
+ muteNoisyConsole();
1211
1277
  warmUpNetwork();
1212
1278
  patchShowAds();
1213
1279
  ensurePreloadObserver();