nodebb-plugin-ezoic-infinite 1.5.61 → 1.5.62
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 +83 -383
- 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;
|
|
547
|
-
}
|
|
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;
|
|
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;
|
|
555
318
|
}
|
|
556
|
-
return false;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
319
|
|
|
560
320
|
|
|
561
321
|
function getWrapIdFromWrap(wrap) {
|
|
@@ -570,20 +330,9 @@ function withInternalDomChange(fn) {
|
|
|
570
330
|
|
|
571
331
|
function safeDestroyById(id) {
|
|
572
332
|
try {
|
|
573
|
-
const domId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
574
|
-
const ph = document.getElementById(domId);
|
|
575
|
-
|
|
576
|
-
// If the element is already gone, do NOT call destroyPlaceholders (Ezoic will log "does not exist").
|
|
577
|
-
if (!ph || !ph.isConnected) return;
|
|
578
|
-
|
|
579
|
-
if (ph.dataset) {
|
|
580
|
-
delete ph.dataset.ezDefined;
|
|
581
|
-
delete ph.dataset.ezRequested;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
333
|
const ez = window.ezstandalone;
|
|
585
334
|
if (ez && typeof ez.destroyPlaceholders === 'function') {
|
|
586
|
-
ez.destroyPlaceholders([
|
|
335
|
+
ez.destroyPlaceholders([`${PLACEHOLDER_PREFIX}${id}`]);
|
|
587
336
|
}
|
|
588
337
|
} catch (e) {}
|
|
589
338
|
}
|
|
@@ -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,42 @@ 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}`);
|
|
411
|
+
if (existingPh && existingPh.isConnected) return null;
|
|
664
412
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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);
|
|
413
|
+
insertingIds.add(id);
|
|
414
|
+
try {
|
|
415
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
416
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
417
|
+
return wrap;
|
|
418
|
+
} finally {
|
|
419
|
+
insertingIds.delete(id);
|
|
420
|
+
}
|
|
683
421
|
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
422
|
|
|
687
423
|
function pickIdFromAll(allIds, cursorKey) {
|
|
688
424
|
const n = allIds.length;
|
|
@@ -695,7 +431,7 @@ function buildWrap(id, kindClass, afterPos, afterPid) {
|
|
|
695
431
|
|
|
696
432
|
const id = allIds[idx];
|
|
697
433
|
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
698
|
-
if (ph &&
|
|
434
|
+
if (ph && ph.isConnected) continue;
|
|
699
435
|
|
|
700
436
|
return id;
|
|
701
437
|
}
|
|
@@ -723,7 +459,6 @@ function buildWrap(id, kindClass, afterPos, afterPid) {
|
|
|
723
459
|
if (ph && state.io) state.io.unobserve(ph);
|
|
724
460
|
} catch (e) {}
|
|
725
461
|
|
|
726
|
-
parkPlaceholderFromWrap(victim);
|
|
727
462
|
victim.remove();
|
|
728
463
|
return true;
|
|
729
464
|
} catch (e) {
|
|
@@ -760,11 +495,9 @@ function drainQueue() {
|
|
|
760
495
|
}
|
|
761
496
|
}
|
|
762
497
|
|
|
763
|
-
|
|
764
498
|
function startShow(id) {
|
|
765
|
-
if (!id) return;
|
|
499
|
+
if (!id || isBlocked()) return;
|
|
766
500
|
|
|
767
|
-
// Reserve an inflight slot, but ALWAYS release on any early exit.
|
|
768
501
|
state.inflight++;
|
|
769
502
|
let released = false;
|
|
770
503
|
const release = () => {
|
|
@@ -778,41 +511,30 @@ function startShow(id) {
|
|
|
778
511
|
|
|
779
512
|
requestAnimationFrame(() => {
|
|
780
513
|
try {
|
|
781
|
-
if (isBlocked())
|
|
514
|
+
if (isBlocked()) return;
|
|
782
515
|
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
if (!phNow || !phNow.isConnected) { clearTimeout(hardTimer); release(); return; }
|
|
516
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
517
|
+
if (!ph || !ph.isConnected) return;
|
|
786
518
|
|
|
787
519
|
const now2 = Date.now();
|
|
788
520
|
const last2 = state.lastShowById.get(id) || 0;
|
|
789
|
-
if (now2 - last2 < 900)
|
|
521
|
+
if (now2 - last2 < 900) return;
|
|
790
522
|
state.lastShowById.set(id, now2);
|
|
791
523
|
|
|
792
524
|
window.ezstandalone = window.ezstandalone || {};
|
|
793
525
|
const ez = window.ezstandalone;
|
|
794
526
|
|
|
795
527
|
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
|
-
}
|
|
528
|
+
// Do NOT call destroyPlaceholders here.
|
|
529
|
+
// In ajaxify + infinite scroll flows, Ezoic can be in the middle of a refresh cycle.
|
|
530
|
+
// Calling destroy on active placeholders is a common source of:
|
|
531
|
+
// - "HTML element ... does not exist"
|
|
532
|
+
// - "Placeholder Id ... already been defined"
|
|
533
|
+
// Prefer a straight showAds; Ezoic will refresh as needed.
|
|
534
|
+
try { ez.showAds(id); } catch (e) {}
|
|
535
|
+
try { markEmptyWrapper(id); } catch (e) {}
|
|
536
|
+
|
|
537
|
+
setTimeout(() => { clearTimeout(hardTimer); release(); }, 650);
|
|
816
538
|
};
|
|
817
539
|
|
|
818
540
|
if (Array.isArray(ez.cmd)) {
|
|
@@ -820,15 +542,13 @@ function startShow(id) {
|
|
|
820
542
|
} else {
|
|
821
543
|
doShow();
|
|
822
544
|
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
release();
|
|
545
|
+
} finally {
|
|
546
|
+
// If we returned early, hardTimer will release.
|
|
826
547
|
}
|
|
827
548
|
});
|
|
828
549
|
}
|
|
829
550
|
|
|
830
551
|
|
|
831
|
-
|
|
832
552
|
// ---------- preload / above-the-fold ----------
|
|
833
553
|
|
|
834
554
|
function ensurePreloadObserver() {
|
|
@@ -935,25 +655,6 @@ function startShow(id) {
|
|
|
935
655
|
inserted += 1;
|
|
936
656
|
}
|
|
937
657
|
|
|
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
658
|
return inserted;
|
|
958
659
|
}
|
|
959
660
|
|
|
@@ -1089,7 +790,6 @@ function startShow(id) {
|
|
|
1089
790
|
// remove all wrappers
|
|
1090
791
|
try {
|
|
1091
792
|
document.querySelectorAll(`.${WRAP_CLASS}`).forEach((el) => {
|
|
1092
|
-
try { parkPlaceholderFromWrap(el); } catch (e) {}
|
|
1093
793
|
try { el.remove(); } catch (e) {}
|
|
1094
794
|
});
|
|
1095
795
|
} 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
|
+
}
|