nodebb-plugin-ezoic-infinite 1.7.42 → 1.7.43
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 +29 -13
- package/package.json +1 -1
- package/public/client.js +58 -67
package/library.js
CHANGED
|
@@ -15,9 +15,11 @@ function normalizeExcludedGroups(value) {
|
|
|
15
15
|
// NodeBB stocke les settings multi-valeurs comme string JSON "[\"group1\",\"group2\"]"
|
|
16
16
|
const s = String(value).trim();
|
|
17
17
|
if (s.startsWith('[')) {
|
|
18
|
-
try {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(s);
|
|
20
|
+
if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean);
|
|
21
|
+
} catch (_) {}
|
|
19
22
|
}
|
|
20
|
-
// Fallback : séparation par virgule
|
|
21
23
|
return s.split(',').map(v => v.trim()).filter(Boolean);
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -28,7 +30,15 @@ function parseBool(v, def = false) {
|
|
|
28
30
|
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
// ── Cache groupes (fix #7 : getAllGroups sans cache sur page ACP) ───────────
|
|
34
|
+
|
|
35
|
+
let _groupsCache = null;
|
|
36
|
+
let _groupsCacheAt = 0;
|
|
37
|
+
const GROUPS_TTL = 60_000; // 1 minute
|
|
38
|
+
|
|
31
39
|
async function getAllGroups() {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (_groupsCache && (now - _groupsCacheAt) < GROUPS_TTL) return _groupsCache;
|
|
32
42
|
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
33
43
|
if (!names || !names.length) {
|
|
34
44
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
@@ -37,7 +47,9 @@ async function getAllGroups() {
|
|
|
37
47
|
const data = await groups.getGroupsData(filtered);
|
|
38
48
|
const valid = data.filter(g => g && g.name);
|
|
39
49
|
valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
40
|
-
|
|
50
|
+
_groupsCache = valid;
|
|
51
|
+
_groupsCacheAt = now;
|
|
52
|
+
return _groupsCache;
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
// ── Settings cache (30s TTL) ────────────────────────────────────────────────
|
|
@@ -88,7 +100,10 @@ ezstandalone.cmd = ezstandalone.cmd || [];
|
|
|
88
100
|
// ── Hooks ──────────────────────────────────────────────────────────────────
|
|
89
101
|
|
|
90
102
|
plugin.onSettingsSet = function (data) {
|
|
91
|
-
if (data && data.hash === SETTINGS_KEY)
|
|
103
|
+
if (data && data.hash === SETTINGS_KEY) {
|
|
104
|
+
_settingsCache = null;
|
|
105
|
+
_groupsCache = null; // invalider aussi le cache groupes
|
|
106
|
+
}
|
|
92
107
|
};
|
|
93
108
|
|
|
94
109
|
plugin.addAdminNavigation = async (header) => {
|
|
@@ -101,11 +116,11 @@ plugin.addAdminNavigation = async (header) => {
|
|
|
101
116
|
* Injecte les scripts Ezoic dans le <head> via templateData.customHTML.
|
|
102
117
|
*
|
|
103
118
|
* NodeBB v4 / thème Harmony : header.tpl contient {{customHTML}} dans le <head>
|
|
104
|
-
* (render.js
|
|
105
|
-
* Le hook filter:middleware.renderHeader reçoit templateData = headerFooterData
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
119
|
+
* (render.js : templateValues.customHTML = meta.config.customHTML).
|
|
120
|
+
* Le hook filter:middleware.renderHeader reçoit templateData = headerFooterData.
|
|
121
|
+
* On préfixe customHTML pour passer AVANT le customHTML admin tout en le préservant.
|
|
122
|
+
*
|
|
123
|
+
* Fix #3 : erreurs loggées côté serveur plutôt qu'avalées silencieusement.
|
|
109
124
|
*/
|
|
110
125
|
plugin.injectEzoicHead = async (data) => {
|
|
111
126
|
try {
|
|
@@ -113,17 +128,18 @@ plugin.injectEzoicHead = async (data) => {
|
|
|
113
128
|
const uid = data.req?.uid ?? 0;
|
|
114
129
|
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
115
130
|
if (!excluded) {
|
|
116
|
-
// Préfixer : nos scripts d'abord, puis le customHTML existant de l'admin
|
|
117
131
|
data.templateData.customHTML = EZOIC_SCRIPTS + (data.templateData.customHTML || '');
|
|
118
132
|
}
|
|
119
|
-
} catch (
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// Log l'erreur mais ne pas planter le rendu de la page
|
|
135
|
+
console.error('[ezoic-infinite] injectEzoicHead error:', err.message || err);
|
|
136
|
+
}
|
|
120
137
|
return data;
|
|
121
138
|
};
|
|
122
139
|
|
|
123
140
|
plugin.init = async ({ router, middleware }) => {
|
|
124
141
|
async function render(req, res) {
|
|
125
|
-
const settings
|
|
126
|
-
const allGroups = await getAllGroups();
|
|
142
|
+
const [settings, allGroups] = await Promise.all([getSettings(), getAllGroups()]);
|
|
127
143
|
res.render('admin/plugins/ezoic-infinite', {
|
|
128
144
|
title: 'Ezoic Infinite Ads',
|
|
129
145
|
...settings,
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,69 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — client.js v51
|
|
3
3
|
*
|
|
4
4
|
* Historique des corrections majeures
|
|
5
5
|
* ────────────────────────────────────
|
|
6
6
|
* v18 Ancrage stable par data-pid / data-index au lieu d'ordinalMap fragile.
|
|
7
|
-
*
|
|
8
|
-
* v19 Intervalle global basé sur l'ordinal absolu (data-index), pas sur
|
|
9
|
-
* la position dans le batch courant.
|
|
10
|
-
*
|
|
7
|
+
* v19 Intervalle global basé sur l'ordinal absolu (data-index).
|
|
11
8
|
* v20 Table KIND : anchorAttr / ordinalAttr / baseTag par kindClass.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* v25 Fix scroll-up / virtualisation NodeBB : decluster + grace period.
|
|
17
|
-
*
|
|
18
|
-
* v26 Suppression définitive du recyclage d'id (causait réinjection en haut).
|
|
19
|
-
*
|
|
20
|
-
* v27 pruneOrphans supprimé (faux-orphelins sur virtualisation NodeBB posts).
|
|
21
|
-
*
|
|
9
|
+
* IO fixe (une instance, jamais recréée). Fix TCF locator.
|
|
10
|
+
* v25 Fix scroll-up / virtualisation NodeBB.
|
|
11
|
+
* v26 Suppression recyclage d'id (causait réinjection en haut).
|
|
12
|
+
* v27 pruneOrphans supprimé (faux-orphelins sur virtualisation posts).
|
|
22
13
|
* v28 decluster supprimé. Wraps persistants pendant la session.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* rechargement. Redondant depuis le fix normalizeExcludedGroups (v49).
|
|
38
|
-
*
|
|
39
|
-
* v43 Seuil de recyclage abaissé à -vh + unobserve avant recyclage.
|
|
40
|
-
*
|
|
41
|
-
* v42 Seuil -(IO_MARGIN + vh) (trop strict, peu de wraps éligibles).
|
|
42
|
-
*
|
|
43
|
-
* v41 Seuil -1vh (trop permissif sur mobile, ignorait IO_MARGIN).
|
|
44
|
-
*
|
|
45
|
-
* v40 Recyclage slots via destroyPlaceholders+define+displayMore avec délais.
|
|
46
|
-
* Séquence : destroy → 300ms → define → 300ms → displayMore.
|
|
47
|
-
* Testé manuellement : fonctionne. displayMore = API Ezoic infinite scroll.
|
|
48
|
-
*
|
|
49
|
-
* v38 Pool épuisé = fin de quota Ezoic par page-view. ez.refresh() interdit
|
|
50
|
-
* sur la même page que ez.enable() — supprimé. moveDistantWrap supprimé :
|
|
51
|
-
* déplacer un wrap "already defined" ne re-sert aucune pub. Pool épuisé →
|
|
52
|
-
* break propre dans injectBetween. muteConsole : ajout warnings refresh.
|
|
53
|
-
*
|
|
54
|
-
* v36 Optimisations chemin critique (scroll → injectBetween) :
|
|
55
|
-
* – S.wrapByKey Map<anchorKey,wrap> : findWrap() passe de querySelector
|
|
56
|
-
* sur tout le doc à un lookup O(1). Mis à jour dans insertAfter,
|
|
57
|
-
* dropWrap et cleanup.
|
|
58
|
-
* – wrapIsLive allégé : pour les voisins immédiats on vérifie les
|
|
59
|
-
* attributs du nœud lui-même sans querySelector global.
|
|
60
|
-
* – MutationObserver : matches() vérifié avant querySelector() pour
|
|
61
|
-
* court-circuiter les sous-arbres entiers ajoutés par NodeBB.
|
|
62
|
-
*
|
|
63
|
-
* v35 Revue complète prod-ready :
|
|
64
|
-
* – initPools protégé contre ré-initialisation inutile (S.poolsReady).
|
|
65
|
-
* – muteConsole élargit à "No valid placeholders for loadMore".
|
|
66
|
-
* – Commentaires et historique nettoyés.
|
|
14
|
+
* v32 pruneOrphansBetween réactivé uniquement pour topics de catégorie.
|
|
15
|
+
* NodeBB NE virtualise PAS les topics → prune safe.
|
|
16
|
+
* Toujours désactivé pour posts (virtualisation → faux-orphelins).
|
|
17
|
+
* v35 initPools protégé (S.poolsReady). muteConsole élargi.
|
|
18
|
+
* v36 S.wrapByKey Map O(1). wrapIsLive allégé. MutationObserver optimisé.
|
|
19
|
+
* v38 ez.refresh() interdit → supprimé. Pool épuisé → break propre.
|
|
20
|
+
* v40 Recyclage via destroyPlaceholders+define+displayMore (délais 300ms).
|
|
21
|
+
* v43 Seuil recyclage -1vh + unobserve avant déplacement.
|
|
22
|
+
* v49 Fix normalizeExcludedGroups : JSON.parse du tableau NodeBB.
|
|
23
|
+
* v50 bindLoginCheck() supprimé (NodeBB recharge après login).
|
|
24
|
+
* v51 #2 fetchConfig() : backoff 10s sur échec réseau (évite spam API).
|
|
25
|
+
* #4 recycleAndMove : re-observe le placeholder après displayMore.
|
|
26
|
+
* #5 ensureTcfLocator : MutationObserver global disconnecté au cleanup.
|
|
27
|
+
* #6 getIO() recrée l'observer si le type d'écran change (resize).
|
|
67
28
|
*/
|
|
68
29
|
(function nbbEzoicInfinite() {
|
|
69
30
|
'use strict';
|
|
@@ -127,6 +88,7 @@
|
|
|
127
88
|
pending: [], // ids en attente de slot inflight
|
|
128
89
|
pendingSet: new Set(),
|
|
129
90
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
91
|
+
tcfObs: null, // MutationObserver TCF locator
|
|
130
92
|
runQueued: false,
|
|
131
93
|
burstActive: false,
|
|
132
94
|
burstDeadline: 0,
|
|
@@ -149,12 +111,17 @@
|
|
|
149
111
|
|
|
150
112
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
151
113
|
|
|
114
|
+
// Fix #2 : backoff 10s sur échec pour éviter de spammer l'API
|
|
115
|
+
// si le réseau est lent ou la route indisponible.
|
|
116
|
+
let _cfgErrorUntil = 0;
|
|
152
117
|
async function fetchConfig() {
|
|
153
118
|
if (S.cfg) return S.cfg;
|
|
119
|
+
if (Date.now() < _cfgErrorUntil) return null;
|
|
154
120
|
try {
|
|
155
121
|
const r = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
156
|
-
if (r.ok) S.cfg = await r.json();
|
|
157
|
-
|
|
122
|
+
if (r.ok) { S.cfg = await r.json(); }
|
|
123
|
+
else { _cfgErrorUntil = Date.now() + 10_000; }
|
|
124
|
+
} catch (_) { _cfgErrorUntil = Date.now() + 10_000; }
|
|
158
125
|
return S.cfg;
|
|
159
126
|
}
|
|
160
127
|
|
|
@@ -356,7 +323,15 @@
|
|
|
356
323
|
// Délais requis : destroyPlaceholders est asynchrone en interne
|
|
357
324
|
const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
|
|
358
325
|
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
359
|
-
|
|
326
|
+
// Fix #4 : re-observer le ph après displayMore pour déclencher scheduleEmptyCheck
|
|
327
|
+
// si la pub ne charge pas (détection wrap vide).
|
|
328
|
+
const doDisplay = () => {
|
|
329
|
+
try { ez.displayMore([id]); } catch (_) {}
|
|
330
|
+
observePh(id);
|
|
331
|
+
const t = ts();
|
|
332
|
+
try { best.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
333
|
+
scheduleEmptyCheck(id, t);
|
|
334
|
+
};
|
|
360
335
|
try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
361
336
|
|
|
362
337
|
return { id, wrap: best };
|
|
@@ -486,8 +461,15 @@
|
|
|
486
461
|
|
|
487
462
|
// ── IntersectionObserver & Show ────────────────────────────────────────────
|
|
488
463
|
|
|
464
|
+
// Fix #6 : recréer l'observer si le type d'écran change (rotation, resize).
|
|
465
|
+
// isMobile() est évalué à chaque appel pour détecter un changement.
|
|
466
|
+
let _ioMobile = null; // dernier état mobile/desktop pour lequel l'IO a été créé
|
|
489
467
|
function getIO() {
|
|
490
|
-
|
|
468
|
+
const mobile = isMobile();
|
|
469
|
+
if (S.io && _ioMobile === mobile) return S.io;
|
|
470
|
+
// Type d'écran changé ou première création : (re)créer l'observer
|
|
471
|
+
if (S.io) { try { S.io.disconnect(); } catch (_) {} S.io = null; }
|
|
472
|
+
_ioMobile = mobile;
|
|
491
473
|
try {
|
|
492
474
|
S.io = new IntersectionObserver(entries => {
|
|
493
475
|
for (const e of entries) {
|
|
@@ -496,7 +478,7 @@
|
|
|
496
478
|
const id = parseInt(e.target.getAttribute('data-ezoic-id'), 10);
|
|
497
479
|
if (Number.isFinite(id) && id > 0) enqueueShow(id);
|
|
498
480
|
}
|
|
499
|
-
}, { root: null, rootMargin:
|
|
481
|
+
}, { root: null, rootMargin: mobile ? IO_MARGIN_MOBILE : IO_MARGIN_DESKTOP, threshold: 0 });
|
|
500
482
|
} catch (_) { S.io = null; }
|
|
501
483
|
return S.io;
|
|
502
484
|
}
|
|
@@ -702,6 +684,8 @@
|
|
|
702
684
|
S.pendingSet.clear();
|
|
703
685
|
S.burstActive = false;
|
|
704
686
|
S.runQueued = false;
|
|
687
|
+
if (S.domObs) { S.domObs.disconnect(); S.domObs = null; }
|
|
688
|
+
if (S.tcfObs) { S.tcfObs.disconnect(); S.tcfObs = null; }
|
|
705
689
|
}
|
|
706
690
|
|
|
707
691
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
@@ -758,9 +742,10 @@
|
|
|
758
742
|
(document.body || document.documentElement).appendChild(f);
|
|
759
743
|
};
|
|
760
744
|
inject();
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
745
|
+
// Fix #5 : ref stockée dans S pour pouvoir déconnecter au cleanup
|
|
746
|
+
if (!S.tcfObs) {
|
|
747
|
+
S.tcfObs = new MutationObserver(inject);
|
|
748
|
+
S.tcfObs.observe(document.documentElement, { childList: true, subtree: true });
|
|
764
749
|
}
|
|
765
750
|
} catch (_) {}
|
|
766
751
|
}
|
|
@@ -826,6 +811,12 @@
|
|
|
826
811
|
ticking = true;
|
|
827
812
|
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
828
813
|
}, { passive: true });
|
|
814
|
+
// Fix #6 : détecter rotation/resize pour recréer l'IO avec la bonne marge
|
|
815
|
+
let resizeTimer = 0;
|
|
816
|
+
window.addEventListener('resize', () => {
|
|
817
|
+
clearTimeout(resizeTimer);
|
|
818
|
+
resizeTimer = setTimeout(() => { getIO(); }, 500);
|
|
819
|
+
}, { passive: true });
|
|
829
820
|
}
|
|
830
821
|
|
|
831
822
|
// ── Boot ───────────────────────────────────────────────────────────────────
|