nodebb-plugin-ezoic-infinite 1.6.89 → 1.6.91

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 CHANGED
@@ -7,59 +7,117 @@ const db = require.main.require('./src/database');
7
7
  const SETTINGS_KEY = 'ezoic-infinite';
8
8
  const plugin = {};
9
9
 
10
+ /**
11
+ * Récupère les paramètres du plugin
12
+ */
10
13
  async function getSettings() {
11
- return await meta.settings.get(SETTINGS_KEY);
14
+ return await meta.settings.get(SETTINGS_KEY);
12
15
  }
13
16
 
17
+ /**
18
+ * Vérifie si l'utilisateur actuel fait partie d'un groupe exclu
19
+ */
14
20
  async function isUserExcluded(uid, excludedGroups) {
15
- if (!uid || !excludedGroups || !excludedGroups.length) return false;
16
- const userGroups = await groups.getUserGroupsNames([uid]);
17
- return excludedGroups.some(g => userGroups[0].includes(g));
21
+ if (!uid || uid <= 0) return false;
22
+ if (!excludedGroups || !excludedGroups.length) return false;
23
+
24
+ // S'assurer que excludedGroups est un tableau
25
+ const excludedList = Array.isArray(excludedGroups) ? excludedGroups : [excludedGroups];
26
+
27
+ // Récupérer les groupes de l'utilisateur
28
+ const userGroups = await groups.getUserGroupsNames([uid]);
29
+
30
+ // Vérifier s'il y a une intersection entre les groupes de l'utilisateur et les exclus
31
+ return excludedList.some(g => userGroups[0].includes(g));
18
32
  }
19
33
 
34
+ /**
35
+ * Récupère la liste de tous les groupes pour l'affichage dans l'admin
36
+ */
20
37
  async function getAllGroups() {
21
- let names = await db.getSortedSetRange('groups:createtime', 0, -1);
22
- const data = await groups.getGroupsData(names);
23
- return data.filter(g => g && g.name).map(g => ({ name: g.name }));
38
+ let names = await db.getSortedSetRange('groups:createtime', 0, -1);
39
+ if (!names || !names.length) {
40
+ names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
41
+ }
42
+ const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
43
+ const data = await groups.getGroupsData(filtered);
44
+ const valid = data.filter(g => g && g.name);
45
+ valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
46
+ return valid;
24
47
  }
25
48
 
49
+ /**
50
+ * Initialisation du plugin (Routes et API)
51
+ */
26
52
  plugin.init = async ({ router, middleware }) => {
27
- async function render(req, res) {
28
- const settings = await getSettings();
29
- const allGroups = await getAllGroups();
30
- res.render('admin/plugins/ezoic-infinite', {
31
- ...settings,
32
- allGroups,
33
- });
34
- }
35
-
36
- router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
37
- router.get('/api/admin/plugins/ezoic-infinite', render);
38
-
39
- router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
40
- const settings = await getSettings();
41
- const excluded = await isUserExcluded(req.uid, settings.excludedGroups || []);
42
- res.json({
43
- excluded,
44
- enableBetweenAds: settings.enableBetweenAds === 'on',
45
- showFirstTopicAd: settings.showFirstTopicAd === 'on',
46
- placeholderIds: settings.placeholderIds,
47
- intervalPosts: settings.intervalPosts || 10,
48
- enableMessageAds: settings.enableMessageAds === 'on',
49
- showFirstMessageAd: settings.showFirstMessageAd === 'on',
50
- messagePlaceholderIds: settings.messagePlaceholderIds,
51
- messageIntervalPosts: settings.messageIntervalPosts || 10,
53
+ async function render(req, res) {
54
+ const settings = await getSettings();
55
+ const allGroups = await getAllGroups();
56
+
57
+ res.render('admin/plugins/ezoic-infinite', {
58
+ title: 'Ezoic Infinite Ads',
59
+ ...settings,
60
+ // On force les booléens pour les cases à cocher dans le template
61
+ enableCategoryAds_checked: settings.enableCategoryAds === 'on' || settings.enableCategoryAds === true ? 'checked' : '',
62
+ enableBetweenAds_checked: settings.enableBetweenAds === 'on' || settings.enableBetweenAds === true ? 'checked' : '',
63
+ enableMessageAds_checked: settings.enableMessageAds === 'on' || settings.enableMessageAds === true ? 'checked' : '',
64
+ allGroups,
65
+ });
66
+ }
67
+
68
+ router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
69
+ router.get('/api/admin/plugins/ezoic-infinite', render);
70
+
71
+ // API de configuration appelée par client.js
72
+ router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
73
+ const settings = await getSettings();
74
+
75
+ // Vérification de l'exclusion
76
+ const excludedGroups = settings.excludedGroups ? (Array.isArray(settings.excludedGroups) ? settings.excludedGroups : [settings.excludedGroups]) : [];
77
+ const excluded = await isUserExcluded(req.uid, excludedGroups);
78
+
79
+ res.json({
80
+ excluded,
81
+ // 1. Accueil (Catégories)
82
+ enableCategoryAds: settings.enableCategoryAds === 'on' || settings.enableCategoryAds === true,
83
+ showFirstCategoryAd: settings.showFirstCategoryAd === 'on' || settings.showFirstCategoryAd === true,
84
+ categoryPlaceholderIds: settings.categoryPlaceholderIds || "",
85
+ intervalCategories: parseInt(settings.intervalCategories, 10) || 5,
86
+
87
+ // 2. Liste des Topics (Page catégorie)
88
+ enableBetweenAds: settings.enableBetweenAds === 'on' || settings.enableBetweenAds === true,
89
+ showFirstTopicAd: settings.showFirstTopicAd === 'on' || settings.showFirstTopicAd === true,
90
+ placeholderIds: settings.placeholderIds || "",
91
+ intervalPosts: parseInt(settings.intervalPosts, 10) || 10,
92
+
93
+ // 3. Messages (Dans un post)
94
+ enableMessageAds: settings.enableMessageAds === 'on' || settings.enableMessageAds === true,
95
+ showFirstMessageAd: settings.showFirstMessageAd === 'on' || settings.showFirstMessageAd === true,
96
+ messagePlaceholderIds: settings.messagePlaceholderIds || "",
97
+ messageIntervalPosts: parseInt(settings.messageIntervalPosts, 10) || 10,
98
+ });
52
99
  });
53
- });
54
100
  };
55
101
 
102
+ /**
103
+ * Ajoute le lien dans le menu de navigation de l'administration
104
+ */
56
105
  plugin.addAdminNavigation = async (header) => {
57
- header.plugins.push({
58
- route: '/plugins/ezoic-infinite',
59
- icon: 'fa-ad',
60
- name: 'Ezoic Infinite'
61
- });
62
- return header;
106
+ header.plugins.push({
107
+ route: '/plugins/ezoic-infinite',
108
+ icon: 'fa-ad',
109
+ name: 'Ezoic Infinite'
110
+ });
111
+ return header;
112
+ };
113
+
114
+ /**
115
+ * Gère la sauvegarde des paramètres
116
+ */
117
+ plugin.onSettingsSet = async (data) => {
118
+ if (data.plugin === 'ezoic-infinite') {
119
+ // Logique optionnelle à l'enregistrement
120
+ }
63
121
  };
64
122
 
65
123
  module.exports = plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.89",
3
+ "version": "1.6.91",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -9,7 +9,6 @@
9
9
 
10
10
  let config = null;
11
11
  let isInternalChange = false;
12
- let definedIds = new Set();
13
12
  let pendingIds = new Set();
14
13
  let refreshTimer = null;
15
14
 
@@ -24,7 +23,6 @@
24
23
  return p;
25
24
  }
26
25
 
27
- // Cette fonction centralise les appels pour éviter de spammer Refresh()
28
26
  function triggerEzoic() {
29
27
  if (typeof window.ezstandalone === 'undefined' || pendingIds.size === 0) return;
30
28
 
@@ -32,21 +30,15 @@
32
30
  const idsToProcess = Array.from(pendingIds);
33
31
  pendingIds.clear();
34
32
 
35
- // 1. Définir les nouveaux IDs
33
+ // On enregistre les IDs
36
34
  window.ezstandalone.define(idsToProcess);
37
35
 
38
- // 2. Si c'est le tout premier appel de la page
39
- if (!window.ezstandalone.enabled) {
40
- window.ezstandalone.enable();
41
- window.ezstandalone.display();
42
- } else {
43
- // 3. Sinon, on utilise Refresh MAIS seulement si on n'est pas
44
- // dans la même micro-seconde que l'initialisation
45
- try {
46
- window.ezstandalone.refresh();
47
- } catch (e) {
48
- console.warn('[Ezoic] Refresh skipped to prevent conflict');
49
- }
36
+ // On appelle UNIQUEMENT refresh.
37
+ // Si Ezoic n'est pas "enabled", il ignorera l'appel sans erreur bloquante.
38
+ try {
39
+ window.ezstandalone.refresh();
40
+ } catch (e) {
41
+ console.debug('[Ezoic] Refresh deferred');
50
42
  }
51
43
  });
52
44
  }
@@ -54,39 +46,38 @@
54
46
  function callEzoic(id) {
55
47
  const pid = parseInt(id, 10);
56
48
  if (isNaN(pid)) return;
57
-
58
- // On ajoute l'ID à la liste des IDs à traiter
59
49
  pendingIds.add(pid);
60
- definedIds.add(pid);
61
50
 
62
- // On attend 200ms que les autres pubs du même scroll arrivent
63
- // avant de lancer l'appel Ezoic unique. C'est le "Debounce".
64
51
  clearTimeout(refreshTimer);
65
- refreshTimer = setTimeout(triggerEzoic, 200);
52
+ refreshTimer = setTimeout(triggerEzoic, 300);
66
53
  }
67
54
 
68
55
  function redistribute() {
69
56
  if (!config || config.excluded) return;
70
57
 
71
- // Sélecteurs Harmony
58
+ // 1. Accueil (Catégories)
59
+ const categoryItems = document.querySelectorAll('.category-item, [component="categories/category"]');
60
+ // 2. Liste Topics
72
61
  const topicItems = document.querySelectorAll('li[component="category/topic"]');
62
+ // 3. Messages
73
63
  const postItems = document.querySelectorAll('[component="post"]');
74
64
 
65
+ if (categoryItems.length > 0 && config.enableCategoryAds) {
66
+ process(Array.from(categoryItems), 'home', config.intervalCategories, config.showFirstCategoryAd);
67
+ }
75
68
  if (topicItems.length > 0 && config.enableBetweenAds) {
76
- processItems(Array.from(topicItems), 'between', config.intervalPosts, config.showFirstTopicAd);
69
+ process(Array.from(topicItems), 'topic-list', config.intervalPosts, config.showFirstTopicAd);
77
70
  }
78
-
79
71
  if (postItems.length > 0 && config.enableMessageAds) {
80
- processItems(Array.from(postItems), 'message', config.messageIntervalPosts, config.showFirstMessageAd);
72
+ process(Array.from(postItems), 'message', config.messageIntervalPosts, config.showFirstMessageAd);
81
73
  }
82
74
  }
83
75
 
84
- function processItems(items, kind, interval, showFirst) {
76
+ function process(items, kind, interval, showFirst) {
85
77
  const int = parseInt(interval, 10) || 10;
86
78
  items.forEach((item, index) => {
87
79
  const pos = index + 1;
88
80
  const shouldHaveAd = (pos === 1 && showFirst) || (pos % int === 0);
89
-
90
81
  const next = item.nextElementSibling;
91
82
  if (shouldHaveAd && !(next && next.classList.contains(WRAP_CLASS))) {
92
83
  const pool = getPool();
@@ -110,19 +101,17 @@
110
101
  const setup = (raw, kind) => {
111
102
  if (!raw) return;
112
103
  raw.split(/[\s,]+/).filter(Boolean).forEach(id => {
113
- if (!document.querySelector(`[data-placeholder-id="${id}"]`)) {
114
- const d = document.createElement('div');
115
- d.className = WRAP_CLASS;
116
- d.setAttribute('data-kind', kind);
117
- d.setAttribute('data-placeholder-id', id);
118
- d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}"></div>`;
119
- pool.appendChild(d);
120
- }
104
+ const d = document.createElement('div');
105
+ d.className = WRAP_CLASS;
106
+ d.setAttribute('data-kind', kind);
107
+ d.setAttribute('data-placeholder-id', id);
108
+ d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}"></div>`;
109
+ pool.appendChild(d);
121
110
  });
122
111
  };
123
- setup(config.placeholderIds, 'between');
112
+ setup(config.categoryPlaceholderIds, 'home');
113
+ setup(config.placeholderIds, 'topic-list');
124
114
  setup(config.messagePlaceholderIds, 'message');
125
-
126
115
  redistribute();
127
116
 
128
117
  const observer = new MutationObserver(() => {
@@ -132,19 +121,7 @@
132
121
  });
133
122
  }
134
123
 
135
- // Crucial pour les SPA : Nettoyer l'état d'Ezoic lors d'un changement de page
136
- window.addEventListener('action:ajaxify.start', function() {
137
- if (typeof window.ezstandalone !== 'undefined' && window.ezstandalone.enabled) {
138
- // Optionnel : window.ezstandalone.destroy();
139
- // Mais souvent il vaut mieux juste laisser Ezoic gérer
140
- }
141
- definedIds.clear();
142
- pendingIds.clear();
143
- });
144
-
145
- window.addEventListener('action:ajaxify.end', function() {
146
- setTimeout(redistribute, 600);
147
- });
124
+ window.addEventListener('action:ajaxify.end', () => setTimeout(redistribute, 500));
148
125
 
149
126
  if (document.readyState === 'loading') {
150
127
  document.addEventListener('DOMContentLoaded', init);
package/public/style.css CHANGED
@@ -1,29 +1,34 @@
1
- /* Container global */
1
+ /* Container global de pub */
2
2
  .nodebb-ezoic-wrap {
3
3
  display: block !important;
4
4
  width: 100% !important;
5
- min-width: 100% !important;
6
5
  margin: 20px 0 !important;
7
6
  min-height: 250px;
8
7
  clear: both;
9
8
  text-align: center;
10
9
  }
11
10
 
12
- /* Fix spécifique pour la liste des topics (Accueil Harmony) */
11
+ /* Accueil (Catégories) */
12
+ [component="categories/category"] + .nodebb-ezoic-wrap,
13
+ .category-item + .nodebb-ezoic-wrap {
14
+ margin: 40px 0 !important;
15
+ border-top: 1px solid rgba(0,0,0,0.05);
16
+ }
17
+
18
+ /* Liste des Topics (Page catégorie) */
13
19
  li[component="category/topic"] + .nodebb-ezoic-wrap {
14
20
  list-style: none !important;
15
- margin-left: 0 !important;
16
21
  padding: 15px 0;
17
22
  border-bottom: 1px solid rgba(0,0,0,0.05);
18
23
  }
19
24
 
20
- /* Si le bloc est vide ou n'a pas encore chargé */
21
- .nodebb-ezoic-wrap:empty {
22
- min-height: 1px;
25
+ /* Messages (Dans un post) */
26
+ [component="post"] + .nodebb-ezoic-wrap {
27
+ margin: 40px 0 !important;
28
+ padding: 20px;
29
+ background: rgba(0,0,0,0.01);
23
30
  }
24
31
 
25
- /* Neutralisation des marges internes d'Ezoic */
26
- .ezoic-ad {
27
- margin: 0 auto !important;
28
- display: inline-block !important;
29
- }
32
+ /* Masquage des blocs vides */
33
+ .nodebb-ezoic-wrap:empty { min-height: 1px; }
34
+ .ezoic-ad { margin: 0 auto !important; }