nodebb-plugin-ezoic-infinite 1.4.67 → 1.4.69
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 +1 -1
- package/public/client.js +390 -750
- package/public/style.css +36 -5
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,810 +1,450 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
|
-
const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
4
|
-
const SELECTORS = {
|
|
5
|
-
topicItem: 'li[component="category/topic"]',
|
|
6
|
-
postItem: '[component="post"][data-pid]',
|
|
7
|
-
categoryItem: 'li[component="categories/category"]',
|
|
8
|
-
}, WRAP_CLASS = 'ezoic-ad';
|
|
9
|
-
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-', MAX_INSERTS_PER_RUN = 3;
|
|
10
|
-
|
|
11
|
-
const sessionDefinedIds = new Set();
|
|
12
|
-
|
|
13
|
-
const insertingIds = new Set(), state = {
|
|
14
|
-
pageKey: null,
|
|
15
|
-
cfg: null,
|
|
16
|
-
cfgPromise: null,
|
|
17
3
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
4
|
+
/**
|
|
5
|
+
* NodeBB + Ezoic infinite ad injector
|
|
6
|
+
* Production-ready refactor:
|
|
7
|
+
* - No blank space: wrappers hidden until filled, removed on timeout
|
|
8
|
+
* - Bounded work per tick + throttled MutationObserver
|
|
9
|
+
* - Clean ajaxify navigation handling
|
|
10
|
+
*/
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
const SELECTORS = {
|
|
13
|
+
topicItem: 'li[component="category/topic"]',
|
|
14
|
+
categoryItem: 'li[component="categories/category"]',
|
|
15
|
+
postItem: '[component="post"][data-pid]',
|
|
16
|
+
postContent: '[component="post/content"]',
|
|
17
|
+
};
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
definedIds: new Set(),
|
|
19
|
+
const CLASS_WRAP = 'ezoic-ad';
|
|
20
|
+
const PH_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
const MAX_INSERTS_PER_TICK = 3;
|
|
23
|
+
const FILL_TIMEOUT_MS = 3500;
|
|
24
|
+
const RECYCLE_MARGIN_PX = 600;
|
|
25
|
+
const SHOW_DEBOUNCE_MS = 500;
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
const state = {
|
|
28
|
+
pageKey: null,
|
|
29
|
+
cfg: null,
|
|
30
|
+
cfgPromise: null,
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
32
|
+
pools: { topics: [], categories: [], messages: [] },
|
|
33
|
+
live: { topics: [], categories: [], messages: [] },
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
out.push(n);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return out;
|
|
51
|
-
}
|
|
35
|
+
scheduled: false,
|
|
36
|
+
lastRunAt: 0,
|
|
37
|
+
timeouts: new Set(),
|
|
38
|
+
observer: null,
|
|
39
|
+
lastShowById: new Map(),
|
|
40
|
+
};
|
|
52
41
|
|
|
53
|
-
function
|
|
54
|
-
if (!raw) return [];
|
|
55
|
-
const lines = String(raw).split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
56
|
-
return uniqInts(lines);
|
|
57
|
-
}
|
|
42
|
+
function now() { return Date.now(); }
|
|
58
43
|
|
|
59
44
|
function getPageKey() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
try {
|
|
46
|
+
const ax = window.ajaxify;
|
|
47
|
+
if (ax && ax.data) {
|
|
48
|
+
if (ax.data.tid) return `topic:${ax.data.tid}`;
|
|
49
|
+
if (ax.data.cid) return `cid:${ax.data.cid}:${window.location.pathname}`;
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {}
|
|
52
|
+
return window.location.pathname || '';
|
|
68
53
|
}
|
|
69
54
|
|
|
70
55
|
function getKind() {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function safeRect(el) {
|
|
103
|
-
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function destroyPlaceholderIds(ids) {
|
|
107
|
-
if (!ids || !ids.length) return;
|
|
108
|
-
const filtered = ids.filter((id) => {
|
|
109
|
-
try { return sessionDefinedIds.has(id); } catch (e) { return true; }
|
|
110
|
-
});
|
|
111
|
-
if (!filtered.length) return;
|
|
112
|
-
|
|
113
|
-
const call = () => {
|
|
114
|
-
try {
|
|
115
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
116
|
-
window.ezstandalone.destroyPlaceholders(filtered);
|
|
117
|
-
}
|
|
118
|
-
} catch (e) {}
|
|
119
|
-
|
|
120
|
-
// Recyclage: libérer IDs après 100ms
|
|
121
|
-
setTimeout(() => {
|
|
122
|
-
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
123
|
-
}, 100);
|
|
124
|
-
};
|
|
125
|
-
try {
|
|
126
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
127
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
128
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
129
|
-
else window.ezstandalone.cmd.push(call);
|
|
130
|
-
} catch (e) {}
|
|
131
|
-
|
|
132
|
-
// Recyclage: libérer IDs après 100ms
|
|
133
|
-
setTimeout(() => {
|
|
134
|
-
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
135
|
-
}, 100);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Nettoyer éléments Ezoic invisibles qui créent espace vertical
|
|
139
|
-
function cleanupInvisibleEzoicElements() {
|
|
140
|
-
try {
|
|
141
|
-
document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
|
|
142
|
-
// Supprimer éléments invisibles après le placeholder
|
|
143
|
-
wrapper.querySelectorAll('*').forEach(el => {
|
|
144
|
-
if (el.id && el.id.startsWith('ezoic-pub-ad-placeholder-')) return;
|
|
145
|
-
|
|
146
|
-
const rect = el.getBoundingClientRect();
|
|
147
|
-
const computed = window.getComputedStyle(el);
|
|
148
|
-
|
|
149
|
-
// Élément invisible mais prend de l'espace
|
|
150
|
-
if (
|
|
151
|
-
(computed.display !== 'none' && computed.visibility !== 'hidden') &&
|
|
152
|
-
(rect.height === 0 || computed.opacity === '0') &&
|
|
153
|
-
el.children.length === 0
|
|
154
|
-
) {
|
|
155
|
-
el.remove();
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
} catch (e) {}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function cleanupEmptyWrappers() {
|
|
163
|
-
try {
|
|
164
|
-
document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
|
|
165
|
-
const ph = wrapper.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
|
|
166
|
-
if (ph && ph.children.length === 0) {
|
|
167
|
-
// Placeholder vide après 3s = pub non chargée
|
|
168
|
-
setTimeout(() => {
|
|
169
|
-
if (ph.children.length === 0) {
|
|
170
|
-
wrapper.remove();
|
|
171
|
-
}
|
|
172
|
-
}, 3000);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
} catch (e) {}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function getRecyclable(liveArr) {
|
|
179
|
-
const margin = 600;
|
|
180
|
-
for (let i = 0; i < liveArr.length; i++) {
|
|
181
|
-
const entry = liveArr[i];
|
|
182
|
-
if (!entry || !entry.wrap || !entry.wrap.isConnected) { liveArr.splice(i, 1); i--; continue; }
|
|
183
|
-
const r = safeRect(entry.wrap);
|
|
184
|
-
if (r && r.bottom < -margin) {
|
|
185
|
-
liveArr.splice(i, 1);
|
|
186
|
-
return entry;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function pickId(pool, liveArr) {
|
|
193
|
-
if (pool.length) return { id: pool.shift(), recycled: null };
|
|
194
|
-
const recycled = getRecyclable(liveArr);
|
|
195
|
-
if (recycled) return { id: recycled.id, recycled };
|
|
196
|
-
return { id: null, recycled: null };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function resetPlaceholderInWrap(wrap, id) {
|
|
200
|
-
try {
|
|
201
|
-
if (!wrap) return;
|
|
202
|
-
try { wrap.removeAttribute('data-ezoic-filled'); } catch (e) {}
|
|
203
|
-
if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
|
|
204
|
-
const old = wrap.querySelector && wrap.querySelector(`#${PLACEHOLDER_PREFIX}${id}`);
|
|
205
|
-
if (old) old.remove();
|
|
206
|
-
// Remove any leftover markup inside wrapper
|
|
207
|
-
wrap.querySelectorAll && wrap.querySelectorAll('iframe, ins').forEach(n => n.remove());
|
|
208
|
-
const ph = document.createElement('div');
|
|
209
|
-
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
210
|
-
wrap.appendChild(ph);
|
|
211
|
-
} catch (e) {}
|
|
56
|
+
const p = window.location.pathname || '';
|
|
57
|
+
if (/^\/topic\//.test(p)) return 'topic';
|
|
58
|
+
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
59
|
+
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
60
|
+
|
|
61
|
+
if (document.querySelector(SELECTORS.categoryItem)) return 'categories';
|
|
62
|
+
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
63
|
+
if (document.querySelector(SELECTORS.topicItem)) return 'categoryTopics';
|
|
64
|
+
return 'other';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseIdPool(raw) {
|
|
68
|
+
if (!raw) return [];
|
|
69
|
+
const tokens = String(raw)
|
|
70
|
+
.split(/[\s,;]+/)
|
|
71
|
+
.map(s => s.trim())
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
|
|
74
|
+
const out = [];
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
for (const t of tokens) {
|
|
77
|
+
const n = parseInt(t, 10);
|
|
78
|
+
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
79
|
+
seen.add(n);
|
|
80
|
+
out.push(n);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
212
84
|
}
|
|
213
85
|
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
86
|
+
async function fetchConfig() {
|
|
87
|
+
if (state.cfg) return state.cfg;
|
|
88
|
+
if (state.cfgPromise) return state.cfgPromise;
|
|
89
|
+
|
|
90
|
+
state.cfgPromise = fetch('/api/plugins/ezoic-infinite/config', {
|
|
91
|
+
credentials: 'same-origin',
|
|
92
|
+
headers: { 'Accept': 'application/json' },
|
|
93
|
+
})
|
|
94
|
+
.then(r => (r && r.ok ? r.json() : null))
|
|
95
|
+
.catch(() => null)
|
|
96
|
+
.then((cfg) => {
|
|
97
|
+
state.cfg = cfg || { excluded: true };
|
|
98
|
+
state.pools.topics = parseIdPool(state.cfg.placeholderIds);
|
|
99
|
+
state.pools.categories = parseIdPool(state.cfg.categoryPlaceholderIds);
|
|
100
|
+
state.pools.messages = parseIdPool(state.cfg.messagePlaceholderIds);
|
|
101
|
+
return state.cfg;
|
|
102
|
+
})
|
|
103
|
+
.finally(() => { state.cfgPromise = null; });
|
|
104
|
+
|
|
105
|
+
return state.cfgPromise;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function rect(el) {
|
|
109
|
+
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function takeRecyclable(kind) {
|
|
113
|
+
const arr = state.live[kind];
|
|
114
|
+
for (let i = 0; i < arr.length; i++) {
|
|
115
|
+
const entry = arr[i];
|
|
116
|
+
if (!entry || !entry.wrap || !entry.wrap.isConnected) {
|
|
117
|
+
arr.splice(i, 1); i--; continue;
|
|
118
|
+
}
|
|
119
|
+
const r = rect(entry.wrap);
|
|
120
|
+
if (r && r.bottom < -RECYCLE_MARGIN_PX) {
|
|
121
|
+
arr.splice(i, 1);
|
|
122
|
+
return entry;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function pickId(kind) {
|
|
129
|
+
const pool = state.pools[kind];
|
|
130
|
+
if (pool && pool.length) return { id: pool.shift(), recycled: null };
|
|
131
|
+
const recycled = takeRecyclable(kind);
|
|
132
|
+
if (recycled) return { id: recycled.id, recycled };
|
|
133
|
+
return { id: null, recycled: null };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildWrap(kind, afterIndex, id) {
|
|
137
|
+
const wrap = document.createElement('div');
|
|
138
|
+
wrap.className = `${CLASS_WRAP} ${CLASS_WRAP}--${kind}`;
|
|
139
|
+
wrap.dataset.ezoicKind = kind;
|
|
140
|
+
wrap.dataset.ezoicAfter = String(afterIndex);
|
|
141
|
+
wrap.dataset.ezoicId = String(id);
|
|
142
|
+
|
|
143
|
+
const ph = document.createElement('div');
|
|
144
|
+
ph.id = `${PH_PREFIX}${id}`;
|
|
145
|
+
wrap.appendChild(ph);
|
|
146
|
+
|
|
147
|
+
return wrap;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function findWrap(kind, afterIndex) {
|
|
151
|
+
return document.querySelector(`.${CLASS_WRAP}[data-ezoic-kind="${kind}"][data-ezoic-after="${afterIndex}"]`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function resetWrapPlaceholder(wrap, id) {
|
|
155
|
+
if (!wrap) return;
|
|
156
|
+
if (wrap.__ezoicObs) {
|
|
157
|
+
try { wrap.__ezoicObs.disconnect(); } catch (e) {}
|
|
158
|
+
wrap.__ezoicObs = null;
|
|
159
|
+
}
|
|
160
|
+
wrap.removeAttribute('data-ezoic-filled');
|
|
161
|
+
wrap.dataset.ezoicId = String(id);
|
|
162
|
+
|
|
163
|
+
while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
|
|
164
|
+
const ph = document.createElement('div');
|
|
165
|
+
ph.id = `${PH_PREFIX}${id}`;
|
|
166
|
+
wrap.appendChild(ph);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function forcePlaceholderAutoHeight(wrap, id) {
|
|
170
|
+
const ph = wrap && wrap.querySelector ? wrap.querySelector(`#${PH_PREFIX}${id}`) : null;
|
|
171
|
+
if (!ph) return;
|
|
172
|
+
ph.style.setProperty('height', 'auto', 'important');
|
|
173
|
+
ph.style.setProperty('min-height', '0px', 'important');
|
|
174
|
+
requestAnimationFrame(() => {
|
|
175
|
+
ph.style.setProperty('height', 'auto', 'important');
|
|
176
|
+
ph.style.setProperty('min-height', '0px', 'important');
|
|
177
|
+
});
|
|
306
178
|
}
|
|
307
179
|
|
|
308
180
|
function markFilled(wrap) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
181
|
+
if (!wrap || !wrap.isConnected) return;
|
|
182
|
+
if (wrap.getAttribute('data-ezoic-filled') === '1') return;
|
|
183
|
+
wrap.setAttribute('data-ezoic-filled', '1');
|
|
184
|
+
|
|
185
|
+
const id = parseInt(wrap.dataset.ezoicId, 10);
|
|
186
|
+
if (Number.isFinite(id) && id > 0) forcePlaceholderAutoHeight(wrap, id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function removeWrap(wrap) {
|
|
190
|
+
if (!wrap) return;
|
|
191
|
+
if (wrap.__ezoicObs) {
|
|
192
|
+
try { wrap.__ezoicObs.disconnect(); } catch (e) {}
|
|
193
|
+
wrap.__ezoicObs = null;
|
|
194
|
+
}
|
|
195
|
+
try { wrap.remove(); } catch (e) {}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function showEzoicAd(id) {
|
|
199
|
+
if (!id || id <= 0) return;
|
|
200
|
+
|
|
201
|
+
const last = state.lastShowById.get(id) || 0;
|
|
202
|
+
if (now() - last < SHOW_DEBOUNCE_MS) return;
|
|
203
|
+
state.lastShowById.set(id, now());
|
|
204
|
+
|
|
205
|
+
const call = () => {
|
|
206
|
+
try {
|
|
207
|
+
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
208
|
+
window.ezstandalone.showAds(id);
|
|
209
|
+
}
|
|
210
|
+
} catch (e) {}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
215
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
216
|
+
if (typeof window.ezstandalone.showAds === 'function') call();
|
|
217
|
+
else window.ezstandalone.cmd.push(call);
|
|
218
|
+
} catch (e) {}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function observeFill(wrap, id) {
|
|
222
|
+
const ph = wrap.querySelector(`#${PH_PREFIX}${id}`);
|
|
223
|
+
if (!ph) return;
|
|
224
|
+
|
|
225
|
+
// immediate
|
|
226
|
+
if (ph.querySelector('iframe, ins, img') || ph.childElementCount > 0) {
|
|
227
|
+
markFilled(wrap);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
315
230
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
231
|
+
const obs = new MutationObserver(() => {
|
|
232
|
+
if (!wrap.isConnected) return;
|
|
233
|
+
const hasCreative = !!ph.querySelector('iframe, ins, img');
|
|
234
|
+
const r = rect(ph);
|
|
235
|
+
if (hasCreative || (r && r.height > 20)) {
|
|
236
|
+
markFilled(wrap);
|
|
237
|
+
try { obs.disconnect(); } catch (e) {}
|
|
238
|
+
wrap.__ezoicObs = null;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
319
241
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (!ph) return;
|
|
324
|
-
// Already filled?
|
|
325
|
-
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
326
|
-
markFilled(wrap); // Afficher wrapper
|
|
327
|
-
sessionDefinedIds.add(id);
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
const obs = new MutationObserver(() => {
|
|
331
|
-
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
332
|
-
markFilled(wrap); // CRITIQUE: Afficher wrapper maintenant
|
|
333
|
-
try { sessionDefinedIds.add(id); } catch (e) {}
|
|
334
|
-
try { obs.disconnect(); } catch (e) {}
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
obs.observe(ph, { childList: true, subtree: true });
|
|
338
|
-
wrap.__ezoicFillObs = obs;
|
|
339
|
-
} catch (e) {}
|
|
340
|
-
}
|
|
242
|
+
wrap.__ezoicObs = obs;
|
|
243
|
+
try { obs.observe(ph, { childList: true, subtree: true, attributes: true }); } catch (e) {}
|
|
244
|
+
}
|
|
341
245
|
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
try { state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id); } catch (e) {}
|
|
352
|
-
try { markFilled(wrap); } catch (e) {}
|
|
246
|
+
function scheduleRemovalIfUnfilled(wrap, kind, id) {
|
|
247
|
+
const t = setTimeout(() => {
|
|
248
|
+
state.timeouts.delete(t);
|
|
249
|
+
if (!wrap || !wrap.isConnected) return;
|
|
250
|
+
if (wrap.getAttribute('data-ezoic-filled') === '1') return;
|
|
251
|
+
removeWrap(wrap);
|
|
252
|
+
if (state.pools[kind] && typeof id === 'number' && id > 0) state.pools[kind].push(id);
|
|
253
|
+
}, FILL_TIMEOUT_MS);
|
|
254
|
+
state.timeouts.add(t);
|
|
353
255
|
}
|
|
354
|
-
|
|
256
|
+
|
|
257
|
+
function insertAfter(target, wrap) {
|
|
258
|
+
if (!target || !wrap) return false;
|
|
259
|
+
const next = target.nextElementSibling;
|
|
260
|
+
if (next && next.classList && next.classList.contains(CLASS_WRAP)) return false;
|
|
261
|
+
try {
|
|
262
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
263
|
+
return true;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
355
267
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (sessionDefinedIds.has(id)) {
|
|
364
|
-
try {
|
|
365
|
-
destroyPlaceholderIds([id]);
|
|
366
|
-
sessionDefinedIds.delete(id);
|
|
367
|
-
} catch (e) {}
|
|
268
|
+
|
|
269
|
+
function computeAfterIndexes(count, interval, showFirst) {
|
|
270
|
+
const out = [];
|
|
271
|
+
const step = Math.max(1, parseInt(interval, 10) || 1);
|
|
272
|
+
const start = showFirst ? 1 : step;
|
|
273
|
+
for (let i = start; i < count; i += step) out.push(i);
|
|
274
|
+
return out;
|
|
368
275
|
}
|
|
369
276
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (now - last < 3500) return;
|
|
373
|
-
|
|
374
|
-
// Ajouter à la batch
|
|
375
|
-
pendingShowAdsIds.add(id);
|
|
376
|
-
|
|
377
|
-
clearTimeout(batchShowAdsTimer);
|
|
378
|
-
batchShowAdsTimer = setTimeout(() => {
|
|
379
|
-
if (pendingShowAdsIds.size === 0) return;
|
|
380
|
-
|
|
381
|
-
const idsArray = Array.from(pendingShowAdsIds);
|
|
382
|
-
pendingShowAdsIds.clear();
|
|
383
|
-
|
|
384
|
-
// Appeler showAds avec TOUS les IDs en une fois
|
|
385
|
-
try {
|
|
386
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
387
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
388
|
-
window.ezstandalone.cmd.push(function() {
|
|
389
|
-
if (typeof window.ezstandalone.showAds === 'function') {
|
|
390
|
-
// Appel batch: showAds(id1, id2, id3...)
|
|
391
|
-
window.ezstandalone.showAds(...idsArray);
|
|
392
|
-
// Tracker tous les IDs
|
|
393
|
-
idsArray.forEach(id => {
|
|
394
|
-
state.lastShowById.set(id, Date.now());
|
|
395
|
-
sessionDefinedIds.add(id);
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
} catch (e) {}
|
|
400
|
-
}, 100);
|
|
401
|
-
}
|
|
277
|
+
function runCategoryTopics(cfg) {
|
|
278
|
+
if (!cfg.enableBetweenAds) return;
|
|
402
279
|
|
|
403
|
-
|
|
404
|
-
|
|
280
|
+
const items = Array.from(document.querySelectorAll(SELECTORS.topicItem));
|
|
281
|
+
if (!items.length) return;
|
|
405
282
|
|
|
406
|
-
|
|
407
|
-
|
|
283
|
+
const after = computeAfterIndexes(items.length, cfg.intervalPosts || 6, !!cfg.showFirstTopicAd);
|
|
284
|
+
let inserts = 0;
|
|
408
285
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
286
|
+
for (const afterIndex of after) {
|
|
287
|
+
if (inserts >= MAX_INSERTS_PER_TICK) break;
|
|
288
|
+
const anchor = items[afterIndex - 1];
|
|
289
|
+
if (!anchor) continue;
|
|
290
|
+
if (findWrap('topics', afterIndex)) continue;
|
|
413
291
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
sessionDefinedIds.add(id);
|
|
417
|
-
return true;
|
|
418
|
-
}
|
|
419
|
-
} catch (e) {}
|
|
420
|
-
return false;
|
|
421
|
-
};
|
|
292
|
+
const { id, recycled } = pickId('topics');
|
|
293
|
+
if (!id) break;
|
|
422
294
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
(function waitForPh() {
|
|
426
|
-
if (state.pageKey !== startPageKey) return;
|
|
427
|
-
if (state.pendingById.has(id)) return;
|
|
295
|
+
const wrap = recycled ? recycled.wrap : buildWrap('topics', afterIndex, id);
|
|
296
|
+
if (recycled) resetWrapPlaceholder(wrap, id);
|
|
428
297
|
|
|
429
|
-
|
|
430
|
-
const el = document.getElementById(phId);
|
|
431
|
-
if (el && el.isConnected) {
|
|
298
|
+
if (!insertAfter(anchor, wrap)) { state.pools.topics.push(id); continue; }
|
|
432
299
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (attempts < 100) {
|
|
443
|
-
const timeoutId = setTimeout(waitForPh, 50);
|
|
444
|
-
state.activeTimeouts.add(timeoutId);
|
|
445
|
-
}
|
|
446
|
-
})();
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
async function fetchConfig() {
|
|
450
|
-
if (state.cfg) return state.cfg;
|
|
451
|
-
if (state.cfgPromise) return state.cfgPromise;
|
|
452
|
-
|
|
453
|
-
state.cfgPromise = (async () => {
|
|
454
|
-
const MAX_TRIES = 3;
|
|
455
|
-
let delay = 800;
|
|
456
|
-
for (let attempt = 1; attempt <= MAX_TRIES; attempt++) {
|
|
457
|
-
try {
|
|
458
|
-
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
459
|
-
if (res.ok) {
|
|
460
|
-
state.cfg = await res.json();
|
|
461
|
-
return state.cfg;
|
|
462
|
-
}
|
|
463
|
-
} catch (e) {}
|
|
464
|
-
if (attempt < MAX_TRIES) await new Promise(r => setTimeout(r, delay));
|
|
465
|
-
delay *= 2;
|
|
300
|
+
state.live.topics.push({ id, wrap });
|
|
301
|
+
observeFill(wrap, id);
|
|
302
|
+
scheduleRemovalIfUnfilled(wrap, 'topics', id);
|
|
303
|
+
if (wrap.querySelector(`#${PH_PREFIX}${id}`)) showEzoicAd(id);
|
|
304
|
+
inserts++;
|
|
305
|
+
}
|
|
466
306
|
}
|
|
467
|
-
return null;
|
|
468
|
-
})();
|
|
469
307
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
function initPools(cfg) {
|
|
474
|
-
if (state.poolTopics.length === 0) state.poolTopics = parsePool(cfg.placeholderIds);
|
|
475
|
-
if (state.poolPosts.length === 0) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
|
|
476
|
-
if (state.poolCategories.length === 0) state.poolCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function computeTargets(count, interval, showFirst) {
|
|
480
|
-
const out = [];
|
|
481
|
-
if (count <= 0) return out;
|
|
482
|
-
if (showFirst) out.push(1);
|
|
483
|
-
for (let i = 1; i <= count; i++) {
|
|
484
|
-
if (i % interval === 0) out.push(i);
|
|
485
|
-
}
|
|
486
|
-
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function injectBetween(kindClass, items, interval, showFirst, kindPool, usedSet) {
|
|
490
|
-
if (!items.length) return 0;
|
|
491
|
-
const targets = computeTargets(items.length, interval, showFirst);
|
|
308
|
+
function runCategories(cfg) {
|
|
309
|
+
if (!cfg.enableCategoryAds) return;
|
|
492
310
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
311
|
+
const items = Array.from(document.querySelectorAll(SELECTORS.categoryItem));
|
|
312
|
+
if (!items.length) return;
|
|
496
313
|
|
|
497
|
-
|
|
498
|
-
|
|
314
|
+
const after = computeAfterIndexes(items.length, cfg.intervalCategories || 4, !!cfg.showFirstCategoryAd);
|
|
315
|
+
let inserts = 0;
|
|
499
316
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
317
|
+
for (const afterIndex of after) {
|
|
318
|
+
if (inserts >= MAX_INSERTS_PER_TICK) break;
|
|
319
|
+
const anchor = items[afterIndex - 1];
|
|
320
|
+
if (!anchor) continue;
|
|
321
|
+
if (findWrap('categories', afterIndex)) continue;
|
|
503
322
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (prevWrap) continue;
|
|
323
|
+
const { id, recycled } = pickId('categories');
|
|
324
|
+
if (!id) break;
|
|
507
325
|
|
|
508
|
-
|
|
326
|
+
const wrap = recycled ? recycled.wrap : buildWrap('categories', afterIndex, id);
|
|
327
|
+
if (recycled) resetWrapPlaceholder(wrap, id);
|
|
509
328
|
|
|
510
|
-
|
|
511
|
-
const id = pick.id;
|
|
512
|
-
if (!id) break;
|
|
329
|
+
if (!insertAfter(anchor, wrap)) { state.pools.categories.push(id); continue; }
|
|
513
330
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
521
|
-
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
522
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
523
|
-
if (!wrap) continue;
|
|
524
|
-
setTimeout(() => {
|
|
525
|
-
callShowAdsWhenReady(id);
|
|
526
|
-
}, 50);
|
|
527
|
-
} else {
|
|
528
|
-
usedSet.add(id);
|
|
529
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
530
|
-
if (!wrap) continue;
|
|
531
|
-
// Micro-délai pour laisser le DOM se synchroniser
|
|
532
|
-
// Appel immédiat au lieu de 10ms delay
|
|
533
|
-
callShowAdsWhenReady(id);
|
|
331
|
+
state.live.categories.push({ id, wrap });
|
|
332
|
+
observeFill(wrap, id);
|
|
333
|
+
scheduleRemovalIfUnfilled(wrap, 'categories', id);
|
|
334
|
+
if (wrap.querySelector(`#${PH_PREFIX}${id}`)) showEzoicAd(id);
|
|
335
|
+
inserts++;
|
|
336
|
+
}
|
|
534
337
|
}
|
|
535
338
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
539
|
-
)) {
|
|
540
|
-
try { wrap.remove(); } catch (e) {}
|
|
541
|
-
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
542
|
-
try { kindPool.unshift(id); } catch (e) {}
|
|
543
|
-
usedSet.delete(id);
|
|
544
|
-
}
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
inserted += 1;
|
|
548
|
-
}
|
|
549
|
-
return inserted;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function enforceNoAdjacentAds() {
|
|
553
|
-
const ads = Array.from(document.querySelectorAll(`.${WRAP_CLASS}`));
|
|
554
|
-
for (let i = 0; i < ads.length; i++) {
|
|
555
|
-
const ad = ads[i], prev = ad.previousElementSibling;
|
|
556
|
-
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
557
|
-
try {
|
|
558
|
-
const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
559
|
-
if (ph) {
|
|
560
|
-
const id = parseInt(ph.id.replace(PLACEHOLDER_PREFIX, ''), 10);
|
|
561
|
-
if (Number.isFinite(id) && id > 0) {
|
|
562
|
-
// Détruire le placeholder si Ezoic l'a déjà défini
|
|
563
|
-
if (sessionDefinedIds.has(id)) {
|
|
564
|
-
destroyPlaceholderIds([id]);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
ad.remove();
|
|
569
|
-
} catch (e) {}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function cleanup() {
|
|
575
|
-
destroyUsedPlaceholders();
|
|
576
|
-
|
|
577
|
-
document.querySelectorAll('.ezoic-ad').forEach(el => {
|
|
578
|
-
try { el.remove(); } catch (e) {}
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
state.pageKey = getPageKey();
|
|
582
|
-
state.cfg = null;
|
|
583
|
-
state.cfgPromise = null;
|
|
584
|
-
|
|
585
|
-
state.poolTopics = [];
|
|
586
|
-
state.poolPosts = [];
|
|
587
|
-
state.poolCategories = [];
|
|
588
|
-
state.usedTopics.clear();
|
|
589
|
-
state.usedPosts.clear();
|
|
590
|
-
state.usedCategories.clear();
|
|
591
|
-
state.lastShowById.clear();
|
|
592
|
-
state.pendingById.clear();
|
|
593
|
-
state.definedIds.clear();
|
|
594
|
-
|
|
595
|
-
state.activeTimeouts.forEach(id => {
|
|
596
|
-
try { clearTimeout(id); } catch (e) {}
|
|
597
|
-
});
|
|
598
|
-
state.activeTimeouts.clear();
|
|
599
|
-
|
|
600
|
-
state.pendingById.clear();
|
|
601
|
-
|
|
602
|
-
if (state.obs) { state.obs.disconnect(); state.obs = null; }
|
|
603
|
-
|
|
604
|
-
state.scheduled = false;
|
|
605
|
-
clearTimeout(state.timer);
|
|
606
|
-
state.timer = null;
|
|
607
|
-
}
|
|
339
|
+
function runMessageAds(cfg) {
|
|
340
|
+
if (!cfg.enableMessageAds) return;
|
|
608
341
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
613
|
-
}
|
|
342
|
+
const posts = Array.from(document.querySelectorAll(SELECTORS.postItem))
|
|
343
|
+
.filter(p => p && p.isConnected && p.querySelector(SELECTORS.postContent));
|
|
344
|
+
if (!posts.length) return;
|
|
614
345
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
346
|
+
const after = computeAfterIndexes(posts.length, cfg.messageIntervalPosts || 3, !!cfg.showFirstMessageAd);
|
|
347
|
+
let inserts = 0;
|
|
619
348
|
|
|
620
|
-
|
|
349
|
+
for (const afterIndex of after) {
|
|
350
|
+
if (inserts >= MAX_INSERTS_PER_TICK) break;
|
|
351
|
+
const anchor = posts[afterIndex - 1];
|
|
352
|
+
if (!anchor) continue;
|
|
353
|
+
if (findWrap('messages', afterIndex)) continue;
|
|
621
354
|
|
|
622
|
-
|
|
623
|
-
|
|
355
|
+
const { id, recycled } = pickId('messages');
|
|
356
|
+
if (!id) break;
|
|
624
357
|
|
|
625
|
-
|
|
358
|
+
const wrap = recycled ? recycled.wrap : buildWrap('messages', afterIndex, id);
|
|
359
|
+
wrap.classList.add('ezoic-ad--message');
|
|
360
|
+
if (recycled) resetWrapPlaceholder(wrap, id);
|
|
626
361
|
|
|
627
|
-
|
|
628
|
-
let inserted = 0;
|
|
362
|
+
if (!insertAfter(anchor, wrap)) { state.pools.messages.push(id); continue; }
|
|
629
363
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
state.usedPosts);
|
|
637
|
-
}
|
|
638
|
-
} else if (kind === 'categoryTopics') {
|
|
639
|
-
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
640
|
-
inserted = injectBetween('ezoic-ad-between', getTopicItems(),
|
|
641
|
-
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
642
|
-
normalizeBool(cfg.showFirstTopicAd),
|
|
643
|
-
state.poolTopics,
|
|
644
|
-
state.usedTopics);
|
|
645
|
-
}
|
|
646
|
-
} else if (kind === 'categories') {
|
|
647
|
-
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
648
|
-
inserted = injectBetween('ezoic-ad-categories', getCategoryItems(),
|
|
649
|
-
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
650
|
-
normalizeBool(cfg.showFirstCategoryAd),
|
|
651
|
-
state.poolCategories,
|
|
652
|
-
state.usedCategories);
|
|
364
|
+
state.live.messages.push({ id, wrap });
|
|
365
|
+
observeFill(wrap, id);
|
|
366
|
+
scheduleRemovalIfUnfilled(wrap, 'messages', id);
|
|
367
|
+
if (wrap.querySelector(`#${PH_PREFIX}${id}`)) showEzoicAd(id);
|
|
368
|
+
inserts++;
|
|
369
|
+
}
|
|
653
370
|
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
enforceNoAdjacentAds();
|
|
657
371
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
else if (kind === 'categories') count = getCategoryItems().length;
|
|
372
|
+
function cleanupForNavigation() {
|
|
373
|
+
for (const t of state.timeouts) clearTimeout(t);
|
|
374
|
+
state.timeouts.clear();
|
|
662
375
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
376
|
+
if (state.observer) {
|
|
377
|
+
try { state.observer.disconnect(); } catch (e) {}
|
|
378
|
+
state.observer = null;
|
|
379
|
+
}
|
|
667
380
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
if (state.poolWaitAttempts < 8) {
|
|
673
|
-
state.poolWaitAttempts += 1;
|
|
674
|
-
setTimeout(arguments[0], 50);
|
|
675
|
-
} else {
|
|
676
|
-
}
|
|
677
|
-
} else if (inserted > 0) {
|
|
678
|
-
}
|
|
381
|
+
state.live.topics = [];
|
|
382
|
+
state.live.categories = [];
|
|
383
|
+
state.live.messages = [];
|
|
384
|
+
state.lastShowById.clear();
|
|
679
385
|
}
|
|
680
386
|
|
|
681
387
|
function scheduleRun() {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
}, { passive: true });
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
function waitForContentThenRun() {
|
|
749
|
-
const MIN_WORDS = 250;
|
|
750
|
-
let attempts = 0;
|
|
751
|
-
const maxAttempts = 20; // 20 × 200ms = 4s max
|
|
752
|
-
|
|
753
|
-
(function check() {
|
|
754
|
-
attempts++;
|
|
755
|
-
|
|
756
|
-
// Compter les mots sur la page
|
|
757
|
-
const text = document.body.innerText || '';
|
|
758
|
-
const wordCount = text.split(/\s+/).filter(Boolean).length;
|
|
759
|
-
|
|
760
|
-
if (wordCount >= MIN_WORDS) {
|
|
761
|
-
// Assez de contenu → lancer l'insertion
|
|
762
|
-
scheduleRun();
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// Pas assez de contenu
|
|
767
|
-
if (attempts >= maxAttempts) {
|
|
768
|
-
// Timeout après 4s → tenter quand même
|
|
769
|
-
scheduleRun();
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// Réessayer dans 200ms
|
|
774
|
-
setTimeout(check, 50);
|
|
775
|
-
})();
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
function waitForEzoicThenRun() {
|
|
779
|
-
let attempts = 0;
|
|
780
|
-
const maxAttempts = 50; // 50 × 200ms = 10s max
|
|
781
|
-
|
|
782
|
-
(function check() {
|
|
783
|
-
attempts++;
|
|
784
|
-
// Vérifier si Ezoic est chargé
|
|
785
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
786
|
-
// Ezoic est prêt → lancer l'insertion
|
|
787
|
-
scheduleRun();
|
|
788
|
-
waitForContentThenRun();
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
// Ezoic pas encore prêt
|
|
792
|
-
if (attempts >= maxAttempts) {
|
|
793
|
-
// Tenter quand même
|
|
794
|
-
scheduleRun();
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
// Réessayer dans 200ms
|
|
798
|
-
setTimeout(check, 50);
|
|
799
|
-
})();
|
|
388
|
+
if (state.scheduled) return;
|
|
389
|
+
state.scheduled = true;
|
|
390
|
+
requestAnimationFrame(() => {
|
|
391
|
+
state.scheduled = false;
|
|
392
|
+
run().catch(() => {});
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function run() {
|
|
397
|
+
const pk = getPageKey();
|
|
398
|
+
if (pk !== state.pageKey) {
|
|
399
|
+
state.pageKey = pk;
|
|
400
|
+
cleanupForNavigation();
|
|
401
|
+
state.cfg = null;
|
|
402
|
+
state.cfgPromise = null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const cfg = await fetchConfig();
|
|
406
|
+
if (!cfg || cfg.excluded) return;
|
|
407
|
+
|
|
408
|
+
const kind = getKind();
|
|
409
|
+
if (kind === 'categoryTopics') runCategoryTopics(cfg);
|
|
410
|
+
if (kind === 'categories') runCategories(cfg);
|
|
411
|
+
if (kind === 'topic') runMessageAds(cfg);
|
|
412
|
+
|
|
413
|
+
// remove obvious empty spacer nodes injected by networks
|
|
414
|
+
try {
|
|
415
|
+
document.querySelectorAll(`.${CLASS_WRAP} > div:empty`).forEach(n => n.remove());
|
|
416
|
+
} catch (e) {}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function start() {
|
|
420
|
+
scheduleRun();
|
|
421
|
+
|
|
422
|
+
state.observer = new MutationObserver(() => {
|
|
423
|
+
const t = now();
|
|
424
|
+
if (t - state.lastRunAt < 200) return;
|
|
425
|
+
state.lastRunAt = t;
|
|
426
|
+
scheduleRun();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
try { state.observer.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
430
|
+
|
|
431
|
+
// NodeBB ajaxify hook
|
|
432
|
+
if (window.jQuery && window.jQuery(window).on) {
|
|
433
|
+
window.jQuery(window).on('action:ajaxify.end', () => {
|
|
434
|
+
state.pageKey = null;
|
|
435
|
+
scheduleRun();
|
|
436
|
+
});
|
|
437
|
+
} else {
|
|
438
|
+
window.addEventListener('popstate', () => {
|
|
439
|
+
state.pageKey = null;
|
|
440
|
+
scheduleRun();
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (document.readyState === 'loading') {
|
|
446
|
+
document.addEventListener('DOMContentLoaded', start);
|
|
447
|
+
} else {
|
|
448
|
+
start();
|
|
800
449
|
}
|
|
801
|
-
|
|
802
|
-
cleanup();
|
|
803
|
-
bind();
|
|
804
|
-
bindScroll();
|
|
805
|
-
ensureObserver();
|
|
806
|
-
state.pageKey = getPageKey();
|
|
807
|
-
|
|
808
|
-
// Attendre que Ezoic soit chargé avant d'insérer
|
|
809
|
-
waitForEzoicThenRun();
|
|
810
|
-
})();
|
|
450
|
+
})();
|
package/public/style.css
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Ezoic Infinite Ads
|
|
3
|
+
- Wrappers are hidden until ad is detected as filled (data-ezoic-filled="1").
|
|
4
|
+
- Prevent reserved heights/margins creating blank space under creatives.
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
.ezoic-ad {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
8
|
+
display: none;
|
|
9
|
+
width: 100%;
|
|
10
|
+
clear: both;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
5
13
|
}
|
|
6
14
|
|
|
7
|
-
.ezoic-ad
|
|
15
|
+
.ezoic-ad[data-ezoic-filled="1"] {
|
|
16
|
+
display: block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Do not let the placeholder reserve fixed height */
|
|
20
|
+
.ezoic-ad > [id^="ezoic-pub-ad-placeholder-"] {
|
|
21
|
+
height: auto !important;
|
|
22
|
+
min-height: 0 !important;
|
|
8
23
|
margin: 0 !important;
|
|
9
24
|
padding: 0 !important;
|
|
10
|
-
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Avoid baseline gaps under iframes/ins */
|
|
28
|
+
.ezoic-ad iframe,
|
|
29
|
+
.ezoic-ad ins {
|
|
30
|
+
display: block !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Remove empty spacer divs that can appear after injection */
|
|
34
|
+
.ezoic-ad > div:empty {
|
|
35
|
+
display: none !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Optional: message-style ad spacing (looks nicer between posts) */
|
|
39
|
+
.ezoic-ad--message[data-ezoic-filled="1"] {
|
|
40
|
+
margin: 0.75rem 0;
|
|
41
|
+
}
|