nodebb-plugin-ezoic-infinite 1.4.23 → 1.4.24
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 +606 -720
package/public/client.js
CHANGED
|
@@ -4,851 +4,737 @@
|
|
|
4
4
|
const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
5
5
|
|
|
6
6
|
const SELECTORS = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const WRAP_CLASS = 'ezoic-ad';
|
|
13
|
-
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
14
|
-
|
|
15
|
-
const MAX_INSERTS_PER_RUN = 3;
|
|
7
|
+
topicItem: 'li[component="category/topic"]',
|
|
8
|
+
postItem: '[component="post"][data-pid]',
|
|
9
|
+
categoryItem: 'li[component="categories/category"]',
|
|
10
|
+
}, WRAP_CLASS = 'ezoic-ad';
|
|
11
|
+
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-', MAX_INSERTS_PER_RUN = 3;
|
|
16
12
|
|
|
17
13
|
// IDs ayant été définis par Ezoic au moins une fois dans cette session navigateur.
|
|
18
14
|
// Survit aux navigations ajaxify (contrairement à state.definedIds remis à zéro dans cleanup).
|
|
19
15
|
// Nécessaire pour savoir si on doit appeler destroyPlaceholders avant recyclage.
|
|
20
16
|
const sessionDefinedIds = new Set();
|
|
21
|
-
|
|
17
|
+
|
|
22
18
|
// IDs actuellement en cours d'insertion (lock pour éviter les doublons de race condition)
|
|
23
|
-
const insertingIds = new Set()
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
definedIds: new Set(),
|
|
52
|
-
|
|
53
|
-
scheduled: false,
|
|
54
|
-
timer: null,
|
|
55
|
-
|
|
56
|
-
obs: null,
|
|
57
|
-
attempts: 0,
|
|
58
|
-
poolWaitAttempts: 0,
|
|
59
|
-
__scrollBound: false,
|
|
19
|
+
const insertingIds = new Set(), state = {
|
|
20
|
+
pageKey: null,
|
|
21
|
+
cfg: null,
|
|
22
|
+
cfgPromise: null,
|
|
23
|
+
|
|
24
|
+
poolTopics: [],
|
|
25
|
+
poolPosts: [],
|
|
26
|
+
poolCategories: [],
|
|
27
|
+
|
|
28
|
+
usedTopics: new Set(),
|
|
29
|
+
usedPosts: new Set(),
|
|
30
|
+
usedCategories: new Set(),
|
|
31
|
+
|
|
32
|
+
// wrappers currently on page (FIFO for recycling)
|
|
33
|
+
liveBetween: [],
|
|
34
|
+
liveMessage: [],
|
|
35
|
+
liveCategory: [],
|
|
36
|
+
|
|
37
|
+
lastShowById: new Map(),
|
|
38
|
+
pendingById: new Set(),
|
|
39
|
+
definedIds: new Set(),
|
|
40
|
+
|
|
41
|
+
scheduled: false,
|
|
42
|
+
timer: null,
|
|
43
|
+
|
|
44
|
+
obs: null,
|
|
45
|
+
lastScrollRun: 0,
|
|
46
|
+
__scrollBound: false,
|
|
60
47
|
};
|
|
61
48
|
|
|
62
49
|
function normalizeBool(v) {
|
|
63
|
-
|
|
50
|
+
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
64
51
|
}
|
|
65
52
|
|
|
66
53
|
function uniqInts(lines) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return out;
|
|
54
|
+
const out = [], seen = new Set();
|
|
55
|
+
for (const v of lines) {
|
|
56
|
+
const n = parseInt(v, 10);
|
|
57
|
+
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
58
|
+
seen.add(n);
|
|
59
|
+
out.push(n);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
77
63
|
}
|
|
78
64
|
|
|
79
65
|
function parsePool(raw) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
66
|
+
if (!raw) return [];
|
|
67
|
+
const lines = String(raw).split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
68
|
+
return uniqInts(lines);
|
|
83
69
|
}
|
|
84
70
|
|
|
85
71
|
function getPageKey() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
72
|
+
try {
|
|
73
|
+
const ax = window.ajaxify;
|
|
74
|
+
if (ax && ax.data) {
|
|
75
|
+
if (ax.data.tid) return `topic:${ax.data.tid}`;
|
|
76
|
+
if (ax.data.cid) return `cid:${ax.data.cid}:${window.location.pathname}`;
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {}
|
|
79
|
+
return window.location.pathname;
|
|
94
80
|
}
|
|
95
81
|
|
|
96
82
|
function getKind() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
83
|
+
const p = window.location.pathname || '';
|
|
84
|
+
if (/^\/topic\//.test(p)) return 'topic';
|
|
85
|
+
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
86
|
+
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
87
|
+
// fallback by DOM
|
|
88
|
+
if (document.querySelector(SELECTORS.categoryItem)) return 'categories';
|
|
89
|
+
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
90
|
+
if (document.querySelector(SELECTORS.topicItem)) return 'categoryTopics';
|
|
91
|
+
return 'other';
|
|
106
92
|
}
|
|
107
93
|
|
|
108
94
|
function getTopicItems() {
|
|
109
|
-
|
|
95
|
+
return Array.from(document.querySelectorAll(SELECTORS.topicItem));
|
|
110
96
|
}
|
|
111
97
|
|
|
112
98
|
function getCategoryItems() {
|
|
113
|
-
|
|
99
|
+
return Array.from(document.querySelectorAll(SELECTORS.categoryItem));
|
|
114
100
|
}
|
|
115
101
|
|
|
116
102
|
function getPostContainers() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
103
|
+
const nodes = Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
104
|
+
return nodes.filter((el) => {
|
|
105
|
+
if (!el || !el.isConnected) return false;
|
|
106
|
+
if (!el.querySelector('[component="post/content"]')) return false;
|
|
107
|
+
const parentPost = el.parentElement && el.parentElement.closest('[component="post"][data-pid]');
|
|
108
|
+
if (parentPost && parentPost !== el) return false;
|
|
109
|
+
if (el.getAttribute('component') === 'post/parent') return false;
|
|
110
|
+
return true;
|
|
111
|
+
});
|
|
126
112
|
}
|
|
127
113
|
|
|
128
|
-
|
|
129
114
|
function safeRect(el) {
|
|
130
|
-
|
|
115
|
+
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
131
116
|
}
|
|
132
117
|
|
|
133
118
|
function destroyPlaceholderIds(ids) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
119
|
+
if (!ids || !ids.length) return;
|
|
120
|
+
// Only destroy ids that were actually "defined" (filled at least once) to avoid Ezoic warnings.
|
|
121
|
+
const filtered = ids.filter((id) => {
|
|
122
|
+
// Utiliser sessionDefinedIds (survit aux navigations) plutôt que state.definedIds
|
|
123
|
+
try { return sessionDefinedIds.has(id); } catch (e) { return true; }
|
|
124
|
+
});
|
|
125
|
+
if (!filtered.length) return;
|
|
126
|
+
|
|
127
|
+
const call = () => {
|
|
128
|
+
try {
|
|
129
|
+
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
130
|
+
window.ezstandalone.destroyPlaceholders(filtered);
|
|
131
|
+
}
|
|
132
|
+
} catch (e) {}
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
136
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
137
|
+
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
138
|
+
else window.ezstandalone.cmd.push(call);
|
|
139
|
+
} catch (e) {}
|
|
155
140
|
}
|
|
156
141
|
|
|
157
142
|
function getRecyclable(liveArr) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
143
|
+
const margin = 600;
|
|
144
|
+
for (let i = 0; i < liveArr.length; i++) {
|
|
145
|
+
const entry = liveArr[i];
|
|
146
|
+
if (!entry || !entry.wrap || !entry.wrap.isConnected) { liveArr.splice(i, 1); i--; continue; }
|
|
147
|
+
const r = safeRect(entry.wrap);
|
|
148
|
+
if (r && r.bottom < -margin) {
|
|
149
|
+
liveArr.splice(i, 1);
|
|
150
|
+
return entry;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
169
154
|
}
|
|
170
155
|
|
|
171
156
|
function pickId(pool, liveArr) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
157
|
+
if (pool.length) return { id: pool.shift(), recycled: null };
|
|
158
|
+
const recycled = getRecyclable(liveArr);
|
|
159
|
+
if (recycled) return { id: recycled.id, recycled };
|
|
160
|
+
return { id: null, recycled: null };
|
|
176
161
|
}
|
|
177
162
|
|
|
178
|
-
|
|
179
163
|
function resetPlaceholderInWrap(wrap, id) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
164
|
+
try {
|
|
165
|
+
if (!wrap) return;
|
|
166
|
+
try { wrap.removeAttribute('data-ezoic-filled'); } catch (e) {}
|
|
167
|
+
try { if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; } } catch (e) {}
|
|
168
|
+
const old = wrap.querySelector && wrap.querySelector(`#${PLACEHOLDER_PREFIX}${id}`);
|
|
169
|
+
if (old) old.remove();
|
|
170
|
+
// Remove any leftover markup inside wrapper
|
|
171
|
+
wrap.querySelectorAll && wrap.querySelectorAll('iframe, ins').forEach(n => n.remove());
|
|
172
|
+
const ph = document.createElement('div');
|
|
173
|
+
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
174
|
+
wrap.appendChild(ph);
|
|
175
|
+
} catch (e) {}
|
|
192
176
|
}
|
|
193
177
|
|
|
194
178
|
function isAdjacentAd(target) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
179
|
+
if (!target || !target.nextElementSibling) return false;
|
|
180
|
+
const next = target.nextElementSibling;
|
|
181
|
+
if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
|
|
182
|
+
return false;
|
|
199
183
|
}
|
|
200
184
|
|
|
201
185
|
function isPrevAd(target) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
186
|
+
if (!target || !target.previousElementSibling) return false;
|
|
187
|
+
const prev = target.previousElementSibling;
|
|
188
|
+
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
|
|
189
|
+
return false;
|
|
206
190
|
}
|
|
207
191
|
|
|
208
192
|
function buildWrap(id, kindClass, afterPos) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
193
|
+
const wrap = document.createElement('div');
|
|
194
|
+
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
195
|
+
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
196
|
+
wrap.style.width = '100%';
|
|
213
197
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
198
|
+
const ph = document.createElement('div');
|
|
199
|
+
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
200
|
+
wrap.appendChild(ph);
|
|
201
|
+
return wrap;
|
|
218
202
|
}
|
|
219
203
|
|
|
220
204
|
function findWrap(kindClass, afterPos) {
|
|
221
|
-
|
|
205
|
+
return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
|
|
222
206
|
}
|
|
223
207
|
|
|
224
208
|
function insertAfter(target, id, kindClass, afterPos) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
209
|
+
if (!target || !target.insertAdjacentElement) return null;
|
|
210
|
+
if (findWrap(kindClass, afterPos)) return null;
|
|
211
|
+
|
|
212
|
+
// CRITICAL: Double-lock pour éviter race conditions sur les doublons
|
|
213
|
+
// 1. Vérifier qu'aucun autre thread n'est en train d'insérer cet ID
|
|
214
|
+
if (insertingIds.has(id)) return null;
|
|
215
|
+
|
|
216
|
+
// 2. Vérifier qu'aucun placeholder avec cet ID n'existe déjà dans le DOM
|
|
217
|
+
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
218
|
+
if (existingPh && existingPh.isConnected) return null;
|
|
219
|
+
|
|
220
|
+
// Acquérir le lock
|
|
221
|
+
insertingIds.add(id);
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
225
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
226
|
+
attachFillObserver(wrap, id);
|
|
227
|
+
return wrap;
|
|
228
|
+
} finally {
|
|
229
|
+
// Libérer le lock après 100ms (le temps que le DOM soit stable)
|
|
230
|
+
setTimeout(() => insertingIds.delete(id), 100);
|
|
231
|
+
}
|
|
248
232
|
}
|
|
249
233
|
|
|
250
234
|
function destroyUsedPlaceholders() {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
state.usedTopics.forEach((id) => ids.push(id));
|
|
254
|
-
state.usedPosts.forEach((id) => ids.push(id));
|
|
255
|
-
state.usedCategories.forEach((id) => ids.push(id));
|
|
256
|
-
} catch (e) {}
|
|
257
|
-
destroyPlaceholderIds(ids);
|
|
235
|
+
const ids = [...state.usedTopics, ...state.usedPosts, ...state.usedCategories];
|
|
236
|
+
if (ids.length) destroyPlaceholderIds(ids);
|
|
258
237
|
}
|
|
259
238
|
|
|
260
239
|
function patchShowAds() {
|
|
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
|
-
applyPatch();
|
|
290
|
-
// Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
|
|
291
|
-
if (!window.__nodebbEzoicPatched) {
|
|
292
|
-
try {
|
|
293
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
294
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
295
|
-
window.ezstandalone.cmd.push(applyPatch);
|
|
296
|
-
} catch (e) {}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
240
|
+
// Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
|
|
241
|
+
// Also ensures the patch is applied even if Ezoic loads after our script.
|
|
242
|
+
const applyPatch = () => {
|
|
243
|
+
try {
|
|
244
|
+
window.ezstandalone = window.ezstandalone || {}, ez = window.ezstandalone;
|
|
245
|
+
if (window.__nodebbEzoicPatched) return;
|
|
246
|
+
if (typeof ez.showAds !== 'function') return;
|
|
247
|
+
|
|
248
|
+
window.__nodebbEzoicPatched = true;
|
|
249
|
+
const orig = ez.showAds;
|
|
250
|
+
|
|
251
|
+
ez.showAds = function (arg) {
|
|
252
|
+
if (Array.isArray(arg)) {
|
|
253
|
+
const seen = new Set();
|
|
254
|
+
for (const v of arg) {
|
|
255
|
+
const id = parseInt(v, 10);
|
|
256
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
257
|
+
seen.add(id);
|
|
258
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
return orig.apply(ez, arguments);
|
|
263
|
+
};
|
|
264
|
+
} catch (e) {}
|
|
265
|
+
};
|
|
300
266
|
|
|
267
|
+
applyPatch();
|
|
268
|
+
// Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
|
|
269
|
+
if (!window.__nodebbEzoicPatched) {
|
|
270
|
+
try {
|
|
271
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
272
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
273
|
+
window.ezstandalone.cmd.push(applyPatch);
|
|
274
|
+
} catch (e) {}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
301
277
|
|
|
302
278
|
function markFilled(wrap) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
279
|
+
try {
|
|
280
|
+
if (!wrap) return;
|
|
281
|
+
// Disconnect the fill observer first (no need to remove+re-add the attribute)
|
|
282
|
+
try { if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; } } catch (e) {}
|
|
283
|
+
wrap.setAttribute('data-ezoic-filled', '1');
|
|
284
|
+
} catch (e) {}
|
|
309
285
|
}
|
|
310
286
|
|
|
311
287
|
function isWrapMarkedFilled(wrap) {
|
|
312
|
-
|
|
288
|
+
try { return wrap && wrap.getAttribute && wrap.getAttribute('data-ezoic-filled') === '1'; } catch (e) { return false; }
|
|
313
289
|
}
|
|
314
290
|
|
|
315
291
|
function attachFillObserver(wrap, id) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
292
|
+
try {
|
|
293
|
+
const ph = wrap && wrap.querySelector && wrap.querySelector(`#${PLACEHOLDER_PREFIX}${id}`);
|
|
294
|
+
if (!ph) return;
|
|
295
|
+
// Already filled?
|
|
296
|
+
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
297
|
+
markFilled(wrap);
|
|
298
|
+
state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const obs = new MutationObserver(() => {
|
|
302
|
+
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
303
|
+
markFilled(wrap);
|
|
304
|
+
try { state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id); } catch (e) {}
|
|
305
|
+
try { obs.disconnect(); } catch (e) {}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
obs.observe(ph, { childList: true, subtree: true });
|
|
309
|
+
// Keep a weak reference on the wrapper so we can disconnect on recycle/remove
|
|
310
|
+
wrap.__ezoicFillObs = obs;
|
|
311
|
+
} catch (e) {}
|
|
336
312
|
}
|
|
337
313
|
|
|
338
314
|
function isPlaceholderFilled(id) {
|
|
339
|
-
|
|
340
|
-
|
|
315
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
316
|
+
if (!ph || !ph.isConnected) return false;
|
|
341
317
|
|
|
342
|
-
|
|
343
|
-
|
|
318
|
+
const wrap = ph.parentElement;
|
|
319
|
+
if (wrap && isWrapMarkedFilled(wrap)) return true;
|
|
344
320
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
321
|
+
const filled = !!(ph.childNodes && ph.childNodes.length > 0);
|
|
322
|
+
if (filled) {
|
|
323
|
+
try { state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id); } catch (e) {}
|
|
324
|
+
try { markFilled(wrap); } catch (e) {}
|
|
325
|
+
}
|
|
326
|
+
return filled;
|
|
351
327
|
}
|
|
352
328
|
|
|
353
329
|
function scheduleRefill(delay = 350) {
|
|
354
|
-
|
|
355
|
-
state.retryTimer = setTimeout(refillUnfilled, delay);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function enqueueRetry(id) {
|
|
359
|
-
if (!id) return;
|
|
360
|
-
if (state.badIds && state.badIds.has(id)) return;
|
|
361
|
-
if (state.retryQueueSet.has(id)) return;
|
|
362
|
-
// Ne pas enqueue si showAds a été appelé récemment (< 2s) pour éviter "already been defined"
|
|
363
|
-
const now = Date.now();
|
|
364
|
-
const last = state.lastShowById.get(id) || 0;
|
|
365
|
-
if (now - last < 2000) return;
|
|
366
|
-
state.retryQueueSet.add(id);
|
|
367
|
-
state.retryQueue.push(id);
|
|
368
|
-
processRetryQueue();
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function processRetryQueue() {
|
|
372
|
-
if (state.retryQueueRunning) return;
|
|
373
|
-
state.retryQueueRunning = true;
|
|
374
|
-
|
|
375
|
-
const step = () => {
|
|
376
|
-
const id = state.retryQueue.shift();
|
|
377
|
-
if (!id) {
|
|
378
|
-
state.retryQueueRunning = false;
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
state.retryQueueSet.delete(id);
|
|
382
|
-
const attempts = (state.retryById.get(id) || 0);
|
|
383
|
-
const phNow = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
384
|
-
const wrapNow = phNow && phNow.parentElement;
|
|
385
|
-
// Reset before re-requesting if Ezoic already defined this id but placeholder is now empty,
|
|
386
|
-
// OR if a previous attempt already failed.
|
|
387
|
-
if (wrapNow && wrapNow.isConnected && !isPlaceholderFilled(id) && !isWrapMarkedFilled(wrapNow) &&
|
|
388
|
-
(state.definedIds.has(id) || attempts > 0)) {
|
|
389
|
-
destroyPlaceholderIds([id]);
|
|
390
|
-
resetPlaceholderInWrap(wrapNow, id);
|
|
391
|
-
attachFillObserver(wrapNow, id);
|
|
392
|
-
}
|
|
393
|
-
// Ne pas appeler showAds si un autre appel est déjà en cours pour cet ID
|
|
394
|
-
if (!state.pendingById.has(id)) {
|
|
395
|
-
callShowAdsWhenReady(id);
|
|
396
|
-
}
|
|
397
|
-
setTimeout(step, 1100);
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
step();
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
function refillUnfilled() {
|
|
405
|
-
// Si tous les IDs disponibles sont blacklistés, ne pas réessayer
|
|
406
|
-
const totalPoolSize = state.poolTopics.length + state.poolPosts.length + state.poolCategories.length +
|
|
407
|
-
state.usedTopics.size + state.usedPosts.size + state.usedCategories.size;
|
|
408
|
-
if (totalPoolSize > 0 && state.badIds.size >= totalPoolSize) {
|
|
409
|
-
// Tous les IDs disponibles sont blacklistés - arrêter les retry
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}`));
|
|
414
|
-
let scheduledAny = false;
|
|
415
|
-
|
|
416
|
-
for (const wrap of wraps) {
|
|
417
|
-
// Skip les wrappers cachés par enforceNoAdjacentAds
|
|
418
|
-
if (wrap.style && wrap.style.display === 'none') continue;
|
|
419
|
-
|
|
420
|
-
const ph = wrap.querySelector && wrap.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
421
|
-
if (!ph) continue;
|
|
422
|
-
const id = parseInt(ph.id.replace(PLACEHOLDER_PREFIX, ''), 10);
|
|
423
|
-
if (!Number.isFinite(id) || id <= 0) continue;
|
|
424
|
-
|
|
425
|
-
if (isPlaceholderFilled(id)) {
|
|
426
|
-
state.retryById.delete(id);
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
// If wrapper was marked filled, don't try to refill even if placeholder temporarily appears empty.
|
|
430
|
-
if (isWrapMarkedFilled(wrap)) {
|
|
431
|
-
state.retryById.delete(id);
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Skip si déjà blacklisté
|
|
436
|
-
if (state.badIds && state.badIds.has(id)) {
|
|
437
|
-
state.retryById.delete(id);
|
|
438
|
-
continue;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const tries = (state.retryById.get(id) || 0);
|
|
442
|
-
if (tries >= 3) {
|
|
443
|
-
state.badIds && state.badIds.add(id);
|
|
444
|
-
state.retryById.delete(id);
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const r = safeRect(wrap);
|
|
449
|
-
if (r && (r.top > window.innerHeight + 1200 || r.bottom < -1200)) continue;
|
|
450
|
-
|
|
451
|
-
// Incrémenter le compteur seulement si Ezoic est chargé
|
|
452
|
-
// Sinon on est juste en attente de chargement, pas un vrai échec
|
|
453
|
-
const ezoicReady = window.ezstandalone && typeof window.ezstandalone.showAds === 'function';
|
|
454
|
-
if (ezoicReady) {
|
|
455
|
-
state.retryById.set(id, tries + 1);
|
|
456
|
-
}
|
|
457
|
-
enqueueRetry(id);
|
|
458
|
-
scheduledAny = true;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Ne re-scheduler que si au moins un ID a été enqueued ET qu'il reste des IDs non-blacklistés
|
|
462
|
-
if (scheduledAny) {
|
|
463
|
-
const totalPoolSize = state.poolTopics.length + state.poolPosts.length + state.poolCategories.length +
|
|
464
|
-
state.usedTopics.size + state.usedPosts.size + state.usedCategories.size;
|
|
465
|
-
if (totalPoolSize === 0 || state.badIds.size < totalPoolSize) {
|
|
466
|
-
scheduleRefill(700);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
330
|
+
state.retryTimer = setTimeout(refillUnfilled, delay);
|
|
469
331
|
}
|
|
470
332
|
|
|
471
333
|
function callShowAdsWhenReady(id) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (attempts < 100) setTimeout(waitForPh, 50);
|
|
527
|
-
})();
|
|
334
|
+
if (!id) return;
|
|
335
|
+
|
|
336
|
+
const now = Date.now(), last = state.lastShowById.get(id) || 0;
|
|
337
|
+
if (now - last < 3500) return;
|
|
338
|
+
|
|
339
|
+
const phId = `${PLACEHOLDER_PREFIX}${id}`, doCall = () => {
|
|
340
|
+
try {
|
|
341
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
342
|
+
if (typeof window.ezstandalone.showAds === 'function') {
|
|
343
|
+
state.lastShowById.set(id, Date.now());
|
|
344
|
+
window.ezstandalone.showAds(id);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
} catch (e) {}
|
|
348
|
+
return false;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const startPageKey = state.pageKey;
|
|
352
|
+
let attempts = 0;
|
|
353
|
+
(function waitForPh() {
|
|
354
|
+
// Abort if the user navigated away since this showAds was scheduled
|
|
355
|
+
if (state.pageKey !== startPageKey) return;
|
|
356
|
+
// Abort if another concurrent call is already handling this id
|
|
357
|
+
if (state.pendingById.has(id)) return;
|
|
358
|
+
attempts += 1;
|
|
359
|
+
const el = document.getElementById(phId);
|
|
360
|
+
if (el && el.isConnected) {
|
|
361
|
+
// Si doCall() réussit, Ezoic est chargé et showAds a été appelé → sortir
|
|
362
|
+
if (doCall()) {
|
|
363
|
+
state.pendingById.delete(id); // nettoyage au cas où
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Ezoic n'est pas encore chargé → attendre via cmd queue
|
|
368
|
+
state.pendingById.add(id);
|
|
369
|
+
|
|
370
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
371
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
372
|
+
window.ezstandalone.cmd.push(() => {
|
|
373
|
+
try {
|
|
374
|
+
if (typeof window.ezstandalone.showAds === 'function') {
|
|
375
|
+
state.pendingById.delete(id);
|
|
376
|
+
state.lastShowById.set(id, Date.now());
|
|
377
|
+
window.ezstandalone.showAds(id);
|
|
378
|
+
}
|
|
379
|
+
} catch (e) {}
|
|
380
|
+
});
|
|
381
|
+
// cmd.push suffit - pas besoin de tick() qui crée des doublons
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (attempts < 100) setTimeout(waitForPh, 50);
|
|
386
|
+
})();
|
|
528
387
|
}
|
|
529
388
|
|
|
530
389
|
async function fetchConfig() {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
390
|
+
if (state.cfg) return state.cfg;
|
|
391
|
+
if (state.cfgPromise) return state.cfgPromise;
|
|
392
|
+
|
|
393
|
+
state.cfgPromise = (async () => {
|
|
394
|
+
const MAX_TRIES = 3;
|
|
395
|
+
let delay = 800;
|
|
396
|
+
for (let attempt = 1; attempt <= MAX_TRIES; attempt++) {
|
|
397
|
+
try {
|
|
398
|
+
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
399
|
+
if (res.ok) {
|
|
400
|
+
state.cfg = await res.json();
|
|
401
|
+
return state.cfg;
|
|
402
|
+
}
|
|
403
|
+
} catch (e) {}
|
|
404
|
+
if (attempt < MAX_TRIES) await new Promise(r => setTimeout(r, delay));
|
|
405
|
+
delay *= 2;
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
})();
|
|
409
|
+
|
|
410
|
+
try { return await state.cfgPromise; } finally { state.cfgPromise = null; }
|
|
552
411
|
}
|
|
553
412
|
|
|
554
413
|
function initPools(cfg) {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
414
|
+
if (state.poolTopics.length === 0) state.poolTopics = parsePool(cfg.placeholderIds);
|
|
415
|
+
if (state.poolPosts.length === 0) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
|
|
416
|
+
if (state.poolCategories.length === 0) state.poolCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
558
417
|
}
|
|
559
418
|
|
|
560
419
|
function computeTargets(count, interval, showFirst) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
420
|
+
const out = [];
|
|
421
|
+
if (count <= 0) return out;
|
|
422
|
+
if (showFirst) out.push(1);
|
|
423
|
+
for (let i = 1; i <= count; i++) {
|
|
424
|
+
if (i % interval === 0) out.push(i);
|
|
425
|
+
}
|
|
426
|
+
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
568
427
|
}
|
|
569
428
|
|
|
570
429
|
function injectBetween(kindClass, items, interval, showFirst, kindPool, usedSet) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
430
|
+
if (!items.length) return 0;
|
|
431
|
+
const targets = computeTargets(items.length, interval, showFirst);
|
|
432
|
+
|
|
433
|
+
let inserted = 0;
|
|
434
|
+
for (const afterPos of targets) {
|
|
435
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
436
|
+
|
|
437
|
+
const el = items[afterPos - 1];
|
|
438
|
+
if (!el || !el.isConnected) continue;
|
|
439
|
+
|
|
440
|
+
// Prevent adjacent ads (DOM-based, robust against virtualization)
|
|
441
|
+
if (isAdjacentAd(el) || isPrevAd(el)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Prevent back-to-back at load
|
|
446
|
+
const prevWrap = findWrap(kindClass, afterPos - 1);
|
|
447
|
+
if (prevWrap) continue;
|
|
448
|
+
|
|
449
|
+
if (findWrap(kindClass, afterPos)) continue;
|
|
450
|
+
|
|
451
|
+
const liveArr = (kindClass === 'ezoic-ad-between') ? state.liveBetween
|
|
452
|
+
: (kindClass === 'ezoic-ad-message') ? state.liveMessage
|
|
453
|
+
: state.liveCategory, pick = pickId(kindPool, liveArr);
|
|
454
|
+
const id = pick.id;
|
|
455
|
+
if (!id) break;
|
|
456
|
+
|
|
457
|
+
let wrap = null;
|
|
458
|
+
if (pick.recycled && pick.recycled.wrap) {
|
|
459
|
+
// Only destroy if Ezoic has actually defined this placeholder before
|
|
460
|
+
if (sessionDefinedIds.has(id)) {
|
|
461
|
+
destroyPlaceholderIds([id]);
|
|
462
|
+
}
|
|
463
|
+
// Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
|
|
464
|
+
const oldWrap = pick.recycled.wrap;
|
|
465
|
+
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
466
|
+
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
467
|
+
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
468
|
+
if (!wrap) continue;
|
|
469
|
+
// Attendre que le wrapper soit dans le DOM puis appeler showAds
|
|
470
|
+
setTimeout(() => {
|
|
471
|
+
if (!false) {
|
|
472
|
+
callShowAdsWhenReady(id);
|
|
473
|
+
}
|
|
474
|
+
}, 1500);
|
|
475
|
+
} else {
|
|
476
|
+
usedSet.add(id);
|
|
477
|
+
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
478
|
+
if (!wrap) continue;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
liveArr.push({ id, wrap });
|
|
482
|
+
// If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
|
|
483
|
+
if (wrap && (
|
|
484
|
+
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
485
|
+
)) {
|
|
486
|
+
try { wrap.remove(); } catch (e) {}
|
|
487
|
+
// Put id back if it was newly consumed (not recycled)
|
|
488
|
+
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
489
|
+
try { kindPool.unshift(id); } catch (e) {}
|
|
490
|
+
usedSet.delete(id);
|
|
491
|
+
}
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
495
|
+
// Ne pas appeler showAds si cet ID a déjà été blacklisté
|
|
496
|
+
if (!false) {
|
|
497
|
+
callShowAdsWhenReady(id);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
inserted += 1;
|
|
501
|
+
}
|
|
502
|
+
return inserted;
|
|
643
503
|
}
|
|
644
504
|
|
|
645
505
|
function enforceNoAdjacentAds() {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
506
|
+
const ads = Array.from(document.querySelectorAll(`.${WRAP_CLASS}`));
|
|
507
|
+
for (let i = 0; i < ads.length; i++) {
|
|
508
|
+
const ad = ads[i], prev = ad.previousElementSibling;
|
|
509
|
+
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
510
|
+
// Supprimer le wrapper adjacent au lieu de le cacher
|
|
511
|
+
try {
|
|
512
|
+
const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
513
|
+
if (ph) {
|
|
514
|
+
const id = parseInt(ph.id.replace(PLACEHOLDER_PREFIX, ''), 10);
|
|
515
|
+
if (Number.isFinite(id) && id > 0) {
|
|
516
|
+
// Détruire le placeholder si Ezoic l'a déjà défini
|
|
517
|
+
if (sessionDefinedIds.has(id)) {
|
|
518
|
+
destroyPlaceholderIds([id]);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
ad.remove();
|
|
523
|
+
} catch (e) {}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
667
526
|
}
|
|
668
527
|
|
|
669
528
|
function cleanup() {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
state.poolWaitAttempts = 0;
|
|
697
|
-
|
|
698
|
-
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
699
|
-
|
|
700
|
-
if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; }
|
|
701
|
-
state.scheduled = false;
|
|
702
|
-
clearTimeout(state.timer);
|
|
703
|
-
state.timer = null;
|
|
704
|
-
clearTimeout(state.retryTimer);
|
|
705
|
-
state.retryTimer = null;
|
|
529
|
+
destroyUsedPlaceholders();
|
|
530
|
+
|
|
531
|
+
state.pageKey = getPageKey();
|
|
532
|
+
state.cfg = null;
|
|
533
|
+
state.cfgPromise = null;
|
|
534
|
+
|
|
535
|
+
state.poolTopics = [];
|
|
536
|
+
state.poolPosts = [];
|
|
537
|
+
state.poolCategories = [];
|
|
538
|
+
state.usedTopics.clear();
|
|
539
|
+
state.usedPosts.clear();
|
|
540
|
+
state.usedCategories.clear();
|
|
541
|
+
state.liveBetween = [];
|
|
542
|
+
state.liveMessage = [];
|
|
543
|
+
state.liveCategory = [];
|
|
544
|
+
|
|
545
|
+
state.lastShowById = new Map();
|
|
546
|
+
state.pendingById = new Set();
|
|
547
|
+
state.definedIds = new Set();
|
|
548
|
+
|
|
549
|
+
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
550
|
+
|
|
551
|
+
if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; }
|
|
552
|
+
state.scheduled = false;
|
|
553
|
+
clearTimeout(state.timer);
|
|
554
|
+
state.timer = null;
|
|
706
555
|
}
|
|
707
556
|
|
|
708
557
|
function ensureObserver() {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
558
|
+
if (state.obs) return;
|
|
559
|
+
state.obs = new MutationObserver(() => scheduleRun('mutation'));
|
|
560
|
+
try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
712
561
|
}
|
|
713
562
|
|
|
714
563
|
async function runCore() {
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
} else if (inserted > 0) {
|
|
780
|
-
state.poolWaitAttempts = 0;
|
|
781
|
-
}
|
|
564
|
+
patchShowAds();
|
|
565
|
+
|
|
566
|
+
const cfg = await fetchConfig();
|
|
567
|
+
if (!cfg || cfg.excluded) return;
|
|
568
|
+
|
|
569
|
+
initPools(cfg);
|
|
570
|
+
|
|
571
|
+
const kind = getKind();
|
|
572
|
+
let inserted = 0;
|
|
573
|
+
|
|
574
|
+
if (kind === 'topic') {
|
|
575
|
+
if (normalizeBool(cfg.enableMessageAds)) {
|
|
576
|
+
inserted = injectBetween('ezoic-ad-message', getPostContainers(),
|
|
577
|
+
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
578
|
+
normalizeBool(cfg.showFirstMessageAd),
|
|
579
|
+
state.poolPosts,
|
|
580
|
+
state.usedPosts);
|
|
581
|
+
}
|
|
582
|
+
} else if (kind === 'categoryTopics') {
|
|
583
|
+
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
584
|
+
inserted = injectBetween('ezoic-ad-between', getTopicItems(),
|
|
585
|
+
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
586
|
+
normalizeBool(cfg.showFirstTopicAd),
|
|
587
|
+
state.poolTopics,
|
|
588
|
+
state.usedTopics);
|
|
589
|
+
}
|
|
590
|
+
} else if (kind === 'categories') {
|
|
591
|
+
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
592
|
+
inserted = injectBetween('ezoic-ad-categories', getCategoryItems(),
|
|
593
|
+
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
594
|
+
normalizeBool(cfg.showFirstCategoryAd),
|
|
595
|
+
state.poolCategories,
|
|
596
|
+
state.usedCategories);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
enforceNoAdjacentAds();
|
|
601
|
+
|
|
602
|
+
// If nothing inserted and list isn't in DOM yet (first click), retry a bit
|
|
603
|
+
let count = 0;
|
|
604
|
+
if (kind === 'topic') count = getPostContainers().length;
|
|
605
|
+
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
606
|
+
else if (kind === 'categories') count = getCategoryItems().length;
|
|
607
|
+
|
|
608
|
+
if (count === 0 && 0 < 25) {
|
|
609
|
+
setTimeout(() => scheduleRun('await-items'), 120);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
614
|
+
// Plus d'insertions possibles ce cycle, continuer immédiatement
|
|
615
|
+
setTimeout(() => scheduleRun('continue'), 140);
|
|
616
|
+
} else if (inserted === 0 && count > 0) {
|
|
617
|
+
// Pool épuisé ou recyclage pas encore disponible.
|
|
618
|
+
// Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
|
|
619
|
+
// le temps de défiler hors écran et devenir recyclables.
|
|
620
|
+
if (state.poolWaitAttempts < 8) {
|
|
621
|
+
state.poolWaitAttempts += 1;
|
|
622
|
+
setTimeout(() => scheduleRun('pool-wait'), 400);
|
|
623
|
+
} else {
|
|
624
|
+
}
|
|
625
|
+
} else if (inserted > 0) {
|
|
626
|
+
}
|
|
782
627
|
}
|
|
783
628
|
|
|
784
629
|
function scheduleRun() {
|
|
785
|
-
|
|
786
|
-
|
|
630
|
+
if (state.scheduled) return;
|
|
631
|
+
state.scheduled = true;
|
|
787
632
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
633
|
+
clearTimeout(state.timer);
|
|
634
|
+
state.timer = setTimeout(() => {
|
|
635
|
+
state.scheduled = false;
|
|
636
|
+
const pk = getPageKey();
|
|
637
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
638
|
+
runCore().catch(() => {});
|
|
639
|
+
}, 80);
|
|
795
640
|
}
|
|
796
641
|
|
|
797
642
|
function bind() {
|
|
798
|
-
|
|
643
|
+
if (!$) return;
|
|
799
644
|
|
|
800
|
-
|
|
645
|
+
$(window).off('.ezoicInfinite');
|
|
801
646
|
|
|
802
|
-
|
|
647
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => cleanup());
|
|
803
648
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
649
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
650
|
+
state.pageKey = getPageKey();
|
|
651
|
+
ensureObserver();
|
|
652
|
+
// CRITIQUE : Après navigation ajaxify, Ezoic est déjà chargé mais ne sait pas
|
|
653
|
+
// qu'on va insérer de nouveaux placeholders. Il faut le forcer à re-scanner.
|
|
654
|
+
try {
|
|
655
|
+
if (window.ezstandalone && typeof window.ezstandalone.enable === 'function') {
|
|
656
|
+
// enable() force Ezoic à re-scanner toute la page
|
|
657
|
+
window.ezstandalone.enable();
|
|
658
|
+
}
|
|
659
|
+
} catch (e) {
|
|
660
|
+
|
|
661
|
+
}
|
|
662
|
+
// Attendre que les nouveaux placeholders soient dans le DOM
|
|
663
|
+
setTimeout(scheduleRun, 500);
|
|
664
|
+
setTimeout(scheduleRun, 1000);
|
|
665
|
+
});
|
|
811
666
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
667
|
+
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
668
|
+
ensureObserver();
|
|
669
|
+
// category.loaded = infinite scroll, Ezoic déjà chargé normalement
|
|
670
|
+
setTimeout(scheduleRun, 300);
|
|
671
|
+
});
|
|
817
672
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
673
|
+
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
674
|
+
ensureObserver();
|
|
675
|
+
setTimeout(scheduleRun, 300);
|
|
676
|
+
});
|
|
822
677
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
678
|
+
$(window).on('action:topic.loaded.ezoicInfinite', () => {
|
|
679
|
+
ensureObserver();
|
|
680
|
+
setTimeout(scheduleRun, 300);
|
|
681
|
+
});
|
|
827
682
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
683
|
+
$(window).on('action:posts.loaded.ezoicInfinite', () => {
|
|
684
|
+
ensureObserver();
|
|
685
|
+
// posts.loaded = infinite scroll
|
|
686
|
+
setTimeout(scheduleRun, 400);
|
|
687
|
+
});
|
|
833
688
|
}
|
|
834
689
|
|
|
835
690
|
function bindScroll() {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
691
|
+
if (state.__scrollBound) return;
|
|
692
|
+
state.__scrollBound = true;
|
|
693
|
+
let ticking = false;
|
|
694
|
+
window.addEventListener('scroll', () => {
|
|
695
|
+
if (ticking) return;
|
|
696
|
+
ticking = true;
|
|
697
|
+
window.requestAnimationFrame(() => {
|
|
698
|
+
ticking = false;
|
|
699
|
+
enforceNoAdjacentAds();
|
|
700
|
+
// Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
|
|
701
|
+
const now = Date.now();
|
|
702
|
+
if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
|
|
703
|
+
state.lastScrollRun = now;
|
|
704
|
+
scheduleRun();
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}, { passive: true });
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Fonction qui attend que Ezoic soit vraiment chargé
|
|
711
|
+
function waitForEzoicThenRun() {
|
|
712
|
+
let attempts = 0;
|
|
713
|
+
const maxAttempts = 50; // 50 × 200ms = 10s max
|
|
714
|
+
|
|
715
|
+
(function check() {
|
|
716
|
+
attempts++;
|
|
717
|
+
// Vérifier si Ezoic est chargé
|
|
718
|
+
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
719
|
+
// Diagnostic : afficher les placeholders disponibles
|
|
720
|
+
try {
|
|
721
|
+
if (window.ezstandalone.placeholders) {
|
|
722
|
+
}
|
|
723
|
+
} catch (e) {}
|
|
724
|
+
// Ezoic est prêt → lancer l'insertion
|
|
725
|
+
scheduleRun();
|
|
726
|
+
setTimeout(scheduleRun, 300);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
// Ezoic pas encore prêt
|
|
730
|
+
if (attempts >= maxAttempts) {
|
|
731
|
+
// Tenter quand même
|
|
732
|
+
scheduleRun();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
// Réessayer dans 200ms
|
|
736
|
+
setTimeout(check, 200);
|
|
737
|
+
})();
|
|
852
738
|
}
|
|
853
739
|
|
|
854
740
|
cleanup();
|
|
@@ -856,7 +742,7 @@
|
|
|
856
742
|
bindScroll();
|
|
857
743
|
ensureObserver();
|
|
858
744
|
state.pageKey = getPageKey();
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
745
|
+
|
|
746
|
+
// Attendre que Ezoic soit chargé avant d'insérer
|
|
747
|
+
waitForEzoicThenRun();
|
|
862
748
|
})();
|