nodebb-plugin-ezoic-infinite 1.5.21 → 1.5.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/library.js +5 -23
- package/package.json +1 -1
- package/public/client.js +383 -640
package/library.js
CHANGED
|
@@ -9,11 +9,7 @@ const plugin = {};
|
|
|
9
9
|
|
|
10
10
|
function normalizeExcludedGroups(value) {
|
|
11
11
|
if (!value) return [];
|
|
12
|
-
|
|
13
|
-
if (Array.isArray(value)) return value.map(String).map(s => s.trim()).filter(Boolean);
|
|
14
|
-
if (typeof value === 'object') {
|
|
15
|
-
return Object.values(value).map(String).map(s => s.trim()).filter(Boolean);
|
|
16
|
-
}
|
|
12
|
+
if (Array.isArray(value)) return value;
|
|
17
13
|
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
18
14
|
}
|
|
19
15
|
|
|
@@ -70,19 +66,9 @@ async function getSettings() {
|
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
async function isUserExcluded(uid, excludedGroups) {
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
if (!list.length) return false;
|
|
77
|
-
|
|
78
|
-
// Guests (uid=0) are not in groups.getUserGroups; treat explicitly if configured
|
|
79
|
-
if (id === 0) {
|
|
80
|
-
return list.includes('guests') || list.includes('guest');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const userGroups = await groups.getUserGroups([id]);
|
|
84
|
-
const names = (userGroups[0] || []).map(g => (g && g.name) ? g.name : String(g));
|
|
85
|
-
return names.some(name => list.includes(String(name).toLowerCase()));
|
|
69
|
+
if (!uid || !excludedGroups.length) return false;
|
|
70
|
+
const userGroups = await groups.getUserGroups([uid]);
|
|
71
|
+
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
86
72
|
}
|
|
87
73
|
|
|
88
74
|
plugin.onSettingsSet = function (data) {
|
|
@@ -121,11 +107,7 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
121
107
|
|
|
122
108
|
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
123
109
|
const settings = await getSettings();
|
|
124
|
-
const
|
|
125
|
-
: (req.user && req.user.uid) ? req.user.uid
|
|
126
|
-
: (res.locals && res.locals.uid) ? res.locals.uid
|
|
127
|
-
: 0;
|
|
128
|
-
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
110
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
129
111
|
|
|
130
112
|
res.json({
|
|
131
113
|
excluded,
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,32 +1,30 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// NodeBB client context
|
|
5
|
+
const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
postItem: '[component="post"][data-pid]',
|
|
9
|
-
categoryItem: 'li[component="categories/category"]'
|
|
10
|
-
};
|
|
7
|
+
const WRAP_CLASS = 'ezoic-ad';
|
|
8
|
+
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// Insert at most N ads per run to keep the UI smooth on infinite scroll
|
|
11
|
+
const MAX_INSERTS_PER_RUN = 3;
|
|
14
12
|
|
|
15
|
-
//
|
|
16
|
-
|
|
13
|
+
// Preload before viewport (tune if you want even earlier)
|
|
14
|
+
const PRELOAD_ROOT_MARGIN = '1200px 0px';
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const SELECTORS = {
|
|
17
|
+
topicItem: 'li[component="category/topic"]',
|
|
18
|
+
postItem: '[component="post"][data-pid]',
|
|
19
|
+
categoryItem: 'li[component="categories/category"]',
|
|
20
|
+
};
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
|
|
22
|
+
// Hard block during navigation to avoid “placeholder does not exist” spam
|
|
23
|
+
let EZOIC_BLOCKED = false;
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
const state = {
|
|
26
26
|
pageKey: null,
|
|
27
|
-
|
|
28
27
|
cfg: null,
|
|
29
|
-
cfgPromise: null,
|
|
30
28
|
|
|
31
29
|
poolTopics: [],
|
|
32
30
|
poolPosts: [],
|
|
@@ -36,58 +34,32 @@
|
|
|
36
34
|
usedPosts: new Set(),
|
|
37
35
|
usedCategories: new Set(),
|
|
38
36
|
|
|
39
|
-
//
|
|
40
|
-
liveTopics: [],
|
|
41
|
-
livePosts: [],
|
|
42
|
-
liveCategories: [],
|
|
43
|
-
|
|
44
|
-
// Throttle showAds calls per id.
|
|
37
|
+
// throttle per placeholder id
|
|
45
38
|
lastShowById: new Map(),
|
|
46
39
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
activeTimeouts: new Set(),
|
|
40
|
+
// observers / schedulers
|
|
41
|
+
domObs: null,
|
|
42
|
+
io: null,
|
|
43
|
+
runQueued: false,
|
|
52
44
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
obs: null,
|
|
45
|
+
// hero
|
|
46
|
+
heroDoneForPage: false,
|
|
47
|
+
};
|
|
57
48
|
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
const sessionDefinedIds = new Set();
|
|
50
|
+
const insertingIds = new Set();
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
canShowAds: false,
|
|
63
|
-
|
|
64
|
-
// Retry counters.
|
|
65
|
-
poolWaitAttempts: 0,
|
|
66
|
-
awaitItemsAttempts: 0
|
|
67
|
-
};
|
|
52
|
+
// ---------- small utils ----------
|
|
68
53
|
|
|
69
54
|
function normalizeBool(v) {
|
|
70
55
|
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
71
56
|
}
|
|
72
57
|
|
|
73
|
-
function setTimeoutTracked(fn, ms) {
|
|
74
|
-
var id = setTimeout(fn, ms);
|
|
75
|
-
state.activeTimeouts.add(id);
|
|
76
|
-
return id;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function clearAllTrackedTimeouts() {
|
|
80
|
-
state.activeTimeouts.forEach(function (id) {
|
|
81
|
-
try { clearTimeout(id); } catch (e) {}
|
|
82
|
-
});
|
|
83
|
-
state.activeTimeouts.clear();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
58
|
function uniqInts(lines) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
for (
|
|
90
|
-
|
|
59
|
+
const out = [];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
for (const v of lines) {
|
|
62
|
+
const n = parseInt(v, 10);
|
|
91
63
|
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
92
64
|
seen.add(n);
|
|
93
65
|
out.push(n);
|
|
@@ -98,31 +70,31 @@
|
|
|
98
70
|
|
|
99
71
|
function parsePool(raw) {
|
|
100
72
|
if (!raw) return [];
|
|
101
|
-
|
|
73
|
+
const lines = String(raw)
|
|
102
74
|
.split(/\r?\n/)
|
|
103
|
-
.map(
|
|
75
|
+
.map(s => s.trim())
|
|
104
76
|
.filter(Boolean);
|
|
105
77
|
return uniqInts(lines);
|
|
106
78
|
}
|
|
107
79
|
|
|
108
80
|
function getPageKey() {
|
|
109
81
|
try {
|
|
110
|
-
|
|
82
|
+
const ax = window.ajaxify;
|
|
111
83
|
if (ax && ax.data) {
|
|
112
|
-
if (ax.data.tid) return
|
|
113
|
-
if (ax.data.cid) return
|
|
84
|
+
if (ax.data.tid) return `topic:${ax.data.tid}`;
|
|
85
|
+
if (ax.data.cid) return `cid:${ax.data.cid}:${window.location.pathname}`;
|
|
114
86
|
}
|
|
115
87
|
} catch (e) {}
|
|
116
88
|
return window.location.pathname;
|
|
117
89
|
}
|
|
118
90
|
|
|
119
91
|
function getKind() {
|
|
120
|
-
|
|
92
|
+
const p = window.location.pathname || '';
|
|
121
93
|
if (/^\/topic\//.test(p)) return 'topic';
|
|
122
94
|
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
123
95
|
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
124
96
|
|
|
125
|
-
//
|
|
97
|
+
// fallback by DOM
|
|
126
98
|
if (document.querySelector(SELECTORS.categoryItem)) return 'categories';
|
|
127
99
|
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
128
100
|
if (document.querySelector(SELECTORS.topicItem)) return 'categoryTopics';
|
|
@@ -138,714 +110,485 @@
|
|
|
138
110
|
}
|
|
139
111
|
|
|
140
112
|
function getPostContainers() {
|
|
141
|
-
|
|
142
|
-
return nodes.filter(
|
|
113
|
+
const nodes = Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
114
|
+
return nodes.filter((el) => {
|
|
143
115
|
if (!el || !el.isConnected) return false;
|
|
144
116
|
if (!el.querySelector('[component="post/content"]')) return false;
|
|
145
|
-
|
|
146
|
-
// Prevent nested / duplicated post wrappers.
|
|
147
|
-
var parentPost = el.parentElement && el.parentElement.closest('[component="post"][data-pid]');
|
|
117
|
+
const parentPost = el.parentElement && el.parentElement.closest('[component="post"][data-pid]');
|
|
148
118
|
if (parentPost && parentPost !== el) return false;
|
|
149
119
|
if (el.getAttribute('component') === 'post/parent') return false;
|
|
150
|
-
|
|
151
120
|
return true;
|
|
152
121
|
});
|
|
153
122
|
}
|
|
154
123
|
|
|
155
|
-
|
|
156
|
-
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function destroyPlaceholderIds(ids) {
|
|
160
|
-
if (!ids || !ids.length) return;
|
|
124
|
+
// ---------- warm-up & patching ----------
|
|
161
125
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
126
|
+
const _warmLinksDone = new Set();
|
|
127
|
+
function warmUpNetwork() {
|
|
128
|
+
try {
|
|
129
|
+
const head = document.head || document.getElementsByTagName('head')[0];
|
|
130
|
+
if (!head) return;
|
|
131
|
+
const links = [
|
|
132
|
+
['preconnect', 'https://g.ezoic.net', true],
|
|
133
|
+
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
134
|
+
['preconnect', 'https://go.ezoic.net', true],
|
|
135
|
+
['dns-prefetch', 'https://go.ezoic.net', false],
|
|
136
|
+
];
|
|
137
|
+
for (const [rel, href, cors] of links) {
|
|
138
|
+
const key = `${rel}|${href}`;
|
|
139
|
+
if (_warmLinksDone.has(key)) continue;
|
|
140
|
+
_warmLinksDone.add(key);
|
|
141
|
+
const link = document.createElement('link');
|
|
142
|
+
link.rel = rel;
|
|
143
|
+
link.href = href;
|
|
144
|
+
if (cors) link.crossOrigin = 'anonymous';
|
|
145
|
+
head.appendChild(link);
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {}
|
|
148
|
+
}
|
|
168
149
|
|
|
169
|
-
|
|
150
|
+
// Patch showAds to avoid warnings when a placeholder disappears (infinite scroll, ajaxify)
|
|
151
|
+
function patchShowAds() {
|
|
152
|
+
const applyPatch = () => {
|
|
170
153
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
154
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
155
|
+
const ez = window.ezstandalone;
|
|
156
|
+
if (window.__nodebbEzoicPatched) return;
|
|
157
|
+
if (typeof ez.showAds !== 'function') return;
|
|
158
|
+
|
|
159
|
+
window.__nodebbEzoicPatched = true;
|
|
160
|
+
const orig = ez.showAds;
|
|
161
|
+
|
|
162
|
+
ez.showAds = function (...args) {
|
|
163
|
+
if (EZOIC_BLOCKED) return;
|
|
164
|
+
|
|
165
|
+
let ids = [];
|
|
166
|
+
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
167
|
+
else ids = args;
|
|
168
|
+
|
|
169
|
+
const seen = new Set();
|
|
170
|
+
for (const v of ids) {
|
|
171
|
+
const id = parseInt(v, 10);
|
|
172
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
173
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
174
|
+
if (!ph || !ph.isConnected) continue;
|
|
175
|
+
seen.add(id);
|
|
176
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
174
179
|
} catch (e) {}
|
|
175
180
|
};
|
|
176
181
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
function getRecyclable(liveArr) {
|
|
186
|
-
var margin = 600; // px above viewport
|
|
187
|
-
for (var i = 0; i < liveArr.length; i++) {
|
|
188
|
-
var entry = liveArr[i];
|
|
189
|
-
if (!entry || !entry.wrap || !entry.wrap.isConnected) {
|
|
190
|
-
liveArr.splice(i, 1);
|
|
191
|
-
i--;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
var r = safeRect(entry.wrap);
|
|
195
|
-
if (r && r.bottom < -margin) {
|
|
196
|
-
liveArr.splice(i, 1);
|
|
197
|
-
return entry;
|
|
198
|
-
}
|
|
182
|
+
applyPatch();
|
|
183
|
+
if (!window.__nodebbEzoicPatched) {
|
|
184
|
+
try {
|
|
185
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
186
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
187
|
+
window.ezstandalone.cmd.push(applyPatch);
|
|
188
|
+
} catch (e) {}
|
|
199
189
|
}
|
|
200
|
-
return null;
|
|
201
190
|
}
|
|
202
191
|
|
|
203
|
-
|
|
204
|
-
if (pool.length) return { id: pool.shift(), recycled: null };
|
|
192
|
+
// ---------- config & pools ----------
|
|
205
193
|
|
|
206
|
-
|
|
207
|
-
if (
|
|
194
|
+
async function fetchConfigOnce() {
|
|
195
|
+
if (state.cfg) return state.cfg;
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
198
|
+
if (!res.ok) return null;
|
|
199
|
+
state.cfg = await res.json();
|
|
200
|
+
return state.cfg;
|
|
201
|
+
} catch (e) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
208
205
|
|
|
209
|
-
|
|
206
|
+
function initPools(cfg) {
|
|
207
|
+
if (!cfg) return;
|
|
208
|
+
if (state.poolTopics.length === 0) state.poolTopics = parsePool(cfg.placeholderIds);
|
|
209
|
+
if (state.poolPosts.length === 0) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
|
|
210
|
+
if (state.poolCategories.length === 0) state.poolCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
|
|
213
|
-
if (!wrap) return null;
|
|
214
|
-
try { wrap.innerHTML = ''; } catch (e) {}
|
|
213
|
+
// ---------- insertion primitives ----------
|
|
215
214
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return
|
|
215
|
+
function isAdjacentAd(target) {
|
|
216
|
+
if (!target) return false;
|
|
217
|
+
const next = target.nextElementSibling;
|
|
218
|
+
if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
|
|
219
|
+
const prev = target.previousElementSibling;
|
|
220
|
+
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
|
|
221
|
+
return false;
|
|
220
222
|
}
|
|
221
223
|
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
function buildWrap(id, kindClass, afterPos) {
|
|
225
|
+
const wrap = document.createElement('div');
|
|
226
|
+
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
227
|
+
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
228
|
+
wrap.style.width = '100%';
|
|
226
229
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
const ph = document.createElement('div');
|
|
231
|
+
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
232
|
+
ph.setAttribute('data-ezoic-id', String(id));
|
|
233
|
+
wrap.appendChild(ph);
|
|
231
234
|
|
|
232
|
-
function buildWrap(id, kindClass) {
|
|
233
|
-
var wrap = document.createElement('div');
|
|
234
|
-
wrap.className = WRAP_CLASS + ' ' + kindClass;
|
|
235
|
-
wrap.setAttribute('data-ezoic-id', String(id));
|
|
236
|
-
resetPlaceholderInWrap(wrap, id);
|
|
237
235
|
return wrap;
|
|
238
236
|
}
|
|
239
237
|
|
|
240
238
|
function findWrap(kindClass, afterPos) {
|
|
241
|
-
|
|
242
|
-
return document.querySelector('.' + kindClass + '[data-after-pos="' + afterPos + '"]');
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function insertAfter(el, id, kindClass, afterPos) {
|
|
246
|
-
try {
|
|
247
|
-
var wrap = buildWrap(id, kindClass);
|
|
248
|
-
wrap.setAttribute('data-after-pos', String(afterPos));
|
|
249
|
-
|
|
250
|
-
if (!el || !el.parentNode) return null;
|
|
251
|
-
if (el.nextSibling) el.parentNode.insertBefore(wrap, el.nextSibling);
|
|
252
|
-
else el.parentNode.appendChild(wrap);
|
|
253
|
-
|
|
254
|
-
attachFillObserver(wrap, id);
|
|
255
|
-
return wrap;
|
|
256
|
-
} catch (e) {}
|
|
257
|
-
return null;
|
|
239
|
+
return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
|
|
258
240
|
}
|
|
259
241
|
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
state.usedCategories.forEach(function (id) { ids.push(id); });
|
|
242
|
+
function insertAfter(target, id, kindClass, afterPos) {
|
|
243
|
+
if (!target || !target.insertAdjacentElement) return null;
|
|
244
|
+
if (findWrap(kindClass, afterPos)) return null;
|
|
245
|
+
if (insertingIds.has(id)) return null;
|
|
265
246
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
247
|
+
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
248
|
+
if (existingPh && existingPh.isConnected) return null;
|
|
269
249
|
|
|
270
|
-
|
|
271
|
-
// Some Ezoic setups require calling showAds via ezstandalone.cmd.
|
|
272
|
-
// We keep existing behavior but make it resilient.
|
|
250
|
+
insertingIds.add(id);
|
|
273
251
|
try {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
function markFilled(id) {
|
|
280
|
-
try { sessionDefinedIds.add(id); } catch (e) {}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function isWrapMarkedFilled(wrap) {
|
|
284
|
-
try { return !!(wrap && wrap.getAttribute && wrap.getAttribute('data-ezoic-filled') === '1'); } catch (e) { return false; }
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function attachFillObserver(wrap, id) {
|
|
288
|
-
if (!wrap || !wrap.isConnected) return;
|
|
289
|
-
|
|
290
|
-
// If already filled, mark and return.
|
|
291
|
-
if (isPlaceholderFilled(wrap)) {
|
|
292
|
-
try { wrap.setAttribute('data-ezoic-filled', '1'); } catch (e) {}
|
|
293
|
-
markFilled(id);
|
|
294
|
-
return;
|
|
252
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
253
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
254
|
+
return wrap;
|
|
255
|
+
} finally {
|
|
256
|
+
insertingIds.delete(id);
|
|
295
257
|
}
|
|
296
|
-
|
|
297
|
-
// Observe for Ezoic inserting ad content into placeholder.
|
|
298
|
-
try {
|
|
299
|
-
var obs = new MutationObserver(function () {
|
|
300
|
-
if (!wrap.isConnected) {
|
|
301
|
-
try { obs.disconnect(); } catch (e) {}
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
if (isPlaceholderFilled(wrap)) {
|
|
305
|
-
try { wrap.setAttribute('data-ezoic-filled', '1'); } catch (e) {}
|
|
306
|
-
markFilled(id);
|
|
307
|
-
try { obs.disconnect(); } catch (e) {}
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
obs.observe(wrap, { childList: true, subtree: true });
|
|
311
|
-
wrap.__ezoicFillObs = obs;
|
|
312
|
-
} catch (e) {}
|
|
313
258
|
}
|
|
314
259
|
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
var ph = wrap.querySelector('[id^="' + PLACEHOLDER_PREFIX + '"]');
|
|
319
|
-
if (!ph) return false;
|
|
320
|
-
if (ph.children && ph.children.length) return true;
|
|
321
|
-
var r = safeRect(wrap);
|
|
322
|
-
if (r && r.height > 20) return true;
|
|
323
|
-
} catch (e) {}
|
|
324
|
-
return false;
|
|
260
|
+
function pickId(pool) {
|
|
261
|
+
return pool.length ? pool.shift() : null;
|
|
325
262
|
}
|
|
326
263
|
|
|
327
|
-
function
|
|
328
|
-
if (!
|
|
329
|
-
|
|
330
|
-
// Ezoic expects DOM to be settled.
|
|
331
|
-
var call = function () {
|
|
332
|
-
try {
|
|
333
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
334
|
-
window.ezstandalone.showAds(ids);
|
|
335
|
-
}
|
|
336
|
-
} catch (e) {}
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') call();
|
|
341
|
-
else if (window.ezstandalone && window.ezstandalone.cmd && Array.isArray(window.ezstandalone.cmd)) window.ezstandalone.cmd.push(call);
|
|
342
|
-
} catch (e) {}
|
|
343
|
-
}
|
|
264
|
+
function showAd(id) {
|
|
265
|
+
if (!id || EZOIC_BLOCKED) return;
|
|
344
266
|
|
|
345
|
-
|
|
346
|
-
|
|
267
|
+
const now = Date.now();
|
|
268
|
+
const last = state.lastShowById.get(id) || 0;
|
|
269
|
+
if (now - last < 1500) return; // basic throttle
|
|
347
270
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
var last = state.lastShowById.get(id) || 0;
|
|
351
|
-
if (now - last < 1200) return;
|
|
271
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
272
|
+
if (!ph || !ph.isConnected) return;
|
|
352
273
|
|
|
353
|
-
if (state.pendingById.has(id)) return;
|
|
354
|
-
state.pendingById.add(id);
|
|
355
274
|
state.lastShowById.set(id, now);
|
|
356
275
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
insertingIds.add(id);
|
|
363
|
-
|
|
364
|
-
var attempts = 0;
|
|
365
|
-
|
|
366
|
-
(function waitForPh() {
|
|
367
|
-
attempts++;
|
|
276
|
+
try {
|
|
277
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
278
|
+
const ez = window.ezstandalone;
|
|
368
279
|
|
|
369
|
-
//
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
|
|
280
|
+
// Fast path
|
|
281
|
+
if (typeof ez.showAds === 'function') {
|
|
282
|
+
ez.showAds(id);
|
|
283
|
+
sessionDefinedIds.add(id);
|
|
373
284
|
return;
|
|
374
285
|
}
|
|
375
286
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
doCall();
|
|
390
|
-
state.pendingById.delete(id);
|
|
391
|
-
insertingIds.delete(id);
|
|
392
|
-
return;
|
|
287
|
+
// Queue once for when Ezoic is ready
|
|
288
|
+
ez.cmd = ez.cmd || [];
|
|
289
|
+
if (!ph.__ezoicQueued) {
|
|
290
|
+
ph.__ezoicQueued = true;
|
|
291
|
+
ez.cmd.push(() => {
|
|
292
|
+
try {
|
|
293
|
+
if (EZOIC_BLOCKED) return;
|
|
294
|
+
const el = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
295
|
+
if (!el || !el.isConnected) return;
|
|
296
|
+
window.ezstandalone.showAds(id);
|
|
297
|
+
sessionDefinedIds.add(id);
|
|
298
|
+
} catch (e) {}
|
|
299
|
+
});
|
|
393
300
|
}
|
|
301
|
+
} catch (e) {}
|
|
302
|
+
}
|
|
394
303
|
|
|
395
|
-
|
|
396
|
-
setTimeoutTracked(waitForPh, 50);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
304
|
+
// ---------- preload / above-the-fold ----------
|
|
399
305
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
306
|
+
function ensurePreloadObserver() {
|
|
307
|
+
if (state.io) return state.io;
|
|
308
|
+
try {
|
|
309
|
+
state.io = new IntersectionObserver((entries) => {
|
|
310
|
+
for (const ent of entries) {
|
|
311
|
+
if (!ent.isIntersecting) continue;
|
|
312
|
+
const el = ent.target;
|
|
313
|
+
try { state.io && state.io.unobserve(el); } catch (e) {}
|
|
314
|
+
|
|
315
|
+
const idAttr = el && el.getAttribute && el.getAttribute('data-ezoic-id');
|
|
316
|
+
const id = parseInt(idAttr, 10);
|
|
317
|
+
if (Number.isFinite(id) && id > 0) showAd(id);
|
|
318
|
+
}
|
|
319
|
+
}, { root: null, rootMargin: PRELOAD_ROOT_MARGIN, threshold: 0.01 });
|
|
320
|
+
} catch (e) {
|
|
321
|
+
state.io = null;
|
|
322
|
+
}
|
|
323
|
+
return state.io;
|
|
404
324
|
}
|
|
405
325
|
|
|
406
|
-
function
|
|
407
|
-
|
|
408
|
-
if (!
|
|
409
|
-
|
|
326
|
+
function observePlaceholder(id) {
|
|
327
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
328
|
+
if (!ph || !ph.isConnected) return;
|
|
329
|
+
const io = ensurePreloadObserver();
|
|
330
|
+
try { io && io.observe(ph); } catch (e) {}
|
|
331
|
+
|
|
332
|
+
// If already above fold, fire immediately
|
|
333
|
+
try {
|
|
334
|
+
const r = ph.getBoundingClientRect();
|
|
335
|
+
if (r.top < window.innerHeight * 1.5 && r.bottom > -200) showAd(id);
|
|
336
|
+
} catch (e) {}
|
|
410
337
|
}
|
|
411
338
|
|
|
339
|
+
// ---------- insertion logic ----------
|
|
340
|
+
|
|
412
341
|
function computeTargets(count, interval, showFirst) {
|
|
413
|
-
|
|
342
|
+
const out = [];
|
|
414
343
|
if (count <= 0) return out;
|
|
415
|
-
|
|
416
344
|
if (showFirst) out.push(1);
|
|
417
|
-
|
|
418
|
-
for (var i = 1; i <= count; i++) {
|
|
345
|
+
for (let i = 1; i <= count; i++) {
|
|
419
346
|
if (i % interval === 0) out.push(i);
|
|
420
347
|
}
|
|
421
|
-
|
|
422
|
-
// Unique + sorted.
|
|
423
|
-
return Array.from(new Set(out)).sort(function (a, b) { return a - b; });
|
|
348
|
+
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
424
349
|
}
|
|
425
350
|
|
|
426
|
-
function injectBetween(kindClass, items, interval, showFirst,
|
|
427
|
-
if (!items
|
|
351
|
+
function injectBetween(kindClass, items, interval, showFirst, pool, usedSet) {
|
|
352
|
+
if (!items.length) return 0;
|
|
428
353
|
|
|
429
|
-
|
|
430
|
-
|
|
354
|
+
const targets = computeTargets(items.length, interval, showFirst);
|
|
355
|
+
let inserted = 0;
|
|
431
356
|
|
|
432
|
-
for (
|
|
433
|
-
var afterPos = targets[t];
|
|
357
|
+
for (const afterPos of targets) {
|
|
434
358
|
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
435
359
|
|
|
436
|
-
|
|
360
|
+
const el = items[afterPos - 1];
|
|
437
361
|
if (!el || !el.isConnected) continue;
|
|
438
|
-
|
|
439
|
-
// Prevent adjacent ads.
|
|
440
|
-
if (isAdjacentAd(el) || isPrevAd(el)) continue;
|
|
441
|
-
|
|
442
|
-
// Prevent duplicates at same logical position.
|
|
443
|
-
if (findWrap(kindClass, afterPos - 1)) continue;
|
|
362
|
+
if (isAdjacentAd(el)) continue;
|
|
444
363
|
if (findWrap(kindClass, afterPos)) continue;
|
|
445
364
|
|
|
446
|
-
|
|
447
|
-
var id = pick.id;
|
|
365
|
+
const id = pickId(pool);
|
|
448
366
|
if (!id) break;
|
|
449
367
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
var oldWrap = pick.recycled.wrap;
|
|
458
|
-
try { if (oldWrap && oldWrap.__ezoicFillObs) oldWrap.__ezoicFillObs.disconnect(); } catch (e) {}
|
|
459
|
-
try { if (oldWrap) oldWrap.remove(); } catch (e) {}
|
|
368
|
+
usedSet.add(id);
|
|
369
|
+
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
370
|
+
if (!wrap) {
|
|
371
|
+
usedSet.delete(id);
|
|
372
|
+
pool.unshift(id);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
460
375
|
|
|
461
|
-
|
|
462
|
-
|
|
376
|
+
observePlaceholder(id);
|
|
377
|
+
inserted += 1;
|
|
378
|
+
}
|
|
463
379
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
} else {
|
|
467
|
-
usedSet.add(id);
|
|
468
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
469
|
-
if (!wrap) continue;
|
|
380
|
+
return inserted;
|
|
381
|
+
}
|
|
470
382
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
383
|
+
async function insertHeroAdEarly() {
|
|
384
|
+
if (state.heroDoneForPage) return;
|
|
385
|
+
const cfg = await fetchConfigOnce();
|
|
386
|
+
if (!cfg || cfg.excluded) return;
|
|
387
|
+
|
|
388
|
+
initPools(cfg);
|
|
389
|
+
|
|
390
|
+
const kind = getKind();
|
|
391
|
+
let items = [];
|
|
392
|
+
let pool = null;
|
|
393
|
+
let usedSet = null;
|
|
394
|
+
let kindClass = '';
|
|
395
|
+
|
|
396
|
+
if (kind === 'topic' && normalizeBool(cfg.enableMessageAds)) {
|
|
397
|
+
items = getPostContainers();
|
|
398
|
+
pool = state.poolPosts;
|
|
399
|
+
usedSet = state.usedPosts;
|
|
400
|
+
kindClass = 'ezoic-ad-message';
|
|
401
|
+
} else if (kind === 'categoryTopics' && normalizeBool(cfg.enableBetweenAds)) {
|
|
402
|
+
items = getTopicItems();
|
|
403
|
+
pool = state.poolTopics;
|
|
404
|
+
usedSet = state.usedTopics;
|
|
405
|
+
kindClass = 'ezoic-ad-between';
|
|
406
|
+
} else if (kind === 'categories' && normalizeBool(cfg.enableCategoryAds)) {
|
|
407
|
+
items = getCategoryItems();
|
|
408
|
+
pool = state.poolCategories;
|
|
409
|
+
usedSet = state.usedCategories;
|
|
410
|
+
kindClass = 'ezoic-ad-categories';
|
|
411
|
+
} else {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
474
414
|
|
|
475
|
-
|
|
415
|
+
if (!items.length) return;
|
|
476
416
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
417
|
+
// Insert after the very first item (above-the-fold)
|
|
418
|
+
const afterPos = 1;
|
|
419
|
+
const el = items[afterPos - 1];
|
|
420
|
+
if (!el || !el.isConnected) return;
|
|
421
|
+
if (isAdjacentAd(el)) return;
|
|
422
|
+
if (findWrap(kindClass, afterPos)) { state.heroDoneForPage = true; return; }
|
|
482
423
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
usedSet.delete(id);
|
|
486
|
-
}
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
424
|
+
const id = pickId(pool);
|
|
425
|
+
if (!id) return;
|
|
489
426
|
|
|
490
|
-
|
|
427
|
+
usedSet.add(id);
|
|
428
|
+
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
429
|
+
if (!wrap) {
|
|
430
|
+
usedSet.delete(id);
|
|
431
|
+
pool.unshift(id);
|
|
432
|
+
return;
|
|
491
433
|
}
|
|
492
434
|
|
|
493
|
-
|
|
435
|
+
state.heroDoneForPage = true;
|
|
436
|
+
observePlaceholder(id);
|
|
494
437
|
}
|
|
495
438
|
|
|
496
|
-
function
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
439
|
+
async function runCore() {
|
|
440
|
+
if (EZOIC_BLOCKED) return;
|
|
441
|
+
|
|
442
|
+
patchShowAds();
|
|
443
|
+
|
|
444
|
+
const cfg = await fetchConfigOnce();
|
|
445
|
+
if (!cfg || cfg.excluded) return;
|
|
446
|
+
initPools(cfg);
|
|
447
|
+
|
|
448
|
+
const kind = getKind();
|
|
449
|
+
|
|
450
|
+
if (kind === 'topic') {
|
|
451
|
+
if (normalizeBool(cfg.enableMessageAds)) {
|
|
452
|
+
injectBetween(
|
|
453
|
+
'ezoic-ad-message',
|
|
454
|
+
getPostContainers(),
|
|
455
|
+
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
456
|
+
normalizeBool(cfg.showFirstMessageAd),
|
|
457
|
+
state.poolPosts,
|
|
458
|
+
state.usedPosts
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
} else if (kind === 'categoryTopics') {
|
|
462
|
+
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
463
|
+
injectBetween(
|
|
464
|
+
'ezoic-ad-between',
|
|
465
|
+
getTopicItems(),
|
|
466
|
+
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
467
|
+
normalizeBool(cfg.showFirstTopicAd),
|
|
468
|
+
state.poolTopics,
|
|
469
|
+
state.usedTopics
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
} else if (kind === 'categories') {
|
|
473
|
+
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
474
|
+
injectBetween(
|
|
475
|
+
'ezoic-ad-categories',
|
|
476
|
+
getCategoryItems(),
|
|
477
|
+
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
478
|
+
normalizeBool(cfg.showFirstCategoryAd),
|
|
479
|
+
state.poolCategories,
|
|
480
|
+
state.usedCategories
|
|
481
|
+
);
|
|
514
482
|
}
|
|
515
483
|
}
|
|
516
484
|
}
|
|
517
485
|
|
|
518
|
-
function
|
|
519
|
-
|
|
520
|
-
state.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (state.obs) {
|
|
529
|
-
try { state.obs.disconnect(); } catch (e) {}
|
|
530
|
-
state.obs = null;
|
|
531
|
-
}
|
|
486
|
+
function scheduleRun() {
|
|
487
|
+
if (state.runQueued) return;
|
|
488
|
+
state.runQueued = true;
|
|
489
|
+
window.requestAnimationFrame(() => {
|
|
490
|
+
state.runQueued = false;
|
|
491
|
+
const pk = getPageKey();
|
|
492
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
493
|
+
runCore().catch(() => {});
|
|
494
|
+
});
|
|
495
|
+
}
|
|
532
496
|
|
|
533
|
-
|
|
534
|
-
destroyUsedPlaceholders();
|
|
497
|
+
// ---------- observers / lifecycle ----------
|
|
535
498
|
|
|
536
|
-
|
|
499
|
+
function cleanup() {
|
|
500
|
+
EZOIC_BLOCKED = true;
|
|
501
|
+
|
|
502
|
+
// remove all wrappers
|
|
537
503
|
try {
|
|
538
|
-
document.querySelectorAll(
|
|
539
|
-
try { if (el && el.__ezoicFillObs) el.__ezoicFillObs.disconnect(); } catch (e) {}
|
|
504
|
+
document.querySelectorAll(`.${WRAP_CLASS}`).forEach((el) => {
|
|
540
505
|
try { el.remove(); } catch (e) {}
|
|
541
506
|
});
|
|
542
507
|
} catch (e) {}
|
|
543
508
|
|
|
544
|
-
//
|
|
545
|
-
state.pageKey = getPageKey();
|
|
509
|
+
// reset state
|
|
546
510
|
state.cfg = null;
|
|
547
|
-
state.cfgPromise = null;
|
|
548
|
-
|
|
549
511
|
state.poolTopics = [];
|
|
550
512
|
state.poolPosts = [];
|
|
551
513
|
state.poolCategories = [];
|
|
552
|
-
|
|
553
514
|
state.usedTopics.clear();
|
|
554
515
|
state.usedPosts.clear();
|
|
555
516
|
state.usedCategories.clear();
|
|
556
|
-
|
|
557
|
-
state.liveTopics = [];
|
|
558
|
-
state.livePosts = [];
|
|
559
|
-
state.liveCategories = [];
|
|
560
|
-
|
|
561
517
|
state.lastShowById.clear();
|
|
562
|
-
state.
|
|
563
|
-
insertingIds.clear();
|
|
518
|
+
state.heroDoneForPage = false;
|
|
564
519
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
state.timer = null;
|
|
569
|
-
}
|
|
520
|
+
sessionDefinedIds.clear();
|
|
521
|
+
|
|
522
|
+
// keep observers alive (MutationObserver will re-trigger after navigation)
|
|
570
523
|
}
|
|
571
524
|
|
|
572
|
-
function
|
|
573
|
-
if (state.
|
|
525
|
+
function ensureDomObserver() {
|
|
526
|
+
if (state.domObs) return;
|
|
527
|
+
state.domObs = new MutationObserver(() => {
|
|
528
|
+
if (!EZOIC_BLOCKED) scheduleRun();
|
|
529
|
+
});
|
|
574
530
|
try {
|
|
575
|
-
state.
|
|
576
|
-
state.obs.observe(document.body, { childList: true, subtree: true });
|
|
531
|
+
state.domObs.observe(document.body, { childList: true, subtree: true });
|
|
577
532
|
} catch (e) {}
|
|
578
533
|
}
|
|
579
534
|
|
|
580
|
-
function
|
|
581
|
-
if (state.scheduled) return;
|
|
582
|
-
state.scheduled = true;
|
|
583
|
-
|
|
584
|
-
if (state.timer) {
|
|
585
|
-
try { clearTimeout(state.timer); } catch (e) {}
|
|
586
|
-
state.timer = null;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
state.timer = setTimeoutTracked(function () {
|
|
590
|
-
state.scheduled = false;
|
|
591
|
-
|
|
592
|
-
// If user navigated away, stop.
|
|
593
|
-
var pk = getPageKey();
|
|
594
|
-
if (state.pageKey && pk !== state.pageKey) return;
|
|
595
|
-
|
|
596
|
-
runCore().catch(function () {});
|
|
597
|
-
}, 80);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function waitForItemsThenRun(kind) {
|
|
601
|
-
// If list isn't in DOM yet (ajaxify transition), retry a bit.
|
|
602
|
-
var count = 0;
|
|
603
|
-
if (kind === 'topic') count = getPostContainers().length;
|
|
604
|
-
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
605
|
-
else if (kind === 'categories') count = getCategoryItems().length;
|
|
606
|
-
|
|
607
|
-
if (count > 0) return true;
|
|
608
|
-
|
|
609
|
-
if (state.awaitItemsAttempts < 25) {
|
|
610
|
-
state.awaitItemsAttempts++;
|
|
611
|
-
setTimeoutTracked(function () { scheduleRun('await-items'); }, 120);
|
|
612
|
-
}
|
|
613
|
-
return false;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function waitForContentThenRun() {
|
|
617
|
-
// Avoid inserting ads on pages with too little content.
|
|
618
|
-
var MIN_WORDS = 250;
|
|
619
|
-
var attempts = 0;
|
|
620
|
-
var maxAttempts = 20; // 20 × 200ms = 4s
|
|
621
|
-
|
|
622
|
-
(function check() {
|
|
623
|
-
attempts++;
|
|
624
|
-
|
|
625
|
-
var text = '';
|
|
626
|
-
try { text = document.body.innerText || ''; } catch (e) {}
|
|
627
|
-
var wordCount = text.split(/\s+/).filter(Boolean).length;
|
|
628
|
-
|
|
629
|
-
if (wordCount >= MIN_WORDS) {
|
|
630
|
-
scheduleRun('content-ok');
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (attempts >= maxAttempts) {
|
|
635
|
-
scheduleRun('content-timeout');
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
setTimeoutTracked(check, 200);
|
|
640
|
-
})();
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function waitForEzoicThenRun() {
|
|
644
|
-
var attempts = 0;
|
|
645
|
-
var maxAttempts = 50; // 50 × 200ms = 10s
|
|
646
|
-
|
|
647
|
-
(function check() {
|
|
648
|
-
attempts++;
|
|
649
|
-
|
|
650
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
651
|
-
scheduleRun('ezoic-ready');
|
|
652
|
-
waitForContentThenRun();
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (attempts >= maxAttempts) {
|
|
657
|
-
scheduleRun('ezoic-timeout');
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
setTimeoutTracked(check, 200);
|
|
662
|
-
})();
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function fetchConfig() {
|
|
666
|
-
if (state.cfg) return Promise.resolve(state.cfg);
|
|
667
|
-
if (state.cfgPromise) return state.cfgPromise;
|
|
668
|
-
|
|
669
|
-
state.cfgPromise = (function () {
|
|
670
|
-
var MAX_TRIES = 3;
|
|
671
|
-
var delay = 800;
|
|
672
|
-
|
|
673
|
-
function attemptFetch(attempt) {
|
|
674
|
-
return fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' })
|
|
675
|
-
.then(function (res) {
|
|
676
|
-
if (!res || !res.ok) throw new Error('bad response');
|
|
677
|
-
return res.json();
|
|
678
|
-
})
|
|
679
|
-
.then(function (json) {
|
|
680
|
-
state.cfg = json;
|
|
681
|
-
return json;
|
|
682
|
-
})
|
|
683
|
-
.catch(function () {
|
|
684
|
-
if (attempt >= MAX_TRIES) return null;
|
|
685
|
-
return new Promise(function (r) { setTimeoutTracked(r, delay); }).then(function () {
|
|
686
|
-
delay *= 2;
|
|
687
|
-
return attemptFetch(attempt + 1);
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
return attemptFetch(1).finally(function () { state.cfgPromise = null; });
|
|
693
|
-
})();
|
|
694
|
-
|
|
695
|
-
return state.cfgPromise;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function runCore() {
|
|
699
|
-
// Navigation safety: never insert during ajaxify teardown.
|
|
700
|
-
if (!state.canShowAds) return Promise.resolve();
|
|
701
|
-
|
|
702
|
-
patchShowAds();
|
|
703
|
-
|
|
704
|
-
return fetchConfig().then(function (cfg) {
|
|
705
|
-
if (!cfg || cfg.excluded) return;
|
|
706
|
-
|
|
707
|
-
initPools(cfg);
|
|
708
|
-
|
|
709
|
-
var kind = getKind();
|
|
710
|
-
var inserted = 0;
|
|
711
|
-
|
|
712
|
-
if (!waitForItemsThenRun(kind)) return;
|
|
713
|
-
|
|
714
|
-
if (kind === 'topic') {
|
|
715
|
-
if (normalizeBool(cfg.enableMessageAds)) {
|
|
716
|
-
inserted = injectBetween(
|
|
717
|
-
'ezoic-ad-message',
|
|
718
|
-
getPostContainers(),
|
|
719
|
-
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
720
|
-
normalizeBool(cfg.showFirstMessageAd),
|
|
721
|
-
state.poolPosts,
|
|
722
|
-
state.usedPosts,
|
|
723
|
-
state.livePosts
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
} else if (kind === 'categoryTopics') {
|
|
727
|
-
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
728
|
-
inserted = injectBetween(
|
|
729
|
-
'ezoic-ad-between',
|
|
730
|
-
getTopicItems(),
|
|
731
|
-
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
732
|
-
normalizeBool(cfg.showFirstTopicAd),
|
|
733
|
-
state.poolTopics,
|
|
734
|
-
state.usedTopics,
|
|
735
|
-
state.liveTopics
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
} else if (kind === 'categories') {
|
|
739
|
-
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
740
|
-
inserted = injectBetween(
|
|
741
|
-
'ezoic-ad-categories',
|
|
742
|
-
getCategoryItems(),
|
|
743
|
-
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
744
|
-
normalizeBool(cfg.showFirstCategoryAd),
|
|
745
|
-
state.poolCategories,
|
|
746
|
-
state.usedCategories,
|
|
747
|
-
state.liveCategories
|
|
748
|
-
);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
enforceNoAdjacentAds();
|
|
753
|
-
|
|
754
|
-
// Recycling: if pool is exhausted, retry a few times to allow old wrappers to scroll off-screen.
|
|
755
|
-
if (inserted === 0) {
|
|
756
|
-
if (state.poolWaitAttempts < 8) {
|
|
757
|
-
state.poolWaitAttempts++;
|
|
758
|
-
setTimeoutTracked(function () { scheduleRun('pool-wait'); }, 400);
|
|
759
|
-
}
|
|
760
|
-
} else {
|
|
761
|
-
// Reset pool wait attempts once we successfully insert something.
|
|
762
|
-
state.poolWaitAttempts = 0;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// If we hit max inserts, continue quickly.
|
|
766
|
-
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
767
|
-
setTimeoutTracked(function () { scheduleRun('continue'); }, 140);
|
|
768
|
-
}
|
|
769
|
-
}).catch(function () {});
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
function bind() {
|
|
535
|
+
function bindNodeBB() {
|
|
773
536
|
if (!$) return;
|
|
774
537
|
|
|
775
538
|
$(window).off('.ezoicInfinite');
|
|
776
539
|
|
|
777
|
-
$(window).on('action:ajaxify.start.ezoicInfinite',
|
|
540
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => {
|
|
778
541
|
cleanup();
|
|
779
542
|
});
|
|
780
543
|
|
|
781
|
-
$(window).on('action:ajaxify.end.ezoicInfinite',
|
|
544
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
782
545
|
state.pageKey = getPageKey();
|
|
783
|
-
|
|
546
|
+
EZOIC_BLOCKED = false;
|
|
784
547
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}, 300);
|
|
790
|
-
});
|
|
548
|
+
warmUpNetwork();
|
|
549
|
+
patchShowAds();
|
|
550
|
+
ensurePreloadObserver();
|
|
551
|
+
ensureDomObserver();
|
|
791
552
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
ensureObserver();
|
|
795
|
-
waitForContentThenRun();
|
|
796
|
-
});
|
|
553
|
+
// Ultra-fast above-the-fold first
|
|
554
|
+
insertHeroAdEarly().catch(() => {});
|
|
797
555
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
waitForContentThenRun();
|
|
556
|
+
// Then normal insertion
|
|
557
|
+
scheduleRun();
|
|
801
558
|
});
|
|
802
559
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
$(window).on('action:posts.loaded.ezoicInfinite', function () {
|
|
809
|
-
ensureObserver();
|
|
810
|
-
waitForContentThenRun();
|
|
560
|
+
// Infinite scroll / partial updates
|
|
561
|
+
$(window).on('action:posts.loaded.ezoicInfinite action:topics.loaded.ezoicInfinite action:category.loaded.ezoicInfinite action:topic.loaded.ezoicInfinite', () => {
|
|
562
|
+
if (EZOIC_BLOCKED) return;
|
|
563
|
+
scheduleRun();
|
|
811
564
|
});
|
|
812
565
|
}
|
|
813
566
|
|
|
814
567
|
function bindScroll() {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
var ticking = false;
|
|
819
|
-
window.addEventListener('scroll', function () {
|
|
568
|
+
let ticking = false;
|
|
569
|
+
window.addEventListener('scroll', () => {
|
|
820
570
|
if (ticking) return;
|
|
821
571
|
ticking = true;
|
|
822
|
-
|
|
823
|
-
window.requestAnimationFrame(function () {
|
|
572
|
+
window.requestAnimationFrame(() => {
|
|
824
573
|
ticking = false;
|
|
825
|
-
|
|
826
|
-
enforceNoAdjacentAds();
|
|
827
|
-
|
|
828
|
-
// Debounce scheduleRun (max once every 2s on scroll).
|
|
829
|
-
var now = Date.now();
|
|
830
|
-
if (!state.lastScrollRun || (now - state.lastScrollRun > 2000)) {
|
|
831
|
-
state.lastScrollRun = now;
|
|
832
|
-
scheduleRun('scroll');
|
|
833
|
-
}
|
|
574
|
+
if (!EZOIC_BLOCKED) scheduleRun();
|
|
834
575
|
});
|
|
835
576
|
}, { passive: true });
|
|
836
577
|
}
|
|
837
578
|
|
|
838
|
-
//
|
|
839
|
-
cleanup();
|
|
840
|
-
bind();
|
|
841
|
-
bindScroll();
|
|
842
|
-
ensureObserver();
|
|
579
|
+
// ---------- boot ----------
|
|
843
580
|
|
|
844
581
|
state.pageKey = getPageKey();
|
|
582
|
+
warmUpNetwork();
|
|
583
|
+
patchShowAds();
|
|
584
|
+
ensurePreloadObserver();
|
|
585
|
+
ensureDomObserver();
|
|
586
|
+
|
|
587
|
+
bindNodeBB();
|
|
588
|
+
bindScroll();
|
|
845
589
|
|
|
846
|
-
//
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
}, 0);
|
|
590
|
+
// First paint: try hero + run
|
|
591
|
+
EZOIC_BLOCKED = false;
|
|
592
|
+
insertHeroAdEarly().catch(() => {});
|
|
593
|
+
scheduleRun();
|
|
851
594
|
})();
|