nodebb-plugin-ezoic-infinite 0.8.7 → 0.9.0
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/plugin.json +3 -0
- package/public/client.js +287 -409
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/client.js
CHANGED
|
@@ -3,61 +3,70 @@
|
|
|
3
3
|
|
|
4
4
|
window.ezoicInfiniteLoaded = true;
|
|
5
5
|
|
|
6
|
-
let cachedConfig;
|
|
6
|
+
let cachedConfig = null;
|
|
7
7
|
let lastFetch = 0;
|
|
8
|
-
let debounceTimer;
|
|
9
|
-
|
|
10
8
|
let inFlight = false;
|
|
11
9
|
let rerunRequested = false;
|
|
10
|
+
let debounceTimer = null;
|
|
12
11
|
|
|
13
|
-
// Per-page state
|
|
14
12
|
let pageKey = null;
|
|
15
13
|
|
|
16
|
-
//
|
|
17
|
-
let
|
|
18
|
-
let
|
|
19
|
-
let fifo = []; // [{afterPostNo, id}]
|
|
14
|
+
// Separate state per "mode" so category and topic don't leak ids
|
|
15
|
+
let usedBetween = new Set();
|
|
16
|
+
let usedMessages = new Set();
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
let
|
|
23
|
-
let
|
|
18
|
+
let seenBetweenAfter = new Set(); // category: after absolute topic position
|
|
19
|
+
let fifoBetween = []; // [{afterPos, id}]
|
|
20
|
+
let seenMsgAfter = new Set(); // topic: after absolute post number
|
|
21
|
+
let fifoMsg = []; // [{afterPostNo, id}]
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
usedIdsMsg = new Set();
|
|
29
|
-
usedIdsBetween = new Set();
|
|
30
|
-
fifo = [];
|
|
31
|
-
fifoCat = [];
|
|
32
|
-
}
|
|
23
|
+
// Destroy spam guard
|
|
24
|
+
window.__ezoicLastDestroy = window.__ezoicLastDestroy || {};
|
|
25
|
+
window.__ezoicRecycling = false;
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
} catch (e) {}
|
|
41
|
-
return window.location.pathname;
|
|
42
|
-
}
|
|
27
|
+
// ---------- Config ----------
|
|
28
|
+
async function fetchConfig() {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (cachedConfig && (now - lastFetch) < 5000) return cachedConfig;
|
|
31
|
+
lastFetch = now;
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch('/api/admin/settings/ezoic-infinite', { credentials: 'same-origin' });
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
cachedConfig = {
|
|
37
|
+
excluded: !!data.excluded,
|
|
38
|
+
enableBetweenAds: data.enableBetweenAds !== false,
|
|
39
|
+
placeholderIds: String(data.placeholderIds || '').trim(),
|
|
40
|
+
intervalPosts: parseInt(data.intervalPosts, 10) || 6,
|
|
41
|
+
|
|
42
|
+
enableMessageAds: data.enableMessageAds !== false,
|
|
43
|
+
messagePlaceholderIds: String(data.messagePlaceholderIds || '').trim(),
|
|
44
|
+
messageIntervalPosts: parseInt(data.messageIntervalPosts, 10) || 3,
|
|
45
|
+
};
|
|
46
|
+
return cachedConfig;
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return cachedConfig || {
|
|
49
|
+
excluded: false,
|
|
50
|
+
enableBetweenAds: true,
|
|
51
|
+
placeholderIds: '',
|
|
52
|
+
intervalPosts: 6,
|
|
53
|
+
enableMessageAds: true,
|
|
54
|
+
messagePlaceholderIds: '',
|
|
55
|
+
messageIntervalPosts: 3,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
function parsePool(text) {
|
|
61
|
+
return String(text || '')
|
|
62
|
+
.split(/\r?\n|,|;/g)
|
|
63
|
+
.map(s => s.trim())
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.map(s => parseInt(s, 10))
|
|
66
|
+
.filter(n => Number.isFinite(n) && n > 0);
|
|
59
67
|
}
|
|
60
68
|
|
|
69
|
+
// ---------- Page detection ----------
|
|
61
70
|
function isTopicPage() {
|
|
62
71
|
try {
|
|
63
72
|
if (ajaxify && ajaxify.data && ajaxify.data.tid) return true;
|
|
@@ -66,246 +75,100 @@ function isTopicPage() {
|
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
function isCategoryTopicListPage() {
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function getTopicPosts() {
|
|
73
|
-
const $primary = $('[component="post"][data-pid]');
|
|
74
|
-
if ($primary.length) return $primary;
|
|
75
|
-
|
|
76
|
-
// Fallback: top-level nodes with post/content (avoid nested)
|
|
77
|
-
return $('[data-pid]').filter(function () {
|
|
78
|
-
const $el = $(this);
|
|
79
|
-
const hasContent = $el.find('[component="post/content"]').length > 0;
|
|
80
|
-
const nested = $el.parents('[data-pid]').length > 0;
|
|
81
|
-
return hasContent && !nested;
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function getCategoryTopicItems() {
|
|
86
|
-
return $('li[component="category/topic"]');
|
|
78
|
+
return document.querySelectorAll('li[component="category/topic"]').length > 0;
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
81
|
+
function getPageKey() {
|
|
82
|
+
try {
|
|
83
|
+
if (ajaxify && ajaxify.data) {
|
|
84
|
+
if (ajaxify.data.tid) return `topic:${ajaxify.data.tid}`;
|
|
85
|
+
if (ajaxify.data.cid) return `category:${ajaxify.data.cid}`;
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {}
|
|
88
|
+
return `path:${window.location.pathname}`;
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
function resetState() {
|
|
92
|
+
usedBetween = new Set();
|
|
93
|
+
usedMessages = new Set();
|
|
94
|
+
seenBetweenAfter = new Set();
|
|
95
|
+
seenMsgAfter = new Set();
|
|
96
|
+
fifoBetween = [];
|
|
97
|
+
fifoMsg = [];
|
|
98
|
+
window.__ezoicLastShowKey = null;
|
|
99
|
+
window.__ezoicLastShowAt = 0;
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
function cleanupOnNav() {
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function pickNextId(pool, usedSet) {
|
|
113
|
-
for (const id of pool) {
|
|
114
|
-
if (!usedSet.has(id)) return id;
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
} finally {
|
|
118
|
-
window.__ezoicRecycling = false;
|
|
119
|
-
}
|
|
103
|
+
document.querySelectorAll('.ezoic-ad-post, .ezoic-ad-topic, .ezoic-ad').forEach(el => el.remove());
|
|
120
104
|
}
|
|
121
105
|
|
|
106
|
+
// ---------- Ezoic helpers ----------
|
|
122
107
|
function destroyEzoicId(id) {
|
|
123
|
-
window.__ezoicLastDestroy = window.__ezoicLastDestroy || {};
|
|
124
108
|
const now = Date.now();
|
|
125
|
-
if (window.__ezoicLastDestroy[id] && now - window.__ezoicLastDestroy[id] < 2000)
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
109
|
+
if (window.__ezoicLastDestroy[id] && (now - window.__ezoicLastDestroy[id]) < 2000) return;
|
|
128
110
|
window.__ezoicLastDestroy[id] = now;
|
|
111
|
+
|
|
129
112
|
try {
|
|
130
113
|
window.ezstandalone = window.ezstandalone || {};
|
|
114
|
+
if (typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
115
|
+
window.ezstandalone.destroyPlaceholders(id);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
131
118
|
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
132
|
-
|
|
119
|
+
window.ezstandalone.cmd.push(function () {
|
|
133
120
|
try {
|
|
134
121
|
if (typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
135
122
|
window.ezstandalone.destroyPlaceholders(id);
|
|
136
123
|
}
|
|
137
124
|
} catch (e) {}
|
|
138
|
-
};
|
|
139
|
-
if (typeof window.ezstandalone.destroyPlaceholders === 'function') fn();
|
|
140
|
-
else window.ezstandalone.cmd.push(fn);
|
|
125
|
+
});
|
|
141
126
|
} catch (e) {}
|
|
142
127
|
}
|
|
143
128
|
|
|
144
|
-
|
|
145
|
-
if (window.__ezoicRecycling) return null;
|
|
146
|
-
window.__ezoicRecycling = true;
|
|
147
|
-
try {
|
|
148
|
-
if (!fifo.length) return null;
|
|
149
|
-
fifo.sort((a, b) => a.afterPostNo - b.afterPostNo);
|
|
150
|
-
|
|
151
|
-
while (fifo.length) {
|
|
152
|
-
const old = fifo.shift();
|
|
153
|
-
const sel = '.ezoic-ad-post[data-ezoic-after="' + old.afterPostNo + '"][data-ezoic-id="' + old.id + '"]';
|
|
154
|
-
const $el = $(sel);
|
|
155
|
-
if (!$el.length) continue;
|
|
156
|
-
|
|
157
|
-
// Never recycle the ad that is right after the last real post (sentinel)
|
|
158
|
-
try {
|
|
159
|
-
const $last = $posts.last();
|
|
160
|
-
if ($last.length && $el.prev().is($last)) {
|
|
161
|
-
fifo.push(old);
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
} catch (e) {}
|
|
165
|
-
|
|
166
|
-
$el.remove();
|
|
167
|
-
usedIdsMsg.delete(old.id);
|
|
168
|
-
destroyEzoicId(old.id);
|
|
169
|
-
return old.id;
|
|
170
|
-
}
|
|
171
|
-
return null;
|
|
172
|
-
} finally {
|
|
173
|
-
window.__ezoicRecycling = false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function recycleOldestCategoryId($items) {
|
|
178
|
-
if (window.__ezoicRecycling) return null;
|
|
179
|
-
window.__ezoicRecycling = true;
|
|
180
|
-
try {
|
|
181
|
-
if (!fifoCat.length) return null;
|
|
182
|
-
fifoCat.sort((a, b) => a.afterPos - b.afterPos);
|
|
183
|
-
|
|
184
|
-
while (fifoCat.length) {
|
|
185
|
-
const old = fifoCat.shift();
|
|
186
|
-
const sel = '.ezoic-ad-topic[data-ezoic-after="' + old.afterPos + '"][data-ezoic-id="' + old.id + '"]';
|
|
187
|
-
const $el = $(sel);
|
|
188
|
-
if (!$el.length) continue;
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
const $last = $items.last();
|
|
192
|
-
if ($last.length && $el.prev().is($last)) {
|
|
193
|
-
fifoCat.push(old);
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
} catch (e) {}
|
|
197
|
-
|
|
198
|
-
$el.remove();
|
|
199
|
-
usedIdsBetween.delete(old.id);
|
|
200
|
-
destroyEzoicId(old.id);
|
|
201
|
-
return old.id;
|
|
202
|
-
}
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
|
|
129
|
+
// Auto-height: hide until filled
|
|
206
130
|
function setupAdAutoHeight() {
|
|
207
131
|
if (window.__ezoicAutoHeightAttached) return;
|
|
208
132
|
window.__ezoicAutoHeightAttached = true;
|
|
209
133
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
wrap.
|
|
218
|
-
wrap.style.
|
|
219
|
-
}
|
|
134
|
+
const attach = function () {
|
|
135
|
+
document.querySelectorAll('.ezoic-ad').forEach(function (wrap) {
|
|
136
|
+
if (!wrap.classList.contains('ezoic-filled')) {
|
|
137
|
+
wrap.style.display = 'none';
|
|
138
|
+
}
|
|
139
|
+
const ph = wrap.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
|
|
140
|
+
if (ph && ph.children && ph.children.length) {
|
|
141
|
+
wrap.classList.add('ezoic-filled');
|
|
142
|
+
wrap.style.display = '';
|
|
143
|
+
}
|
|
220
144
|
});
|
|
145
|
+
};
|
|
221
146
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
if (wrap.__ezoicRO) return;
|
|
229
|
-
wrap.__ezoicRO = true;
|
|
230
|
-
ro.observe(wrap);
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
attach();
|
|
235
|
-
setTimeout(attach, 600);
|
|
236
|
-
setTimeout(attach, 1600);
|
|
237
|
-
} catch (e) {}
|
|
147
|
+
attach();
|
|
148
|
+
setTimeout(attach, 500);
|
|
149
|
+
setTimeout(attach, 1500);
|
|
150
|
+
setTimeout(attach, 3000);
|
|
151
|
+
setInterval(attach, 1000);
|
|
238
152
|
}
|
|
239
153
|
|
|
240
154
|
function callEzoic(ids) {
|
|
241
155
|
window.ezstandalone = window.ezstandalone || {};
|
|
242
156
|
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
243
157
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
list.forEach(function (id) {
|
|
247
|
-
const sel = '.ezoic-ad[data-ezoic-id="' + id + '"]';
|
|
248
|
-
const wraps = document.querySelectorAll(sel);
|
|
249
|
-
let anyUnrendered = false;
|
|
250
|
-
wraps.forEach(function (w) {
|
|
251
|
-
if (w.getAttribute('data-ezoic-rendered') !== '1') {
|
|
252
|
-
anyUnrendered = true;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
if (!wraps.length) {
|
|
256
|
-
// fallback: if wrapper is missing, allow showAds (Ezoic might still handle placeholder)
|
|
257
|
-
out.push(id);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
if (anyUnrendered) {
|
|
261
|
-
wraps.forEach(function (w) { w.setAttribute('data-ezoic-rendered', '1'); });
|
|
262
|
-
out.push(id);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
return out;
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
const collect = function () {
|
|
269
|
-
const list = [];
|
|
270
|
-
document.querySelectorAll('.ezoic-ad').forEach(function (wrap) {
|
|
271
|
-
// Collapse until content is injected
|
|
272
|
-
if (!wrap.classList.contains('ezoic-filled')) {
|
|
273
|
-
wrap.style.display = 'none';
|
|
274
|
-
}
|
|
275
|
-
if (wrap.getAttribute('data-ezoic-rendered') === '1') return;
|
|
276
|
-
const ph = wrap.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
|
|
277
|
-
if (ph && ph.children && ph.children.length) {
|
|
278
|
-
// Content injected
|
|
279
|
-
wrap.classList.add('ezoic-filled');
|
|
280
|
-
wrap.style.display = '';
|
|
281
|
-
}
|
|
282
|
-
if (!ph) return;
|
|
283
|
-
const idStr = ph.id.replace('ezoic-pub-ad-placeholder-', '');
|
|
284
|
-
const id = parseInt(idStr, 10);
|
|
285
|
-
if (!Number.isFinite(id) || id <= 0) return;
|
|
286
|
-
wrap.setAttribute('data-ezoic-rendered', '1');
|
|
287
|
-
list.push(id);
|
|
288
|
-
});
|
|
289
|
-
return Array.from(new Set(list));
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const input = (ids && ids.length) ? Array.from(new Set(ids)) : null;
|
|
293
|
-
const toShow = input ? markAndFilterByIds(input) : collect();
|
|
294
|
-
if (!toShow.length) return;
|
|
158
|
+
const uniq = Array.from(new Set(ids || []));
|
|
159
|
+
if (!uniq.length) return;
|
|
295
160
|
|
|
296
|
-
// De-dupe rapid
|
|
297
|
-
const key =
|
|
161
|
+
// De-dupe rapid duplicates
|
|
162
|
+
const key = uniq.join(',');
|
|
298
163
|
const now = Date.now();
|
|
299
|
-
if (window.__ezoicLastShowKey === key && (now - (window.__ezoicLastShowAt || 0)) < 1200)
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
164
|
+
if (window.__ezoicLastShowKey === key && (now - (window.__ezoicLastShowAt || 0)) < 1200) return;
|
|
302
165
|
window.__ezoicLastShowKey = key;
|
|
303
166
|
window.__ezoicLastShowAt = now;
|
|
304
167
|
|
|
305
168
|
const run = function () {
|
|
306
169
|
try {
|
|
307
170
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
308
|
-
window.ezstandalone.showAds.apply(window.ezstandalone,
|
|
171
|
+
window.ezstandalone.showAds.apply(window.ezstandalone, uniq);
|
|
309
172
|
return true;
|
|
310
173
|
}
|
|
311
174
|
} catch (e) {}
|
|
@@ -316,230 +179,237 @@ function callEzoic(ids) {
|
|
|
316
179
|
window.ezstandalone.cmd.push(function () { run(); });
|
|
317
180
|
|
|
318
181
|
// Retry only if showAds isn't available yet
|
|
319
|
-
let tries = 0;
|
|
320
|
-
const maxTries = 6;
|
|
321
|
-
const retry = function () {
|
|
322
|
-
tries++;
|
|
323
|
-
const ok = run();
|
|
324
|
-
if (ok) return;
|
|
325
|
-
try {
|
|
326
|
-
if (typeof window.ezstandalone.showAds === 'function') return;
|
|
327
|
-
} catch (e) {}
|
|
328
|
-
if (tries < maxTries) setTimeout(retry, 800);
|
|
329
|
-
};
|
|
330
|
-
|
|
331
182
|
try {
|
|
332
183
|
if (typeof window.ezstandalone.showAds !== 'function') {
|
|
184
|
+
let tries = 0;
|
|
185
|
+
const retry = function () {
|
|
186
|
+
tries++;
|
|
187
|
+
if (run()) return;
|
|
188
|
+
if (tries < 6) setTimeout(retry, 800);
|
|
189
|
+
};
|
|
333
190
|
setTimeout(retry, 800);
|
|
334
191
|
}
|
|
335
|
-
} catch (e) {
|
|
336
|
-
setTimeout(retry, 800);
|
|
337
|
-
}
|
|
192
|
+
} catch (e) {}
|
|
338
193
|
}
|
|
339
194
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
195
|
+
// ---------- Pool logic ----------
|
|
196
|
+
function pickNextId(pool, usedSet) {
|
|
197
|
+
for (const id of pool) {
|
|
198
|
+
if (!usedSet.has(id)) return id;
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
345
202
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
203
|
+
function recycleOldest(fifo, usedSet, selector, avoidAfterNode) {
|
|
204
|
+
if (window.__ezoicRecycling) return null;
|
|
205
|
+
window.__ezoicRecycling = true;
|
|
206
|
+
try {
|
|
207
|
+
fifo.sort((a, b) => (a.after - b.after));
|
|
208
|
+
while (fifo.length) {
|
|
209
|
+
const old = fifo.shift();
|
|
210
|
+
const el = document.querySelector(selector(old));
|
|
211
|
+
if (!el) continue;
|
|
349
212
|
|
|
350
|
-
|
|
351
|
-
|
|
213
|
+
// Don't recycle if it is right after the last real item (protect sentinel)
|
|
214
|
+
try {
|
|
215
|
+
if (avoidAfterNode && el.previousElementSibling === avoidAfterNode) {
|
|
216
|
+
fifo.push(old);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
} catch (e) {}
|
|
352
220
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
221
|
+
el.remove();
|
|
222
|
+
usedSet.delete(old.id);
|
|
223
|
+
destroyEzoicId(old.id);
|
|
224
|
+
return old.id;
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
} finally {
|
|
228
|
+
window.__ezoicRecycling = false;
|
|
229
|
+
}
|
|
359
230
|
}
|
|
360
231
|
|
|
361
|
-
|
|
362
|
-
|
|
232
|
+
// ---------- Injection ----------
|
|
233
|
+
function injectBetweenAds(config) {
|
|
234
|
+
if (!config.enableBetweenAds) return;
|
|
235
|
+
const pool = parsePool(config.placeholderIds);
|
|
236
|
+
if (!pool.length) return;
|
|
363
237
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if ($p.is($posts.last())) return;
|
|
238
|
+
const interval = Math.max(1, config.intervalPosts);
|
|
239
|
+
const items = Array.from(document.querySelectorAll('li[component="category/topic"]'));
|
|
240
|
+
if (!items.length) return;
|
|
368
241
|
|
|
369
|
-
|
|
370
|
-
|
|
242
|
+
const newIds = [];
|
|
243
|
+
const lastItem = items[items.length - 1];
|
|
371
244
|
|
|
372
|
-
|
|
373
|
-
|
|
245
|
+
for (let i = 0; i < items.length; i++) {
|
|
246
|
+
const pos = i + 1; // absolute position in loaded list
|
|
247
|
+
if (pos % interval !== 0) continue;
|
|
248
|
+
if (seenBetweenAfter.has(pos)) continue;
|
|
374
249
|
|
|
375
|
-
let id = pickNextId(pool,
|
|
250
|
+
let id = pickNextId(pool, usedBetween);
|
|
376
251
|
if (!id) {
|
|
377
|
-
|
|
378
|
-
|
|
252
|
+
// recycle oldest
|
|
253
|
+
id = recycleOldest(
|
|
254
|
+
fifoBetween,
|
|
255
|
+
usedBetween,
|
|
256
|
+
(o) => `.ezoic-ad-topic[data-ezoic-after="${o.after}"][data-ezoic-id="${o.id}"]`,
|
|
257
|
+
lastItem
|
|
258
|
+
);
|
|
259
|
+
if (!id) break;
|
|
379
260
|
}
|
|
380
261
|
|
|
381
|
-
const
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
);
|
|
262
|
+
const anchor = items[i];
|
|
263
|
+
const wrap = document.createElement('li');
|
|
264
|
+
wrap.className = 'ezoic-ad ezoic-ad-topic';
|
|
265
|
+
wrap.setAttribute('data-ezoic-after', String(pos));
|
|
266
|
+
wrap.setAttribute('data-ezoic-id', String(id));
|
|
267
|
+
wrap.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}"></div>`;
|
|
388
268
|
|
|
389
|
-
|
|
269
|
+
anchor.insertAdjacentElement('afterend', wrap);
|
|
390
270
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
271
|
+
seenBetweenAfter.add(pos);
|
|
272
|
+
usedBetween.add(id);
|
|
273
|
+
fifoBetween.push({ after: pos, id });
|
|
394
274
|
newIds.push(id);
|
|
395
|
-
}
|
|
275
|
+
}
|
|
396
276
|
|
|
397
|
-
|
|
277
|
+
if (newIds.length) callEzoic(newIds);
|
|
398
278
|
}
|
|
399
279
|
|
|
400
|
-
function
|
|
401
|
-
|
|
280
|
+
function getPostNumberFromPost(postEl) {
|
|
281
|
+
// Prefer post-index text (#72)
|
|
282
|
+
try {
|
|
283
|
+
const idx = postEl.querySelector('a.post-index');
|
|
284
|
+
if (idx) {
|
|
285
|
+
const n = parseInt(String(idx.textContent || '').replace('#', '').trim(), 10);
|
|
286
|
+
if (Number.isFinite(n)) return n;
|
|
287
|
+
}
|
|
288
|
+
} catch (e) {}
|
|
402
289
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if ($it.is($items.last())) return;
|
|
290
|
+
// Fallback: pid-based ordering not reliable, but DOM order is ok for interval within loaded window.
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
407
293
|
|
|
408
|
-
|
|
409
|
-
|
|
294
|
+
function injectMessageAds(config) {
|
|
295
|
+
if (!config.enableMessageAds) return;
|
|
296
|
+
const pool = parsePool(config.messagePlaceholderIds);
|
|
297
|
+
if (!pool.length) return;
|
|
410
298
|
|
|
411
|
-
|
|
412
|
-
|
|
299
|
+
const interval = Math.max(1, config.messageIntervalPosts);
|
|
300
|
+
const posts = Array.from(document.querySelectorAll('[component="post"][data-pid]'));
|
|
301
|
+
if (!posts.length) return;
|
|
413
302
|
|
|
414
|
-
|
|
303
|
+
const newIds = [];
|
|
304
|
+
const lastPost = posts[posts.length - 1];
|
|
305
|
+
|
|
306
|
+
// Determine absolute post numbers if available; else use DOM position
|
|
307
|
+
let numbers = posts.map((p, i) => ({ el: p, no: getPostNumberFromPost(p) || (i + 1) }));
|
|
308
|
+
// Ensure strictly increasing by DOM
|
|
309
|
+
numbers = numbers.map((x, i) => ({ el: x.el, no: x.no || (i + 1) }));
|
|
310
|
+
|
|
311
|
+
for (const entry of numbers) {
|
|
312
|
+
const afterNo = entry.no;
|
|
313
|
+
if (afterNo % interval !== 0) continue;
|
|
314
|
+
if (seenMsgAfter.has(afterNo)) continue;
|
|
315
|
+
|
|
316
|
+
let id = pickNextId(pool, usedMessages);
|
|
415
317
|
if (!id) {
|
|
416
|
-
id =
|
|
417
|
-
|
|
318
|
+
id = recycleOldest(
|
|
319
|
+
fifoMsg,
|
|
320
|
+
usedMessages,
|
|
321
|
+
(o) => `.ezoic-ad-post[data-ezoic-after="${o.after}"][data-ezoic-id="${o.id}"]`,
|
|
322
|
+
lastPost
|
|
323
|
+
);
|
|
324
|
+
if (!id) break;
|
|
418
325
|
}
|
|
419
326
|
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
'data-ezoic-after="' + pos + '" data-ezoic-id="' + id + '"'
|
|
426
|
-
);
|
|
327
|
+
const wrap = document.createElement('div');
|
|
328
|
+
wrap.className = 'ezoic-ad ezoic-ad-post';
|
|
329
|
+
wrap.setAttribute('data-ezoic-after', String(afterNo));
|
|
330
|
+
wrap.setAttribute('data-ezoic-id', String(id));
|
|
331
|
+
wrap.innerHTML = `<div class="ezoic-ad-message-inner"><div id="ezoic-pub-ad-placeholder-${id}"></div></div>`;
|
|
427
332
|
|
|
428
|
-
|
|
333
|
+
entry.el.insertAdjacentElement('afterend', wrap);
|
|
429
334
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
335
|
+
seenMsgAfter.add(afterNo);
|
|
336
|
+
usedMessages.add(id);
|
|
337
|
+
fifoMsg.push({ after: afterNo, id });
|
|
433
338
|
newIds.push(id);
|
|
434
|
-
}
|
|
339
|
+
}
|
|
435
340
|
|
|
436
|
-
|
|
341
|
+
if (newIds.length) callEzoic(newIds);
|
|
437
342
|
}
|
|
438
343
|
|
|
344
|
+
// ---------- Main refresh ----------
|
|
439
345
|
async function refreshAds() {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const key = getPageKey();
|
|
444
|
-
if (pageKey !== key) {
|
|
445
|
-
pageKey = key;
|
|
446
|
-
resetState();
|
|
447
|
-
cleanupOnNav();
|
|
346
|
+
if (inFlight) {
|
|
347
|
+
rerunRequested = true;
|
|
348
|
+
return;
|
|
448
349
|
}
|
|
449
|
-
|
|
450
|
-
if (inFlight) { rerunRequested = true; return; }
|
|
451
350
|
inFlight = true;
|
|
351
|
+
rerunRequested = false;
|
|
452
352
|
|
|
453
353
|
try {
|
|
454
354
|
const cfg = await fetchConfig();
|
|
455
|
-
if (
|
|
456
|
-
|
|
457
|
-
const betweenPool = parsePool(cfg.placeholderIds);
|
|
458
|
-
const betweenInterval = Math.max(1, parseInt(cfg.intervalPosts, 10) || 6);
|
|
355
|
+
if (cfg.excluded) return;
|
|
459
356
|
|
|
460
|
-
const
|
|
461
|
-
|
|
357
|
+
const key = getPageKey();
|
|
358
|
+
if (key !== pageKey) {
|
|
359
|
+
pageKey = key;
|
|
360
|
+
resetState();
|
|
361
|
+
cleanupOnNav();
|
|
362
|
+
}
|
|
462
363
|
|
|
463
|
-
const hasTopicList = isCategoryTopicListPage();
|
|
464
364
|
const onTopic = isTopicPage();
|
|
465
|
-
const onCategory =
|
|
466
|
-
|
|
467
|
-
const newIds = [];
|
|
365
|
+
const onCategory = isCategoryTopicListPage() && !onTopic;
|
|
468
366
|
|
|
469
|
-
|
|
470
|
-
if (
|
|
471
|
-
const $items = getCategoryTopicItems();
|
|
472
|
-
if (cfg.enableBetweenAds && betweenPool.length && $items.length) {
|
|
473
|
-
newIds.push(...injectCategoryBetweenAds($items, betweenPool, betweenInterval));
|
|
474
|
-
}
|
|
475
|
-
callEzoic(newIds);
|
|
476
|
-
// also ensure any unrendered placeholders are rendered
|
|
477
|
-
callEzoic();
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (onTopic) {
|
|
482
|
-
const $posts = getTopicPosts();
|
|
483
|
-
if (cfg.enableMessageAds && messagePool.length && $posts.length) {
|
|
484
|
-
newIds.push(...injectTopicMessageAds($posts, messagePool, messageInterval));
|
|
485
|
-
}
|
|
486
|
-
callEzoic(newIds);
|
|
487
|
-
}
|
|
367
|
+
if (onCategory) injectBetweenAds(cfg);
|
|
368
|
+
if (onTopic) injectMessageAds(cfg);
|
|
488
369
|
} finally {
|
|
489
370
|
inFlight = false;
|
|
490
|
-
if (rerunRequested)
|
|
491
|
-
rerunRequested = false;
|
|
492
|
-
setTimeout(refreshAds, 160);
|
|
493
|
-
}
|
|
371
|
+
if (rerunRequested) setTimeout(refreshAds, 50);
|
|
494
372
|
}
|
|
495
373
|
}
|
|
496
374
|
|
|
497
375
|
function debounceRefresh() {
|
|
498
|
-
clearTimeout(debounceTimer);
|
|
499
|
-
debounceTimer = setTimeout(refreshAds,
|
|
376
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
377
|
+
debounceTimer = setTimeout(refreshAds, 250);
|
|
500
378
|
}
|
|
501
379
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
if (targetPath === window.location.pathname) {
|
|
514
|
-
return;
|
|
380
|
+
// ---------- Observers / Events ----------
|
|
381
|
+
(function setupTriggers() {
|
|
382
|
+
// Ajaxify navigation: only cleanup when URL changes
|
|
383
|
+
$(window).on('action:ajaxify.start', function (ev, data) {
|
|
384
|
+
try {
|
|
385
|
+
const targetUrl = (data && (data.url || data.href)) ? String(data.url || data.href) : '';
|
|
386
|
+
if (targetUrl) {
|
|
387
|
+
const a = document.createElement('a');
|
|
388
|
+
a.href = targetUrl;
|
|
389
|
+
const targetPath = a.pathname || targetUrl;
|
|
390
|
+
if (targetPath === window.location.pathname) return;
|
|
515
391
|
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
});
|
|
522
|
-
$(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded action:topics.loaded action:category.loaded', debounceRefresh);
|
|
392
|
+
} catch (e) {}
|
|
393
|
+
pageKey = null;
|
|
394
|
+
resetState();
|
|
395
|
+
cleanupOnNav();
|
|
396
|
+
});
|
|
523
397
|
|
|
524
|
-
|
|
398
|
+
$(window).on('action:ajaxify.end action:posts.loaded action:topics.loaded action:topic.loaded action:category.loaded', debounceRefresh);
|
|
525
399
|
|
|
526
|
-
//
|
|
527
|
-
(function setupEzoicObserver() {
|
|
528
|
-
if (window.__ezoicInfiniteObserver) return;
|
|
400
|
+
// MutationObserver (new posts/topics appended)
|
|
529
401
|
try {
|
|
530
|
-
const trigger = function () { debounceRefresh(); };
|
|
531
|
-
|
|
532
402
|
const obs = new MutationObserver(function (mutations) {
|
|
533
403
|
for (const m of mutations) {
|
|
534
|
-
if (!m.addedNodes
|
|
404
|
+
if (!m.addedNodes) continue;
|
|
535
405
|
for (const n of m.addedNodes) {
|
|
536
406
|
if (!n || n.nodeType !== 1) continue;
|
|
537
407
|
if (n.matches && (n.matches('[component="post"][data-pid]') || n.matches('li[component="category/topic"]'))) {
|
|
538
|
-
|
|
408
|
+
debounceRefresh();
|
|
539
409
|
return;
|
|
540
410
|
}
|
|
541
411
|
if (n.querySelector && (n.querySelector('[component="post"][data-pid]') || n.querySelector('li[component="category/topic"]'))) {
|
|
542
|
-
|
|
412
|
+
debounceRefresh();
|
|
543
413
|
return;
|
|
544
414
|
}
|
|
545
415
|
}
|
|
@@ -547,19 +417,27 @@ setTimeout(function () { setupAdAutoHeight(); debounceRefresh(); }, 2200);
|
|
|
547
417
|
});
|
|
548
418
|
obs.observe(document.body, { childList: true, subtree: true });
|
|
549
419
|
window.__ezoicInfiniteObserver = obs;
|
|
550
|
-
|
|
551
|
-
let lastPostCount = 0;
|
|
552
|
-
let lastTopicCount = 0;
|
|
553
|
-
setInterval(function () {
|
|
554
|
-
try {
|
|
555
|
-
const posts = document.querySelectorAll('[component="post"][data-pid]').length;
|
|
556
|
-
const topics = document.querySelectorAll('li[component="category/topic"]').length;
|
|
557
|
-
if (posts !== lastPostCount || topics !== lastTopicCount) {
|
|
558
|
-
lastPostCount = posts;
|
|
559
|
-
lastTopicCount = topics;
|
|
560
|
-
trigger();
|
|
561
|
-
}
|
|
562
|
-
} catch (e) {}
|
|
563
|
-
}, 1500);
|
|
564
420
|
} catch (e) {}
|
|
421
|
+
|
|
422
|
+
// Poller fallback (count changes)
|
|
423
|
+
let lastPosts = 0;
|
|
424
|
+
let lastTopics = 0;
|
|
425
|
+
setInterval(function () {
|
|
426
|
+
const p = document.querySelectorAll('[component="post"][data-pid]').length;
|
|
427
|
+
const t = document.querySelectorAll('li[component="category/topic"]').length;
|
|
428
|
+
if (p !== lastPosts || t !== lastTopics) {
|
|
429
|
+
lastPosts = p; lastTopics = t;
|
|
430
|
+
debounceRefresh();
|
|
431
|
+
}
|
|
432
|
+
}, 1500);
|
|
433
|
+
|
|
434
|
+
// First run(s) - important for hard load
|
|
435
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
436
|
+
debounceRefresh();
|
|
437
|
+
setTimeout(debounceRefresh, 1500);
|
|
438
|
+
setTimeout(debounceRefresh, 5000);
|
|
439
|
+
setTimeout(debounceRefresh, 10000);
|
|
440
|
+
});
|
|
441
|
+
// In case this script loads after DOMContentLoaded
|
|
442
|
+
setTimeout(debounceRefresh, 800);
|
|
565
443
|
})();
|