nodebb-plugin-ezoic-infinite 0.6.7 → 0.6.8

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 +104 -74
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "0.6.7",
3
+ "version": "0.6.8",
4
4
  "description": "Ezoic ads with infinite scroll using a pool of placeholder IDs",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -10,19 +10,27 @@ let debounceTimer;
10
10
  let inFlight = false;
11
11
  let rerunRequested = false;
12
12
 
13
- // Per-page incremental state
13
+ // Per-page state (keyed by tid/cid)
14
14
  let pageKey = null;
15
- let seenSlots = new Set(); // slots already processed (never cleared on eviction)
16
- let usedIds = new Set(); // IDs currently present in DOM
17
- let adsFIFO = []; // [{slot, id}] oldest first
18
15
 
19
- function resetPageState() {
20
- seenSlots = new Set();
16
+ // Topic page state: anchor ads to absolute post number (not DOM index)
17
+ let seenAfterPostNo = new Set(); // post numbers we've already inserted an ad after
18
+ let usedIds = new Set(); // ids currently in DOM
19
+ let fifo = []; // [{afterPostNo, id}]
20
+
21
+ // Category page state: anchor to absolute topic position if available
22
+ let seenAfterTopicPos = new Set(); // topic positions we've inserted after
23
+ let fifoCat = []; // [{afterPos, id}]
24
+
25
+ function resetState() {
26
+ seenAfterPostNo = new Set();
27
+ seenAfterTopicPos = new Set();
21
28
  usedIds = new Set();
22
- adsFIFO = [];
29
+ fifo = [];
30
+ fifoCat = [];
23
31
  }
24
32
 
25
- function currentPageKey() {
33
+ function getPageKey() {
26
34
  try {
27
35
  if (ajaxify && ajaxify.data) {
28
36
  if (ajaxify.data.tid) return 'topic:' + ajaxify.data.tid;
@@ -58,11 +66,10 @@ function isCategoryTopicListPage() {
58
66
  }
59
67
 
60
68
  function getTopicPosts() {
61
- // Only real posts
62
69
  const $primary = $('[component="post"][data-pid]');
63
70
  if ($primary.length) return $primary;
64
71
 
65
- // Fallback: top-level with post/content
72
+ // fallback: top-level with post/content
66
73
  return $('[data-pid]').filter(function () {
67
74
  const $el = $(this);
68
75
  const hasContent = $el.find('[component="post/content"]').length > 0;
@@ -94,7 +101,7 @@ function makeWrapperLike($target, classes, innerHtml, attrs) {
94
101
  return '<div class="' + classes + '"' + attrStr + '>' + innerHtml + '</div>';
95
102
  }
96
103
 
97
- function cleanupWrappersOnNav() {
104
+ function cleanupOnNav() {
98
105
  $('.ezoic-ad-post, .ezoic-ad-topic, .ezoic-ad-between').remove();
99
106
  }
100
107
 
@@ -105,11 +112,26 @@ function pickNextId(pool) {
105
112
  return null;
106
113
  }
107
114
 
108
- function evictOldestOne() {
109
- const old = adsFIFO.shift();
115
+ function removeOldestTopicAd() {
116
+ fifo.sort((a, b) => a.afterPostNo - b.afterPostNo);
117
+ const old = fifo.shift();
110
118
  if (!old) return false;
111
119
 
112
- const sel = '[data-ezoic-slot="' + old.slot + '"][data-ezoic-id="' + old.id + '"]';
120
+ const sel = '.ezoic-ad-post[data-ezoic-after="' + old.afterPostNo + '"][data-ezoic-id="' + old.id + '"]';
121
+ const $el = $(sel);
122
+ if ($el.length) $el.remove();
123
+
124
+ usedIds.delete(old.id);
125
+ // DO NOT delete seenAfterPostNo to prevent re-insertion in the top area
126
+ return true;
127
+ }
128
+
129
+ function removeOldestCategoryAd() {
130
+ fifoCat.sort((a, b) => a.afterPos - b.afterPos);
131
+ const old = fifoCat.shift();
132
+ if (!old) return false;
133
+
134
+ const sel = '.ezoic-ad-topic[data-ezoic-after="' + old.afterPos + '"][data-ezoic-id="' + old.id + '"]';
113
135
  const $el = $(sel);
114
136
  if ($el.length) $el.remove();
115
137
 
@@ -135,7 +157,6 @@ function callEzoic(ids) {
135
157
 
136
158
  window.ezstandalone.cmd.push(function () { run(); });
137
159
 
138
- // Retry (Ezoic can load late)
139
160
  let tries = 0;
140
161
  const maxTries = 6;
141
162
  const timer = setInterval(function () {
@@ -144,93 +165,105 @@ function callEzoic(ids) {
144
165
  }, 800);
145
166
  }
146
167
 
147
- function injectBetweenIncremental($items, pool, interval, wrapperClass) {
148
- const total = $items.length;
149
- const maxSlot = Math.floor(total / interval);
150
- if (maxSlot <= 0) return [];
168
+ function getPostNumber($post) {
169
+ const di = parseInt($post.attr('data-index'), 10);
170
+ if (Number.isFinite(di) && di > 0) return di;
171
+
172
+ const txt = ($post.find('a.post-index').first().text() || '').trim();
173
+ const m = txt.match(/#\s*(\d+)/);
174
+ if (m) return parseInt(m[1], 10);
175
+
176
+ return NaN;
177
+ }
178
+
179
+ function getTopicPos($item) {
180
+ const pos = parseInt($item.attr('data-index'), 10);
181
+ if (Number.isFinite(pos) && pos >= 0) return pos + 1;
182
+ const schemaPos = parseInt($item.find('meta[itemprop="position"]').attr('content'), 10);
183
+ if (Number.isFinite(schemaPos) && schemaPos > 0) return schemaPos;
184
+ return NaN;
185
+ }
151
186
 
187
+ function injectTopicMessageAds($posts, pool, interval) {
152
188
  const newIds = [];
153
189
 
154
- for (let slot = 1; slot <= maxSlot; slot++) {
155
- if (seenSlots.has(slot)) continue;
190
+ $posts.each(function () {
191
+ const $p = $(this);
192
+ const postNo = getPostNumber($p);
193
+ if (!Number.isFinite(postNo) || postNo <= 0) return;
156
194
 
157
- const index = slot * interval - 1;
158
- const $target = $items.eq(index);
159
- if (!$target.length) continue;
195
+ if (postNo % interval !== 0) return;
196
+ if (seenAfterPostNo.has(postNo)) return;
160
197
 
161
198
  let id = pickNextId(pool);
162
199
  if (!id) {
163
- if (!evictOldestOne()) break;
200
+ if (!removeOldestTopicAd()) return;
164
201
  id = pickNextId(pool);
165
- if (!id) break;
202
+ if (!id) return;
166
203
  }
167
204
 
168
- const placeholder = '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>';
205
+ const inner = '<div class="ezoic-ad-message-inner"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
169
206
  const html = makeWrapperLike(
170
- $target,
171
- wrapperClass + ' ezoic-ad',
172
- placeholder,
173
- 'data-ezoic-slot="' + slot + '" data-ezoic-id="' + id + '"'
207
+ $p,
208
+ 'ezoic-ad-post ezoic-ad',
209
+ inner,
210
+ 'data-ezoic-after="' + postNo + '" data-ezoic-id="' + id + '"'
174
211
  );
175
212
 
176
- $target.after(html);
213
+ $p.after(html);
177
214
 
178
- seenSlots.add(slot);
215
+ seenAfterPostNo.add(postNo);
179
216
  usedIds.add(id);
180
- adsFIFO.push({ slot: slot, id: id });
217
+ fifo.push({ afterPostNo: postNo, id: id });
181
218
  newIds.push(id);
182
- }
219
+ });
183
220
 
184
221
  return newIds;
185
222
  }
186
223
 
187
- function injectMessageIncremental($posts, pool, interval) {
188
- const total = $posts.length;
189
- const maxSlot = Math.floor(total / interval);
190
- if (maxSlot <= 0) return [];
191
-
224
+ function injectCategoryBetweenAds($items, pool, interval) {
192
225
  const newIds = [];
193
226
 
194
- for (let slot = 1; slot <= maxSlot; slot++) {
195
- if (seenSlots.has(slot)) continue;
227
+ $items.each(function () {
228
+ const $it = $(this);
229
+ const pos = getTopicPos($it);
230
+ if (!Number.isFinite(pos) || pos <= 0) return;
196
231
 
197
- const index = slot * interval - 1;
198
- const $target = $posts.eq(index);
199
- if (!$target.length) continue;
232
+ if (pos % interval !== 0) return;
233
+ if (seenAfterTopicPos.has(pos)) return;
200
234
 
201
235
  let id = pickNextId(pool);
202
236
  if (!id) {
203
- if (!evictOldestOne()) break;
237
+ if (!removeOldestCategoryAd()) return;
204
238
  id = pickNextId(pool);
205
- if (!id) break;
239
+ if (!id) return;
206
240
  }
207
241
 
208
- // Do NOT use class "post" nor component="post" for ads: can break NodeBB infinite scroll.
209
- const inner = '<div class="ezoic-ad-message-inner"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
242
+ const placeholder = '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>';
210
243
  const html = makeWrapperLike(
211
- $target,
212
- 'ezoic-ad-post ezoic-ad',
213
- inner,
214
- 'data-ezoic-slot="' + slot + '" data-ezoic-id="' + id + '"'
244
+ $it,
245
+ 'ezoic-ad-topic ezoic-ad',
246
+ placeholder,
247
+ 'data-ezoic-after="' + pos + '" data-ezoic-id="' + id + '"'
215
248
  );
216
249
 
217
- $target.after(html);
250
+ $it.after(html);
218
251
 
219
- seenSlots.add(slot);
252
+ seenAfterTopicPos.add(pos);
220
253
  usedIds.add(id);
221
- adsFIFO.push({ slot: slot, id: id });
254
+ fifoCat.push({ afterPos: pos, id: id });
222
255
  newIds.push(id);
223
- }
256
+ });
224
257
 
225
258
  return newIds;
226
259
  }
227
260
 
228
261
  async function refreshAds() {
229
- const key = currentPageKey();
262
+ const key = getPageKey();
230
263
  if (pageKey !== key) {
231
264
  pageKey = key;
232
- resetPageState();
233
- cleanupWrappersOnNav();
265
+ resetState();
266
+ cleanupOnNav();
234
267
  }
235
268
 
236
269
  if (inFlight) { rerunRequested = true; return; }
@@ -249,24 +282,21 @@ async function refreshAds() {
249
282
  const onTopic = isTopicPage();
250
283
  const onCategory = !onTopic && isCategoryTopicListPage();
251
284
 
252
- const $posts = onTopic ? getTopicPosts() : $();
253
- const $topicItems = onCategory ? getCategoryTopicItems() : $();
254
-
255
- if (!$posts.length && !$topicItems.length) return;
256
-
257
285
  const newIds = [];
258
286
 
259
- if ($topicItems.length) {
260
- if (cfg.enableBetweenAds && betweenPool.length) {
261
- newIds.push(...injectBetweenIncremental($topicItems, betweenPool, betweenInterval, 'ezoic-ad-topic'));
287
+ if (onCategory) {
288
+ const $items = getCategoryTopicItems();
289
+ if (cfg.enableBetweenAds && betweenPool.length && $items.length) {
290
+ newIds.push(...injectCategoryBetweenAds($items, betweenPool, betweenInterval));
262
291
  }
263
292
  callEzoic(newIds);
264
293
  return;
265
294
  }
266
295
 
267
- if ($posts.length) {
268
- if (cfg.enableMessageAds && messagePool.length) {
269
- newIds.push(...injectMessageIncremental($posts, messagePool, messageInterval));
296
+ if (onTopic) {
297
+ const $posts = getTopicPosts();
298
+ if (cfg.enableMessageAds && messagePool.length && $posts.length) {
299
+ newIds.push(...injectTopicMessageAds($posts, messagePool, messageInterval));
270
300
  }
271
301
  callEzoic(newIds);
272
302
  }
@@ -274,16 +304,16 @@ async function refreshAds() {
274
304
  inFlight = false;
275
305
  if (rerunRequested) {
276
306
  rerunRequested = false;
277
- setTimeout(refreshAds, 120);
307
+ setTimeout(refreshAds, 160);
278
308
  }
279
309
  }
280
310
  }
281
311
 
282
312
  function debounceRefresh() {
283
313
  clearTimeout(debounceTimer);
284
- debounceTimer = setTimeout(refreshAds, 200);
314
+ debounceTimer = setTimeout(refreshAds, 220);
285
315
  }
286
316
 
287
317
  $(document).ready(debounceRefresh);
288
318
  $(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded', debounceRefresh);
289
- setTimeout(debounceRefresh, 1800);
319
+ setTimeout(debounceRefresh, 2200);