nodebb-plugin-ezoic-infinite 1.6.81 → 1.6.83

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,66 +2,58 @@
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');
6
5
 
7
6
  const SETTINGS_KEY = 'ezoic-infinite';
8
7
  const plugin = {};
9
8
 
10
9
  async function getSettings() {
11
- const settings = await meta.settings.get(SETTINGS_KEY);
12
- return settings || {};
13
- }
14
-
15
- async function isUserExcluded(uid, excludedGroups) {
16
- if (!uid || !excludedGroups) return false;
17
- const groupsList = Array.isArray(excludedGroups) ? excludedGroups : [excludedGroups];
18
- if (!groupsList.length) return false;
19
- return await groups.isMemberOfGroups(uid, groupsList);
10
+ return await meta.settings.get(SETTINGS_KEY) || {};
20
11
  }
21
12
 
22
13
  plugin.init = async ({ router, middleware }) => {
23
- const renderAdmin = async (req, res) => {
24
- const settings = await getSettings();
25
- const names = await db.getSortedSetRange('groups:createtime', 0, -1);
26
- const groupsData = await groups.getGroupsData(names);
27
- const allGroups = groupsData.filter(g => g && g.name).map(g => ({ name: g.name }));
28
-
29
- res.render('admin/plugins/ezoic-infinite', {
30
- ...settings,
31
- allGroups,
32
- // Helper pour le tpl
33
- enableBetweenAds_checked: settings.enableBetweenAds === 'on' ? 'checked' : '',
34
- enableMessageAds_checked: settings.enableMessageAds === 'on' ? 'checked' : ''
35
- });
36
- };
37
-
38
- router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, renderAdmin);
39
- router.get('/api/admin/plugins/ezoic-infinite', renderAdmin);
40
-
41
- router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
42
- const settings = await getSettings();
43
- const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
44
- res.json({
45
- excluded,
46
- enableBetweenAds: settings.enableBetweenAds === 'on',
47
- showFirstTopicAd: settings.showFirstTopicAd === 'on',
48
- placeholderIds: settings.placeholderIds || '',
49
- intervalPosts: settings.intervalPosts || 10,
50
- enableMessageAds: settings.enableMessageAds === 'on',
51
- showFirstMessageAd: settings.showFirstMessageAd === 'on',
52
- messagePlaceholderIds: settings.messagePlaceholderIds || '',
53
- messageIntervalPosts: settings.messageIntervalPosts || 10,
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
+ });
54
51
  });
55
- });
56
52
  };
57
53
 
58
54
  plugin.addAdminNavigation = async (header) => {
59
- header.plugins.push({
60
- route: '/plugins/ezoic-infinite',
61
- icon: 'fa-ad',
62
- name: 'Ezoic Infinite'
63
- });
64
- return header;
55
+ header.plugins.push({ route: '/plugins/ezoic-infinite', icon: 'fa-ad', name: 'Ezoic Infinite' });
56
+ return header;
65
57
  };
66
58
 
67
59
  module.exports = plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.81",
3
+ "version": "1.6.83",
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
@@ -1,237 +1,119 @@
1
1
  (function () {
2
- 'use strict';
2
+ 'use strict';
3
3
 
4
- let lastScrollY = 0;
5
- let scrollDir = 1;
6
- try {
7
- lastScrollY = window.scrollY || 0;
8
- window.addEventListener('scroll', () => {
9
- const y = window.scrollY || 0;
10
- const d = y - lastScrollY;
11
- if (Math.abs(d) > 4) {
12
- scrollDir = d > 0 ? 1 : -1;
13
- lastScrollY = y;
14
- }
15
- }, { passive: true });
16
- } catch (e) {}
4
+ if (window.ezInfiniteInjected) return;
5
+ window.ezInfiniteInjected = true;
17
6
 
18
- const WRAP_CLASS = 'nodebb-ezoic-wrap';
19
- const POOL_ID = 'nodebb-ezoic-placeholder-pool';
20
- const MAX_INSERTS_PER_RUN = 8;
21
-
22
- let config = null;
23
- let isInternalChange = false;
24
- let ezInitialized = false; // Flag crucial pour éviter l'erreur de "refresh"
25
-
26
- function withInternalDomChange(fn) {
27
- isInternalChange = true;
28
- try { fn(); } finally { isInternalChange = false; }
29
- }
30
-
31
- function getPool() {
32
- let p = document.getElementById(POOL_ID);
33
- if (!p) {
34
- p = document.createElement('div');
35
- p.id = POOL_ID;
36
- p.style.display = 'none';
37
- document.body.appendChild(p);
38
- }
39
- return p;
40
- }
41
-
42
- function releaseWrapNode(wrap) {
43
- if (!wrap || !wrap.parentNode) return;
44
- const pool = getPool();
45
- wrap.classList.remove('ez-orphan-hidden');
46
- if (wrap.parentNode !== pool) {
47
- pool.appendChild(wrap);
48
- }
49
- }
50
-
51
- // Anti-pillup : évite que deux pubs se suivent
52
- function decluster(container) {
53
- const wraps = Array.from(container.querySelectorAll(`.${WRAP_CLASS}`));
54
- wraps.forEach(wrap => {
55
- let next = wrap.nextElementSibling;
56
- if (next && next.classList.contains(WRAP_CLASS)) {
57
- withInternalDomChange(() => releaseWrapNode(next));
58
- }
59
- });
60
- }
61
-
62
- // Nettoyage des pubs orphelines lors de la virtualisation
63
- function pruneOrphanWraps() {
64
- const wraps = document.querySelectorAll(`.${WRAP_CLASS}`);
65
- const items = document.querySelectorAll('[component="category/topic"], [component="post"]');
66
- const itemSet = new Set(items);
67
-
68
- wraps.forEach(wrap => {
69
- if (wrap.parentElement && wrap.parentElement.id === POOL_ID) return;
70
- let hasNeighbor = false;
71
- let prev = wrap.previousElementSibling;
72
- for (let i = 0; i < 3 && prev; i++) {
73
- if (itemSet.has(prev)) { hasNeighbor = true; break; }
74
- prev = prev.previousElementSibling;
75
- }
76
- if (!hasNeighbor) {
77
- let next = wrap.nextElementSibling;
78
- for (let i = 0; i < 3 && next; i++) {
79
- if (itemSet.has(next)) { hasNeighbor = true; break; }
80
- next = next.nextElementSibling;
81
- }
82
- }
83
-
84
- if (!hasNeighbor) {
85
- if (scrollDir === -1) {
86
- withInternalDomChange(() => releaseWrapNode(wrap));
87
- } else {
88
- wrap.classList.add('ez-orphan-hidden');
7
+ const WRAP_CLASS = 'nodebb-ezoic-wrap';
8
+ const POOL_ID = 'nodebb-ezoic-placeholder-pool';
9
+
10
+ let config = null;
11
+ let isInternalChange = false;
12
+ let ezEnabled = false;
13
+
14
+ function getPool() {
15
+ let p = document.getElementById(POOL_ID);
16
+ if (!p) {
17
+ p = document.createElement('div');
18
+ p.id = POOL_ID;
19
+ p.style.display = 'none';
20
+ document.body.appendChild(p);
89
21
  }
90
- } else {
91
- wrap.classList.remove('ez-orphan-hidden');
92
- }
93
- });
94
- }
95
-
96
- function callEzoic(placeholderId) {
97
- if (typeof window.ezstandalone === 'undefined') return;
98
- const pid = parseInt(placeholderId, 10);
99
- if (isNaN(pid)) return;
100
-
101
- try {
102
- if (!ezInitialized) {
103
- // PREMIER APPEL : define + enable + display
104
- window.ezstandalone.define(pid);
105
- window.ezstandalone.enable();
106
- window.ezstandalone.display();
107
- ezInitialized = true;
108
- } else {
109
- // APPELS SUIVANTS (Scroll) : define + refresh
110
- window.ezstandalone.define(pid);
111
- // On attend que le DOM soit stable pour le refresh
112
- setTimeout(() => {
113
- if (document.getElementById('ezoic-pub-ad-placeholder-' + pid)) {
114
- window.ezstandalone.refresh();
115
- }
116
- }, 150);
117
- }
118
- } catch (e) {
119
- console.warn('[Ezoic-Infinite] Ez Error:', e.message);
22
+ return p;
120
23
  }
121
- }
122
24
 
123
- function redistribute(container) {
124
- if (!container || !config || config.excluded) return;
125
-
126
- pruneOrphanWraps();
127
- decluster(container);
128
-
129
- const isTopicList = container.getAttribute('component') === 'category' || container.classList.contains('topic-list');
130
- const isPostList = container.getAttribute('component') === 'topic' || container.querySelectorAll('[component="post"]').length > 0;
131
-
132
- let enabled = false;
133
- let interval = 10;
134
- let kind = '';
135
- let items = [];
136
- let showFirst = false;
137
-
138
- if (isTopicList && config.enableBetweenAds) {
139
- enabled = true;
140
- interval = parseInt(config.intervalPosts, 10) || 10;
141
- kind = 'between';
142
- items = Array.from(container.querySelectorAll('[component="category/topic"]'));
143
- showFirst = config.showFirstTopicAd;
144
- } else if (isPostList && config.enableMessageAds) {
145
- enabled = true;
146
- interval = parseInt(config.messageIntervalPosts, 10) || 10;
147
- kind = 'message';
148
- items = Array.from(container.querySelectorAll('[component="post"]'));
149
- showFirst = config.showFirstMessageAd;
25
+ function callEzoic(pid) {
26
+ if (typeof window.ezstandalone === 'undefined') return;
27
+ const id = parseInt(pid, 10);
28
+ try {
29
+ window.ezstandalone.define(id);
30
+ if (!ezEnabled) {
31
+ window.ezstandalone.enable();
32
+ window.ezstandalone.display();
33
+ ezEnabled = true;
34
+ } else {
35
+ // Délai pour s'assurer que le div est bien rendu par le navigateur
36
+ setTimeout(() => { window.ezstandalone.refresh(); }, 200);
37
+ }
38
+ } catch (e) { console.warn('[Ezoic] Error:', e); }
150
39
  }
151
40
 
152
- if (!enabled || items.length === 0) return;
153
-
154
- let inserts = 0;
155
- items.forEach((item, index) => {
156
- if (inserts >= MAX_INSERTS_PER_RUN) return;
157
- const pos = index + 1;
158
- let shouldHaveAd = (pos % interval === 0);
159
- if (pos === 1 && showFirst) shouldHaveAd = true;
160
-
161
- const next = item.nextElementSibling;
162
- const hasAdAfter = next && next.classList.contains(WRAP_CLASS);
163
-
164
- if (shouldHaveAd && !hasAdAfter) {
165
- const pool = getPool();
166
- const available = pool.querySelector(`.${WRAP_CLASS}[data-kind="${kind}"]`);
167
- if (available) {
168
- withInternalDomChange(() => {
169
- item.parentNode.insertBefore(available, item.nextSibling);
170
- callEzoic(available.getAttribute('data-placeholder-id'));
171
- });
172
- inserts++;
41
+ function redistribute() {
42
+ if (!config || config.excluded) return;
43
+
44
+ // Sélecteurs spécifiques à NodeBB 4.x / Harmony
45
+ const topicItems = document.querySelectorAll('[component="category/topic"]');
46
+ const postItems = document.querySelectorAll('[component="post"]');
47
+
48
+ let items = [], kind = '', interval = 10, showFirst = false;
49
+
50
+ if (topicItems.length > 0 && config.enableBetweenAds) {
51
+ items = Array.from(topicItems);
52
+ kind = 'between';
53
+ interval = parseInt(config.intervalPosts, 10);
54
+ showFirst = config.showFirstTopicAd;
55
+ } else if (postItems.length > 0 && config.enableMessageAds) {
56
+ items = Array.from(postItems);
57
+ kind = 'message';
58
+ interval = parseInt(config.messageIntervalPosts, 10);
59
+ showFirst = config.showFirstMessageAd;
173
60
  }
174
- }
175
- });
176
- }
177
61
 
178
- function fetchConfig(cb) {
179
- fetch('/api/plugins/ezoic-infinite/config')
180
- .then(r => r.json())
181
- .then(data => {
182
- config = data;
183
- const pool = getPool();
184
- const setupPool = (idsRaw, kind) => {
185
- if (!idsRaw) return;
186
- const ids = idsRaw.split(/[\s,]+/).map(s => s.trim()).filter(Boolean);
187
- ids.forEach(id => {
188
- if (!document.querySelector(`[data-placeholder-id="${id}"]`)) {
189
- const d = document.createElement('div');
190
- d.className = WRAP_CLASS + ' nodebb-ezoic-ad-' + kind;
191
- d.setAttribute('data-kind', kind);
192
- d.setAttribute('data-placeholder-id', id);
193
- const inner = document.createElement('div');
194
- inner.id = 'ezoic-pub-ad-placeholder-' + id;
195
- inner.className = 'ezoic-ad';
196
- d.appendChild(inner);
197
- pool.appendChild(d);
62
+ if (items.length === 0) return;
63
+
64
+ items.forEach((item, index) => {
65
+ const pos = index + 1;
66
+ const shouldHaveAd = (pos === 1 && showFirst) || (pos % interval === 0);
67
+
68
+ const next = item.nextElementSibling;
69
+ if (shouldHaveAd && !(next && next.classList.contains(WRAP_CLASS))) {
70
+ const pool = getPool();
71
+ const available = pool.querySelector(`.${WRAP_CLASS}[data-kind="${kind}"]`);
72
+ if (available) {
73
+ isInternalChange = true;
74
+ item.parentNode.insertBefore(available, item.nextSibling);
75
+ callEzoic(available.getAttribute('data-placeholder-id'));
76
+ setTimeout(() => { isInternalChange = false; }, 100);
77
+ }
198
78
  }
199
- });
200
- };
201
- setupPool(config.placeholderIds, 'between');
202
- setupPool(config.messagePlaceholderIds, 'message');
203
- cb();
204
- }).catch(e => console.error('[Ezoic] Config load error', e));
205
- }
206
-
207
- let timer = null;
208
- function schedule() {
209
- if (timer) clearTimeout(timer);
210
- timer = setTimeout(() => {
211
- // Sélecteurs pour NodeBB 4.x / Harmony
212
- const lists = document.querySelectorAll('[component="category"], .topic-list, [component="topic"], [component="category/topic/list"]');
213
- lists.forEach(redistribute);
214
- }, 200);
215
- }
216
-
217
- function init() {
218
- fetchConfig(() => {
219
- schedule();
220
- if (typeof MutationObserver !== 'undefined') {
221
- const mo = new MutationObserver((muts) => {
222
- if (isInternalChange) return;
223
- // On vérifie si un élément structurel a été ajouté
224
- const shouldRun = muts.some(m => m.addedNodes.length > 0);
225
- if (shouldRun) schedule();
226
79
  });
227
- mo.observe(document.body, { childList: true, subtree: true });
228
- }
229
- });
230
- }
80
+ }
231
81
 
232
- if (document.readyState === 'loading') {
233
- document.addEventListener('DOMContentLoaded', init);
234
- } else {
235
- init();
236
- }
82
+ function init() {
83
+ fetch('/api/plugins/ezoic-infinite/config')
84
+ .then(r => r.json())
85
+ .then(data => {
86
+ config = data;
87
+ const pool = getPool();
88
+ const setup = (raw, kind) => {
89
+ if (!raw) return;
90
+ raw.split(/[\s,]+/).filter(Boolean).forEach(id => {
91
+ if (!document.querySelector(`[data-placeholder-id="${id}"]`)) {
92
+ const d = document.createElement('div');
93
+ d.className = WRAP_CLASS;
94
+ d.setAttribute('data-kind', kind);
95
+ d.setAttribute('data-placeholder-id', id);
96
+ d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}" class="ezoic-ad"></div>`;
97
+ pool.appendChild(d);
98
+ }
99
+ });
100
+ };
101
+ setup(config.placeholderIds, 'between');
102
+ setup(config.messagePlaceholderIds, 'message');
103
+
104
+ redistribute();
105
+
106
+ // On surveille les changements de page et l'infinite scroll
107
+ const observer = new MutationObserver(() => {
108
+ if (!isInternalChange) redistribute();
109
+ });
110
+ observer.observe(document.body, { childList: true, subtree: true });
111
+ });
112
+ }
113
+
114
+ if (document.readyState === 'loading') {
115
+ document.addEventListener('DOMContentLoaded', init);
116
+ } else {
117
+ init();
118
+ }
237
119
  })();
package/public/style.css CHANGED
@@ -1,35 +1,20 @@
1
- /* Container principal */
1
+ /* Style du container injecté */
2
2
  .nodebb-ezoic-wrap {
3
- display: block;
4
- width: 100%;
5
- margin: 20px 0 !important;
6
- padding: 0 !important;
7
- clear: both;
8
- min-height: 50px; /* Important pour que l'ad-tester détecte le bloc */
3
+ display: block !important;
4
+ width: 100% !important;
5
+ margin: 30px 0 !important;
6
+ min-height: 100px; /* Force un espace pour qu'Ezoic puisse injecter */
7
+ clear: both;
9
8
  }
10
9
 
11
- /* Cache les pubs orphelines sans les supprimer du DOM */
12
- .nodebb-ezoic-wrap.ez-orphan-hidden {
13
- display: none !important;
14
- height: 0 !important;
15
- min-height: 0 !important;
16
- margin: 0 !important;
17
- }
18
-
19
- /* Nettoyage des styles internes Ezoic */
20
- .nodebb-ezoic-wrap .ezoic-ad {
21
- margin: 0 auto !important;
22
- padding: 0 !important;
23
- min-height: 1px !important;
24
- height: auto !important;
25
- }
26
-
27
- /* Neutralise les marges doubles */
10
+ /* ANTI-PILLUP : Interdit deux pubs consécutives */
28
11
  .nodebb-ezoic-wrap + .nodebb-ezoic-wrap {
29
- margin-top: 0 !important;
12
+ display: none !important;
30
13
  }
31
14
 
32
- /* Évite les débordements sur mobile */
33
- .ezoic-ad iframe {
34
- max-width: 100% !important;
15
+ /* Alignement Harmony */
16
+ [component="category/topic"] + .nodebb-ezoic-wrap,
17
+ [component="post"] + .nodebb-ezoic-wrap {
18
+ border-top: 1px solid rgba(0,0,0,0.05);
19
+ padding: 15px 0;
35
20
  }
package/public/test.txt DELETED
@@ -1 +0,0 @@
1
- hi