nodebb-plugin-ezoic-infinite 1.5.25 → 1.5.27
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 -39
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
categoryItem: 'li[component="categories/category"]',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
let
|
|
22
|
+
// Soft block during navigation / heavy DOM churn to avoid “placeholder does not exist” spam
|
|
23
|
+
let blockedUntil = 0;
|
|
24
|
+
function isBlocked() {
|
|
25
|
+
return Date.now() < blockedUntil;
|
|
26
|
+
}
|
|
24
27
|
|
|
25
28
|
const state = {
|
|
26
29
|
pageKey: null,
|
|
@@ -36,6 +39,11 @@
|
|
|
36
39
|
|
|
37
40
|
// throttle per placeholder id
|
|
38
41
|
lastShowById: new Map(),
|
|
42
|
+
internalDomChange: 0,
|
|
43
|
+
lastRecycleAt: { topic: 0, categoryTopics: 0, categories: 0 },
|
|
44
|
+
|
|
45
|
+
// track placeholders that have been shown at least once in this pageview
|
|
46
|
+
usedOnce: new Set(),
|
|
39
47
|
|
|
40
48
|
// observers / schedulers
|
|
41
49
|
domObs: null,
|
|
@@ -169,7 +177,7 @@
|
|
|
169
177
|
const orig = ez.showAds;
|
|
170
178
|
|
|
171
179
|
ez.showAds = function (...args) {
|
|
172
|
-
if (
|
|
180
|
+
if (isBlocked()) return;
|
|
173
181
|
|
|
174
182
|
let ids = [];
|
|
175
183
|
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
@@ -198,7 +206,28 @@
|
|
|
198
206
|
}
|
|
199
207
|
}
|
|
200
208
|
|
|
201
|
-
|
|
209
|
+
const RECYCLE_COOLDOWN_MS = 1500;
|
|
210
|
+
|
|
211
|
+
function kindKeyFromClass(kindClass) {
|
|
212
|
+
if (kindClass === 'ezoic-ad-message') return 'topic';
|
|
213
|
+
if (kindClass === 'ezoic-ad-between') return 'categoryTopics';
|
|
214
|
+
if (kindClass === 'ezoic-ad-categories') return 'categories';
|
|
215
|
+
return 'topic';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function withInternalDomChange(fn) {
|
|
219
|
+
state.internalDomChange++;
|
|
220
|
+
try { fn(); } finally { state.internalDomChange--; }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function canRecycle(kind) {
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
const last = state.lastRecycleAt[kind] || 0;
|
|
226
|
+
if (now - last < RECYCLE_COOLDOWN_MS) return false;
|
|
227
|
+
state.lastRecycleAt[kind] = now;
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
// ---------- config & pools ----------
|
|
202
231
|
|
|
203
232
|
async function fetchConfigOnce() {
|
|
204
233
|
if (state.cfg) return state.cfg;
|
|
@@ -313,43 +342,71 @@
|
|
|
313
342
|
}
|
|
314
343
|
|
|
315
344
|
function showAd(id) {
|
|
316
|
-
if (!id ||
|
|
345
|
+
if (!id || isBlocked()) return;
|
|
317
346
|
|
|
318
347
|
const now = Date.now();
|
|
319
348
|
const last = state.lastShowById.get(id) || 0;
|
|
320
349
|
if (now - last < 1500) return; // basic throttle
|
|
321
350
|
|
|
322
|
-
|
|
323
|
-
|
|
351
|
+
// Defer one frame so the placeholder is definitely in DOM after insertion/recycle
|
|
352
|
+
requestAnimationFrame(() => {
|
|
353
|
+
if (isBlocked()) return;
|
|
324
354
|
|
|
325
|
-
|
|
355
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
356
|
+
if (!ph || !ph.isConnected) return;
|
|
326
357
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
358
|
+
const now2 = Date.now();
|
|
359
|
+
const last2 = state.lastShowById.get(id) || 0;
|
|
360
|
+
if (now2 - last2 < 1200) return;
|
|
361
|
+
state.lastShowById.set(id, now2);
|
|
330
362
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
ez.
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
363
|
+
try {
|
|
364
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
365
|
+
const ez = window.ezstandalone;
|
|
336
366
|
|
|
337
|
-
|
|
338
|
-
ez.cmd = ez.cmd || [];
|
|
339
|
-
if (!ph.__ezoicQueued) {
|
|
340
|
-
ph.__ezoicQueued = true;
|
|
341
|
-
ez.cmd.push(() => {
|
|
367
|
+
const doShow = () => {
|
|
342
368
|
try {
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
369
|
+
if (state.usedOnce && state.usedOnce.has(id) && typeof ez.destroyPlaceholders === 'function') {
|
|
370
|
+
// Avoid Ezoic caching state for reused placeholders
|
|
371
|
+
ez.destroyPlaceholders(id);
|
|
372
|
+
}
|
|
347
373
|
} catch (e) {}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
374
|
+
try { ez.showAds(id); } catch (e) {}
|
|
375
|
+
try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// Fast path
|
|
379
|
+
if (typeof ez.showAds === 'function') {
|
|
380
|
+
doShow();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Queue once for when Ezoic is ready
|
|
385
|
+
ez.cmd = ez.cmd || [];
|
|
386
|
+
if (!ph.__ezoicQueued) {
|
|
387
|
+
ph.__ezoicQueued = true;
|
|
388
|
+
ez.cmd.push(() => {
|
|
389
|
+
try {
|
|
390
|
+
if (isBlocked()) return;
|
|
391
|
+
const el = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
392
|
+
if (!el || !el.isConnected) return;
|
|
393
|
+
const ez2 = window.ezstandalone;
|
|
394
|
+
if (!ez2 || typeof ez2.showAds !== 'function') return;
|
|
395
|
+
try {
|
|
396
|
+
if (state.usedOnce && state.usedOnce.has(id) && typeof ez2.destroyPlaceholders === 'function') {
|
|
397
|
+
ez2.destroyPlaceholders(id);
|
|
398
|
+
}
|
|
399
|
+
} catch (e) {}
|
|
400
|
+
try { ez2.showAds(id); } catch (e) {}
|
|
401
|
+
try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
|
|
402
|
+
} catch (e) {}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
} catch (e) {}
|
|
406
|
+
});
|
|
351
407
|
}
|
|
352
408
|
|
|
409
|
+
|
|
353
410
|
// ---------- preload / above-the-fold ----------
|
|
354
411
|
|
|
355
412
|
function ensurePreloadObserver() {
|
|
@@ -413,12 +470,20 @@
|
|
|
413
470
|
|
|
414
471
|
let id = pickIdFromAll(allIds, cursorKey);
|
|
415
472
|
if (!id) {
|
|
416
|
-
// No free ids: recycle an old ad wrapper so we can reuse its placeholder id
|
|
417
|
-
|
|
473
|
+
// No free ids: recycle an old ad wrapper so we can reuse its placeholder id.
|
|
474
|
+
// Guard against tight observer loops.
|
|
475
|
+
if (!canRecycle(kindKeyFromClass(kindClass))) {
|
|
476
|
+
dbg('recycle-skip-cooldown', kindClass);
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
let recycled = false;
|
|
480
|
+
withInternalDomChange(() => {
|
|
481
|
+
recycled = removeOneOldWrap(kindClass);
|
|
482
|
+
});
|
|
418
483
|
dbg('recycle-needed', kindClass, { recycled, ids: allIds.length });
|
|
419
|
-
|
|
484
|
+
// Stop this run after a recycle; the next mutation/scroll will retry injection.
|
|
485
|
+
break;
|
|
420
486
|
}
|
|
421
|
-
if (!id) break;
|
|
422
487
|
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
423
488
|
if (!wrap) {
|
|
424
489
|
continue;
|
|
@@ -491,7 +556,7 @@
|
|
|
491
556
|
}
|
|
492
557
|
|
|
493
558
|
async function runCore() {
|
|
494
|
-
if (
|
|
559
|
+
if (isBlocked()) { dbg('blocked'); return; }
|
|
495
560
|
|
|
496
561
|
patchShowAds();
|
|
497
562
|
|
|
@@ -552,7 +617,7 @@
|
|
|
552
617
|
// ---------- observers / lifecycle ----------
|
|
553
618
|
|
|
554
619
|
function cleanup() {
|
|
555
|
-
|
|
620
|
+
blockedUntil = Date.now() + 1200;
|
|
556
621
|
|
|
557
622
|
// remove all wrappers
|
|
558
623
|
try {
|
|
@@ -570,6 +635,7 @@
|
|
|
570
635
|
state.curPosts = 0;
|
|
571
636
|
state.curCategories = 0;
|
|
572
637
|
state.lastShowById.clear();
|
|
638
|
+
try { state.usedOnce && state.usedOnce.clear(); } catch (e) {}
|
|
573
639
|
state.heroDoneForPage = false;
|
|
574
640
|
|
|
575
641
|
// keep observers alive (MutationObserver will re-trigger after navigation)
|
|
@@ -578,7 +644,8 @@
|
|
|
578
644
|
function ensureDomObserver() {
|
|
579
645
|
if (state.domObs) return;
|
|
580
646
|
state.domObs = new MutationObserver(() => {
|
|
581
|
-
if (
|
|
647
|
+
if (state.internalDomChange > 0) return;
|
|
648
|
+
if (!isBlocked()) scheduleRun();
|
|
582
649
|
});
|
|
583
650
|
try {
|
|
584
651
|
state.domObs.observe(document.body, { childList: true, subtree: true });
|
|
@@ -596,7 +663,7 @@
|
|
|
596
663
|
|
|
597
664
|
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
598
665
|
state.pageKey = getPageKey();
|
|
599
|
-
|
|
666
|
+
blockedUntil = 0;
|
|
600
667
|
|
|
601
668
|
warmUpNetwork();
|
|
602
669
|
patchShowAds();
|
|
@@ -612,7 +679,7 @@
|
|
|
612
679
|
|
|
613
680
|
// Infinite scroll / partial updates
|
|
614
681
|
$(window).on('action:posts.loaded.ezoicInfinite action:topics.loaded.ezoicInfinite action:category.loaded.ezoicInfinite action:topic.loaded.ezoicInfinite', () => {
|
|
615
|
-
if (
|
|
682
|
+
if (isBlocked()) return;
|
|
616
683
|
scheduleRun();
|
|
617
684
|
});
|
|
618
685
|
}
|
|
@@ -624,7 +691,7 @@
|
|
|
624
691
|
ticking = true;
|
|
625
692
|
window.requestAnimationFrame(() => {
|
|
626
693
|
ticking = false;
|
|
627
|
-
if (!
|
|
694
|
+
if (!isBlocked()) scheduleRun();
|
|
628
695
|
});
|
|
629
696
|
}, { passive: true });
|
|
630
697
|
}
|
|
@@ -641,7 +708,7 @@
|
|
|
641
708
|
bindScroll();
|
|
642
709
|
|
|
643
710
|
// First paint: try hero + run
|
|
644
|
-
|
|
711
|
+
blockedUntil = 0;
|
|
645
712
|
insertHeroAdEarly().catch(() => {});
|
|
646
713
|
scheduleRun();
|
|
647
714
|
})();
|