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