nodebb-plugin-ezoic-infinite 1.6.96 → 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
@@ -11,44 +11,25 @@ async function getSettings() {
11
11
  return await meta.settings.get(SETTINGS_KEY);
12
12
  }
13
13
 
14
- /**
15
- * Vérifie l'exclusion par groupe avec gestion du format NodeBB
16
- */
17
14
  async function isUserExcluded(uid, excludedGroups) {
18
15
  if (!uid || uid <= 0) return false;
19
- if (!excludedGroups || (Array.isArray(excludedGroups) && excludedGroups.length === 0)) return false;
20
-
21
- const excludedList = Array.isArray(excludedGroups) ? excludedGroups : [excludedGroups];
16
+ if (!excludedGroups || !excludedGroups.length) return false;
22
17
  const userGroups = await groups.getUserGroupsNames([uid]);
23
-
24
- // userGroups[0] contient le tableau des noms de groupes de l'utilisateur
25
- return excludedList.some(g => userGroups[0].includes(g));
18
+ return excludedGroups.some(g => userGroups[0].includes(g));
26
19
  }
27
20
 
28
21
  async function getAllGroups() {
29
22
  let names = await db.getSortedSetRange('groups:createtime', 0, -1);
30
- if (!names || !names.length) {
31
- names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
32
- }
33
- const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
34
- const data = await groups.getGroupsData(filtered);
35
- const valid = data.filter(g => g && g.name);
36
- valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
37
- return valid;
23
+ const data = await groups.getGroupsData(names);
24
+ return data.filter(g => g && g.name).map(g => ({ name: g.name }));
38
25
  }
39
26
 
40
27
  plugin.init = async ({ router, middleware }) => {
41
28
  async function render(req, res) {
42
29
  const settings = await getSettings();
43
30
  const allGroups = await getAllGroups();
44
-
45
31
  res.render('admin/plugins/ezoic-infinite', {
46
- title: 'Ezoic Infinite Ads',
47
32
  ...settings,
48
- // On s'assure que les checkbox sont bien cochées dans l'admin
49
- enableCategoryAds_checked: settings.enableCategoryAds === 'on' ? 'checked' : '',
50
- enableBetweenAds_checked: settings.enableBetweenAds === 'on' ? 'checked' : '',
51
- enableMessageAds_checked: settings.enableMessageAds === 'on' ? 'checked' : '',
52
33
  allGroups,
53
34
  });
54
35
  }
@@ -56,47 +37,30 @@ plugin.init = async ({ router, middleware }) => {
56
37
  router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
57
38
  router.get('/api/admin/plugins/ezoic-infinite', render);
58
39
 
59
- // API consommée par le client.js
60
40
  router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
61
41
  const settings = await getSettings();
62
- const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
63
-
42
+ const excluded = await isUserExcluded(req.uid, settings.excludedGroups || []);
64
43
  res.json({
65
44
  excluded,
66
- // Pool Accueil
67
45
  enableCategoryAds: settings.enableCategoryAds === 'on',
68
46
  showFirstCategoryAd: settings.showFirstCategoryAd === 'on',
69
- categoryPlaceholderIds: settings.categoryPlaceholderIds || "",
47
+ categoryPlaceholderIds: settings.categoryPlaceholderIds,
70
48
  intervalCategories: parseInt(settings.intervalCategories, 10) || 5,
71
-
72
- // Pool Topics (Catégories)
73
49
  enableBetweenAds: settings.enableBetweenAds === 'on',
74
50
  showFirstTopicAd: settings.showFirstTopicAd === 'on',
75
- placeholderIds: settings.placeholderIds || "",
51
+ placeholderIds: settings.placeholderIds,
76
52
  intervalPosts: parseInt(settings.intervalPosts, 10) || 10,
77
-
78
- // Pool Messages (Topics)
79
53
  enableMessageAds: settings.enableMessageAds === 'on',
80
54
  showFirstMessageAd: settings.showFirstMessageAd === 'on',
81
- messagePlaceholderIds: settings.messagePlaceholderIds || "",
55
+ messagePlaceholderIds: settings.messagePlaceholderIds,
82
56
  messageIntervalPosts: parseInt(settings.messageIntervalPosts, 10) || 10,
83
57
  });
84
58
  });
85
59
  };
86
60
 
87
61
  plugin.addAdminNavigation = async (header) => {
88
- header.plugins.push({
89
- route: '/plugins/ezoic-infinite',
90
- icon: 'fa-ad',
91
- name: 'Ezoic Infinite'
92
- });
62
+ header.plugins.push({ route: '/plugins/ezoic-infinite', icon: 'fa-ad', name: 'Ezoic Infinite' });
93
63
  return header;
94
64
  };
95
65
 
96
- plugin.onSettingsSet = async (data) => {
97
- if (data.plugin === 'ezoic-infinite') {
98
- // Optionnel : purger un cache si nécessaire
99
- }
100
- };
101
-
102
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.96",
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
  }