nodebb-plugin-ezoic-infinite 1.6.68 → 1.6.69
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 +106 -1
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -119,6 +119,10 @@
|
|
|
119
119
|
burstDeadline:0,
|
|
120
120
|
burstCount: 0,
|
|
121
121
|
lastBurstTs: 0,
|
|
122
|
+
|
|
123
|
+
// Detached wraps cache (to survive NodeBB virtual/infinite scroll recycling)
|
|
124
|
+
// kindClass -> Map(afterPos -> wrapNode)
|
|
125
|
+
detached: new Map(),
|
|
122
126
|
};
|
|
123
127
|
|
|
124
128
|
let blockedUntil = 0;
|
|
@@ -394,6 +398,54 @@
|
|
|
394
398
|
try { wrap.remove(); } catch (e) {}
|
|
395
399
|
}
|
|
396
400
|
|
|
401
|
+
function getDetachedMap(kindClass) {
|
|
402
|
+
let m = st.detached.get(kindClass);
|
|
403
|
+
if (!m) { m = new Map(); st.detached.set(kindClass, m); }
|
|
404
|
+
return m;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function detachWrap(kindClass, wrap) {
|
|
408
|
+
// Keep a wrap node in memory so we can re-attach it later without
|
|
409
|
+
// re-requesting the same placement (avoids GPT "format already created" and
|
|
410
|
+
// prevents ads from vanishing permanently when NodeBB recycles DOM nodes).
|
|
411
|
+
try {
|
|
412
|
+
const afterPos = parseInt(wrap.getAttribute('data-ezoic-after') || '0', 10);
|
|
413
|
+
if (!afterPos) return dropWrap(wrap);
|
|
414
|
+
|
|
415
|
+
// Unobserve placeholder while detached
|
|
416
|
+
try {
|
|
417
|
+
const ph = wrap.querySelector('[id^="' + PLACEHOLDER_PREFIX + '"]');
|
|
418
|
+
if (ph) try { st.io && st.io.unobserve(ph); } catch (e) {}
|
|
419
|
+
} catch (e) {}
|
|
420
|
+
|
|
421
|
+
const m = getDetachedMap(kindClass);
|
|
422
|
+
// If a cached wrap for that position already exists, prefer the newest
|
|
423
|
+
// and fully drop the old one.
|
|
424
|
+
if (m.has(afterPos)) {
|
|
425
|
+
try { dropWrap(m.get(afterPos)); } catch (e) {}
|
|
426
|
+
}
|
|
427
|
+
m.set(afterPos, wrap);
|
|
428
|
+
|
|
429
|
+
// Hard cap cache size per kind to avoid unbounded memory growth.
|
|
430
|
+
if (m.size > 80) {
|
|
431
|
+
const oldest = [...m.entries()].sort((a, b) => {
|
|
432
|
+
const ta = parseInt((a[1] && a[1].getAttribute('data-created')) || '0', 10);
|
|
433
|
+
const tb = parseInt((b[1] && b[1].getAttribute('data-created')) || '0', 10);
|
|
434
|
+
return ta - tb;
|
|
435
|
+
}).slice(0, m.size - 80);
|
|
436
|
+
oldest.forEach(([pos, node]) => {
|
|
437
|
+
m.delete(pos);
|
|
438
|
+
try { dropWrap(node); } catch (e) {}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Actually detach from DOM
|
|
443
|
+
try { wrap.remove(); } catch (e) {}
|
|
444
|
+
} catch (e) {
|
|
445
|
+
try { dropWrap(wrap); } catch (x) {}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
397
449
|
function pickId(poolKey, cursorKey) {
|
|
398
450
|
const pool = st.pools[poolKey];
|
|
399
451
|
if (!pool.length) return null;
|
|
@@ -573,16 +625,38 @@
|
|
|
573
625
|
if (itemSet.has(el)) { found = true; break; }
|
|
574
626
|
}
|
|
575
627
|
|
|
576
|
-
if (!found)
|
|
628
|
+
if (!found) {
|
|
629
|
+
// Detach instead of destroy so ads can survive DOM recycling.
|
|
630
|
+
withInternal(() => detachWrap(kindClass, wrap));
|
|
631
|
+
}
|
|
577
632
|
});
|
|
578
633
|
}
|
|
579
634
|
|
|
635
|
+
function reapDetached(kindClass) {
|
|
636
|
+
// Soft-prune very old detached nodes.
|
|
637
|
+
try {
|
|
638
|
+
const m = st.detached.get(kindClass);
|
|
639
|
+
if (!m || !m.size) return;
|
|
640
|
+
for (const [pos, wrap] of m.entries()) {
|
|
641
|
+
if (!wrap) { m.delete(pos); continue; }
|
|
642
|
+
if (wrap.isConnected) { m.delete(pos); continue; }
|
|
643
|
+
const created = parseInt(wrap.getAttribute('data-created') || '0', 10);
|
|
644
|
+
if (created && (now() - created) > 15 * 60 * 1000) {
|
|
645
|
+
m.delete(pos);
|
|
646
|
+
try { dropWrap(wrap); } catch (e) {}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
} catch (e) {}
|
|
650
|
+
}
|
|
651
|
+
|
|
580
652
|
function injectWraps(kindClass, items, interval, showFirst, poolKey, cursorKey) {
|
|
581
653
|
if (!items.length) return 0;
|
|
582
654
|
const { map, targets } = computeTargets(items, interval, showFirst);
|
|
583
655
|
const max = MAX_INSERTS_PER_RUN + (isBoosted() ? 1 : 0);
|
|
584
656
|
let n = 0;
|
|
585
657
|
|
|
658
|
+
const detachedMap = st.detached.get(kindClass);
|
|
659
|
+
|
|
586
660
|
for (const pos of targets) {
|
|
587
661
|
if (n >= max) break;
|
|
588
662
|
const el = map.get(pos);
|
|
@@ -597,6 +671,27 @@
|
|
|
597
671
|
if (isAdjacentWrap(el)) continue;
|
|
598
672
|
if (findWrapAt(kindClass, pos)) continue;
|
|
599
673
|
|
|
674
|
+
// If we previously had a wrap at this exact position but NodeBB recycled
|
|
675
|
+
// the DOM, re-attach the cached wrap instead of creating a new one.
|
|
676
|
+
if (detachedMap && detachedMap.has(pos)) {
|
|
677
|
+
const cached = detachedMap.get(pos);
|
|
678
|
+
detachedMap.delete(pos);
|
|
679
|
+
if (cached) {
|
|
680
|
+
withInternal(() => el.insertAdjacentElement('afterend', cached));
|
|
681
|
+
// Re-observe placeholder and trigger show only if the cached wrap is empty.
|
|
682
|
+
try {
|
|
683
|
+
const ph = cached.querySelector('[id^="' + PLACEHOLDER_PREFIX + '"]');
|
|
684
|
+
const id = ph ? parseInt(ph.getAttribute('data-ezoic-id') || (ph.id || '').split('-').pop(), 10) : 0;
|
|
685
|
+
if (id > 0) {
|
|
686
|
+
observePh(id);
|
|
687
|
+
if (!isFilled(cached)) enqueueShow(id);
|
|
688
|
+
}
|
|
689
|
+
} catch (e) {}
|
|
690
|
+
n++;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
600
695
|
const id = pickId(poolKey, cursorKey);
|
|
601
696
|
if (!id) break;
|
|
602
697
|
|
|
@@ -684,6 +779,7 @@
|
|
|
684
779
|
const items = getItems('topic');
|
|
685
780
|
const set = new Set(items);
|
|
686
781
|
removeOrphanWraps('ezoic-ad-message', set);
|
|
782
|
+
reapDetached('ezoic-ad-message');
|
|
687
783
|
inserted += injectWraps('ezoic-ad-message', items,
|
|
688
784
|
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
689
785
|
normBool(cfg.showFirstMessageAd), 'post', 'post');
|
|
@@ -692,6 +788,7 @@
|
|
|
692
788
|
const items = getItems('categoryTopics');
|
|
693
789
|
const set = new Set(items);
|
|
694
790
|
removeOrphanWraps('ezoic-ad-between', set);
|
|
791
|
+
reapDetached('ezoic-ad-between');
|
|
695
792
|
inserted += injectWraps('ezoic-ad-between', items,
|
|
696
793
|
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
697
794
|
normBool(cfg.showFirstTopicAd), 'topic', 'topic');
|
|
@@ -701,6 +798,7 @@
|
|
|
701
798
|
const items = getItems('categories');
|
|
702
799
|
const set = new Set(items);
|
|
703
800
|
removeOrphanWraps('ezoic-ad-categories', set);
|
|
801
|
+
reapDetached('ezoic-ad-categories');
|
|
704
802
|
inserted += injectWraps('ezoic-ad-categories', items,
|
|
705
803
|
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
706
804
|
normBool(cfg.showFirstCategoryAd), 'category', 'category');
|
|
@@ -792,6 +890,13 @@
|
|
|
792
890
|
function cleanup() {
|
|
793
891
|
blockedUntil = now() + 1200;
|
|
794
892
|
try { document.querySelectorAll('.' + WRAP_CLASS).forEach(dropWrap); } catch (e) {}
|
|
893
|
+
try {
|
|
894
|
+
for (const m of st.detached.values()) {
|
|
895
|
+
for (const w of m.values()) try { dropWrap(w); } catch (e) {}
|
|
896
|
+
m.clear();
|
|
897
|
+
}
|
|
898
|
+
st.detached.clear();
|
|
899
|
+
} catch (e) {}
|
|
795
900
|
st.cfg = null;
|
|
796
901
|
st.pools = { topic: [], post: [], category: [] };
|
|
797
902
|
st.cursors = { topic: 0, post: 0, category: 0 };
|