nodebb-plugin-ezoic-infinite 1.6.64 → 1.6.65
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 +22 -94
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -402,22 +402,12 @@
|
|
|
402
402
|
|
|
403
403
|
// ─── Insertion primitives ───────────────────────────────────────────────────
|
|
404
404
|
|
|
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) {
|
|
405
|
+
function buildWrap(id, kindClass, afterPos, createPlaceholder) {
|
|
413
406
|
const wrap = document.createElement('div');
|
|
414
407
|
wrap.className = WRAP_CLASS + ' ' + kindClass;
|
|
415
408
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
416
409
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
417
410
|
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
411
|
if (afterPos === 1) wrap.setAttribute('data-ezoic-pin', '1');
|
|
422
412
|
wrap.style.width = '100%';
|
|
423
413
|
if (createPlaceholder) {
|
|
@@ -429,27 +419,14 @@
|
|
|
429
419
|
return wrap;
|
|
430
420
|
}
|
|
431
421
|
|
|
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
422
|
function insertAfter(target, id, kindClass, afterPos) {
|
|
444
423
|
if (!target || !target.insertAdjacentElement) return null;
|
|
445
424
|
if (findWrap(kindClass, afterPos)) return null;
|
|
446
|
-
const anchorId = getAnchorStableId(target);
|
|
447
|
-
if (anchorId && findWrapByAnchor(kindClass, anchorId)) return null; // already anchored
|
|
448
425
|
if (insertingIds.has(id)) return null;
|
|
449
426
|
const existingPh = document.getElementById(PLACEHOLDER_PREFIX + id);
|
|
450
427
|
insertingIds.add(id);
|
|
451
428
|
try {
|
|
452
|
-
const wrap = buildWrap(id, kindClass, afterPos, !existingPh
|
|
429
|
+
const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
|
|
453
430
|
target.insertAdjacentElement('afterend', wrap);
|
|
454
431
|
if (existingPh) {
|
|
455
432
|
existingPh.setAttribute('data-ezoic-id', String(id));
|
|
@@ -494,21 +471,12 @@
|
|
|
494
471
|
const itemSet = new Set(items);
|
|
495
472
|
const isMessage = kindClass === 'ezoic-ad-message';
|
|
496
473
|
// Between/category ads are NEVER fully released — only hidden/shown.
|
|
474
|
+
// Releasing frees IDs into the pool → re-injection at wrong positions.
|
|
497
475
|
const allowRelease = isMessage;
|
|
498
476
|
let removed = 0;
|
|
499
477
|
|
|
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).
|
|
478
|
+
const hasNearbyItem = (wrap) => {
|
|
479
|
+
// If wrap is inside a li.nodebb-ezoic-host, check the HOST's siblings.
|
|
512
480
|
const pivot = (wrap.parentElement && wrap.parentElement.classList &&
|
|
513
481
|
wrap.parentElement.classList.contains(HOST_CLASS))
|
|
514
482
|
? wrap.parentElement : wrap;
|
|
@@ -530,12 +498,12 @@
|
|
|
530
498
|
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
531
499
|
if (created && (now() - created) < keepEmptyWrapMs()) return;
|
|
532
500
|
|
|
533
|
-
if (
|
|
501
|
+
if (hasNearbyItem(wrap)) {
|
|
534
502
|
try { wrap.classList.remove('ez-orphan-hidden'); wrap.style.display = ''; } catch (e) {}
|
|
535
503
|
return;
|
|
536
504
|
}
|
|
537
505
|
|
|
538
|
-
// Anchor
|
|
506
|
+
// Anchor gone → hide.
|
|
539
507
|
try { wrap.classList.add('ez-orphan-hidden'); wrap.style.display = 'none'; } catch (e) {}
|
|
540
508
|
if (!allowRelease) return;
|
|
541
509
|
|
|
@@ -552,55 +520,6 @@
|
|
|
552
520
|
return removed;
|
|
553
521
|
}
|
|
554
522
|
|
|
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
523
|
function decluster(kindClass) {
|
|
605
524
|
const wraps = Array.from(document.querySelectorAll('.' + WRAP_CLASS + '.' + kindClass));
|
|
606
525
|
if (wraps.length < 2) return 0;
|
|
@@ -990,10 +909,25 @@
|
|
|
990
909
|
const maxInserts = MAX_INSERTS_PER_RUN + (isBoosted() ? 1 : 0);
|
|
991
910
|
let inserted = 0;
|
|
992
911
|
|
|
912
|
+
// Viewport top — we never inject after an element that is fully above this.
|
|
913
|
+
// This is the definitive guard against NodeBB loading content above the fold
|
|
914
|
+
// and our scanner immediately injecting ads on those newly-loaded top items.
|
|
915
|
+
// We use a generous negative margin so items just barely scrolled above still
|
|
916
|
+
// get ads injected (they'll be visible when the user scrolls back a little).
|
|
917
|
+
const viewportSafeTop = -(window.innerHeight || 800) * 0.5;
|
|
918
|
+
|
|
993
919
|
for (const afterPos of targets) {
|
|
994
920
|
if (inserted >= maxInserts) break;
|
|
995
921
|
const el = ordinalMap.get(afterPos);
|
|
996
922
|
if (!el || !el.isConnected) continue;
|
|
923
|
+
|
|
924
|
+
// Skip elements that are well above the viewport.
|
|
925
|
+
// getBoundingClientRect is cheap on modern engines (no forced layout).
|
|
926
|
+
try {
|
|
927
|
+
const rect = el.getBoundingClientRect();
|
|
928
|
+
if (rect.bottom < viewportSafeTop) continue;
|
|
929
|
+
} catch (e) {}
|
|
930
|
+
|
|
997
931
|
if (isAdjacentAd(el)) continue;
|
|
998
932
|
if (findWrap(kindClass, afterPos)) continue;
|
|
999
933
|
|
|
@@ -1052,11 +986,6 @@
|
|
|
1052
986
|
|
|
1053
987
|
} else if (kind === 'categoryTopics' && normalizeBool(cfg.enableBetweenAds)) {
|
|
1054
988
|
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
989
|
pruneOrphanWraps('ezoic-ad-between', items);
|
|
1061
990
|
if (canInject) {
|
|
1062
991
|
inserted += injectBetween('ezoic-ad-between', items,
|
|
@@ -1068,7 +997,6 @@
|
|
|
1068
997
|
|
|
1069
998
|
} else if (kind === 'categories' && normalizeBool(cfg.enableCategoryAds)) {
|
|
1070
999
|
const items = getCategoryItems();
|
|
1071
|
-
reanchorWraps('ezoic-ad-categories', items);
|
|
1072
1000
|
pruneOrphanWraps('ezoic-ad-categories', items);
|
|
1073
1001
|
if (canInject) {
|
|
1074
1002
|
inserted += injectBetween('ezoic-ad-categories', items,
|