nodebb-plugin-ezoic-infinite 1.4.23 → 1.4.25
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 +607 -725
package/public/client.js
CHANGED
|
@@ -4,851 +4,733 @@
|
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function scheduleRefill(delay = 350) {
|
|
354
|
-
clearTimeout(state.retryTimer);
|
|
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
|
-
}
|
|
315
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
316
|
+
if (!ph || !ph.isConnected) return false;
|
|
317
|
+
|
|
318
|
+
const wrap = ph.parentElement;
|
|
319
|
+
if (wrap && isWrapMarkedFilled(wrap)) return true;
|
|
320
|
+
|
|
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;
|
|
469
327
|
}
|
|
470
328
|
|
|
471
329
|
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
|
-
})();
|
|
330
|
+
if (!id) return;
|
|
331
|
+
|
|
332
|
+
const now = Date.now(), last = state.lastShowById.get(id) || 0;
|
|
333
|
+
if (now - last < 3500) return;
|
|
334
|
+
|
|
335
|
+
const phId = `${PLACEHOLDER_PREFIX}${id}`, doCall = () => {
|
|
336
|
+
try {
|
|
337
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
338
|
+
if (typeof window.ezstandalone.showAds === 'function') {
|
|
339
|
+
state.lastShowById.set(id, Date.now());
|
|
340
|
+
window.ezstandalone.showAds(id);
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
} catch (e) {}
|
|
344
|
+
return false;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const startPageKey = state.pageKey;
|
|
348
|
+
let attempts = 0;
|
|
349
|
+
(function waitForPh() {
|
|
350
|
+
// Abort if the user navigated away since this showAds was scheduled
|
|
351
|
+
if (state.pageKey !== startPageKey) return;
|
|
352
|
+
// Abort if another concurrent call is already handling this id
|
|
353
|
+
if (state.pendingById.has(id)) return;
|
|
354
|
+
attempts += 1;
|
|
355
|
+
const el = document.getElementById(phId);
|
|
356
|
+
if (el && el.isConnected) {
|
|
357
|
+
// Si doCall() réussit, Ezoic est chargé et showAds a été appelé → sortir
|
|
358
|
+
if (doCall()) {
|
|
359
|
+
state.pendingById.delete(id); // nettoyage au cas où
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Ezoic n'est pas encore chargé → attendre via cmd queue
|
|
364
|
+
state.pendingById.add(id);
|
|
365
|
+
|
|
366
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
367
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
368
|
+
window.ezstandalone.cmd.push(() => {
|
|
369
|
+
try {
|
|
370
|
+
if (typeof window.ezstandalone.showAds === 'function') {
|
|
371
|
+
state.pendingById.delete(id);
|
|
372
|
+
state.lastShowById.set(id, Date.now());
|
|
373
|
+
window.ezstandalone.showAds(id);
|
|
374
|
+
}
|
|
375
|
+
} catch (e) {}
|
|
376
|
+
});
|
|
377
|
+
// cmd.push suffit - pas besoin de tick() qui crée des doublons
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (attempts < 100) setTimeout(waitForPh, 50);
|
|
382
|
+
})();
|
|
528
383
|
}
|
|
529
384
|
|
|
530
385
|
async function fetchConfig() {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
386
|
+
if (state.cfg) return state.cfg;
|
|
387
|
+
if (state.cfgPromise) return state.cfgPromise;
|
|
388
|
+
|
|
389
|
+
state.cfgPromise = (async () => {
|
|
390
|
+
const MAX_TRIES = 3;
|
|
391
|
+
let delay = 800;
|
|
392
|
+
for (let attempt = 1; attempt <= MAX_TRIES; attempt++) {
|
|
393
|
+
try {
|
|
394
|
+
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
395
|
+
if (res.ok) {
|
|
396
|
+
state.cfg = await res.json();
|
|
397
|
+
return state.cfg;
|
|
398
|
+
}
|
|
399
|
+
} catch (e) {}
|
|
400
|
+
if (attempt < MAX_TRIES) await new Promise(r => setTimeout(r, delay));
|
|
401
|
+
delay *= 2;
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
})();
|
|
405
|
+
|
|
406
|
+
try { return await state.cfgPromise; } finally { state.cfgPromise = null; }
|
|
552
407
|
}
|
|
553
408
|
|
|
554
409
|
function initPools(cfg) {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
410
|
+
if (state.poolTopics.length === 0) state.poolTopics = parsePool(cfg.placeholderIds);
|
|
411
|
+
if (state.poolPosts.length === 0) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
|
|
412
|
+
if (state.poolCategories.length === 0) state.poolCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
558
413
|
}
|
|
559
414
|
|
|
560
415
|
function computeTargets(count, interval, showFirst) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
416
|
+
const out = [];
|
|
417
|
+
if (count <= 0) return out;
|
|
418
|
+
if (showFirst) out.push(1);
|
|
419
|
+
for (let i = 1; i <= count; i++) {
|
|
420
|
+
if (i % interval === 0) out.push(i);
|
|
421
|
+
}
|
|
422
|
+
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
568
423
|
}
|
|
569
424
|
|
|
570
425
|
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
|
-
|
|
426
|
+
if (!items.length) return 0;
|
|
427
|
+
const targets = computeTargets(items.length, interval, showFirst);
|
|
428
|
+
|
|
429
|
+
let inserted = 0;
|
|
430
|
+
for (const afterPos of targets) {
|
|
431
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
432
|
+
|
|
433
|
+
const el = items[afterPos - 1];
|
|
434
|
+
if (!el || !el.isConnected) continue;
|
|
435
|
+
|
|
436
|
+
// Prevent adjacent ads (DOM-based, robust against virtualization)
|
|
437
|
+
if (isAdjacentAd(el) || isPrevAd(el)) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Prevent back-to-back at load
|
|
442
|
+
const prevWrap = findWrap(kindClass, afterPos - 1);
|
|
443
|
+
if (prevWrap) continue;
|
|
444
|
+
|
|
445
|
+
if (findWrap(kindClass, afterPos)) continue;
|
|
446
|
+
|
|
447
|
+
const liveArr = (kindClass === 'ezoic-ad-between') ? state.liveBetween
|
|
448
|
+
: (kindClass === 'ezoic-ad-message') ? state.liveMessage
|
|
449
|
+
: state.liveCategory, pick = pickId(kindPool, liveArr);
|
|
450
|
+
const id = pick.id;
|
|
451
|
+
if (!id) break;
|
|
452
|
+
|
|
453
|
+
let wrap = null;
|
|
454
|
+
if (pick.recycled && pick.recycled.wrap) {
|
|
455
|
+
// Only destroy if Ezoic has actually defined this placeholder before
|
|
456
|
+
if (sessionDefinedIds.has(id)) {
|
|
457
|
+
destroyPlaceholderIds([id]);
|
|
458
|
+
}
|
|
459
|
+
// Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
|
|
460
|
+
const oldWrap = pick.recycled.wrap;
|
|
461
|
+
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
462
|
+
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
463
|
+
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
464
|
+
if (!wrap) continue;
|
|
465
|
+
// Attendre que le wrapper soit dans le DOM puis appeler showAds
|
|
466
|
+
setTimeout(() => {
|
|
467
|
+
if (!false) {
|
|
468
|
+
callShowAdsWhenReady(id);
|
|
469
|
+
}
|
|
470
|
+
}, 1500);
|
|
471
|
+
} else {
|
|
472
|
+
usedSet.add(id);
|
|
473
|
+
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
474
|
+
if (!wrap) continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
liveArr.push({ id, wrap });
|
|
478
|
+
// If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
|
|
479
|
+
if (wrap && (
|
|
480
|
+
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
481
|
+
)) {
|
|
482
|
+
try { wrap.remove(); } catch (e) {}
|
|
483
|
+
// Put id back if it was newly consumed (not recycled)
|
|
484
|
+
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
485
|
+
try { kindPool.unshift(id); } catch (e) {}
|
|
486
|
+
usedSet.delete(id);
|
|
487
|
+
}
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
491
|
+
// Ne pas appeler showAds si cet ID a déjà été blacklisté
|
|
492
|
+
if (!false) {
|
|
493
|
+
callShowAdsWhenReady(id);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
inserted += 1;
|
|
497
|
+
}
|
|
498
|
+
return inserted;
|
|
643
499
|
}
|
|
644
500
|
|
|
645
501
|
function enforceNoAdjacentAds() {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
502
|
+
const ads = Array.from(document.querySelectorAll(`.${WRAP_CLASS}`));
|
|
503
|
+
for (let i = 0; i < ads.length; i++) {
|
|
504
|
+
const ad = ads[i], prev = ad.previousElementSibling;
|
|
505
|
+
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
506
|
+
// Supprimer le wrapper adjacent au lieu de le cacher
|
|
507
|
+
try {
|
|
508
|
+
const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
|
|
509
|
+
if (ph) {
|
|
510
|
+
const id = parseInt(ph.id.replace(PLACEHOLDER_PREFIX, ''), 10);
|
|
511
|
+
if (Number.isFinite(id) && id > 0) {
|
|
512
|
+
// Détruire le placeholder si Ezoic l'a déjà défini
|
|
513
|
+
if (sessionDefinedIds.has(id)) {
|
|
514
|
+
destroyPlaceholderIds([id]);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
ad.remove();
|
|
519
|
+
} catch (e) {}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
667
522
|
}
|
|
668
523
|
|
|
669
524
|
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;
|
|
525
|
+
destroyUsedPlaceholders();
|
|
526
|
+
|
|
527
|
+
state.pageKey = getPageKey();
|
|
528
|
+
state.cfg = null;
|
|
529
|
+
state.cfgPromise = null;
|
|
530
|
+
|
|
531
|
+
state.poolTopics = [];
|
|
532
|
+
state.poolPosts = [];
|
|
533
|
+
state.poolCategories = [];
|
|
534
|
+
state.usedTopics.clear();
|
|
535
|
+
state.usedPosts.clear();
|
|
536
|
+
state.usedCategories.clear();
|
|
537
|
+
state.liveBetween = [];
|
|
538
|
+
state.liveMessage = [];
|
|
539
|
+
state.liveCategory = [];
|
|
540
|
+
|
|
541
|
+
state.lastShowById = new Map();
|
|
542
|
+
state.pendingById = new Set();
|
|
543
|
+
state.definedIds = new Set();
|
|
544
|
+
|
|
545
|
+
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
546
|
+
|
|
547
|
+
if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; }
|
|
548
|
+
state.scheduled = false;
|
|
549
|
+
clearTimeout(state.timer);
|
|
550
|
+
state.timer = null;
|
|
706
551
|
}
|
|
707
552
|
|
|
708
553
|
function ensureObserver() {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
554
|
+
if (state.obs) return;
|
|
555
|
+
state.obs = new MutationObserver(() => scheduleRun('mutation'));
|
|
556
|
+
try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
712
557
|
}
|
|
713
558
|
|
|
714
559
|
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
|
-
}
|
|
560
|
+
patchShowAds();
|
|
561
|
+
|
|
562
|
+
const cfg = await fetchConfig();
|
|
563
|
+
if (!cfg || cfg.excluded) return;
|
|
564
|
+
|
|
565
|
+
initPools(cfg);
|
|
566
|
+
|
|
567
|
+
const kind = getKind();
|
|
568
|
+
let inserted = 0;
|
|
569
|
+
|
|
570
|
+
if (kind === 'topic') {
|
|
571
|
+
if (normalizeBool(cfg.enableMessageAds)) {
|
|
572
|
+
inserted = injectBetween('ezoic-ad-message', getPostContainers(),
|
|
573
|
+
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
574
|
+
normalizeBool(cfg.showFirstMessageAd),
|
|
575
|
+
state.poolPosts,
|
|
576
|
+
state.usedPosts);
|
|
577
|
+
}
|
|
578
|
+
} else if (kind === 'categoryTopics') {
|
|
579
|
+
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
580
|
+
inserted = injectBetween('ezoic-ad-between', getTopicItems(),
|
|
581
|
+
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
582
|
+
normalizeBool(cfg.showFirstTopicAd),
|
|
583
|
+
state.poolTopics,
|
|
584
|
+
state.usedTopics);
|
|
585
|
+
}
|
|
586
|
+
} else if (kind === 'categories') {
|
|
587
|
+
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
588
|
+
inserted = injectBetween('ezoic-ad-categories', getCategoryItems(),
|
|
589
|
+
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
590
|
+
normalizeBool(cfg.showFirstCategoryAd),
|
|
591
|
+
state.poolCategories,
|
|
592
|
+
state.usedCategories);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
enforceNoAdjacentAds();
|
|
597
|
+
|
|
598
|
+
// If nothing inserted and list isn't in DOM yet (first click), retry a bit
|
|
599
|
+
let count = 0;
|
|
600
|
+
if (kind === 'topic') count = getPostContainers().length;
|
|
601
|
+
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
602
|
+
else if (kind === 'categories') count = getCategoryItems().length;
|
|
603
|
+
|
|
604
|
+
if (count === 0 && 0 < 25) {
|
|
605
|
+
setTimeout(() => scheduleRun('await-items'), 120);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
610
|
+
// Plus d'insertions possibles ce cycle, continuer immédiatement
|
|
611
|
+
setTimeout(() => scheduleRun('continue'), 140);
|
|
612
|
+
} else if (inserted === 0 && count > 0) {
|
|
613
|
+
// Pool épuisé ou recyclage pas encore disponible.
|
|
614
|
+
// Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
|
|
615
|
+
// le temps de défiler hors écran et devenir recyclables.
|
|
616
|
+
if (state.poolWaitAttempts < 8) {
|
|
617
|
+
state.poolWaitAttempts += 1;
|
|
618
|
+
setTimeout(() => scheduleRun('pool-wait'), 400);
|
|
619
|
+
} else {
|
|
620
|
+
}
|
|
621
|
+
} else if (inserted > 0) {
|
|
622
|
+
}
|
|
782
623
|
}
|
|
783
624
|
|
|
784
625
|
function scheduleRun() {
|
|
785
|
-
|
|
786
|
-
|
|
626
|
+
if (state.scheduled) return;
|
|
627
|
+
state.scheduled = true;
|
|
787
628
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
629
|
+
clearTimeout(state.timer);
|
|
630
|
+
state.timer = setTimeout(() => {
|
|
631
|
+
state.scheduled = false;
|
|
632
|
+
const pk = getPageKey();
|
|
633
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
634
|
+
runCore().catch(() => {});
|
|
635
|
+
}, 80);
|
|
795
636
|
}
|
|
796
637
|
|
|
797
638
|
function bind() {
|
|
798
|
-
|
|
639
|
+
if (!$) return;
|
|
799
640
|
|
|
800
|
-
|
|
641
|
+
$(window).off('.ezoicInfinite');
|
|
801
642
|
|
|
802
|
-
|
|
643
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => cleanup());
|
|
803
644
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
645
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
646
|
+
state.pageKey = getPageKey();
|
|
647
|
+
ensureObserver();
|
|
648
|
+
// CRITIQUE : Après navigation ajaxify, Ezoic est déjà chargé mais ne sait pas
|
|
649
|
+
// qu'on va insérer de nouveaux placeholders. Il faut le forcer à re-scanner.
|
|
650
|
+
try {
|
|
651
|
+
if (window.ezstandalone && typeof window.ezstandalone.enable === 'function') {
|
|
652
|
+
// enable() force Ezoic à re-scanner toute la page
|
|
653
|
+
window.ezstandalone.enable();
|
|
654
|
+
}
|
|
655
|
+
} catch (e) {
|
|
656
|
+
|
|
657
|
+
}
|
|
658
|
+
// Attendre que les nouveaux placeholders soient dans le DOM
|
|
659
|
+
setTimeout(scheduleRun, 500);
|
|
660
|
+
setTimeout(scheduleRun, 1000);
|
|
661
|
+
});
|
|
811
662
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
663
|
+
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
664
|
+
ensureObserver();
|
|
665
|
+
// category.loaded = infinite scroll, Ezoic déjà chargé normalement
|
|
666
|
+
setTimeout(scheduleRun, 300);
|
|
667
|
+
});
|
|
817
668
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
669
|
+
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
670
|
+
ensureObserver();
|
|
671
|
+
setTimeout(scheduleRun, 300);
|
|
672
|
+
});
|
|
822
673
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
674
|
+
$(window).on('action:topic.loaded.ezoicInfinite', () => {
|
|
675
|
+
ensureObserver();
|
|
676
|
+
setTimeout(scheduleRun, 300);
|
|
677
|
+
});
|
|
827
678
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
679
|
+
$(window).on('action:posts.loaded.ezoicInfinite', () => {
|
|
680
|
+
ensureObserver();
|
|
681
|
+
// posts.loaded = infinite scroll
|
|
682
|
+
setTimeout(scheduleRun, 400);
|
|
683
|
+
});
|
|
833
684
|
}
|
|
834
685
|
|
|
835
686
|
function bindScroll() {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
687
|
+
if (state.__scrollBound) return;
|
|
688
|
+
state.__scrollBound = true;
|
|
689
|
+
let ticking = false;
|
|
690
|
+
window.addEventListener('scroll', () => {
|
|
691
|
+
if (ticking) return;
|
|
692
|
+
ticking = true;
|
|
693
|
+
window.requestAnimationFrame(() => {
|
|
694
|
+
ticking = false;
|
|
695
|
+
enforceNoAdjacentAds();
|
|
696
|
+
// Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
|
|
697
|
+
const now = Date.now();
|
|
698
|
+
if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
|
|
699
|
+
state.lastScrollRun = now;
|
|
700
|
+
scheduleRun();
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
}, { passive: true });
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Fonction qui attend que Ezoic soit vraiment chargé
|
|
707
|
+
function waitForEzoicThenRun() {
|
|
708
|
+
let attempts = 0;
|
|
709
|
+
const maxAttempts = 50; // 50 × 200ms = 10s max
|
|
710
|
+
|
|
711
|
+
(function check() {
|
|
712
|
+
attempts++;
|
|
713
|
+
// Vérifier si Ezoic est chargé
|
|
714
|
+
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
715
|
+
// Diagnostic : afficher les placeholders disponibles
|
|
716
|
+
try {
|
|
717
|
+
if (window.ezstandalone.placeholders) {
|
|
718
|
+
}
|
|
719
|
+
} catch (e) {}
|
|
720
|
+
// Ezoic est prêt → lancer l'insertion
|
|
721
|
+
scheduleRun();
|
|
722
|
+
setTimeout(scheduleRun, 300);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
// Ezoic pas encore prêt
|
|
726
|
+
if (attempts >= maxAttempts) {
|
|
727
|
+
// Tenter quand même
|
|
728
|
+
scheduleRun();
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
// Réessayer dans 200ms
|
|
732
|
+
setTimeout(check, 200);
|
|
733
|
+
})();
|
|
852
734
|
}
|
|
853
735
|
|
|
854
736
|
cleanup();
|
|
@@ -856,7 +738,7 @@
|
|
|
856
738
|
bindScroll();
|
|
857
739
|
ensureObserver();
|
|
858
740
|
state.pageKey = getPageKey();
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
741
|
+
|
|
742
|
+
// Attendre que Ezoic soit chargé avant d'insérer
|
|
743
|
+
waitForEzoicThenRun();
|
|
862
744
|
})();
|