nodebb-plugin-ezoic-infinite 1.6.87 → 1.6.89

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
@@ -2,58 +2,64 @@
2
2
 
3
3
  const meta = require.main.require('./src/meta');
4
4
  const groups = require.main.require('./src/groups');
5
+ const db = require.main.require('./src/database');
5
6
 
6
7
  const SETTINGS_KEY = 'ezoic-infinite';
7
8
  const plugin = {};
8
9
 
9
10
  async function getSettings() {
10
- return await meta.settings.get(SETTINGS_KEY) || {};
11
+ return await meta.settings.get(SETTINGS_KEY);
12
+ }
13
+
14
+ 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));
18
+ }
19
+
20
+ 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 }));
11
24
  }
12
25
 
13
26
  plugin.init = async ({ router, middleware }) => {
14
- const renderAdmin = async (req, res) => {
15
- const settings = await getSettings();
16
- const db = require.main.require('./src/database');
17
- const names = await db.getSortedSetRange('groups:createtime', 0, -1);
18
- const groupsData = await groups.getGroupsData(names);
19
- const allGroups = groupsData.filter(g => g && g.name).map(g => ({ name: g.name }));
20
-
21
- res.render('admin/plugins/ezoic-infinite', {
22
- ...settings,
23
- allGroups,
24
- enableBetweenAds_checked: settings.enableBetweenAds === 'on' ? 'checked' : '',
25
- enableMessageAds_checked: settings.enableMessageAds === 'on' ? 'checked' : ''
26
- });
27
- };
28
-
29
- router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, renderAdmin);
30
- router.get('/api/admin/plugins/ezoic-infinite', renderAdmin);
31
-
32
- router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
33
- const settings = await getSettings();
34
- let excluded = false;
35
- if (req.uid && settings.excludedGroups) {
36
- const groupsList = Array.isArray(settings.excludedGroups) ? settings.excludedGroups : [settings.excludedGroups];
37
- excluded = await groups.isMemberOfGroups(req.uid, groupsList);
38
- }
39
-
40
- res.json({
41
- excluded,
42
- enableBetweenAds: settings.enableBetweenAds === 'on',
43
- showFirstTopicAd: settings.showFirstTopicAd === 'on',
44
- placeholderIds: settings.placeholderIds || '',
45
- intervalPosts: settings.intervalPosts || 10,
46
- enableMessageAds: settings.enableMessageAds === 'on',
47
- showFirstMessageAd: settings.showFirstMessageAd === 'on',
48
- messagePlaceholderIds: settings.messagePlaceholderIds || '',
49
- messageIntervalPosts: settings.messageIntervalPosts || 10,
50
- });
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,
51
52
  });
53
+ });
52
54
  };
53
55
 
54
56
  plugin.addAdminNavigation = async (header) => {
55
- header.plugins.push({ route: '/plugins/ezoic-infinite', icon: 'fa-ad', name: 'Ezoic Infinite' });
56
- return header;
57
+ header.plugins.push({
58
+ route: '/plugins/ezoic-infinite',
59
+ icon: 'fa-ad',
60
+ name: 'Ezoic Infinite'
61
+ });
62
+ return header;
57
63
  };
58
64
 
59
65
  module.exports = plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.87",
3
+ "version": "1.6.89",
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,9 @@
9
9
 
10
10
  let config = null;
11
11
  let isInternalChange = false;
12
- let activePlaceholders = new Set();
12
+ let definedIds = new Set();
13
+ let pendingIds = new Set();
14
+ let refreshTimer = null;
13
15
 
14
16
  function getPool() {
15
17
  let p = document.getElementById(POOL_ID);
@@ -22,48 +24,64 @@
22
24
  return p;
23
25
  }
24
26
 
25
- // Utilisation stricte de la file d'attente CMD du zip
26
- function callEzoic(id) {
27
- if (typeof window.ezstandalone === 'undefined') return;
28
-
27
+ // Cette fonction centralise les appels pour éviter de spammer Refresh()
28
+ function triggerEzoic() {
29
+ if (typeof window.ezstandalone === 'undefined' || pendingIds.size === 0) return;
30
+
29
31
  window.ezstandalone.cmd.push(function() {
30
- // Si l'ID est déjà affiché, on ne fait rien pour éviter le spam de refresh
31
- if (activePlaceholders.has(id)) {
32
- window.ezstandalone.refresh();
33
- return;
34
- }
32
+ const idsToProcess = Array.from(pendingIds);
33
+ pendingIds.clear();
35
34
 
36
- window.ezstandalone.define(id);
37
- activePlaceholders.add(id);
35
+ // 1. Définir les nouveaux IDs
36
+ window.ezstandalone.define(idsToProcess);
38
37
 
38
+ // 2. Si c'est le tout premier appel de la page
39
39
  if (!window.ezstandalone.enabled) {
40
40
  window.ezstandalone.enable();
41
41
  window.ezstandalone.display();
42
42
  } else {
43
- window.ezstandalone.refresh();
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
+ }
44
50
  }
45
51
  });
46
52
  }
47
53
 
54
+ function callEzoic(id) {
55
+ const pid = parseInt(id, 10);
56
+ if (isNaN(pid)) return;
57
+
58
+ // On ajoute l'ID à la liste des IDs à traiter
59
+ pendingIds.add(pid);
60
+ definedIds.add(pid);
61
+
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
+ clearTimeout(refreshTimer);
65
+ refreshTimer = setTimeout(triggerEzoic, 200);
66
+ }
67
+
48
68
  function redistribute() {
49
69
  if (!config || config.excluded) return;
50
70
 
51
- // Utilisation des sélecteurs exacts du zip V17 (Harmony/NodeBB 4)
71
+ // Sélecteurs Harmony
52
72
  const topicItems = document.querySelectorAll('li[component="category/topic"]');
53
- const postItems = document.querySelectorAll('[component="post"][data-pid]');
73
+ const postItems = document.querySelectorAll('[component="post"]');
54
74
 
55
- // Injection pour la page d'accueil / catégories
56
75
  if (topicItems.length > 0 && config.enableBetweenAds) {
57
- inject(Array.from(topicItems), 'between', config.intervalPosts, config.showFirstTopicAd);
76
+ processItems(Array.from(topicItems), 'between', config.intervalPosts, config.showFirstTopicAd);
58
77
  }
59
-
60
- // Injection pour les messages (topics)
78
+
61
79
  if (postItems.length > 0 && config.enableMessageAds) {
62
- inject(Array.from(postItems), 'message', config.messageIntervalPosts, config.showFirstMessageAd);
80
+ processItems(Array.from(postItems), 'message', config.messageIntervalPosts, config.showFirstMessageAd);
63
81
  }
64
82
  }
65
83
 
66
- function inject(items, kind, interval, showFirst) {
84
+ function processItems(items, kind, interval, showFirst) {
67
85
  const int = parseInt(interval, 10) || 10;
68
86
  items.forEach((item, index) => {
69
87
  const pos = index + 1;
@@ -73,15 +91,10 @@
73
91
  if (shouldHaveAd && !(next && next.classList.contains(WRAP_CLASS))) {
74
92
  const pool = getPool();
75
93
  const available = pool.querySelector(`.${WRAP_CLASS}[data-kind="${kind}"]`);
76
-
77
94
  if (available) {
78
95
  isInternalChange = true;
79
- // On insère l'élément comme dans le zip original
80
96
  item.parentNode.insertBefore(available, item.nextSibling);
81
-
82
- const pid = parseInt(available.getAttribute('data-placeholder-id'), 10);
83
- callEzoic(pid);
84
-
97
+ callEzoic(available.getAttribute('data-placeholder-id'));
85
98
  setTimeout(() => { isInternalChange = false; }, 100);
86
99
  }
87
100
  }
@@ -94,8 +107,7 @@
94
107
  .then(data => {
95
108
  config = data;
96
109
  const pool = getPool();
97
-
98
- const setupPool = (raw, kind) => {
110
+ const setup = (raw, kind) => {
99
111
  if (!raw) return;
100
112
  raw.split(/[\s,]+/).filter(Boolean).forEach(id => {
101
113
  if (!document.querySelector(`[data-placeholder-id="${id}"]`)) {
@@ -103,19 +115,16 @@
103
115
  d.className = WRAP_CLASS;
104
116
  d.setAttribute('data-kind', kind);
105
117
  d.setAttribute('data-placeholder-id', id);
106
- // Structure HTML propre
107
- d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}" class="ezoic-ad"></div>`;
118
+ d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}"></div>`;
108
119
  pool.appendChild(d);
109
120
  }
110
121
  });
111
122
  };
112
-
113
- setupPool(config.placeholderIds, 'between');
114
- setupPool(config.messagePlaceholderIds, 'message');
123
+ setup(config.placeholderIds, 'between');
124
+ setup(config.messagePlaceholderIds, 'message');
115
125
 
116
126
  redistribute();
117
127
 
118
- // Observer pour l'infinite scroll (reprise du zip)
119
128
  const observer = new MutationObserver(() => {
120
129
  if (!isInternalChange) redistribute();
121
130
  });
@@ -123,12 +132,19 @@
123
132
  });
124
133
  }
125
134
 
126
- // Événements NodeBB pour gérer la navigation sans rechargement
127
- if (window.jQuery) {
128
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function() {
129
- setTimeout(redistribute, 500);
130
- });
131
- }
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
+ });
132
148
 
133
149
  if (document.readyState === 'loading') {
134
150
  document.addEventListener('DOMContentLoaded', init);
package/public/style.css CHANGED
@@ -1,20 +1,29 @@
1
+ /* Container global */
1
2
  .nodebb-ezoic-wrap {
2
- display: block !important;
3
- width: 100% !important;
4
- margin: 30px 0 !important;
5
- min-height: 250px;
6
- clear: both;
3
+ display: block !important;
4
+ width: 100% !important;
5
+ min-width: 100% !important;
6
+ margin: 20px 0 !important;
7
+ min-height: 250px;
8
+ clear: both;
9
+ text-align: center;
7
10
  }
8
11
 
9
- /* Fix pour les listes de l'accueil Harmony */
12
+ /* Fix spécifique pour la liste des topics (Accueil Harmony) */
10
13
  li[component="category/topic"] + .nodebb-ezoic-wrap {
11
- list-style: none;
12
- padding: 15px;
13
- background: rgba(0,0,0,0.02);
14
- border-radius: 8px;
14
+ list-style: none !important;
15
+ margin-left: 0 !important;
16
+ padding: 15px 0;
17
+ border-bottom: 1px solid rgba(0,0,0,0.05);
15
18
  }
16
19
 
17
- /* Éviter les trous si la pub ne charge pas */
20
+ /* Si le bloc est vide ou n'a pas encore chargé */
18
21
  .nodebb-ezoic-wrap:empty {
19
- min-height: 1px;
22
+ min-height: 1px;
23
+ }
24
+
25
+ /* Neutralisation des marges internes d'Ezoic */
26
+ .ezoic-ad {
27
+ margin: 0 auto !important;
28
+ display: inline-block !important;
20
29
  }