nodebb-plugin-ezoic-infinite 1.6.57 → 1.6.58
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 +115 -38
- package/package.json +1 -1
- package/public/client.js +21 -19
- package/public/style.css +17 -69
package/library.js
CHANGED
|
@@ -4,50 +4,127 @@ const meta = require.main.require('./src/meta');
|
|
|
4
4
|
const groups = require.main.require('./src/groups');
|
|
5
5
|
const db = require.main.require('./src/database');
|
|
6
6
|
|
|
7
|
+
const SETTINGS_KEY = 'ezoic-infinite';
|
|
7
8
|
const plugin = {};
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
function normalizeExcludedGroups(value) {
|
|
11
|
+
if (!value) return [];
|
|
12
|
+
if (Array.isArray(value)) return value;
|
|
13
|
+
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseBool(v, def = false) {
|
|
17
|
+
if (v === undefined || v === null || v === '') return def;
|
|
18
|
+
if (typeof v === 'boolean') return v;
|
|
19
|
+
const s = String(v).toLowerCase();
|
|
20
|
+
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function getAllGroups() {
|
|
24
|
+
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
25
|
+
if (!names || !names.length) {
|
|
26
|
+
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
27
|
+
}
|
|
28
|
+
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
29
|
+
const data = await groups.getGroupsData(filtered);
|
|
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;
|
|
34
|
+
}
|
|
35
|
+
let _settingsCache = null;
|
|
36
|
+
let _settingsCacheAt = 0;
|
|
37
|
+
const SETTINGS_TTL = 30000; // 30s
|
|
38
|
+
|
|
10
39
|
async function getSettings() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (_settingsCache && (now - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
|
|
42
|
+
const s = await meta.settings.get(SETTINGS_KEY);
|
|
43
|
+
_settingsCacheAt = Date.now();
|
|
44
|
+
_settingsCache = {
|
|
45
|
+
// Between-post ads (simple blocks) in category topic list
|
|
46
|
+
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
47
|
+
showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
|
|
48
|
+
placeholderIds: (s.placeholderIds || '').trim(),
|
|
49
|
+
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
50
|
+
|
|
51
|
+
// Home/categories list ads (between categories on / or /categories)
|
|
52
|
+
enableCategoryAds: parseBool(s.enableCategoryAds, false),
|
|
53
|
+
showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
|
|
54
|
+
categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
|
|
55
|
+
intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
|
|
56
|
+
|
|
57
|
+
// "Ad message" between replies (looks like a post)
|
|
58
|
+
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
59
|
+
showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
|
|
60
|
+
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
61
|
+
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
62
|
+
|
|
63
|
+
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
64
|
+
};
|
|
65
|
+
return _settingsCache;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function isUserExcluded(uid, excludedGroups) {
|
|
69
|
+
if (!uid || !excludedGroups.length) return false;
|
|
70
|
+
const userGroups = await groups.getUserGroups([uid]);
|
|
71
|
+
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
23
72
|
}
|
|
24
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
|
+
|
|
81
|
+
plugin.addAdminNavigation = async (header) => {
|
|
82
|
+
header.plugins = header.plugins || [];
|
|
83
|
+
header.plugins.push({
|
|
84
|
+
route: '/plugins/ezoic-infinite',
|
|
85
|
+
icon: 'fa-ad',
|
|
86
|
+
name: 'Ezoic Infinite Ads'
|
|
87
|
+
});
|
|
88
|
+
return header;
|
|
89
|
+
};
|
|
90
|
+
|
|
25
91
|
plugin.init = async ({ router, middleware }) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
37
|
-
const settings = await getSettings();
|
|
38
|
-
let excluded = false;
|
|
39
|
-
|
|
40
|
-
if (req.uid && settings.excludedGroups.length) {
|
|
41
|
-
excluded = await groups.isMemberOfAny(req.uid, settings.excludedGroups);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
res.json({
|
|
45
|
-
...settings,
|
|
46
|
-
excluded
|
|
47
|
-
});
|
|
92
|
+
async function render(req, res) {
|
|
93
|
+
const settings = await getSettings();
|
|
94
|
+
const allGroups = await getAllGroups();
|
|
95
|
+
|
|
96
|
+
res.render('admin/plugins/ezoic-infinite', {
|
|
97
|
+
title: 'Ezoic Infinite Ads',
|
|
98
|
+
...settings,
|
|
99
|
+
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
100
|
+
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
101
|
+
allGroups,
|
|
48
102
|
});
|
|
49
|
-
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
106
|
+
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
107
|
+
|
|
108
|
+
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
109
|
+
const settings = await getSettings();
|
|
110
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
50
111
|
|
|
51
|
-
|
|
112
|
+
res.json({
|
|
113
|
+
excluded,
|
|
114
|
+
enableBetweenAds: settings.enableBetweenAds,
|
|
115
|
+
showFirstTopicAd: settings.showFirstTopicAd,
|
|
116
|
+
placeholderIds: settings.placeholderIds,
|
|
117
|
+
intervalPosts: settings.intervalPosts,
|
|
118
|
+
enableCategoryAds: settings.enableCategoryAds,
|
|
119
|
+
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
120
|
+
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
121
|
+
intervalCategories: settings.intervalCategories,
|
|
122
|
+
enableMessageAds: settings.enableMessageAds,
|
|
123
|
+
showFirstMessageAd: settings.showFirstMessageAd,
|
|
124
|
+
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
125
|
+
messageIntervalPosts: settings.messageIntervalPosts,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
};
|
|
52
129
|
|
|
53
|
-
module.exports = plugin;
|
|
130
|
+
module.exports = plugin;
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
let config = null;
|
|
9
9
|
let usedIds = new Set();
|
|
10
|
-
let isEzoicEnabled = false; // Flag crucial pour corriger vos logs d'erreur
|
|
11
10
|
let scheduleTimer = null;
|
|
11
|
+
let isEzoicEnabled = false;
|
|
12
12
|
|
|
13
|
-
// --- SÉLECTEURS ---
|
|
13
|
+
// --- SÉLECTEURS COMPATIBLES HARMONY ---
|
|
14
14
|
function getTopicList() {
|
|
15
15
|
return document.querySelector('[component="topic/list"], [component="category"], [component="categories"]');
|
|
16
16
|
}
|
|
@@ -19,26 +19,26 @@
|
|
|
19
19
|
return document.querySelector('[component="topic"], [component="post/list"]');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// ---
|
|
22
|
+
// --- GESTION DU SDK EZOIC (SÉCURISÉE) ---
|
|
23
23
|
function callEzoic(idsToDefine) {
|
|
24
24
|
if (!window.ezstandalone || !idsToDefine.length) return;
|
|
25
25
|
|
|
26
26
|
try {
|
|
27
|
-
// 1.
|
|
27
|
+
// 1. On définit les nouveaux placeholders
|
|
28
28
|
window.ezstandalone.define(idsToDefine);
|
|
29
29
|
|
|
30
|
-
// 2.
|
|
30
|
+
// 2. On gère le cycle de vie (Correction de tes erreurs de log)
|
|
31
31
|
if (!isEzoicEnabled && !window.ezstandalone.enabled) {
|
|
32
32
|
window.ezstandalone.enable();
|
|
33
33
|
window.ezstandalone.display();
|
|
34
|
-
isEzoicEnabled = true;
|
|
35
|
-
console.log('[Ezoic] First Enable & Display');
|
|
34
|
+
isEzoicEnabled = true;
|
|
36
35
|
} else {
|
|
37
|
-
//
|
|
36
|
+
// Si déjà activé, on attend un cycle CPU pour refresh proprement
|
|
38
37
|
setTimeout(() => {
|
|
39
|
-
window.ezstandalone.
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (window.ezstandalone.enabled) {
|
|
39
|
+
window.ezstandalone.refresh();
|
|
40
|
+
}
|
|
41
|
+
}, 200);
|
|
42
42
|
}
|
|
43
43
|
} catch (e) {
|
|
44
44
|
console.warn('[Ezoic] SDK Error:', e);
|
|
@@ -58,22 +58,22 @@
|
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// --- INJECTION ---
|
|
61
|
+
// --- LOGIQUE D'INJECTION ---
|
|
62
62
|
function inject() {
|
|
63
63
|
if (!config || config.excluded) return;
|
|
64
64
|
const newIds = [];
|
|
65
|
-
|
|
66
|
-
// A. Topics / Categories
|
|
67
65
|
const ul = getTopicList();
|
|
66
|
+
|
|
67
|
+
// A. LISTE DES SUJETS
|
|
68
68
|
if (ul && config.enableBetweenAds) {
|
|
69
|
-
// Nettoyage
|
|
69
|
+
// Nettoyage des blocs vides qui pourraient traîner
|
|
70
70
|
ul.querySelectorAll('.' + WRAP_CLASS).forEach(w => {
|
|
71
71
|
if (!w.previousElementSibling || w.previousElementSibling.tagName !== 'LI') w.remove();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
const items = Array.from(ul.children).filter(c => c.tagName === 'LI' && !c.classList.contains(WRAP_CLASS));
|
|
75
75
|
|
|
76
|
-
//
|
|
76
|
+
// Pub n°1 (Après le premier sujet)
|
|
77
77
|
if (config.showFirstTopicAd && !ul.querySelector(`[${PINNED_ATTR}="true"]`)) {
|
|
78
78
|
const id = getNextId(config.placeholderIds);
|
|
79
79
|
if (id) {
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
//
|
|
85
|
+
// Intervalles
|
|
86
86
|
const interval = parseInt(config.intervalPosts, 10) || 5;
|
|
87
87
|
items.forEach((li, idx) => {
|
|
88
88
|
if ((idx + 1) % interval === 0 && idx > 0) {
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
// B.
|
|
100
|
+
// B. LISTE DES MESSAGES (Sujets ouverts)
|
|
101
101
|
const postList = getPostList();
|
|
102
102
|
if (postList && config.enableMessageAds) {
|
|
103
103
|
const posts = Array.from(postList.querySelectorAll('[component="post"]'));
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
function insertAd(target, id, isPinned, extraClass = '') {
|
|
119
|
+
if (!target) return;
|
|
119
120
|
const wrap = document.createElement('div');
|
|
120
121
|
wrap.className = `${WRAP_CLASS} ezoic-ad-between ${extraClass}`;
|
|
121
122
|
if (isPinned) wrap.setAttribute(PINNED_ATTR, 'true');
|
|
@@ -123,7 +124,7 @@
|
|
|
123
124
|
target.after(wrap);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
// ---
|
|
127
|
+
// --- INITIALISATION ---
|
|
127
128
|
async function init() {
|
|
128
129
|
try {
|
|
129
130
|
const res = await fetch('/api/plugins/ezoic-infinite/config');
|
|
@@ -131,6 +132,7 @@
|
|
|
131
132
|
|
|
132
133
|
inject();
|
|
133
134
|
|
|
135
|
+
// MutationObserver pour détecter le scroll infini
|
|
134
136
|
const observer = new MutationObserver((mutations) => {
|
|
135
137
|
if (mutations.some(m => m.addedNodes.length > 0)) {
|
|
136
138
|
if (scheduleTimer) clearTimeout(scheduleTimer);
|
package/public/style.css
CHANGED
|
@@ -1,80 +1,28 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* Conteneur de pub */
|
|
2
2
|
.nodebb-ezoic-wrap {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
overflow: hidden;
|
|
10
|
-
/* Améliore les performances de rendu sur NodeBB 4.x */
|
|
11
|
-
contain: layout style;
|
|
12
|
-
/* Assure une visibilité minimale pour que le SDK Ezoic puisse mesurer l'emplacement */
|
|
13
|
-
min-height: 50px;
|
|
3
|
+
display: block !important;
|
|
4
|
+
width: 100% !important;
|
|
5
|
+
clear: both !important;
|
|
6
|
+
margin: 25px 0 !important;
|
|
7
|
+
min-height: 100px; /* Crucial pour que Ezoic détecte l'emplacement */
|
|
8
|
+
text-align: center;
|
|
14
9
|
}
|
|
15
10
|
|
|
16
|
-
/*
|
|
11
|
+
/* Fix spécifique pour le premier sujet */
|
|
17
12
|
.nodebb-ezoic-wrap[data-ezoic-pinned="true"] {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
margin-top: 5px !important;
|
|
13
|
+
min-height: 150px;
|
|
14
|
+
border-bottom: 1px solid rgba(0,0,0,0.05);
|
|
15
|
+
margin-top: 0 !important;
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
/*
|
|
25
|
-
[id^="ezoic-pub-ad-placeholder-"] {
|
|
26
|
-
margin: 0 auto !important;
|
|
27
|
-
padding: 0 !important;
|
|
28
|
-
text-align: center;
|
|
29
|
-
min-height: 1px;
|
|
30
|
-
line-height: 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/* --- Forcer le centrage et la réactivité des pubs Ezoic --- */
|
|
34
|
-
.nodebb-ezoic-wrap span.ezoic-ad,
|
|
35
|
-
.nodebb-ezoic-wrap .ezoic-ad,
|
|
36
|
-
.nodebb-ezoic-wrap iframe {
|
|
37
|
-
display: block !important;
|
|
38
|
-
margin: 0 auto !important;
|
|
39
|
-
max-width: 100% !important;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/* --- Gestion des blocs vides (Anti-Trous blancs) --- */
|
|
43
|
-
/* Si Ezoic ne remplit pas l'emplacement ou si le script le vide,
|
|
44
|
-
on réduit l'espace pour ne pas casser le design du forum */
|
|
18
|
+
/* Cache les blocs vides pour éviter les trous blancs */
|
|
45
19
|
.nodebb-ezoic-wrap:empty {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
border: none !important;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/* --- Adaptation pour les messages (Topic View) --- */
|
|
53
|
-
.ezoic-msg-first {
|
|
54
|
-
margin-bottom: 35px !important;
|
|
55
|
-
padding-bottom: 15px !important;
|
|
56
|
-
border-bottom: 1px solid var(--border-color, #eee);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/* --- Correction pour le mode sombre (NodeBB 4 Harmony) --- */
|
|
60
|
-
[data-theme="dark"] .nodebb-ezoic-wrap[data-ezoic-pinned="true"] {
|
|
61
|
-
border-bottom-color: rgba(255,255,255,0.1);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/* --- Neutralisation des marges Ezoic sur mobile --- */
|
|
65
|
-
@media (max-width: 767px) {
|
|
66
|
-
.nodebb-ezoic-wrap {
|
|
67
|
-
margin: 10px 0 !important;
|
|
68
|
-
min-height: 30px;
|
|
69
|
-
}
|
|
70
|
-
.nodebb-ezoic-wrap[data-ezoic-pinned="true"] {
|
|
71
|
-
min-height: 100px;
|
|
72
|
-
}
|
|
20
|
+
height: 0 !important;
|
|
21
|
+
min-height: 0 !important;
|
|
22
|
+
margin: 0 !important;
|
|
73
23
|
}
|
|
74
24
|
|
|
75
|
-
/*
|
|
76
|
-
Ezoic injecte parfois du 'position: sticky' qui fait "flotter" les pubs
|
|
77
|
-
de manière erratique lors du scroll infini. On le neutralise ici. */
|
|
25
|
+
/* Évite que les pubs "volent" au scroll */
|
|
78
26
|
.nodebb-ezoic-wrap .ezads-sticky-intradiv {
|
|
79
|
-
|
|
27
|
+
position: static !important;
|
|
80
28
|
}
|