nodebb-plugin-ezoic-infinite 1.6.95 → 1.6.97

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,60 +7,29 @@ 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
- */
13
10
  async function getSettings() {
14
11
  return await meta.settings.get(SETTINGS_KEY);
15
12
  }
16
13
 
17
- /**
18
- * Vérifie si l'utilisateur actuel fait partie d'un groupe exclu
19
- */
20
14
  async function isUserExcluded(uid, excludedGroups) {
21
15
  if (!uid || uid <= 0) return false;
22
16
  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
17
  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
+ return excludedGroups.some(g => userGroups[0].includes(g));
32
19
  }
33
20
 
34
- /**
35
- * Récupère la liste de tous les groupes pour l'affichage dans l'admin
36
- */
37
21
  async function getAllGroups() {
38
22
  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;
23
+ const data = await groups.getGroupsData(names);
24
+ return data.filter(g => g && g.name).map(g => ({ name: g.name }));
47
25
  }
48
26
 
49
- /**
50
- * Initialisation du plugin (Routes et API)
51
- */
52
27
  plugin.init = async ({ router, middleware }) => {
53
28
  async function render(req, res) {
54
29
  const settings = await getSettings();
55
30
  const allGroups = await getAllGroups();
56
-
57
31
  res.render('admin/plugins/ezoic-infinite', {
58
- title: 'Ezoic Infinite Ads',
59
32
  ...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
33
  allGroups,
65
34
  });
66
35
  }
@@ -68,56 +37,30 @@ plugin.init = async ({ router, middleware }) => {
68
37
  router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
69
38
  router.get('/api/admin/plugins/ezoic-infinite', render);
70
39
 
71
- // API de configuration appelée par client.js
72
40
  router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
73
41
  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
-
42
+ const excluded = await isUserExcluded(req.uid, settings.excludedGroups || []);
79
43
  res.json({
80
44
  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 || "",
45
+ enableCategoryAds: settings.enableCategoryAds === 'on',
46
+ showFirstCategoryAd: settings.showFirstCategoryAd === 'on',
47
+ categoryPlaceholderIds: settings.categoryPlaceholderIds,
85
48
  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 || "",
49
+ enableBetweenAds: settings.enableBetweenAds === 'on',
50
+ showFirstTopicAd: settings.showFirstTopicAd === 'on',
51
+ placeholderIds: settings.placeholderIds,
91
52
  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 || "",
53
+ enableMessageAds: settings.enableMessageAds === 'on',
54
+ showFirstMessageAd: settings.showFirstMessageAd === 'on',
55
+ messagePlaceholderIds: settings.messagePlaceholderIds,
97
56
  messageIntervalPosts: parseInt(settings.messageIntervalPosts, 10) || 10,
98
57
  });
99
58
  });
100
59
  };
101
60
 
102
- /**
103
- * Ajoute le lien dans le menu de navigation de l'administration
104
- */
105
61
  plugin.addAdminNavigation = async (header) => {
106
- header.plugins.push({
107
- route: '/plugins/ezoic-infinite',
108
- icon: 'fa-ad',
109
- name: 'Ezoic Infinite'
110
- });
62
+ header.plugins.push({ route: '/plugins/ezoic-infinite', icon: 'fa-ad', name: 'Ezoic Infinite' });
111
63
  return header;
112
64
  };
113
65
 
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
- }
121
- };
122
-
123
66
  module.exports = plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.95",
3
+ "version": "1.6.97",
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
@@ -8,6 +8,7 @@
8
8
  const POOL_ID = 'nodebb-ezoic-placeholder-pool';
9
9
  let config = null;
10
10
  let isInternalChange = false;
11
+ let activeIds = new Set();
11
12
 
12
13
  function getPool() {
13
14
  let p = document.getElementById(POOL_ID);
@@ -20,68 +21,67 @@
20
21
  return p;
21
22
  }
22
23
 
23
- // Cette fonction force Ezoic à remplir le placeholder
24
- function forceFillAd(id) {
24
+ function callEzoic(id) {
25
25
  if (typeof window.ezstandalone === 'undefined') return;
26
+ const pid = parseInt(id, 10);
27
+ if (isNaN(pid)) return;
26
28
 
27
29
  window.ezstandalone.cmd.push(function() {
28
- // 1. On s'assure que l'ID est bien dans la liste d'Ezoic
29
- window.ezstandalone.define(parseInt(id, 10));
30
-
31
- // 2. On attend un micro-délai pour que le DOM soit stable
32
- setTimeout(() => {
33
- try {
34
- // La doc précise que showAds avec IDs est le mode "Dynamic"
35
- window.ezstandalone.showAds(parseInt(id, 10));
36
- // On force un rafraîchissement spécifique si showAds ne suffit pas
37
- if (window.ezstandalone.refresh) {
38
- window.ezstandalone.refresh(parseInt(id, 10));
39
- }
40
- } catch (e) {
41
- console.warn('[Ezoic] Fill failed for', id, e);
42
- }
43
- }, 200);
30
+ window.ezstandalone.define(pid);
31
+ if (!window.ezstandalone.enabled) {
32
+ window.ezstandalone.enable();
33
+ window.ezstandalone.showAds();
34
+ } else {
35
+ window.ezstandalone.showAds(pid);
36
+ }
37
+ activeIds.add(pid);
44
38
  });
45
39
  }
46
40
 
47
41
  function redistribute() {
48
42
  if (!config || config.excluded) return;
49
43
 
50
- const selectors = {
51
- 'home': '.category-item, [component="categories/category"]',
52
- 'topic-list': 'li[component="category/topic"]',
53
- 'message': '[component="post"]'
54
- };
55
-
56
- const process = (items, kind, interval, showFirst) => {
57
- const int = parseInt(interval, 10) || 10;
58
- const pool = getPool();
59
-
60
- items.forEach((item, index) => {
61
- const pos = index + 1;
62
- const shouldHaveAd = (pos === 1 && showFirst) || (pos % int === 0);
63
- const next = item.nextElementSibling;
64
-
65
- if (shouldHaveAd && !(next && next.classList.contains(WRAP_CLASS))) {
66
- const available = pool.querySelector(`.${WRAP_CLASS}[data-kind="${kind}"]`);
67
- if (available) {
68
- isInternalChange = true;
69
- // On insère l'élément
70
- item.parentNode.insertBefore(available, item.nextSibling);
71
-
72
- // On déclenche le remplissage publicitaire
73
- const pid = available.getAttribute('data-placeholder-id');
74
- forceFillAd(pid);
75
-
76
- setTimeout(() => { isInternalChange = false; }, 50);
77
- }
78
- }
79
- });
80
- };
44
+ // 1. Accueil / Catégories
45
+ const categoryItems = document.querySelectorAll('.category-item, [component="categories/category"]');
46
+ if (config.enableCategoryAds) process(Array.from(categoryItems), 'home', config.intervalCategories, config.showFirstCategoryAd, 'div');
47
+
48
+ // 2. Liste des Topics (Harmony utilise des <li>)
49
+ const topicItems = document.querySelectorAll('li[component="category/topic"]');
50
+ if (config.enableBetweenAds) process(Array.from(topicItems), 'topic-list', config.intervalPosts, config.showFirstTopicAd, 'li');
81
51
 
82
- if (config.enableCategoryAds) process(Array.from(document.querySelectorAll(selectors.home)), 'home', config.intervalCategories, config.showFirstCategoryAd);
83
- if (config.enableBetweenAds) process(Array.from(document.querySelectorAll(selectors['topic-list'])), 'topic-list', config.intervalPosts, config.showFirstTopicAd);
84
- if (config.enableMessageAds) process(Array.from(document.querySelectorAll(selectors.message)), 'message', config.messageIntervalPosts, config.showFirstMessageAd);
52
+ // 3. Messages (Posts)
53
+ const postItems = document.querySelectorAll('[component="post"]');
54
+ if (config.enableMessageAds) process(Array.from(postItems), 'message', config.messageIntervalPosts, config.showFirstMessageAd, 'div');
55
+ }
56
+
57
+ function process(items, kind, interval, showFirst, wrapperTag) {
58
+ const int = parseInt(interval, 10) || 10;
59
+ const pool = getPool();
60
+
61
+ items.forEach((item, index) => {
62
+ const pos = index + 1;
63
+ const shouldHaveAd = (pos === 1 && showFirst) || (pos % int === 0);
64
+ const next = item.nextElementSibling;
65
+
66
+ if (shouldHaveAd && !(next && next.classList.contains(WRAP_CLASS))) {
67
+ const available = pool.querySelector(`[data-kind="${kind}"]`);
68
+ if (available) {
69
+ isInternalChange = true;
70
+
71
+ // Création d'un conteneur qui respecte le type de parent (li ou div)
72
+ const wrapper = document.createElement(wrapperTag);
73
+ wrapper.className = WRAP_CLASS + ' ezoic-added-content';
74
+ wrapper.setAttribute('data-kind', kind);
75
+ wrapper.setAttribute('data-placeholder-id', available.getAttribute('data-placeholder-id'));
76
+ wrapper.innerHTML = available.innerHTML;
77
+
78
+ item.parentNode.insertBefore(wrapper, item.nextSibling);
79
+ callEzoic(available.getAttribute('data-placeholder-id'));
80
+
81
+ setTimeout(() => { isInternalChange = false; }, 50);
82
+ }
83
+ }
84
+ });
85
85
  }
86
86
 
87
87
  function init() {
@@ -90,21 +90,16 @@
90
90
  .then(data => {
91
91
  config = data;
92
92
  const pool = getPool();
93
-
94
93
  const setup = (raw, kind) => {
95
94
  if (!raw) return;
96
95
  raw.split(/[\s,]+/).filter(Boolean).forEach(id => {
97
- if (!document.getElementById(`ezoic-pub-ad-placeholder-${id}`)) {
98
- const d = document.createElement('div');
99
- d.className = WRAP_CLASS;
100
- d.setAttribute('data-kind', kind);
101
- d.setAttribute('data-placeholder-id', id);
102
- d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}"></div>`;
103
- pool.appendChild(d);
104
- }
96
+ const d = document.createElement('div');
97
+ d.setAttribute('data-kind', kind);
98
+ d.setAttribute('data-placeholder-id', id);
99
+ d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}"></div>`;
100
+ pool.appendChild(d);
105
101
  });
106
102
  };
107
-
108
103
  setup(config.categoryPlaceholderIds, 'home');
109
104
  setup(config.placeholderIds, 'topic-list');
110
105
  setup(config.messagePlaceholderIds, 'message');
@@ -118,8 +113,18 @@
118
113
  });
119
114
  }
120
115
 
121
- window.addEventListener('action:ajaxify.end', () => {
122
- setTimeout(redistribute, 500);
116
+ // GESTION NAVIGATION SANS RECHARGE (SPA)
117
+ window.addEventListener('action:ajaxify.start', function() {
118
+ if (typeof window.ezstandalone !== 'undefined' && activeIds.size > 0) {
119
+ window.ezstandalone.cmd.push(function() {
120
+ window.ezstandalone.destroyAll();
121
+ activeIds.clear();
122
+ });
123
+ }
124
+ });
125
+
126
+ window.addEventListener('action:ajaxify.end', function() {
127
+ setTimeout(redistribute, 600);
123
128
  });
124
129
 
125
130
  if (document.readyState === 'loading') {
package/public/style.css CHANGED
@@ -1,91 +1,37 @@
1
- /* ============================================================
2
- CONTAINER GLOBAL DES PUBS
3
- ============================================================ */
1
+ /* Isolation et Taille */
4
2
  .nodebb-ezoic-wrap {
5
3
  display: block !important;
6
4
  width: 100% !important;
7
- min-height: 250px; /* Réserve l'espace pour éviter le saut de contenu (CLS) */
5
+ min-height: 250px !important;
8
6
  margin: 30px 0 !important;
9
- padding: 0;
10
7
  clear: both !important;
11
8
  text-align: center;
12
- position: relative;
13
- overflow: hidden;
9
+ list-style: none !important; /* Pour les injections en <li> */
14
10
  }
15
11
 
16
- /* On s'assure que le contenu Ezoic à l'intérieur est centré */
17
- .nodebb-ezoic-wrap > div {
18
- margin: 0 auto !important;
19
- }
20
-
21
- /* ============================================================
22
- 1. PAGE D'ACCUEIL (Liste des catégories)
23
- ============================================================ */
24
- [component="categories/category"] + .nodebb-ezoic-wrap,
25
- .category-item + .nodebb-ezoic-wrap {
26
- margin: 40px 0 !important;
27
- border-top: 1px solid rgba(0,0,0,0.05);
28
- padding-top: 25px;
29
- background: transparent;
30
- }
31
-
32
- /* ============================================================
33
- 2. PAGE CATÉGORIE (Liste des Topics / Sujets)
34
- ============================================================ */
35
- /* Important : NodeBB Harmony utilise des <li> pour les topics.
36
- On s'assure que notre wrap ne casse pas la structure de liste
37
- mais s'affiche comme un bloc complet. */
38
- li[component="category/topic"] + .nodebb-ezoic-wrap {
39
- list-style: none !important;
40
- margin: 0 !important;
41
- padding: 20px 0 !important;
42
- border-bottom: 1px solid rgba(0,0,0,0.05);
43
- display: block !important;
12
+ /* Fix pour l'accueil Harmony */
13
+ li.nodebb-ezoic-wrap {
44
14
  float: none !important;
45
- width: 100%;
15
+ padding: 20px 0;
16
+ border-bottom: 1px solid rgba(0,0,0,0.05);
46
17
  }
47
18
 
48
- /* ============================================================
49
- 3. PAGE DES POSTS (Messages à l'intérieur d'un topic)
50
- ============================================================ */
19
+ /* Éviter que les pubs ne se collent aux messages */
51
20
  [component="post"] + .nodebb-ezoic-wrap {
52
- margin: 45px 0 !important;
53
- padding: 25px;
54
- background: rgba(0,0,0,0.02); /* Léger fond pour distinguer le message pub */
55
- border-radius: 12px;
56
- border: 1px inset rgba(0,0,0,0.03);
21
+ background: rgba(0,0,0,0.02);
22
+ padding: 20px;
23
+ border-radius: 10px;
57
24
  }
58
25
 
59
- /* ============================================================
60
- CORRECTIFS ANTI "PILE-UP" & AFFICHAGE
61
- ============================================================ */
62
-
63
- /* Évite que les pubs ne soient invisibles si Ezoic ne charge rien immédiatement */
64
- .nodebb-ezoic-wrap:empty {
65
- min-height: 1px;
66
- height: 1px;
26
+ /* Anti-remontée (Pile-up) */
27
+ .nodebb-ezoic-wrap::before, .nodebb-ezoic-wrap::after {
28
+ content: "";
29
+ display: table;
30
+ clear: both;
67
31
  }
68
32
 
69
- /* Neutralise les marges forcées par Ezoic qui pourraient décaler le layout */
33
+ /* Centrage forcé des iframes Ezoic */
70
34
  .ezoic-ad {
71
- margin: 10px auto !important;
35
+ margin: 0 auto !important;
72
36
  display: block !important;
73
- }
74
-
75
- /* Mobile : Réduction des marges pour ne pas perdre trop de place */
76
- @media (max-width: 767px) {
77
- .nodebb-ezoic-wrap {
78
- margin: 20px 0 !important;
79
- min-height: 100px;
80
- }
81
- [component="post"] + .nodebb-ezoic-wrap {
82
- padding: 10px;
83
- margin: 25px 0 !important;
84
- }
85
- }
86
-
87
- /* Empêche les sauts de ligne bizarres dans les listes Harmony */
88
- .categories > li.nodebb-ezoic-wrap,
89
- .category > li.nodebb-ezoic-wrap {
90
- float: none !important;
91
37
  }