nodebb-plugin-ezoic-infinite 1.5.41 → 1.5.44
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 +136 -3
- package/public/style.css +23 -0
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -117,6 +117,7 @@ function mutationHasRelevantAddedNodes(mutations) {
|
|
|
117
117
|
pageKey: null,
|
|
118
118
|
cfg: null,
|
|
119
119
|
|
|
120
|
+
navSeq: 0,
|
|
120
121
|
// Full lists (never consumed) + cursors for round-robin reuse
|
|
121
122
|
allTopics: [],
|
|
122
123
|
allPosts: [],
|
|
@@ -136,6 +137,7 @@ function mutationHasRelevantAddedNodes(mutations) {
|
|
|
136
137
|
// observers / schedulers
|
|
137
138
|
domObs: null,
|
|
138
139
|
tightenObs: null,
|
|
140
|
+
fillObs: null,
|
|
139
141
|
io: null,
|
|
140
142
|
runQueued: false,
|
|
141
143
|
|
|
@@ -265,6 +267,7 @@ function mutationHasRelevantAddedNodes(mutations) {
|
|
|
265
267
|
|
|
266
268
|
ez.showAds = function (...args) {
|
|
267
269
|
if (isBlocked()) return;
|
|
270
|
+
if (seq !== state.navSeq) return;
|
|
268
271
|
|
|
269
272
|
let ids = [];
|
|
270
273
|
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
@@ -542,7 +545,7 @@ function pruneOrphanWraps(kindClass, items) {
|
|
|
542
545
|
}
|
|
543
546
|
function buildWrap(id, kindClass, afterPos) {
|
|
544
547
|
const wrap = document.createElement('div');
|
|
545
|
-
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
548
|
+
wrap.className = `${WRAP_CLASS} ${kindClass} ez-pending`;
|
|
546
549
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
547
550
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
548
551
|
wrap.style.width = '100%';
|
|
@@ -557,6 +560,103 @@ function buildWrap(id, kindClass, afterPos) {
|
|
|
557
560
|
return wrap;
|
|
558
561
|
}
|
|
559
562
|
|
|
563
|
+
// ---------- Fill detection & collapse handling (lightweight) ----------
|
|
564
|
+
// If ad fill is slow, showing a big empty slot is visually jarring. We keep
|
|
565
|
+
// our injected wrapper collapsed (ez-pending) until a creative is present,
|
|
566
|
+
// then mark it ez-ready.
|
|
567
|
+
|
|
568
|
+
function wrapHasFilledCreative(wrap) {
|
|
569
|
+
try {
|
|
570
|
+
if (!wrap || !wrap.isConnected) return false;
|
|
571
|
+
// Safeframe container (most common)
|
|
572
|
+
const c = wrap.querySelector('div[id$="__container__"]');
|
|
573
|
+
if (c && c.offsetHeight > 10) return true;
|
|
574
|
+
// Any iframe with non-trivial height
|
|
575
|
+
const f = wrap.querySelector('iframe');
|
|
576
|
+
if (!f) return false;
|
|
577
|
+
if (f.getAttribute('data-load-complete') === 'true') return true;
|
|
578
|
+
if (f.offsetHeight > 10) return true;
|
|
579
|
+
return false;
|
|
580
|
+
} catch (e) {}
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function markWrapFilledIfNeeded(wrap) {
|
|
585
|
+
try {
|
|
586
|
+
if (!wrap || !wrap.isConnected) return;
|
|
587
|
+
if (!wrap.classList || !wrap.classList.contains(WRAP_CLASS)) return;
|
|
588
|
+
// Only our injected wrappers are DIVs with data-ezoic-wrapid.
|
|
589
|
+
if (wrap.tagName !== 'DIV') return;
|
|
590
|
+
if (!wrap.getAttribute('data-ezoic-wrapid')) return;
|
|
591
|
+
|
|
592
|
+
if (wrapHasFilledCreative(wrap)) {
|
|
593
|
+
wrap.classList.remove('ez-pending');
|
|
594
|
+
wrap.classList.add('ez-ready');
|
|
595
|
+
}
|
|
596
|
+
} catch (e) {}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function ensureFillObserver() {
|
|
600
|
+
if (state.fillObs) return;
|
|
601
|
+
|
|
602
|
+
let raf = 0;
|
|
603
|
+
const pending = new Set();
|
|
604
|
+
const schedule = (wrap) => {
|
|
605
|
+
if (!wrap) return;
|
|
606
|
+
pending.add(wrap);
|
|
607
|
+
if (raf) return;
|
|
608
|
+
raf = requestAnimationFrame(() => {
|
|
609
|
+
raf = 0;
|
|
610
|
+
for (const w of pending) markWrapFilledIfNeeded(w);
|
|
611
|
+
pending.clear();
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const closestWrap = (node) => {
|
|
616
|
+
try {
|
|
617
|
+
if (!node || node.nodeType !== 1) return null;
|
|
618
|
+
const el = /** @type {Element} */ (node);
|
|
619
|
+
if (el.tagName === 'DIV' && el.classList && el.classList.contains(WRAP_CLASS) && el.getAttribute('data-ezoic-wrapid')) return el;
|
|
620
|
+
if (el.closest) return el.closest(`div.${WRAP_CLASS}[data-ezoic-wrapid]`);
|
|
621
|
+
} catch (e) {}
|
|
622
|
+
return null;
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
state.fillObs = new MutationObserver((mutations) => {
|
|
626
|
+
try {
|
|
627
|
+
for (const m of mutations) {
|
|
628
|
+
if (m.type === 'attributes') {
|
|
629
|
+
const w = closestWrap(m.target);
|
|
630
|
+
if (w) schedule(w);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (!m.addedNodes || !m.addedNodes.length) continue;
|
|
634
|
+
for (const n of m.addedNodes) {
|
|
635
|
+
const w = closestWrap(n);
|
|
636
|
+
if (w) schedule(w);
|
|
637
|
+
if (n && n.nodeType === 1 && n.querySelectorAll) {
|
|
638
|
+
n.querySelectorAll(`div.${WRAP_CLASS}[data-ezoic-wrapid]`).forEach(schedule);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
} catch (e) {}
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
try {
|
|
646
|
+
state.fillObs.observe(document.documentElement, {
|
|
647
|
+
subtree: true,
|
|
648
|
+
childList: true,
|
|
649
|
+
attributes: true,
|
|
650
|
+
attributeFilter: ['style', 'class', 'data-load-complete', 'height', 'src'],
|
|
651
|
+
});
|
|
652
|
+
} catch (e) {}
|
|
653
|
+
|
|
654
|
+
// Kick once for already-present wraps
|
|
655
|
+
try {
|
|
656
|
+
document.querySelectorAll(`div.${WRAP_CLASS}[data-ezoic-wrapid]`).forEach(markWrapFilledIfNeeded);
|
|
657
|
+
} catch (e) {}
|
|
658
|
+
}
|
|
659
|
+
|
|
560
660
|
function findWrap(kindClass, afterPos) {
|
|
561
661
|
return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
|
|
562
662
|
}
|
|
@@ -664,6 +764,7 @@ function drainQueue() {
|
|
|
664
764
|
function startShow(id) {
|
|
665
765
|
if (!id || isBlocked()) return;
|
|
666
766
|
|
|
767
|
+
const seq = state.navSeq;
|
|
667
768
|
state.inflight++;
|
|
668
769
|
let released = false;
|
|
669
770
|
const release = () => {
|
|
@@ -679,7 +780,15 @@ function startShow(id) {
|
|
|
679
780
|
try {
|
|
680
781
|
if (isBlocked()) return;
|
|
681
782
|
|
|
682
|
-
|
|
783
|
+
// Ensure placeholder is armed (arm-on-load). On some NodeBB transitions the
|
|
784
|
+
// wrapper may exist but the placeholder id is not yet assigned.
|
|
785
|
+
const wrap = findWrapById(id);
|
|
786
|
+
if (wrap && wrap.isConnected) {
|
|
787
|
+
try { armPlaceholder(wrap, id); } catch (e) {}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const domId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
791
|
+
const ph = document.getElementById(domId);
|
|
683
792
|
if (!ph || !ph.isConnected) return;
|
|
684
793
|
|
|
685
794
|
const now2 = Date.now();
|
|
@@ -691,13 +800,29 @@ function startShow(id) {
|
|
|
691
800
|
const ez = window.ezstandalone;
|
|
692
801
|
|
|
693
802
|
const doShow = () => {
|
|
803
|
+
// Re-check right before showing: the placeholder can disappear between
|
|
804
|
+
// scheduling and execution (ajaxify/infinite scroll DOM churn).
|
|
805
|
+
if (seq !== state.navSeq) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
|
|
806
|
+
const phNow = document.getElementById(domId);
|
|
807
|
+
if (!phNow || !phNow.isConnected) {
|
|
808
|
+
try { clearTimeout(hardTimer); } catch (e) {}
|
|
809
|
+
release();
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
694
813
|
try {
|
|
695
814
|
if (state.usedOnce && state.usedOnce.has(id)) {
|
|
696
815
|
safeDestroyById(id);
|
|
697
816
|
}
|
|
698
817
|
} catch (e) {}
|
|
699
818
|
|
|
700
|
-
|
|
819
|
+
// Let the DOM settle for one macrotask; NodeBB can reflow/replace nodes right after mutations.
|
|
820
|
+
setTimeout(() => {
|
|
821
|
+
if (seq !== state.navSeq) { return; }
|
|
822
|
+
const phFinal = document.getElementById(domId);
|
|
823
|
+
if (!phFinal || !phFinal.isConnected) { return; }
|
|
824
|
+
try { ez.showAds(id); } catch (e) {}
|
|
825
|
+
}, 0);
|
|
701
826
|
try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
|
|
702
827
|
|
|
703
828
|
setTimeout(() => { clearTimeout(hardTimer); release(); }, 650);
|
|
@@ -960,6 +1085,12 @@ function startShow(id) {
|
|
|
960
1085
|
function cleanup() {
|
|
961
1086
|
blockedUntil = Date.now() + 1200;
|
|
962
1087
|
|
|
1088
|
+
// invalidate any queued showAds from previous view
|
|
1089
|
+
state.navSeq++;
|
|
1090
|
+
state.inflight = 0;
|
|
1091
|
+
state.pending = [];
|
|
1092
|
+
state.pendingSet = new Set();
|
|
1093
|
+
|
|
963
1094
|
// remove all wrappers
|
|
964
1095
|
try {
|
|
965
1096
|
withInternalDomChange(() => {
|
|
@@ -1022,6 +1153,7 @@ function startShow(id) {
|
|
|
1022
1153
|
warmUpNetwork();
|
|
1023
1154
|
patchShowAds();
|
|
1024
1155
|
ensureTightenObserver();
|
|
1156
|
+
ensureFillObserver();
|
|
1025
1157
|
ensurePreloadObserver();
|
|
1026
1158
|
ensureDomObserver();
|
|
1027
1159
|
|
|
@@ -1045,6 +1177,7 @@ function startShow(id) {
|
|
|
1045
1177
|
warmUpNetwork();
|
|
1046
1178
|
patchShowAds();
|
|
1047
1179
|
ensureTightenObserver();
|
|
1180
|
+
ensureFillObserver();
|
|
1048
1181
|
ensurePreloadObserver();
|
|
1049
1182
|
ensureDomObserver();
|
|
1050
1183
|
bindNodeBB();
|
package/public/style.css
CHANGED
|
@@ -9,3 +9,26 @@
|
|
|
9
9
|
clear: both;
|
|
10
10
|
position: relative;
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
UX: collapse injected wrapper slots until the creative is actually filled.
|
|
15
|
+
This avoids showing a large empty placeholder when ad fill is slow.
|
|
16
|
+
Only applies to our injected wrapper DIVs (not Ezoic's internal SPANs).
|
|
17
|
+
*/
|
|
18
|
+
div.ezoic-ad.ez-pending {
|
|
19
|
+
height: 1px !important;
|
|
20
|
+
min-height: 1px !important;
|
|
21
|
+
overflow: hidden !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
div.ezoic-ad.ez-ready {
|
|
25
|
+
height: auto !important;
|
|
26
|
+
overflow: visible !important;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Remove baseline gaps under iframes inside Ezoic creatives */
|
|
30
|
+
span.ezoic-ad iframe,
|
|
31
|
+
span.ezoic-ad div[id$="__container__"] iframe {
|
|
32
|
+
display: block !important;
|
|
33
|
+
vertical-align: top !important;
|
|
34
|
+
}
|