nodebb-plugin-ezoic-infinite 0.5.4 → 0.5.5

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.5",
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;
@@ -50,8 +43,9 @@ function tagName($el) {
50
43
  return ($el && $el.length ? (($el.prop('tagName') || '').toUpperCase()) : '');
51
44
  }
52
45
 
46
+ // Remove *all* nodes with a given id (duplicates are possible if misconfigured)
53
47
  function removePlaceholdersByPool(pool) {
54
- pool.forEach(id => $('#ezoic-pub-ad-placeholder-' + id).remove());
48
+ pool.forEach(id => $('[id="ezoic-pub-ad-placeholder-' + id + '"]').remove());
55
49
  }
56
50
 
57
51
  function removeAdWrappers() {
@@ -60,13 +54,8 @@ function removeAdWrappers() {
60
54
  }
61
55
 
62
56
  function computeWindowSlots(totalItems, interval, poolSize) {
63
- // totalItems posts -> number of ad slots at positions interval, 2*interval, ...
64
57
  const slots = Math.floor(totalItems / interval);
65
58
  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
59
  const start = Math.max(1, slots - poolSize + 1);
71
60
  const out = [];
72
61
  for (let s = start; s <= slots; s++) out.push(s);
@@ -81,16 +70,16 @@ function makeWrapperLike($target, classes, innerHtml) {
81
70
  return '<div class="' + classes + '" data-ezoic-ad="1">' + innerHtml + '</div>';
82
71
  }
83
72
 
84
- function insertBetweenPosts($posts, pool, interval) {
73
+ function insertBetweenPosts($posts, ids, interval) {
85
74
  const total = $posts.length;
86
- const slotsToRender = computeWindowSlots(total, interval, pool.length);
75
+ const slotsToRender = computeWindowSlots(total, interval, ids.length);
87
76
  if (!slotsToRender.length) return [];
88
77
 
89
78
  const activeIds = [];
90
79
  for (let i = 0; i < slotsToRender.length; i++) {
91
80
  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
81
+ const id = ids[i];
82
+ const index = slotNumber * interval - 1;
94
83
  const $target = $posts.eq(index);
95
84
  if (!$target.length) continue;
96
85
 
@@ -101,15 +90,15 @@ function insertBetweenPosts($posts, pool, interval) {
101
90
  return activeIds;
102
91
  }
103
92
 
104
- function insertAdMessagesBetweenReplies($posts, pool, interval) {
93
+ function insertAdMessagesBetweenReplies($posts, ids, interval) {
105
94
  const total = $posts.length;
106
- const slotsToRender = computeWindowSlots(total, interval, pool.length);
95
+ const slotsToRender = computeWindowSlots(total, interval, ids.length);
107
96
  if (!slotsToRender.length) return [];
108
97
 
109
98
  const activeIds = [];
110
99
  for (let i = 0; i < slotsToRender.length; i++) {
111
100
  const slotNumber = slotsToRender[i];
112
- const id = pool[i];
101
+ const id = ids[i];
113
102
  const index = slotNumber * interval - 1;
114
103
  const $target = $posts.eq(index);
115
104
  if (!$target.length) continue;
@@ -122,6 +111,18 @@ function insertAdMessagesBetweenReplies($posts, pool, interval) {
122
111
  return activeIds;
123
112
  }
124
113
 
114
+ function uniqueConcat(a, b) {
115
+ const seen = new Set();
116
+ const out = [];
117
+ [...a, ...b].forEach((x) => {
118
+ if (!seen.has(x)) {
119
+ seen.add(x);
120
+ out.push(x);
121
+ }
122
+ });
123
+ return out;
124
+ }
125
+
125
126
  async function refreshAds() {
126
127
  let cfg;
127
128
  try { cfg = await fetchConfig(); } catch (e) { return; }
@@ -136,19 +137,28 @@ async function refreshAds() {
136
137
  const $posts = getTopicPosts();
137
138
  if (!$posts.length) return;
138
139
 
139
- // Clean first
140
+ // Clean first (remove wrappers + any placeholders, even duplicates)
140
141
  removeAdWrappers();
141
- removePlaceholdersByPool(betweenPool);
142
- removePlaceholdersByPool(messagePool);
142
+ removePlaceholdersByPool(uniqueConcat(betweenPool, messagePool));
143
+
144
+ // IMPORTANT:
145
+ // IDs must be UNIQUE on the page. If admin reuses the same IDs for both pools,
146
+ // we allocate unique IDs across "between posts" and "message ads" to avoid duplicates.
147
+ const combinedUnique = uniqueConcat(betweenPool, messagePool);
143
148
 
144
149
  const activeIds = [];
150
+ let cursor = 0;
145
151
 
146
152
  if (cfg.enableBetweenAds && betweenPool.length) {
147
- activeIds.push(...insertBetweenPosts($posts, betweenPool, betweenInterval));
153
+ const idsForBetween = combinedUnique.slice(cursor, cursor + betweenPool.length);
154
+ cursor += idsForBetween.length;
155
+ activeIds.push(...insertBetweenPosts($posts, idsForBetween, betweenInterval));
148
156
  }
149
157
 
150
158
  if (cfg.enableMessageAds && messagePool.length) {
151
- activeIds.push(...insertAdMessagesBetweenReplies($posts, messagePool, messageInterval));
159
+ const idsForMessage = combinedUnique.slice(cursor, cursor + messagePool.length);
160
+ cursor += idsForMessage.length;
161
+ activeIds.push(...insertAdMessagesBetweenReplies($posts, idsForMessage, messageInterval));
152
162
  }
153
163
 
154
164
  // Ezoic render
@@ -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">