nodebb-plugin-ezoic-infinite 1.5.66 → 1.5.67
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 +71 -145
- package/public/style.css +4 -4
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -82,10 +82,6 @@
|
|
|
82
82
|
io: null,
|
|
83
83
|
runQueued: false,
|
|
84
84
|
|
|
85
|
-
needsMoreRun: false,
|
|
86
|
-
moreRunBurst: 0,
|
|
87
|
-
moreRunLast: 0,
|
|
88
|
-
|
|
89
85
|
// preloading budget
|
|
90
86
|
inflight: 0,
|
|
91
87
|
pending: [],
|
|
@@ -99,20 +95,11 @@
|
|
|
99
95
|
|
|
100
96
|
// hero)
|
|
101
97
|
heroDoneForPage: false,
|
|
98
|
+
burstRuns: 0,
|
|
102
99
|
};
|
|
103
100
|
|
|
104
101
|
const insertingIds = new Set();
|
|
105
102
|
|
|
106
|
-
function unemptyIfFilled(wrap, ph) {
|
|
107
|
-
try {
|
|
108
|
-
if (!wrap || !ph) return false;
|
|
109
|
-
const hasAd = !!(ph.querySelector && ph.querySelector('iframe, ins, img, .ez-ad, .ezoic-ad'));
|
|
110
|
-
if (hasAd) wrap.classList.remove('is-empty');
|
|
111
|
-
return hasAd;
|
|
112
|
-
} catch (e) { return false; }
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
103
|
|
|
117
104
|
function markEmptyWrapper(id) {
|
|
118
105
|
try {
|
|
@@ -130,17 +117,6 @@
|
|
|
130
117
|
// consider empty if only whitespace and no iframes/ins/img
|
|
131
118
|
const hasAd = !!(ph2.querySelector && ph2.querySelector('iframe, ins, img, .ez-ad, .ezoic-ad'));
|
|
132
119
|
if (!hasAd) w2.classList.add('is-empty');
|
|
133
|
-
// If the ad fills later, immediately uncollapse to avoid "missing ads" perception.
|
|
134
|
-
if (!unemptyIfFilled(w2, ph2)) {
|
|
135
|
-
try {
|
|
136
|
-
const mo = new MutationObserver(() => {
|
|
137
|
-
if (unemptyIfFilled(w2, ph2)) { try { mo.disconnect(); } catch (e) {} }
|
|
138
|
-
});
|
|
139
|
-
mo.observe(ph2, { childList: true, subtree: true });
|
|
140
|
-
// safety stop
|
|
141
|
-
setTimeout(() => { try { mo.disconnect(); } catch (e) {} }, 30000);
|
|
142
|
-
} catch (e) {}
|
|
143
|
-
}
|
|
144
120
|
} catch (e) {}
|
|
145
121
|
}, 3500);
|
|
146
122
|
} catch (e) {}
|
|
@@ -259,40 +235,22 @@
|
|
|
259
235
|
window.__nodebbEzoicPatched = true;
|
|
260
236
|
const orig = ez.showAds;
|
|
261
237
|
|
|
262
|
-
// Important: preserve the original calling convention.
|
|
263
|
-
// Some Ezoic builds expect an array; calling one-by-one can lead to
|
|
264
|
-
// repeated define attempts and "Placeholder Id ... already been defined".
|
|
265
238
|
ez.showAds = function (...args) {
|
|
266
239
|
if (isBlocked()) return;
|
|
267
240
|
|
|
268
|
-
const now = Date.now();
|
|
269
241
|
let ids = [];
|
|
270
|
-
|
|
271
|
-
if (isArrayCall) ids = args[0];
|
|
242
|
+
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
272
243
|
else ids = args;
|
|
273
244
|
|
|
274
|
-
const filtered = [];
|
|
275
245
|
const seen = new Set();
|
|
276
246
|
for (const v of ids) {
|
|
277
247
|
const id = parseInt(v, 10);
|
|
278
248
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
279
249
|
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
280
250
|
if (!ph || !ph.isConnected) continue;
|
|
281
|
-
|
|
282
|
-
// Extra throttle to avoid rapid duplicate defines during ajaxify churn
|
|
283
|
-
const last = state.lastShowById.get(id) || 0;
|
|
284
|
-
if (now - last < 650) continue;
|
|
285
|
-
state.lastShowById.set(id, now);
|
|
286
|
-
|
|
287
251
|
seen.add(id);
|
|
288
|
-
|
|
252
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
289
253
|
}
|
|
290
|
-
|
|
291
|
-
if (!filtered.length) return;
|
|
292
|
-
try {
|
|
293
|
-
if (isArrayCall) orig.call(ez, filtered);
|
|
294
|
-
else orig.apply(ez, filtered);
|
|
295
|
-
} catch (e) {}
|
|
296
254
|
};
|
|
297
255
|
} catch (e) {}
|
|
298
256
|
};
|
|
@@ -390,21 +348,11 @@ function withInternalDomChange(fn) {
|
|
|
390
348
|
// NodeBB can insert separators/spacers; accept an anchor within a few previous siblings
|
|
391
349
|
let ok = false;
|
|
392
350
|
let prev = wrap.previousElementSibling;
|
|
393
|
-
for (let i = 0; i <
|
|
351
|
+
for (let i = 0; i < 3 && prev; i++) {
|
|
394
352
|
if (itemSet.has(prev)) { ok = true; break; }
|
|
395
353
|
prev = prev.previousElementSibling;
|
|
396
354
|
}
|
|
397
355
|
|
|
398
|
-
// If it is already filled (iframe/ins/img), be conservative and keep it.
|
|
399
|
-
// Prevents ads "disappearing too fast" during ajaxify churn / minor rerenders.
|
|
400
|
-
if (!ok) {
|
|
401
|
-
try {
|
|
402
|
-
const ph = wrap.querySelector && wrap.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
403
|
-
const filled = !!(ph && ph.querySelector && ph.querySelector('iframe, ins, img, .ez-ad, .ezoic-ad'));
|
|
404
|
-
if (filled) ok = true;
|
|
405
|
-
} catch (e) {}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
356
|
if (!ok) {
|
|
409
357
|
const id = getWrapIdFromWrap(wrap);
|
|
410
358
|
withInternalDomChange(() => {
|
|
@@ -421,28 +369,6 @@ function withInternalDomChange(fn) {
|
|
|
421
369
|
return removed;
|
|
422
370
|
}
|
|
423
371
|
|
|
424
|
-
function declusterWraps(kindClass) {
|
|
425
|
-
try {
|
|
426
|
-
const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`));
|
|
427
|
-
if (wraps.length < 2) return;
|
|
428
|
-
for (let i = 1; i < wraps.length; i++) {
|
|
429
|
-
const w = wraps[i];
|
|
430
|
-
if (!w || !w.isConnected) continue;
|
|
431
|
-
// If previous siblings contain another wrap within 2 hops, remove this one.
|
|
432
|
-
let prev = w.previousElementSibling;
|
|
433
|
-
let hops = 0;
|
|
434
|
-
while (prev && hops < 3) {
|
|
435
|
-
if (prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
436
|
-
withInternalDomChange(() => { try { w.remove(); } catch (e) {} });
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
prev = prev.previousElementSibling;
|
|
440
|
-
hops++;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
} catch (e) {}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
372
|
function refreshEmptyState(id) {
|
|
447
373
|
// After Ezoic has had a moment to fill the placeholder, toggle the CSS class.
|
|
448
374
|
window.setTimeout(() => {
|
|
@@ -458,25 +384,17 @@ function withInternalDomChange(fn) {
|
|
|
458
384
|
}, 3500);
|
|
459
385
|
}
|
|
460
386
|
|
|
461
|
-
function buildWrap(id, kindClass, afterPos
|
|
387
|
+
function buildWrap(id, kindClass, afterPos) {
|
|
462
388
|
const wrap = document.createElement('div');
|
|
463
389
|
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
464
390
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
465
391
|
wrap.setAttribute('data-ezoic-wrapid', String(id));
|
|
466
392
|
wrap.style.width = '100%';
|
|
467
393
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
} catch (e) {}
|
|
473
|
-
wrap.appendChild(existingPlaceholder);
|
|
474
|
-
} else {
|
|
475
|
-
const ph = document.createElement('div');
|
|
476
|
-
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
477
|
-
ph.setAttribute('data-ezoic-id', String(id));
|
|
478
|
-
wrap.appendChild(ph);
|
|
479
|
-
}
|
|
394
|
+
const ph = document.createElement('div');
|
|
395
|
+
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
396
|
+
ph.setAttribute('data-ezoic-id', String(id));
|
|
397
|
+
wrap.appendChild(ph);
|
|
480
398
|
|
|
481
399
|
return wrap;
|
|
482
400
|
}
|
|
@@ -490,28 +408,24 @@ function buildWrap(id, kindClass, afterPos, existingPlaceholder) {
|
|
|
490
408
|
if (findWrap(kindClass, afterPos)) return null;
|
|
491
409
|
if (insertingIds.has(id)) return null;
|
|
492
410
|
|
|
411
|
+
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
412
|
+
|
|
493
413
|
insertingIds.add(id);
|
|
494
414
|
try {
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
-
if (existingPh && existingPh.isConnected) {
|
|
503
|
-
moved = existingPh;
|
|
504
|
-
// If it was inside one of our wrappers, drop that empty wrapper.
|
|
415
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
416
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
417
|
+
|
|
418
|
+
// If a placeholder with this id already exists elsewhere (some Ezoic flows
|
|
419
|
+
// pre-create placeholders), move it into our wrapper instead of aborting.
|
|
420
|
+
// replaceChild moves the node atomically (no detach window).
|
|
421
|
+
if (existingPh && existingPh !== wrap.firstElementChild) {
|
|
505
422
|
try {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
423
|
+
existingPh.setAttribute('data-ezoic-id', String(id));
|
|
424
|
+
wrap.replaceChild(existingPh, wrap.firstElementChild);
|
|
425
|
+
} catch (e) {
|
|
426
|
+
// Keep the new placeholder if replace fails.
|
|
427
|
+
}
|
|
511
428
|
}
|
|
512
|
-
|
|
513
|
-
const wrap = buildWrap(id, kindClass, afterPos, moved);
|
|
514
|
-
target.insertAdjacentElement('afterend', wrap);
|
|
515
429
|
return wrap;
|
|
516
430
|
} finally {
|
|
517
431
|
insertingIds.delete(id);
|
|
@@ -711,6 +625,15 @@ function startShow(id) {
|
|
|
711
625
|
if (i % interval === 0) out.push(i);
|
|
712
626
|
}
|
|
713
627
|
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
628
|
+
// If we inserted the maximum batch, likely there are more targets.
|
|
629
|
+
// Schedule a follow-up pass (bounded via scheduleRun coalescing).
|
|
630
|
+
const maxInserts = MAX_INSERTS_PER_RUN + (isBoosted() ? 1 : 0);
|
|
631
|
+
if (insertedThisRun >= maxInserts) {
|
|
632
|
+
scheduleRun(120);
|
|
633
|
+
scheduleRun(420);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
714
637
|
}
|
|
715
638
|
|
|
716
639
|
function injectBetween(kindClass, items, interval, showFirst, allIds, cursorKey) {
|
|
@@ -720,10 +643,8 @@ function startShow(id) {
|
|
|
720
643
|
let inserted = 0;
|
|
721
644
|
const maxInserts = MAX_INSERTS_PER_RUN + (isBoosted() ? 1 : 0);
|
|
722
645
|
|
|
723
|
-
let hitLimit = false;
|
|
724
|
-
|
|
725
646
|
for (const afterPos of targets) {
|
|
726
|
-
if (inserted >= maxInserts)
|
|
647
|
+
if (inserted >= maxInserts) break;
|
|
727
648
|
|
|
728
649
|
const el = items[afterPos - 1];
|
|
729
650
|
if (!el || !el.isConnected) continue;
|
|
@@ -755,11 +676,6 @@ function startShow(id) {
|
|
|
755
676
|
inserted += 1;
|
|
756
677
|
}
|
|
757
678
|
|
|
758
|
-
if (hitLimit) {
|
|
759
|
-
// We intentionally cap per run for smoothness. If we hit the cap, queue another pass.
|
|
760
|
-
state.needsMoreRun = true;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
679
|
return inserted;
|
|
764
680
|
}
|
|
765
681
|
|
|
@@ -771,8 +687,6 @@ function startShow(id) {
|
|
|
771
687
|
|
|
772
688
|
initPools(cfg);
|
|
773
689
|
|
|
774
|
-
state.needsMoreRun = false;
|
|
775
|
-
|
|
776
690
|
const kind = getKind();
|
|
777
691
|
let items = [];
|
|
778
692
|
let allIds = [];
|
|
@@ -826,6 +740,7 @@ function startShow(id) {
|
|
|
826
740
|
|
|
827
741
|
async function runCore() {
|
|
828
742
|
if (isBlocked()) { dbg('blocked'); return; }
|
|
743
|
+
let insertedThisRun = 0;
|
|
829
744
|
|
|
830
745
|
patchShowAds();
|
|
831
746
|
|
|
@@ -840,7 +755,7 @@ function startShow(id) {
|
|
|
840
755
|
if (normalizeBool(cfg.enableMessageAds)) {
|
|
841
756
|
const __items = getPostContainers();
|
|
842
757
|
pruneOrphanWraps('ezoic-ad-message', __items);
|
|
843
|
-
injectBetween(
|
|
758
|
+
insertedThisRun += injectBetween(
|
|
844
759
|
'ezoic-ad-message',
|
|
845
760
|
__items,
|
|
846
761
|
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
@@ -848,13 +763,12 @@ function startShow(id) {
|
|
|
848
763
|
state.allPosts,
|
|
849
764
|
'curPosts'
|
|
850
765
|
);
|
|
851
|
-
declusterWraps('ezoic-ad-message');
|
|
852
766
|
}
|
|
853
767
|
} else if (kind === 'categoryTopics') {
|
|
854
768
|
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
855
769
|
const __items = getTopicItems();
|
|
856
770
|
pruneOrphanWraps('ezoic-ad-between', __items);
|
|
857
|
-
injectBetween(
|
|
771
|
+
insertedThisRun += injectBetween(
|
|
858
772
|
'ezoic-ad-between',
|
|
859
773
|
__items,
|
|
860
774
|
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
@@ -862,13 +776,12 @@ function startShow(id) {
|
|
|
862
776
|
state.allTopics,
|
|
863
777
|
'curTopics'
|
|
864
778
|
);
|
|
865
|
-
declusterWraps('ezoic-ad-between');
|
|
866
779
|
}
|
|
867
780
|
} else if (kind === 'categories') {
|
|
868
781
|
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
869
782
|
const __items = getCategoryItems();
|
|
870
783
|
pruneOrphanWraps('ezoic-ad-categories', __items);
|
|
871
|
-
injectBetween(
|
|
784
|
+
insertedThisRun += injectBetween(
|
|
872
785
|
'ezoic-ad-categories',
|
|
873
786
|
__items,
|
|
874
787
|
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
@@ -876,34 +789,47 @@ function startShow(id) {
|
|
|
876
789
|
state.allCategories,
|
|
877
790
|
'curCategories'
|
|
878
791
|
);
|
|
879
|
-
declusterWraps('ezoic-ad-categories');
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
// If we hit the per-run insertion cap, schedule another pass soon.
|
|
883
|
-
// Guard against infinite loops when nothing more can be inserted (e.g. no ids / adjacency everywhere).
|
|
884
|
-
if (state.needsMoreRun) {
|
|
885
|
-
const now = Date.now();
|
|
886
|
-
if (now - state.moreRunLast > 800) state.moreRunBurst = 0;
|
|
887
|
-
state.moreRunLast = now;
|
|
888
|
-
state.moreRunBurst += 1;
|
|
889
|
-
if (state.moreRunBurst <= 8) {
|
|
890
|
-
setTimeout(scheduleRun, 60);
|
|
891
792
|
}
|
|
892
793
|
}
|
|
893
|
-
|
|
894
794
|
}
|
|
895
795
|
|
|
896
|
-
function scheduleRun() {
|
|
796
|
+
function scheduleRun(delayMs = 0) {
|
|
797
|
+
// schedule a single run (coalesced)
|
|
897
798
|
if (state.runQueued) return;
|
|
898
799
|
state.runQueued = true;
|
|
899
|
-
|
|
800
|
+
const doRun = () => {
|
|
900
801
|
state.runQueued = false;
|
|
901
802
|
const pk = getPageKey();
|
|
902
803
|
if (state.pageKey && pk !== state.pageKey) return;
|
|
903
804
|
runCore().catch(() => {});
|
|
904
|
-
}
|
|
805
|
+
};
|
|
806
|
+
if (delayMs > 0) {
|
|
807
|
+
window.setTimeout(() => window.requestAnimationFrame(doRun), delayMs);
|
|
808
|
+
} else {
|
|
809
|
+
window.requestAnimationFrame(doRun);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function scheduleBurst() {
|
|
814
|
+
// During ajaxify/infinite scroll, the DOM may arrive in waves.
|
|
815
|
+
// We run a small, bounded burst to ensure all 1/X targets are reached.
|
|
816
|
+
const pk = getPageKey();
|
|
817
|
+
state.pageKey = pk;
|
|
818
|
+
state.burstRuns = 0;
|
|
819
|
+
const burst = () => {
|
|
820
|
+
if (getPageKey() !== pk) return;
|
|
821
|
+
if (state.burstRuns >= 6) return;
|
|
822
|
+
state.burstRuns += 1;
|
|
823
|
+
scheduleRun(0);
|
|
824
|
+
// follow-up passes catch late-rendered items (especially on topics)
|
|
825
|
+
window.setTimeout(() => scheduleRun(0), 180);
|
|
826
|
+
window.setTimeout(() => scheduleRun(0), 650);
|
|
827
|
+
window.setTimeout(() => scheduleRun(0), 1400);
|
|
828
|
+
};
|
|
829
|
+
burst();
|
|
905
830
|
}
|
|
906
831
|
|
|
832
|
+
|
|
907
833
|
// ---------- observers / lifecycle ----------
|
|
908
834
|
|
|
909
835
|
function cleanup() {
|
|
@@ -938,7 +864,7 @@ function startShow(id) {
|
|
|
938
864
|
if (state.domObs) return;
|
|
939
865
|
state.domObs = new MutationObserver(() => {
|
|
940
866
|
if (state.internalDomChange > 0) return;
|
|
941
|
-
if (!isBlocked())
|
|
867
|
+
if (!isBlocked()) scheduleBurst();
|
|
942
868
|
});
|
|
943
869
|
try {
|
|
944
870
|
state.domObs.observe(document.body, { childList: true, subtree: true });
|
|
@@ -967,13 +893,13 @@ function startShow(id) {
|
|
|
967
893
|
insertHeroAdEarly().catch(() => {});
|
|
968
894
|
|
|
969
895
|
// Then normal insertion
|
|
970
|
-
|
|
896
|
+
scheduleBurst();
|
|
971
897
|
});
|
|
972
898
|
|
|
973
899
|
// Infinite scroll / partial updates
|
|
974
900
|
$(window).on('action:posts.loaded.ezoicInfinite action:topics.loaded.ezoicInfinite action:category.loaded.ezoicInfinite action:topic.loaded.ezoicInfinite', () => {
|
|
975
901
|
if (isBlocked()) return;
|
|
976
|
-
|
|
902
|
+
scheduleBurst();
|
|
977
903
|
});
|
|
978
904
|
}
|
|
979
905
|
|
|
@@ -1007,7 +933,7 @@ function startShow(id) {
|
|
|
1007
933
|
ticking = true;
|
|
1008
934
|
window.requestAnimationFrame(() => {
|
|
1009
935
|
ticking = false;
|
|
1010
|
-
if (!isBlocked())
|
|
936
|
+
if (!isBlocked()) scheduleBurst();
|
|
1011
937
|
});
|
|
1012
938
|
}, { passive: true });
|
|
1013
939
|
}
|
|
@@ -1026,5 +952,5 @@ function startShow(id) {
|
|
|
1026
952
|
// First paint: try hero + run
|
|
1027
953
|
blockedUntil = 0;
|
|
1028
954
|
insertHeroAdEarly().catch(() => {});
|
|
1029
|
-
|
|
955
|
+
scheduleBurst();
|
|
1030
956
|
})();
|
package/public/style.css
CHANGED
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
display: block !important;
|
|
30
30
|
margin: 0 !important;
|
|
31
31
|
padding: 0 !important;
|
|
32
|
-
height:
|
|
33
|
-
min-height:
|
|
32
|
+
height: 0 !important;
|
|
33
|
+
min-height: 0 !important;
|
|
34
34
|
overflow: hidden !important;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.nodebb-ezoic-wrap {
|
|
38
|
-
min-height:
|
|
38
|
+
min-height: 0 !important;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
.nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"] {
|
|
42
|
-
min-height:
|
|
42
|
+
min-height: 0 !important;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/*
|