nodebb-plugin-ezoic-infinite 0.6.2 → 0.6.4
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 +1 -1
- package/public/client.js +148 -87
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -5,13 +5,33 @@ window.ezoicInfiniteLoaded = true;
|
|
|
5
5
|
|
|
6
6
|
let cachedConfig;
|
|
7
7
|
let lastFetch = 0;
|
|
8
|
-
|
|
9
8
|
let debounceTimer;
|
|
10
|
-
let lastSignature = null;
|
|
11
9
|
|
|
12
10
|
let inFlight = false;
|
|
13
11
|
let rerunRequested = false;
|
|
14
12
|
|
|
13
|
+
// Per-page incremental state
|
|
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, selector}] in insertion order (oldest first)
|
|
18
|
+
|
|
19
|
+
function resetPageState() {
|
|
20
|
+
injectedSlots = new Set();
|
|
21
|
+
usedIds = new Set();
|
|
22
|
+
adsFIFO = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function currentPageKey() {
|
|
26
|
+
try {
|
|
27
|
+
if (ajaxify && ajaxify.data) {
|
|
28
|
+
if (ajaxify.data.tid) return 'topic:' + ajaxify.data.tid;
|
|
29
|
+
if (ajaxify.data.cid) return 'category:' + ajaxify.data.cid;
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {}
|
|
32
|
+
return window.location.pathname;
|
|
33
|
+
}
|
|
34
|
+
|
|
15
35
|
function parsePool(raw) {
|
|
16
36
|
if (!raw) return [];
|
|
17
37
|
return Array.from(new Set(
|
|
@@ -29,7 +49,6 @@ async function fetchConfig() {
|
|
|
29
49
|
return cachedConfig;
|
|
30
50
|
}
|
|
31
51
|
|
|
32
|
-
// Robust DOM-based detection (no dependency on ajaxify template timing)
|
|
33
52
|
function isTopicPage() {
|
|
34
53
|
return $('[component="post/content"]').length > 0 || $('[component="post"][data-pid]').length > 0;
|
|
35
54
|
}
|
|
@@ -42,7 +61,6 @@ function getTopicPosts() {
|
|
|
42
61
|
const $primary = $('[component="post"][data-pid]');
|
|
43
62
|
if ($primary.length) return $primary.not('.ezoic-ad-post');
|
|
44
63
|
|
|
45
|
-
// Fallback: top-level data-pid containing post/content
|
|
46
64
|
return $('[data-pid]').filter(function () {
|
|
47
65
|
const $el = $(this);
|
|
48
66
|
const hasContent = $el.find('[component="post/content"]').length > 0;
|
|
@@ -59,83 +77,26 @@ function tagName($el) {
|
|
|
59
77
|
return ($el && $el.length ? (($el.prop('tagName') || '').toUpperCase()) : '');
|
|
60
78
|
}
|
|
61
79
|
|
|
62
|
-
function makeWrapperLike($target, classes, innerHtml) {
|
|
80
|
+
function makeWrapperLike($target, classes, innerHtml, attrs) {
|
|
63
81
|
const t = tagName($target);
|
|
82
|
+
const attrStr = attrs ? ' ' + attrs : '';
|
|
64
83
|
if (t === 'LI') {
|
|
65
|
-
return '<li class="' + classes + ' list-unstyled"
|
|
84
|
+
return '<li class="' + classes + ' list-unstyled"' + attrStr + '>' + innerHtml + '</li>';
|
|
66
85
|
}
|
|
67
|
-
return '<div class="' + classes + '"
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function computeWindowSlots(totalItems, interval, poolSize) {
|
|
71
|
-
const slots = Math.floor(totalItems / interval);
|
|
72
|
-
if (slots <= 0) return [];
|
|
73
|
-
const start = Math.max(1, slots - poolSize + 1);
|
|
74
|
-
const out = [];
|
|
75
|
-
for (let s = start; s <= slots; s++) out.push(s);
|
|
76
|
-
return out;
|
|
86
|
+
return '<div class="' + classes + '"' + attrStr + '>' + innerHtml + '</div>';
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
function
|
|
80
|
-
$('.ezoic-ad-post').remove();
|
|
81
|
-
$('.ezoic-ad-between').remove();
|
|
82
|
-
$('.ezoic-ad-topic').remove();
|
|
89
|
+
function cleanupWrappersOnNav() {
|
|
90
|
+
$('.ezoic-ad-post, .ezoic-ad-between, .ezoic-ad-topic').remove();
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
if (!slotsToRender.length) return [];
|
|
89
|
-
|
|
90
|
-
const activeIds = [];
|
|
91
|
-
for (let i = 0; i < slotsToRender.length; i++) {
|
|
92
|
-
const slotNumber = slotsToRender[i];
|
|
93
|
-
const id = ids[i];
|
|
94
|
-
const index = slotNumber * interval - 1;
|
|
95
|
-
const $target = $items.eq(index);
|
|
96
|
-
if (!$target.length) continue;
|
|
97
|
-
|
|
98
|
-
const html = makeWrapperLike($target, wrapperClass, '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>');
|
|
99
|
-
$target.after(html);
|
|
100
|
-
activeIds.push(id);
|
|
93
|
+
function pickNextId(pool) {
|
|
94
|
+
for (const id of pool) {
|
|
95
|
+
if (!usedIds.has(id)) return id;
|
|
101
96
|
}
|
|
102
|
-
return
|
|
97
|
+
return null;
|
|
103
98
|
}
|
|
104
99
|
|
|
105
|
-
function insertMessageAds($posts, ids, interval) {
|
|
106
|
-
const total = $posts.length;
|
|
107
|
-
const slotsToRender = computeWindowSlots(total, interval, ids.length);
|
|
108
|
-
if (!slotsToRender.length) return [];
|
|
109
|
-
|
|
110
|
-
const activeIds = [];
|
|
111
|
-
for (let i = 0; i < slotsToRender.length; i++) {
|
|
112
|
-
const slotNumber = slotsToRender[i];
|
|
113
|
-
const id = ids[i];
|
|
114
|
-
const index = slotNumber * interval - 1;
|
|
115
|
-
const $target = $posts.eq(index);
|
|
116
|
-
if (!$target.length) continue;
|
|
117
|
-
|
|
118
|
-
const inner = '<div class="content"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
|
|
119
|
-
const html = makeWrapperLike($target, 'post ezoic-ad-post', inner);
|
|
120
|
-
$target.after(html);
|
|
121
|
-
activeIds.push(id);
|
|
122
|
-
}
|
|
123
|
-
return activeIds;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function signatureFor($posts, $topicItems) {
|
|
127
|
-
if ($posts.length) {
|
|
128
|
-
const lastPid = $posts.last().attr('data-pid') || '';
|
|
129
|
-
return 'topic:' + $posts.length + ':' + lastPid;
|
|
130
|
-
}
|
|
131
|
-
if ($topicItems.length) {
|
|
132
|
-
const lastTid = $topicItems.last().attr('data-tid') || '';
|
|
133
|
-
return 'category:' + $topicItems.length + ':' + lastTid;
|
|
134
|
-
}
|
|
135
|
-
return 'none';
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Call Ezoic when ready (retry a few times, non-destructive)
|
|
139
100
|
function callEzoic(ids) {
|
|
140
101
|
if (!ids || !ids.length) return;
|
|
141
102
|
|
|
@@ -152,10 +113,9 @@ function callEzoic(ids) {
|
|
|
152
113
|
return false;
|
|
153
114
|
};
|
|
154
115
|
|
|
155
|
-
// Use cmd queue first (best if Ezoic loads later)
|
|
156
116
|
window.ezstandalone.cmd.push(function () { run(); });
|
|
157
117
|
|
|
158
|
-
//
|
|
118
|
+
// Retry (Ezoic can load late)
|
|
159
119
|
let tries = 0;
|
|
160
120
|
const maxTries = 6;
|
|
161
121
|
const timer = setInterval(function () {
|
|
@@ -164,7 +124,114 @@ function callEzoic(ids) {
|
|
|
164
124
|
}, 800);
|
|
165
125
|
}
|
|
166
126
|
|
|
127
|
+
function fifoEvictIfNeeded(poolSize) {
|
|
128
|
+
// Keep at most poolSize ads in DOM: remove oldest when we exceed pool capacity.
|
|
129
|
+
// This makes ads "follow" the scroll (new ads appear, oldest disappear) without jumping positions.
|
|
130
|
+
while (adsFIFO.length > poolSize) {
|
|
131
|
+
const old = adsFIFO.shift();
|
|
132
|
+
if (!old) break;
|
|
133
|
+
|
|
134
|
+
const $el = $(old.selector);
|
|
135
|
+
if ($el.length) $el.remove();
|
|
136
|
+
|
|
137
|
+
injectedSlots.delete(old.slot);
|
|
138
|
+
usedIds.delete(old.id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function injectBetweenIncremental($items, pool, interval, wrapperClass) {
|
|
143
|
+
const total = $items.length;
|
|
144
|
+
const maxSlot = Math.floor(total / interval);
|
|
145
|
+
if (maxSlot <= 0) return [];
|
|
146
|
+
|
|
147
|
+
const newIds = [];
|
|
148
|
+
|
|
149
|
+
for (let slot = 1; slot <= maxSlot; slot++) {
|
|
150
|
+
if (injectedSlots.has(slot)) continue;
|
|
151
|
+
|
|
152
|
+
const index = slot * interval - 1;
|
|
153
|
+
const $target = $items.eq(index);
|
|
154
|
+
if (!$target.length) continue;
|
|
155
|
+
|
|
156
|
+
const id = pickNextId(pool);
|
|
157
|
+
if (!id) {
|
|
158
|
+
// No free IDs right now; stop. Oldest will be evicted once new ads can be created.
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const placeholder = '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>';
|
|
163
|
+
const selector = '.ezoic-ad-topic[data-ezoic-slot="' + slot + '"][data-ezoic-id="' + id + '"]';
|
|
164
|
+
const html = makeWrapperLike(
|
|
165
|
+
$target,
|
|
166
|
+
wrapperClass,
|
|
167
|
+
placeholder,
|
|
168
|
+
'data-ezoic-slot="' + slot + '" data-ezoic-id="' + id + '"'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
$target.after(html);
|
|
172
|
+
|
|
173
|
+
injectedSlots.add(slot);
|
|
174
|
+
usedIds.add(id);
|
|
175
|
+
adsFIFO.push({ slot: slot, id: id, selector: selector });
|
|
176
|
+
|
|
177
|
+
newIds.push(id);
|
|
178
|
+
|
|
179
|
+
// Enforce capacity immediately (prevents runaway and keeps ads near viewport)
|
|
180
|
+
fifoEvictIfNeeded(pool.length);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return newIds;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function injectMessageIncremental($posts, pool, interval) {
|
|
187
|
+
const total = $posts.length;
|
|
188
|
+
const maxSlot = Math.floor(total / interval);
|
|
189
|
+
if (maxSlot <= 0) return [];
|
|
190
|
+
|
|
191
|
+
const newIds = [];
|
|
192
|
+
|
|
193
|
+
for (let slot = 1; slot <= maxSlot; slot++) {
|
|
194
|
+
if (injectedSlots.has(slot)) continue;
|
|
195
|
+
|
|
196
|
+
const index = slot * interval - 1;
|
|
197
|
+
const $target = $posts.eq(index);
|
|
198
|
+
if (!$target.length) continue;
|
|
199
|
+
|
|
200
|
+
const id = pickNextId(pool);
|
|
201
|
+
if (!id) break;
|
|
202
|
+
|
|
203
|
+
const inner = '<div class="content"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
|
|
204
|
+
const selector = '.ezoic-ad-post[data-ezoic-slot="' + slot + '"][data-ezoic-id="' + id + '"]';
|
|
205
|
+
const html = makeWrapperLike(
|
|
206
|
+
$target,
|
|
207
|
+
'post ezoic-ad-post',
|
|
208
|
+
inner,
|
|
209
|
+
'data-ezoic-slot="' + slot + '" data-ezoic-id="' + id + '"'
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
$target.after(html);
|
|
213
|
+
|
|
214
|
+
injectedSlots.add(slot);
|
|
215
|
+
usedIds.add(id);
|
|
216
|
+
adsFIFO.push({ slot: slot, id: id, selector: selector });
|
|
217
|
+
|
|
218
|
+
newIds.push(id);
|
|
219
|
+
|
|
220
|
+
fifoEvictIfNeeded(pool.length);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return newIds;
|
|
224
|
+
}
|
|
225
|
+
|
|
167
226
|
async function refreshAds() {
|
|
227
|
+
// Reset state on navigation
|
|
228
|
+
const key = currentPageKey();
|
|
229
|
+
if (pageKey !== key) {
|
|
230
|
+
pageKey = key;
|
|
231
|
+
resetPageState();
|
|
232
|
+
cleanupWrappersOnNav();
|
|
233
|
+
}
|
|
234
|
+
|
|
168
235
|
if (inFlight) { rerunRequested = true; return; }
|
|
169
236
|
inFlight = true;
|
|
170
237
|
|
|
@@ -186,45 +253,39 @@ async function refreshAds() {
|
|
|
186
253
|
|
|
187
254
|
if (!$posts.length && !$topicItems.length) return;
|
|
188
255
|
|
|
189
|
-
const
|
|
190
|
-
if (sig === lastSignature) return;
|
|
191
|
-
lastSignature = sig;
|
|
192
|
-
|
|
193
|
-
removeOurWrappers();
|
|
194
|
-
|
|
195
|
-
const activeIds = [];
|
|
256
|
+
const newIds = [];
|
|
196
257
|
|
|
197
258
|
// Your rule:
|
|
198
259
|
// - Category topic list: BETWEEN only
|
|
199
260
|
// - Topic page: MESSAGE only
|
|
200
261
|
if ($topicItems.length) {
|
|
201
262
|
if (cfg.enableBetweenAds && betweenPool.length) {
|
|
202
|
-
|
|
263
|
+
newIds.push(...injectBetweenIncremental($topicItems, betweenPool, betweenInterval, 'ezoic-ad-topic'));
|
|
203
264
|
}
|
|
204
|
-
callEzoic(
|
|
265
|
+
callEzoic(newIds);
|
|
205
266
|
return;
|
|
206
267
|
}
|
|
207
268
|
|
|
208
269
|
if ($posts.length) {
|
|
209
270
|
if (cfg.enableMessageAds && messagePool.length) {
|
|
210
|
-
|
|
271
|
+
newIds.push(...injectMessageIncremental($posts, messagePool, messageInterval));
|
|
211
272
|
}
|
|
212
|
-
callEzoic(
|
|
273
|
+
callEzoic(newIds);
|
|
213
274
|
}
|
|
214
275
|
} finally {
|
|
215
276
|
inFlight = false;
|
|
216
277
|
if (rerunRequested) {
|
|
217
278
|
rerunRequested = false;
|
|
218
|
-
setTimeout(refreshAds,
|
|
279
|
+
setTimeout(refreshAds, 120);
|
|
219
280
|
}
|
|
220
281
|
}
|
|
221
282
|
}
|
|
222
283
|
|
|
223
284
|
function debounceRefresh() {
|
|
224
285
|
clearTimeout(debounceTimer);
|
|
225
|
-
debounceTimer = setTimeout(refreshAds,
|
|
286
|
+
debounceTimer = setTimeout(refreshAds, 180);
|
|
226
287
|
}
|
|
227
288
|
|
|
228
289
|
$(document).ready(debounceRefresh);
|
|
229
290
|
$(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded', debounceRefresh);
|
|
230
|
-
setTimeout(debounceRefresh,
|
|
291
|
+
setTimeout(debounceRefresh, 1800);
|