nodebb-plugin-ezoic-infinite 1.5.39 → 1.5.41
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 +143 -1
- package/public/style.css +2 -0
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -135,6 +135,7 @@ function mutationHasRelevantAddedNodes(mutations) {
|
|
|
135
135
|
|
|
136
136
|
// observers / schedulers
|
|
137
137
|
domObs: null,
|
|
138
|
+
tightenObs: null,
|
|
138
139
|
io: null,
|
|
139
140
|
runQueued: false,
|
|
140
141
|
|
|
@@ -292,6 +293,124 @@ function mutationHasRelevantAddedNodes(mutations) {
|
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
295
|
|
|
296
|
+
// ---------- Ezoic min-height tightening (lightweight) ----------
|
|
297
|
+
// Some Ezoic placements reserve a large min-height (e.g. 400px) even when
|
|
298
|
+
// the rendered iframe/container is smaller (often 250px). That creates a
|
|
299
|
+
// visible empty gap and can make creatives appear to "slide" inside the slot.
|
|
300
|
+
// We correct ONLY those cases, without scroll listeners or full-page rescans.
|
|
301
|
+
|
|
302
|
+
function getRenderedAdHeight(adSpan) {
|
|
303
|
+
try {
|
|
304
|
+
const c = adSpan.querySelector('div[id$="__container__"]');
|
|
305
|
+
if (c && c.offsetHeight) return c.offsetHeight;
|
|
306
|
+
const f = adSpan.querySelector('iframe');
|
|
307
|
+
if (!f) return 0;
|
|
308
|
+
const attr = parseInt(f.getAttribute('height') || '', 10);
|
|
309
|
+
if (Number.isFinite(attr) && attr > 0) return attr;
|
|
310
|
+
if (f.offsetHeight) return f.offsetHeight;
|
|
311
|
+
} catch (e) {}
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function tightenMinHeight(adSpan) {
|
|
316
|
+
try {
|
|
317
|
+
if (!adSpan || adSpan.nodeType !== 1) return;
|
|
318
|
+
if (adSpan.tagName !== 'SPAN') return;
|
|
319
|
+
if (!adSpan.classList || !adSpan.classList.contains('ezoic-ad')) return;
|
|
320
|
+
|
|
321
|
+
// Some Ezoic templates apply sticky/fixed positioning inside the ad slot
|
|
322
|
+
// (e.g. .ezads-sticky-intradiv) which can make the creative appear to
|
|
323
|
+
// "slide" within an oversized container. Neutralize it inside the slot.
|
|
324
|
+
try {
|
|
325
|
+
const sticky = adSpan.querySelectorAll('.ezads-sticky-intradiv');
|
|
326
|
+
sticky.forEach((el) => {
|
|
327
|
+
el.style.setProperty('position', 'static', 'important');
|
|
328
|
+
el.style.setProperty('top', 'auto', 'important');
|
|
329
|
+
el.style.setProperty('bottom', 'auto', 'important');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Safety net: any descendant that ends up sticky/fixed via inline style
|
|
333
|
+
// (rare, but causes "floating" creatives).
|
|
334
|
+
const positioned = adSpan.querySelectorAll('[style*="position: sticky"], [style*="position:sticky"], [style*="position: fixed"], [style*="position:fixed"]');
|
|
335
|
+
positioned.forEach((el) => {
|
|
336
|
+
el.style.setProperty('position', 'static', 'important');
|
|
337
|
+
el.style.setProperty('top', 'auto', 'important');
|
|
338
|
+
el.style.setProperty('bottom', 'auto', 'important');
|
|
339
|
+
});
|
|
340
|
+
} catch (e) {}
|
|
341
|
+
|
|
342
|
+
const mhStr = adSpan.style && adSpan.style.minHeight ? String(adSpan.style.minHeight) : '';
|
|
343
|
+
const mh = mhStr ? parseInt(mhStr, 10) : 0;
|
|
344
|
+
if (!mh || mh < 350) return; // only fix the "400px"-style reservations
|
|
345
|
+
|
|
346
|
+
const h = getRenderedAdHeight(adSpan);
|
|
347
|
+
if (!h || h <= 0) return;
|
|
348
|
+
if (h >= mh) return;
|
|
349
|
+
|
|
350
|
+
adSpan.style.setProperty('min-height', `${h}px`, 'important');
|
|
351
|
+
} catch (e) {}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function closestEzoicAdSpan(node) {
|
|
355
|
+
try {
|
|
356
|
+
if (!node || node.nodeType !== 1) return null;
|
|
357
|
+
const el = /** @type {Element} */ (node);
|
|
358
|
+
if (el.tagName === 'SPAN' && el.classList && el.classList.contains('ezoic-ad')) return el;
|
|
359
|
+
if (el.closest) return el.closest('span.ezoic-ad');
|
|
360
|
+
} catch (e) {}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function ensureTightenObserver() {
|
|
365
|
+
if (state.tightenObs) return;
|
|
366
|
+
|
|
367
|
+
let raf = 0;
|
|
368
|
+
const pending = new Set();
|
|
369
|
+
const schedule = (adSpan) => {
|
|
370
|
+
if (!adSpan) return;
|
|
371
|
+
pending.add(adSpan);
|
|
372
|
+
if (raf) return;
|
|
373
|
+
raf = requestAnimationFrame(() => {
|
|
374
|
+
raf = 0;
|
|
375
|
+
for (const el of pending) tightenMinHeight(el);
|
|
376
|
+
pending.clear();
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
state.tightenObs = new MutationObserver((mutations) => {
|
|
381
|
+
try {
|
|
382
|
+
for (const m of mutations) {
|
|
383
|
+
if (m.type === 'attributes') {
|
|
384
|
+
const ad = closestEzoicAdSpan(m.target);
|
|
385
|
+
if (ad) schedule(ad);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (!m.addedNodes || !m.addedNodes.length) continue;
|
|
389
|
+
for (const n of m.addedNodes) {
|
|
390
|
+
const ad = closestEzoicAdSpan(n);
|
|
391
|
+
if (ad) schedule(ad);
|
|
392
|
+
if (n && n.nodeType === 1 && n.querySelectorAll) {
|
|
393
|
+
n.querySelectorAll('span.ezoic-ad').forEach(schedule);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} catch (e) {}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
state.tightenObs.observe(document.documentElement, {
|
|
402
|
+
subtree: true,
|
|
403
|
+
childList: true,
|
|
404
|
+
attributes: true,
|
|
405
|
+
attributeFilter: ['style', 'class', 'data-load-complete', 'height'],
|
|
406
|
+
});
|
|
407
|
+
} catch (e) {}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
document.querySelectorAll('span.ezoic-ad[style*="min-height"]').forEach(tightenMinHeight);
|
|
411
|
+
} catch (e) {}
|
|
412
|
+
}
|
|
413
|
+
|
|
295
414
|
const RECYCLE_COOLDOWN_MS = 1500;
|
|
296
415
|
|
|
297
416
|
function kindKeyFromClass(kindClass) {
|
|
@@ -853,6 +972,9 @@ function startShow(id) {
|
|
|
853
972
|
// reset perf profile cache
|
|
854
973
|
state.perfProfile = null;
|
|
855
974
|
|
|
975
|
+
// tighten observer is global; keep it across ajaxify navigation but ensure it exists
|
|
976
|
+
// (do not disconnect here to avoid missing late style rewrites during transitions)
|
|
977
|
+
|
|
856
978
|
// reset state
|
|
857
979
|
state.cfg = null;
|
|
858
980
|
state.allTopics = [];
|
|
@@ -899,6 +1021,7 @@ function startShow(id) {
|
|
|
899
1021
|
|
|
900
1022
|
warmUpNetwork();
|
|
901
1023
|
patchShowAds();
|
|
1024
|
+
ensureTightenObserver();
|
|
902
1025
|
ensurePreloadObserver();
|
|
903
1026
|
ensureDomObserver();
|
|
904
1027
|
|
|
@@ -921,11 +1044,30 @@ function startShow(id) {
|
|
|
921
1044
|
state.pageKey = getPageKey();
|
|
922
1045
|
warmUpNetwork();
|
|
923
1046
|
patchShowAds();
|
|
1047
|
+
ensureTightenObserver();
|
|
924
1048
|
ensurePreloadObserver();
|
|
925
1049
|
ensureDomObserver();
|
|
926
|
-
|
|
927
1050
|
bindNodeBB();
|
|
928
1051
|
|
|
1052
|
+
// Lightweight scroll kick: NodeBB infinite scroll can keep many nodes and only append occasionally.
|
|
1053
|
+
// Without a scroll trigger, we might not inject new placeholders until another DOM mutation occurs.
|
|
1054
|
+
// This is throttled and only triggers near the bottom to keep CPU usage minimal.
|
|
1055
|
+
state.lastScrollKick = 0;
|
|
1056
|
+
window.addEventListener('scroll', () => {
|
|
1057
|
+
const now = Date.now();
|
|
1058
|
+
if (now - state.lastScrollKick < 250) return;
|
|
1059
|
+
state.lastScrollKick = now;
|
|
1060
|
+
|
|
1061
|
+
// Only kick when user is approaching the end of currently rendered content
|
|
1062
|
+
const doc = document.documentElement;
|
|
1063
|
+
const scrollTop = window.pageYOffset || doc.scrollTop || 0;
|
|
1064
|
+
const viewportH = window.innerHeight || doc.clientHeight || 0;
|
|
1065
|
+
const fullH = Math.max(doc.scrollHeight, document.body ? document.body.scrollHeight : 0);
|
|
1066
|
+
if (scrollTop + viewportH > fullH - 2000) {
|
|
1067
|
+
if (!isBlocked()) scheduleRun();
|
|
1068
|
+
}
|
|
1069
|
+
}, { passive: true });
|
|
1070
|
+
|
|
929
1071
|
// First paint: try hero + run
|
|
930
1072
|
blockedUntil = 0;
|
|
931
1073
|
insertHeroAdEarly().catch(() => {});
|