nodebb-plugin-ezoic-infinite 1.5.90 → 1.5.91

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 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.90",
3
+ "version": "1.5.91",
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
@@ -1,32 +1,54 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- // ===== V5.1 multi-ads stable anchors =====
5
- function getStableAnchorKey(el, fallbackPos) {
4
+ // ===== V6 anchor-map stability layer =====
5
+ var __ezoicAnchorMap = Object.create(null);
6
+
7
+ function ezoicGetAnchorKey(el, afterPos, kindClass) {
6
8
  try {
7
- if (!el) return 'pos:' + String(fallbackPos || 0);
9
+ if (!el) return kindClass + ':pos:' + String(afterPos || 0);
8
10
  var pid = el.getAttribute && (el.getAttribute('data-pid') || el.getAttribute('data-id'));
9
- if (pid) return 'pid:' + String(pid);
11
+ if (pid) return kindClass + ':pid:' + String(pid);
10
12
  var tid = el.getAttribute && (el.getAttribute('data-tid') || el.getAttribute('data-topic-id'));
11
- if (tid) return 'tid:' + String(tid);
12
- var topic = el.querySelector && el.querySelector('[data-tid],[data-topic-id]');
13
- if (topic) {
14
- var t = topic.getAttribute('data-tid') || topic.getAttribute('data-topic-id');
15
- if (t) return 'tid:' + String(t);
13
+ if (tid) return kindClass + ':tid:' + String(tid);
14
+ var a = el.querySelector && el.querySelector('[data-pid],[data-id],[data-tid],[data-topic-id]');
15
+ if (a) {
16
+ var p = a.getAttribute('data-pid') || a.getAttribute('data-id');
17
+ if (p) return kindClass + ':pid:' + String(p);
18
+ var t = a.getAttribute('data-tid') || a.getAttribute('data-topic-id');
19
+ if (t) return kindClass + ':tid:' + String(t);
16
20
  }
17
21
  } catch (e) {}
18
- return 'pos:' + String(fallbackPos || 0);
22
+ return kindClass + ':pos:' + String(afterPos || 0);
19
23
  }
20
24
 
21
- function findWrapByAnchor(kindClass, anchorKey) {
25
+ function ezoicRegisterWrapAnchor(wrap, anchorKey) {
22
26
  try {
23
- var sel = '.nodebb-ezoic-wrap.' + kindClass + '[data-ezoic-anchor="' + anchorKey.replace(/"/g, '') + '"]';
24
- return document.querySelector(sel);
25
- } catch (e) {
26
- return null;
27
- }
27
+ if (!wrap || !anchorKey) return;
28
+ wrap.setAttribute('data-ezoic-anchor', anchorKey);
29
+ __ezoicAnchorMap[anchorKey] = wrap;
30
+ } catch (e) {}
31
+ }
32
+
33
+ function ezoicFindWrapByAnchor(anchorKey) {
34
+ try {
35
+ var w = __ezoicAnchorMap[anchorKey];
36
+ if (w && w.isConnected) return w;
37
+ } catch (e) {}
38
+ try {
39
+ return document.querySelector('.nodebb-ezoic-wrap[data-ezoic-anchor="' + String(anchorKey).replace(/"/g, '') + '"]');
40
+ } catch (e) { return null; }
41
+ }
42
+
43
+ function ezoicCleanupAnchorMap() {
44
+ try {
45
+ Object.keys(__ezoicAnchorMap).forEach(function(k){
46
+ var w = __ezoicAnchorMap[k];
47
+ if (!w || !w.isConnected) delete __ezoicAnchorMap[k];
48
+ });
49
+ } catch (e) {}
28
50
  }
29
- // ===== /V5.1 =====
51
+ // ===== /V6 =====
30
52
 
31
53
 
32
54
  // Track scroll direction to avoid aggressive recycling when the user scrolls upward.
@@ -668,7 +690,8 @@ function globalGapFixInit() {
668
690
  const wrap = document.createElement('div');
669
691
  wrap.className = `${WRAP_CLASS} ${kindClass}`;
670
692
  wrap.setAttribute('data-ezoic-after', String(afterPos));
671
- try { wrap.setAttribute('data-ezoic-anchor', getStableAnchorKey(el, afterPos)); } catch (e) {}
693
+ var __anchorKey = ezoicGetAnchorKey(el, afterPos, kindClass);
694
+ ezoicRegisterWrapAnchor(wrap, __anchorKey);
672
695
  wrap.setAttribute('data-ezoic-wrapid', String(id));
673
696
  wrap.setAttribute('data-created', String(now()));
674
697
  // "Pinned" placements (after the first item) should remain stable.
@@ -796,8 +819,8 @@ if (isMessage) {
796
819
  const farBelow = r.top > vh * 4;
797
820
  if (!farAbove && !farBelow) return;
798
821
  } catch (e) {
799
- // keep original behavior in base build
800
- }
822
+ return;
823
+ }
801
824
  }
802
825
 
803
826
  withInternalDomChange(() => releaseWrapNode(wrap));
@@ -1108,9 +1131,7 @@ function buildOrdinalMap(items) {
1108
1131
  }
1109
1132
 
1110
1133
  function injectBetween(kindClass, items, interval, showFirst, allIds, cursorKey) {
1111
-
1112
- // V5.1: stable anchor dedupe (prevents regrouping while preserving multiple ads)
1113
-
1134
+ ezoicCleanupAnchorMap();
1114
1135
  if (!items.length) return 0;
1115
1136
 
1116
1137
  const { map: ordinalMap, max: maxOrdinal } = buildOrdinalMap(items);
@@ -1134,9 +1155,10 @@ function buildOrdinalMap(items) {
1134
1155
  // This avoids "Placeholder Id X has already been defined" and also ensures ads keep
1135
1156
  // appearing on very long infinite scroll sessions.
1136
1157
  if (!id) {
1137
- // Only recycle while scrolling down. Recycling while scrolling up tends to
1138
- // cause perceived instability (ads bunching/disappearing).
1139
- recycledWrap = scrollDir > 0 ? pickRecyclableWrap(kindClass) : null;
1158
+ // Safe mode: disable recycling for topic message ads to prevent visual "jumping"
1159
+ // (ads seemingly moving back under the first post during virtualized scroll).
1160
+ const allowRecycle = kindClass !== 'ezoic-ad-message';
1161
+ recycledWrap = (allowRecycle && scrollDir > 0) ? pickRecyclableWrap(kindClass) : null;
1140
1162
  if (recycledWrap) {
1141
1163
  id = recycledWrap.getAttribute('data-ezoic-wrapid') || '';
1142
1164
  }
@@ -1163,7 +1185,7 @@ function buildOrdinalMap(items) {
1163
1185
  }
1164
1186
 
1165
1187
  function moveWrapAfter(anchorEl, wrap, kindClass, afterPos) {
1166
- // keep original behavior in base build
1188
+ return;
1167
1189
  }
1168
1190
 
1169
1191
  async function runCore() {
@@ -1503,3 +1525,28 @@ function buildOrdinalMap(items) {
1503
1525
  insertHeroAdEarly().catch(() => {});
1504
1526
  requestBurst();
1505
1527
  })();
1528
+
1529
+
1530
+ // V6 guard: prevent unexpected regrouping under first topic
1531
+ try {
1532
+ if (typeof MutationObserver !== 'undefined' && !window.__ezoicWrapGuardInstalled) {
1533
+ window.__ezoicWrapGuardInstalled = true;
1534
+ var guardObserver = new MutationObserver(function() {
1535
+ try {
1536
+ Object.keys(__ezoicAnchorMap).forEach(function(k){
1537
+ var w = __ezoicAnchorMap[k];
1538
+ if (!w || !w.isConnected) return;
1539
+ var anchor = w.getAttribute('data-ezoic-anchor') || '';
1540
+ var m = anchor.match(/:(pid|tid):(.+)$/);
1541
+ if (!m) return;
1542
+ var val = m[2];
1543
+ var anchorEl = document.querySelector('[data-pid="' + val + '"], [data-id="' + val + '"], [data-tid="' + val + '"], [data-topic-id="' + val + '"]');
1544
+ if (anchorEl && w.previousElementSibling !== anchorEl) {
1545
+ if (anchorEl.parentNode) anchorEl.parentNode.insertBefore(w, anchorEl.nextSibling);
1546
+ }
1547
+ });
1548
+ } catch (e) {}
1549
+ });
1550
+ guardObserver.observe(document.body || document.documentElement, { childList: true, subtree: true });
1551
+ }
1552
+ } catch (e) {}