nodebb-plugin-ezoic-infinite 0.5.3 → 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.3",
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
@@ -1,6 +1,8 @@
1
1
  /* globals ajaxify */
2
2
  'use strict';
3
3
 
4
+ window.ezoicInfiniteLoaded = true;
5
+
4
6
  let cachedConfig;
5
7
  let lastFetch = 0;
6
8
  let debounceTimer;
@@ -23,17 +25,27 @@ function parsePool(raw) {
23
25
  }
24
26
 
25
27
  function getTopicPosts() {
26
- const $p = $('[component="post"]').not('.ezoic-ad-post');
27
- if ($p.length) return $p;
28
+ const $primary = $('[component="post"][data-pid]');
29
+ if ($primary.length) return $primary.not('.ezoic-ad-post');
30
+
31
+ const $top = $('[data-pid]').filter(function () {
32
+ const $el = $(this);
33
+ const hasContent = $el.find('[component="post/content"]').length > 0;
34
+ const nested = $el.parents('[data-pid]').length > 0;
35
+ return hasContent && !nested;
36
+ });
37
+ if ($top.length) return $top.not('.ezoic-ad-post');
38
+
28
39
  return $('.posts .post').not('.ezoic-ad-post');
29
40
  }
30
41
 
31
- function isLi($el) {
32
- return ($el && $el.length && (($el.prop('tagName') || '').toUpperCase() === 'LI'));
42
+ function tagName($el) {
43
+ return ($el && $el.length ? (($el.prop('tagName') || '').toUpperCase()) : '');
33
44
  }
34
45
 
46
+ // Remove *all* nodes with a given id (duplicates are possible if misconfigured)
35
47
  function removePlaceholdersByPool(pool) {
36
- pool.forEach(id => $('#ezoic-pub-ad-placeholder-' + id).remove());
48
+ pool.forEach(id => $('[id="ezoic-pub-ad-placeholder-' + id + '"]').remove());
37
49
  }
38
50
 
39
51
  function removeAdWrappers() {
@@ -50,77 +62,70 @@ function computeWindowSlots(totalItems, interval, poolSize) {
50
62
  return out;
51
63
  }
52
64
 
53
- function makeBetweenWrapper($targetPost, placeholderId) {
54
- if (isLi($targetPost)) {
55
- return (
56
- '<li class="ezoic-ad-between list-unstyled" data-ezoic-ad="1">' +
57
- '<div id="ezoic-pub-ad-placeholder-' + placeholderId + '"></div>' +
58
- '</li>'
59
- );
65
+ function makeWrapperLike($target, classes, innerHtml) {
66
+ const t = tagName($target);
67
+ if (t === 'LI') {
68
+ return '<li class="' + classes + ' list-unstyled" data-ezoic-ad="1">' + innerHtml + '</li>';
60
69
  }
61
- return '<div class="ezoic-ad-between" id="ezoic-pub-ad-placeholder-' + placeholderId + '"></div>';
62
- }
63
-
64
- function makeAdMessageWrapper($targetPost, placeholderId) {
65
- if (isLi($targetPost)) {
66
- return (
67
- '<li class="post ezoic-ad-post" data-ezoic-ad="1">' +
68
- '<div class="content">' +
69
- '<div id="ezoic-pub-ad-placeholder-' + placeholderId + '"></div>' +
70
- '</div>' +
71
- '</li>'
72
- );
73
- }
74
-
75
- return (
76
- '<div class="post ezoic-ad-post" data-ezoic-ad="1">' +
77
- '<div class="content">' +
78
- '<div id="ezoic-pub-ad-placeholder-' + placeholderId + '"></div>' +
79
- '</div>' +
80
- '</div>'
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];
81
+ const id = ids[i];
93
82
  const index = slotNumber * interval - 1;
94
83
  const $target = $posts.eq(index);
95
84
  if (!$target.length) continue;
96
85
 
97
- $target.after(makeBetweenWrapper($target, id));
86
+ const html = makeWrapperLike($target, 'ezoic-ad-between', '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>');
87
+ $target.after(html);
98
88
  activeIds.push(id);
99
89
  }
100
90
  return activeIds;
101
91
  }
102
92
 
103
- function insertAdMessagesBetweenReplies($posts, pool, interval) {
93
+ function insertAdMessagesBetweenReplies($posts, ids, interval) {
104
94
  const total = $posts.length;
105
- const slotsToRender = computeWindowSlots(total, interval, pool.length);
95
+ const slotsToRender = computeWindowSlots(total, interval, ids.length);
106
96
  if (!slotsToRender.length) return [];
107
97
 
108
98
  const activeIds = [];
109
99
  for (let i = 0; i < slotsToRender.length; i++) {
110
100
  const slotNumber = slotsToRender[i];
111
- const id = pool[i];
101
+ const id = ids[i];
112
102
  const index = slotNumber * interval - 1;
113
103
  const $target = $posts.eq(index);
114
104
  if (!$target.length) continue;
115
105
 
116
- $target.after(makeAdMessageWrapper($target, id));
106
+ const inner = '<div class="content"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
107
+ const html = makeWrapperLike($target, 'post ezoic-ad-post', inner);
108
+ $target.after(html);
117
109
  activeIds.push(id);
118
110
  }
119
111
  return activeIds;
120
112
  }
121
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
+
122
126
  async function refreshAds() {
123
- const cfg = await fetchConfig();
127
+ let cfg;
128
+ try { cfg = await fetchConfig(); } catch (e) { return; }
124
129
  if (!cfg || cfg.excluded) return;
125
130
 
126
131
  const betweenPool = parsePool(cfg.placeholderIds);
@@ -132,32 +137,48 @@ async function refreshAds() {
132
137
  const $posts = getTopicPosts();
133
138
  if (!$posts.length) return;
134
139
 
140
+ // Clean first (remove wrappers + any placeholders, even duplicates)
135
141
  removeAdWrappers();
136
- removePlaceholdersByPool(betweenPool);
137
- 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);
138
148
 
139
149
  const activeIds = [];
150
+ let cursor = 0;
140
151
 
141
152
  if (cfg.enableBetweenAds && betweenPool.length) {
142
- 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));
143
156
  }
144
157
 
145
158
  if (cfg.enableMessageAds && messagePool.length) {
146
- 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));
147
162
  }
148
163
 
164
+ // Ezoic render
149
165
  if (activeIds.length && window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
150
166
  try { window.ezstandalone.destroyPlaceholders(); } catch (e) {}
151
167
  }
152
168
  activeIds.forEach(id => {
153
- try { window.ezstandalone && typeof window.ezstandalone.showAds === 'function' && window.ezstandalone.showAds(id); } catch (e) {}
169
+ try {
170
+ if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
171
+ window.ezstandalone.showAds(id);
172
+ }
173
+ } catch (e) {}
154
174
  });
155
175
  }
156
176
 
157
177
  function debounceRefresh() {
158
178
  clearTimeout(debounceTimer);
159
- debounceTimer = setTimeout(refreshAds, 100);
179
+ debounceTimer = setTimeout(refreshAds, 150);
160
180
  }
161
181
 
182
+ $(document).ready(debounceRefresh);
162
183
  $(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded', debounceRefresh);
163
- setTimeout(debounceRefresh, 800);
184
+ setTimeout(debounceRefresh, 1500);
@@ -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">