nodebb-plugin-ezoic-infinite 1.4.90 → 1.4.91
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/public/client.js +173 -578
- package/public/public/style.css +3 -5
package/package.json
CHANGED
package/public/public/client.js
CHANGED
|
@@ -11,52 +11,33 @@
|
|
|
11
11
|
|
|
12
12
|
const WRAP_CLASS = 'ezoic-ad';
|
|
13
13
|
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
14
|
-
|
|
15
|
-
const MAX_INSERTS_PER_RUN = 2;
|
|
14
|
+
const MAX_INSERTS_PER_RUN = 3;
|
|
16
15
|
|
|
17
16
|
const state = {
|
|
18
17
|
pageKey: null,
|
|
19
18
|
cfg: null,
|
|
20
19
|
cfgPromise: null,
|
|
21
|
-
|
|
22
20
|
poolTopics: [],
|
|
23
21
|
poolPosts: [],
|
|
24
22
|
poolCategories: [],
|
|
25
|
-
|
|
26
23
|
usedTopics: new Set(),
|
|
27
24
|
usedPosts: new Set(),
|
|
28
25
|
usedCategories: new Set(),
|
|
29
|
-
|
|
30
|
-
// wrappers currently on page (FIFO for recycling)
|
|
31
|
-
liveBetween: [],
|
|
32
|
-
liveMessage: [],
|
|
33
|
-
liveCategory: [],
|
|
34
|
-
usedCategories: new Set(),
|
|
35
|
-
|
|
36
26
|
lastShowById: new Map(),
|
|
37
|
-
|
|
38
|
-
retryById: new Map(),
|
|
39
|
-
retryTimer: null,
|
|
40
|
-
retryQueue: [],
|
|
41
|
-
retryQueueSet: new Set(),
|
|
42
|
-
retryQueueRunning: false,
|
|
43
|
-
badIds: new Set(),
|
|
44
|
-
definedIds: new Set(),
|
|
45
|
-
|
|
27
|
+
canShowAds: false,
|
|
46
28
|
scheduled: false,
|
|
47
29
|
timer: null,
|
|
48
|
-
|
|
49
30
|
obs: null,
|
|
50
|
-
attempts: 0,
|
|
51
31
|
};
|
|
52
32
|
|
|
33
|
+
const sessionDefinedIds = new Set();
|
|
34
|
+
|
|
53
35
|
function normalizeBool(v) {
|
|
54
36
|
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
55
37
|
}
|
|
56
38
|
|
|
57
39
|
function uniqInts(lines) {
|
|
58
|
-
const out = [];
|
|
59
|
-
const seen = new Set();
|
|
40
|
+
const out = [], seen = new Set();
|
|
60
41
|
for (const v of lines) {
|
|
61
42
|
const n = parseInt(v, 10);
|
|
62
43
|
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
@@ -89,10 +70,6 @@
|
|
|
89
70
|
if (/^\/topic\//.test(p)) return 'topic';
|
|
90
71
|
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
91
72
|
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
92
|
-
// fallback by DOM
|
|
93
|
-
if (document.querySelector(SELECTORS.categoryItem)) return 'categories';
|
|
94
|
-
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
95
|
-
if (document.querySelector(SELECTORS.topicItem)) return 'categoryTopics';
|
|
96
73
|
return 'other';
|
|
97
74
|
}
|
|
98
75
|
|
|
@@ -100,120 +77,67 @@
|
|
|
100
77
|
return Array.from(document.querySelectorAll(SELECTORS.topicItem));
|
|
101
78
|
}
|
|
102
79
|
|
|
80
|
+
function getPostContainers() {
|
|
81
|
+
return Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
82
|
+
}
|
|
83
|
+
|
|
103
84
|
function getCategoryItems() {
|
|
104
85
|
return Array.from(document.querySelectorAll(SELECTORS.categoryItem));
|
|
105
86
|
}
|
|
106
87
|
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (!el || !el.isConnected) return false;
|
|
111
|
-
if (!el.querySelector('[component="post/content"]')) return false;
|
|
112
|
-
const parentPost = el.parentElement && el.parentElement.closest('[component="post"][data-pid]');
|
|
113
|
-
if (parentPost && parentPost !== el) return false;
|
|
114
|
-
if (el.getAttribute('component') === 'post/parent') return false;
|
|
115
|
-
return true;
|
|
116
|
-
});
|
|
117
|
-
}
|
|
88
|
+
async function fetchConfig() {
|
|
89
|
+
if (state.cfg) return state.cfg;
|
|
90
|
+
if (state.cfgPromise) return state.cfgPromise;
|
|
118
91
|
|
|
92
|
+
state.cfgPromise = (async () => {
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch('/api/ezoic-infinite/config');
|
|
95
|
+
if (!res.ok) return null;
|
|
96
|
+
const cfg = await res.json();
|
|
97
|
+
state.cfg = cfg;
|
|
98
|
+
return cfg;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
119
103
|
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
return state.cfgPromise;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function initPools(cfg) {
|
|
108
|
+
if (!state.poolTopics.length) state.poolTopics = parsePool(cfg.poolTopics);
|
|
109
|
+
if (!state.poolPosts.length) state.poolPosts = parsePool(cfg.poolPosts);
|
|
110
|
+
if (!state.poolCategories.length) state.poolCategories = parsePool(cfg.poolCategories);
|
|
122
111
|
}
|
|
123
112
|
|
|
124
113
|
function destroyPlaceholderIds(ids) {
|
|
125
114
|
if (!ids || !ids.length) return;
|
|
126
|
-
|
|
127
|
-
const filtered = ids.filter((id) => {
|
|
128
|
-
try { return state.definedIds && state.definedIds.has(id); } catch (e) { return true; }
|
|
129
|
-
});
|
|
115
|
+
const filtered = ids.filter(id => Number.isFinite(id) && id > 0);
|
|
130
116
|
if (!filtered.length) return;
|
|
131
117
|
|
|
132
|
-
const call = () => {
|
|
133
|
-
try {
|
|
134
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
135
|
-
window.ezstandalone.destroyPlaceholders(filtered);
|
|
136
|
-
}
|
|
137
|
-
} catch (e) {}
|
|
138
|
-
};
|
|
139
|
-
try {
|
|
140
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
141
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
142
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
143
|
-
else window.ezstandalone.cmd.push(call);
|
|
144
|
-
} catch (e) {}
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
118
|
try {
|
|
148
119
|
window.ezstandalone = window.ezstandalone || {};
|
|
149
120
|
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
121
|
+
|
|
122
|
+
const call = () => {
|
|
123
|
+
if (typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
124
|
+
window.ezstandalone.destroyPlaceholders(filtered);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
154
127
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (!entry || !entry.wrap || !entry.wrap.isConnected) { liveArr.splice(i, 1); i--; continue; }
|
|
160
|
-
const r = safeRect(entry.wrap);
|
|
161
|
-
if (r && r.bottom < -margin) {
|
|
162
|
-
liveArr.splice(i, 1);
|
|
163
|
-
return entry;
|
|
128
|
+
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
129
|
+
call();
|
|
130
|
+
} else {
|
|
131
|
+
window.ezstandalone.cmd.push(call);
|
|
164
132
|
}
|
|
165
|
-
}
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function moveWrapAfter(wrap, target, kindClass, afterPos) {
|
|
170
|
-
try {
|
|
171
|
-
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
172
|
-
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
173
|
-
target.insertAdjacentElement('afterend', wrap);
|
|
174
|
-
return true;
|
|
175
|
-
} catch (e) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
133
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return { id: null, recycled: null };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
function resetPlaceholderInWrap(wrap, id) {
|
|
189
|
-
try {
|
|
190
|
-
if (!wrap) return;
|
|
191
|
-
try { wrap.removeAttribute('data-ezoic-filled'); } catch (e) {}
|
|
192
|
-
try { if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; } } catch (e) {}
|
|
193
|
-
const old = wrap.querySelector && wrap.querySelector(`#${PLACEHOLDER_PREFIX}${id}`);
|
|
194
|
-
if (old) old.remove();
|
|
195
|
-
// Remove any leftover markup inside wrapper
|
|
196
|
-
wrap.querySelectorAll && wrap.querySelectorAll('iframe, ins').forEach(n => n.remove());
|
|
197
|
-
const ph = document.createElement('div');
|
|
198
|
-
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
199
|
-
wrap.appendChild(ph);
|
|
134
|
+
// Recyclage: libérer après 100ms
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
137
|
+
}, 100);
|
|
200
138
|
} catch (e) {}
|
|
201
139
|
}
|
|
202
140
|
|
|
203
|
-
function isAdjacentAd(target) {
|
|
204
|
-
if (!target || !target.nextElementSibling) return false;
|
|
205
|
-
const next = target.nextElementSibling;
|
|
206
|
-
if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function isPrevAd(target) {
|
|
211
|
-
if (!target || !target.previousElementSibling) return false;
|
|
212
|
-
const prev = target.previousElementSibling;
|
|
213
|
-
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
141
|
function buildWrap(id, kindClass, afterPos) {
|
|
218
142
|
const wrap = document.createElement('div');
|
|
219
143
|
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
@@ -233,404 +157,150 @@
|
|
|
233
157
|
function insertAfter(target, id, kindClass, afterPos) {
|
|
234
158
|
if (!target || !target.insertAdjacentElement) return null;
|
|
235
159
|
if (findWrap(kindClass, afterPos)) return null;
|
|
236
|
-
const wrap = buildWrap(id, kindClass, afterPos);
|
|
237
|
-
target.insertAdjacentElement('afterend', wrap);
|
|
238
|
-
attachFillObserver(wrap, id);
|
|
239
|
-
return wrap;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function destroyUsedPlaceholders() {
|
|
243
|
-
const ids = [];
|
|
244
|
-
try {
|
|
245
|
-
state.usedTopics.forEach((id) => ids.push(id));
|
|
246
|
-
state.usedPosts.forEach((id) => ids.push(id));
|
|
247
|
-
state.usedCategories && state.usedCategories.forEach((id) => ids.push(id));
|
|
248
|
-
} catch (e) {}
|
|
249
|
-
destroyPlaceholderIds(ids);
|
|
250
|
-
}
|
|
251
|
-
} catch (e) {}
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
256
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
257
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
258
|
-
else window.ezstandalone.cmd.push(call);
|
|
259
|
-
} catch (e) {}
|
|
260
|
-
}
|
|
261
160
|
|
|
262
|
-
function patchShowAds() {
|
|
263
|
-
// Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
|
|
264
161
|
try {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
ez.__nodebbEzoicPatched = true;
|
|
271
|
-
const orig = ez.showAds;
|
|
272
|
-
|
|
273
|
-
ez.showAds = function (arg) {
|
|
274
|
-
if (Array.isArray(arg)) {
|
|
275
|
-
const seen = new Set();
|
|
276
|
-
for (const v of arg) {
|
|
277
|
-
const id = parseInt(v, 10);
|
|
278
|
-
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
279
|
-
seen.add(id);
|
|
280
|
-
try { orig.call(ez, id); } catch (e) {}
|
|
281
|
-
}
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
return orig.apply(ez, arguments);
|
|
285
|
-
};
|
|
286
|
-
} catch (e) {}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
function markFilled(wrap) {
|
|
292
|
-
try {
|
|
293
|
-
if (!wrap) return;
|
|
294
|
-
try { wrap.removeAttribute('data-ezoic-filled'); } catch (e) {}
|
|
295
|
-
try { if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; } } catch (e) {}
|
|
296
|
-
wrap.setAttribute('data-ezoic-filled', '1');
|
|
297
|
-
} catch (e) {}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function isWrapMarkedFilled(wrap) {
|
|
301
|
-
try { return wrap && wrap.getAttribute && wrap.getAttribute('data-ezoic-filled') === '1'; } catch (e) { return false; }
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function attachFillObserver(wrap, id) {
|
|
305
|
-
try {
|
|
306
|
-
const ph = wrap && wrap.querySelector && wrap.querySelector(`#${PLACEHOLDER_PREFIX}${id}`);
|
|
307
|
-
if (!ph) return;
|
|
308
|
-
// Already filled?
|
|
309
|
-
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
310
|
-
markFilled(wrap);
|
|
311
|
-
state.definedIds && state.definedIds.add(id);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const obs = new MutationObserver(() => {
|
|
315
|
-
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
316
|
-
markFilled(wrap);
|
|
317
|
-
try { state.definedIds && state.definedIds.add(id); } catch (e) {}
|
|
318
|
-
try { obs.disconnect(); } catch (e) {}
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
obs.observe(ph, { childList: true, subtree: true });
|
|
322
|
-
// Keep a weak reference on the wrapper so we can disconnect on recycle/remove
|
|
323
|
-
wrap.__ezoicFillObs = obs;
|
|
324
|
-
} catch (e) {}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function isPlaceholderFilled(id) {
|
|
328
|
-
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
329
|
-
if (!ph || !ph.isConnected) return false;
|
|
330
|
-
|
|
331
|
-
const wrap = ph.parentElement;
|
|
332
|
-
if (wrap && isWrapMarkedFilled(wrap)) return true;
|
|
333
|
-
|
|
334
|
-
const filled = !!(ph.childNodes && ph.childNodes.length > 0);
|
|
335
|
-
if (filled) {
|
|
336
|
-
try { state.definedIds && state.definedIds.add(id); } catch (e) {}
|
|
337
|
-
try { markFilled(wrap); } catch (e) {}
|
|
162
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
163
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
164
|
+
return wrap;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return null;
|
|
338
167
|
}
|
|
339
|
-
return filled;
|
|
340
|
-
}
|
|
341
|
-
return filled;
|
|
342
168
|
}
|
|
343
169
|
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
170
|
+
function pickId(pool) {
|
|
171
|
+
if (!pool || !pool.length) return null;
|
|
172
|
+
return pool.shift();
|
|
347
173
|
}
|
|
348
174
|
|
|
349
|
-
function
|
|
175
|
+
function callShowAds(id) {
|
|
350
176
|
if (!id) return;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
function processRetryQueue() {
|
|
359
|
-
if (state.retryQueueRunning) return;
|
|
360
|
-
state.retryQueueRunning = true;
|
|
361
|
-
|
|
362
|
-
const step = () => {
|
|
363
|
-
const id = state.retryQueue.shift();
|
|
364
|
-
if (!id) {
|
|
365
|
-
state.retryQueueRunning = false;
|
|
366
|
-
state.badIds = new Set();
|
|
367
|
-
state.definedIds = new Set();
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
state.retryQueueSet.delete(id);
|
|
371
|
-
// If this id was previously attempted and still empty, force a full reset before re-requesting.
|
|
372
|
-
const attempts = (state.retryById.get(id) || 0);
|
|
373
|
-
const phNow = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
374
|
-
const wrapNow = phNow && phNow.parentElement;
|
|
375
|
-
// If Ezoic already defined this id earlier but the placeholder is empty now, we must destroy+reset before re-showAds.
|
|
376
|
-
if (wrapNow && wrapNow.isConnected && state.definedIds && state.definedIds.has(id) && !isPlaceholderFilled(id) && !isWrapMarkedFilled(wrapNow)) {
|
|
377
|
-
destroyPlaceholderIds([id]);
|
|
378
|
-
resetPlaceholderInWrap(wrapNow, id);
|
|
379
|
-
}
|
|
380
|
-
// If this id was previously attempted and still empty, force a full reset before re-requesting.
|
|
381
|
-
if (attempts > 0 && wrapNow && wrapNow.isConnected && !isPlaceholderFilled(id) && !isWrapMarkedFilled(wrapNow)) {
|
|
382
|
-
destroyPlaceholderIds([id]);
|
|
383
|
-
resetPlaceholderInWrap(wrapNow, id);
|
|
384
|
-
}
|
|
385
|
-
callShowAdsWhenReady(id);
|
|
386
|
-
setTimeout(step, 1100);
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
step();
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
function refillUnfilled() {
|
|
394
|
-
const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}`));
|
|
395
|
-
let scheduledAny = false;
|
|
396
|
-
|
|
397
|
-
for (const wrap of wraps) {
|
|
398
|
-
const ph = wrap.querySelector && wrap.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
399
|
-
if (!ph) continue;
|
|
400
|
-
const id = parseInt(ph.id.replace(PLACEHOLDER_PREFIX, ''), 10);
|
|
401
|
-
if (!Number.isFinite(id) || id <= 0) continue;
|
|
402
|
-
|
|
403
|
-
if (isPlaceholderFilled(id)) {
|
|
404
|
-
state.retryById.delete(id);
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
// If wrapper was marked filled, don't try to refill even if placeholder temporarily appears empty.
|
|
408
|
-
if (isWrapMarkedFilled(wrap)) {
|
|
409
|
-
state.retryById.delete(id);
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const tries = (state.retryById.get(id) || 0);
|
|
414
|
-
if (tries >= 8) { state.badIds && state.badIds.add(id); continue; }
|
|
415
|
-
|
|
416
|
-
const r = safeRect(wrap);
|
|
417
|
-
if (r && (r.top > window.innerHeight + 1200 || r.bottom < -1200)) continue;
|
|
418
|
-
|
|
419
|
-
state.retryById.set(id, tries + 1);
|
|
420
|
-
enqueueRetry(id);
|
|
421
|
-
scheduledAny = true;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (scheduledAny) scheduleRefill(700);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function callShowAdsWhenReady(id) {
|
|
428
|
-
if (!id) return;
|
|
429
|
-
|
|
430
|
-
const now = Date.now();
|
|
431
|
-
const last = state.lastShowById.get(id) || 0;
|
|
432
|
-
if (now - last < 3500) return;
|
|
433
|
-
|
|
434
|
-
const phId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
435
|
-
|
|
436
|
-
const doCall = () => {
|
|
437
|
-
try {
|
|
438
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
180
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
181
|
+
|
|
182
|
+
window.ezstandalone.cmd.push(function() {
|
|
439
183
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
440
|
-
state.lastShowById.set(id, Date.now());
|
|
441
184
|
window.ezstandalone.showAds(id);
|
|
442
|
-
|
|
185
|
+
sessionDefinedIds.add(id);
|
|
443
186
|
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
let attempts = 0;
|
|
449
|
-
(function waitForPh() {
|
|
450
|
-
attempts += 1;
|
|
451
|
-
const el = document.getElementById(phId);
|
|
452
|
-
if (el && el.isConnected) {
|
|
453
|
-
if (doCall()) return;
|
|
454
|
-
|
|
455
|
-
if (state.pendingById.has(id)) return;
|
|
456
|
-
state.pendingById.add(id);
|
|
457
|
-
|
|
458
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
459
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
460
|
-
window.ezstandalone.cmd.push(() => {
|
|
461
|
-
try {
|
|
462
|
-
if (typeof window.ezstandalone.showAds === 'function') {
|
|
463
|
-
state.pendingById.delete(id);
|
|
464
|
-
state.lastShowById.set(id, Date.now());
|
|
465
|
-
window.ezstandalone.showAds(id);
|
|
466
|
-
}
|
|
467
|
-
} catch (e) {}
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
let tries = 0;
|
|
471
|
-
(function tick() {
|
|
472
|
-
tries += 1;
|
|
473
|
-
if (doCall() || tries >= 5) {
|
|
474
|
-
if (tries >= 5) state.pendingById.delete(id);
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
setTimeout(tick, 700);
|
|
478
|
-
})();
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (attempts < 50) setTimeout(waitForPh, 50);
|
|
483
|
-
})();
|
|
187
|
+
});
|
|
188
|
+
} catch (e) {}
|
|
484
189
|
}
|
|
485
190
|
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
if (Array.isArray(pool) && pool.length) return pool.shift();
|
|
489
|
-
return null;
|
|
490
|
-
}
|
|
191
|
+
function injectBetween(kindClass, items, interval, showFirst, pool, usedSet) {
|
|
192
|
+
if (!items || items.length === 0) return 0;
|
|
491
193
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (state.cfgPromise) return state.cfgPromise;
|
|
495
|
-
|
|
496
|
-
state.cfgPromise = (async () => {
|
|
497
|
-
try {
|
|
498
|
-
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
499
|
-
if (!res.ok) return null;
|
|
500
|
-
state.cfg = await res.json();
|
|
501
|
-
return state.cfg;
|
|
502
|
-
} catch (e) {
|
|
503
|
-
return null;
|
|
504
|
-
} finally {
|
|
505
|
-
state.cfgPromise = null;
|
|
506
|
-
}
|
|
507
|
-
})();
|
|
508
|
-
|
|
509
|
-
return state.cfgPromise;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function initPools(cfg) {
|
|
513
|
-
if (state.poolTopics.length === 0) state.poolTopics = parsePool(cfg.placeholderIds);
|
|
514
|
-
if (state.poolPosts.length === 0) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
|
|
515
|
-
if (state.poolCategories.length === 0) state.poolCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
516
|
-
}
|
|
194
|
+
let inserted = 0;
|
|
195
|
+
const targets = [];
|
|
517
196
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (i % interval === 0) out.push(i);
|
|
197
|
+
for (let i = 0; i < items.length; i++) {
|
|
198
|
+
const afterPos = i + 1;
|
|
199
|
+
if (afterPos === 1 && !showFirst) continue;
|
|
200
|
+
if (afterPos % interval !== (showFirst ? 1 : 0)) continue;
|
|
201
|
+
targets.push(afterPos);
|
|
524
202
|
}
|
|
525
|
-
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
526
|
-
}
|
|
527
203
|
|
|
528
|
-
function injectBetween(kindClass, items, interval, showFirst, kindPool, usedSet) {
|
|
529
|
-
if (!items.length) return 0;
|
|
530
|
-
const targets = computeTargets(items.length, interval, showFirst);
|
|
531
|
-
|
|
532
|
-
let inserted = 0;
|
|
533
204
|
for (const afterPos of targets) {
|
|
534
205
|
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
535
206
|
|
|
536
207
|
const el = items[afterPos - 1];
|
|
537
208
|
if (!el || !el.isConnected) continue;
|
|
538
|
-
|
|
539
|
-
// Prevent adjacent ads (DOM-based, robust against virtualization)
|
|
540
|
-
if (isAdjacentAd(el) || isPrevAd(el)) {
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// Prevent back-to-back at load
|
|
545
|
-
const prevWrap = findWrap(kindClass, afterPos - 1);
|
|
546
|
-
if (prevWrap) continue;
|
|
547
|
-
|
|
548
209
|
if (findWrap(kindClass, afterPos)) continue;
|
|
549
210
|
|
|
550
|
-
const
|
|
551
|
-
: (kindClass === 'ezoic-ad-message') ? state.liveMessage
|
|
552
|
-
: state.liveCategory;
|
|
553
|
-
|
|
554
|
-
const pick = pickId(kindPool, liveArr);
|
|
555
|
-
const id = pick.id;
|
|
211
|
+
const id = pickId(pool);
|
|
556
212
|
if (!id) break;
|
|
557
213
|
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
// Only destroy if Ezoic has actually defined this placeholder before
|
|
561
|
-
if (state.definedIds && state.definedIds.has(id)) {
|
|
562
|
-
destroyPlaceholderIds([id]);
|
|
563
|
-
}
|
|
564
|
-
// Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
|
|
565
|
-
const oldWrap = pick.recycled.wrap;
|
|
566
|
-
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
567
|
-
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
568
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
569
|
-
if (!wrap) continue;
|
|
570
|
-
setTimeout(() => { enqueueRetry(id); }, 450);
|
|
571
|
-
} else {
|
|
572
|
-
usedSet.add(id);
|
|
573
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
574
|
-
if (!wrap) continue;
|
|
575
|
-
}
|
|
214
|
+
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
215
|
+
if (!wrap) continue;
|
|
576
216
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (wrap && (wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))) {
|
|
580
|
-
try { wrap.remove(); } catch (e) {}
|
|
581
|
-
// Put id back if it was newly consumed (not recycled)
|
|
582
|
-
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
583
|
-
try { kindPool.unshift(id); } catch (e) {}
|
|
584
|
-
try { usedSet.delete(id); } catch (e) {}
|
|
585
|
-
}
|
|
586
|
-
inserted -= 0; // no-op
|
|
587
|
-
continue;
|
|
588
|
-
}
|
|
589
|
-
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
590
|
-
callShowAdsWhenReady(id);
|
|
591
|
-
}
|
|
217
|
+
usedSet.add(id);
|
|
218
|
+
callShowAds(id);
|
|
592
219
|
inserted += 1;
|
|
593
220
|
}
|
|
221
|
+
|
|
594
222
|
return inserted;
|
|
595
223
|
}
|
|
596
224
|
|
|
597
|
-
function
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
225
|
+
async function runCore() {
|
|
226
|
+
if (!state.canShowAds) return;
|
|
227
|
+
|
|
228
|
+
const cfg = await fetchConfig();
|
|
229
|
+
if (!cfg || cfg.excluded) return;
|
|
230
|
+
|
|
231
|
+
initPools(cfg);
|
|
232
|
+
|
|
233
|
+
const kind = getKind();
|
|
234
|
+
let inserted = 0;
|
|
235
|
+
|
|
236
|
+
if (kind === 'topic' && normalizeBool(cfg.enableMessageAds)) {
|
|
237
|
+
inserted = injectBetween(
|
|
238
|
+
'ezoic-ad-message',
|
|
239
|
+
getPostContainers(),
|
|
240
|
+
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
241
|
+
normalizeBool(cfg.showFirstMessageAd),
|
|
242
|
+
state.poolPosts,
|
|
243
|
+
state.usedPosts
|
|
244
|
+
);
|
|
245
|
+
} else if (kind === 'categoryTopics' && normalizeBool(cfg.enableBetweenAds)) {
|
|
246
|
+
inserted = injectBetween(
|
|
247
|
+
'ezoic-ad-between',
|
|
248
|
+
getTopicItems(),
|
|
249
|
+
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
250
|
+
normalizeBool(cfg.showFirstTopicAd),
|
|
251
|
+
state.poolTopics,
|
|
252
|
+
state.usedTopics
|
|
253
|
+
);
|
|
254
|
+
} else if (kind === 'categories' && normalizeBool(cfg.enableCategoryAds)) {
|
|
255
|
+
inserted = injectBetween(
|
|
256
|
+
'ezoic-ad-category',
|
|
257
|
+
getCategoryItems(),
|
|
258
|
+
Math.max(1, parseInt(cfg.intervalCategories, 10) || 6),
|
|
259
|
+
normalizeBool(cfg.showFirstCategoryAd),
|
|
260
|
+
state.poolCategories,
|
|
261
|
+
state.usedCategories
|
|
262
|
+
);
|
|
604
263
|
}
|
|
605
264
|
}
|
|
606
265
|
|
|
266
|
+
function scheduleRun() {
|
|
267
|
+
if (state.scheduled) return;
|
|
268
|
+
state.scheduled = true;
|
|
269
|
+
|
|
270
|
+
clearTimeout(state.timer);
|
|
271
|
+
state.timer = setTimeout(() => {
|
|
272
|
+
state.scheduled = false;
|
|
273
|
+
const pk = getPageKey();
|
|
274
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
275
|
+
runCore().catch(() => {});
|
|
276
|
+
}, 50);
|
|
277
|
+
}
|
|
278
|
+
|
|
607
279
|
function cleanup() {
|
|
608
|
-
|
|
280
|
+
const allIds = [...state.usedTopics, ...state.usedPosts, ...state.usedCategories];
|
|
281
|
+
if (allIds.length) destroyPlaceholderIds(allIds);
|
|
282
|
+
|
|
283
|
+
document.querySelectorAll('.ezoic-ad').forEach(el => {
|
|
284
|
+
try { el.remove(); } catch (e) {}
|
|
285
|
+
});
|
|
609
286
|
|
|
610
287
|
state.pageKey = getPageKey();
|
|
611
288
|
state.cfg = null;
|
|
612
289
|
state.cfgPromise = null;
|
|
613
|
-
|
|
614
290
|
state.poolTopics = [];
|
|
615
291
|
state.poolPosts = [];
|
|
616
292
|
state.poolCategories = [];
|
|
617
|
-
state.poolCategories = [];
|
|
618
293
|
state.usedTopics.clear();
|
|
619
294
|
state.usedPosts.clear();
|
|
620
|
-
state.usedCategories && state.usedCategories.clear();
|
|
621
|
-
state.liveBetween = [];
|
|
622
|
-
state.liveMessage = [];
|
|
623
|
-
state.liveCategory = [];
|
|
624
295
|
state.usedCategories.clear();
|
|
296
|
+
state.lastShowById.clear();
|
|
297
|
+
sessionDefinedIds.clear();
|
|
625
298
|
|
|
626
|
-
state.
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
299
|
+
if (state.obs) {
|
|
300
|
+
state.obs.disconnect();
|
|
301
|
+
state.obs = null;
|
|
302
|
+
}
|
|
632
303
|
|
|
633
|
-
if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; }
|
|
634
304
|
state.scheduled = false;
|
|
635
305
|
clearTimeout(state.timer);
|
|
636
306
|
state.timer = null;
|
|
@@ -638,139 +308,64 @@
|
|
|
638
308
|
|
|
639
309
|
function ensureObserver() {
|
|
640
310
|
if (state.obs) return;
|
|
641
|
-
state.obs = new MutationObserver(() => scheduleRun(
|
|
642
|
-
try {
|
|
643
|
-
|
|
311
|
+
state.obs = new MutationObserver(() => scheduleRun());
|
|
312
|
+
try {
|
|
313
|
+
state.obs.observe(document.body, { childList: true, subtree: true });
|
|
314
|
+
} catch (e) {}
|
|
644
315
|
}
|
|
645
316
|
|
|
646
|
-
|
|
647
|
-
patchShowAds();
|
|
648
|
-
|
|
649
|
-
const cfg = await fetchConfig();
|
|
650
|
-
if (!cfg || cfg.excluded) return;
|
|
651
|
-
|
|
652
|
-
initPools(cfg);
|
|
653
|
-
|
|
317
|
+
function waitForContentThenRun() {
|
|
654
318
|
const kind = getKind();
|
|
655
|
-
let
|
|
319
|
+
let selector = SELECTORS.postItem;
|
|
320
|
+
if (kind === 'categoryTopics') selector = SELECTORS.topicItem;
|
|
321
|
+
else if (kind === 'categories') selector = SELECTORS.categoryItem;
|
|
656
322
|
|
|
657
|
-
|
|
658
|
-
if (
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
'message',
|
|
663
|
-
state.usedPosts);
|
|
664
|
-
}
|
|
665
|
-
} else if (kind === 'categoryTopics') {
|
|
666
|
-
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
667
|
-
inserted = injectBetween('ezoic-ad-between', getTopicItems(),
|
|
668
|
-
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
669
|
-
normalizeBool(cfg.showFirstTopicAd),
|
|
670
|
-
'between',
|
|
671
|
-
state.usedTopics);
|
|
672
|
-
}
|
|
673
|
-
} else if (kind === 'categories') {
|
|
674
|
-
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
675
|
-
inserted = injectBetween('ezoic-ad-categories', getCategoryItems(),
|
|
676
|
-
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
677
|
-
normalizeBool(cfg.showFirstCategoryAd),
|
|
678
|
-
'categories',
|
|
679
|
-
state.usedCategories);
|
|
323
|
+
const check = () => {
|
|
324
|
+
if (document.querySelector(selector)) {
|
|
325
|
+
scheduleRun();
|
|
326
|
+
} else {
|
|
327
|
+
setTimeout(check, 200);
|
|
680
328
|
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
enforceNoAdjacentAds();
|
|
684
|
-
scheduleRefill(250);
|
|
685
|
-
|
|
686
|
-
// If nothing inserted and list isn't in DOM yet (first click), retry a bit
|
|
687
|
-
let count = 0;
|
|
688
|
-
if (kind === 'topic') count = getPostContainers().length;
|
|
689
|
-
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
690
|
-
else if (kind === 'categories') count = getCategoryItems().length;
|
|
691
|
-
|
|
692
|
-
if (count === 0 && state.attempts < 25) {
|
|
693
|
-
state.attempts += 1;
|
|
694
|
-
setTimeout(() => scheduleRun('await-items'), 120);
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (inserted >= MAX_INSERTS_PER_RUN) setTimeout(() => scheduleRun('continue'), 140);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function scheduleRun() {
|
|
702
|
-
if (state.scheduled) return;
|
|
703
|
-
state.scheduled = true;
|
|
704
|
-
|
|
705
|
-
clearTimeout(state.timer);
|
|
706
|
-
state.timer = setTimeout(() => {
|
|
707
|
-
state.scheduled = false;
|
|
708
|
-
const pk = getPageKey();
|
|
709
|
-
if (state.pageKey && pk !== state.pageKey) return;
|
|
710
|
-
runCore().catch(() => {});
|
|
711
|
-
}, 80);
|
|
329
|
+
};
|
|
330
|
+
check();
|
|
712
331
|
}
|
|
713
332
|
|
|
714
333
|
function bind() {
|
|
715
334
|
if (!$) return;
|
|
716
335
|
|
|
717
336
|
$(window).off('.ezoicInfinite');
|
|
718
|
-
|
|
719
337
|
$(window).on('action:ajaxify.start.ezoicInfinite', () => cleanup());
|
|
720
|
-
|
|
721
338
|
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
722
339
|
state.pageKey = getPageKey();
|
|
723
340
|
ensureObserver();
|
|
341
|
+
state.canShowAds = true;
|
|
724
342
|
scheduleRun();
|
|
725
|
-
setTimeout(scheduleRun, 200);
|
|
726
|
-
setTimeout(scheduleRun, 700);
|
|
727
343
|
});
|
|
728
344
|
|
|
729
345
|
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
730
346
|
ensureObserver();
|
|
731
|
-
|
|
732
|
-
setTimeout(scheduleRun, 250);
|
|
347
|
+
waitForContentThenRun();
|
|
733
348
|
});
|
|
734
349
|
|
|
735
350
|
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
736
351
|
ensureObserver();
|
|
737
|
-
|
|
738
|
-
setTimeout(scheduleRun, 150);
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
$(window).on('action:topic.loaded.ezoicInfinite', () => {
|
|
742
|
-
ensureObserver();
|
|
743
|
-
scheduleRun();
|
|
744
|
-
setTimeout(scheduleRun, 200);
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
$(window).on('action:posts.loaded.ezoicInfinite', () => {
|
|
748
|
-
ensureObserver();
|
|
749
|
-
scheduleRun();
|
|
750
|
-
setTimeout(scheduleRun, 150);
|
|
352
|
+
waitForContentThenRun();
|
|
751
353
|
});
|
|
752
354
|
}
|
|
753
355
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
})()
|
|
761
|
-
function bindScroll() {
|
|
762
|
-
if (state.__scrollBound) return;
|
|
763
|
-
state.__scrollBound = true;
|
|
764
|
-
let ticking = false;
|
|
765
|
-
window.addEventListener('scroll', () => {
|
|
766
|
-
if (ticking) return;
|
|
767
|
-
ticking = true;
|
|
768
|
-
window.requestAnimationFrame(() => {
|
|
769
|
-
ticking = false;
|
|
770
|
-
enforceNoAdjacentAds();
|
|
771
|
-
scheduleRefill(200);
|
|
772
|
-
});
|
|
773
|
-
}, { passive: true });
|
|
356
|
+
function init() {
|
|
357
|
+
state.pageKey = getPageKey();
|
|
358
|
+
state.canShowAds = true;
|
|
359
|
+
bind();
|
|
360
|
+
ensureObserver();
|
|
361
|
+
waitForContentThenRun();
|
|
774
362
|
}
|
|
775
363
|
|
|
776
|
-
|
|
364
|
+
if ($ && $(document).ready) {
|
|
365
|
+
$(document).ready(init);
|
|
366
|
+
} else if (document.readyState === 'loading') {
|
|
367
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
368
|
+
} else {
|
|
369
|
+
init();
|
|
370
|
+
}
|
|
371
|
+
})();
|
package/public/public/style.css
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
.ezoic-ad {
|
|
2
2
|
padding: 0 !important;
|
|
3
3
|
margin: 0 !important;
|
|
4
|
+
min-height: 0 !important;
|
|
5
|
+
min-width: 0 !important;
|
|
4
6
|
}
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
.ezoic-ad,
|
|
8
|
-
.ezoic-ad *,
|
|
9
|
-
span.ezoic-ad,
|
|
10
|
-
span[class*="ezoic"] {
|
|
8
|
+
.ezoic-ad * {
|
|
11
9
|
min-height: 0 !important;
|
|
12
10
|
min-width: 0 !important;
|
|
13
11
|
}
|