nodebb-plugin-ezoic-infinite 0.5.4 → 0.5.6

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.5.4",
3
+ "version": "0.5.6",
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
@@ -24,17 +24,10 @@ function parsePool(raw) {
24
24
  ));
25
25
  }
26
26
 
27
- /**
28
- * Harmony: the real post wrapper is usually [component="post"][data-pid]
29
- * We must avoid counting nested nodes like component="post/parent" or other elements
30
- * that can also carry data-pid in some setups.
31
- */
32
27
  function getTopicPosts() {
33
28
  const $primary = $('[component="post"][data-pid]');
34
29
  if ($primary.length) return $primary.not('.ezoic-ad-post');
35
30
 
36
- // Fallback: top-level nodes with data-pid that contain the post content,
37
- // excluding nodes nested inside another data-pid container.
38
31
  const $top = $('[data-pid]').filter(function () {
39
32
  const $el = $(this);
40
33
  const hasContent = $el.find('[component="post/content"]').length > 0;
@@ -46,27 +39,27 @@ function getTopicPosts() {
46
39
  return $('.posts .post').not('.ezoic-ad-post');
47
40
  }
48
41
 
42
+ function getCategoryTopicItems() {
43
+ return $('li[component="category/topic"]').not('.ezoic-ad-topic');
44
+ }
45
+
49
46
  function tagName($el) {
50
47
  return ($el && $el.length ? (($el.prop('tagName') || '').toUpperCase()) : '');
51
48
  }
52
49
 
53
50
  function removePlaceholdersByPool(pool) {
54
- pool.forEach(id => $('#ezoic-pub-ad-placeholder-' + id).remove());
51
+ pool.forEach(id => $('[id="ezoic-pub-ad-placeholder-' + id + '"]').remove());
55
52
  }
56
53
 
57
54
  function removeAdWrappers() {
58
55
  $('.ezoic-ad-post').remove();
59
56
  $('.ezoic-ad-between').remove();
57
+ $('.ezoic-ad-topic').remove();
60
58
  }
61
59
 
62
60
  function computeWindowSlots(totalItems, interval, poolSize) {
63
- // totalItems posts -> number of ad slots at positions interval, 2*interval, ...
64
61
  const slots = Math.floor(totalItems / interval);
65
62
  if (slots <= 0) return [];
66
-
67
- // IMPORTANT:
68
- // We cannot display more than poolSize ads at once because Ezoic placeholder IDs
69
- // must be unique on the page. If slots > poolSize, we only render the latest poolSize slots.
70
63
  const start = Math.max(1, slots - poolSize + 1);
71
64
  const out = [];
72
65
  for (let s = start; s <= slots; s++) out.push(s);
@@ -81,35 +74,35 @@ function makeWrapperLike($target, classes, innerHtml) {
81
74
  return '<div class="' + classes + '" data-ezoic-ad="1">' + innerHtml + '</div>';
82
75
  }
83
76
 
84
- function insertBetweenPosts($posts, pool, interval) {
85
- const total = $posts.length;
86
- const slotsToRender = computeWindowSlots(total, interval, pool.length);
77
+ function insertBetweenGeneric($items, ids, interval, wrapperClass) {
78
+ const total = $items.length;
79
+ const slotsToRender = computeWindowSlots(total, interval, ids.length);
87
80
  if (!slotsToRender.length) return [];
88
81
 
89
82
  const activeIds = [];
90
83
  for (let i = 0; i < slotsToRender.length; i++) {
91
84
  const slotNumber = slotsToRender[i];
92
- const id = pool[i]; // map latest slots to pool in order
93
- const index = slotNumber * interval - 1; // 0-based index of the post after which we insert
94
- const $target = $posts.eq(index);
85
+ const id = ids[i];
86
+ const index = slotNumber * interval - 1;
87
+ const $target = $items.eq(index);
95
88
  if (!$target.length) continue;
96
89
 
97
- const html = makeWrapperLike($target, 'ezoic-ad-between', '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>');
90
+ const html = makeWrapperLike($target, wrapperClass, '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>');
98
91
  $target.after(html);
99
92
  activeIds.push(id);
100
93
  }
101
94
  return activeIds;
102
95
  }
103
96
 
104
- function insertAdMessagesBetweenReplies($posts, pool, interval) {
97
+ function insertAdMessagesBetweenReplies($posts, ids, interval) {
105
98
  const total = $posts.length;
106
- const slotsToRender = computeWindowSlots(total, interval, pool.length);
99
+ const slotsToRender = computeWindowSlots(total, interval, ids.length);
107
100
  if (!slotsToRender.length) return [];
108
101
 
109
102
  const activeIds = [];
110
103
  for (let i = 0; i < slotsToRender.length; i++) {
111
104
  const slotNumber = slotsToRender[i];
112
- const id = pool[i];
105
+ const id = ids[i];
113
106
  const index = slotNumber * interval - 1;
114
107
  const $target = $posts.eq(index);
115
108
  if (!$target.length) continue;
@@ -122,6 +115,18 @@ function insertAdMessagesBetweenReplies($posts, pool, interval) {
122
115
  return activeIds;
123
116
  }
124
117
 
118
+ function uniqueConcat(a, b) {
119
+ const seen = new Set();
120
+ const out = [];
121
+ [...a, ...b].forEach((x) => {
122
+ if (!seen.has(x)) {
123
+ seen.add(x);
124
+ out.push(x);
125
+ }
126
+ });
127
+ return out;
128
+ }
129
+
125
130
  async function refreshAds() {
126
131
  let cfg;
127
132
  try { cfg = await fetchConfig(); } catch (e) { return; }
@@ -134,24 +139,41 @@ async function refreshAds() {
134
139
  const messageInterval = Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3);
135
140
 
136
141
  const $posts = getTopicPosts();
137
- if (!$posts.length) return;
142
+ const $topicItems = getCategoryTopicItems();
143
+
144
+ if (!$posts.length && !$topicItems.length) return;
138
145
 
139
- // Clean first
140
146
  removeAdWrappers();
141
- removePlaceholdersByPool(betweenPool);
142
- removePlaceholdersByPool(messagePool);
147
+ removePlaceholdersByPool(uniqueConcat(betweenPool, messagePool));
148
+
149
+ const combinedUnique = uniqueConcat(betweenPool, messagePool);
143
150
 
144
151
  const activeIds = [];
145
152
 
146
- if (cfg.enableBetweenAds && betweenPool.length) {
147
- activeIds.push(...insertBetweenPosts($posts, betweenPool, betweenInterval));
153
+ // Category topic list page: inject between topic items
154
+ if (!$posts.length && $topicItems.length) {
155
+ if (cfg.enableBetweenAds && betweenPool.length) {
156
+ activeIds.push(...insertBetweenGeneric($topicItems, combinedUnique.slice(0, betweenPool.length), betweenInterval, 'ezoic-ad-topic'));
157
+ }
148
158
  }
149
159
 
150
- if (cfg.enableMessageAds && messagePool.length) {
151
- activeIds.push(...insertAdMessagesBetweenReplies($posts, messagePool, messageInterval));
160
+ // Topic page: inject between replies + message ads
161
+ if ($posts.length) {
162
+ let cursor = 0;
163
+
164
+ if (cfg.enableBetweenAds && betweenPool.length) {
165
+ const idsForBetween = combinedUnique.slice(cursor, cursor + betweenPool.length);
166
+ cursor += idsForBetween.length;
167
+ activeIds.push(...insertBetweenGeneric($posts, idsForBetween, betweenInterval, 'ezoic-ad-between'));
168
+ }
169
+
170
+ if (cfg.enableMessageAds && messagePool.length) {
171
+ const idsForMessage = combinedUnique.slice(cursor, cursor + messagePool.length);
172
+ cursor += idsForMessage.length;
173
+ activeIds.push(...insertAdMessagesBetweenReplies($posts, idsForMessage, messageInterval));
174
+ }
152
175
  }
153
176
 
154
- // Ezoic render
155
177
  if (activeIds.length && window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
156
178
  try { window.ezstandalone.destroyPlaceholders(); } catch (e) {}
157
179
  }
@@ -33,7 +33,7 @@
33
33
  <div class="mb-3">
34
34
  <label class="form-label" for="messagePlaceholderIds">Pool d’IDs Ezoic (message)</label>
35
35
  <textarea id="messagePlaceholderIds" name="messagePlaceholderIds" class="form-control" rows="4">{messagePlaceholderIds}</textarea>
36
- <p class="form-text">Pool séparé recommandé pour éviter la réutilisation d’IDs.</p>
36
+ <p class="form-text">Pool séparé recommandé pour éviter la réutilisation d’IDs. IMPORTANT : ne réutilise pas les mêmes IDs dans les deux pools.</p>
37
37
  </div>
38
38
 
39
39
  <div class="mb-3">