nodebb-plugin-ezoic-infinite 1.5.26 → 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 +102 -52
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,8 @@
|
|
|
36
39
|
|
|
37
40
|
// throttle per placeholder id
|
|
38
41
|
lastShowById: new Map(),
|
|
42
|
+
internalDomChange: 0,
|
|
43
|
+
lastRecycleAt: { topic: 0, categoryTopics: 0, categories: 0 },
|
|
39
44
|
|
|
40
45
|
// track placeholders that have been shown at least once in this pageview
|
|
41
46
|
usedOnce: new Set(),
|
|
@@ -172,7 +177,7 @@
|
|
|
172
177
|
const orig = ez.showAds;
|
|
173
178
|
|
|
174
179
|
ez.showAds = function (...args) {
|
|
175
|
-
if (
|
|
180
|
+
if (isBlocked()) return;
|
|
176
181
|
|
|
177
182
|
let ids = [];
|
|
178
183
|
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
@@ -201,7 +206,28 @@
|
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
|
|
204
|
-
|
|
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 ----------
|
|
205
231
|
|
|
206
232
|
async function fetchConfigOnce() {
|
|
207
233
|
if (state.cfg) return state.cfg;
|
|
@@ -316,56 +342,71 @@
|
|
|
316
342
|
}
|
|
317
343
|
|
|
318
344
|
function showAd(id) {
|
|
319
|
-
if (!id ||
|
|
345
|
+
if (!id || isBlocked()) return;
|
|
320
346
|
|
|
321
347
|
const now = Date.now();
|
|
322
348
|
const last = state.lastShowById.get(id) || 0;
|
|
323
349
|
if (now - last < 1500) return; // basic throttle
|
|
324
350
|
|
|
325
|
-
|
|
326
|
-
|
|
351
|
+
// Defer one frame so the placeholder is definitely in DOM after insertion/recycle
|
|
352
|
+
requestAnimationFrame(() => {
|
|
353
|
+
if (isBlocked()) return;
|
|
327
354
|
|
|
328
|
-
|
|
355
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
356
|
+
if (!ph || !ph.isConnected) return;
|
|
329
357
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// Fast path
|
|
335
|
-
if (typeof ez.showAds === 'function') {
|
|
336
|
-
try {
|
|
337
|
-
if (state.usedOnce && state.usedOnce.has(id) && typeof ez.destroyPlaceholders === 'function') {
|
|
338
|
-
ez.destroyPlaceholders(id);
|
|
339
|
-
}
|
|
340
|
-
} catch (e) {}
|
|
341
|
-
ez.showAds(id);
|
|
342
|
-
try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
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);
|
|
345
362
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
363
|
+
try {
|
|
364
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
365
|
+
const ez = window.ezstandalone;
|
|
366
|
+
|
|
367
|
+
const doShow = () => {
|
|
351
368
|
try {
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
}
|
|
373
|
+
} catch (e) {}
|
|
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(() => {
|
|
356
389
|
try {
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
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) {}
|
|
360
402
|
} catch (e) {}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
} catch (e) {}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
} catch (e) {}
|
|
406
|
+
});
|
|
367
407
|
}
|
|
368
408
|
|
|
409
|
+
|
|
369
410
|
// ---------- preload / above-the-fold ----------
|
|
370
411
|
|
|
371
412
|
function ensurePreloadObserver() {
|
|
@@ -429,12 +470,20 @@
|
|
|
429
470
|
|
|
430
471
|
let id = pickIdFromAll(allIds, cursorKey);
|
|
431
472
|
if (!id) {
|
|
432
|
-
// No free ids: recycle an old ad wrapper so we can reuse its placeholder id
|
|
433
|
-
|
|
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
|
+
});
|
|
434
483
|
dbg('recycle-needed', kindClass, { recycled, ids: allIds.length });
|
|
435
|
-
|
|
484
|
+
// Stop this run after a recycle; the next mutation/scroll will retry injection.
|
|
485
|
+
break;
|
|
436
486
|
}
|
|
437
|
-
if (!id) break;
|
|
438
487
|
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
439
488
|
if (!wrap) {
|
|
440
489
|
continue;
|
|
@@ -507,7 +556,7 @@
|
|
|
507
556
|
}
|
|
508
557
|
|
|
509
558
|
async function runCore() {
|
|
510
|
-
if (
|
|
559
|
+
if (isBlocked()) { dbg('blocked'); return; }
|
|
511
560
|
|
|
512
561
|
patchShowAds();
|
|
513
562
|
|
|
@@ -568,7 +617,7 @@
|
|
|
568
617
|
// ---------- observers / lifecycle ----------
|
|
569
618
|
|
|
570
619
|
function cleanup() {
|
|
571
|
-
|
|
620
|
+
blockedUntil = Date.now() + 1200;
|
|
572
621
|
|
|
573
622
|
// remove all wrappers
|
|
574
623
|
try {
|
|
@@ -595,7 +644,8 @@
|
|
|
595
644
|
function ensureDomObserver() {
|
|
596
645
|
if (state.domObs) return;
|
|
597
646
|
state.domObs = new MutationObserver(() => {
|
|
598
|
-
if (
|
|
647
|
+
if (state.internalDomChange > 0) return;
|
|
648
|
+
if (!isBlocked()) scheduleRun();
|
|
599
649
|
});
|
|
600
650
|
try {
|
|
601
651
|
state.domObs.observe(document.body, { childList: true, subtree: true });
|
|
@@ -613,7 +663,7 @@
|
|
|
613
663
|
|
|
614
664
|
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
615
665
|
state.pageKey = getPageKey();
|
|
616
|
-
|
|
666
|
+
blockedUntil = 0;
|
|
617
667
|
|
|
618
668
|
warmUpNetwork();
|
|
619
669
|
patchShowAds();
|
|
@@ -629,7 +679,7 @@
|
|
|
629
679
|
|
|
630
680
|
// Infinite scroll / partial updates
|
|
631
681
|
$(window).on('action:posts.loaded.ezoicInfinite action:topics.loaded.ezoicInfinite action:category.loaded.ezoicInfinite action:topic.loaded.ezoicInfinite', () => {
|
|
632
|
-
if (
|
|
682
|
+
if (isBlocked()) return;
|
|
633
683
|
scheduleRun();
|
|
634
684
|
});
|
|
635
685
|
}
|
|
@@ -641,7 +691,7 @@
|
|
|
641
691
|
ticking = true;
|
|
642
692
|
window.requestAnimationFrame(() => {
|
|
643
693
|
ticking = false;
|
|
644
|
-
if (!
|
|
694
|
+
if (!isBlocked()) scheduleRun();
|
|
645
695
|
});
|
|
646
696
|
}, { passive: true });
|
|
647
697
|
}
|
|
@@ -658,7 +708,7 @@
|
|
|
658
708
|
bindScroll();
|
|
659
709
|
|
|
660
710
|
// First paint: try hero + run
|
|
661
|
-
|
|
711
|
+
blockedUntil = 0;
|
|
662
712
|
insertHeroAdEarly().catch(() => {});
|
|
663
713
|
scheduleRun();
|
|
664
714
|
})();
|