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