nodebb-plugin-ezoic-infinite 1.6.64 → 1.6.66
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 +23 -105
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -79,17 +79,12 @@
|
|
|
79
79
|
} catch (e) {}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
f.style.display = 'none';
|
|
89
|
-
f.id = f.name = '__tcfapiLocator';
|
|
90
|
-
(document.body || document.documentElement).appendChild(f);
|
|
91
|
-
} catch (e) {}
|
|
92
|
-
}
|
|
82
|
+
// ensureTcfApiLocator intentionally removed:
|
|
83
|
+
// Creating our own __tcfapiLocator iframe conflicts with the CMP's own locator
|
|
84
|
+
// management and causes "Cannot read properties of null (reading 'postMessage')"
|
|
85
|
+
// errors when the CMP tries to postMessage to an iframe we created then navigated away.
|
|
86
|
+
// The CMP (Ezoic/IAB TCF) handles its own locator iframe lifecycle.
|
|
87
|
+
function ensureTcfApiLocator() { /* no-op */ }
|
|
93
88
|
|
|
94
89
|
// ─── Ezoic min-height tightener ─────────────────────────────────────────────
|
|
95
90
|
// Ezoic injects `min-height:400px !important` on nested wrappers via inline
|
|
@@ -402,22 +397,12 @@
|
|
|
402
397
|
|
|
403
398
|
// ─── Insertion primitives ───────────────────────────────────────────────────
|
|
404
399
|
|
|
405
|
-
|
|
406
|
-
function getAnchorStableId(el) {
|
|
407
|
-
if (!el) return '';
|
|
408
|
-
return el.getAttribute('data-tid') || el.getAttribute('data-pid') ||
|
|
409
|
-
el.getAttribute('data-cid') || el.getAttribute('data-index') || '';
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function buildWrap(id, kindClass, afterPos, createPlaceholder, anchorStableId) {
|
|
400
|
+
function buildWrap(id, kindClass, afterPos, createPlaceholder) {
|
|
413
401
|
const wrap = document.createElement('div');
|
|
414
402
|
wrap.className = WRAP_CLASS + ' ' + kindClass;
|
|
415
403
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
416
404
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
417
405
|
wrap.setAttribute('data-created', String(now()));
|
|
418
|
-
// Store the anchor element's stable DOM id so we can re-anchor after
|
|
419
|
-
// NodeBB re-renders the list (which changes ordinal positions).
|
|
420
|
-
if (anchorStableId) wrap.setAttribute('data-ezoic-anchor', String(anchorStableId));
|
|
421
406
|
if (afterPos === 1) wrap.setAttribute('data-ezoic-pin', '1');
|
|
422
407
|
wrap.style.width = '100%';
|
|
423
408
|
if (createPlaceholder) {
|
|
@@ -429,27 +414,14 @@
|
|
|
429
414
|
return wrap;
|
|
430
415
|
}
|
|
431
416
|
|
|
432
|
-
/**
|
|
433
|
-
* Find a wrap that is already anchored to a specific stable id (tid/pid/cid).
|
|
434
|
-
* Used to prevent double-injection when NodeBB re-renders and ordinals shift.
|
|
435
|
-
*/
|
|
436
|
-
function findWrapByAnchor(kindClass, anchorStableId) {
|
|
437
|
-
if (!anchorStableId) return null;
|
|
438
|
-
return document.querySelector(
|
|
439
|
-
'.' + WRAP_CLASS + '.' + kindClass + '[data-ezoic-anchor="' + CSS.escape(String(anchorStableId)) + '"]'
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
417
|
function insertAfter(target, id, kindClass, afterPos) {
|
|
444
418
|
if (!target || !target.insertAdjacentElement) return null;
|
|
445
419
|
if (findWrap(kindClass, afterPos)) return null;
|
|
446
|
-
const anchorId = getAnchorStableId(target);
|
|
447
|
-
if (anchorId && findWrapByAnchor(kindClass, anchorId)) return null; // already anchored
|
|
448
420
|
if (insertingIds.has(id)) return null;
|
|
449
421
|
const existingPh = document.getElementById(PLACEHOLDER_PREFIX + id);
|
|
450
422
|
insertingIds.add(id);
|
|
451
423
|
try {
|
|
452
|
-
const wrap = buildWrap(id, kindClass, afterPos, !existingPh
|
|
424
|
+
const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
|
|
453
425
|
target.insertAdjacentElement('afterend', wrap);
|
|
454
426
|
if (existingPh) {
|
|
455
427
|
existingPh.setAttribute('data-ezoic-id', String(id));
|
|
@@ -494,21 +466,12 @@
|
|
|
494
466
|
const itemSet = new Set(items);
|
|
495
467
|
const isMessage = kindClass === 'ezoic-ad-message';
|
|
496
468
|
// Between/category ads are NEVER fully released — only hidden/shown.
|
|
469
|
+
// Releasing frees IDs into the pool → re-injection at wrong positions.
|
|
497
470
|
const allowRelease = isMessage;
|
|
498
471
|
let removed = 0;
|
|
499
472
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
items.forEach((el) => {
|
|
503
|
-
const sid = getAnchorStableId(el);
|
|
504
|
-
if (sid) liveAnchorIds.add(sid);
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
const anchorIsLive = (wrap) => {
|
|
508
|
-
// Primary: check by stable id (survives NodeBB re-renders).
|
|
509
|
-
const sid = wrap.getAttribute('data-ezoic-anchor');
|
|
510
|
-
if (sid) return liveAnchorIds.has(sid);
|
|
511
|
-
// Fallback: DOM-proximity check (for wraps without stable id).
|
|
473
|
+
const hasNearbyItem = (wrap) => {
|
|
474
|
+
// If wrap is inside a li.nodebb-ezoic-host, check the HOST's siblings.
|
|
512
475
|
const pivot = (wrap.parentElement && wrap.parentElement.classList &&
|
|
513
476
|
wrap.parentElement.classList.contains(HOST_CLASS))
|
|
514
477
|
? wrap.parentElement : wrap;
|
|
@@ -530,12 +493,12 @@
|
|
|
530
493
|
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
531
494
|
if (created && (now() - created) < keepEmptyWrapMs()) return;
|
|
532
495
|
|
|
533
|
-
if (
|
|
496
|
+
if (hasNearbyItem(wrap)) {
|
|
534
497
|
try { wrap.classList.remove('ez-orphan-hidden'); wrap.style.display = ''; } catch (e) {}
|
|
535
498
|
return;
|
|
536
499
|
}
|
|
537
500
|
|
|
538
|
-
// Anchor
|
|
501
|
+
// Anchor gone → hide.
|
|
539
502
|
try { wrap.classList.add('ez-orphan-hidden'); wrap.style.display = 'none'; } catch (e) {}
|
|
540
503
|
if (!allowRelease) return;
|
|
541
504
|
|
|
@@ -552,55 +515,6 @@
|
|
|
552
515
|
return removed;
|
|
553
516
|
}
|
|
554
517
|
|
|
555
|
-
/**
|
|
556
|
-
* Re-anchor between/category wraps after a NodeBB list re-render.
|
|
557
|
-
*
|
|
558
|
-
* When NodeBB rebuilds the topic list (infinite scroll above, sort change…),
|
|
559
|
-
* it re-inserts topic <li> elements in a new DOM order. Our wraps are still
|
|
560
|
-
* in the DOM but they may now sit between wrong topics because their ordinals
|
|
561
|
-
* changed. We use the stable anchor id (data-tid / data-pid) recorded at
|
|
562
|
-
* insertion time to find each wrap's correct anchor topic and move it there.
|
|
563
|
-
*/
|
|
564
|
-
function reanchorWraps(kindClass, items) {
|
|
565
|
-
// Build tid → element map from the current live items.
|
|
566
|
-
const tidMap = new Map();
|
|
567
|
-
items.forEach((el) => {
|
|
568
|
-
const sid = getAnchorStableId(el);
|
|
569
|
-
if (sid) tidMap.set(sid, el);
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
document.querySelectorAll('.' + WRAP_CLASS + '.' + kindClass).forEach((wrap) => {
|
|
573
|
-
try {
|
|
574
|
-
const sid = wrap.getAttribute('data-ezoic-anchor');
|
|
575
|
-
if (!sid) return;
|
|
576
|
-
const anchor = tidMap.get(sid);
|
|
577
|
-
if (!anchor || !anchor.isConnected) return;
|
|
578
|
-
|
|
579
|
-
// Check if the wrap is already correctly positioned (immediately after anchor).
|
|
580
|
-
// Account for li.nodebb-ezoic-host wrapper.
|
|
581
|
-
const wrapOrHost = (wrap.parentElement && wrap.parentElement.classList &&
|
|
582
|
-
wrap.parentElement.classList.contains(HOST_CLASS))
|
|
583
|
-
? wrap.parentElement : wrap;
|
|
584
|
-
|
|
585
|
-
if (wrapOrHost.previousElementSibling === anchor) {
|
|
586
|
-
// Already in the right place — just un-hide if it was hidden.
|
|
587
|
-
try { wrap.classList.remove('ez-orphan-hidden'); wrap.style.display = ''; } catch (e) {}
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Update data-ezoic-after to the anchor's current ordinal so findWrap still works.
|
|
592
|
-
const newOrdinal = getAnchorStableId(anchor)
|
|
593
|
-
? (parseInt(anchor.getAttribute('data-index') || '-1', 10) + 1) || 0
|
|
594
|
-
: 0;
|
|
595
|
-
if (newOrdinal > 0) wrap.setAttribute('data-ezoic-after', String(newOrdinal));
|
|
596
|
-
|
|
597
|
-
// Move the wrap (or its host) to after the anchor.
|
|
598
|
-
anchor.insertAdjacentElement('afterend', wrapOrHost);
|
|
599
|
-
try { wrap.classList.remove('ez-orphan-hidden'); wrap.style.display = ''; } catch (e) {}
|
|
600
|
-
} catch (e) {}
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
|
|
604
518
|
function decluster(kindClass) {
|
|
605
519
|
const wraps = Array.from(document.querySelectorAll('.' + WRAP_CLASS + '.' + kindClass));
|
|
606
520
|
if (wraps.length < 2) return 0;
|
|
@@ -994,6 +908,16 @@
|
|
|
994
908
|
if (inserted >= maxInserts) break;
|
|
995
909
|
const el = ordinalMap.get(afterPos);
|
|
996
910
|
if (!el || !el.isConnected) continue;
|
|
911
|
+
|
|
912
|
+
// Never inject after an element that is fully above the viewport top.
|
|
913
|
+
// This is the definitive guard: NodeBB loads items above the fold when the
|
|
914
|
+
// user scrolls up. Those items have rect.bottom < 0 at the moment of load.
|
|
915
|
+
// Any negative margin ("a little above is ok") still causes the pile-up
|
|
916
|
+
// because NodeBB loads a whole batch just above — we must use exactly 0.
|
|
917
|
+
try {
|
|
918
|
+
if (el.getBoundingClientRect().bottom < 0) continue;
|
|
919
|
+
} catch (e) {}
|
|
920
|
+
|
|
997
921
|
if (isAdjacentAd(el)) continue;
|
|
998
922
|
if (findWrap(kindClass, afterPos)) continue;
|
|
999
923
|
|
|
@@ -1052,11 +976,6 @@
|
|
|
1052
976
|
|
|
1053
977
|
} else if (kind === 'categoryTopics' && normalizeBool(cfg.enableBetweenAds)) {
|
|
1054
978
|
const items = getTopicItems();
|
|
1055
|
-
// Re-anchor FIRST: move existing wraps to their correct topic after any
|
|
1056
|
-
// NodeBB list re-render (ordinals may have shifted). This must happen before
|
|
1057
|
-
// pruneOrphanWraps (which uses live anchor ids) and before injectBetween
|
|
1058
|
-
// (which checks findWrap/findWrapByAnchor to avoid duplicates).
|
|
1059
|
-
reanchorWraps('ezoic-ad-between', items);
|
|
1060
979
|
pruneOrphanWraps('ezoic-ad-between', items);
|
|
1061
980
|
if (canInject) {
|
|
1062
981
|
inserted += injectBetween('ezoic-ad-between', items,
|
|
@@ -1068,7 +987,6 @@
|
|
|
1068
987
|
|
|
1069
988
|
} else if (kind === 'categories' && normalizeBool(cfg.enableCategoryAds)) {
|
|
1070
989
|
const items = getCategoryItems();
|
|
1071
|
-
reanchorWraps('ezoic-ad-categories', items);
|
|
1072
990
|
pruneOrphanWraps('ezoic-ad-categories', items);
|
|
1073
991
|
if (canInject) {
|
|
1074
992
|
inserted += injectBetween('ezoic-ad-categories', items,
|