nodebb-plugin-ezoic-infinite 1.5.61 → 1.5.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/package.json +1 -1
- package/public/client.js +99 -388
- package/public/style.css +20 -10
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -4,136 +4,12 @@
|
|
|
4
4
|
// NodeBB client context
|
|
5
5
|
const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// IMPORTANT: must NOT collide with Ezoic's own markup (they use `.ezoic-ad`).
|
|
8
|
+
// If we reuse that class, cleanup/pruning can delete real ads and cause
|
|
9
|
+
// "placeholder does not exist" spam + broken 1/X insertion.
|
|
10
|
+
const WRAP_CLASS = 'nodebb-ezoic-wrap';
|
|
8
11
|
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
// Offscreen pool to keep placeholder elements alive across ajaxify/navigation.
|
|
12
|
-
// This prevents Ezoic from trying to define ids that are not currently injected,
|
|
13
|
-
// and eliminates "HTML element with id ... does not exist" noise.
|
|
14
|
-
const POOL_ID = 'ezoic-placeholder-pool';
|
|
15
|
-
|
|
16
|
-
function isInPool(el) {
|
|
17
|
-
try { return !!(el && el.closest && el.closest('#' + POOL_ID)); } catch (e) { return false; }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function isPlaceholderInUse(ph) {
|
|
21
|
-
// In use = connected AND not parked in our offscreen pool.
|
|
22
|
-
try { return !!(ph && ph.isConnected && !isInPool(ph)); } catch (e) { return false; }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function ensurePool() {
|
|
26
|
-
let pool = document.getElementById(POOL_ID);
|
|
27
|
-
if (pool) {
|
|
28
|
-
// In rare cases (aggressive SPA navigation), the pool may get detached.
|
|
29
|
-
if (!pool.isConnected) {
|
|
30
|
-
try { document.body.appendChild(pool); } catch (e) {}
|
|
31
|
-
}
|
|
32
|
-
return pool;
|
|
33
|
-
}
|
|
34
|
-
pool = document.createElement('div');
|
|
35
|
-
pool.id = POOL_ID;
|
|
36
|
-
pool.style.position = 'absolute';
|
|
37
|
-
pool.style.left = '-99999px';
|
|
38
|
-
pool.style.top = '0';
|
|
39
|
-
pool.style.width = '1px';
|
|
40
|
-
pool.style.height = '1px';
|
|
41
|
-
pool.style.overflow = 'hidden';
|
|
42
|
-
pool.setAttribute('aria-hidden', 'true');
|
|
43
|
-
// Attach early (documentElement exists before body), so placeholders are always connected.
|
|
44
|
-
try { document.body.appendChild(pool); } catch (e) {}
|
|
45
|
-
// If body exists later, move it under body (purely cosmetic).
|
|
46
|
-
try {
|
|
47
|
-
if (document.body && pool.parentNode !== document.body) {
|
|
48
|
-
document.body.appendChild(pool);
|
|
49
|
-
}
|
|
50
|
-
} catch (e) {}
|
|
51
|
-
return pool;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Create placeholder divs for all configured ids upfront.
|
|
55
|
-
// Ezoic sometimes attempts to initialize/refresh a range of ids even if we
|
|
56
|
-
// haven't injected them yet; keeping them in the offscreen pool prevents
|
|
57
|
-
// "HTML element ... does not exist" spam.
|
|
58
|
-
function primePool(ids) {
|
|
59
|
-
try {
|
|
60
|
-
if (!ids || !ids.length) return;
|
|
61
|
-
const pool = ensurePool();
|
|
62
|
-
for (const v of ids) {
|
|
63
|
-
const id = parseInt(v, 10);
|
|
64
|
-
if (!Number.isFinite(id) || id <= 0) continue;
|
|
65
|
-
const domId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
66
|
-
let ph = document.getElementById(domId);
|
|
67
|
-
if (!ph) {
|
|
68
|
-
ph = document.createElement('div');
|
|
69
|
-
ph.id = domId;
|
|
70
|
-
ph.setAttribute('data-ezoic-id', String(id));
|
|
71
|
-
pool.appendChild(ph);
|
|
72
|
-
} else if (!ph.isConnected) {
|
|
73
|
-
pool.appendChild(ph);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
} catch (e) {}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// Prime a continuous id range (inclusive) into the offscreen pool.
|
|
81
|
-
// Ezoic sometimes references ids outside our configured list (e.g. +N); a small buffer prevents "does not exist" spam.
|
|
82
|
-
function primePoolRange(minId, maxId) {
|
|
83
|
-
try {
|
|
84
|
-
minId = parseInt(minId, 10);
|
|
85
|
-
maxId = parseInt(maxId, 10);
|
|
86
|
-
if (!Number.isFinite(minId) || !Number.isFinite(maxId)) return;
|
|
87
|
-
if (minId <= 0 || maxId <= 0) return;
|
|
88
|
-
if (maxId < minId) { const t = minId; minId = maxId; maxId = t; }
|
|
89
|
-
const span = Math.min(400, Math.max(0, maxId - minId)); // cap
|
|
90
|
-
const pool = ensurePool();
|
|
91
|
-
for (let id = minId; id <= minId + span; id += 1) {
|
|
92
|
-
const domId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
93
|
-
let ph = document.getElementById(domId);
|
|
94
|
-
if (!ph) {
|
|
95
|
-
ph = document.createElement('div');
|
|
96
|
-
ph.id = domId;
|
|
97
|
-
ph.setAttribute('data-ezoic-id', String(id));
|
|
98
|
-
pool.appendChild(ph);
|
|
99
|
-
} else if (!ph.isConnected) {
|
|
100
|
-
pool.appendChild(ph);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
} catch (e) {}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
function acquirePlaceholder(id) {
|
|
108
|
-
const domId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
109
|
-
let ph = document.getElementById(domId);
|
|
110
|
-
if (!ph) {
|
|
111
|
-
ph = document.createElement('div');
|
|
112
|
-
ph.id = domId;
|
|
113
|
-
ph.setAttribute('data-ezoic-id', String(id));
|
|
114
|
-
ensurePool().appendChild(ph);
|
|
115
|
-
}
|
|
116
|
-
// Note: appendChild will automatically move the node, no manual detach (avoids race gaps).
|
|
117
|
-
// Clear request/defined flags when reusing
|
|
118
|
-
try {
|
|
119
|
-
if (ph.dataset) {
|
|
120
|
-
ph.dataset.ezRequested = '0';
|
|
121
|
-
ph.dataset.ezDefined = '0';
|
|
122
|
-
ph.dataset.ezActive = '0';
|
|
123
|
-
}
|
|
124
|
-
} catch (e) {}
|
|
125
|
-
return ph;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function parkPlaceholderFromWrap(wrap) {
|
|
129
|
-
try {
|
|
130
|
-
const ph = wrap && wrap.querySelector ? wrap.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`) : null;
|
|
131
|
-
if (!ph) return;
|
|
132
|
-
try { if (state && state.io) state.io.unobserve(ph); } catch (e) {}
|
|
133
|
-
try { if (ph.dataset) ph.dataset.ezActive = '0'; } catch (e) {}
|
|
134
|
-
ensurePool().appendChild(ph);
|
|
135
|
-
} catch (e) {}
|
|
136
|
-
}
|
|
137
13
|
// Insert at most N ads per run to keep the UI smooth on infinite scroll
|
|
138
14
|
const MAX_INSERTS_PER_RUN = 3;
|
|
139
15
|
|
|
@@ -346,107 +222,34 @@ function parkPlaceholderFromWrap(wrap) {
|
|
|
346
222
|
} catch (e) {}
|
|
347
223
|
}
|
|
348
224
|
|
|
349
|
-
|
|
350
225
|
// Patch showAds to avoid warnings when a placeholder disappears (infinite scroll, ajaxify)
|
|
351
|
-
// Ezoic can (re)define ezstandalone.showAds over time; we hook assignments to keep the guard.
|
|
352
226
|
function patchShowAds() {
|
|
353
|
-
|
|
354
|
-
if (orig && orig.__nodebbWrapped) return orig;
|
|
355
|
-
|
|
356
|
-
const wrapped = function (...args) {
|
|
357
|
-
if (isBlocked()) return;
|
|
358
|
-
|
|
359
|
-
let ids = [];
|
|
360
|
-
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
361
|
-
else ids = args;
|
|
362
|
-
|
|
363
|
-
const seen = new Set();
|
|
364
|
-
for (const v of ids) {
|
|
365
|
-
const id = parseInt(v, 10);
|
|
366
|
-
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
367
|
-
|
|
368
|
-
const domId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
369
|
-
let ph = document.getElementById(domId);
|
|
370
|
-
if (!ph || !ph.isConnected) {
|
|
371
|
-
// If Ezoic (or another script) tries to show an id we haven't injected yet,
|
|
372
|
-
// create the placeholder in the offscreen pool so it exists, but don't load.
|
|
373
|
-
try {
|
|
374
|
-
ph = document.createElement('div');
|
|
375
|
-
ph.id = domId;
|
|
376
|
-
ph.setAttribute('data-ezoic-id', String(id));
|
|
377
|
-
if (ph.dataset) ph.dataset.ezActive = '0';
|
|
378
|
-
ensurePool().appendChild(ph);
|
|
379
|
-
} catch (e) {}
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Only allow loads for placeholders actively injected into the page (not parked in the pool)
|
|
384
|
-
// and currently marked active.
|
|
385
|
-
if (!isPlaceholderInUse(ph)) continue;
|
|
386
|
-
if (ph.dataset && ph.dataset.ezActive !== '1') continue;
|
|
387
|
-
|
|
388
|
-
// Prevent repeated "define" attempts on the same placeholder while it remains in DOM.
|
|
389
|
-
if (ph.dataset && (ph.dataset.ezRequested === '1' || ph.dataset.ezDefined === '1')) continue;
|
|
390
|
-
|
|
391
|
-
seen.add(id);
|
|
392
|
-
try {
|
|
393
|
-
if (ph.dataset) ph.dataset.ezRequested = '1';
|
|
394
|
-
orig.call(ez, id);
|
|
395
|
-
if (ph.dataset) {
|
|
396
|
-
ph.dataset.ezDefined = '1';
|
|
397
|
-
ph.dataset.ezRequested = '0';
|
|
398
|
-
}
|
|
399
|
-
} catch (e) {
|
|
400
|
-
try { if (ph.dataset) ph.dataset.ezRequested = '0'; } catch (_) {}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
try { wrapped.__nodebbWrapped = true; } catch (e) {}
|
|
405
|
-
return wrapped;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function ensureHook() {
|
|
227
|
+
const applyPatch = () => {
|
|
409
228
|
try {
|
|
410
229
|
window.ezstandalone = window.ezstandalone || {};
|
|
411
230
|
const ez = window.ezstandalone;
|
|
231
|
+
if (window.__nodebbEzoicPatched) return;
|
|
232
|
+
if (typeof ez.showAds !== 'function') return;
|
|
412
233
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
ez.__nodebbShowAdsHooked = true;
|
|
416
|
-
let _showAds = ez.showAds;
|
|
417
|
-
|
|
418
|
-
Object.defineProperty(ez, 'showAds', {
|
|
419
|
-
configurable: true,
|
|
420
|
-
enumerable: true,
|
|
421
|
-
get() {
|
|
422
|
-
return _showAds;
|
|
423
|
-
},
|
|
424
|
-
set(fn) {
|
|
425
|
-
_showAds = (typeof fn === 'function') ? wrapShowAds(ez, fn) : fn;
|
|
426
|
-
},
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
// If showAds already exists, wrap it immediately
|
|
430
|
-
if (typeof _showAds === 'function') {
|
|
431
|
-
_showAds = wrapShowAds(ez, _showAds);
|
|
432
|
-
}
|
|
433
|
-
} else if (typeof ez.showAds === 'function' && !ez.showAds.__nodebbWrapped) {
|
|
434
|
-
// In case defineProperty wasn't possible (rare), re-wrap
|
|
435
|
-
ez.showAds = wrapShowAds(ez, ez.showAds);
|
|
436
|
-
}
|
|
437
|
-
} catch (e) {}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
ensureHook();
|
|
234
|
+
window.__nodebbEzoicPatched = true;
|
|
235
|
+
const orig = ez.showAds;
|
|
441
236
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
445
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
446
|
-
window.ezstandalone.cmd.push(ensureHook);
|
|
447
|
-
} catch (e) {}
|
|
448
|
-
}
|
|
237
|
+
ez.showAds = function (...args) {
|
|
238
|
+
if (isBlocked()) return;
|
|
449
239
|
|
|
240
|
+
let ids = [];
|
|
241
|
+
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
242
|
+
else ids = args;
|
|
243
|
+
|
|
244
|
+
const seen = new Set();
|
|
245
|
+
for (const v of ids) {
|
|
246
|
+
const id = parseInt(v, 10);
|
|
247
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
248
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
249
|
+
if (!ph || !ph.isConnected) continue;
|
|
250
|
+
seen.add(id);
|
|
251
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
252
|
+
}
|
|
450
253
|
};
|
|
451
254
|
} catch (e) {}
|
|
452
255
|
};
|
|
@@ -497,65 +300,22 @@ function withInternalDomChange(fn) {
|
|
|
497
300
|
}
|
|
498
301
|
|
|
499
302
|
function initPools(cfg) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
// Keep placeholders alive even before they're injected.
|
|
506
|
-
// Ezoic can reference ids slightly outside our configured lists (often sequential),
|
|
507
|
-
// so we prime a small buffered range to prevent "does not exist" spam.
|
|
508
|
-
const primeBuffered = (ids) => {
|
|
509
|
-
try {
|
|
510
|
-
if (!ids || !ids.length) return;
|
|
511
|
-
primePool(ids);
|
|
512
|
-
|
|
513
|
-
let min = Infinity, max = -Infinity;
|
|
514
|
-
for (const v of ids) {
|
|
515
|
-
const n = parseInt(v, 10);
|
|
516
|
-
if (!Number.isFinite(n) || n <= 0) continue;
|
|
517
|
-
if (n < min) min = n;
|
|
518
|
-
if (n > max) max = n;
|
|
519
|
-
}
|
|
520
|
-
if (!Number.isFinite(min) || !Number.isFinite(max)) return;
|
|
521
|
-
|
|
522
|
-
// small buffer in both directions; range prime is capped internally
|
|
523
|
-
primePoolRange(Math.max(1, min - 5), max + 120);
|
|
524
|
-
} catch (e) {}
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
primeBuffered(state.allTopics);
|
|
528
|
-
primeBuffered(state.allPosts);
|
|
529
|
-
primeBuffered(state.allCategories);
|
|
530
|
-
}
|
|
531
|
-
|
|
303
|
+
if (!cfg) return;
|
|
304
|
+
if (state.allTopics.length === 0) state.allTopics = parsePool(cfg.placeholderIds);
|
|
305
|
+
if (state.allPosts.length === 0) state.allPosts = parsePool(cfg.messagePlaceholderIds);
|
|
306
|
+
if (state.allCategories.length === 0) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
307
|
+
}
|
|
532
308
|
|
|
533
309
|
// ---------- insertion primitives ----------
|
|
534
310
|
|
|
535
311
|
function isAdjacentAd(target) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (!el) break;
|
|
543
|
-
if (isAdEl(el)) return true;
|
|
544
|
-
// Skip elements that are effectively spacers/dividers
|
|
545
|
-
const h = el.getBoundingClientRect ? el.getBoundingClientRect().height : 0;
|
|
546
|
-
if (h > 8) break;
|
|
312
|
+
if (!target) return false;
|
|
313
|
+
const next = target.nextElementSibling;
|
|
314
|
+
if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
|
|
315
|
+
const prev = target.previousElementSibling;
|
|
316
|
+
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
|
|
317
|
+
return false;
|
|
547
318
|
}
|
|
548
|
-
el = target;
|
|
549
|
-
for (let i = 0; i < 3; i += 1) {
|
|
550
|
-
el = el.previousElementSibling;
|
|
551
|
-
if (!el) break;
|
|
552
|
-
if (isAdEl(el)) return true;
|
|
553
|
-
const h = el.getBoundingClientRect ? el.getBoundingClientRect().height : 0;
|
|
554
|
-
if (h > 8) break;
|
|
555
|
-
}
|
|
556
|
-
return false;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
319
|
|
|
560
320
|
|
|
561
321
|
function getWrapIdFromWrap(wrap) {
|
|
@@ -569,23 +329,12 @@ function withInternalDomChange(fn) {
|
|
|
569
329
|
}
|
|
570
330
|
|
|
571
331
|
function safeDestroyById(id) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (ph.dataset) {
|
|
580
|
-
delete ph.dataset.ezDefined;
|
|
581
|
-
delete ph.dataset.ezRequested;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const ez = window.ezstandalone;
|
|
585
|
-
if (ez && typeof ez.destroyPlaceholders === 'function') {
|
|
586
|
-
ez.destroyPlaceholders([domId]);
|
|
587
|
-
}
|
|
588
|
-
} catch (e) {}
|
|
332
|
+
// IMPORTANT:
|
|
333
|
+
// Do NOT call ez.destroyPlaceholders here.
|
|
334
|
+
// In NodeBB ajaxify/infinite-scroll flows, Ezoic can be mid-refresh.
|
|
335
|
+
// Destroy calls can create churn, reduce fill, and generate "does not exist" spam.
|
|
336
|
+
// We only remove our wrapper; Ezoic manages slot lifecycle.
|
|
337
|
+
return;
|
|
589
338
|
}
|
|
590
339
|
|
|
591
340
|
function pruneOrphanWraps(kindClass, items) {
|
|
@@ -608,7 +357,6 @@ function withInternalDomChange(fn) {
|
|
|
608
357
|
withInternalDomChange(() => {
|
|
609
358
|
try {
|
|
610
359
|
if (id) safeDestroyById(id);
|
|
611
|
-
parkPlaceholderFromWrap(wrap);
|
|
612
360
|
wrap.remove();
|
|
613
361
|
} catch (e) {}
|
|
614
362
|
});
|
|
@@ -635,54 +383,53 @@ function withInternalDomChange(fn) {
|
|
|
635
383
|
}, 3500);
|
|
636
384
|
}
|
|
637
385
|
|
|
638
|
-
function buildWrap(id, kindClass, afterPos
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
wrap.style.width = '100%';
|
|
386
|
+
function buildWrap(id, kindClass, afterPos) {
|
|
387
|
+
const wrap = document.createElement('div');
|
|
388
|
+
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
389
|
+
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
390
|
+
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
391
|
+
wrap.style.width = '100%';
|
|
645
392
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
393
|
+
const ph = document.createElement('div');
|
|
394
|
+
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
395
|
+
ph.setAttribute('data-ezoic-id', String(id));
|
|
396
|
+
wrap.appendChild(ph);
|
|
649
397
|
|
|
650
|
-
|
|
651
|
-
}
|
|
398
|
+
return wrap;
|
|
399
|
+
}
|
|
652
400
|
|
|
401
|
+
function findWrap(kindClass, afterPos) {
|
|
402
|
+
return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
|
|
403
|
+
}
|
|
653
404
|
|
|
654
|
-
function
|
|
655
|
-
|
|
656
|
-
if (
|
|
657
|
-
|
|
658
|
-
if (w) return w;
|
|
659
|
-
}
|
|
660
|
-
} catch (e) {}
|
|
661
|
-
return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
|
|
662
|
-
}
|
|
405
|
+
function insertAfter(target, id, kindClass, afterPos) {
|
|
406
|
+
if (!target || !target.insertAdjacentElement) return null;
|
|
407
|
+
if (findWrap(kindClass, afterPos)) return null;
|
|
408
|
+
if (insertingIds.has(id)) return null;
|
|
663
409
|
|
|
410
|
+
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
664
411
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
if (insertingIds.has(id)) return null;
|
|
670
|
-
|
|
671
|
-
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
672
|
-
if (existingPh && isPlaceholderInUse(existingPh)) return null;
|
|
673
|
-
|
|
674
|
-
insertingIds.add(id);
|
|
675
|
-
try {
|
|
676
|
-
const wrap = buildWrap(id, kindClass, afterPos, afterPid);
|
|
677
|
-
target.insertAdjacentElement('afterend', wrap);
|
|
678
|
-
// Mark post as processed anchor to stabilize 1/X across infinite scroll reflows
|
|
679
|
-
try { if (afterPid) target.setAttribute('data-ezoic-anchored', '1'); } catch (e) {}
|
|
680
|
-
return wrap;
|
|
681
|
-
} finally {
|
|
682
|
-
insertingIds.delete(id);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
412
|
+
insertingIds.add(id);
|
|
413
|
+
try {
|
|
414
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
415
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
685
416
|
|
|
417
|
+
// If a placeholder with this id already exists elsewhere (some Ezoic flows
|
|
418
|
+
// pre-create placeholders), move it into our wrapper instead of aborting.
|
|
419
|
+
// replaceChild moves the node atomically (no detach window).
|
|
420
|
+
if (existingPh && existingPh !== wrap.firstElementChild) {
|
|
421
|
+
try {
|
|
422
|
+
existingPh.setAttribute('data-ezoic-id', String(id));
|
|
423
|
+
wrap.replaceChild(existingPh, wrap.firstElementChild);
|
|
424
|
+
} catch (e) {
|
|
425
|
+
// Keep the new placeholder if replace fails.
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return wrap;
|
|
429
|
+
} finally {
|
|
430
|
+
insertingIds.delete(id);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
686
433
|
|
|
687
434
|
function pickIdFromAll(allIds, cursorKey) {
|
|
688
435
|
const n = allIds.length;
|
|
@@ -695,7 +442,7 @@ function buildWrap(id, kindClass, afterPos, afterPid) {
|
|
|
695
442
|
|
|
696
443
|
const id = allIds[idx];
|
|
697
444
|
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
698
|
-
if (ph &&
|
|
445
|
+
if (ph && ph.isConnected) continue;
|
|
699
446
|
|
|
700
447
|
return id;
|
|
701
448
|
}
|
|
@@ -723,7 +470,6 @@ function buildWrap(id, kindClass, afterPos, afterPid) {
|
|
|
723
470
|
if (ph && state.io) state.io.unobserve(ph);
|
|
724
471
|
} catch (e) {}
|
|
725
472
|
|
|
726
|
-
parkPlaceholderFromWrap(victim);
|
|
727
473
|
victim.remove();
|
|
728
474
|
return true;
|
|
729
475
|
} catch (e) {
|
|
@@ -760,11 +506,9 @@ function drainQueue() {
|
|
|
760
506
|
}
|
|
761
507
|
}
|
|
762
508
|
|
|
763
|
-
|
|
764
509
|
function startShow(id) {
|
|
765
|
-
if (!id) return;
|
|
510
|
+
if (!id || isBlocked()) return;
|
|
766
511
|
|
|
767
|
-
// Reserve an inflight slot, but ALWAYS release on any early exit.
|
|
768
512
|
state.inflight++;
|
|
769
513
|
let released = false;
|
|
770
514
|
const release = () => {
|
|
@@ -778,41 +522,30 @@ function startShow(id) {
|
|
|
778
522
|
|
|
779
523
|
requestAnimationFrame(() => {
|
|
780
524
|
try {
|
|
781
|
-
if (isBlocked())
|
|
525
|
+
if (isBlocked()) return;
|
|
782
526
|
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
if (!phNow || !phNow.isConnected) { clearTimeout(hardTimer); release(); return; }
|
|
527
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
528
|
+
if (!ph || !ph.isConnected) return;
|
|
786
529
|
|
|
787
530
|
const now2 = Date.now();
|
|
788
531
|
const last2 = state.lastShowById.get(id) || 0;
|
|
789
|
-
if (now2 - last2 < 900)
|
|
532
|
+
if (now2 - last2 < 900) return;
|
|
790
533
|
state.lastShowById.set(id, now2);
|
|
791
534
|
|
|
792
535
|
window.ezstandalone = window.ezstandalone || {};
|
|
793
536
|
const ez = window.ezstandalone;
|
|
794
537
|
|
|
795
538
|
const doShow = () => {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// showAds is patched to ignore missing placeholders and repeated defines.
|
|
809
|
-
ez.showAds(id);
|
|
810
|
-
|
|
811
|
-
try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
|
|
812
|
-
} catch (e) {}
|
|
813
|
-
finally {
|
|
814
|
-
setTimeout(() => { try { clearTimeout(hardTimer); } catch (_) {} release(); }, 650);
|
|
815
|
-
}
|
|
539
|
+
// Do NOT call destroyPlaceholders here.
|
|
540
|
+
// In ajaxify + infinite scroll flows, Ezoic can be in the middle of a refresh cycle.
|
|
541
|
+
// Calling destroy on active placeholders is a common source of:
|
|
542
|
+
// - "HTML element ... does not exist"
|
|
543
|
+
// - "Placeholder Id ... already been defined"
|
|
544
|
+
// Prefer a straight showAds; Ezoic will refresh as needed.
|
|
545
|
+
try { ez.showAds(id); } catch (e) {}
|
|
546
|
+
try { markEmptyWrapper(id); } catch (e) {}
|
|
547
|
+
|
|
548
|
+
setTimeout(() => { clearTimeout(hardTimer); release(); }, 650);
|
|
816
549
|
};
|
|
817
550
|
|
|
818
551
|
if (Array.isArray(ez.cmd)) {
|
|
@@ -820,15 +553,13 @@ function startShow(id) {
|
|
|
820
553
|
} else {
|
|
821
554
|
doShow();
|
|
822
555
|
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
release();
|
|
556
|
+
} finally {
|
|
557
|
+
// If we returned early, hardTimer will release.
|
|
826
558
|
}
|
|
827
559
|
});
|
|
828
560
|
}
|
|
829
561
|
|
|
830
562
|
|
|
831
|
-
|
|
832
563
|
// ---------- preload / above-the-fold ----------
|
|
833
564
|
|
|
834
565
|
function ensurePreloadObserver() {
|
|
@@ -935,25 +666,6 @@ function startShow(id) {
|
|
|
935
666
|
inserted += 1;
|
|
936
667
|
}
|
|
937
668
|
|
|
938
|
-
// Safety: if DOM churn results in two consecutive ad wrappers, remove the latter.
|
|
939
|
-
// (This can happen when NodeBB inserts/removes spacer elements around the anchor.)
|
|
940
|
-
try {
|
|
941
|
-
const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`));
|
|
942
|
-
for (const w of wraps) {
|
|
943
|
-
const prev = w.previousElementSibling;
|
|
944
|
-
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
945
|
-
withInternalDomChange(() => {
|
|
946
|
-
try {
|
|
947
|
-
const id = getWrapIdFromWrap(w);
|
|
948
|
-
if (id) safeDestroyById(id);
|
|
949
|
-
parkPlaceholderFromWrap(w);
|
|
950
|
-
w.remove();
|
|
951
|
-
} catch (e) {}
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
} catch (e) {}
|
|
956
|
-
|
|
957
669
|
return inserted;
|
|
958
670
|
}
|
|
959
671
|
|
|
@@ -1089,7 +801,6 @@ function startShow(id) {
|
|
|
1089
801
|
// remove all wrappers
|
|
1090
802
|
try {
|
|
1091
803
|
document.querySelectorAll(`.${WRAP_CLASS}`).forEach((el) => {
|
|
1092
|
-
try { parkPlaceholderFromWrap(el); } catch (e) {}
|
|
1093
804
|
try { el.remove(); } catch (e) {}
|
|
1094
805
|
});
|
|
1095
806
|
} catch (e) {}
|
package/public/style.css
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
1
|
+
/*
|
|
2
|
+
Keep our NodeBB-inserted wrappers CLS-safe.
|
|
3
|
+
NOTE: must not rely on `.ezoic-ad` because Ezoic uses that class internally.
|
|
4
|
+
*/
|
|
5
|
+
.nodebb-ezoic-wrap {
|
|
3
6
|
display: block;
|
|
4
7
|
width: 100%;
|
|
5
8
|
margin: 0 !important;
|
|
@@ -7,22 +10,22 @@
|
|
|
7
10
|
overflow: hidden;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
.ezoic-
|
|
13
|
+
.nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"] {
|
|
11
14
|
margin: 0 !important;
|
|
12
15
|
padding: 0 !important;
|
|
13
16
|
min-height: 1px; /* keeps placeholder measurable for IO */
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
/* Ezoic
|
|
17
|
-
.ezoic-
|
|
18
|
-
.ezoic-
|
|
19
|
+
/* If Ezoic wraps inside our wrapper, keep it tight */
|
|
20
|
+
.nodebb-ezoic-wrap span.ezoic-ad,
|
|
21
|
+
.nodebb-ezoic-wrap .ezoic-ad {
|
|
19
22
|
margin: 0 !important;
|
|
20
23
|
padding: 0 !important;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
/* Collapse empty ad blocks (prevents "holes" when an ad doesn't fill or gets destroyed) */
|
|
25
|
-
.ezoic-
|
|
28
|
+
.nodebb-ezoic-wrap.is-empty {
|
|
26
29
|
display: block !important;
|
|
27
30
|
margin: 0 !important;
|
|
28
31
|
padding: 0 !important;
|
|
@@ -31,12 +34,19 @@
|
|
|
31
34
|
overflow: hidden !important;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
.ezoic-
|
|
37
|
+
.nodebb-ezoic-wrap {
|
|
35
38
|
min-height: 0 !important;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
.ezoic-
|
|
41
|
+
.nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"] {
|
|
39
42
|
min-height: 0 !important;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
/*
|
|
46
|
+
Optional: also neutralize spacing on native Ezoic `.ezoic-ad` blocks.
|
|
47
|
+
(Keeps your previous "CSS very good" behavior.)
|
|
48
|
+
*/
|
|
49
|
+
.ezoic-ad {
|
|
50
|
+
margin: 0 !important;
|
|
51
|
+
padding: 0 !important;
|
|
52
|
+
}
|