nodebb-plugin-ezoic-infinite 1.8.61 → 1.8.63
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/library.js +3 -2
- package/package.json +1 -1
- package/public/client.js +208 -370
package/library.js
CHANGED
|
@@ -141,12 +141,13 @@ const HEAD_PRECONNECTS = [
|
|
|
141
141
|
const EZOIC_SCRIPTS = [
|
|
142
142
|
'<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>',
|
|
143
143
|
'<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>',
|
|
144
|
+
'<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>',
|
|
144
145
|
'<script>',
|
|
145
146
|
'window._ezaq = window._ezaq || {};',
|
|
146
147
|
'window.ezstandalone = window.ezstandalone || {};',
|
|
147
148
|
'ezstandalone.cmd = ezstandalone.cmd || [];',
|
|
148
149
|
'</script>',
|
|
149
|
-
'<script
|
|
150
|
+
'<script src="//ezoicanalytics.com/analytics.js"></script>',
|
|
150
151
|
].join('\n');
|
|
151
152
|
|
|
152
153
|
// ── Hooks ────────────────────────────────────────────────────────────────────
|
|
@@ -234,4 +235,4 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
234
235
|
});
|
|
235
236
|
};
|
|
236
237
|
|
|
237
|
-
module.exports = plugin;
|
|
238
|
+
module.exports = plugin;
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js v2.
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — client.js v2.3.0
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - Ezoic API: showAds() + destroyPlaceholders()
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
4
|
+
* Architecture based on battle-tested v50, with targeted improvements:
|
|
5
|
+
* - Ezoic API: showAds() + destroyPlaceholders() per official docs
|
|
6
|
+
* - wrapsByClass Set for O(1) recycle lookup (no querySelectorAll)
|
|
7
|
+
* - MutationObserver: ad fill detection + virtualization re-observe
|
|
8
|
+
* - Conservative empty check (30s/60s, GPT-aware)
|
|
9
|
+
* - aria-hidden + TCF locator protection
|
|
10
10
|
*/
|
|
11
11
|
(function nbbEzoicInfinite() {
|
|
12
12
|
'use strict';
|
|
@@ -24,28 +24,25 @@
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
const TIMING = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
MIN_PRUNE_AGE_MS:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
EMPTY_CHECK_MS_1: 30_000,
|
|
28
|
+
EMPTY_CHECK_MS_2: 60_000,
|
|
29
|
+
MIN_PRUNE_AGE_MS: 8_000,
|
|
30
|
+
RECYCLE_MIN_AGE_MS: 5_000,
|
|
31
|
+
SHOW_THROTTLE_MS: 900,
|
|
32
|
+
BURST_COOLDOWN_MS: 200,
|
|
33
|
+
BLOCK_DURATION_MS: 1_500,
|
|
34
|
+
SHOW_TIMEOUT_MS: 7_000,
|
|
35
|
+
SHOW_RELEASE_MS: 700,
|
|
36
|
+
RECYCLE_DELAY_MS: 450,
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
BURST_WINDOW_MS: 2_000,
|
|
43
|
-
};
|
|
39
|
+
const MAX_INSERTS_RUN = 6;
|
|
40
|
+
const MAX_INFLIGHT = 4;
|
|
41
|
+
const MAX_BURST_STEPS = 8;
|
|
42
|
+
const BURST_WINDOW_MS = 2_000;
|
|
44
43
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
MOBILE: '3500px 0px 3500px 0px',
|
|
48
|
-
};
|
|
44
|
+
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
45
|
+
const IO_MARGIN_MOBILE = '3500px 0px 3500px 0px';
|
|
49
46
|
|
|
50
47
|
const SEL = {
|
|
51
48
|
post: '[component="post"][data-pid]',
|
|
@@ -60,39 +57,26 @@
|
|
|
60
57
|
};
|
|
61
58
|
|
|
62
59
|
const FILL_SEL = 'iframe, ins, img, video, [data-google-container-id], div[id$="__container__"]';
|
|
63
|
-
const RECYCLE_MIN_AGE_MS = 5_000;
|
|
64
60
|
|
|
65
61
|
// ── Utility ────────────────────────────────────────────────────────────────
|
|
66
62
|
|
|
67
63
|
const now = () => Date.now();
|
|
68
64
|
const isMobile = () => window.innerWidth < 768;
|
|
69
65
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
70
|
-
|
|
71
|
-
function isFilled(node) {
|
|
72
|
-
return node?.querySelector?.(FILL_SEL) != null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function isPlaceholderUsed(ph) {
|
|
76
|
-
if (!ph?.isConnected) return false;
|
|
77
|
-
return ph.querySelector('ins.adsbygoogle, iframe, [data-google-container-id], div[id$="__container__"]') != null;
|
|
78
|
-
}
|
|
66
|
+
const isFilled = n => !!(n?.querySelector?.(FILL_SEL));
|
|
79
67
|
|
|
80
68
|
function parseIds(raw) {
|
|
81
|
-
const out = [];
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (n > 0 && Number.isFinite(n) && !seen.has(n)) {
|
|
86
|
-
seen.add(n);
|
|
87
|
-
out.push(n);
|
|
88
|
-
}
|
|
69
|
+
const out = [], seen = new Set();
|
|
70
|
+
for (const v of String(raw || '').split(/[\r\n,\s]+/)) {
|
|
71
|
+
const n = parseInt(v, 10);
|
|
72
|
+
if (n > 0 && Number.isFinite(n) && !seen.has(n)) { seen.add(n); out.push(n); }
|
|
89
73
|
}
|
|
90
74
|
return out;
|
|
91
75
|
}
|
|
92
76
|
|
|
93
77
|
// ── State ──────────────────────────────────────────────────────────────────
|
|
94
78
|
|
|
95
|
-
const
|
|
79
|
+
const S = {
|
|
96
80
|
pageKey: null,
|
|
97
81
|
kind: null,
|
|
98
82
|
cfg: null,
|
|
@@ -101,18 +85,17 @@
|
|
|
101
85
|
pools: { topics: [], posts: [], categories: [] },
|
|
102
86
|
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
103
87
|
|
|
104
|
-
mountedIds:
|
|
105
|
-
|
|
106
|
-
lastShow: new Map(), // id → timestamp
|
|
88
|
+
mountedIds: new Set(),
|
|
89
|
+
lastShow: new Map(),
|
|
107
90
|
|
|
108
|
-
wrapByKey: new Map(),
|
|
109
|
-
wrapsByClass: new Map(),
|
|
91
|
+
wrapByKey: new Map(), // anchorKey → wrap element
|
|
92
|
+
wrapsByClass: new Map(), // kindClass → Set<wrap>
|
|
110
93
|
|
|
111
94
|
io: null,
|
|
112
95
|
domObs: null,
|
|
113
96
|
|
|
114
|
-
mutGuard:
|
|
115
|
-
blockedUntil:
|
|
97
|
+
mutGuard: 0,
|
|
98
|
+
blockedUntil: 0,
|
|
116
99
|
|
|
117
100
|
inflight: 0,
|
|
118
101
|
pending: [],
|
|
@@ -123,40 +106,36 @@
|
|
|
123
106
|
burstDeadline: 0,
|
|
124
107
|
burstCount: 0,
|
|
125
108
|
lastBurstTs: 0,
|
|
126
|
-
firstShown: false,
|
|
127
109
|
};
|
|
128
110
|
|
|
129
|
-
const isBlocked = () => now() <
|
|
111
|
+
const isBlocked = () => now() < S.blockedUntil;
|
|
130
112
|
|
|
131
113
|
function mutate(fn) {
|
|
132
|
-
|
|
133
|
-
try { fn(); } finally {
|
|
114
|
+
S.mutGuard++;
|
|
115
|
+
try { fn(); } finally { S.mutGuard--; }
|
|
134
116
|
}
|
|
135
117
|
|
|
136
118
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
137
119
|
|
|
138
120
|
async function fetchConfig() {
|
|
139
|
-
if (
|
|
121
|
+
if (S.cfg) return S.cfg;
|
|
140
122
|
try {
|
|
141
123
|
const inline = window.__nbbEzoicCfg;
|
|
142
|
-
if (inline && typeof inline === 'object') {
|
|
143
|
-
state.cfg = inline;
|
|
144
|
-
return state.cfg;
|
|
145
|
-
}
|
|
124
|
+
if (inline && typeof inline === 'object') { S.cfg = inline; return S.cfg; }
|
|
146
125
|
} catch (_) {}
|
|
147
126
|
try {
|
|
148
127
|
const r = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
149
|
-
if (r.ok)
|
|
128
|
+
if (r.ok) S.cfg = await r.json();
|
|
150
129
|
} catch (_) {}
|
|
151
|
-
return
|
|
130
|
+
return S.cfg;
|
|
152
131
|
}
|
|
153
132
|
|
|
154
133
|
function initPools(cfg) {
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
134
|
+
if (S.poolsReady) return;
|
|
135
|
+
S.pools.topics = parseIds(cfg.placeholderIds);
|
|
136
|
+
S.pools.posts = parseIds(cfg.messagePlaceholderIds);
|
|
137
|
+
S.pools.categories = parseIds(cfg.categoryPlaceholderIds);
|
|
138
|
+
S.poolsReady = true;
|
|
160
139
|
}
|
|
161
140
|
|
|
162
141
|
// ── Page identity ──────────────────────────────────────────────────────────
|
|
@@ -182,7 +161,7 @@
|
|
|
182
161
|
}
|
|
183
162
|
|
|
184
163
|
function getKind() {
|
|
185
|
-
return
|
|
164
|
+
return S.kind || (S.kind = detectKind());
|
|
186
165
|
}
|
|
187
166
|
|
|
188
167
|
// ── DOM queries ────────────────────────────────────────────────────────────
|
|
@@ -202,8 +181,8 @@
|
|
|
202
181
|
return out;
|
|
203
182
|
}
|
|
204
183
|
|
|
205
|
-
|
|
206
|
-
|
|
184
|
+
const getTopics = () => Array.from(document.querySelectorAll(SEL.topic));
|
|
185
|
+
const getCategories = () => Array.from(document.querySelectorAll(SEL.category));
|
|
207
186
|
|
|
208
187
|
// ── Anchor keys & wrap registry ────────────────────────────────────────────
|
|
209
188
|
|
|
@@ -224,60 +203,45 @@
|
|
|
224
203
|
const anchorKey = (klass, el) => `${klass}:${stableId(klass, el)}`;
|
|
225
204
|
|
|
226
205
|
function findWrap(key) {
|
|
227
|
-
const w =
|
|
206
|
+
const w = S.wrapByKey.get(key);
|
|
228
207
|
return w?.isConnected ? w : null;
|
|
229
208
|
}
|
|
230
209
|
|
|
231
210
|
function getWrapSet(klass) {
|
|
232
|
-
let set =
|
|
233
|
-
if (!set) { set = new Set();
|
|
211
|
+
let set = S.wrapsByClass.get(klass);
|
|
212
|
+
if (!set) { set = new Set(); S.wrapsByClass.set(klass, set); }
|
|
234
213
|
return set;
|
|
235
214
|
}
|
|
236
215
|
|
|
237
|
-
// ── GC disconnected wraps
|
|
216
|
+
// ── GC disconnected wraps ──────────────────────────────────────────────────
|
|
217
|
+
// NodeBB virtualizes posts off-viewport. The MutationObserver catches most
|
|
218
|
+
// removals, but this is a safety net for edge cases.
|
|
238
219
|
|
|
239
220
|
function gcDisconnectedWraps() {
|
|
240
|
-
for (const [key, w] of
|
|
241
|
-
if (!w?.isConnected)
|
|
221
|
+
for (const [key, w] of S.wrapByKey) {
|
|
222
|
+
if (!w?.isConnected) S.wrapByKey.delete(key);
|
|
242
223
|
}
|
|
243
|
-
for (const [klass, set] of
|
|
244
|
-
for (const w of
|
|
224
|
+
for (const [klass, set] of S.wrapsByClass) {
|
|
225
|
+
for (const w of set) {
|
|
245
226
|
if (w?.isConnected) continue;
|
|
246
227
|
set.delete(w);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
state.phState.delete(id);
|
|
252
|
-
state.lastShow.delete(id);
|
|
253
|
-
}
|
|
254
|
-
} catch (_) {}
|
|
255
|
-
}
|
|
256
|
-
if (!set.size) state.wrapsByClass.delete(klass);
|
|
257
|
-
}
|
|
258
|
-
try {
|
|
259
|
-
const live = new Set();
|
|
260
|
-
for (const w of document.querySelectorAll(`.${WRAP_CLASS}`)) {
|
|
261
|
-
const id = parseInt(w.getAttribute(ATTR.WRAPID) || '0', 10);
|
|
262
|
-
if (id > 0) live.add(id);
|
|
263
|
-
}
|
|
264
|
-
for (const id of Array.from(state.mountedIds)) {
|
|
265
|
-
if (!live.has(id)) {
|
|
266
|
-
state.mountedIds.delete(id);
|
|
267
|
-
state.phState.delete(id);
|
|
268
|
-
state.lastShow.delete(id);
|
|
228
|
+
const id = parseInt(w.getAttribute?.(ATTR.WRAPID), 10);
|
|
229
|
+
if (Number.isFinite(id)) {
|
|
230
|
+
S.mountedIds.delete(id);
|
|
231
|
+
S.lastShow.delete(id);
|
|
269
232
|
}
|
|
270
233
|
}
|
|
271
|
-
|
|
234
|
+
if (!set.size) S.wrapsByClass.delete(klass);
|
|
235
|
+
}
|
|
272
236
|
}
|
|
273
237
|
|
|
274
|
-
// ── Wrap lifecycle
|
|
238
|
+
// ── Wrap lifecycle ─────────────────────────────────────────────────────────
|
|
275
239
|
|
|
276
240
|
function wrapIsLive(wrap) {
|
|
277
241
|
if (!wrap?.classList?.contains(WRAP_CLASS)) return false;
|
|
278
242
|
const key = wrap.getAttribute(ATTR.ANCHOR);
|
|
279
243
|
if (!key) return false;
|
|
280
|
-
if (
|
|
244
|
+
if (S.wrapByKey.get(key) === wrap) return wrap.isConnected;
|
|
281
245
|
const colonIdx = key.indexOf(':');
|
|
282
246
|
const klass = key.slice(0, colonIdx);
|
|
283
247
|
const anchorId = key.slice(colonIdx + 1);
|
|
@@ -308,14 +272,12 @@
|
|
|
308
272
|
const ph = wrap.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
309
273
|
if (!ph || !isFilled(ph)) return false;
|
|
310
274
|
wrap.classList.remove('is-empty');
|
|
311
|
-
const id = parseInt(wrap.getAttribute(ATTR.WRAPID) || '0', 10);
|
|
312
|
-
if (id > 0) state.phState.set(id, 'shown');
|
|
313
275
|
return true;
|
|
314
276
|
}
|
|
315
277
|
|
|
316
278
|
function scheduleUncollapseChecks(wrap) {
|
|
317
279
|
if (!wrap) return;
|
|
318
|
-
for (const ms of [500,
|
|
280
|
+
for (const ms of [500, 3000, 10000]) {
|
|
319
281
|
setTimeout(() => { try { clearEmptyIfFilled(wrap); } catch (_) {} }, ms);
|
|
320
282
|
}
|
|
321
283
|
}
|
|
@@ -323,21 +285,19 @@
|
|
|
323
285
|
// ── Pool management ────────────────────────────────────────────────────────
|
|
324
286
|
|
|
325
287
|
function pickId(poolKey) {
|
|
326
|
-
const pool =
|
|
288
|
+
const pool = S.pools[poolKey];
|
|
327
289
|
if (!pool.length) return null;
|
|
328
290
|
for (let t = 0; t < pool.length; t++) {
|
|
329
|
-
const idx =
|
|
330
|
-
|
|
291
|
+
const idx = S.cursors[poolKey] % pool.length;
|
|
292
|
+
S.cursors[poolKey] = (S.cursors[poolKey] + 1) % pool.length;
|
|
331
293
|
const id = pool[idx];
|
|
332
|
-
if (!
|
|
294
|
+
if (!S.mountedIds.has(id)) return id;
|
|
333
295
|
}
|
|
334
296
|
return null;
|
|
335
297
|
}
|
|
336
298
|
|
|
337
299
|
// ── Recycling ──────────────────────────────────────────────────────────────
|
|
338
|
-
//
|
|
339
|
-
// Per Ezoic docs: destroyPlaceholders(id) → remove old HTML →
|
|
340
|
-
// recreate fresh placeholder → showAds(id).
|
|
300
|
+
// Per Ezoic docs: destroyPlaceholders(id) → remove HTML → fresh placeholder → showAds(id)
|
|
341
301
|
|
|
342
302
|
function recycleWrap(klass, targetEl, newKey) {
|
|
343
303
|
const ez = window.ezstandalone;
|
|
@@ -350,18 +310,13 @@
|
|
|
350
310
|
let bestEmpty = null, bestEmptyY = Infinity;
|
|
351
311
|
let bestFull = null, bestFullY = Infinity;
|
|
352
312
|
|
|
353
|
-
const wraps =
|
|
313
|
+
const wraps = S.wrapsByClass.get(klass);
|
|
354
314
|
if (!wraps) return null;
|
|
355
315
|
|
|
356
316
|
for (const wrap of wraps) {
|
|
357
317
|
try {
|
|
358
|
-
// Skip young wraps (ad might still be loading)
|
|
359
318
|
const created = parseInt(wrap.getAttribute(ATTR.CREATED) || '0', 10);
|
|
360
|
-
if (t - created < RECYCLE_MIN_AGE_MS) continue;
|
|
361
|
-
// Skip wraps with inflight showAds
|
|
362
|
-
const wid = parseInt(wrap.getAttribute(ATTR.WRAPID), 10);
|
|
363
|
-
if (wid > 0 && state.phState.get(wid) === 'show-queued') continue;
|
|
364
|
-
|
|
319
|
+
if (t - created < TIMING.RECYCLE_MIN_AGE_MS) continue;
|
|
365
320
|
const bottom = wrap.getBoundingClientRect().bottom;
|
|
366
321
|
if (bottom > threshold) continue;
|
|
367
322
|
if (!isFilled(wrap)) {
|
|
@@ -377,22 +332,19 @@
|
|
|
377
332
|
|
|
378
333
|
const id = parseInt(best.getAttribute(ATTR.WRAPID), 10);
|
|
379
334
|
if (!Number.isFinite(id)) return null;
|
|
380
|
-
|
|
381
335
|
const oldKey = best.getAttribute(ATTR.ANCHOR);
|
|
382
336
|
|
|
383
337
|
// Unobserve before moving
|
|
384
338
|
try {
|
|
385
339
|
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
386
|
-
if (ph)
|
|
340
|
+
if (ph) S.io?.unobserve(ph);
|
|
387
341
|
} catch (_) {}
|
|
388
342
|
|
|
389
|
-
// Ezoic recycle: destroy →
|
|
343
|
+
// Ezoic recycle: destroy → fresh DOM → showAds
|
|
390
344
|
const doRecycle = () => {
|
|
391
|
-
state.phState.set(id, 'destroyed');
|
|
392
345
|
try { ez.destroyPlaceholders(id); } catch (_) {}
|
|
393
346
|
|
|
394
347
|
setTimeout(() => {
|
|
395
|
-
// Recreate fresh placeholder DOM at new position
|
|
396
348
|
mutate(() => {
|
|
397
349
|
best.setAttribute(ATTR.ANCHOR, newKey);
|
|
398
350
|
best.setAttribute(ATTR.CREATED, String(now()));
|
|
@@ -403,27 +355,21 @@
|
|
|
403
355
|
const fresh = document.createElement('div');
|
|
404
356
|
fresh.id = `${PH_PREFIX}${id}`;
|
|
405
357
|
fresh.setAttribute('data-ezoic-id', String(id));
|
|
406
|
-
fresh.style.minHeight = '1px';
|
|
407
358
|
best.appendChild(fresh);
|
|
408
359
|
targetEl.insertAdjacentElement('afterend', best);
|
|
409
360
|
});
|
|
410
361
|
|
|
411
|
-
if (oldKey &&
|
|
412
|
-
|
|
362
|
+
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
363
|
+
S.wrapByKey.set(newKey, best);
|
|
413
364
|
|
|
414
|
-
// Re-show after DOM is settled
|
|
415
365
|
setTimeout(() => {
|
|
416
|
-
|
|
417
|
-
state.phState.set(id, 'new');
|
|
366
|
+
observePh(id);
|
|
418
367
|
enqueueShow(id);
|
|
419
368
|
}, TIMING.RECYCLE_DELAY_MS);
|
|
420
369
|
}, TIMING.RECYCLE_DELAY_MS);
|
|
421
370
|
};
|
|
422
371
|
|
|
423
|
-
try {
|
|
424
|
-
(typeof ez.cmd?.push === 'function') ? ez.cmd.push(doRecycle) : doRecycle();
|
|
425
|
-
} catch (_) {}
|
|
426
|
-
|
|
372
|
+
try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doRecycle) : doRecycle(); } catch (_) {}
|
|
427
373
|
return { id, wrap: best };
|
|
428
374
|
}
|
|
429
375
|
|
|
@@ -437,11 +383,9 @@
|
|
|
437
383
|
w.setAttribute(ATTR.CREATED, String(now()));
|
|
438
384
|
w.setAttribute(ATTR.SHOWN, '0');
|
|
439
385
|
w.style.cssText = 'width:100%;display:block';
|
|
440
|
-
|
|
441
386
|
const ph = document.createElement('div');
|
|
442
387
|
ph.id = `${PH_PREFIX}${id}`;
|
|
443
388
|
ph.setAttribute('data-ezoic-id', String(id));
|
|
444
|
-
ph.style.minHeight = '1px';
|
|
445
389
|
w.appendChild(ph);
|
|
446
390
|
return w;
|
|
447
391
|
}
|
|
@@ -449,15 +393,12 @@
|
|
|
449
393
|
function insertAfter(el, id, klass, key) {
|
|
450
394
|
if (!el?.insertAdjacentElement) return null;
|
|
451
395
|
if (findWrap(key)) return null;
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
if (existing?.isConnected) return null;
|
|
455
|
-
|
|
396
|
+
if (S.mountedIds.has(id)) return null;
|
|
397
|
+
if (document.getElementById(`${PH_PREFIX}${id}`)?.isConnected) return null;
|
|
456
398
|
const w = makeWrap(id, klass, key);
|
|
457
399
|
mutate(() => el.insertAdjacentElement('afterend', w));
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
state.wrapByKey.set(key, w);
|
|
400
|
+
S.mountedIds.add(id);
|
|
401
|
+
S.wrapByKey.set(key, w);
|
|
461
402
|
getWrapSet(klass).add(w);
|
|
462
403
|
return w;
|
|
463
404
|
}
|
|
@@ -465,17 +406,17 @@
|
|
|
465
406
|
function dropWrap(w) {
|
|
466
407
|
try {
|
|
467
408
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
468
|
-
if (ph instanceof Element)
|
|
409
|
+
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
469
410
|
const id = parseInt(w.getAttribute(ATTR.WRAPID), 10);
|
|
470
411
|
if (Number.isFinite(id)) {
|
|
471
|
-
|
|
472
|
-
|
|
412
|
+
S.mountedIds.delete(id);
|
|
413
|
+
S.lastShow.delete(id);
|
|
473
414
|
}
|
|
474
415
|
const key = w.getAttribute(ATTR.ANCHOR);
|
|
475
|
-
if (key &&
|
|
416
|
+
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
476
417
|
for (const cls of w.classList) {
|
|
477
418
|
if (cls !== WRAP_CLASS && cls.startsWith('ezoic-ad-')) {
|
|
478
|
-
|
|
419
|
+
S.wrapsByClass.get(cls)?.delete(w);
|
|
479
420
|
break;
|
|
480
421
|
}
|
|
481
422
|
}
|
|
@@ -488,7 +429,7 @@
|
|
|
488
429
|
function pruneOrphansBetween() {
|
|
489
430
|
const klass = 'ezoic-ad-between';
|
|
490
431
|
const cfg = KIND[klass];
|
|
491
|
-
const wraps =
|
|
432
|
+
const wraps = S.wrapsByClass.get(klass);
|
|
492
433
|
if (!wraps?.size) return;
|
|
493
434
|
|
|
494
435
|
const liveAnchors = new Set();
|
|
@@ -496,16 +437,13 @@
|
|
|
496
437
|
const v = el.getAttribute(cfg.anchorAttr);
|
|
497
438
|
if (v) liveAnchors.add(v);
|
|
498
439
|
}
|
|
499
|
-
|
|
500
440
|
const t = now();
|
|
501
441
|
for (const w of wraps) {
|
|
502
442
|
const created = parseInt(w.getAttribute(ATTR.CREATED) || '0', 10);
|
|
503
443
|
if (t - created < TIMING.MIN_PRUNE_AGE_MS) continue;
|
|
504
444
|
const key = w.getAttribute(ATTR.ANCHOR) ?? '';
|
|
505
445
|
const sid = key.slice(klass.length + 1);
|
|
506
|
-
if (!sid || !liveAnchors.has(sid))
|
|
507
|
-
mutate(() => dropWrap(w));
|
|
508
|
-
}
|
|
446
|
+
if (!sid || !liveAnchors.has(sid)) mutate(() => dropWrap(w));
|
|
509
447
|
}
|
|
510
448
|
}
|
|
511
449
|
|
|
@@ -529,26 +467,18 @@
|
|
|
529
467
|
function injectBetween(klass, items, interval, showFirst, poolKey) {
|
|
530
468
|
if (!items.length) return 0;
|
|
531
469
|
let inserted = 0;
|
|
532
|
-
|
|
533
470
|
for (const el of items) {
|
|
534
|
-
if (inserted >=
|
|
471
|
+
if (inserted >= MAX_INSERTS_RUN) break;
|
|
535
472
|
if (!el?.isConnected) continue;
|
|
536
|
-
|
|
537
473
|
const ord = ordinal(klass, el);
|
|
538
474
|
if (!(showFirst && ord === 0) && (ord + 1) % interval !== 0) continue;
|
|
539
475
|
if (adjacentWrap(el)) continue;
|
|
540
|
-
|
|
541
476
|
const key = anchorKey(klass, el);
|
|
542
477
|
if (findWrap(key)) continue;
|
|
543
|
-
|
|
544
478
|
const id = pickId(poolKey);
|
|
545
479
|
if (id) {
|
|
546
480
|
const w = insertAfter(el, id, klass, key);
|
|
547
|
-
if (w) {
|
|
548
|
-
observePlaceholder(id);
|
|
549
|
-
if (!state.firstShown) { state.firstShown = true; enqueueShow(id); }
|
|
550
|
-
inserted++;
|
|
551
|
-
}
|
|
481
|
+
if (w) { observePh(id); inserted++; }
|
|
552
482
|
} else {
|
|
553
483
|
const recycled = recycleWrap(klass, el, key);
|
|
554
484
|
if (!recycled) break;
|
|
@@ -561,69 +491,58 @@
|
|
|
561
491
|
// ── IntersectionObserver ───────────────────────────────────────────────────
|
|
562
492
|
|
|
563
493
|
function getIO() {
|
|
564
|
-
if (
|
|
494
|
+
if (S.io) return S.io;
|
|
565
495
|
try {
|
|
566
|
-
|
|
567
|
-
for (const
|
|
568
|
-
if (!
|
|
569
|
-
if (
|
|
570
|
-
const id = parseInt(
|
|
496
|
+
S.io = new IntersectionObserver(entries => {
|
|
497
|
+
for (const e of entries) {
|
|
498
|
+
if (!e.isIntersecting) continue;
|
|
499
|
+
if (e.target instanceof Element) S.io?.unobserve(e.target);
|
|
500
|
+
const id = parseInt(e.target.getAttribute('data-ezoic-id'), 10);
|
|
571
501
|
if (id > 0) enqueueShow(id);
|
|
572
502
|
}
|
|
573
503
|
}, {
|
|
574
504
|
root: null,
|
|
575
|
-
rootMargin: isMobile() ?
|
|
505
|
+
rootMargin: isMobile() ? IO_MARGIN_MOBILE : IO_MARGIN_DESKTOP,
|
|
576
506
|
threshold: 0,
|
|
577
507
|
});
|
|
578
|
-
} catch (_) {
|
|
579
|
-
return
|
|
508
|
+
} catch (_) { S.io = null; }
|
|
509
|
+
return S.io;
|
|
580
510
|
}
|
|
581
511
|
|
|
582
|
-
function
|
|
512
|
+
function observePh(id) {
|
|
583
513
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
584
|
-
if (ph?.isConnected) {
|
|
585
|
-
try { getIO()?.observe(ph); } catch (_) {}
|
|
586
|
-
}
|
|
514
|
+
if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
|
|
587
515
|
}
|
|
588
516
|
|
|
589
517
|
// ── Show queue ─────────────────────────────────────────────────────────────
|
|
590
518
|
|
|
591
519
|
function enqueueShow(id) {
|
|
592
520
|
if (!id || isBlocked()) return;
|
|
593
|
-
|
|
594
|
-
if (
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (state.inflight >= LIMITS.MAX_INFLIGHT) {
|
|
598
|
-
if (!state.pendingSet.has(id)) {
|
|
599
|
-
state.pending.push(id);
|
|
600
|
-
state.pendingSet.add(id);
|
|
601
|
-
state.phState.set(id, 'show-queued');
|
|
602
|
-
}
|
|
521
|
+
if (now() - (S.lastShow.get(id) ?? 0) < TIMING.SHOW_THROTTLE_MS) return;
|
|
522
|
+
if (S.inflight >= MAX_INFLIGHT) {
|
|
523
|
+
if (!S.pendingSet.has(id)) { S.pending.push(id); S.pendingSet.add(id); }
|
|
603
524
|
return;
|
|
604
525
|
}
|
|
605
|
-
state.phState.set(id, 'show-queued');
|
|
606
526
|
startShow(id);
|
|
607
527
|
}
|
|
608
528
|
|
|
609
529
|
function drainQueue() {
|
|
610
530
|
if (isBlocked()) return;
|
|
611
|
-
while (
|
|
612
|
-
const id =
|
|
613
|
-
|
|
531
|
+
while (S.inflight < MAX_INFLIGHT && S.pending.length) {
|
|
532
|
+
const id = S.pending.shift();
|
|
533
|
+
S.pendingSet.delete(id);
|
|
614
534
|
startShow(id);
|
|
615
535
|
}
|
|
616
536
|
}
|
|
617
537
|
|
|
618
538
|
function startShow(id) {
|
|
619
539
|
if (!id || isBlocked()) return;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
let released = false;
|
|
540
|
+
S.inflight++;
|
|
541
|
+
let done = false;
|
|
623
542
|
const release = () => {
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
|
|
543
|
+
if (done) return;
|
|
544
|
+
done = true;
|
|
545
|
+
S.inflight = Math.max(0, S.inflight - 1);
|
|
627
546
|
drainQueue();
|
|
628
547
|
};
|
|
629
548
|
const timer = setTimeout(release, TIMING.SHOW_TIMEOUT_MS);
|
|
@@ -631,52 +550,31 @@
|
|
|
631
550
|
requestAnimationFrame(() => {
|
|
632
551
|
try {
|
|
633
552
|
if (isBlocked()) { clearTimeout(timer); return release(); }
|
|
634
|
-
|
|
635
553
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
636
|
-
if (!ph?.isConnected) {
|
|
637
|
-
state.phState.delete(id);
|
|
638
|
-
clearTimeout(timer);
|
|
639
|
-
return release();
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (isFilled(ph) || isPlaceholderUsed(ph)) {
|
|
643
|
-
state.phState.set(id, 'shown');
|
|
644
|
-
clearTimeout(timer);
|
|
645
|
-
return release();
|
|
646
|
-
}
|
|
554
|
+
if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
|
|
647
555
|
|
|
648
556
|
const t = now();
|
|
649
|
-
if (t - (
|
|
650
|
-
|
|
651
|
-
return release();
|
|
652
|
-
}
|
|
653
|
-
state.lastShow.set(id, t);
|
|
557
|
+
if (t - (S.lastShow.get(id) ?? 0) < TIMING.SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
558
|
+
S.lastShow.set(id, t);
|
|
654
559
|
|
|
655
|
-
|
|
656
|
-
|
|
560
|
+
const wrap = ph.closest(`.${WRAP_CLASS}`);
|
|
561
|
+
try { wrap?.setAttribute(ATTR.SHOWN, String(t)); } catch (_) {}
|
|
657
562
|
|
|
658
563
|
window.ezstandalone = window.ezstandalone || {};
|
|
659
564
|
const ez = window.ezstandalone;
|
|
660
|
-
|
|
661
565
|
const doShow = () => {
|
|
662
|
-
const wrap = ph.closest(`.${WRAP_CLASS}`);
|
|
663
566
|
try { ez.showAds(id); } catch (_) {}
|
|
664
567
|
if (wrap) scheduleUncollapseChecks(wrap);
|
|
665
568
|
scheduleEmptyCheck(id, t);
|
|
666
569
|
setTimeout(() => { clearTimeout(timer); release(); }, TIMING.SHOW_RELEASE_MS);
|
|
667
570
|
};
|
|
668
|
-
|
|
669
571
|
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
670
|
-
} catch (_) {
|
|
671
|
-
clearTimeout(timer);
|
|
672
|
-
release();
|
|
673
|
-
}
|
|
572
|
+
} catch (_) { clearTimeout(timer); release(); }
|
|
674
573
|
});
|
|
675
574
|
}
|
|
676
575
|
|
|
677
576
|
function scheduleEmptyCheck(id, showTs) {
|
|
678
|
-
|
|
679
|
-
for (const delay of [TIMING.EMPTY_CHECK_EARLY_MS, TIMING.EMPTY_CHECK_LATE_MS]) {
|
|
577
|
+
for (const delay of [TIMING.EMPTY_CHECK_MS_1, TIMING.EMPTY_CHECK_MS_2]) {
|
|
680
578
|
setTimeout(() => {
|
|
681
579
|
try {
|
|
682
580
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
@@ -684,9 +582,8 @@
|
|
|
684
582
|
if (!wrap || !ph?.isConnected) return;
|
|
685
583
|
if (parseInt(wrap.getAttribute(ATTR.SHOWN) || '0', 10) > showTs) return;
|
|
686
584
|
if (clearEmptyIfFilled(wrap)) return;
|
|
687
|
-
// Don't collapse if
|
|
585
|
+
// Don't collapse if GPT slot exists (still loading)
|
|
688
586
|
if (ph.querySelector('[id^="div-gpt-ad"]')) return;
|
|
689
|
-
// Don't collapse if placeholder has meaningful height
|
|
690
587
|
if (ph.offsetHeight > 10) return;
|
|
691
588
|
wrap.classList.add('is-empty');
|
|
692
589
|
} catch (_) {}
|
|
@@ -695,10 +592,7 @@
|
|
|
695
592
|
}
|
|
696
593
|
|
|
697
594
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
698
|
-
//
|
|
699
|
-
// Intercepts ez.showAds() to filter disconnected placeholders and
|
|
700
|
-
// block calls during navigation. Matches v50 behavior: individual calls,
|
|
701
|
-
// no batching.
|
|
595
|
+
// Matches v50: individual calls, no batching, pass-through no-arg calls.
|
|
702
596
|
|
|
703
597
|
function patchShowAds() {
|
|
704
598
|
const apply = () => {
|
|
@@ -707,10 +601,8 @@
|
|
|
707
601
|
const ez = window.ezstandalone;
|
|
708
602
|
if (window.__nbbEzPatched || typeof ez.showAds !== 'function') return;
|
|
709
603
|
window.__nbbEzPatched = true;
|
|
710
|
-
|
|
711
604
|
const orig = ez.showAds.bind(ez);
|
|
712
605
|
ez.showAds = function (...args) {
|
|
713
|
-
// No-arg call = Ezoic internal page refresh — pass through
|
|
714
606
|
if (args.length === 0) return orig();
|
|
715
607
|
if (isBlocked()) return;
|
|
716
608
|
const ids = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
|
|
@@ -725,7 +617,6 @@
|
|
|
725
617
|
};
|
|
726
618
|
} catch (_) {}
|
|
727
619
|
};
|
|
728
|
-
|
|
729
620
|
apply();
|
|
730
621
|
if (!window.__nbbEzPatched) {
|
|
731
622
|
window.ezstandalone = window.ezstandalone || {};
|
|
@@ -737,6 +628,7 @@
|
|
|
737
628
|
|
|
738
629
|
async function runCore() {
|
|
739
630
|
if (isBlocked()) return 0;
|
|
631
|
+
patchShowAds();
|
|
740
632
|
try { gcDisconnectedWraps(); } catch (_) {}
|
|
741
633
|
|
|
742
634
|
const cfg = await fetchConfig();
|
|
@@ -753,32 +645,26 @@
|
|
|
753
645
|
};
|
|
754
646
|
|
|
755
647
|
if (kind === 'topic') {
|
|
756
|
-
return exec(
|
|
757
|
-
|
|
758
|
-
cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts'
|
|
759
|
-
);
|
|
648
|
+
return exec('ezoic-ad-message', getPosts,
|
|
649
|
+
cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts');
|
|
760
650
|
}
|
|
761
651
|
if (kind === 'categoryTopics') {
|
|
762
652
|
pruneOrphansBetween();
|
|
763
|
-
return exec(
|
|
764
|
-
|
|
765
|
-
cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics'
|
|
766
|
-
);
|
|
653
|
+
return exec('ezoic-ad-between', getTopics,
|
|
654
|
+
cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics');
|
|
767
655
|
}
|
|
768
|
-
return exec(
|
|
769
|
-
'
|
|
770
|
-
cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories'
|
|
771
|
-
);
|
|
656
|
+
return exec('ezoic-ad-categories', getCategories,
|
|
657
|
+
cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories');
|
|
772
658
|
}
|
|
773
659
|
|
|
774
660
|
// ── Scheduler & burst ──────────────────────────────────────────────────────
|
|
775
661
|
|
|
776
662
|
function scheduleRun(cb) {
|
|
777
|
-
if (
|
|
778
|
-
|
|
663
|
+
if (S.runQueued) return;
|
|
664
|
+
S.runQueued = true;
|
|
779
665
|
requestAnimationFrame(async () => {
|
|
780
|
-
|
|
781
|
-
if (
|
|
666
|
+
S.runQueued = false;
|
|
667
|
+
if (S.pageKey && pageKey() !== S.pageKey) return;
|
|
782
668
|
let n = 0;
|
|
783
669
|
try { n = await runCore(); } catch (_) {}
|
|
784
670
|
try { cb?.(n); } catch (_) {}
|
|
@@ -788,21 +674,20 @@
|
|
|
788
674
|
function requestBurst() {
|
|
789
675
|
if (isBlocked()) return;
|
|
790
676
|
const t = now();
|
|
791
|
-
if (t -
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if (
|
|
796
|
-
|
|
797
|
-
|
|
677
|
+
if (t - S.lastBurstTs < TIMING.BURST_COOLDOWN_MS) return;
|
|
678
|
+
S.lastBurstTs = t;
|
|
679
|
+
S.pageKey = pageKey();
|
|
680
|
+
S.burstDeadline = t + BURST_WINDOW_MS;
|
|
681
|
+
if (S.burstActive) return;
|
|
682
|
+
S.burstActive = true;
|
|
683
|
+
S.burstCount = 0;
|
|
798
684
|
const step = () => {
|
|
799
|
-
if (pageKey() !==
|
|
800
|
-
|
|
801
|
-
return;
|
|
685
|
+
if (pageKey() !== S.pageKey || isBlocked() || now() > S.burstDeadline || S.burstCount >= MAX_BURST_STEPS) {
|
|
686
|
+
S.burstActive = false; return;
|
|
802
687
|
}
|
|
803
|
-
|
|
688
|
+
S.burstCount++;
|
|
804
689
|
scheduleRun(n => {
|
|
805
|
-
if (!n && !
|
|
690
|
+
if (!n && !S.pending.length) { S.burstActive = false; return; }
|
|
806
691
|
setTimeout(step, n > 0 ? 150 : 300);
|
|
807
692
|
});
|
|
808
693
|
};
|
|
@@ -812,37 +697,32 @@
|
|
|
812
697
|
// ── Cleanup on navigation ──────────────────────────────────────────────────
|
|
813
698
|
|
|
814
699
|
function cleanup() {
|
|
815
|
-
|
|
816
|
-
|
|
700
|
+
S.blockedUntil = now() + TIMING.BLOCK_DURATION_MS;
|
|
817
701
|
mutate(() => {
|
|
818
702
|
for (const w of document.querySelectorAll(`.${WRAP_CLASS}`)) dropWrap(w);
|
|
819
703
|
});
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
state.runQueued = false;
|
|
835
|
-
state.firstShown = false;
|
|
704
|
+
S.cfg = null;
|
|
705
|
+
S.poolsReady = false;
|
|
706
|
+
S.pools = { topics: [], posts: [], categories: [] };
|
|
707
|
+
S.cursors = { topics: 0, posts: 0, categories: 0 };
|
|
708
|
+
S.mountedIds.clear();
|
|
709
|
+
S.lastShow.clear();
|
|
710
|
+
S.wrapByKey.clear();
|
|
711
|
+
S.wrapsByClass.clear();
|
|
712
|
+
S.kind = null;
|
|
713
|
+
S.inflight = 0;
|
|
714
|
+
S.pending = [];
|
|
715
|
+
S.pendingSet.clear();
|
|
716
|
+
S.burstActive = false;
|
|
717
|
+
S.runQueued = false;
|
|
836
718
|
}
|
|
837
719
|
|
|
838
720
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
839
721
|
|
|
840
722
|
function ensureDomObserver() {
|
|
841
|
-
if (
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
if (state.mutGuard > 0 || isBlocked()) return;
|
|
845
|
-
|
|
723
|
+
if (S.domObs) return;
|
|
724
|
+
S.domObs = new MutationObserver(muts => {
|
|
725
|
+
if (S.mutGuard > 0 || isBlocked()) return;
|
|
846
726
|
let needsBurst = false;
|
|
847
727
|
const kind = getKind();
|
|
848
728
|
const relevantSels =
|
|
@@ -850,10 +730,8 @@
|
|
|
850
730
|
kind === 'categoryTopics' ? [SEL.topic] :
|
|
851
731
|
kind === 'categories' ? [SEL.category] :
|
|
852
732
|
[SEL.post, SEL.topic, SEL.category];
|
|
853
|
-
|
|
854
733
|
for (const m of muts) {
|
|
855
734
|
if (m.type !== 'childList') continue;
|
|
856
|
-
|
|
857
735
|
// Free IDs from wraps removed by NodeBB virtualization
|
|
858
736
|
for (const node of m.removedNodes) {
|
|
859
737
|
if (!(node instanceof Element)) continue;
|
|
@@ -862,15 +740,13 @@
|
|
|
862
740
|
dropWrap(node);
|
|
863
741
|
} else {
|
|
864
742
|
const wraps = node.querySelectorAll?.(`.${WRAP_CLASS}`);
|
|
865
|
-
if (wraps?.length)
|
|
743
|
+
if (wraps?.length) for (const w of wraps) dropWrap(w);
|
|
866
744
|
}
|
|
867
745
|
} catch (_) {}
|
|
868
746
|
}
|
|
869
|
-
|
|
870
747
|
for (const node of m.addedNodes) {
|
|
871
748
|
if (!(node instanceof Element)) continue;
|
|
872
|
-
|
|
873
|
-
// Ad fill detection
|
|
749
|
+
// Ad fill detection → uncollapse
|
|
874
750
|
try {
|
|
875
751
|
if (node.matches?.(FILL_SEL) || node.querySelector?.(FILL_SEL)) {
|
|
876
752
|
const wrap = node.closest?.(`.${WRAP_CLASS}.is-empty`) ||
|
|
@@ -878,20 +754,19 @@
|
|
|
878
754
|
if (wrap) clearEmptyIfFilled(wrap);
|
|
879
755
|
}
|
|
880
756
|
} catch (_) {}
|
|
881
|
-
|
|
882
757
|
// Re-observe wraps re-inserted by NodeBB virtualization
|
|
883
758
|
try {
|
|
884
|
-
const
|
|
759
|
+
const reinserted = node.classList?.contains(WRAP_CLASS)
|
|
885
760
|
? [node]
|
|
886
761
|
: Array.from(node.querySelectorAll?.(`.${WRAP_CLASS}`) || []);
|
|
887
|
-
for (const wrap of
|
|
762
|
+
for (const wrap of reinserted) {
|
|
888
763
|
const id = parseInt(wrap.getAttribute(ATTR.WRAPID), 10);
|
|
889
|
-
if (
|
|
890
|
-
|
|
891
|
-
|
|
764
|
+
if (id > 0) {
|
|
765
|
+
const ph = wrap.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
766
|
+
if (ph) try { getIO()?.observe(ph); } catch (_) {}
|
|
767
|
+
}
|
|
892
768
|
}
|
|
893
769
|
} catch (_) {}
|
|
894
|
-
|
|
895
770
|
// New content detection
|
|
896
771
|
if (!needsBurst) {
|
|
897
772
|
for (const sel of relevantSels) {
|
|
@@ -903,29 +778,23 @@
|
|
|
903
778
|
}
|
|
904
779
|
if (needsBurst) break;
|
|
905
780
|
}
|
|
906
|
-
|
|
907
781
|
if (needsBurst) requestBurst();
|
|
908
782
|
});
|
|
909
|
-
|
|
910
|
-
try {
|
|
911
|
-
state.domObs.observe(document.body, { childList: true, subtree: true });
|
|
912
|
-
} catch (_) {}
|
|
783
|
+
try { S.domObs.observe(document.body, { childList: true, subtree: true }); } catch (_) {}
|
|
913
784
|
}
|
|
914
785
|
|
|
915
|
-
// ── TCF / CMP Protection
|
|
786
|
+
// ── TCF / CMP Protection ───────────────────────────────────────────────────
|
|
916
787
|
|
|
917
788
|
function ensureTcfLocator() {
|
|
918
789
|
if (!window.__tcfapi && !window.__cmp) return;
|
|
919
|
-
|
|
920
790
|
const LOCATOR_ID = '__tcfapiLocator';
|
|
921
|
-
|
|
922
791
|
const ensureInHead = () => {
|
|
923
792
|
let existing = document.getElementById(LOCATOR_ID);
|
|
924
793
|
if (existing) {
|
|
925
794
|
if (existing.parentElement !== document.head) {
|
|
926
795
|
try { document.head.appendChild(existing); } catch (_) {}
|
|
927
796
|
}
|
|
928
|
-
return
|
|
797
|
+
return;
|
|
929
798
|
}
|
|
930
799
|
const f = document.createElement('iframe');
|
|
931
800
|
f.style.display = 'none';
|
|
@@ -933,9 +802,7 @@
|
|
|
933
802
|
try { document.head.appendChild(f); } catch (_) {
|
|
934
803
|
(document.body || document.documentElement).appendChild(f);
|
|
935
804
|
}
|
|
936
|
-
return f;
|
|
937
805
|
};
|
|
938
|
-
|
|
939
806
|
ensureInHead();
|
|
940
807
|
|
|
941
808
|
if (!window.__nbbCmpGuarded) {
|
|
@@ -977,12 +844,8 @@
|
|
|
977
844
|
window.__nbbTcfObs.observe(document.body || document.documentElement, {
|
|
978
845
|
childList: true, subtree: false,
|
|
979
846
|
});
|
|
980
|
-
} catch (_) {}
|
|
981
|
-
try {
|
|
982
847
|
if (document.head) {
|
|
983
|
-
window.__nbbTcfObs.observe(document.head, {
|
|
984
|
-
childList: true, subtree: false,
|
|
985
|
-
});
|
|
848
|
+
window.__nbbTcfObs.observe(document.head, { childList: true, subtree: false });
|
|
986
849
|
}
|
|
987
850
|
} catch (_) {}
|
|
988
851
|
}
|
|
@@ -1003,8 +866,7 @@
|
|
|
1003
866
|
window.__nbbAriaObs = new MutationObserver(remove);
|
|
1004
867
|
try {
|
|
1005
868
|
window.__nbbAriaObs.observe(document.body, {
|
|
1006
|
-
attributes: true,
|
|
1007
|
-
attributeFilter: ['aria-hidden'],
|
|
869
|
+
attributes: true, attributeFilter: ['aria-hidden'],
|
|
1008
870
|
});
|
|
1009
871
|
} catch (_) {}
|
|
1010
872
|
}
|
|
@@ -1014,8 +876,7 @@
|
|
|
1014
876
|
function muteConsole() {
|
|
1015
877
|
if (window.__nbbEzMuted) return;
|
|
1016
878
|
window.__nbbEzMuted = true;
|
|
1017
|
-
|
|
1018
|
-
const PREFIXES = [
|
|
879
|
+
const MUTED = [
|
|
1019
880
|
'[EzoicAds JS]: Placeholder Id',
|
|
1020
881
|
'No valid placeholders for loadMore',
|
|
1021
882
|
'cannot call refresh on the same page',
|
|
@@ -1024,22 +885,22 @@
|
|
|
1024
885
|
'[CMP] Error in custom getTCData',
|
|
1025
886
|
'vignette: no interstitial API',
|
|
1026
887
|
'Ezoic JS-Enable should only ever',
|
|
1027
|
-
|
|
1028
|
-
|
|
888
|
+
'### sending slotDestroyed',
|
|
889
|
+
'Error loading identity bridging',
|
|
1029
890
|
`with id ${PH_PREFIX}`,
|
|
1030
891
|
'adsbygoogle.push() error: All',
|
|
1031
892
|
'has already been defined',
|
|
1032
893
|
'bad response. Status',
|
|
894
|
+
'slotDestroyed event',
|
|
895
|
+
'identity bridging',
|
|
1033
896
|
];
|
|
1034
|
-
|
|
1035
897
|
for (const method of ['log', 'info', 'warn', 'error']) {
|
|
1036
898
|
const orig = console[method];
|
|
1037
899
|
if (typeof orig !== 'function') continue;
|
|
1038
900
|
console[method] = function (...args) {
|
|
1039
901
|
if (typeof args[0] === 'string') {
|
|
1040
902
|
const msg = args[0];
|
|
1041
|
-
for (const p of
|
|
1042
|
-
for (const p of PATTERNS) { if (msg.includes(p)) return; }
|
|
903
|
+
for (const p of MUTED) { if (msg.includes(p)) return; }
|
|
1043
904
|
}
|
|
1044
905
|
return orig.apply(console, args);
|
|
1045
906
|
};
|
|
@@ -1048,28 +909,25 @@
|
|
|
1048
909
|
|
|
1049
910
|
// ── Network warmup ─────────────────────────────────────────────────────────
|
|
1050
911
|
|
|
1051
|
-
|
|
1052
|
-
|
|
912
|
+
const _warmed = new Set();
|
|
1053
913
|
function warmNetwork() {
|
|
1054
|
-
if (_networkWarmed) return;
|
|
1055
|
-
_networkWarmed = true;
|
|
1056
914
|
const head = document.head;
|
|
1057
915
|
if (!head) return;
|
|
1058
|
-
const
|
|
916
|
+
for (const [rel, href, cors] of [
|
|
1059
917
|
['preconnect', 'https://g.ezoic.net', true ],
|
|
1060
918
|
['preconnect', 'https://go.ezoic.net', true ],
|
|
1061
919
|
['preconnect', 'https://securepubads.g.doubleclick.net', true ],
|
|
1062
920
|
['preconnect', 'https://pagead2.googlesyndication.com', true ],
|
|
1063
921
|
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
1064
922
|
['dns-prefetch', 'https://securepubads.g.doubleclick.net', false],
|
|
1065
|
-
]
|
|
1066
|
-
|
|
1067
|
-
if (
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
if (cors)
|
|
1072
|
-
head.appendChild(
|
|
923
|
+
]) {
|
|
924
|
+
const k = `${rel}|${href}`;
|
|
925
|
+
if (_warmed.has(k)) continue;
|
|
926
|
+
_warmed.add(k);
|
|
927
|
+
const l = document.createElement('link');
|
|
928
|
+
l.rel = rel; l.href = href;
|
|
929
|
+
if (cors) l.crossOrigin = 'anonymous';
|
|
930
|
+
head.appendChild(l);
|
|
1073
931
|
}
|
|
1074
932
|
}
|
|
1075
933
|
|
|
@@ -1078,47 +936,30 @@
|
|
|
1078
936
|
function bindNodeBB() {
|
|
1079
937
|
const $ = window.jQuery;
|
|
1080
938
|
if (!$) return;
|
|
1081
|
-
|
|
1082
939
|
$(window).off('.nbbEzoic');
|
|
1083
|
-
|
|
1084
|
-
// Cleanup on every navigation, same as v50
|
|
1085
940
|
$(window).on('action:ajaxify.start.nbbEzoic', cleanup);
|
|
1086
|
-
|
|
1087
941
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
ensureTcfLocator();
|
|
1094
|
-
protectAriaHidden();
|
|
1095
|
-
warmNetwork();
|
|
1096
|
-
patchShowAds();
|
|
1097
|
-
getIO();
|
|
1098
|
-
ensureDomObserver();
|
|
942
|
+
S.pageKey = pageKey();
|
|
943
|
+
S.kind = null;
|
|
944
|
+
S.blockedUntil = 0;
|
|
945
|
+
muteConsole(); ensureTcfLocator(); protectAriaHidden();
|
|
946
|
+
warmNetwork(); patchShowAds(); getIO(); ensureDomObserver();
|
|
1099
947
|
requestBurst();
|
|
1100
948
|
});
|
|
1101
949
|
|
|
1102
950
|
const burstEvents = [
|
|
1103
|
-
'action:ajaxify.contentLoaded',
|
|
1104
|
-
'action:
|
|
1105
|
-
'action:
|
|
1106
|
-
'action:categories.loaded',
|
|
1107
|
-
'action:category.loaded',
|
|
1108
|
-
'action:topic.loaded',
|
|
951
|
+
'action:ajaxify.contentLoaded', 'action:posts.loaded',
|
|
952
|
+
'action:topics.loaded', 'action:categories.loaded',
|
|
953
|
+
'action:category.loaded', 'action:topic.loaded',
|
|
1109
954
|
].map(e => `${e}.nbbEzoic`).join(' ');
|
|
1110
|
-
|
|
1111
955
|
$(window).on(burstEvents, () => { if (!isBlocked()) requestBurst(); });
|
|
1112
956
|
|
|
1113
957
|
try {
|
|
1114
958
|
require(['hooks'], hooks => {
|
|
1115
959
|
if (typeof hooks?.on !== 'function') return;
|
|
1116
960
|
for (const ev of [
|
|
1117
|
-
'action:ajaxify.end',
|
|
1118
|
-
'action:
|
|
1119
|
-
'action:topics.loaded',
|
|
1120
|
-
'action:categories.loaded',
|
|
1121
|
-
'action:topic.loaded',
|
|
961
|
+
'action:ajaxify.end', 'action:posts.loaded', 'action:topics.loaded',
|
|
962
|
+
'action:categories.loaded', 'action:topic.loaded',
|
|
1122
963
|
]) {
|
|
1123
964
|
try { hooks.on(ev, () => { if (!isBlocked()) requestBurst(); }); } catch (_) {}
|
|
1124
965
|
}
|
|
@@ -1131,16 +972,13 @@
|
|
|
1131
972
|
window.addEventListener('scroll', () => {
|
|
1132
973
|
if (ticking) return;
|
|
1133
974
|
ticking = true;
|
|
1134
|
-
requestAnimationFrame(() => {
|
|
1135
|
-
ticking = false;
|
|
1136
|
-
requestBurst();
|
|
1137
|
-
});
|
|
975
|
+
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
1138
976
|
}, { passive: true });
|
|
1139
977
|
}
|
|
1140
978
|
|
|
1141
979
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
1142
980
|
|
|
1143
|
-
|
|
981
|
+
S.pageKey = pageKey();
|
|
1144
982
|
muteConsole();
|
|
1145
983
|
ensureTcfLocator();
|
|
1146
984
|
protectAriaHidden();
|
|
@@ -1150,7 +988,7 @@
|
|
|
1150
988
|
ensureDomObserver();
|
|
1151
989
|
bindNodeBB();
|
|
1152
990
|
bindScroll();
|
|
1153
|
-
|
|
991
|
+
S.blockedUntil = 0;
|
|
1154
992
|
requestBurst();
|
|
1155
993
|
|
|
1156
994
|
})();
|