nodebb-plugin-ezoic-infinite 1.1.6 → 1.2.2
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 +255 -339
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -2,93 +2,81 @@
|
|
|
2
2
|
(function () {
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
const
|
|
5
|
+
// Optional debug switch
|
|
6
|
+
const DEBUG = !!window.__ezoicInfiniteDebug;
|
|
7
|
+
|
|
8
|
+
const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
7
9
|
|
|
8
10
|
const SELECTORS = {
|
|
9
|
-
|
|
11
|
+
topicItem: 'li[component="category/topic"]',
|
|
10
12
|
postItem: '[component="post"][data-pid]',
|
|
11
|
-
postContent: '[component="post/content"]',
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
const WRAP_CLASS = 'ezoic-ad';
|
|
15
16
|
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
16
17
|
|
|
18
|
+
// Prevent “burst” injection: at most N inserts per run per kind
|
|
19
|
+
const MAX_INSERTS_PER_RUN = 2;
|
|
20
|
+
|
|
17
21
|
const state = {
|
|
18
22
|
pageKey: null,
|
|
19
23
|
cfg: null,
|
|
20
24
|
cfgPromise: null,
|
|
21
|
-
// pools and used ids
|
|
22
|
-
usedBetween: new Set(),
|
|
23
|
-
usedMessage: new Set(),
|
|
24
|
-
fifoBetween: [],
|
|
25
|
-
fifoMessage: [],
|
|
26
|
-
// showAds anti-double
|
|
27
|
-
lastShowById: {},
|
|
28
|
-
pendingById: {},
|
|
29
|
-
// retry bookkeeping
|
|
30
|
-
retryCount: {},
|
|
31
|
-
observers: {},
|
|
32
|
-
};
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const ez = window.ezstandalone;
|
|
38
|
-
if (ez.__nodebbEzoicPatched) return;
|
|
39
|
-
if (typeof ez.showAds !== 'function') return;
|
|
26
|
+
// Per-page pools (refilled by recycling)
|
|
27
|
+
poolTopics: [],
|
|
28
|
+
poolPosts: [],
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
// Track inserted ads per page
|
|
31
|
+
usedTopics: new Set(),
|
|
32
|
+
usedPosts: new Set(),
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Split into individual calls to avoid Ezoic loadMore/placeholder issues.
|
|
48
|
-
try {
|
|
49
|
-
if (window.__ezoicInfiniteDebug) {
|
|
50
|
-
console.warn('[ezoic-infinite] showAds(batch) detected. Source stack:', new Error().stack);
|
|
51
|
-
}
|
|
52
|
-
} catch (e) {}
|
|
34
|
+
// Track which anchors we already evaluated to avoid reprocessing everything on each event
|
|
35
|
+
seenTopicAnchors: new WeakSet(),
|
|
36
|
+
seenPostAnchors: new WeakSet(),
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
38
|
+
// FIFO for recycling
|
|
39
|
+
fifoTopics: [],
|
|
40
|
+
fifoPosts: [],
|
|
41
|
+
|
|
42
|
+
// showAds anti-double
|
|
43
|
+
lastShowById: new Map(),
|
|
44
|
+
pendingById: new Set(),
|
|
45
|
+
|
|
46
|
+
// debounce
|
|
47
|
+
scheduled: false,
|
|
48
|
+
timer: null,
|
|
49
|
+
|
|
50
|
+
// observers
|
|
51
|
+
obs: null,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function log(...args) { if (DEBUG) console.log('[ezoic-infinite]', ...args); }
|
|
55
|
+
function warn(...args) { if (DEBUG) console.warn('[ezoic-infinite]', ...args); }
|
|
67
56
|
|
|
68
57
|
function normalizeBool(v) {
|
|
69
58
|
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
70
59
|
}
|
|
71
60
|
|
|
72
|
-
function
|
|
73
|
-
if (!raw) return [];
|
|
74
|
-
const lines = String(raw)
|
|
75
|
-
.split(/\r?\n/)
|
|
76
|
-
.map(s => s.trim())
|
|
77
|
-
.filter(Boolean);
|
|
78
|
-
|
|
79
|
-
const ids = lines
|
|
80
|
-
.map(s => parseInt(s, 10))
|
|
81
|
-
.filter(n => Number.isFinite(n) && n > 0);
|
|
82
|
-
|
|
83
|
-
// unique preserve order
|
|
61
|
+
function uniqInts(lines) {
|
|
84
62
|
const out = [];
|
|
85
63
|
const seen = new Set();
|
|
86
|
-
for (const
|
|
87
|
-
|
|
64
|
+
for (const v of lines) {
|
|
65
|
+
const n = parseInt(v, 10);
|
|
66
|
+
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
67
|
+
seen.add(n);
|
|
68
|
+
out.push(n);
|
|
69
|
+
}
|
|
88
70
|
}
|
|
89
71
|
return out;
|
|
90
72
|
}
|
|
91
73
|
|
|
74
|
+
function parsePool(raw) {
|
|
75
|
+
if (!raw) return [];
|
|
76
|
+
const lines = String(raw).split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
77
|
+
return uniqInts(lines);
|
|
78
|
+
}
|
|
79
|
+
|
|
92
80
|
function getPageKey() {
|
|
93
81
|
try {
|
|
94
82
|
const ax = window.ajaxify;
|
|
@@ -100,47 +88,42 @@
|
|
|
100
88
|
return window.location.pathname;
|
|
101
89
|
}
|
|
102
90
|
|
|
103
|
-
function
|
|
91
|
+
function getKind() {
|
|
104
92
|
const p = window.location.pathname || '';
|
|
105
93
|
if (/^\/topic\//.test(p)) return 'topic';
|
|
106
94
|
if (/^\/category\//.test(p)) return 'category';
|
|
107
|
-
// fallback
|
|
95
|
+
// fallback
|
|
108
96
|
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
109
|
-
if (document.querySelector(SELECTORS.topicListItem)) return 'category';
|
|
110
97
|
return 'category';
|
|
111
98
|
}
|
|
112
99
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
function hasAdImmediatelyAfter(targetEl) {
|
|
118
|
-
if (!targetEl) return false;
|
|
119
|
-
const next = targetEl.nextElementSibling;
|
|
120
|
-
return !!(next && next.classList && next.classList.contains(WRAP_CLASS));
|
|
100
|
+
function hasAdImmediatelyAfter(el) {
|
|
101
|
+
const n = el && el.nextElementSibling;
|
|
102
|
+
return !!(n && n.classList && n.classList.contains(WRAP_CLASS));
|
|
121
103
|
}
|
|
122
104
|
|
|
123
105
|
function buildWrap(id, kind, afterPos) {
|
|
124
106
|
const wrap = document.createElement('div');
|
|
125
107
|
wrap.className = `${WRAP_CLASS} ${kind}`;
|
|
126
108
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
127
|
-
wrap.setAttribute('data-ezoic-kind', kind);
|
|
128
109
|
wrap.style.width = '100%';
|
|
129
|
-
|
|
130
110
|
const ph = document.createElement('div');
|
|
131
111
|
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
132
112
|
wrap.appendChild(ph);
|
|
133
|
-
|
|
134
113
|
return wrap;
|
|
135
114
|
}
|
|
136
115
|
|
|
137
|
-
function insertAfter(
|
|
138
|
-
if (!
|
|
116
|
+
function insertAfter(target, id, kind, afterPos) {
|
|
117
|
+
if (!target || !target.insertAdjacentElement) return null;
|
|
139
118
|
const wrap = buildWrap(id, kind, afterPos);
|
|
140
|
-
|
|
119
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
141
120
|
return wrap;
|
|
142
121
|
}
|
|
143
122
|
|
|
123
|
+
function safeRect(el) {
|
|
124
|
+
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
125
|
+
}
|
|
126
|
+
|
|
144
127
|
function destroyPlaceholder(id) {
|
|
145
128
|
try {
|
|
146
129
|
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
@@ -149,16 +132,45 @@
|
|
|
149
132
|
} catch (e) {}
|
|
150
133
|
}
|
|
151
134
|
|
|
152
|
-
|
|
135
|
+
// Patch ezstandalone.showAds to split batch calls (we NEVER call batch from this plugin)
|
|
136
|
+
function patchShowAds() {
|
|
137
|
+
try {
|
|
138
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
139
|
+
const ez = window.ezstandalone;
|
|
140
|
+
if (ez.__nodebbEzoicPatched) return;
|
|
141
|
+
if (typeof ez.showAds !== 'function') return;
|
|
142
|
+
|
|
143
|
+
ez.__nodebbEzoicPatched = true;
|
|
144
|
+
const orig = ez.showAds;
|
|
145
|
+
|
|
146
|
+
ez.showAds = function patchedShowAds(arg) {
|
|
147
|
+
if (Array.isArray(arg)) {
|
|
148
|
+
try { warn('showAds(batch) detected. Splitting…'); } catch (e) {}
|
|
149
|
+
const ids = uniqInts(arg);
|
|
150
|
+
for (const id of ids) {
|
|
151
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
return orig.apply(ez, arguments);
|
|
156
|
+
};
|
|
157
|
+
} catch (e) {}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function callShowAdsWhenReady(id) {
|
|
153
161
|
if (!id) return;
|
|
154
|
-
|
|
155
|
-
const
|
|
162
|
+
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
const last = state.lastShowById.get(id) || 0;
|
|
165
|
+
if (now - last < 3500) return;
|
|
166
|
+
|
|
167
|
+
const phId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
156
168
|
|
|
157
169
|
const doCall = () => {
|
|
158
170
|
try {
|
|
159
171
|
window.ezstandalone = window.ezstandalone || {};
|
|
160
172
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
161
|
-
state.lastShowById
|
|
173
|
+
state.lastShowById.set(id, Date.now());
|
|
162
174
|
window.ezstandalone.showAds(id);
|
|
163
175
|
return true;
|
|
164
176
|
}
|
|
@@ -166,74 +178,58 @@
|
|
|
166
178
|
return false;
|
|
167
179
|
};
|
|
168
180
|
|
|
169
|
-
// Wait until the placeholder exists in the DOM (critical on ajaxify navigation)
|
|
170
181
|
let attempts = 0;
|
|
171
|
-
(function
|
|
182
|
+
(function waitForPh() {
|
|
172
183
|
attempts += 1;
|
|
173
|
-
|
|
174
|
-
const el = document.getElementById(placeholderId);
|
|
184
|
+
const el = document.getElementById(phId);
|
|
175
185
|
if (el && el.isConnected) {
|
|
176
|
-
const now = Date.now();
|
|
177
|
-
const last = state.lastShowById[key] || 0;
|
|
178
|
-
if (now - last < 4000) return;
|
|
179
|
-
|
|
180
186
|
if (doCall()) return;
|
|
181
187
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
state.pendingById[key] = true;
|
|
188
|
+
if (state.pendingById.has(id)) return;
|
|
189
|
+
state.pendingById.add(id);
|
|
185
190
|
|
|
186
191
|
window.ezstandalone = window.ezstandalone || {};
|
|
187
192
|
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
188
193
|
window.ezstandalone.cmd.push(() => {
|
|
189
194
|
try {
|
|
190
195
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
191
|
-
|
|
192
|
-
state.lastShowById
|
|
196
|
+
state.pendingById.delete(id);
|
|
197
|
+
state.lastShowById.set(id, Date.now());
|
|
193
198
|
window.ezstandalone.showAds(id);
|
|
194
199
|
}
|
|
195
200
|
} catch (e) {}
|
|
196
201
|
});
|
|
197
202
|
|
|
198
|
-
//
|
|
203
|
+
// short retries
|
|
199
204
|
let tries = 0;
|
|
200
205
|
(function tick() {
|
|
201
206
|
tries += 1;
|
|
202
|
-
if (doCall() || tries >=
|
|
203
|
-
if (tries >=
|
|
207
|
+
if (doCall() || tries >= 5) {
|
|
208
|
+
if (tries >= 5) state.pendingById.delete(id);
|
|
204
209
|
return;
|
|
205
210
|
}
|
|
206
|
-
setTimeout(tick,
|
|
211
|
+
setTimeout(tick, 700);
|
|
207
212
|
})();
|
|
208
|
-
|
|
209
213
|
return;
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
if (attempts < 40)
|
|
213
|
-
setTimeout(waitForDom, 50);
|
|
214
|
-
}
|
|
216
|
+
if (attempts < 40) setTimeout(waitForPh, 50);
|
|
215
217
|
})();
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const margin = 1200;
|
|
222
|
-
const vpTop = -margin;
|
|
223
|
-
|
|
224
|
-
fifo.sort((a, b) => a.after - b.after);
|
|
220
|
+
function recycleOne(pool, usedSet, fifo, wrapperSelector) {
|
|
221
|
+
const margin = 1600;
|
|
222
|
+
const topLimit = -margin;
|
|
225
223
|
|
|
226
224
|
for (let i = 0; i < fifo.length; i++) {
|
|
227
225
|
const old = fifo[i];
|
|
228
|
-
const
|
|
229
|
-
if (!
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const r = safeGetRect(el);
|
|
234
|
-
if (r && r.bottom < vpTop) {
|
|
226
|
+
const w = document.querySelector(wrapperSelector(old));
|
|
227
|
+
if (!w) { fifo.splice(i, 1); i--; continue; }
|
|
228
|
+
|
|
229
|
+
const r = safeRect(w);
|
|
230
|
+
if (r && r.bottom < topLimit) {
|
|
235
231
|
fifo.splice(i, 1);
|
|
236
|
-
|
|
232
|
+
w.remove();
|
|
237
233
|
usedSet.delete(old.id);
|
|
238
234
|
destroyPlaceholder(old.id);
|
|
239
235
|
pool.push(old.id);
|
|
@@ -243,47 +239,20 @@
|
|
|
243
239
|
return false;
|
|
244
240
|
}
|
|
245
241
|
|
|
246
|
-
function nextId(
|
|
242
|
+
function nextId(kind) {
|
|
243
|
+
const isTopics = kind === 'between';
|
|
244
|
+
const pool = isTopics ? state.poolTopics : state.poolPosts;
|
|
245
|
+
const used = isTopics ? state.usedTopics : state.usedPosts;
|
|
246
|
+
const fifo = isTopics ? state.fifoTopics : state.fifoPosts;
|
|
247
|
+
const sel = isTopics
|
|
248
|
+
? (old) => `.${WRAP_CLASS}.ezoic-ad-between[data-ezoic-after="${old.after}"]`
|
|
249
|
+
: (old) => `.${WRAP_CLASS}.ezoic-ad-message[data-ezoic-after="${old.after}"]`;
|
|
250
|
+
|
|
247
251
|
if (pool.length) return pool.shift();
|
|
248
|
-
|
|
249
|
-
const recycled = recycleIfNeeded(pool, usedSet, fifo, selectorFn);
|
|
250
|
-
if (recycled && pool.length) return pool.shift();
|
|
252
|
+
if (recycleOne(pool, used, fifo, sel) && pool.length) return pool.shift();
|
|
251
253
|
return null;
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
function getTopicItems() {
|
|
255
|
-
return Array.from(document.querySelectorAll(SELECTORS.topicListItem));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function getPostItems() {
|
|
259
|
-
// NodeBB harmony: component=post with data-pid is stable
|
|
260
|
-
return Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function cleanupForNewPage() {
|
|
264
|
-
state.pageKey = getPageKey();
|
|
265
|
-
state.cfg = null;
|
|
266
|
-
state.cfgPromise = null;
|
|
267
|
-
|
|
268
|
-
state.usedBetween.clear();
|
|
269
|
-
state.usedMessage.clear();
|
|
270
|
-
state.fifoBetween = [];
|
|
271
|
-
state.fifoMessage = [];
|
|
272
|
-
|
|
273
|
-
state.lastShowById = {};
|
|
274
|
-
state.pendingById = {};
|
|
275
|
-
|
|
276
|
-
// Remove injected wrappers
|
|
277
|
-
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
278
|
-
|
|
279
|
-
// Disconnect observers
|
|
280
|
-
Object.values(state.observers).forEach(obs => { try { obs.disconnect(); } catch (e) {} });
|
|
281
|
-
state.observers = {};
|
|
282
|
-
|
|
283
|
-
state.retryCount = {};
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
256
|
async function fetchConfig() {
|
|
288
257
|
if (state.cfg) return state.cfg;
|
|
289
258
|
if (state.cfgPromise) return state.cfgPromise;
|
|
@@ -292,9 +261,8 @@
|
|
|
292
261
|
try {
|
|
293
262
|
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
294
263
|
if (!res.ok) return null;
|
|
295
|
-
|
|
296
|
-
state.cfg
|
|
297
|
-
return cfg;
|
|
264
|
+
state.cfg = await res.json();
|
|
265
|
+
return state.cfg;
|
|
298
266
|
} catch (e) {
|
|
299
267
|
return null;
|
|
300
268
|
} finally {
|
|
@@ -305,259 +273,207 @@
|
|
|
305
273
|
return state.cfgPromise;
|
|
306
274
|
}
|
|
307
275
|
|
|
308
|
-
function
|
|
309
|
-
|
|
310
|
-
if (state.
|
|
311
|
-
|
|
312
|
-
const hasTargets = () => {
|
|
313
|
-
if (kind === 'topic') return document.querySelectorAll(SELECTORS.postItem).length > 0;
|
|
314
|
-
return document.querySelectorAll(SELECTORS.topicListItem).length > 0;
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
if (hasTargets()) {
|
|
318
|
-
cb();
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const obs = new MutationObserver(() => {
|
|
323
|
-
if (hasTargets()) {
|
|
324
|
-
try { obs.disconnect(); } catch (e) {}
|
|
325
|
-
delete state.observers[key];
|
|
326
|
-
cb();
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
state.observers[key] = obs;
|
|
331
|
-
try {
|
|
332
|
-
obs.observe(document.body, { childList: true, subtree: true });
|
|
333
|
-
} catch (e) {}
|
|
334
|
-
|
|
335
|
-
setTimeout(() => {
|
|
336
|
-
try { obs.disconnect(); } catch (e) {}
|
|
337
|
-
delete state.observers[key];
|
|
338
|
-
}, 15000);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function scheduleRetry(kind) {
|
|
342
|
-
const key = `${kind}:${getPageKey()}`;
|
|
343
|
-
state.retryCount[key] = state.retryCount[key] || 0;
|
|
344
|
-
if (state.retryCount[key] >= 24) return;
|
|
345
|
-
state.retryCount[key] += 1;
|
|
346
|
-
setTimeout(run, 250);
|
|
347
|
-
}
|
|
348
|
-
} catch (e) {}
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
state.predeclared[kind].add(key);
|
|
352
|
-
|
|
353
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
354
|
-
call();
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
359
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
360
|
-
window.ezstandalone.cmd.push(call);
|
|
361
|
-
} catch (e) {}
|
|
276
|
+
function initPools(cfg) {
|
|
277
|
+
// Re-init pools once per page
|
|
278
|
+
if (state.poolTopics.length === 0) state.poolTopics = parsePool(cfg.placeholderIds);
|
|
279
|
+
if (state.poolPosts.length === 0) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
|
|
362
280
|
}
|
|
363
281
|
|
|
364
|
-
function
|
|
365
|
-
if (!normalizeBool(cfg.enableBetweenAds)) return;
|
|
282
|
+
function injectTopics(cfg) {
|
|
283
|
+
if (!normalizeBool(cfg.enableBetweenAds)) return 0;
|
|
366
284
|
|
|
367
285
|
const interval = Math.max(1, parseInt(cfg.intervalPosts, 10) || 6);
|
|
368
|
-
const
|
|
286
|
+
const first = normalizeBool(cfg.showFirstTopicAd);
|
|
369
287
|
|
|
370
|
-
const
|
|
371
|
-
if (!
|
|
372
|
-
|
|
373
|
-
const items = getTopicItems();
|
|
374
|
-
if (!items.length) return;
|
|
375
|
-
|
|
376
|
-
for (let idx = 0; idx < items.length; idx++) {
|
|
377
|
-
const li = items[idx];
|
|
378
|
-
const pos = idx + 1;
|
|
288
|
+
const items = Array.from(document.querySelectorAll(SELECTORS.topicItem));
|
|
289
|
+
if (!items.length) return 0;
|
|
379
290
|
|
|
291
|
+
let inserted = 0;
|
|
292
|
+
for (let i = 0; i < items.length; i++) {
|
|
293
|
+
const li = items[i];
|
|
380
294
|
if (!li || !li.isConnected) continue;
|
|
381
295
|
|
|
382
|
-
//
|
|
383
|
-
|
|
296
|
+
// Avoid re-processing anchors already evaluated
|
|
297
|
+
if (state.seenTopicAnchors.has(li)) continue;
|
|
298
|
+
|
|
299
|
+
const pos = i + 1;
|
|
300
|
+
const ok = (first && pos === 1) || (pos % interval === 0);
|
|
301
|
+
state.seenTopicAnchors.add(li);
|
|
384
302
|
if (!ok) continue;
|
|
385
303
|
|
|
386
304
|
if (hasAdImmediatelyAfter(li)) continue;
|
|
387
305
|
|
|
388
|
-
const id = nextId(
|
|
306
|
+
const id = nextId('between');
|
|
389
307
|
if (!id) break;
|
|
390
308
|
|
|
391
|
-
state.
|
|
309
|
+
state.usedTopics.add(id);
|
|
392
310
|
const wrap = insertAfter(li, id, 'ezoic-ad-between', pos);
|
|
393
311
|
if (!wrap) continue;
|
|
394
312
|
|
|
395
|
-
state.
|
|
313
|
+
state.fifoTopics.push({ id, after: pos });
|
|
314
|
+
inserted += 1;
|
|
396
315
|
|
|
397
|
-
|
|
316
|
+
callShowAdsWhenReady(id);
|
|
317
|
+
|
|
318
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
398
319
|
}
|
|
320
|
+
return inserted;
|
|
399
321
|
}
|
|
400
322
|
|
|
401
|
-
function
|
|
402
|
-
if (!normalizeBool(cfg.enableMessageAds)) return;
|
|
323
|
+
function injectPosts(cfg) {
|
|
324
|
+
if (!normalizeBool(cfg.enableMessageAds)) return 0;
|
|
403
325
|
|
|
404
326
|
const interval = Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3);
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
const pool = parsePool(cfg.messagePlaceholderIds);
|
|
408
|
-
if (!pool.length) return;
|
|
327
|
+
const first = normalizeBool(cfg.showFirstMessageAd);
|
|
409
328
|
|
|
410
|
-
const posts =
|
|
411
|
-
if (!posts.length) return;
|
|
329
|
+
const posts = Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
330
|
+
if (!posts.length) return 0;
|
|
412
331
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const
|
|
332
|
+
let inserted = 0;
|
|
333
|
+
for (let i = 0; i < posts.length; i++) {
|
|
334
|
+
const post = posts[i];
|
|
416
335
|
if (!post || !post.isConnected) continue;
|
|
417
336
|
|
|
418
|
-
|
|
337
|
+
if (state.seenPostAnchors.has(post)) continue;
|
|
338
|
+
|
|
339
|
+
const no = i + 1;
|
|
340
|
+
const ok = (first && no === 1) || (no % interval === 0);
|
|
341
|
+
state.seenPostAnchors.add(post);
|
|
419
342
|
if (!ok) continue;
|
|
420
343
|
|
|
421
344
|
if (hasAdImmediatelyAfter(post)) continue;
|
|
422
345
|
|
|
423
|
-
const id = nextId(
|
|
346
|
+
const id = nextId('message');
|
|
424
347
|
if (!id) break;
|
|
425
348
|
|
|
426
|
-
state.
|
|
349
|
+
state.usedPosts.add(id);
|
|
427
350
|
const wrap = insertAfter(post, id, 'ezoic-ad-message', no);
|
|
428
351
|
if (!wrap) continue;
|
|
429
352
|
|
|
430
|
-
state.
|
|
353
|
+
state.fifoPosts.push({ id, after: no });
|
|
354
|
+
inserted += 1;
|
|
355
|
+
|
|
356
|
+
callShowAdsWhenReady(id);
|
|
431
357
|
|
|
432
|
-
|
|
358
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
433
359
|
}
|
|
360
|
+
return inserted;
|
|
434
361
|
}
|
|
435
362
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (scheduled) return;
|
|
441
|
-
scheduled = true;
|
|
442
|
-
|
|
443
|
-
const startKey = getPageKey();
|
|
444
|
-
|
|
445
|
-
const settleAndRun = () => {
|
|
446
|
-
// If page changed, reschedule for new page
|
|
447
|
-
if (getPageKey() !== startKey) { scheduled = false; scheduleRun('page-changed'); return; }
|
|
363
|
+
function cleanup() {
|
|
364
|
+
state.pageKey = getPageKey();
|
|
365
|
+
state.cfg = null;
|
|
366
|
+
state.cfgPromise = null;
|
|
448
367
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
368
|
+
state.poolTopics = [];
|
|
369
|
+
state.poolPosts = [];
|
|
370
|
+
state.usedTopics.clear();
|
|
371
|
+
state.usedPosts.clear();
|
|
452
372
|
|
|
453
|
-
|
|
454
|
-
|
|
373
|
+
state.seenTopicAnchors = new WeakSet();
|
|
374
|
+
state.seenPostAnchors = new WeakSet();
|
|
455
375
|
|
|
456
|
-
|
|
457
|
-
|
|
376
|
+
state.fifoTopics = [];
|
|
377
|
+
state.fifoPosts = [];
|
|
458
378
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
else { stableTicks = 0; lastCount = c; }
|
|
379
|
+
state.lastShowById = new Map();
|
|
380
|
+
state.pendingById = new Set();
|
|
462
381
|
|
|
463
|
-
|
|
464
|
-
scheduled = false;
|
|
465
|
-
run();
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
requestAnimationFrame(step);
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
requestAnimationFrame(step);
|
|
472
|
-
};
|
|
382
|
+
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
473
383
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
384
|
+
if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; }
|
|
385
|
+
state.scheduled = false;
|
|
386
|
+
clearTimeout(state.timer);
|
|
387
|
+
state.timer = null;
|
|
477
388
|
}
|
|
478
389
|
|
|
479
|
-
function
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
});
|
|
485
|
-
});
|
|
390
|
+
function ensureObserver() {
|
|
391
|
+
if (state.obs) return;
|
|
392
|
+
state.obs = new MutationObserver(() => scheduleRun('dom-mutation'));
|
|
393
|
+
try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
394
|
+
setTimeout(() => { if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; } }, 12000);
|
|
486
395
|
}
|
|
487
396
|
|
|
488
|
-
async function
|
|
489
|
-
|
|
490
|
-
const cfg = await fetchConfig();
|
|
491
|
-
if (!cfg || cfg.excluded) return;
|
|
397
|
+
async function runCore() {
|
|
398
|
+
patchShowAds();
|
|
492
399
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
496
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
497
|
-
if (typeof window.ezstandalone.showAds !== 'function') {
|
|
498
|
-
const rk = 'ezoicReadyRerun:' + getPageKey();
|
|
499
|
-
if (!state.retryCount[rk]) {
|
|
500
|
-
state.retryCount[rk] = 1;
|
|
501
|
-
window.ezstandalone.cmd.push(() => { setTimeout(run, 0); });
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
} catch (e) {}
|
|
400
|
+
const cfg = await fetchConfig();
|
|
401
|
+
if (!cfg || cfg.excluded) return;
|
|
505
402
|
|
|
506
|
-
|
|
403
|
+
initPools(cfg);
|
|
507
404
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
} catch (e) {
|
|
518
|
-
// Never break NodeBB UI
|
|
405
|
+
const kind = getKind();
|
|
406
|
+
let inserted = 0;
|
|
407
|
+
|
|
408
|
+
if (kind === 'topic') inserted = injectPosts(cfg);
|
|
409
|
+
else inserted = injectTopics(cfg);
|
|
410
|
+
|
|
411
|
+
// If we inserted max per run, schedule another pass to gradually fill (avoids “burst”)
|
|
412
|
+
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
413
|
+
setTimeout(() => scheduleRun('continue-fill'), 120);
|
|
519
414
|
}
|
|
520
415
|
}
|
|
521
416
|
|
|
522
|
-
function
|
|
523
|
-
if (
|
|
417
|
+
function scheduleRun(reason) {
|
|
418
|
+
if (state.scheduled) return;
|
|
419
|
+
state.scheduled = true;
|
|
420
|
+
|
|
421
|
+
clearTimeout(state.timer);
|
|
422
|
+
state.timer = setTimeout(() => {
|
|
423
|
+
state.scheduled = false;
|
|
424
|
+
// Ensure we're still on same page
|
|
425
|
+
const pk = getPageKey();
|
|
426
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
427
|
+
runCore().catch(() => {});
|
|
428
|
+
}, 80);
|
|
429
|
+
|
|
430
|
+
function bind() {
|
|
431
|
+
if (!$) return;
|
|
524
432
|
|
|
525
|
-
|
|
526
|
-
$w.off('.ezoicInfinite');
|
|
433
|
+
$(window).off('.ezoicInfinite');
|
|
527
434
|
|
|
528
|
-
$
|
|
529
|
-
|
|
435
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => {
|
|
436
|
+
cleanup();
|
|
530
437
|
});
|
|
531
438
|
|
|
532
|
-
$
|
|
533
|
-
|
|
439
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
440
|
+
state.pageKey = getPageKey();
|
|
441
|
+
ensureObserver();
|
|
534
442
|
scheduleRun('ajaxify.end');
|
|
535
443
|
setTimeout(() => scheduleRun('ajaxify.end+250'), 250);
|
|
536
|
-
setTimeout(() => scheduleRun('ajaxify.end+
|
|
444
|
+
setTimeout(() => scheduleRun('ajaxify.end+800'), 800);
|
|
537
445
|
});
|
|
538
446
|
|
|
539
|
-
$
|
|
447
|
+
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
448
|
+
ensureObserver();
|
|
540
449
|
scheduleRun('category.loaded');
|
|
541
|
-
setTimeout(() => scheduleRun('category.loaded+
|
|
542
|
-
setTimeout(() => scheduleRun('category.loaded+700'), 700);
|
|
450
|
+
setTimeout(() => scheduleRun('category.loaded+250'), 250);
|
|
543
451
|
});
|
|
544
452
|
|
|
545
|
-
$
|
|
453
|
+
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
454
|
+
ensureObserver();
|
|
455
|
+
scheduleRun('topics.loaded');
|
|
456
|
+
setTimeout(() => scheduleRun('topics.loaded+150'), 150);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
$(window).on('action:topic.loaded.ezoicInfinite', () => {
|
|
460
|
+
ensureObserver();
|
|
546
461
|
scheduleRun('topic.loaded');
|
|
547
|
-
setTimeout(() => scheduleRun('topic.loaded+
|
|
462
|
+
setTimeout(() => scheduleRun('topic.loaded+200'), 200);
|
|
548
463
|
});
|
|
549
464
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
465
|
+
$(window).on('action:posts.loaded.ezoicInfinite', () => {
|
|
466
|
+
ensureObserver();
|
|
467
|
+
scheduleRun('posts.loaded');
|
|
468
|
+
setTimeout(() => scheduleRun('posts.loaded+150'), 150);
|
|
469
|
+
});
|
|
553
470
|
}
|
|
554
471
|
|
|
555
472
|
// Boot
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
scheduleRun('
|
|
562
|
-
setTimeout(() => scheduleRun('first-load+250'), 250);
|
|
473
|
+
cleanup();
|
|
474
|
+
bind();
|
|
475
|
+
ensureObserver();
|
|
476
|
+
state.pageKey = getPageKey();
|
|
477
|
+
scheduleRun('boot');
|
|
478
|
+
setTimeout(() => scheduleRun('boot+250'), 250);
|
|
563
479
|
})();
|