nodebb-plugin-ezoic-infinite 1.5.18 → 1.5.21
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/library.js +23 -5
- package/package.json +1 -1
- package/public/client.js +706 -682
- package/public/style.css +21 -3
package/public/client.js
CHANGED
|
@@ -1,827 +1,851 @@
|
|
|
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
3
|
|
|
11
|
-
|
|
12
|
-
const sessionDefinedIds = new Set();
|
|
4
|
+
var $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
13
5
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
var SELECTORS = {
|
|
7
|
+
topicItem: 'li[component="category/topic"]',
|
|
8
|
+
postItem: '[component="post"][data-pid]',
|
|
9
|
+
categoryItem: 'li[component="categories/category"]'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
var WRAP_CLASS = 'ezoic-ad';
|
|
13
|
+
var PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
14
|
+
|
|
15
|
+
// Hard limits to avoid runaway insertion during mutations / infinite scroll.
|
|
16
|
+
var MAX_INSERTS_PER_RUN = 3;
|
|
17
|
+
|
|
18
|
+
// Placeholders that have been "defined" (filled) at least once in this browser session.
|
|
19
|
+
// This survives ajaxify navigations, which is important for safe recycle/destroy logic.
|
|
20
|
+
var sessionDefinedIds = new Set();
|
|
21
|
+
|
|
22
|
+
// Prevent re-entrant insertion of the same id while Ezoic is processing it.
|
|
23
|
+
var insertingIds = new Set();
|
|
24
|
+
|
|
25
|
+
var state = {
|
|
26
|
+
pageKey: null,
|
|
27
|
+
|
|
28
|
+
cfg: null,
|
|
29
|
+
cfgPromise: null,
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
poolTopics: [],
|
|
32
|
+
poolPosts: [],
|
|
33
|
+
poolCategories: [],
|
|
22
34
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
usedTopics: new Set(),
|
|
36
|
+
usedPosts: new Set(),
|
|
37
|
+
usedCategories: new Set(),
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
// Track wrappers that are still in the DOM to recycle ids once they are far above viewport.
|
|
40
|
+
liveTopics: [],
|
|
41
|
+
livePosts: [],
|
|
42
|
+
liveCategories: [],
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
// Throttle showAds calls per id.
|
|
45
|
+
lastShowById: new Map(),
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
// Ids for which we scheduled/attempted showAds and should not schedule again immediately.
|
|
48
|
+
pendingById: new Set(),
|
|
49
|
+
|
|
50
|
+
// Timeouts created by this script (so we can cancel on ajaxify.start).
|
|
51
|
+
activeTimeouts: new Set(),
|
|
52
|
+
|
|
53
|
+
// Run scheduling / mutation observer.
|
|
54
|
+
scheduled: false,
|
|
55
|
+
timer: null,
|
|
56
|
+
obs: null,
|
|
57
|
+
|
|
58
|
+
// Scroll throttling.
|
|
59
|
+
lastScrollRun: 0,
|
|
60
|
+
|
|
61
|
+
// Navigation safety gate: we only insert after ajaxify.end settles.
|
|
62
|
+
canShowAds: false,
|
|
63
|
+
|
|
64
|
+
// Retry counters.
|
|
65
|
+
poolWaitAttempts: 0,
|
|
66
|
+
awaitItemsAttempts: 0
|
|
67
|
+
};
|
|
37
68
|
|
|
38
69
|
function normalizeBool(v) {
|
|
39
|
-
|
|
70
|
+
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
40
71
|
}
|
|
41
72
|
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
47
|
-
seen.add(n);
|
|
48
|
-
out.push(n);
|
|
73
|
+
function setTimeoutTracked(fn, ms) {
|
|
74
|
+
var id = setTimeout(fn, ms);
|
|
75
|
+
state.activeTimeouts.add(id);
|
|
76
|
+
return id;
|
|
49
77
|
}
|
|
78
|
+
|
|
79
|
+
function clearAllTrackedTimeouts() {
|
|
80
|
+
state.activeTimeouts.forEach(function (id) {
|
|
81
|
+
try { clearTimeout(id); } catch (e) {}
|
|
82
|
+
});
|
|
83
|
+
state.activeTimeouts.clear();
|
|
50
84
|
}
|
|
51
|
-
|
|
85
|
+
|
|
86
|
+
function uniqInts(lines) {
|
|
87
|
+
var out = [];
|
|
88
|
+
var seen = new Set();
|
|
89
|
+
for (var i = 0; i < lines.length; i++) {
|
|
90
|
+
var n = parseInt(lines[i], 10);
|
|
91
|
+
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
92
|
+
seen.add(n);
|
|
93
|
+
out.push(n);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
52
97
|
}
|
|
53
98
|
|
|
54
99
|
function parsePool(raw) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
100
|
+
if (!raw) return [];
|
|
101
|
+
var lines = String(raw)
|
|
102
|
+
.split(/\r?\n/)
|
|
103
|
+
.map(function (s) { return s.trim(); })
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
return uniqInts(lines);
|
|
58
106
|
}
|
|
59
107
|
|
|
60
108
|
function getPageKey() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
109
|
+
try {
|
|
110
|
+
var ax = window.ajaxify;
|
|
111
|
+
if (ax && ax.data) {
|
|
112
|
+
if (ax.data.tid) return 'topic:' + ax.data.tid;
|
|
113
|
+
if (ax.data.cid) return 'cid:' + ax.data.cid + ':' + window.location.pathname;
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {}
|
|
116
|
+
return window.location.pathname;
|
|
69
117
|
}
|
|
70
118
|
|
|
71
119
|
function getKind() {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
120
|
+
var p = window.location.pathname || '';
|
|
121
|
+
if (/^\/topic\//.test(p)) return 'topic';
|
|
122
|
+
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
123
|
+
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
124
|
+
|
|
125
|
+
// Fallback by DOM.
|
|
126
|
+
if (document.querySelector(SELECTORS.categoryItem)) return 'categories';
|
|
127
|
+
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
128
|
+
if (document.querySelector(SELECTORS.topicItem)) return 'categoryTopics';
|
|
129
|
+
return 'other';
|
|
81
130
|
}
|
|
82
131
|
|
|
83
132
|
function getTopicItems() {
|
|
84
|
-
|
|
133
|
+
return Array.from(document.querySelectorAll(SELECTORS.topicItem));
|
|
85
134
|
}
|
|
86
135
|
|
|
87
136
|
function getCategoryItems() {
|
|
88
|
-
|
|
137
|
+
return Array.from(document.querySelectorAll(SELECTORS.categoryItem));
|
|
89
138
|
}
|
|
90
139
|
|
|
91
140
|
function getPostContainers() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
141
|
+
var nodes = Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
142
|
+
return nodes.filter(function (el) {
|
|
143
|
+
if (!el || !el.isConnected) return false;
|
|
144
|
+
if (!el.querySelector('[component="post/content"]')) return false;
|
|
145
|
+
|
|
146
|
+
// Prevent nested / duplicated post wrappers.
|
|
147
|
+
var parentPost = el.parentElement && el.parentElement.closest('[component="post"][data-pid]');
|
|
148
|
+
if (parentPost && parentPost !== el) return false;
|
|
149
|
+
if (el.getAttribute('component') === 'post/parent') return false;
|
|
150
|
+
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
101
153
|
}
|
|
102
154
|
|
|
103
155
|
function safeRect(el) {
|
|
104
|
-
|
|
156
|
+
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
105
157
|
}
|
|
106
158
|
|
|
107
159
|
function destroyPlaceholderIds(ids) {
|
|
108
|
-
|
|
109
|
-
// Only destroy ids that were actually "defined" (filled at least once) to avoid Ezoic warnings.
|
|
110
|
-
const filtered = ids.filter((id) => {
|
|
111
|
-
// Utiliser sessionDefinedIds (survit aux navigations) plutôt que state.definedIds
|
|
112
|
-
try { return sessionDefinedIds.has(id); } catch (e) { return true; }
|
|
113
|
-
});
|
|
114
|
-
if (!filtered.length) return;
|
|
115
|
-
|
|
116
|
-
const call = () => {
|
|
117
|
-
try {
|
|
118
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
119
|
-
window.ezstandalone.destroyPlaceholders(filtered);
|
|
120
|
-
}
|
|
121
|
-
} catch (e) {}
|
|
122
|
-
};
|
|
123
|
-
try {
|
|
124
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
125
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
126
|
-
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
127
|
-
else window.ezstandalone.cmd.push(call);
|
|
128
|
-
} catch (e) {}
|
|
129
|
-
}
|
|
160
|
+
if (!ids || !ids.length) return;
|
|
130
161
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
162
|
+
// Only destroy ids that were actually "defined" (filled at least once) to avoid Ezoic warnings.
|
|
163
|
+
var filtered = ids.filter(function (id) {
|
|
164
|
+
try { return sessionDefinedIds.has(id); } catch (e) { return true; }
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (!filtered.length) return;
|
|
168
|
+
|
|
169
|
+
var call = function () {
|
|
170
|
+
try {
|
|
171
|
+
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
172
|
+
window.ezstandalone.destroyPlaceholders(filtered);
|
|
173
|
+
}
|
|
174
|
+
} catch (e) {}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
179
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
180
|
+
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
|
|
181
|
+
else window.ezstandalone.cmd.push(call);
|
|
182
|
+
} catch (e) {}
|
|
147
183
|
}
|
|
148
184
|
|
|
149
185
|
function getRecyclable(liveArr) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
186
|
+
var margin = 600; // px above viewport
|
|
187
|
+
for (var i = 0; i < liveArr.length; i++) {
|
|
188
|
+
var entry = liveArr[i];
|
|
189
|
+
if (!entry || !entry.wrap || !entry.wrap.isConnected) {
|
|
190
|
+
liveArr.splice(i, 1);
|
|
191
|
+
i--;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
var r = safeRect(entry.wrap);
|
|
195
|
+
if (r && r.bottom < -margin) {
|
|
196
|
+
liveArr.splice(i, 1);
|
|
197
|
+
return entry;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
161
201
|
}
|
|
162
202
|
|
|
163
203
|
function pickId(pool, liveArr) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
204
|
+
if (pool.length) return { id: pool.shift(), recycled: null };
|
|
205
|
+
|
|
206
|
+
var recycled = getRecyclable(liveArr);
|
|
207
|
+
if (recycled) return { id: recycled.id, recycled: recycled };
|
|
208
|
+
|
|
209
|
+
return { id: null, recycled: null };
|
|
168
210
|
}
|
|
169
211
|
|
|
170
212
|
function resetPlaceholderInWrap(wrap, id) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
wrap.querySelectorAll && wrap.querySelectorAll('iframe, ins').forEach(n => n.remove());
|
|
179
|
-
const ph = document.createElement('div');
|
|
180
|
-
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
181
|
-
wrap.appendChild(ph);
|
|
182
|
-
} catch (e) {}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function isAdjacentAd(target) {
|
|
186
|
-
if (!target || !target.nextElementSibling) return false;
|
|
187
|
-
const next = target.nextElementSibling;
|
|
188
|
-
if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function isPrevAd(target) {
|
|
193
|
-
if (!target || !target.previousElementSibling) return false;
|
|
194
|
-
const prev = target.previousElementSibling;
|
|
195
|
-
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function buildWrap(id, kindClass, afterPos) {
|
|
200
|
-
const wrap = document.createElement('div');
|
|
201
|
-
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
202
|
-
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
203
|
-
wrap.style.width = '100%';
|
|
204
|
-
|
|
205
|
-
const ph = document.createElement('div');
|
|
206
|
-
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
207
|
-
wrap.appendChild(ph);
|
|
208
|
-
return wrap;
|
|
213
|
+
if (!wrap) return null;
|
|
214
|
+
try { wrap.innerHTML = ''; } catch (e) {}
|
|
215
|
+
|
|
216
|
+
var ph = document.createElement('div');
|
|
217
|
+
ph.id = PLACEHOLDER_PREFIX + id;
|
|
218
|
+
wrap.appendChild(ph);
|
|
219
|
+
return ph;
|
|
209
220
|
}
|
|
210
221
|
|
|
211
|
-
function
|
|
212
|
-
|
|
222
|
+
function isAdjacentAd(el) {
|
|
223
|
+
var next = el && el.nextElementSibling;
|
|
224
|
+
return !!(next && next.classList && next.classList.contains(WRAP_CLASS));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isPrevAd(el) {
|
|
228
|
+
var prev = el && el.previousElementSibling;
|
|
229
|
+
return !!(prev && prev.classList && prev.classList.contains(WRAP_CLASS));
|
|
213
230
|
}
|
|
214
231
|
|
|
215
|
-
function
|
|
216
|
-
|
|
217
|
-
|
|
232
|
+
function buildWrap(id, kindClass) {
|
|
233
|
+
var wrap = document.createElement('div');
|
|
234
|
+
wrap.className = WRAP_CLASS + ' ' + kindClass;
|
|
235
|
+
wrap.setAttribute('data-ezoic-id', String(id));
|
|
236
|
+
resetPlaceholderInWrap(wrap, id);
|
|
237
|
+
return wrap;
|
|
238
|
+
}
|
|
218
239
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
240
|
+
function findWrap(kindClass, afterPos) {
|
|
241
|
+
// Search a wrapper marker that we set on insertion.
|
|
242
|
+
return document.querySelector('.' + kindClass + '[data-after-pos="' + afterPos + '"]');
|
|
243
|
+
}
|
|
222
244
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
245
|
+
function insertAfter(el, id, kindClass, afterPos) {
|
|
246
|
+
try {
|
|
247
|
+
var wrap = buildWrap(id, kindClass);
|
|
248
|
+
wrap.setAttribute('data-after-pos', String(afterPos));
|
|
226
249
|
|
|
227
|
-
|
|
228
|
-
|
|
250
|
+
if (!el || !el.parentNode) return null;
|
|
251
|
+
if (el.nextSibling) el.parentNode.insertBefore(wrap, el.nextSibling);
|
|
252
|
+
else el.parentNode.appendChild(wrap);
|
|
229
253
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return wrap;
|
|
235
|
-
} finally {
|
|
236
|
-
// Libérer le lock après 100ms (le temps que le DOM soit stable)
|
|
237
|
-
setTimeout(() => insertingIds.delete(id), 50);
|
|
238
|
-
}
|
|
254
|
+
attachFillObserver(wrap, id);
|
|
255
|
+
return wrap;
|
|
256
|
+
} catch (e) {}
|
|
257
|
+
return null;
|
|
239
258
|
}
|
|
240
259
|
|
|
241
260
|
function destroyUsedPlaceholders() {
|
|
242
|
-
|
|
243
|
-
|
|
261
|
+
var ids = [];
|
|
262
|
+
state.usedTopics.forEach(function (id) { ids.push(id); });
|
|
263
|
+
state.usedPosts.forEach(function (id) { ids.push(id); });
|
|
264
|
+
state.usedCategories.forEach(function (id) { ids.push(id); });
|
|
265
|
+
|
|
266
|
+
// Only destroy placeholders that were filled at least once in this session.
|
|
267
|
+
destroyPlaceholderIds(ids);
|
|
244
268
|
}
|
|
245
269
|
|
|
246
270
|
function patchShowAds() {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (typeof ez.showAds !== 'function') return;
|
|
254
|
-
|
|
255
|
-
window.__nodebbEzoicPatched = true;
|
|
256
|
-
const orig = ez.showAds;
|
|
257
|
-
|
|
258
|
-
ez.showAds = function (arg) {
|
|
259
|
-
if (Array.isArray(arg)) {
|
|
260
|
-
const seen = new Set();
|
|
261
|
-
for (const v of arg) {
|
|
262
|
-
const id = parseInt(v, 10);
|
|
263
|
-
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
264
|
-
seen.add(id);
|
|
265
|
-
try { orig.call(ez, id); } catch (e) {}
|
|
266
|
-
}
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
return orig.apply(ez, arguments);
|
|
270
|
-
};
|
|
271
|
-
} catch (e) {}
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
applyPatch();
|
|
275
|
-
// Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
|
|
276
|
-
if (!window.__nodebbEzoicPatched) {
|
|
277
|
-
try {
|
|
278
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
279
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
280
|
-
window.ezstandalone.cmd.push(applyPatch);
|
|
281
|
-
} catch (e) {}
|
|
282
|
-
}
|
|
271
|
+
// Some Ezoic setups require calling showAds via ezstandalone.cmd.
|
|
272
|
+
// We keep existing behavior but make it resilient.
|
|
273
|
+
try {
|
|
274
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
275
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
276
|
+
} catch (e) {}
|
|
283
277
|
}
|
|
284
278
|
|
|
285
|
-
function markFilled(
|
|
286
|
-
|
|
287
|
-
if (!wrap) return;
|
|
288
|
-
// Disconnect the fill observer first (no need to remove+re-add the attribute)
|
|
289
|
-
if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
|
|
290
|
-
wrap.setAttribute('data-ezoic-filled', '1');
|
|
291
|
-
} catch (e) {}
|
|
279
|
+
function markFilled(id) {
|
|
280
|
+
try { sessionDefinedIds.add(id); } catch (e) {}
|
|
292
281
|
}
|
|
293
282
|
|
|
294
283
|
function isWrapMarkedFilled(wrap) {
|
|
295
|
-
|
|
284
|
+
try { return !!(wrap && wrap.getAttribute && wrap.getAttribute('data-ezoic-filled') === '1'); } catch (e) { return false; }
|
|
296
285
|
}
|
|
297
286
|
|
|
298
287
|
function attachFillObserver(wrap, id) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (now - last < 3500) return;
|
|
355
|
-
|
|
356
|
-
// Ajouter à la batch
|
|
357
|
-
pendingShowAdsIds.add(id);
|
|
358
|
-
|
|
359
|
-
// Debounce: attendre 100ms pour collecter tous les IDs
|
|
360
|
-
clearTimeout(batchShowAdsTimer);
|
|
361
|
-
batchShowAdsTimer = setTimeout(() => {
|
|
362
|
-
if (pendingShowAdsIds.size === 0) return;
|
|
363
|
-
|
|
364
|
-
const idsArray = Array.from(pendingShowAdsIds);
|
|
365
|
-
pendingShowAdsIds.clear();
|
|
366
|
-
|
|
367
|
-
// Appeler showAds avec TOUS les IDs en une fois
|
|
368
|
-
try {
|
|
369
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
370
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
371
|
-
window.ezstandalone.cmd.push(function() {
|
|
372
|
-
if (typeof window.ezstandalone.showAds === 'function') {
|
|
373
|
-
// Appel batch: showAds(id1, id2, id3...)
|
|
374
|
-
window.ezstandalone.showAds(...idsArray);
|
|
375
|
-
// Tracker tous les IDs
|
|
376
|
-
idsArray.forEach(id => {
|
|
377
|
-
state.lastShowById.set(id, Date.now());
|
|
378
|
-
sessionDefinedIds.add(id);
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
} catch (e) {}
|
|
383
|
-
}, 100);
|
|
288
|
+
if (!wrap || !wrap.isConnected) return;
|
|
289
|
+
|
|
290
|
+
// If already filled, mark and return.
|
|
291
|
+
if (isPlaceholderFilled(wrap)) {
|
|
292
|
+
try { wrap.setAttribute('data-ezoic-filled', '1'); } catch (e) {}
|
|
293
|
+
markFilled(id);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Observe for Ezoic inserting ad content into placeholder.
|
|
298
|
+
try {
|
|
299
|
+
var obs = new MutationObserver(function () {
|
|
300
|
+
if (!wrap.isConnected) {
|
|
301
|
+
try { obs.disconnect(); } catch (e) {}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (isPlaceholderFilled(wrap)) {
|
|
305
|
+
try { wrap.setAttribute('data-ezoic-filled', '1'); } catch (e) {}
|
|
306
|
+
markFilled(id);
|
|
307
|
+
try { obs.disconnect(); } catch (e) {}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
obs.observe(wrap, { childList: true, subtree: true });
|
|
311
|
+
wrap.__ezoicFillObs = obs;
|
|
312
|
+
} catch (e) {}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function isPlaceholderFilled(wrap) {
|
|
316
|
+
// Heuristic: placeholder exists AND has descendants or meaningful height.
|
|
317
|
+
try {
|
|
318
|
+
var ph = wrap.querySelector('[id^="' + PLACEHOLDER_PREFIX + '"]');
|
|
319
|
+
if (!ph) return false;
|
|
320
|
+
if (ph.children && ph.children.length) return true;
|
|
321
|
+
var r = safeRect(wrap);
|
|
322
|
+
if (r && r.height > 20) return true;
|
|
323
|
+
} catch (e) {}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function scheduleShowAdsBatch(ids) {
|
|
328
|
+
if (!ids || !ids.length) return;
|
|
329
|
+
|
|
330
|
+
// Ezoic expects DOM to be settled.
|
|
331
|
+
var call = function () {
|
|
332
|
+
try {
|
|
333
|
+
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
334
|
+
window.ezstandalone.showAds(ids);
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') call();
|
|
341
|
+
else if (window.ezstandalone && window.ezstandalone.cmd && Array.isArray(window.ezstandalone.cmd)) window.ezstandalone.cmd.push(call);
|
|
342
|
+
} catch (e) {}
|
|
384
343
|
}
|
|
385
344
|
|
|
386
345
|
function callShowAdsWhenReady(id) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
346
|
+
if (!id) return;
|
|
347
|
+
|
|
348
|
+
// Throttle per-id.
|
|
349
|
+
var now = Date.now();
|
|
350
|
+
var last = state.lastShowById.get(id) || 0;
|
|
351
|
+
if (now - last < 1200) return;
|
|
352
|
+
|
|
353
|
+
if (state.pendingById.has(id)) return;
|
|
354
|
+
state.pendingById.add(id);
|
|
355
|
+
state.lastShowById.set(id, now);
|
|
356
|
+
|
|
357
|
+
// Guard against re-entrancy.
|
|
358
|
+
if (insertingIds.has(id)) {
|
|
359
|
+
state.pendingById.delete(id);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
insertingIds.add(id);
|
|
363
|
+
|
|
364
|
+
var attempts = 0;
|
|
365
|
+
|
|
366
|
+
(function waitForPh() {
|
|
367
|
+
attempts++;
|
|
368
|
+
|
|
369
|
+
// Navigation safety: if we navigated away, stop.
|
|
370
|
+
if (!state.canShowAds) {
|
|
371
|
+
state.pendingById.delete(id);
|
|
372
|
+
insertingIds.delete(id);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
var ph = document.getElementById(PLACEHOLDER_PREFIX + id);
|
|
377
|
+
|
|
378
|
+
var doCall = function () {
|
|
379
|
+
try {
|
|
380
|
+
// If placeholder is gone, stop.
|
|
381
|
+
if (!ph || !ph.isConnected) return false;
|
|
382
|
+
scheduleShowAdsBatch([id]);
|
|
383
|
+
return true;
|
|
384
|
+
} catch (e) {}
|
|
385
|
+
return false;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
if (ph && ph.isConnected) {
|
|
389
|
+
doCall();
|
|
390
|
+
state.pendingById.delete(id);
|
|
391
|
+
insertingIds.delete(id);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (attempts < 100) {
|
|
396
|
+
setTimeoutTracked(waitForPh, 50);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Timeout: give up silently.
|
|
401
|
+
state.pendingById.delete(id);
|
|
402
|
+
insertingIds.delete(id);
|
|
403
|
+
})();
|
|
404
|
+
}
|
|
396
405
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
406
|
+
function initPools(cfg) {
|
|
407
|
+
if (!state.poolTopics.length) state.poolTopics = parsePool(cfg.placeholderIds);
|
|
408
|
+
if (!state.poolPosts.length) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
|
|
409
|
+
if (!state.poolCategories.length) state.poolCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
401
410
|
}
|
|
402
|
-
} catch (e) {}
|
|
403
|
-
return false;
|
|
404
|
-
};
|
|
405
411
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// Abort if the user navigated away since this showAds was scheduled
|
|
410
|
-
if (state.pageKey !== startPageKey) return;
|
|
411
|
-
// Abort if another concurrent call is already handling this id
|
|
412
|
-
if (state.pendingById.has(id)) return;
|
|
412
|
+
function computeTargets(count, interval, showFirst) {
|
|
413
|
+
var out = [];
|
|
414
|
+
if (count <= 0) return out;
|
|
413
415
|
|
|
414
|
-
|
|
415
|
-
const el = document.getElementById(phId);
|
|
416
|
-
if (el && el.isConnected) {
|
|
417
|
-
// CRITIQUE: Vérifier que le placeholder est VISIBLE
|
|
416
|
+
if (showFirst) out.push(1);
|
|
418
417
|
|
|
419
|
-
|
|
418
|
+
for (var i = 1; i <= count; i++) {
|
|
419
|
+
if (i % interval === 0) out.push(i);
|
|
420
|
+
}
|
|
420
421
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
state.pendingById.delete(id);
|
|
424
|
-
return;
|
|
422
|
+
// Unique + sorted.
|
|
423
|
+
return Array.from(new Set(out)).sort(function (a, b) { return a - b; });
|
|
425
424
|
}
|
|
426
425
|
|
|
427
|
-
|
|
426
|
+
function injectBetween(kindClass, items, interval, showFirst, kindPool, usedSet, liveArr) {
|
|
427
|
+
if (!items || !items.length) return 0;
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
state.activeTimeouts.add(timeoutId);
|
|
432
|
-
}
|
|
433
|
-
})();
|
|
434
|
-
}
|
|
429
|
+
var targets = computeTargets(items.length, interval, showFirst);
|
|
430
|
+
var inserted = 0;
|
|
435
431
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
432
|
+
for (var t = 0; t < targets.length; t++) {
|
|
433
|
+
var afterPos = targets[t];
|
|
434
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
439
435
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
let delay = 800;
|
|
443
|
-
for (let attempt = 1; attempt <= MAX_TRIES; attempt++) {
|
|
444
|
-
try {
|
|
445
|
-
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
446
|
-
if (res.ok) {
|
|
447
|
-
state.cfg = await res.json();
|
|
448
|
-
return state.cfg;
|
|
449
|
-
}
|
|
450
|
-
} catch (e) {}
|
|
451
|
-
if (attempt < MAX_TRIES) await new Promise(r => setTimeout(r, delay));
|
|
452
|
-
delay *= 2;
|
|
453
|
-
}
|
|
454
|
-
return null;
|
|
455
|
-
})();
|
|
436
|
+
var el = items[afterPos - 1];
|
|
437
|
+
if (!el || !el.isConnected) continue;
|
|
456
438
|
|
|
457
|
-
|
|
458
|
-
|
|
439
|
+
// Prevent adjacent ads.
|
|
440
|
+
if (isAdjacentAd(el) || isPrevAd(el)) continue;
|
|
459
441
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (state.poolCategories.length === 0) state.poolCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
464
|
-
}
|
|
442
|
+
// Prevent duplicates at same logical position.
|
|
443
|
+
if (findWrap(kindClass, afterPos - 1)) continue;
|
|
444
|
+
if (findWrap(kindClass, afterPos)) continue;
|
|
465
445
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (showFirst) out.push(1);
|
|
470
|
-
for (let i = 1; i <= count; i++) {
|
|
471
|
-
if (i % interval === 0) out.push(i);
|
|
472
|
-
}
|
|
473
|
-
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
474
|
-
}
|
|
446
|
+
var pick = pickId(kindPool, liveArr);
|
|
447
|
+
var id = pick.id;
|
|
448
|
+
if (!id) break;
|
|
475
449
|
|
|
476
|
-
|
|
477
|
-
if (!items.length) return 0;
|
|
478
|
-
const targets = computeTargets(items.length, interval, showFirst);
|
|
450
|
+
var wrap = null;
|
|
479
451
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
452
|
+
if (pick.recycled && pick.recycled.wrap) {
|
|
453
|
+
// Recycle: only destroy if Ezoic has actually defined this placeholder before.
|
|
454
|
+
if (sessionDefinedIds.has(id)) destroyPlaceholderIds([id]);
|
|
483
455
|
|
|
484
|
-
|
|
485
|
-
|
|
456
|
+
// Remove old wrapper.
|
|
457
|
+
var oldWrap = pick.recycled.wrap;
|
|
458
|
+
try { if (oldWrap && oldWrap.__ezoicFillObs) oldWrap.__ezoicFillObs.disconnect(); } catch (e) {}
|
|
459
|
+
try { if (oldWrap) oldWrap.remove(); } catch (e) {}
|
|
486
460
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
461
|
+
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
462
|
+
if (!wrap) continue;
|
|
491
463
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
464
|
+
// Give Ezoic a moment after DOM insertion.
|
|
465
|
+
setTimeoutTracked(function () { callShowAdsWhenReady(id); }, 700);
|
|
466
|
+
} else {
|
|
467
|
+
usedSet.add(id);
|
|
468
|
+
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
469
|
+
if (!wrap) continue;
|
|
495
470
|
|
|
496
|
-
|
|
471
|
+
// Micro-delay to allow layout/DOM settle.
|
|
472
|
+
setTimeoutTracked(function () { callShowAdsWhenReady(id); }, 10);
|
|
473
|
+
}
|
|
497
474
|
|
|
498
|
-
|
|
499
|
-
const id = pick.id;
|
|
500
|
-
if (!id) break;
|
|
475
|
+
liveArr.push({ id: id, wrap: wrap });
|
|
501
476
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
// Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
|
|
509
|
-
const oldWrap = pick.recycled.wrap;
|
|
510
|
-
try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
|
|
511
|
-
try { oldWrap && oldWrap.remove(); } catch (e) {}
|
|
512
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
513
|
-
if (!wrap) continue;
|
|
514
|
-
// Attendre que le wrapper soit dans le DOM puis appeler showAds
|
|
515
|
-
setTimeout(() => {
|
|
516
|
-
callShowAdsWhenReady(id);
|
|
517
|
-
}, 50);
|
|
518
|
-
} else {
|
|
519
|
-
usedSet.add(id);
|
|
520
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
521
|
-
if (!wrap) continue;
|
|
522
|
-
// Micro-délai pour laisser le DOM se synchroniser
|
|
523
|
-
// Appel immédiat au lieu de 10ms delay
|
|
524
|
-
callShowAdsWhenReady(id);
|
|
525
|
-
}
|
|
477
|
+
// Final safety: if adjacency happened due to DOM shifts, rollback.
|
|
478
|
+
var prev = wrap && wrap.previousElementSibling;
|
|
479
|
+
var next = wrap && wrap.nextElementSibling;
|
|
480
|
+
if (wrap && ((prev && prev.classList && prev.classList.contains(WRAP_CLASS)) || (next && next.classList && next.classList.contains(WRAP_CLASS)))) {
|
|
481
|
+
try { wrap.remove(); } catch (e) {}
|
|
526
482
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
inserted += 1;
|
|
541
|
-
}
|
|
542
|
-
return inserted;
|
|
483
|
+
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
484
|
+
try { kindPool.unshift(id); } catch (e) {}
|
|
485
|
+
usedSet.delete(id);
|
|
486
|
+
}
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
inserted++;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return inserted;
|
|
543
494
|
}
|
|
544
495
|
|
|
545
496
|
function enforceNoAdjacentAds() {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
497
|
+
var ads = Array.from(document.querySelectorAll('.' + WRAP_CLASS));
|
|
498
|
+
for (var i = 0; i < ads.length; i++) {
|
|
499
|
+
var ad = ads[i];
|
|
500
|
+
var prev = ad.previousElementSibling;
|
|
501
|
+
|
|
502
|
+
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
|
|
503
|
+
// Remove adjacent wrapper (do not hide).
|
|
504
|
+
try {
|
|
505
|
+
var ph = ad.querySelector('[id^="' + PLACEHOLDER_PREFIX + '"]');
|
|
506
|
+
if (ph) {
|
|
507
|
+
var id = parseInt(ph.id.replace(PLACEHOLDER_PREFIX, ''), 10);
|
|
508
|
+
if (Number.isFinite(id) && id > 0 && sessionDefinedIds.has(id)) {
|
|
509
|
+
destroyPlaceholderIds([id]);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
ad.remove();
|
|
513
|
+
} catch (e) {}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
566
516
|
}
|
|
567
517
|
|
|
568
518
|
function cleanup() {
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
519
|
+
// Stop any insertion during navigation / DOM teardown.
|
|
520
|
+
state.canShowAds = false;
|
|
521
|
+
state.poolWaitAttempts = 0;
|
|
522
|
+
state.awaitItemsAttempts = 0;
|
|
523
|
+
|
|
524
|
+
// Cancel any pending showAds timeouts.
|
|
525
|
+
clearAllTrackedTimeouts();
|
|
526
|
+
|
|
527
|
+
// Disconnect global observer to avoid mutations during teardown.
|
|
528
|
+
if (state.obs) {
|
|
529
|
+
try { state.obs.disconnect(); } catch (e) {}
|
|
530
|
+
state.obs = null;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Destroy placeholders that were used (only those that were actually defined).
|
|
534
|
+
destroyUsedPlaceholders();
|
|
535
|
+
|
|
536
|
+
// Remove wrappers from DOM (safe because insertion is now blocked).
|
|
537
|
+
try {
|
|
538
|
+
document.querySelectorAll('.' + WRAP_CLASS).forEach(function (el) {
|
|
539
|
+
try { if (el && el.__ezoicFillObs) el.__ezoicFillObs.disconnect(); } catch (e) {}
|
|
540
|
+
try { el.remove(); } catch (e) {}
|
|
541
|
+
});
|
|
542
|
+
} catch (e) {}
|
|
543
|
+
|
|
544
|
+
// Reset runtime caches.
|
|
545
|
+
state.pageKey = getPageKey();
|
|
546
|
+
state.cfg = null;
|
|
547
|
+
state.cfgPromise = null;
|
|
548
|
+
|
|
549
|
+
state.poolTopics = [];
|
|
550
|
+
state.poolPosts = [];
|
|
551
|
+
state.poolCategories = [];
|
|
552
|
+
|
|
553
|
+
state.usedTopics.clear();
|
|
554
|
+
state.usedPosts.clear();
|
|
555
|
+
state.usedCategories.clear();
|
|
556
|
+
|
|
557
|
+
state.liveTopics = [];
|
|
558
|
+
state.livePosts = [];
|
|
559
|
+
state.liveCategories = [];
|
|
560
|
+
|
|
561
|
+
state.lastShowById.clear();
|
|
562
|
+
state.pendingById.clear();
|
|
563
|
+
insertingIds.clear();
|
|
564
|
+
|
|
565
|
+
state.scheduled = false;
|
|
566
|
+
if (state.timer) {
|
|
567
|
+
try { clearTimeout(state.timer); } catch (e) {}
|
|
568
|
+
state.timer = null;
|
|
569
|
+
}
|
|
612
570
|
}
|
|
613
571
|
|
|
614
572
|
function ensureObserver() {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
async function runCore() {
|
|
621
|
-
// Attendre que canInsert soit true (protection race condition navigation)
|
|
622
|
-
if (!state.canShowAds) {
|
|
623
|
-
return;
|
|
573
|
+
if (state.obs) return;
|
|
574
|
+
try {
|
|
575
|
+
state.obs = new MutationObserver(function () { scheduleRun('mutation'); });
|
|
576
|
+
state.obs.observe(document.body, { childList: true, subtree: true });
|
|
577
|
+
} catch (e) {}
|
|
624
578
|
}
|
|
625
579
|
|
|
626
|
-
|
|
580
|
+
function scheduleRun(/* reason */) {
|
|
581
|
+
if (state.scheduled) return;
|
|
582
|
+
state.scheduled = true;
|
|
627
583
|
|
|
628
|
-
|
|
629
|
-
|
|
584
|
+
if (state.timer) {
|
|
585
|
+
try { clearTimeout(state.timer); } catch (e) {}
|
|
586
|
+
state.timer = null;
|
|
587
|
+
}
|
|
630
588
|
|
|
631
|
-
|
|
589
|
+
state.timer = setTimeoutTracked(function () {
|
|
590
|
+
state.scheduled = false;
|
|
632
591
|
|
|
633
|
-
|
|
634
|
-
|
|
592
|
+
// If user navigated away, stop.
|
|
593
|
+
var pk = getPageKey();
|
|
594
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
635
595
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
inserted = injectBetween('ezoic-ad-message', getPostContainers(),
|
|
639
|
-
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
640
|
-
normalizeBool(cfg.showFirstMessageAd),
|
|
641
|
-
state.poolPosts,
|
|
642
|
-
state.usedPosts);
|
|
643
|
-
}
|
|
644
|
-
} else if (kind === 'categoryTopics') {
|
|
645
|
-
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
646
|
-
inserted = injectBetween('ezoic-ad-between', getTopicItems(),
|
|
647
|
-
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
648
|
-
normalizeBool(cfg.showFirstTopicAd),
|
|
649
|
-
state.poolTopics,
|
|
650
|
-
state.usedTopics);
|
|
651
|
-
}
|
|
652
|
-
} else if (kind === 'categories') {
|
|
653
|
-
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
654
|
-
inserted = injectBetween('ezoic-ad-categories', getCategoryItems(),
|
|
655
|
-
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
656
|
-
normalizeBool(cfg.showFirstCategoryAd),
|
|
657
|
-
state.poolCategories,
|
|
658
|
-
state.usedCategories);
|
|
659
|
-
}
|
|
596
|
+
runCore().catch(function () {});
|
|
597
|
+
}, 80);
|
|
660
598
|
}
|
|
661
599
|
|
|
662
|
-
|
|
600
|
+
function waitForItemsThenRun(kind) {
|
|
601
|
+
// If list isn't in DOM yet (ajaxify transition), retry a bit.
|
|
602
|
+
var count = 0;
|
|
603
|
+
if (kind === 'topic') count = getPostContainers().length;
|
|
604
|
+
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
605
|
+
else if (kind === 'categories') count = getCategoryItems().length;
|
|
663
606
|
|
|
664
|
-
|
|
665
|
-
let count = 0;
|
|
666
|
-
if (kind === 'topic') count = getPostContainers().length;
|
|
667
|
-
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
668
|
-
else if (kind === 'categories') count = getCategoryItems().length;
|
|
607
|
+
if (count > 0) return true;
|
|
669
608
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
609
|
+
if (state.awaitItemsAttempts < 25) {
|
|
610
|
+
state.awaitItemsAttempts++;
|
|
611
|
+
setTimeoutTracked(function () { scheduleRun('await-items'); }, 120);
|
|
612
|
+
}
|
|
613
|
+
return false;
|
|
673
614
|
}
|
|
674
615
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
// Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
|
|
681
|
-
// le temps de défiler hors écran et devenir recyclables.
|
|
682
|
-
if (state.poolWaitAttempts < 8) {
|
|
683
|
-
state.poolWaitAttempts += 1;
|
|
684
|
-
setTimeout(arguments[0], 50);
|
|
685
|
-
} else {
|
|
686
|
-
}
|
|
687
|
-
} else if (inserted > 0) {
|
|
688
|
-
}
|
|
689
|
-
}
|
|
616
|
+
function waitForContentThenRun() {
|
|
617
|
+
// Avoid inserting ads on pages with too little content.
|
|
618
|
+
var MIN_WORDS = 250;
|
|
619
|
+
var attempts = 0;
|
|
620
|
+
var maxAttempts = 20; // 20 × 200ms = 4s
|
|
690
621
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
state.scheduled = true;
|
|
622
|
+
(function check() {
|
|
623
|
+
attempts++;
|
|
694
624
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const pk = getPageKey();
|
|
699
|
-
if (state.pageKey && pk !== state.pageKey) return;
|
|
700
|
-
runCore().catch(() => {});
|
|
701
|
-
}, 50);
|
|
702
|
-
}
|
|
625
|
+
var text = '';
|
|
626
|
+
try { text = document.body.innerText || ''; } catch (e) {}
|
|
627
|
+
var wordCount = text.split(/\s+/).filter(Boolean).length;
|
|
703
628
|
|
|
704
|
-
|
|
705
|
-
|
|
629
|
+
if (wordCount >= MIN_WORDS) {
|
|
630
|
+
scheduleRun('content-ok');
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
706
633
|
|
|
707
|
-
|
|
634
|
+
if (attempts >= maxAttempts) {
|
|
635
|
+
scheduleRun('content-timeout');
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
708
638
|
|
|
709
|
-
|
|
639
|
+
setTimeoutTracked(check, 200);
|
|
640
|
+
})();
|
|
641
|
+
}
|
|
710
642
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
643
|
+
function waitForEzoicThenRun() {
|
|
644
|
+
var attempts = 0;
|
|
645
|
+
var maxAttempts = 50; // 50 × 200ms = 10s
|
|
646
|
+
|
|
647
|
+
(function check() {
|
|
648
|
+
attempts++;
|
|
649
|
+
|
|
650
|
+
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
651
|
+
scheduleRun('ezoic-ready');
|
|
652
|
+
waitForContentThenRun();
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (attempts >= maxAttempts) {
|
|
657
|
+
scheduleRun('ezoic-timeout');
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
setTimeoutTracked(check, 200);
|
|
662
|
+
})();
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function fetchConfig() {
|
|
666
|
+
if (state.cfg) return Promise.resolve(state.cfg);
|
|
667
|
+
if (state.cfgPromise) return state.cfgPromise;
|
|
668
|
+
|
|
669
|
+
state.cfgPromise = (function () {
|
|
670
|
+
var MAX_TRIES = 3;
|
|
671
|
+
var delay = 800;
|
|
672
|
+
|
|
673
|
+
function attemptFetch(attempt) {
|
|
674
|
+
return fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' })
|
|
675
|
+
.then(function (res) {
|
|
676
|
+
if (!res || !res.ok) throw new Error('bad response');
|
|
677
|
+
return res.json();
|
|
678
|
+
})
|
|
679
|
+
.then(function (json) {
|
|
680
|
+
state.cfg = json;
|
|
681
|
+
return json;
|
|
682
|
+
})
|
|
683
|
+
.catch(function () {
|
|
684
|
+
if (attempt >= MAX_TRIES) return null;
|
|
685
|
+
return new Promise(function (r) { setTimeoutTracked(r, delay); }).then(function () {
|
|
686
|
+
delay *= 2;
|
|
687
|
+
return attemptFetch(attempt + 1);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return attemptFetch(1).finally(function () { state.cfgPromise = null; });
|
|
693
|
+
})();
|
|
694
|
+
|
|
695
|
+
return state.cfgPromise;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function runCore() {
|
|
699
|
+
// Navigation safety: never insert during ajaxify teardown.
|
|
700
|
+
if (!state.canShowAds) return Promise.resolve();
|
|
701
|
+
|
|
702
|
+
patchShowAds();
|
|
703
|
+
|
|
704
|
+
return fetchConfig().then(function (cfg) {
|
|
705
|
+
if (!cfg || cfg.excluded) return;
|
|
706
|
+
|
|
707
|
+
initPools(cfg);
|
|
708
|
+
|
|
709
|
+
var kind = getKind();
|
|
710
|
+
var inserted = 0;
|
|
711
|
+
|
|
712
|
+
if (!waitForItemsThenRun(kind)) return;
|
|
713
|
+
|
|
714
|
+
if (kind === 'topic') {
|
|
715
|
+
if (normalizeBool(cfg.enableMessageAds)) {
|
|
716
|
+
inserted = injectBetween(
|
|
717
|
+
'ezoic-ad-message',
|
|
718
|
+
getPostContainers(),
|
|
719
|
+
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
720
|
+
normalizeBool(cfg.showFirstMessageAd),
|
|
721
|
+
state.poolPosts,
|
|
722
|
+
state.usedPosts,
|
|
723
|
+
state.livePosts
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
} else if (kind === 'categoryTopics') {
|
|
727
|
+
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
728
|
+
inserted = injectBetween(
|
|
729
|
+
'ezoic-ad-between',
|
|
730
|
+
getTopicItems(),
|
|
731
|
+
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
732
|
+
normalizeBool(cfg.showFirstTopicAd),
|
|
733
|
+
state.poolTopics,
|
|
734
|
+
state.usedTopics,
|
|
735
|
+
state.liveTopics
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
} else if (kind === 'categories') {
|
|
739
|
+
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
740
|
+
inserted = injectBetween(
|
|
741
|
+
'ezoic-ad-categories',
|
|
742
|
+
getCategoryItems(),
|
|
743
|
+
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
744
|
+
normalizeBool(cfg.showFirstCategoryAd),
|
|
745
|
+
state.poolCategories,
|
|
746
|
+
state.usedCategories,
|
|
747
|
+
state.liveCategories
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
enforceNoAdjacentAds();
|
|
753
|
+
|
|
754
|
+
// Recycling: if pool is exhausted, retry a few times to allow old wrappers to scroll off-screen.
|
|
755
|
+
if (inserted === 0) {
|
|
756
|
+
if (state.poolWaitAttempts < 8) {
|
|
757
|
+
state.poolWaitAttempts++;
|
|
758
|
+
setTimeoutTracked(function () { scheduleRun('pool-wait'); }, 400);
|
|
759
|
+
}
|
|
760
|
+
} else {
|
|
761
|
+
// Reset pool wait attempts once we successfully insert something.
|
|
762
|
+
state.poolWaitAttempts = 0;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// If we hit max inserts, continue quickly.
|
|
766
|
+
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
767
|
+
setTimeoutTracked(function () { scheduleRun('continue'); }, 140);
|
|
768
|
+
}
|
|
769
|
+
}).catch(function () {});
|
|
770
|
+
}
|
|
714
771
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
// Sinon race condition: NodeBB vide le DOM pendant que Ezoic essaie d'accéder aux placeholders
|
|
718
|
-
state.canShowAds = true;
|
|
719
|
-
});
|
|
772
|
+
function bind() {
|
|
773
|
+
if (!$) return;
|
|
720
774
|
|
|
721
|
-
|
|
722
|
-
ensureObserver();
|
|
723
|
-
// category.loaded = infinite scroll, Ezoic déjà chargé normalement
|
|
724
|
-
waitForContentThenRun();
|
|
725
|
-
});
|
|
726
|
-
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
727
|
-
ensureObserver();
|
|
728
|
-
waitForContentThenRun();
|
|
729
|
-
});
|
|
775
|
+
$(window).off('.ezoicInfinite');
|
|
730
776
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
});
|
|
777
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', function () {
|
|
778
|
+
cleanup();
|
|
779
|
+
});
|
|
735
780
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
waitForContentThenRun();
|
|
740
|
-
});
|
|
741
|
-
}
|
|
781
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', function () {
|
|
782
|
+
state.pageKey = getPageKey();
|
|
783
|
+
ensureObserver();
|
|
742
784
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
ticking = true;
|
|
750
|
-
window.requestAnimationFrame(() => {
|
|
751
|
-
ticking = false;
|
|
752
|
-
enforceNoAdjacentAds();
|
|
753
|
-
// Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
|
|
754
|
-
const now = Date.now();
|
|
755
|
-
if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
|
|
756
|
-
state.lastScrollRun = now;
|
|
757
|
-
scheduleRun();
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
}, { passive: true });
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// Fonction qui attend que la page ait assez de contenu avant d'insérer les pubs
|
|
764
|
-
function waitForContentThenRun() {
|
|
765
|
-
const MIN_WORDS = 250;
|
|
766
|
-
let attempts = 0;
|
|
767
|
-
const maxAttempts = 20; // 20 × 200ms = 4s max
|
|
785
|
+
// Delay gate to avoid racing NodeBB DOM swap vs Ezoic processing.
|
|
786
|
+
setTimeoutTracked(function () {
|
|
787
|
+
state.canShowAds = true;
|
|
788
|
+
waitForEzoicThenRun();
|
|
789
|
+
}, 300);
|
|
790
|
+
});
|
|
768
791
|
|
|
769
|
-
|
|
770
|
-
|
|
792
|
+
// Infinite-scroll and "loaded" events.
|
|
793
|
+
$(window).on('action:category.loaded.ezoicInfinite', function () {
|
|
794
|
+
ensureObserver();
|
|
795
|
+
waitForContentThenRun();
|
|
796
|
+
});
|
|
771
797
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
798
|
+
$(window).on('action:topics.loaded.ezoicInfinite', function () {
|
|
799
|
+
ensureObserver();
|
|
800
|
+
waitForContentThenRun();
|
|
801
|
+
});
|
|
775
802
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}
|
|
803
|
+
$(window).on('action:topic.loaded.ezoicInfinite', function () {
|
|
804
|
+
ensureObserver();
|
|
805
|
+
waitForContentThenRun();
|
|
806
|
+
});
|
|
781
807
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
return;
|
|
808
|
+
$(window).on('action:posts.loaded.ezoicInfinite', function () {
|
|
809
|
+
ensureObserver();
|
|
810
|
+
waitForContentThenRun();
|
|
811
|
+
});
|
|
787
812
|
}
|
|
788
813
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}
|
|
814
|
+
function bindScroll() {
|
|
815
|
+
if (state.lastScrollRun > 0) return;
|
|
816
|
+
state.lastScrollRun = Date.now();
|
|
793
817
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
818
|
+
var ticking = false;
|
|
819
|
+
window.addEventListener('scroll', function () {
|
|
820
|
+
if (ticking) return;
|
|
821
|
+
ticking = true;
|
|
798
822
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
// Réessayer dans 200ms
|
|
815
|
-
setTimeout(check, 50);
|
|
816
|
-
})();
|
|
823
|
+
window.requestAnimationFrame(function () {
|
|
824
|
+
ticking = false;
|
|
825
|
+
|
|
826
|
+
enforceNoAdjacentAds();
|
|
827
|
+
|
|
828
|
+
// Debounce scheduleRun (max once every 2s on scroll).
|
|
829
|
+
var now = Date.now();
|
|
830
|
+
if (!state.lastScrollRun || (now - state.lastScrollRun > 2000)) {
|
|
831
|
+
state.lastScrollRun = now;
|
|
832
|
+
scheduleRun('scroll');
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
}, { passive: true });
|
|
817
836
|
}
|
|
818
837
|
|
|
838
|
+
// Boot.
|
|
819
839
|
cleanup();
|
|
820
840
|
bind();
|
|
821
841
|
bindScroll();
|
|
822
842
|
ensureObserver();
|
|
843
|
+
|
|
823
844
|
state.pageKey = getPageKey();
|
|
824
845
|
|
|
825
|
-
//
|
|
826
|
-
|
|
827
|
-
|
|
846
|
+
// Direct page load: allow insertion after initial tick (no ajaxify.end).
|
|
847
|
+
setTimeoutTracked(function () {
|
|
848
|
+
state.canShowAds = true;
|
|
849
|
+
waitForEzoicThenRun();
|
|
850
|
+
}, 0);
|
|
851
|
+
})();
|