nodebb-plugin-ezoic-infinite 1.6.86 → 1.6.88

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.86",
3
+ "version": "1.6.88",
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,134 +1,138 @@
1
1
  (function () {
2
- 'use strict';
2
+ 'use strict';
3
3
 
4
- if (window.ezInfiniteInjected) return;
5
- window.ezInfiniteInjected = true;
4
+ if (window.ezInfiniteInjected) return;
5
+ window.ezInfiniteInjected = true;
6
6
 
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 ezIsLoaded = false;
13
- let lastEnableTime = 0;
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 definedIds = new Set();
13
+ let ezoicEnabledOnThisPage = false;
14
14
 
15
- function getPool() {
16
- let p = document.getElementById(POOL_ID);
17
- if (!p) {
18
- p = document.createElement('div');
19
- p.id = POOL_ID;
20
- p.style.display = 'none';
21
- document.body.appendChild(p);
22
- }
23
- return p;
15
+ function getPool() {
16
+ let p = document.getElementById(POOL_ID);
17
+ if (!p) {
18
+ p = document.createElement('div');
19
+ p.id = POOL_ID;
20
+ p.style.display = 'none';
21
+ document.body.appendChild(p);
24
22
  }
23
+ return p;
24
+ }
25
25
 
26
- function callEzoic(pid) {
27
- if (typeof window.ezstandalone === 'undefined') return;
28
- const id = parseInt(pid, 10);
29
- if (isNaN(id)) return;
26
+ function callEzoic(id) {
27
+ if (typeof window.ezstandalone === 'undefined') return;
28
+ const pid = parseInt(id, 10);
29
+ if (isNaN(pid)) return;
30
30
 
31
- window.ezstandalone.cmd.push(function() {
32
- window.ezstandalone.define(id);
33
-
34
- // Si pas encore activé globalement
35
- if (!window.ezstandalone.enabled) {
36
- window.ezstandalone.enable();
37
- window.ezstandalone.display();
38
- lastEnableTime = Date.now();
39
- } else {
40
- // SECURITÉ : On attend au moins 2 secondes après un 'enable'
41
- // avant d'autoriser un 'refresh' pour éviter l'erreur "same page"
42
- const timeSinceEnable = Date.now() - lastEnableTime;
43
- if (timeSinceEnable > 2000) {
44
- window.ezstandalone.refresh();
45
- } else {
46
- // Si on est trop proche du enable, on retente dans 2.5s
47
- setTimeout(function() {
48
- window.ezstandalone.refresh();
49
- }, 2500);
50
- }
51
- }
52
- });
53
- }
31
+ window.ezstandalone.cmd.push(function() {
32
+ // On définit l'ID s'il est nouveau
33
+ if (!definedIds.has(pid)) {
34
+ window.ezstandalone.define(pid);
35
+ definedIds.add(pid);
36
+ }
54
37
 
55
- function redistribute() {
56
- if (!config || config.excluded) return;
38
+ // Protection contre l'erreur "cannot call refresh on same page enable"
39
+ if (!window.ezstandalone.enabled && !ezoicEnabledOnThisPage) {
40
+ window.ezstandalone.enable();
41
+ window.ezstandalone.display();
42
+ ezoicEnabledOnThisPage = true;
43
+ } else {
44
+ // Délai de sécurité de 500ms pour laisser Ezoic finir son cycle initial
45
+ setTimeout(function() {
46
+ try {
47
+ window.ezstandalone.refresh();
48
+ } catch (e) {
49
+ console.debug('[Ezoic] Refresh deferred');
50
+ }
51
+ }, 500);
52
+ }
53
+ });
54
+ }
57
55
 
58
- // Harmony NodeBB 4 - Sélecteurs Accueil et Topics
59
- const topicItems = document.querySelectorAll('[component="category/topic"]');
60
- const postItems = document.querySelectorAll('[component="post"]');
56
+ function redistribute() {
57
+ if (!config || config.excluded) return;
61
58
 
62
- // Page d'accueil / Catégories
63
- if (topicItems.length > 0 && config.enableBetweenAds) {
64
- processItems(Array.from(topicItems), 'between', config.intervalPosts, config.showFirstTopicAd);
65
- }
66
-
67
- // Topics (Messages)
68
- if (postItems.length > 0 && config.enableMessageAds) {
69
- processItems(Array.from(postItems), 'message', config.messageIntervalPosts, config.showFirstMessageAd);
70
- }
59
+ // Sélecteurs V17 (Harmony)
60
+ const topicItems = document.querySelectorAll('li[component="category/topic"]');
61
+ const postItems = document.querySelectorAll('[component="post"]');
62
+
63
+ if (topicItems.length > 0 && config.enableBetweenAds) {
64
+ processItems(Array.from(topicItems), 'between', config.intervalPosts, config.showFirstTopicAd);
65
+ }
66
+
67
+ if (postItems.length > 0 && config.enableMessageAds) {
68
+ processItems(Array.from(postItems), 'message', config.messageIntervalPosts, config.showFirstMessageAd);
71
69
  }
70
+ }
71
+
72
+ function processItems(items, kind, interval, showFirst) {
73
+ const int = parseInt(interval, 10) || 10;
74
+ items.forEach((item, index) => {
75
+ const pos = index + 1;
76
+ const shouldHaveAd = (pos === 1 && showFirst) || (pos % int === 0);
77
+
78
+ const next = item.nextElementSibling;
79
+ if (shouldHaveAd && !(next && next.classList.contains(WRAP_CLASS))) {
80
+ const pool = getPool();
81
+ const available = pool.querySelector(`.${WRAP_CLASS}[data-kind="${kind}"]`);
82
+ if (available) {
83
+ isInternalChange = true;
84
+ item.parentNode.insertBefore(available, item.nextSibling);
85
+ callEzoic(available.getAttribute('data-placeholder-id'));
86
+ setTimeout(() => { isInternalChange = false; }, 100);
87
+ }
88
+ }
89
+ });
90
+ }
72
91
 
73
- function processItems(items, kind, interval, showFirst) {
74
- const int = parseInt(interval, 10) || 10;
75
- items.forEach((item, index) => {
76
- const pos = index + 1;
77
- const shouldHaveAd = (pos === 1 && showFirst) || (pos % int === 0);
78
-
79
- const next = item.nextElementSibling;
80
- if (shouldHaveAd && !(next && next.classList.contains(WRAP_CLASS))) {
81
- const pool = getPool();
82
- const available = pool.querySelector(`.${WRAP_CLASS}[data-kind="${kind}"]`);
83
- if (available) {
84
- isInternalChange = true;
85
- item.parentNode.insertBefore(available, item.nextSibling);
86
- callEzoic(available.getAttribute('data-placeholder-id'));
87
- setTimeout(() => { isInternalChange = false; }, 100);
88
- }
92
+ function init() {
93
+ fetch('/api/plugins/ezoic-infinite/config')
94
+ .then(r => r.json())
95
+ .then(data => {
96
+ config = data;
97
+ const pool = getPool();
98
+ const setup = (raw, kind) => {
99
+ if (!raw) return;
100
+ raw.split(/[\s,]+/).filter(Boolean).forEach(id => {
101
+ if (!document.querySelector(`[data-placeholder-id="${id}"]`)) {
102
+ const d = document.createElement('div');
103
+ d.className = WRAP_CLASS;
104
+ d.setAttribute('data-kind', kind);
105
+ d.setAttribute('data-placeholder-id', id);
106
+ d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}"></div>`;
107
+ pool.appendChild(d);
89
108
  }
90
- });
91
- }
109
+ });
110
+ };
111
+ setup(config.placeholderIds, 'between');
112
+ setup(config.messagePlaceholderIds, 'message');
113
+
114
+ redistribute();
92
115
 
93
- function init() {
94
- fetch('/api/plugins/ezoic-infinite/config')
95
- .then(r => r.json())
96
- .then(data => {
97
- config = data;
98
- const pool = getPool();
99
- const setup = (raw, kind) => {
100
- if (!raw) return;
101
- raw.split(/[\s,]+/).filter(Boolean).forEach(id => {
102
- if (!document.querySelector(`[data-placeholder-id="${id}"]`)) {
103
- const d = document.createElement('div');
104
- d.className = WRAP_CLASS;
105
- d.setAttribute('data-kind', kind);
106
- d.setAttribute('data-placeholder-id', id);
107
- d.innerHTML = `<div id="ezoic-pub-ad-placeholder-${id}" class="ezoic-ad"></div>`;
108
- pool.appendChild(d);
109
- }
110
- });
111
- };
112
- setup(config.placeholderIds, 'between');
113
- setup(config.messagePlaceholderIds, 'message');
114
-
115
- redistribute();
116
+ const observer = new MutationObserver(() => {
117
+ if (!isInternalChange) redistribute();
118
+ });
119
+ observer.observe(document.body, { childList: true, subtree: true });
120
+ });
121
+ }
116
122
 
117
- const observer = new MutationObserver(() => {
118
- if (!isInternalChange) redistribute();
119
- });
120
- observer.observe(document.body, { childList: true, subtree: true });
121
- });
122
- }
123
+ // Reset lors de la navigation NodeBB (SPA)
124
+ window.addEventListener('action:ajaxify.start', function() {
125
+ ezoicEnabledOnThisPage = false;
126
+ definedIds.clear();
127
+ });
123
128
 
124
- // Correction Page d'accueil : Forcer redistribution quand on finit de charger une page
125
- window.addEventListener('action:ajaxify.end', function() {
126
- setTimeout(redistribute, 800);
127
- });
129
+ window.addEventListener('action:ajaxify.end', function() {
130
+ setTimeout(redistribute, 600);
131
+ });
128
132
 
129
- if (document.readyState === 'loading') {
130
- document.addEventListener('DOMContentLoaded', init);
131
- } else {
132
- init();
133
- }
133
+ if (document.readyState === 'loading') {
134
+ document.addEventListener('DOMContentLoaded', init);
135
+ } else {
136
+ init();
137
+ }
134
138
  })();
package/public/style.css CHANGED
@@ -1,4 +1,4 @@
1
- /* Container principal */
1
+ /* Container global */
2
2
  .nodebb-ezoic-wrap {
3
3
  display: block !important;
4
4
  width: 100% !important;
@@ -6,26 +6,24 @@
6
6
  margin: 20px 0 !important;
7
7
  min-height: 250px;
8
8
  clear: both;
9
- float: none !important;
9
+ text-align: center;
10
10
  }
11
11
 
12
- /* Forcer l'affichage dans la liste des catégories/topics (Accueil) */
13
- [component="category/topic"] {
14
- margin-bottom: 0 !important;
12
+ /* Fix spécifique pour la liste des topics (Accueil Harmony) */
13
+ li[component="category/topic"] + .nodebb-ezoic-wrap {
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
- .nodebb-ezoic-wrap[data-kind="between"] {
18
- background: transparent;
19
- border-top: 1px solid rgba(0,0,0,0.05);
20
- padding: 20px 0;
21
- }
22
-
23
- /* Cache si vraiment aucun contenu injecté après 5s */
20
+ /* Si le bloc est vide ou n'a pas encore chargé */
24
21
  .nodebb-ezoic-wrap:empty {
25
22
  min-height: 1px;
26
23
  }
27
24
 
28
- /* Empêche les doublons */
29
- .nodebb-ezoic-wrap + .nodebb-ezoic-wrap {
30
- display: none !important;
25
+ /* Neutralisation des marges internes d'Ezoic */
26
+ .ezoic-ad {
27
+ margin: 0 auto !important;
28
+ display: inline-block !important;
31
29
  }