nodebb-plugin-ezoic-infinite 1.6.53 → 1.6.54

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.53",
3
+ "version": "1.6.54",
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,68 +1,27 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- // --- 1. GESTION DU SCROLL ---
5
- let lastScrollY = 0;
6
- let scrollDir = 1;
7
- try {
8
- lastScrollY = window.scrollY || 0;
9
- window.addEventListener('scroll', () => {
10
- const y = window.scrollY || 0;
11
- const d = y - lastScrollY;
12
- if (Math.abs(d) > 4) {
13
- scrollDir = d > 0 ? 1 : -1;
14
- lastScrollY = y;
15
- }
16
- }, { passive: true });
17
- } catch (e) {}
18
-
19
4
  const WRAP_CLASS = 'nodebb-ezoic-wrap';
20
- const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
21
5
  const PINNED_ATTR = 'data-ezoic-pinned';
6
+ const PROCESSED_ATTR = 'data-ezoic-seen'; // Nouveau : marqueur de sujet
7
+ const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
22
8
 
23
9
  let config = null;
24
10
  let usedIds = new Set();
25
11
  let scheduleTimer = null;
26
12
 
27
- // --- 2. SÉLECTEURS DE THÈMES (Harmony/Persona) ---
28
13
  function getTopicList() {
29
- return document.querySelector('.category [component="category"], .categories [component="categories"], [component="topic/list"], .topic-list');
30
- }
31
-
32
- function getPostList() {
33
- return document.querySelector('[component="topic"], [component="post/list"]');
34
- }
35
-
36
- // --- 3. FIX ANTI-PILEUP / ANTI-REMONTÉE ---
37
- function pruneOrphans(ul) {
38
- if (!ul) return;
39
- const wraps = ul.querySelectorAll('.' + WRAP_CLASS);
40
- wraps.forEach(wrap => {
41
- // Si c'est la pub après le 1er sujet, on la force à rester collée au 1er LI
42
- if (wrap.getAttribute(PINNED_ATTR) === 'true') {
43
- const firstLi = ul.querySelector('li[data-tid], li:first-child');
44
- if (firstLi && wrap.previousElementSibling !== firstLi) {
45
- firstLi.after(wrap);
46
- }
47
- return;
48
- }
49
- // Si on remonte, on supprime les pubs qui perdent leur "ancre" (le LI précédent)
50
- // NodeBB détruit les LI du haut pour la performance, on doit suivre le mouvement
51
- if (scrollDir === -1 && (!wrap.previousElementSibling || wrap.previousElementSibling.tagName !== 'LI')) {
52
- wrap.remove();
53
- }
54
- });
14
+ return document.querySelector('[component="topic/list"], [component="category"], [component="categories"], .topic-list');
55
15
  }
56
16
 
57
- // --- 4. LOGIQUE EZOIC (Pools & Refresh) ---
58
- function getNextId(poolStr) {
59
- if (!poolStr) return null;
60
- const ids = poolStr.split(/[\s,]+/).filter(Boolean);
17
+ function getNextId(pool) {
18
+ if (!pool) return null;
19
+ const ids = pool.split(/[\s,]+/).filter(Boolean);
61
20
  for (const id of ids) {
62
- const num = id.replace(/[^\d]/g, '');
63
- if (num && !usedIds.has(num)) {
64
- usedIds.add(num);
65
- return num;
21
+ const n = id.replace(/[^\d]/g, '');
22
+ if (n && !usedIds.has(n)) {
23
+ usedIds.add(n);
24
+ return n;
66
25
  }
67
26
  }
68
27
  return null;
@@ -79,92 +38,87 @@
79
38
  } else {
80
39
  window.ezstandalone.refresh();
81
40
  }
82
- } catch (e) { console.warn('[ezoic] refresh error', id, e); }
41
+ } catch (e) { console.warn('[ezoic] err', e); }
83
42
  }
84
43
  }
85
44
 
86
- // --- 5. INJECTION ---
87
45
  function inject() {
88
46
  if (!config || config.excluded) return;
89
-
90
- // A. Gestion des SUJETS (Liste de catégories/topics)
91
47
  const ul = getTopicList();
92
- if (ul && config.enableBetweenAds) {
93
- pruneOrphans(ul);
94
- const items = Array.from(ul.children).filter(c => c.tagName === 'LI' && !c.classList.contains(WRAP_CLASS));
95
-
96
- // Pub 1 (Pinned)
97
- if (config.showFirstTopicAd && !ul.querySelector(`[${PINNED_ATTR}="true"]`)) {
98
- insertAd(items[0], getNextId(config.placeholderIds), true);
99
- }
48
+ if (!ul) return;
100
49
 
101
- // Intervalle (uniquement en scroll vers le bas)
102
- if (scrollDir === 1) {
103
- const interval = parseInt(config.intervalPosts, 10) || 5;
104
- items.forEach((li, idx) => {
105
- if ((idx + 1) % interval === 0 && idx > 0) {
106
- if (!li.nextElementSibling?.classList.contains(WRAP_CLASS)) {
107
- insertAd(li, getNextId(config.placeholderIds), false);
108
- }
109
- }
110
- });
111
- }
50
+ // 1. On récupère tous les LI qui ne sont pas des pubs
51
+ const items = Array.from(ul.children).filter(c => c.tagName === 'LI' && !c.classList.contains(WRAP_CLASS));
52
+ if (!items.length) return;
53
+
54
+ // 2. PUB 1 (Après le 1er sujet)
55
+ if (config.showFirstTopicAd && !ul.querySelector(`[${PINNED_ATTR}="true"]`)) {
56
+ items[0].setAttribute(PROCESSED_ATTR, 'true');
57
+ insertAd(items[0], getNextId(config.placeholderIds), true);
112
58
  }
113
59
 
114
- // B. Gestion des MESSAGES (Post list)
115
- const postList = getPostList();
116
- if (postList && config.enableMessageAds) {
117
- const posts = Array.from(postList.querySelectorAll('[component="post"]')).filter(p => !p.closest('.' + WRAP_CLASS));
118
- if (config.showFirstMessageAd && posts.length > 0 && !postList.querySelector('.ezoic-msg-first')) {
119
- insertAd(posts[0], getNextId(config.messagePlaceholderIds), false, 'ezoic-msg-first');
60
+ // 3. PUBS SUIVANTES (Intervalle)
61
+ const interval = parseInt(config.intervalPosts, 10) || 5;
62
+
63
+ // On ne traite que les éléments qui n'ont pas encore été "vus" par le script
64
+ items.forEach((li, idx) => {
65
+ const positionAbsolue = idx + 1;
66
+
67
+ // Si on tombe sur un multiple de l'intervalle ET que le LI n'a pas déjà été marqué
68
+ if (positionAbsolue > 1 && positionAbsolue % interval === 0) {
69
+ // Vérification anti-doublon : est-ce que ce LI a déjà une pub juste après lui ?
70
+ const hasAdAfter = li.nextElementSibling && li.nextElementSibling.classList.contains(WRAP_CLASS);
71
+
72
+ if (!hasAdAfter) {
73
+ insertAd(li, getNextId(config.placeholderIds), false);
74
+ }
120
75
  }
121
- // Logique d'intervalle message ici si besoin (similaire aux topics)
122
- }
76
+ });
123
77
  }
124
78
 
125
- function insertAd(target, id, isPinned, extraClass = '') {
126
- if (!target || !id) return;
79
+ function insertAd(target, id, isPinned) {
80
+ if (!id || !target) return;
81
+
127
82
  const wrap = document.createElement('div');
128
- wrap.className = `${WRAP_CLASS} ezoic-ad-between ${extraClass}`;
83
+ wrap.className = WRAP_CLASS + ' ezoic-ad-between';
129
84
  if (isPinned) wrap.setAttribute(PINNED_ATTR, 'true');
130
85
 
131
86
  const placeholder = document.createElement('div');
132
87
  placeholder.id = PLACEHOLDER_PREFIX + id;
133
88
  wrap.appendChild(placeholder);
134
89
 
90
+ // Insertion physique
135
91
  target.after(wrap);
136
- callEzoic(id);
92
+
93
+ // On force un petit délai pour laisser Ezoic voir le nouveau div dans le DOM
94
+ setTimeout(() => callEzoic(id), 50);
137
95
  }
138
96
 
139
- // --- 6. INITIALISATION ---
140
- async function fetchConfig() {
97
+ async function init() {
141
98
  try {
142
99
  const res = await fetch('/api/plugins/ezoic-infinite/config');
143
100
  config = await res.json();
144
- } catch (e) {}
145
- }
146
101
 
147
- function schedule() {
148
- if (scheduleTimer) clearTimeout(scheduleTimer);
149
- scheduleTimer = setTimeout(inject, 200);
150
- }
102
+ // Premier passage
103
+ inject();
151
104
 
152
- function init() {
153
- fetchConfig().then(() => {
154
- schedule();
105
+ // Observation du DOM (pour le scroll infini)
155
106
  const ul = getTopicList();
156
107
  if (ul && typeof MutationObserver !== 'undefined') {
157
- new MutationObserver((m) => {
158
- if (m.some(record => record.addedNodes.length > 0)) schedule();
159
- }).observe(ul, { childList: true });
108
+ const mo = new MutationObserver(() => {
109
+ if (scheduleTimer) clearTimeout(scheduleTimer);
110
+ scheduleTimer = setTimeout(inject, 300);
111
+ });
112
+ mo.observe(ul, { childList: true });
160
113
  }
161
- });
162
114
 
163
- if (window.jQuery) {
164
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', () => {
165
- setTimeout(schedule, 400);
166
- });
167
- }
115
+ // Hooks NodeBB
116
+ if (window.jQuery) {
117
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', () => {
118
+ setTimeout(inject, 500);
119
+ });
120
+ }
121
+ } catch (e) { console.error('[ezoic] init fail', e); }
168
122
  }
169
123
 
170
124
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
package/public/style.css CHANGED
@@ -86,3 +86,15 @@ li.nodebb-ezoic-host { list-style: none; width: 100%; display: block; }
86
86
  li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; display: block; }
87
87
  /* ===== /V17 ===== */
88
88
 
89
+ .nodebb-ezoic-wrap {
90
+ display: block;
91
+ width: 100%;
92
+ clear: both;
93
+ margin: 20px 0 !important; /* Ajoute un peu d'espace pour le visuel */
94
+ min-height: 50px; /* Évite que le bloc ne soit écrasé à 0px */
95
+ }
96
+
97
+ /* Si la pub est vide, on garde un petit espace pour éviter le saut au scroll up */
98
+ .nodebb-ezoic-wrap:empty {
99
+ min-height: 1px;
100
+ }