nodebb-plugin-ezoic-infinite 0.6.6 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "0.6.6",
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/plugin.json CHANGED
@@ -22,5 +22,8 @@
22
22
  "scripts": [
23
23
  "public/client.js"
24
24
  ],
25
- "templates": "public/templates"
25
+ "templates": "public/templates",
26
+ "css": [
27
+ "public/style.css"
28
+ ]
26
29
  }
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 injectedSlots = new Set(); // slot numbers already injected on this page
16
- let usedIds = new Set(); // IDs currently present in DOM
17
- let adsFIFO = []; // [{slot, id}] oldest first
18
15
 
19
- function resetPageState() {
20
- injectedSlots = 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;
@@ -59,22 +67,22 @@ function isCategoryTopicListPage() {
59
67
 
60
68
  function getTopicPosts() {
61
69
  const $primary = $('[component="post"][data-pid]');
62
- if ($primary.length) return $primary.not('.ezoic-ad-post');
70
+ if ($primary.length) return $primary;
63
71
 
72
+ // fallback: top-level with post/content
64
73
  return $('[data-pid]').filter(function () {
65
74
  const $el = $(this);
66
75
  const hasContent = $el.find('[component="post/content"]').length > 0;
67
76
  const nested = $el.parents('[data-pid]').length > 0;
68
77
  return hasContent && !nested;
69
- }).not('.ezoic-ad-post');
78
+ });
70
79
  }
71
80
 
72
81
  function getCategoryTopicItems() {
73
- return $('li[component="category/topic"]').not('.ezoic-ad-topic');
82
+ return $('li[component="category/topic"]');
74
83
  }
75
84
 
76
- // IMPORTANT: Use the SAME tag type as the surrounding list container.
77
- // If posts/topics are in a <ul>/<ol>, the wrapper MUST be a <li> or the browser may move it outside the list (often to the top).
85
+ // If target's parent is UL/OL, wrapper MUST be LI (otherwise browser may move it to top)
78
86
  function wrapperTagFor($target) {
79
87
  if (!$target || !$target.length) return 'div';
80
88
  const parentTag = ($target.parent().prop('tagName') || '').toUpperCase();
@@ -93,8 +101,8 @@ function makeWrapperLike($target, classes, innerHtml, attrs) {
93
101
  return '<div class="' + classes + '"' + attrStr + '>' + innerHtml + '</div>';
94
102
  }
95
103
 
96
- function cleanupWrappersOnNav() {
97
- $('.ezoic-ad-post, .ezoic-ad-between, .ezoic-ad-topic').remove();
104
+ function cleanupOnNav() {
105
+ $('.ezoic-ad-post, .ezoic-ad-topic, .ezoic-ad-between').remove();
98
106
  }
99
107
 
100
108
  function pickNextId(pool) {
@@ -104,15 +112,29 @@ function pickNextId(pool) {
104
112
  return null;
105
113
  }
106
114
 
107
- function evictOldestOne() {
108
- const old = adsFIFO.shift();
115
+ function removeOldestTopicAd() {
116
+ fifo.sort((a, b) => a.afterPostNo - b.afterPostNo);
117
+ const old = fifo.shift();
118
+ if (!old) return false;
119
+
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();
109
132
  if (!old) return false;
110
133
 
111
- const sel = '[data-ezoic-slot="' + old.slot + '"][data-ezoic-id="' + old.id + '"]';
134
+ const sel = '.ezoic-ad-topic[data-ezoic-after="' + old.afterPos + '"][data-ezoic-id="' + old.id + '"]';
112
135
  const $el = $(sel);
113
136
  if ($el.length) $el.remove();
114
137
 
115
- injectedSlots.delete(old.slot);
116
138
  usedIds.delete(old.id);
117
139
  return true;
118
140
  }
@@ -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
+ }
151
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
+ }
186
+
187
+ function injectTopicMessageAds($posts, pool, interval) {
152
188
  const newIds = [];
153
189
 
154
- for (let slot = 1; slot <= maxSlot; slot++) {
155
- if (injectedSlots.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,
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
- injectedSlots.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 (injectedSlots.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
- const inner = '<div class="content"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
242
+ const placeholder = '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>';
209
243
  const html = makeWrapperLike(
210
- $target,
211
- 'post ezoic-ad-post',
212
- inner,
213
- '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 + '"'
214
248
  );
215
249
 
216
- $target.after(html);
250
+ $it.after(html);
217
251
 
218
- injectedSlots.add(slot);
252
+ seenAfterTopicPos.add(pos);
219
253
  usedIds.add(id);
220
- adsFIFO.push({ slot: slot, id: id });
254
+ fifoCat.push({ afterPos: pos, id: id });
221
255
  newIds.push(id);
222
- }
256
+ });
223
257
 
224
258
  return newIds;
225
259
  }
226
260
 
227
261
  async function refreshAds() {
228
- // Reset state on navigation
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,27 +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
- // Your rule:
260
- // - Category topic list: BETWEEN only
261
- // - Topic page: MESSAGE only
262
- if ($topicItems.length) {
263
- if (cfg.enableBetweenAds && betweenPool.length) {
264
- 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));
265
291
  }
266
292
  callEzoic(newIds);
267
293
  return;
268
294
  }
269
295
 
270
- if ($posts.length) {
271
- if (cfg.enableMessageAds && messagePool.length) {
272
- 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));
273
300
  }
274
301
  callEzoic(newIds);
275
302
  }
@@ -277,16 +304,16 @@ async function refreshAds() {
277
304
  inFlight = false;
278
305
  if (rerunRequested) {
279
306
  rerunRequested = false;
280
- setTimeout(refreshAds, 120);
307
+ setTimeout(refreshAds, 160);
281
308
  }
282
309
  }
283
310
  }
284
311
 
285
312
  function debounceRefresh() {
286
313
  clearTimeout(debounceTimer);
287
- debounceTimer = setTimeout(refreshAds, 180);
314
+ debounceTimer = setTimeout(refreshAds, 220);
288
315
  }
289
316
 
290
317
  $(document).ready(debounceRefresh);
291
318
  $(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded', debounceRefresh);
292
- setTimeout(debounceRefresh, 1800);
319
+ setTimeout(debounceRefresh, 2200);
@@ -0,0 +1,2 @@
1
+ .ezoic-ad-post{margin:0.75rem 0;}
2
+ .ezoic-ad-message-inner{padding:0.75rem 0;}