nodebb-plugin-ezoic-infinite 1.4.96 → 1.4.97
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 +230 -108
- package/public/style.css +13 -1
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
categoryItem: 'li[component="categories/category"]',
|
|
8
8
|
}, WRAP_CLASS = 'ezoic-ad';
|
|
9
9
|
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-', MAX_INSERTS_PER_RUN = 3;
|
|
10
|
+
// Ultra-fast: preload ads before they enter viewport (especially above-the-fold)
|
|
11
|
+
const PRELOAD_ROOT_MARGIN = '1200px 0px';
|
|
12
|
+
const ABOVE_FOLD_MULT = 1.5; // render immediately if within 1.5× viewport height
|
|
13
|
+
const FAST_START_MS = 2500; // aggressive preload window after page load
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
// FLAG GLOBAL: Bloquer Ezoic pendant navigation
|
|
12
17
|
let EZOIC_BLOCKED = false;
|
|
@@ -37,7 +42,43 @@
|
|
|
37
42
|
|
|
38
43
|
obs: null,
|
|
39
44
|
activeTimeouts: new Set(),
|
|
40
|
-
lastScrollRun: 0,
|
|
45
|
+
lastScrollRun: 0,
|
|
46
|
+
io: null,
|
|
47
|
+
fastStartUntil: 0,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// Track currently inserted ad wrappers for recycling
|
|
52
|
+
const liveArr = [];
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// Network warm-up: reduce latency to Ezoic/CDN on first above-the-fold render
|
|
56
|
+
const _warmLinksDone = new Set();
|
|
57
|
+
function warmUpNetwork() {
|
|
58
|
+
try {
|
|
59
|
+
const head = document.head || document.getElementsByTagName('head')[0];
|
|
60
|
+
if (!head) return;
|
|
61
|
+
const links = [
|
|
62
|
+
['preconnect', 'https://g.ezoic.net', true],
|
|
63
|
+
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
64
|
+
['preconnect', 'https://go.ezoic.net', true],
|
|
65
|
+
['dns-prefetch', 'https://go.ezoic.net', false],
|
|
66
|
+
['preconnect', 'https://www.ezoic.net', true],
|
|
67
|
+
['dns-prefetch', 'https://www.ezoic.net', false],
|
|
68
|
+
];
|
|
69
|
+
for (const [rel, href, cors] of links) {
|
|
70
|
+
const key = rel + '|' + href;
|
|
71
|
+
if (_warmLinksDone.has(key)) continue;
|
|
72
|
+
_warmLinksDone.add(key);
|
|
73
|
+
const link = document.createElement('link');
|
|
74
|
+
link.rel = rel;
|
|
75
|
+
link.href = href;
|
|
76
|
+
if (cors) link.crossOrigin = 'anonymous';
|
|
77
|
+
head.appendChild(link);
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
}
|
|
81
|
+
|
|
41
82
|
|
|
42
83
|
function normalizeBool(v) {
|
|
43
84
|
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
@@ -123,9 +164,9 @@
|
|
|
123
164
|
} catch (e) {}
|
|
124
165
|
|
|
125
166
|
// Recyclage: libérer IDs après 100ms
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
169
|
+
}, 100);
|
|
129
170
|
};
|
|
130
171
|
try {
|
|
131
172
|
window.ezstandalone = window.ezstandalone || {};
|
|
@@ -197,11 +238,17 @@
|
|
|
197
238
|
if (ph && ph.children.length === 0) {
|
|
198
239
|
// Placeholder vide après 3s = pub non chargée
|
|
199
240
|
setTimeout(() => {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
241
|
+
try {
|
|
242
|
+
// Give Ezoic more time on slower connections, and don't remove if we recently requested an ad.
|
|
243
|
+
const phId = ph && ph.id;
|
|
244
|
+
const id = phId ? parseInt(phId.replace(PLACEHOLDER_PREFIX, ''), 10) : 0;
|
|
245
|
+
const last = (id && state.lastShowById && state.lastShowById.get(id)) || 0;
|
|
246
|
+
if (ph.children.length === 0 && (!last || Date.now() - last > 10000)) {
|
|
247
|
+
wrapper.remove();
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {}
|
|
250
|
+
}, 8000);
|
|
251
|
+
}
|
|
205
252
|
});
|
|
206
253
|
} catch (e) {}
|
|
207
254
|
}
|
|
@@ -302,67 +349,52 @@
|
|
|
302
349
|
|
|
303
350
|
function patchShowAds() {
|
|
304
351
|
const applyPatch = () => {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
try { orig.call(ez, id); } catch (e) {}
|
|
341
|
-
}
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Pour appels simples, vérifier aussi
|
|
346
|
-
if (typeof arg === 'number') {
|
|
347
|
-
const phId = `ezoic-pub-ad-placeholder-${arg}`;
|
|
348
|
-
const ph = document.getElementById(phId);
|
|
349
|
-
if (!ph || !ph.isConnected) return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return orig.apply(ez, arguments);
|
|
353
|
-
};
|
|
354
|
-
} catch (e) {}
|
|
352
|
+
try {
|
|
353
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
354
|
+
const ez = window.ezstandalone;
|
|
355
|
+
if (window.__nodebbEzoicPatched) return;
|
|
356
|
+
if (typeof ez.showAds !== 'function') return;
|
|
357
|
+
|
|
358
|
+
window.__nodebbEzoicPatched = true;
|
|
359
|
+
const orig = ez.showAds;
|
|
360
|
+
|
|
361
|
+
// Ezoic's ez-standalone.js logs warnings when asked to render an ID
|
|
362
|
+
// that isn't present in the DOM anymore. In an infinite-scroll context
|
|
363
|
+
// we must filter and call per-id.
|
|
364
|
+
ez.showAds = function (...args) {
|
|
365
|
+
if (EZOIC_BLOCKED) return;
|
|
366
|
+
|
|
367
|
+
let ids = [];
|
|
368
|
+
if (args.length === 1 && Array.isArray(args[0])) {
|
|
369
|
+
ids = args[0];
|
|
370
|
+
} else {
|
|
371
|
+
ids = args;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const seen = new Set();
|
|
375
|
+
for (const v of ids) {
|
|
376
|
+
const id = parseInt(v, 10);
|
|
377
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
378
|
+
|
|
379
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
380
|
+
if (!ph || !ph.isConnected) continue;
|
|
381
|
+
|
|
382
|
+
seen.add(id);
|
|
383
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
} catch (e) {}
|
|
355
387
|
};
|
|
356
388
|
|
|
357
389
|
applyPatch();
|
|
358
390
|
if (!window.__nodebbEzoicPatched) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
391
|
+
try {
|
|
392
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
393
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
394
|
+
window.ezstandalone.cmd.push(applyPatch);
|
|
395
|
+
} catch (e) {}
|
|
365
396
|
}
|
|
397
|
+
}
|
|
366
398
|
|
|
367
399
|
function markFilled(wrap) {
|
|
368
400
|
try {
|
|
@@ -465,7 +497,7 @@
|
|
|
465
497
|
window.ezstandalone.cmd.push(function() {
|
|
466
498
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
467
499
|
// Appel batch: showAds(id1, id2, id3...)
|
|
468
|
-
window.ezstandalone.showAds(
|
|
500
|
+
window.ezstandalone.showAds(validIds);
|
|
469
501
|
// Tracker tous les IDs
|
|
470
502
|
validIds.forEach(id => {
|
|
471
503
|
state.lastShowById.set(id, Date.now());
|
|
@@ -482,53 +514,95 @@
|
|
|
482
514
|
}, 100);
|
|
483
515
|
}
|
|
484
516
|
|
|
485
|
-
|
|
517
|
+
|
|
518
|
+
function callShowAdsWhenReady(id) {
|
|
486
519
|
if (!id) return;
|
|
487
520
|
|
|
488
521
|
const now = Date.now(), last = state.lastShowById.get(id) || 0;
|
|
489
|
-
if (now - last <
|
|
490
|
-
|
|
491
|
-
const phId = `${PLACEHOLDER_PREFIX}${id}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
522
|
+
if (now - last < 2500) return;
|
|
523
|
+
|
|
524
|
+
const phId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
525
|
+
|
|
526
|
+
const tryShowOrQueue = () => {
|
|
527
|
+
if (EZOIC_BLOCKED) return false;
|
|
528
|
+
const el = document.getElementById(phId);
|
|
529
|
+
if (!el || !el.isConnected) return false;
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
533
|
+
const ez = window.ezstandalone;
|
|
534
|
+
|
|
535
|
+
if (typeof ez.showAds === 'function') {
|
|
536
|
+
state.lastShowById.set(id, Date.now());
|
|
537
|
+
ez.showAds(id);
|
|
538
|
+
sessionDefinedIds.add(id);
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Queue once to run as soon as Ezoic is ready
|
|
543
|
+
ez.cmd = ez.cmd || [];
|
|
544
|
+
if (!el.__ezoicQueued) {
|
|
545
|
+
el.__ezoicQueued = true;
|
|
546
|
+
ez.cmd.push(() => {
|
|
547
|
+
try {
|
|
548
|
+
if (EZOIC_BLOCKED) return;
|
|
549
|
+
const ph = document.getElementById(phId);
|
|
550
|
+
if (!ph || !ph.isConnected) return;
|
|
551
|
+
state.lastShowById.set(id, Date.now());
|
|
552
|
+
window.ezstandalone.showAds(id);
|
|
553
|
+
sessionDefinedIds.add(id);
|
|
554
|
+
} catch (e) {}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
} catch (e) {}
|
|
558
|
+
return false;
|
|
503
559
|
};
|
|
504
560
|
|
|
505
561
|
const startPageKey = state.pageKey;
|
|
506
562
|
let attempts = 0;
|
|
507
|
-
(function waitForPh() {
|
|
508
|
-
if (state.pageKey !== startPageKey) return;
|
|
509
|
-
if (state.pendingById.has(id)) return;
|
|
510
|
-
|
|
511
|
-
attempts += 1;
|
|
512
|
-
const el = document.getElementById(phId);
|
|
513
|
-
if (el && el.isConnected) {
|
|
514
|
-
|
|
515
|
-
// Si on arrive ici, soit visible, soit timeout
|
|
516
563
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
564
|
+
(function waitForPh() {
|
|
565
|
+
if (state.pageKey !== startPageKey) return;
|
|
566
|
+
|
|
567
|
+
const el = document.getElementById(phId);
|
|
568
|
+
if (el && el.isConnected) {
|
|
569
|
+
// tag id for IO callback
|
|
570
|
+
try { el.setAttribute('data-ezoic-id', String(id)); } catch (e) {}
|
|
571
|
+
|
|
572
|
+
// If above-the-fold (or during the fast-start window), fire immediately.
|
|
573
|
+
const inFastWindow = state.fastStartUntil && Date.now() < state.fastStartUntil;
|
|
574
|
+
if (isAboveFold(el) || (inFastWindow && (el.getBoundingClientRect().top < window.innerHeight * 3))) {
|
|
575
|
+
if (tryShowOrQueue()) {
|
|
576
|
+
state.pendingById.delete(id);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Otherwise, preload via IntersectionObserver
|
|
582
|
+
if (!state.pendingById.has(id)) {
|
|
583
|
+
state.pendingById.add(id);
|
|
584
|
+
const io = ensurePreloadObserver();
|
|
585
|
+
try { io && io.observe(el); } catch (e) {}
|
|
586
|
+
|
|
587
|
+
// Safety fallback: if it still hasn't rendered after 6s, force attempt.
|
|
588
|
+
const t = setTimeout(() => {
|
|
589
|
+
tryShowOrQueue();
|
|
590
|
+
state.pendingById.delete(id);
|
|
591
|
+
}, 6000);
|
|
592
|
+
state.activeTimeouts.add(t);
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
attempts += 1;
|
|
598
|
+
if (attempts < 100) {
|
|
599
|
+
const timeoutId = setTimeout(waitForPh, 25);
|
|
600
|
+
state.activeTimeouts.add(timeoutId);
|
|
601
|
+
}
|
|
528
602
|
})();
|
|
529
|
-
|
|
603
|
+
}
|
|
530
604
|
|
|
531
|
-
|
|
605
|
+
async function fetchConfig() {
|
|
532
606
|
if (state.cfg) return state.cfg;
|
|
533
607
|
if (state.cfgPromise) return state.cfgPromise;
|
|
534
608
|
|
|
@@ -718,6 +792,7 @@
|
|
|
718
792
|
state.pendingById.clear();
|
|
719
793
|
|
|
720
794
|
if (state.obs) { state.obs.disconnect(); state.obs = null; }
|
|
795
|
+
if (state.io) { try { state.io.disconnect(); } catch (e) {} state.io = null; }
|
|
721
796
|
|
|
722
797
|
state.scheduled = false;
|
|
723
798
|
clearTimeout(state.timer);
|
|
@@ -730,6 +805,34 @@
|
|
|
730
805
|
try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
731
806
|
}
|
|
732
807
|
|
|
808
|
+
function ensurePreloadObserver() {
|
|
809
|
+
if (state.io) return state.io;
|
|
810
|
+
try {
|
|
811
|
+
state.io = new IntersectionObserver((entries) => {
|
|
812
|
+
for (const ent of entries) {
|
|
813
|
+
if (!ent.isIntersecting) continue;
|
|
814
|
+
const el = ent.target;
|
|
815
|
+
try { state.io && state.io.unobserve(el); } catch (e) {}
|
|
816
|
+
const idAttr = el && el.getAttribute && el.getAttribute('data-ezoic-id');
|
|
817
|
+
const id = parseInt(idAttr, 10);
|
|
818
|
+
if (Number.isFinite(id) && id > 0) {
|
|
819
|
+
// Try immediately; otherwise it will queue via ezstandalone.cmd
|
|
820
|
+
try { callShowAdsWhenReady(id); } catch (e) {}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}, { root: null, rootMargin: PRELOAD_ROOT_MARGIN, threshold: 0.01 });
|
|
824
|
+
} catch (e) { state.io = null; }
|
|
825
|
+
return state.io;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function isAboveFold(el) {
|
|
829
|
+
try {
|
|
830
|
+
if (!el || !el.getBoundingClientRect) return false;
|
|
831
|
+
const r = el.getBoundingClientRect();
|
|
832
|
+
return r.top < (window.innerHeight * ABOVE_FOLD_MULT) && r.bottom > -200;
|
|
833
|
+
} catch (e) { return false; }
|
|
834
|
+
}
|
|
835
|
+
|
|
733
836
|
async function runCore() {
|
|
734
837
|
// CRITIQUE: Ne rien insérer si navigation en cours
|
|
735
838
|
if (EZOIC_BLOCKED) {
|
|
@@ -821,17 +924,28 @@
|
|
|
821
924
|
|
|
822
925
|
$(window).on('action:ajaxify.start.ezoicInfinite', () => cleanup());
|
|
823
926
|
|
|
824
|
-
|
|
927
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
825
928
|
state.pageKey = getPageKey();
|
|
826
|
-
|
|
929
|
+
|
|
827
930
|
// Débloquer Ezoic IMMÉDIATEMENT pour la nouvelle page
|
|
828
931
|
EZOIC_BLOCKED = false;
|
|
829
|
-
|
|
932
|
+
|
|
933
|
+
// Warm-up réseau + runtime Ezoic dès que possible
|
|
934
|
+
warmUpNetwork();
|
|
935
|
+
patchShowAds();
|
|
936
|
+
|
|
937
|
+
// Ultra-fast preload window
|
|
938
|
+
state.fastStartUntil = Date.now() + FAST_START_MS;
|
|
939
|
+
|
|
830
940
|
ensureObserver();
|
|
941
|
+
ensurePreloadObserver();
|
|
831
942
|
|
|
832
943
|
state.canShowAds = true;
|
|
833
|
-
|
|
834
|
-
//
|
|
944
|
+
|
|
945
|
+
// HERO slot ASAP (above-the-fold)
|
|
946
|
+
insertHeroAdEarly();
|
|
947
|
+
|
|
948
|
+
// Relancer l'insertion normale
|
|
835
949
|
scheduleRun();
|
|
836
950
|
});
|
|
837
951
|
|
|
@@ -933,8 +1047,16 @@
|
|
|
933
1047
|
bind();
|
|
934
1048
|
bindScroll();
|
|
935
1049
|
ensureObserver();
|
|
1050
|
+
ensurePreloadObserver();
|
|
936
1051
|
state.pageKey = getPageKey();
|
|
937
1052
|
|
|
1053
|
+
// Warm-up réseau dès le boot
|
|
1054
|
+
warmUpNetwork();
|
|
1055
|
+
patchShowAds();
|
|
1056
|
+
state.fastStartUntil = Date.now() + FAST_START_MS;
|
|
1057
|
+
// HERO slot ASAP au premier chargement
|
|
1058
|
+
insertHeroAdEarly();
|
|
1059
|
+
|
|
938
1060
|
// Attendre que Ezoic soit chargé avant d'insérer
|
|
939
1061
|
waitForEzoicThenRun();
|
|
940
1062
|
})();
|
package/public/style.css
CHANGED
|
@@ -4,4 +4,16 @@ span.ezoic-ad,
|
|
|
4
4
|
span[class*="ezoic"] {
|
|
5
5
|
min-height: 0 !important;
|
|
6
6
|
min-width: 0 !important;
|
|
7
|
-
}
|
|
7
|
+
}
|
|
8
|
+
/* Reduce layout shifts and kill extra spacing around Ezoic wrappers */
|
|
9
|
+
.ezoic-ad {
|
|
10
|
+
display: block;
|
|
11
|
+
width: 100%;
|
|
12
|
+
margin: 0 !important;
|
|
13
|
+
padding: 0 !important;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
.ezoic-ad > [id^="ezoic-pub-ad-placeholder-"] {
|
|
17
|
+
margin: 0 !important;
|
|
18
|
+
padding: 0 !important;
|
|
19
|
+
}
|