nodebb-plugin-ezoic-infinite 1.4.70 → 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 +766 -376
- package/public/style.css +25 -23
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,464 +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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const RECYCLE_MARGIN_PX = 600;
|
|
27
|
-
const SHOW_DEBOUNCE_MS = 500;
|
|
28
|
-
|
|
29
|
-
const state = {
|
|
30
|
-
pageKey: null,
|
|
31
|
-
cfg: null,
|
|
32
|
-
cfgPromise: null,
|
|
33
|
-
|
|
34
|
-
pools: { topics: [], categories: [], messages: [] },
|
|
35
|
-
live: { topics: [], categories: [], messages: [] },
|
|
36
|
-
|
|
37
|
-
scheduled: false,
|
|
38
|
-
lastRunAt: 0,
|
|
39
|
-
timeouts: new Set(),
|
|
40
|
-
observer: null,
|
|
41
|
-
lastShowById: new Map(),
|
|
42
|
-
};
|
|
18
|
+
poolTopics: [],
|
|
19
|
+
poolPosts: [],
|
|
20
|
+
poolCategories: [],
|
|
21
|
+
|
|
22
|
+
usedTopics: new Set(),
|
|
23
|
+
usedPosts: new Set(),
|
|
24
|
+
usedCategories: new Set(),
|
|
43
25
|
|
|
44
|
-
|
|
26
|
+
lastShowById: new Map(),
|
|
27
|
+
pendingById: new Set(),
|
|
28
|
+
definedIds: new Set(),
|
|
29
|
+
|
|
30
|
+
scheduled: false,
|
|
31
|
+
timer: null,
|
|
32
|
+
|
|
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
|
+
}
|
|
40
|
+
|
|
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
|
+
}
|
|
45
58
|
|
|
46
59
|
function getPageKey() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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;
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
function getKind() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function parseIdPool(raw) {
|
|
70
|
-
if (!raw) return [];
|
|
71
|
-
const tokens = String(raw)
|
|
72
|
-
.split(/[\s,;]+/)
|
|
73
|
-
.map(s => s.trim())
|
|
74
|
-
.filter(Boolean);
|
|
75
|
-
|
|
76
|
-
const out = [];
|
|
77
|
-
const seen = new Set();
|
|
78
|
-
for (const t of tokens) {
|
|
79
|
-
const n = parseInt(t, 10);
|
|
80
|
-
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
81
|
-
seen.add(n);
|
|
82
|
-
out.push(n);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
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';
|
|
86
80
|
}
|
|
87
81
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
state.cfgPromise = fetch('/api/plugins/ezoic-infinite/config', {
|
|
93
|
-
credentials: 'same-origin',
|
|
94
|
-
headers: { 'Accept': 'application/json' },
|
|
95
|
-
})
|
|
96
|
-
.then(r => (r && r.ok ? r.json() : null))
|
|
97
|
-
.catch(() => null)
|
|
98
|
-
.then((cfg) => {
|
|
99
|
-
state.cfg = cfg || { excluded: true };
|
|
100
|
-
state.pools.topics = parseIdPool(state.cfg.placeholderIds);
|
|
101
|
-
state.pools.categories = parseIdPool(state.cfg.categoryPlaceholderIds);
|
|
102
|
-
state.pools.messages = parseIdPool(state.cfg.messagePlaceholderIds);
|
|
103
|
-
return state.cfg;
|
|
104
|
-
})
|
|
105
|
-
.finally(() => { state.cfgPromise = null; });
|
|
106
|
-
|
|
107
|
-
return state.cfgPromise;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function rect(el) {
|
|
111
|
-
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function takeRecyclable(kind) {
|
|
115
|
-
const arr = state.live[kind];
|
|
116
|
-
for (let i = 0; i < arr.length; i++) {
|
|
117
|
-
const entry = arr[i];
|
|
118
|
-
if (!entry || !entry.wrap || !entry.wrap.isConnected) {
|
|
119
|
-
arr.splice(i, 1); i--; continue;
|
|
120
|
-
}
|
|
121
|
-
const r = rect(entry.wrap);
|
|
122
|
-
if (r && r.bottom < -RECYCLE_MARGIN_PX) {
|
|
123
|
-
arr.splice(i, 1);
|
|
124
|
-
return entry;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function pickId(kind) {
|
|
131
|
-
const pool = state.pools[kind];
|
|
132
|
-
if (pool && pool.length) return { id: pool.shift(), recycled: null };
|
|
133
|
-
const recycled = takeRecyclable(kind);
|
|
134
|
-
if (recycled) return { id: recycled.id, recycled };
|
|
135
|
-
return { id: null, recycled: null };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function buildWrap(kind, afterIndex, id) {
|
|
139
|
-
const wrap = document.createElement('div');
|
|
140
|
-
wrap.className = `${CLASS_WRAP} ${CLASS_WRAP}--${kind}`;
|
|
141
|
-
wrap.dataset.ezoicKind = kind;
|
|
142
|
-
wrap.dataset.ezoicAfter = String(afterIndex);
|
|
143
|
-
wrap.dataset.ezoicId = String(id);
|
|
144
|
-
|
|
145
|
-
const ph = document.createElement('div');
|
|
146
|
-
ph.id = `${PH_PREFIX}${id}`;
|
|
147
|
-
wrap.appendChild(ph);
|
|
148
|
-
|
|
149
|
-
return wrap;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function findWrap(kind, afterIndex) {
|
|
153
|
-
return document.querySelector(`.${CLASS_WRAP}[data-ezoic-kind="${kind}"][data-ezoic-after="${afterIndex}"]`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function resetWrapPlaceholder(wrap, id) {
|
|
157
|
-
if (!wrap) return;
|
|
158
|
-
if (wrap.__ezoicObs) {
|
|
159
|
-
try { wrap.__ezoicObs.disconnect(); } catch (e) {}
|
|
160
|
-
wrap.__ezoicObs = null;
|
|
161
|
-
}
|
|
162
|
-
wrap.removeAttribute('data-ezoic-filled');
|
|
163
|
-
wrap.dataset.ezoicId = String(id);
|
|
82
|
+
function getTopicItems() {
|
|
83
|
+
return Array.from(document.querySelectorAll(SELECTORS.topicItem));
|
|
84
|
+
}
|
|
164
85
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
ph.id = `${PH_PREFIX}${id}`;
|
|
168
|
-
wrap.appendChild(ph);
|
|
86
|
+
function getCategoryItems() {
|
|
87
|
+
return Array.from(document.querySelectorAll(SELECTORS.categoryItem));
|
|
169
88
|
}
|
|
170
89
|
|
|
171
|
-
function
|
|
172
|
-
|
|
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
|
+
}
|
|
101
|
+
|
|
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;
|
|
203
|
+
}
|
|
204
|
+
|
|
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}`);
|
|
173
298
|
if (!ph) return;
|
|
174
299
|
ph.style.setProperty('height', 'auto', 'important');
|
|
175
300
|
ph.style.setProperty('min-height', '0px', 'important');
|
|
176
301
|
requestAnimationFrame(() => {
|
|
177
|
-
|
|
178
|
-
|
|
302
|
+
try {
|
|
303
|
+
ph.style.setProperty('height', 'auto', 'important');
|
|
304
|
+
ph.style.setProperty('min-height', '0px', 'important');
|
|
305
|
+
} catch (e) {}
|
|
179
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;
|
|
180
330
|
}
|
|
331
|
+
return orig.apply(ez, arguments);
|
|
332
|
+
};
|
|
333
|
+
} catch (e) {}
|
|
334
|
+
};
|
|
181
335
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
}
|
|
186
345
|
|
|
187
|
-
|
|
188
|
-
|
|
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) {}
|
|
189
353
|
}
|
|
190
354
|
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
if (wrap.__ezoicObs) {
|
|
194
|
-
try { wrap.__ezoicObs.disconnect(); } catch (e) {}
|
|
195
|
-
wrap.__ezoicObs = null;
|
|
196
|
-
}
|
|
197
|
-
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; }
|
|
198
357
|
}
|
|
199
358
|
|
|
200
|
-
function
|
|
201
|
-
|
|
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
|
+
}
|
|
202
380
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
381
|
+
function isPlaceholderFilled(id) {
|
|
382
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
383
|
+
if (!ph || !ph.isConnected) return false;
|
|
206
384
|
|
|
207
|
-
|
|
208
|
-
|
|
385
|
+
const wrap = ph.parentElement;
|
|
386
|
+
if (wrap && isWrapMarkedFilled(wrap)) return true;
|
|
209
387
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// Let the DOM settle one frame (helps with ajaxify/morphdom timing)
|
|
218
|
-
requestAnimationFrame(() => {
|
|
219
|
-
if (expectedPageKey !== state.pageKey) return;
|
|
220
|
-
if (!document.getElementById(`${PH_PREFIX}${id}`)) return;
|
|
221
|
-
window.ezstandalone.showAds(id);
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
} catch (e) {}
|
|
225
|
-
};
|
|
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;
|
|
394
|
+
}
|
|
226
395
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
396
|
+
let batchShowAdsTimer = null;
|
|
397
|
+
const pendingShowAdsIds = new Set();
|
|
398
|
+
|
|
399
|
+
function scheduleShowAdsBatch(id) {
|
|
400
|
+
if (!id) return;
|
|
401
|
+
|
|
402
|
+
if (sessionDefinedIds.has(id)) {
|
|
403
|
+
try {
|
|
404
|
+
destroyPlaceholderIds([id]);
|
|
405
|
+
sessionDefinedIds.delete(id);
|
|
406
|
+
} catch (e) {}
|
|
233
407
|
}
|
|
234
408
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
+
});
|
|
436
|
+
}
|
|
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);
|
|
445
|
+
}
|
|
238
446
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (hasCreative || (r && r.height > 20)) {
|
|
250
|
-
markFilled(wrap);
|
|
251
|
-
try { obs.disconnect(); } catch (e) {}
|
|
252
|
-
wrap.__ezoicObs = null;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
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') {
|
|
255
457
|
|
|
256
|
-
|
|
257
|
-
|
|
458
|
+
state.lastShowById.set(id, Date.now());
|
|
459
|
+
window.ezstandalone.showAds(id);
|
|
460
|
+
sessionDefinedIds.add(id);
|
|
461
|
+
return true;
|
|
258
462
|
}
|
|
463
|
+
} catch (e) {}
|
|
464
|
+
return false;
|
|
465
|
+
};
|
|
466
|
+
|
|
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;
|
|
259
472
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
473
|
+
attempts += 1;
|
|
474
|
+
const el = document.getElementById(phId);
|
|
475
|
+
if (el && el.isConnected) {
|
|
476
|
+
|
|
477
|
+
// Si on arrive ici, soit visible, soit timeout
|
|
478
|
+
|
|
479
|
+
if (doCall()) {
|
|
480
|
+
state.pendingById.delete(id);
|
|
481
|
+
return;
|
|
269
482
|
}
|
|
270
483
|
|
|
271
|
-
function insertAfter(target, wrap) {
|
|
272
|
-
if (!target || !wrap) return false;
|
|
273
|
-
const next = target.nextElementSibling;
|
|
274
|
-
if (next && next.classList && next.classList.contains(CLASS_WRAP)) return false;
|
|
275
|
-
try {
|
|
276
|
-
target.insertAdjacentElement('afterend', wrap);
|
|
277
|
-
return true;
|
|
278
|
-
} catch (e) {
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
484
|
}
|
|
282
485
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return out;
|
|
486
|
+
if (attempts < 100) {
|
|
487
|
+
const timeoutId = setTimeout(waitForPh, 50);
|
|
488
|
+
state.activeTimeouts.add(timeoutId);
|
|
489
|
+
}
|
|
490
|
+
})();
|
|
289
491
|
}
|
|
290
492
|
|
|
291
|
-
function
|
|
292
|
-
|
|
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
|
+
})();
|
|
513
|
+
|
|
514
|
+
try { return await state.cfgPromise; } finally { state.cfgPromise = null; }
|
|
515
|
+
}
|
|
516
|
+
|
|
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
|
+
}
|
|
522
|
+
|
|
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
|
+
}
|
|
293
532
|
|
|
294
|
-
|
|
295
|
-
|
|
533
|
+
function injectBetween(kindClass, items, interval, showFirst, kindPool, usedSet) {
|
|
534
|
+
if (!items.length) return 0;
|
|
535
|
+
const targets = computeTargets(items.length, interval, showFirst);
|
|
296
536
|
|
|
297
|
-
|
|
298
|
-
|
|
537
|
+
let inserted = 0;
|
|
538
|
+
for (const afterPos of targets) {
|
|
539
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
299
540
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const anchor = items[afterIndex - 1];
|
|
303
|
-
if (!anchor) continue;
|
|
304
|
-
if (findWrap('topics', afterIndex)) continue;
|
|
541
|
+
const el = items[afterPos - 1];
|
|
542
|
+
if (!el || !el.isConnected) continue;
|
|
305
543
|
|
|
306
|
-
|
|
307
|
-
|
|
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;
|
|
308
553
|
|
|
309
|
-
|
|
310
|
-
|
|
554
|
+
const pick = pickId(kindPool, []);
|
|
555
|
+
const id = pick.id;
|
|
556
|
+
if (!id) break;
|
|
311
557
|
|
|
312
|
-
|
|
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
|
+
}
|
|
313
595
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
+
}
|
|
320
616
|
}
|
|
321
617
|
|
|
322
|
-
function
|
|
323
|
-
|
|
618
|
+
function cleanup() {
|
|
619
|
+
destroyUsedPlaceholders();
|
|
324
620
|
|
|
325
|
-
|
|
326
|
-
|
|
621
|
+
document.querySelectorAll('.ezoic-ad').forEach(el => {
|
|
622
|
+
try { el.remove(); } catch (e) {}
|
|
623
|
+
});
|
|
327
624
|
|
|
328
|
-
|
|
329
|
-
|
|
625
|
+
state.pageKey = getPageKey();
|
|
626
|
+
state.cfg = null;
|
|
627
|
+
state.cfgPromise = null;
|
|
330
628
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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();
|
|
336
638
|
|
|
337
|
-
|
|
338
|
-
|
|
639
|
+
state.activeTimeouts.forEach(id => {
|
|
640
|
+
try { clearTimeout(id); } catch (e) {}
|
|
641
|
+
});
|
|
642
|
+
state.activeTimeouts.clear();
|
|
339
643
|
|
|
340
|
-
|
|
341
|
-
if (recycled) resetWrapPlaceholder(wrap, id);
|
|
644
|
+
state.pendingById.clear();
|
|
342
645
|
|
|
343
|
-
|
|
646
|
+
if (state.obs) { state.obs.disconnect(); state.obs = null; }
|
|
344
647
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (wrap.querySelector(`#${PH_PREFIX}${id}`)) showEzoicAd(id);
|
|
349
|
-
inserts++;
|
|
350
|
-
}
|
|
648
|
+
state.scheduled = false;
|
|
649
|
+
clearTimeout(state.timer);
|
|
650
|
+
state.timer = null;
|
|
351
651
|
}
|
|
352
652
|
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (!posts.length) return;
|
|
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
|
+
}
|
|
359
658
|
|
|
360
|
-
|
|
361
|
-
|
|
659
|
+
async function runCore() {
|
|
660
|
+
if (!state.canShowAds) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
362
663
|
|
|
363
|
-
|
|
364
|
-
if (inserts >= MAX_INSERTS_PER_TICK) break;
|
|
365
|
-
const anchor = posts[afterIndex - 1];
|
|
366
|
-
if (!anchor) continue;
|
|
367
|
-
if (findWrap('messages', afterIndex)) continue;
|
|
664
|
+
patchShowAds();
|
|
368
665
|
|
|
369
|
-
|
|
370
|
-
|
|
666
|
+
const cfg = await fetchConfig();
|
|
667
|
+
if (!cfg || cfg.excluded) return;
|
|
371
668
|
|
|
372
|
-
|
|
373
|
-
wrap.classList.add('ezoic-ad--message');
|
|
374
|
-
if (recycled) resetWrapPlaceholder(wrap, id);
|
|
669
|
+
initPools(cfg);
|
|
375
670
|
|
|
376
|
-
|
|
671
|
+
const kind = getKind();
|
|
672
|
+
let inserted = 0;
|
|
377
673
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
+
}
|
|
384
698
|
}
|
|
385
699
|
|
|
386
|
-
|
|
387
|
-
for (const t of state.timeouts) clearTimeout(t);
|
|
388
|
-
state.timeouts.clear();
|
|
700
|
+
enforceNoAdjacentAds();
|
|
389
701
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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;
|
|
394
706
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
+
}
|
|
399
723
|
}
|
|
400
724
|
|
|
401
725
|
function scheduleRun() {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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);
|
|
408
736
|
}
|
|
409
737
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
+
});
|
|
418
751
|
|
|
419
|
-
|
|
420
|
-
|
|
752
|
+
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
753
|
+
ensureObserver();
|
|
754
|
+
waitForContentThenRun();
|
|
755
|
+
});
|
|
756
|
+
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
757
|
+
ensureObserver();
|
|
758
|
+
waitForContentThenRun();
|
|
759
|
+
});
|
|
421
760
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
761
|
+
$(window).on('action:topic.loaded.ezoicInfinite', () => {
|
|
762
|
+
ensureObserver();
|
|
763
|
+
waitForContentThenRun();
|
|
764
|
+
});
|
|
426
765
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
766
|
+
$(window).on('action:posts.loaded.ezoicInfinite', () => {
|
|
767
|
+
ensureObserver();
|
|
768
|
+
// posts.loaded = infinite scroll
|
|
769
|
+
waitForContentThenRun();
|
|
770
|
+
});
|
|
431
771
|
}
|
|
432
772
|
|
|
433
|
-
function
|
|
434
|
-
|
|
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
|
+
}
|
|
435
791
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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++;
|
|
442
799
|
|
|
443
|
-
|
|
800
|
+
// Compter les mots sur la page
|
|
801
|
+
const text = document.body.innerText || '';
|
|
802
|
+
const wordCount = text.split(/\s+/).filter(Boolean).length;
|
|
444
803
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
scheduleRun();
|
|
450
|
-
});
|
|
451
|
-
} else {
|
|
452
|
-
window.addEventListener('popstate', () => {
|
|
453
|
-
state.pageKey = null;
|
|
454
|
-
scheduleRun();
|
|
455
|
-
});
|
|
456
|
-
}
|
|
804
|
+
if (wordCount >= MIN_WORDS) {
|
|
805
|
+
// Assez de contenu → lancer l'insertion
|
|
806
|
+
scheduleRun();
|
|
807
|
+
return;
|
|
457
808
|
}
|
|
458
809
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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;
|
|
463
834
|
}
|
|
464
|
-
|
|
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
|
}
|