nodebb-plugin-ezoic-infinite 1.6.54 → 1.6.55

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/client.js +99 -60
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.54",
3
+ "version": "1.6.55",
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,32 +1,39 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
+ // --- 1. VARIABLES ET CONFIG ---
4
5
  const WRAP_CLASS = 'nodebb-ezoic-wrap';
5
6
  const PINNED_ATTR = 'data-ezoic-pinned';
6
- const PROCESSED_ATTR = 'data-ezoic-seen'; // Nouveau : marqueur de sujet
7
7
  const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
8
-
8
+
9
9
  let config = null;
10
10
  let usedIds = new Set();
11
11
  let scheduleTimer = null;
12
12
 
13
+ // --- 2. SÉLECTEURS DE THÈMES ---
13
14
  function getTopicList() {
14
- return document.querySelector('[component="topic/list"], [component="category"], [component="categories"], .topic-list');
15
+ return document.querySelector('.category [component="category"], .categories [component="categories"], [component="topic/list"], .topic-list');
16
+ }
17
+
18
+ function getPostList() {
19
+ return document.querySelector('[component="topic"], [component="post/list"]');
15
20
  }
16
21
 
17
- function getNextId(pool) {
18
- if (!pool) return null;
19
- const ids = pool.split(/[\s,]+/).filter(Boolean);
22
+ // --- 3. LOGIQUE DES POOLS D'ID ---
23
+ function getNextId(poolStr) {
24
+ if (!poolStr) return null;
25
+ const ids = poolStr.split(/[\s,]+/).filter(Boolean);
20
26
  for (const id of ids) {
21
- const n = id.replace(/[^\d]/g, '');
22
- if (n && !usedIds.has(n)) {
23
- usedIds.add(n);
24
- return n;
27
+ const num = id.replace(/[^\d]/g, '');
28
+ if (num && !usedIds.has(num)) {
29
+ usedIds.add(num);
30
+ return num;
25
31
  }
26
32
  }
27
33
  return null;
28
34
  }
29
35
 
36
+ // --- 4. APPEL EZOIC (REFRESH & SHOW) ---
30
37
  function callEzoic(id) {
31
38
  if (window.ezstandalone) {
32
39
  try {
@@ -38,87 +45,119 @@
38
45
  } else {
39
46
  window.ezstandalone.refresh();
40
47
  }
41
- } catch (e) { console.warn('[ezoic] err', e); }
48
+ } catch (e) { console.warn('[ezoic] refresh error', id, e); }
42
49
  }
43
50
  }
44
51
 
52
+ // --- 5. INJECTION SÉCURISÉE (ANTI-PILEUP) ---
45
53
  function inject() {
46
54
  if (!config || config.excluded) return;
47
- const ul = getTopicList();
48
- if (!ul) return;
49
55
 
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;
56
+ // A. LISTE DES SUJETS (Topics)
57
+ const ul = getTopicList();
58
+ if (ul && config.enableBetweenAds) {
59
+ // Nettoyage des pubs orphelines (celles qui n'ont plus de LI au dessus à cause du scroll up)
60
+ const wraps = ul.querySelectorAll('.' + WRAP_CLASS);
61
+ wraps.forEach(w => {
62
+ if (!w.previousElementSibling || w.previousElementSibling.tagName !== 'LI') w.remove();
63
+ });
64
+
65
+ const items = Array.from(ul.children).filter(c => c.tagName === 'LI' && !c.classList.contains(WRAP_CLASS));
66
+ if (items.length > 0) {
67
+ // Pub n°1 (Pinned)
68
+ if (config.showFirstTopicAd && !ul.querySelector(`[${PINNED_ATTR}="true"]`)) {
69
+ insertAd(items[0], getNextId(config.placeholderIds), true);
70
+ }
53
71
 
54
- // 2. PUB N°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);
72
+ // Pubs d'intervalles
73
+ const interval = parseInt(config.intervalPosts, 10) || 5;
74
+ items.forEach((li, idx) => {
75
+ const pos = idx + 1;
76
+ if (pos > 1 && pos % interval === 0) {
77
+ // Vérifie si ce LI a déjà une pub juste après lui pour éviter les doublons
78
+ const next = li.nextElementSibling;
79
+ if (!next || !next.classList.contains(WRAP_CLASS)) {
80
+ insertAd(li, getNextId(config.placeholderIds), false);
81
+ }
82
+ }
83
+ });
84
+ }
58
85
  }
59
86
 
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;
87
+ // B. LISTE DES MESSAGES (Posts)
88
+ const postList = getPostList();
89
+ if (postList && config.enableMessageAds) {
90
+ const posts = Array.from(postList.querySelectorAll('[component="post"]'));
91
+ if (config.showFirstMessageAd && posts.length > 0 && !postList.querySelector('.ezoic-msg-first')) {
92
+ insertAd(posts[0], getNextId(config.messagePlaceholderIds), false, 'ezoic-msg-first');
93
+ }
66
94
 
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
- }
95
+ const msgInterval = parseInt(config.messageIntervalPosts, 10) || 5;
96
+ if (msgInterval > 0) {
97
+ posts.forEach((post, idx) => {
98
+ const pPos = idx + 1;
99
+ if (pPos > 1 && pPos % msgInterval === 0) {
100
+ if (!post.nextElementSibling || !post.nextElementSibling.classList.contains(WRAP_CLASS)) {
101
+ insertAd(post, getNextId(config.messagePlaceholderIds), false);
102
+ }
103
+ }
104
+ });
75
105
  }
76
- });
106
+ }
77
107
  }
78
108
 
79
- function insertAd(target, id, isPinned) {
80
- if (!id || !target) return;
81
-
109
+ function insertAd(target, id, isPinned, extraClass = '') {
110
+ if (!target || !id) return;
82
111
  const wrap = document.createElement('div');
83
- wrap.className = WRAP_CLASS + ' ezoic-ad-between';
112
+ wrap.className = `${WRAP_CLASS} ezoic-ad-between ${extraClass}`;
84
113
  if (isPinned) wrap.setAttribute(PINNED_ATTR, 'true');
85
114
 
86
115
  const placeholder = document.createElement('div');
87
116
  placeholder.id = PLACEHOLDER_PREFIX + id;
88
117
  wrap.appendChild(placeholder);
89
118
 
90
- // Insertion physique
91
119
  target.after(wrap);
92
120
 
93
- // On force un petit délai pour laisser Ezoic voir le nouveau div dans le DOM
121
+ // On laisse le DOM respirer 50ms avant d'appeler Ezoic
94
122
  setTimeout(() => callEzoic(id), 50);
95
123
  }
96
124
 
97
- async function init() {
125
+ // --- 6. INITIALISATION ET HOOKS ---
126
+ async function fetchConfig() {
98
127
  try {
99
128
  const res = await fetch('/api/plugins/ezoic-infinite/config');
100
129
  config = await res.json();
130
+ } catch (e) { console.error('[ezoic] config fetch failed'); }
131
+ }
101
132
 
102
- // Premier passage
103
- inject();
133
+ function schedule() {
134
+ if (scheduleTimer) clearTimeout(scheduleTimer);
135
+ scheduleTimer = setTimeout(inject, 250);
136
+ }
104
137
 
105
- // Observation du DOM (pour le scroll infini)
106
- const ul = getTopicList();
107
- if (ul && typeof MutationObserver !== 'undefined') {
108
- const mo = new MutationObserver(() => {
109
- if (scheduleTimer) clearTimeout(scheduleTimer);
110
- scheduleTimer = setTimeout(inject, 300);
111
- });
112
- mo.observe(ul, { childList: true });
113
- }
138
+ function init() {
139
+ fetchConfig().then(() => {
140
+ schedule();
141
+
142
+ // MutationObserver pour le scroll infini
143
+ const body = document.body;
144
+ const mo = new MutationObserver((mutations) => {
145
+ for (let m of mutations) {
146
+ if (m.addedNodes.length > 0) {
147
+ schedule();
148
+ break;
149
+ }
150
+ }
151
+ });
152
+ mo.observe(body, { childList: true, subtree: true });
153
+ });
114
154
 
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); }
155
+ // Compatibilité jQuery/NodeBB Events
156
+ if (window.jQuery) {
157
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', () => {
158
+ setTimeout(schedule, 500);
159
+ });
160
+ }
122
161
  }
123
162
 
124
163
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);