nodebb-plugin-ezoic-infinite 1.4.8 → 1.4.10
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/library.js +21 -5
- package/package.json +1 -1
- package/plugin.json +4 -0
- package/public/admin.js +2 -3
- package/public/client.js +75 -82
package/library.js
CHANGED
|
@@ -27,13 +27,21 @@ async function getAllGroups() {
|
|
|
27
27
|
}
|
|
28
28
|
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
29
29
|
const data = await groups.getGroupsData(filtered);
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
// Filter out nulls (groups deleted between the sorted-set read and getGroupsData)
|
|
31
|
+
const valid = data.filter(g => g && g.name);
|
|
32
|
+
valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
33
|
+
return valid;
|
|
33
34
|
}
|
|
35
|
+
let _settingsCache = null;
|
|
36
|
+
let _settingsCacheAt = 0;
|
|
37
|
+
const SETTINGS_TTL = 30000; // 30s
|
|
38
|
+
|
|
34
39
|
async function getSettings() {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (_settingsCache && (now - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
|
|
35
42
|
const s = await meta.settings.get(SETTINGS_KEY);
|
|
36
|
-
|
|
43
|
+
_settingsCacheAt = Date.now();
|
|
44
|
+
_settingsCache = {
|
|
37
45
|
// Between-post ads (simple blocks) in category topic list
|
|
38
46
|
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
39
47
|
showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
|
|
@@ -54,6 +62,7 @@ async function getSettings() {
|
|
|
54
62
|
|
|
55
63
|
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
56
64
|
};
|
|
65
|
+
return _settingsCache;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
async function isUserExcluded(uid, excludedGroups) {
|
|
@@ -62,6 +71,13 @@ async function isUserExcluded(uid, excludedGroups) {
|
|
|
62
71
|
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
plugin.onSettingsSet = function (data) {
|
|
75
|
+
// Invalider le cache dès que les settings de ce plugin sont sauvegardés via l'ACP
|
|
76
|
+
if (data && data.hash === SETTINGS_KEY) {
|
|
77
|
+
_settingsCache = null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
65
81
|
plugin.addAdminNavigation = async (header) => {
|
|
66
82
|
header.plugins = header.plugins || [];
|
|
67
83
|
header.plugins.push({
|
|
@@ -89,7 +105,7 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
89
105
|
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
90
106
|
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
91
107
|
|
|
92
|
-
router.get('/api/plugins/ezoic-infinite/config',
|
|
108
|
+
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
93
109
|
const settings = await getSettings();
|
|
94
110
|
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
95
111
|
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -13,11 +13,10 @@
|
|
|
13
13
|
e.preventDefault();
|
|
14
14
|
|
|
15
15
|
Settings.save('ezoic-infinite', $form, function () {
|
|
16
|
-
// Toast vert (NodeBB core)
|
|
17
16
|
if (alerts && typeof alerts.success === 'function') {
|
|
18
|
-
alerts.success('
|
|
17
|
+
alerts.success('[[admin/settings:saved]]');
|
|
19
18
|
} else if (window.app && typeof window.app.alertSuccess === 'function') {
|
|
20
|
-
window.app.alertSuccess('
|
|
19
|
+
window.app.alertSuccess('[[admin/settings:saved]]');
|
|
21
20
|
}
|
|
22
21
|
});
|
|
23
22
|
});
|
package/public/client.js
CHANGED
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
liveBetween: [],
|
|
32
32
|
liveMessage: [],
|
|
33
33
|
liveCategory: [],
|
|
34
|
-
usedCategories: new Set(),
|
|
35
34
|
|
|
36
35
|
lastShowById: new Map(),
|
|
37
36
|
pendingById: new Set(),
|
|
@@ -48,6 +47,8 @@
|
|
|
48
47
|
|
|
49
48
|
obs: null,
|
|
50
49
|
attempts: 0,
|
|
50
|
+
poolWaitAttempts: 0,
|
|
51
|
+
__scrollBound: false,
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
function normalizeBool(v) {
|
|
@@ -143,18 +144,9 @@
|
|
|
143
144
|
else window.ezstandalone.cmd.push(call);
|
|
144
145
|
} catch (e) {}
|
|
145
146
|
}
|
|
146
|
-
} catch (e) {}
|
|
147
|
-
};
|
|
148
|
-
try {
|
|
149
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
150
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
151
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
152
|
-
else window.ezstandalone.cmd.push(call);
|
|
153
|
-
} catch (e) {}
|
|
154
|
-
}
|
|
155
147
|
|
|
156
148
|
function getRecyclable(liveArr) {
|
|
157
|
-
const margin =
|
|
149
|
+
const margin = 600;
|
|
158
150
|
for (let i = 0; i < liveArr.length; i++) {
|
|
159
151
|
const entry = liveArr[i];
|
|
160
152
|
if (!entry || !entry.wrap || !entry.wrap.isConnected) { liveArr.splice(i, 1); i--; continue; }
|
|
@@ -167,17 +159,6 @@
|
|
|
167
159
|
return null;
|
|
168
160
|
}
|
|
169
161
|
|
|
170
|
-
function moveWrapAfter(wrap, target, kindClass, afterPos) {
|
|
171
|
-
try {
|
|
172
|
-
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
173
|
-
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
174
|
-
target.insertAdjacentElement('afterend', wrap);
|
|
175
|
-
return true;
|
|
176
|
-
} catch (e) {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
162
|
function pickId(pool, liveArr) {
|
|
182
163
|
if (pool.length) return { id: pool.shift(), recycled: null };
|
|
183
164
|
const recycled = getRecyclable(liveArr);
|
|
@@ -245,30 +226,20 @@
|
|
|
245
226
|
try {
|
|
246
227
|
state.usedTopics.forEach((id) => ids.push(id));
|
|
247
228
|
state.usedPosts.forEach((id) => ids.push(id));
|
|
248
|
-
state.usedCategories
|
|
229
|
+
state.usedCategories.forEach((id) => ids.push(id));
|
|
249
230
|
} catch (e) {}
|
|
250
231
|
destroyPlaceholderIds(ids);
|
|
251
232
|
}
|
|
252
|
-
} catch (e) {}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
257
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
258
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
259
|
-
else window.ezstandalone.cmd.push(call);
|
|
260
|
-
} catch (e) {}
|
|
261
|
-
}
|
|
262
233
|
|
|
263
234
|
function patchShowAds() {
|
|
264
235
|
// Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
|
|
265
236
|
try {
|
|
266
237
|
window.ezstandalone = window.ezstandalone || {};
|
|
267
238
|
const ez = window.ezstandalone;
|
|
268
|
-
if (
|
|
239
|
+
if (window.__nodebbEzoicPatched) return;
|
|
269
240
|
if (typeof ez.showAds !== 'function') return;
|
|
270
241
|
|
|
271
|
-
|
|
242
|
+
window.__nodebbEzoicPatched = true;
|
|
272
243
|
const orig = ez.showAds;
|
|
273
244
|
|
|
274
245
|
ez.showAds = function (arg) {
|
|
@@ -292,7 +263,7 @@
|
|
|
292
263
|
function markFilled(wrap) {
|
|
293
264
|
try {
|
|
294
265
|
if (!wrap) return;
|
|
295
|
-
|
|
266
|
+
// Disconnect the fill observer first (no need to remove+re-add the attribute)
|
|
296
267
|
try { if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; } } catch (e) {}
|
|
297
268
|
wrap.setAttribute('data-ezoic-filled', '1');
|
|
298
269
|
} catch (e) {}
|
|
@@ -339,8 +310,6 @@
|
|
|
339
310
|
}
|
|
340
311
|
return filled;
|
|
341
312
|
}
|
|
342
|
-
return filled;
|
|
343
|
-
}
|
|
344
313
|
|
|
345
314
|
function scheduleRefill(delay = 350) {
|
|
346
315
|
clearTimeout(state.retryTimer);
|
|
@@ -364,24 +333,19 @@
|
|
|
364
333
|
const id = state.retryQueue.shift();
|
|
365
334
|
if (!id) {
|
|
366
335
|
state.retryQueueRunning = false;
|
|
367
|
-
state.badIds = new Set();
|
|
368
|
-
state.definedIds = new Set();
|
|
369
336
|
return;
|
|
370
337
|
}
|
|
371
338
|
state.retryQueueSet.delete(id);
|
|
372
|
-
// If this id was previously attempted and still empty, force a full reset before re-requesting.
|
|
373
339
|
const attempts = (state.retryById.get(id) || 0);
|
|
374
340
|
const phNow = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
375
341
|
const wrapNow = phNow && phNow.parentElement;
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
// If this id was previously attempted and still empty, force a full reset before re-requesting.
|
|
382
|
-
if (attempts > 0 && wrapNow && wrapNow.isConnected && !isPlaceholderFilled(id) && !isWrapMarkedFilled(wrapNow)) {
|
|
342
|
+
// Reset before re-requesting if Ezoic already defined this id but placeholder is now empty,
|
|
343
|
+
// OR if a previous attempt already failed.
|
|
344
|
+
if (wrapNow && wrapNow.isConnected && !isPlaceholderFilled(id) && !isWrapMarkedFilled(wrapNow) &&
|
|
345
|
+
(state.definedIds.has(id) || attempts > 0)) {
|
|
383
346
|
destroyPlaceholderIds([id]);
|
|
384
347
|
resetPlaceholderInWrap(wrapNow, id);
|
|
348
|
+
attachFillObserver(wrapNow, id);
|
|
385
349
|
}
|
|
386
350
|
callShowAdsWhenReady(id);
|
|
387
351
|
setTimeout(step, 1100);
|
|
@@ -446,8 +410,11 @@
|
|
|
446
410
|
return false;
|
|
447
411
|
};
|
|
448
412
|
|
|
413
|
+
const startPageKey = state.pageKey;
|
|
449
414
|
let attempts = 0;
|
|
450
415
|
(function waitForPh() {
|
|
416
|
+
// Abort if the user navigated away since this showAds was scheduled
|
|
417
|
+
if (state.pageKey !== startPageKey) return;
|
|
451
418
|
attempts += 1;
|
|
452
419
|
const el = document.getElementById(phId);
|
|
453
420
|
if (el && el.isConnected) {
|
|
@@ -470,6 +437,7 @@
|
|
|
470
437
|
|
|
471
438
|
let tries = 0;
|
|
472
439
|
(function tick() {
|
|
440
|
+
if (state.pageKey !== startPageKey) { state.pendingById.delete(id); return; }
|
|
473
441
|
tries += 1;
|
|
474
442
|
if (doCall() || tries >= 5) {
|
|
475
443
|
if (tries >= 5) state.pendingById.delete(id);
|
|
@@ -484,30 +452,28 @@
|
|
|
484
452
|
})();
|
|
485
453
|
}
|
|
486
454
|
|
|
487
|
-
function nextId(pool) {
|
|
488
|
-
// backward compatible: the injector passes the pool array
|
|
489
|
-
if (Array.isArray(pool) && pool.length) return pool.shift();
|
|
490
|
-
return null;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
455
|
async function fetchConfig() {
|
|
494
456
|
if (state.cfg) return state.cfg;
|
|
495
457
|
if (state.cfgPromise) return state.cfgPromise;
|
|
496
458
|
|
|
497
459
|
state.cfgPromise = (async () => {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
460
|
+
const MAX_TRIES = 3;
|
|
461
|
+
let delay = 800;
|
|
462
|
+
for (let attempt = 1; attempt <= MAX_TRIES; attempt++) {
|
|
463
|
+
try {
|
|
464
|
+
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
465
|
+
if (res.ok) {
|
|
466
|
+
state.cfg = await res.json();
|
|
467
|
+
return state.cfg;
|
|
468
|
+
}
|
|
469
|
+
} catch (e) {}
|
|
470
|
+
if (attempt < MAX_TRIES) await new Promise(r => setTimeout(r, delay));
|
|
471
|
+
delay *= 2;
|
|
507
472
|
}
|
|
473
|
+
return null;
|
|
508
474
|
})();
|
|
509
475
|
|
|
510
|
-
return state.cfgPromise;
|
|
476
|
+
try { return await state.cfgPromise; } finally { state.cfgPromise = null; }
|
|
511
477
|
}
|
|
512
478
|
|
|
513
479
|
function initPools(cfg) {
|
|
@@ -577,14 +543,16 @@
|
|
|
577
543
|
|
|
578
544
|
liveArr.push({ id, wrap });
|
|
579
545
|
// If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
|
|
580
|
-
if (wrap && (
|
|
546
|
+
if (wrap && (
|
|
547
|
+
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) ||
|
|
548
|
+
(wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
549
|
+
)) {
|
|
581
550
|
try { wrap.remove(); } catch (e) {}
|
|
582
551
|
// Put id back if it was newly consumed (not recycled)
|
|
583
552
|
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
584
553
|
try { kindPool.unshift(id); } catch (e) {}
|
|
585
554
|
try { usedSet.delete(id); } catch (e) {}
|
|
586
555
|
}
|
|
587
|
-
inserted -= 0; // no-op
|
|
588
556
|
continue;
|
|
589
557
|
}
|
|
590
558
|
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
@@ -615,19 +583,24 @@
|
|
|
615
583
|
state.poolTopics = [];
|
|
616
584
|
state.poolPosts = [];
|
|
617
585
|
state.poolCategories = [];
|
|
618
|
-
state.poolCategories = [];
|
|
619
586
|
state.usedTopics.clear();
|
|
620
587
|
state.usedPosts.clear();
|
|
621
|
-
state.usedCategories
|
|
588
|
+
state.usedCategories.clear();
|
|
622
589
|
state.liveBetween = [];
|
|
623
590
|
state.liveMessage = [];
|
|
624
591
|
state.liveCategory = [];
|
|
625
|
-
state.usedCategories.clear();
|
|
626
592
|
|
|
627
593
|
state.lastShowById = new Map();
|
|
628
594
|
state.pendingById = new Set();
|
|
595
|
+
state.retryById = new Map();
|
|
596
|
+
state.retryQueue = [];
|
|
597
|
+
state.retryQueueSet = new Set();
|
|
598
|
+
state.retryQueueRunning = false;
|
|
599
|
+
state.badIds = new Set();
|
|
600
|
+
state.definedIds = new Set();
|
|
629
601
|
|
|
630
602
|
state.attempts = 0;
|
|
603
|
+
state.poolWaitAttempts = 0;
|
|
631
604
|
|
|
632
605
|
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
633
606
|
|
|
@@ -635,13 +608,14 @@
|
|
|
635
608
|
state.scheduled = false;
|
|
636
609
|
clearTimeout(state.timer);
|
|
637
610
|
state.timer = null;
|
|
611
|
+
clearTimeout(state.retryTimer);
|
|
612
|
+
state.retryTimer = null;
|
|
638
613
|
}
|
|
639
614
|
|
|
640
615
|
function ensureObserver() {
|
|
641
616
|
if (state.obs) return;
|
|
642
617
|
state.obs = new MutationObserver(() => scheduleRun('mutation'));
|
|
643
618
|
try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
644
|
-
setTimeout(() => { if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; } }, 15000);
|
|
645
619
|
}
|
|
646
620
|
|
|
647
621
|
async function runCore() {
|
|
@@ -660,7 +634,7 @@
|
|
|
660
634
|
inserted = injectBetween('ezoic-ad-message', getPostContainers(),
|
|
661
635
|
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
662
636
|
normalizeBool(cfg.showFirstMessageAd),
|
|
663
|
-
|
|
637
|
+
state.poolPosts,
|
|
664
638
|
state.usedPosts);
|
|
665
639
|
}
|
|
666
640
|
} else if (kind === 'categoryTopics') {
|
|
@@ -668,7 +642,7 @@
|
|
|
668
642
|
inserted = injectBetween('ezoic-ad-between', getTopicItems(),
|
|
669
643
|
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
670
644
|
normalizeBool(cfg.showFirstTopicAd),
|
|
671
|
-
|
|
645
|
+
state.poolTopics,
|
|
672
646
|
state.usedTopics);
|
|
673
647
|
}
|
|
674
648
|
} else if (kind === 'categories') {
|
|
@@ -676,7 +650,7 @@
|
|
|
676
650
|
inserted = injectBetween('ezoic-ad-categories', getCategoryItems(),
|
|
677
651
|
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
678
652
|
normalizeBool(cfg.showFirstCategoryAd),
|
|
679
|
-
|
|
653
|
+
state.poolCategories,
|
|
680
654
|
state.usedCategories);
|
|
681
655
|
}
|
|
682
656
|
}
|
|
@@ -696,7 +670,22 @@
|
|
|
696
670
|
return;
|
|
697
671
|
}
|
|
698
672
|
|
|
699
|
-
if (inserted >= MAX_INSERTS_PER_RUN)
|
|
673
|
+
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
674
|
+
// Plus d'insertions possibles ce cycle, continuer immédiatement
|
|
675
|
+
setTimeout(() => scheduleRun('continue'), 140);
|
|
676
|
+
} else if (inserted === 0 && count > 0) {
|
|
677
|
+
// Pool épuisé ou recyclage pas encore disponible.
|
|
678
|
+
// Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
|
|
679
|
+
// le temps de défiler hors écran et devenir recyclables.
|
|
680
|
+
if (state.poolWaitAttempts < 8) {
|
|
681
|
+
state.poolWaitAttempts += 1;
|
|
682
|
+
setTimeout(() => scheduleRun('pool-wait'), 400);
|
|
683
|
+
} else {
|
|
684
|
+
state.poolWaitAttempts = 0;
|
|
685
|
+
}
|
|
686
|
+
} else if (inserted > 0) {
|
|
687
|
+
state.poolWaitAttempts = 0;
|
|
688
|
+
}
|
|
700
689
|
}
|
|
701
690
|
|
|
702
691
|
function scheduleRun() {
|
|
@@ -752,13 +741,6 @@
|
|
|
752
741
|
});
|
|
753
742
|
}
|
|
754
743
|
|
|
755
|
-
cleanup();
|
|
756
|
-
bind();
|
|
757
|
-
ensureObserver();
|
|
758
|
-
state.pageKey = getPageKey();
|
|
759
|
-
scheduleRun();
|
|
760
|
-
setTimeout(scheduleRun, 250);
|
|
761
|
-
})()
|
|
762
744
|
function bindScroll() {
|
|
763
745
|
if (state.__scrollBound) return;
|
|
764
746
|
state.__scrollBound = true;
|
|
@@ -768,10 +750,21 @@
|
|
|
768
750
|
ticking = true;
|
|
769
751
|
window.requestAnimationFrame(() => {
|
|
770
752
|
ticking = false;
|
|
753
|
+
// Réinitialiser le compteur d'attente de recyclage à chaque reprise de scroll
|
|
754
|
+
state.poolWaitAttempts = 0;
|
|
771
755
|
enforceNoAdjacentAds();
|
|
772
756
|
scheduleRefill(200);
|
|
757
|
+
// Tenter aussi d'insérer de nouvelles pubs si de nouveaux items sont apparus
|
|
758
|
+
scheduleRun();
|
|
773
759
|
});
|
|
774
760
|
}, { passive: true });
|
|
775
761
|
}
|
|
776
762
|
|
|
777
|
-
;
|
|
763
|
+
cleanup();
|
|
764
|
+
bind();
|
|
765
|
+
bindScroll();
|
|
766
|
+
ensureObserver();
|
|
767
|
+
state.pageKey = getPageKey();
|
|
768
|
+
scheduleRun();
|
|
769
|
+
setTimeout(scheduleRun, 250);
|
|
770
|
+
})();
|