nodebb-plugin-ezoic-infinite 1.5.22 → 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 -636
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,710 +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
|
-
window.ezstandalone.cmd.push(
|
|
183
|
-
}
|
|
184
|
-
} catch (e) {}}
|
|
185
|
-
|
|
186
|
-
function getRecyclable(liveArr) {
|
|
187
|
-
var margin = 600; // px above viewport
|
|
188
|
-
for (var i = 0; i < liveArr.length; i++) {
|
|
189
|
-
var entry = liveArr[i];
|
|
190
|
-
if (!entry || !entry.wrap || !entry.wrap.isConnected) {
|
|
191
|
-
liveArr.splice(i, 1);
|
|
192
|
-
i--;
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
var r = safeRect(entry.wrap);
|
|
196
|
-
if (r && r.bottom < -margin) {
|
|
197
|
-
liveArr.splice(i, 1);
|
|
198
|
-
return entry;
|
|
199
|
-
}
|
|
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) {}
|
|
200
189
|
}
|
|
201
|
-
return null;
|
|
202
190
|
}
|
|
203
191
|
|
|
204
|
-
|
|
205
|
-
if (pool.length) return { id: pool.shift(), recycled: null };
|
|
192
|
+
// ---------- config & pools ----------
|
|
206
193
|
|
|
207
|
-
|
|
208
|
-
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
|
+
}
|
|
209
205
|
|
|
210
|
-
|
|
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);
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
if (!wrap) return null;
|
|
215
|
-
try { wrap.innerHTML = ''; } catch (e) {}
|
|
213
|
+
// ---------- insertion primitives ----------
|
|
216
214
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
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;
|
|
221
222
|
}
|
|
222
223
|
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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%';
|
|
227
229
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
const ph = document.createElement('div');
|
|
231
|
+
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
232
|
+
ph.setAttribute('data-ezoic-id', String(id));
|
|
233
|
+
wrap.appendChild(ph);
|
|
232
234
|
|
|
233
|
-
function buildWrap(id, kindClass) {
|
|
234
|
-
var wrap = document.createElement('div');
|
|
235
|
-
wrap.className = WRAP_CLASS + ' ' + kindClass;
|
|
236
|
-
wrap.setAttribute('data-ezoic-id', String(id));
|
|
237
|
-
resetPlaceholderInWrap(wrap, id);
|
|
238
235
|
return wrap;
|
|
239
236
|
}
|
|
240
237
|
|
|
241
238
|
function findWrap(kindClass, afterPos) {
|
|
242
|
-
|
|
243
|
-
return document.querySelector('.' + kindClass + '[data-after-pos="' + afterPos + '"]');
|
|
239
|
+
return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
|
|
244
240
|
}
|
|
245
241
|
|
|
246
|
-
function insertAfter(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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;
|
|
250
246
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
else el.parentNode.appendChild(wrap);
|
|
247
|
+
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
248
|
+
if (existingPh && existingPh.isConnected) return null;
|
|
254
249
|
|
|
255
|
-
|
|
250
|
+
insertingIds.add(id);
|
|
251
|
+
try {
|
|
252
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
253
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
256
254
|
return wrap;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function destroyUsedPlaceholders() {
|
|
262
|
-
var ids = [];
|
|
263
|
-
state.usedTopics.forEach(function (id) { ids.push(id); });
|
|
264
|
-
state.usedPosts.forEach(function (id) { ids.push(id); });
|
|
265
|
-
state.usedCategories.forEach(function (id) { ids.push(id); });
|
|
266
|
-
|
|
267
|
-
// Only destroy placeholders that were filled at least once in this session.
|
|
268
|
-
destroyPlaceholderIds(ids);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function patchShowAds() {
|
|
272
|
-
// Intentionally left blank: ezstandalone is managed elsewhere.
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function markFilled(id) {
|
|
276
|
-
try { sessionDefinedIds.add(id); } catch (e) {}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function isWrapMarkedFilled(wrap) {
|
|
280
|
-
try { return !!(wrap && wrap.getAttribute && wrap.getAttribute('data-ezoic-filled') === '1'); } catch (e) { return false; }
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function attachFillObserver(wrap, id) {
|
|
284
|
-
if (!wrap || !wrap.isConnected) return;
|
|
285
|
-
|
|
286
|
-
// If already filled, mark and return.
|
|
287
|
-
if (isPlaceholderFilled(wrap)) {
|
|
288
|
-
try { wrap.setAttribute('data-ezoic-filled', '1'); } catch (e) {}
|
|
289
|
-
markFilled(id);
|
|
290
|
-
return;
|
|
255
|
+
} finally {
|
|
256
|
+
insertingIds.delete(id);
|
|
291
257
|
}
|
|
292
|
-
|
|
293
|
-
// Observe for Ezoic inserting ad content into placeholder.
|
|
294
|
-
try {
|
|
295
|
-
var obs = new MutationObserver(function () {
|
|
296
|
-
if (!wrap.isConnected) {
|
|
297
|
-
try { obs.disconnect(); } catch (e) {}
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (isPlaceholderFilled(wrap)) {
|
|
301
|
-
try { wrap.setAttribute('data-ezoic-filled', '1'); } catch (e) {}
|
|
302
|
-
markFilled(id);
|
|
303
|
-
try { obs.disconnect(); } catch (e) {}
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
obs.observe(wrap, { childList: true, subtree: true });
|
|
307
|
-
wrap.__ezoicFillObs = obs;
|
|
308
|
-
} catch (e) {}
|
|
309
258
|
}
|
|
310
259
|
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
var ph = wrap.querySelector('[id^="' + PLACEHOLDER_PREFIX + '"]');
|
|
315
|
-
if (!ph) return false;
|
|
316
|
-
if (ph.children && ph.children.length) return true;
|
|
317
|
-
var r = safeRect(wrap);
|
|
318
|
-
if (r && r.height > 20) return true;
|
|
319
|
-
} catch (e) {}
|
|
320
|
-
return false;
|
|
260
|
+
function pickId(pool) {
|
|
261
|
+
return pool.length ? pool.shift() : null;
|
|
321
262
|
}
|
|
322
263
|
|
|
323
|
-
function
|
|
324
|
-
if (!
|
|
325
|
-
|
|
326
|
-
// Ezoic expects DOM to be settled.
|
|
327
|
-
var call = function () {
|
|
328
|
-
try {
|
|
329
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
330
|
-
window.ezstandalone.showAds(ids);
|
|
331
|
-
}
|
|
332
|
-
} catch (e) {}
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
try {
|
|
336
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') call();
|
|
337
|
-
else if (window.ezstandalone && window.ezstandalone.cmd && Array.isArray(window.ezstandalone.cmd)) window.ezstandalone.cmd.push(call);
|
|
338
|
-
} catch (e) {}
|
|
339
|
-
}
|
|
264
|
+
function showAd(id) {
|
|
265
|
+
if (!id || EZOIC_BLOCKED) return;
|
|
340
266
|
|
|
341
|
-
|
|
342
|
-
|
|
267
|
+
const now = Date.now();
|
|
268
|
+
const last = state.lastShowById.get(id) || 0;
|
|
269
|
+
if (now - last < 1500) return; // basic throttle
|
|
343
270
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
var last = state.lastShowById.get(id) || 0;
|
|
347
|
-
if (now - last < 1200) return;
|
|
271
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
272
|
+
if (!ph || !ph.isConnected) return;
|
|
348
273
|
|
|
349
|
-
if (state.pendingById.has(id)) return;
|
|
350
|
-
state.pendingById.add(id);
|
|
351
274
|
state.lastShowById.set(id, now);
|
|
352
275
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
insertingIds.add(id);
|
|
359
|
-
|
|
360
|
-
var attempts = 0;
|
|
361
|
-
|
|
362
|
-
(function waitForPh() {
|
|
363
|
-
attempts++;
|
|
276
|
+
try {
|
|
277
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
278
|
+
const ez = window.ezstandalone;
|
|
364
279
|
|
|
365
|
-
//
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
280
|
+
// Fast path
|
|
281
|
+
if (typeof ez.showAds === 'function') {
|
|
282
|
+
ez.showAds(id);
|
|
283
|
+
sessionDefinedIds.add(id);
|
|
369
284
|
return;
|
|
370
285
|
}
|
|
371
286
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
doCall();
|
|
386
|
-
state.pendingById.delete(id);
|
|
387
|
-
insertingIds.delete(id);
|
|
388
|
-
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
|
+
});
|
|
389
300
|
}
|
|
301
|
+
} catch (e) {}
|
|
302
|
+
}
|
|
390
303
|
|
|
391
|
-
|
|
392
|
-
setTimeoutTracked(waitForPh, 50);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
304
|
+
// ---------- preload / above-the-fold ----------
|
|
395
305
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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;
|
|
400
324
|
}
|
|
401
325
|
|
|
402
|
-
function
|
|
403
|
-
|
|
404
|
-
if (!
|
|
405
|
-
|
|
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) {}
|
|
406
337
|
}
|
|
407
338
|
|
|
339
|
+
// ---------- insertion logic ----------
|
|
340
|
+
|
|
408
341
|
function computeTargets(count, interval, showFirst) {
|
|
409
|
-
|
|
342
|
+
const out = [];
|
|
410
343
|
if (count <= 0) return out;
|
|
411
|
-
|
|
412
344
|
if (showFirst) out.push(1);
|
|
413
|
-
|
|
414
|
-
for (var i = 1; i <= count; i++) {
|
|
345
|
+
for (let i = 1; i <= count; i++) {
|
|
415
346
|
if (i % interval === 0) out.push(i);
|
|
416
347
|
}
|
|
417
|
-
|
|
418
|
-
// Unique + sorted.
|
|
419
|
-
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);
|
|
420
349
|
}
|
|
421
350
|
|
|
422
|
-
function injectBetween(kindClass, items, interval, showFirst,
|
|
423
|
-
if (!items
|
|
351
|
+
function injectBetween(kindClass, items, interval, showFirst, pool, usedSet) {
|
|
352
|
+
if (!items.length) return 0;
|
|
424
353
|
|
|
425
|
-
|
|
426
|
-
|
|
354
|
+
const targets = computeTargets(items.length, interval, showFirst);
|
|
355
|
+
let inserted = 0;
|
|
427
356
|
|
|
428
|
-
for (
|
|
429
|
-
var afterPos = targets[t];
|
|
357
|
+
for (const afterPos of targets) {
|
|
430
358
|
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
431
359
|
|
|
432
|
-
|
|
360
|
+
const el = items[afterPos - 1];
|
|
433
361
|
if (!el || !el.isConnected) continue;
|
|
434
|
-
|
|
435
|
-
// Prevent adjacent ads.
|
|
436
|
-
if (isAdjacentAd(el) || isPrevAd(el)) continue;
|
|
437
|
-
|
|
438
|
-
// Prevent duplicates at same logical position.
|
|
439
|
-
if (findWrap(kindClass, afterPos - 1)) continue;
|
|
362
|
+
if (isAdjacentAd(el)) continue;
|
|
440
363
|
if (findWrap(kindClass, afterPos)) continue;
|
|
441
364
|
|
|
442
|
-
|
|
443
|
-
var id = pick.id;
|
|
365
|
+
const id = pickId(pool);
|
|
444
366
|
if (!id) break;
|
|
445
367
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
var oldWrap = pick.recycled.wrap;
|
|
454
|
-
try { if (oldWrap && oldWrap.__ezoicFillObs) oldWrap.__ezoicFillObs.disconnect(); } catch (e) {}
|
|
455
|
-
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
|
+
}
|
|
456
375
|
|
|
457
|
-
|
|
458
|
-
|
|
376
|
+
observePlaceholder(id);
|
|
377
|
+
inserted += 1;
|
|
378
|
+
}
|
|
459
379
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
} else {
|
|
463
|
-
usedSet.add(id);
|
|
464
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
465
|
-
if (!wrap) continue;
|
|
380
|
+
return inserted;
|
|
381
|
+
}
|
|
466
382
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
+
}
|
|
470
414
|
|
|
471
|
-
|
|
415
|
+
if (!items.length) return;
|
|
472
416
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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; }
|
|
478
423
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
usedSet.delete(id);
|
|
482
|
-
}
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
424
|
+
const id = pickId(pool);
|
|
425
|
+
if (!id) return;
|
|
485
426
|
|
|
486
|
-
|
|
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;
|
|
487
433
|
}
|
|
488
434
|
|
|
489
|
-
|
|
435
|
+
state.heroDoneForPage = true;
|
|
436
|
+
observePlaceholder(id);
|
|
490
437
|
}
|
|
491
438
|
|
|
492
|
-
function
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
+
);
|
|
510
482
|
}
|
|
511
483
|
}
|
|
512
484
|
}
|
|
513
485
|
|
|
514
|
-
function
|
|
515
|
-
|
|
516
|
-
state.
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (state.obs) {
|
|
525
|
-
try { state.obs.disconnect(); } catch (e) {}
|
|
526
|
-
state.obs = null;
|
|
527
|
-
}
|
|
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
|
+
}
|
|
528
496
|
|
|
529
|
-
|
|
530
|
-
destroyUsedPlaceholders();
|
|
497
|
+
// ---------- observers / lifecycle ----------
|
|
531
498
|
|
|
532
|
-
|
|
499
|
+
function cleanup() {
|
|
500
|
+
EZOIC_BLOCKED = true;
|
|
501
|
+
|
|
502
|
+
// remove all wrappers
|
|
533
503
|
try {
|
|
534
|
-
document.querySelectorAll(
|
|
535
|
-
try { if (el && el.__ezoicFillObs) el.__ezoicFillObs.disconnect(); } catch (e) {}
|
|
504
|
+
document.querySelectorAll(`.${WRAP_CLASS}`).forEach((el) => {
|
|
536
505
|
try { el.remove(); } catch (e) {}
|
|
537
506
|
});
|
|
538
507
|
} catch (e) {}
|
|
539
508
|
|
|
540
|
-
//
|
|
541
|
-
state.pageKey = getPageKey();
|
|
509
|
+
// reset state
|
|
542
510
|
state.cfg = null;
|
|
543
|
-
state.cfgPromise = null;
|
|
544
|
-
|
|
545
511
|
state.poolTopics = [];
|
|
546
512
|
state.poolPosts = [];
|
|
547
513
|
state.poolCategories = [];
|
|
548
|
-
|
|
549
514
|
state.usedTopics.clear();
|
|
550
515
|
state.usedPosts.clear();
|
|
551
516
|
state.usedCategories.clear();
|
|
552
|
-
|
|
553
|
-
state.liveTopics = [];
|
|
554
|
-
state.livePosts = [];
|
|
555
|
-
state.liveCategories = [];
|
|
556
|
-
|
|
557
517
|
state.lastShowById.clear();
|
|
558
|
-
state.
|
|
559
|
-
insertingIds.clear();
|
|
518
|
+
state.heroDoneForPage = false;
|
|
560
519
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
state.timer = null;
|
|
565
|
-
}
|
|
520
|
+
sessionDefinedIds.clear();
|
|
521
|
+
|
|
522
|
+
// keep observers alive (MutationObserver will re-trigger after navigation)
|
|
566
523
|
}
|
|
567
524
|
|
|
568
|
-
function
|
|
569
|
-
if (state.
|
|
525
|
+
function ensureDomObserver() {
|
|
526
|
+
if (state.domObs) return;
|
|
527
|
+
state.domObs = new MutationObserver(() => {
|
|
528
|
+
if (!EZOIC_BLOCKED) scheduleRun();
|
|
529
|
+
});
|
|
570
530
|
try {
|
|
571
|
-
state.
|
|
572
|
-
state.obs.observe(document.body, { childList: true, subtree: true });
|
|
531
|
+
state.domObs.observe(document.body, { childList: true, subtree: true });
|
|
573
532
|
} catch (e) {}
|
|
574
533
|
}
|
|
575
534
|
|
|
576
|
-
function
|
|
577
|
-
if (state.scheduled) return;
|
|
578
|
-
state.scheduled = true;
|
|
579
|
-
|
|
580
|
-
if (state.timer) {
|
|
581
|
-
try { clearTimeout(state.timer); } catch (e) {}
|
|
582
|
-
state.timer = null;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
state.timer = setTimeoutTracked(function () {
|
|
586
|
-
state.scheduled = false;
|
|
587
|
-
|
|
588
|
-
// If user navigated away, stop.
|
|
589
|
-
var pk = getPageKey();
|
|
590
|
-
if (state.pageKey && pk !== state.pageKey) return;
|
|
591
|
-
|
|
592
|
-
runCore().catch(function () {});
|
|
593
|
-
}, 80);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function waitForItemsThenRun(kind) {
|
|
597
|
-
// If list isn't in DOM yet (ajaxify transition), retry a bit.
|
|
598
|
-
var count = 0;
|
|
599
|
-
if (kind === 'topic') count = getPostContainers().length;
|
|
600
|
-
else if (kind === 'categoryTopics') count = getTopicItems().length;
|
|
601
|
-
else if (kind === 'categories') count = getCategoryItems().length;
|
|
602
|
-
|
|
603
|
-
if (count > 0) return true;
|
|
604
|
-
|
|
605
|
-
if (state.awaitItemsAttempts < 25) {
|
|
606
|
-
state.awaitItemsAttempts++;
|
|
607
|
-
setTimeoutTracked(function () { scheduleRun('await-items'); }, 120);
|
|
608
|
-
}
|
|
609
|
-
return false;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
function waitForContentThenRun() {
|
|
613
|
-
// Avoid inserting ads on pages with too little content.
|
|
614
|
-
var MIN_WORDS = 250;
|
|
615
|
-
var attempts = 0;
|
|
616
|
-
var maxAttempts = 20; // 20 × 200ms = 4s
|
|
617
|
-
|
|
618
|
-
(function check() {
|
|
619
|
-
attempts++;
|
|
620
|
-
|
|
621
|
-
var text = '';
|
|
622
|
-
try { text = document.body.innerText || ''; } catch (e) {}
|
|
623
|
-
var wordCount = text.split(/\s+/).filter(Boolean).length;
|
|
624
|
-
|
|
625
|
-
if (wordCount >= MIN_WORDS) {
|
|
626
|
-
scheduleRun('content-ok');
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (attempts >= maxAttempts) {
|
|
631
|
-
scheduleRun('content-timeout');
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
setTimeoutTracked(check, 200);
|
|
636
|
-
})();
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function waitForEzoicThenRun() {
|
|
640
|
-
var attempts = 0;
|
|
641
|
-
var maxAttempts = 50; // 50 × 200ms = 10s
|
|
642
|
-
|
|
643
|
-
(function check() {
|
|
644
|
-
attempts++;
|
|
645
|
-
|
|
646
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
647
|
-
scheduleRun('ezoic-ready');
|
|
648
|
-
waitForContentThenRun();
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
if (attempts >= maxAttempts) {
|
|
653
|
-
scheduleRun('ezoic-timeout');
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
setTimeoutTracked(check, 200);
|
|
658
|
-
})();
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function fetchConfig() {
|
|
662
|
-
if (state.cfg) return Promise.resolve(state.cfg);
|
|
663
|
-
if (state.cfgPromise) return state.cfgPromise;
|
|
664
|
-
|
|
665
|
-
state.cfgPromise = (function () {
|
|
666
|
-
var MAX_TRIES = 3;
|
|
667
|
-
var delay = 800;
|
|
668
|
-
|
|
669
|
-
function attemptFetch(attempt) {
|
|
670
|
-
return fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' })
|
|
671
|
-
.then(function (res) {
|
|
672
|
-
if (!res || !res.ok) throw new Error('bad response');
|
|
673
|
-
return res.json();
|
|
674
|
-
})
|
|
675
|
-
.then(function (json) {
|
|
676
|
-
state.cfg = json;
|
|
677
|
-
return json;
|
|
678
|
-
})
|
|
679
|
-
.catch(function () {
|
|
680
|
-
if (attempt >= MAX_TRIES) return null;
|
|
681
|
-
return new Promise(function (r) { setTimeoutTracked(r, delay); }).then(function () {
|
|
682
|
-
delay *= 2;
|
|
683
|
-
return attemptFetch(attempt + 1);
|
|
684
|
-
});
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return attemptFetch(1).finally(function () { state.cfgPromise = null; });
|
|
689
|
-
})();
|
|
690
|
-
|
|
691
|
-
return state.cfgPromise;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
function runCore() {
|
|
695
|
-
// Navigation safety: never insert during ajaxify teardown.
|
|
696
|
-
if (!state.canShowAds) return Promise.resolve();
|
|
697
|
-
|
|
698
|
-
patchShowAds();
|
|
699
|
-
|
|
700
|
-
return fetchConfig().then(function (cfg) {
|
|
701
|
-
if (!cfg || cfg.excluded) return;
|
|
702
|
-
|
|
703
|
-
initPools(cfg);
|
|
704
|
-
|
|
705
|
-
var kind = getKind();
|
|
706
|
-
var inserted = 0;
|
|
707
|
-
|
|
708
|
-
if (!waitForItemsThenRun(kind)) return;
|
|
709
|
-
|
|
710
|
-
if (kind === 'topic') {
|
|
711
|
-
if (normalizeBool(cfg.enableMessageAds)) {
|
|
712
|
-
inserted = injectBetween(
|
|
713
|
-
'ezoic-ad-message',
|
|
714
|
-
getPostContainers(),
|
|
715
|
-
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
716
|
-
normalizeBool(cfg.showFirstMessageAd),
|
|
717
|
-
state.poolPosts,
|
|
718
|
-
state.usedPosts,
|
|
719
|
-
state.livePosts
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
} else if (kind === 'categoryTopics') {
|
|
723
|
-
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
724
|
-
inserted = injectBetween(
|
|
725
|
-
'ezoic-ad-between',
|
|
726
|
-
getTopicItems(),
|
|
727
|
-
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
728
|
-
normalizeBool(cfg.showFirstTopicAd),
|
|
729
|
-
state.poolTopics,
|
|
730
|
-
state.usedTopics,
|
|
731
|
-
state.liveTopics
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
} else if (kind === 'categories') {
|
|
735
|
-
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
736
|
-
inserted = injectBetween(
|
|
737
|
-
'ezoic-ad-categories',
|
|
738
|
-
getCategoryItems(),
|
|
739
|
-
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
740
|
-
normalizeBool(cfg.showFirstCategoryAd),
|
|
741
|
-
state.poolCategories,
|
|
742
|
-
state.usedCategories,
|
|
743
|
-
state.liveCategories
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
enforceNoAdjacentAds();
|
|
749
|
-
|
|
750
|
-
// Recycling: if pool is exhausted, retry a few times to allow old wrappers to scroll off-screen.
|
|
751
|
-
if (inserted === 0) {
|
|
752
|
-
if (state.poolWaitAttempts < 8) {
|
|
753
|
-
state.poolWaitAttempts++;
|
|
754
|
-
setTimeoutTracked(function () { scheduleRun('pool-wait'); }, 400);
|
|
755
|
-
}
|
|
756
|
-
} else {
|
|
757
|
-
// Reset pool wait attempts once we successfully insert something.
|
|
758
|
-
state.poolWaitAttempts = 0;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// If we hit max inserts, continue quickly.
|
|
762
|
-
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
763
|
-
setTimeoutTracked(function () { scheduleRun('continue'); }, 140);
|
|
764
|
-
}
|
|
765
|
-
}).catch(function () {});
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
function bind() {
|
|
535
|
+
function bindNodeBB() {
|
|
769
536
|
if (!$) return;
|
|
770
537
|
|
|
771
538
|
$(window).off('.ezoicInfinite');
|
|
772
539
|
|
|
773
|
-
$(window).on('action:ajaxify.start.ezoicInfinite',
|
|
540
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => {
|
|
774
541
|
cleanup();
|
|
775
542
|
});
|
|
776
543
|
|
|
777
|
-
$(window).on('action:ajaxify.end.ezoicInfinite',
|
|
544
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
778
545
|
state.pageKey = getPageKey();
|
|
779
|
-
|
|
546
|
+
EZOIC_BLOCKED = false;
|
|
780
547
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}, 300);
|
|
786
|
-
});
|
|
548
|
+
warmUpNetwork();
|
|
549
|
+
patchShowAds();
|
|
550
|
+
ensurePreloadObserver();
|
|
551
|
+
ensureDomObserver();
|
|
787
552
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
ensureObserver();
|
|
791
|
-
waitForContentThenRun();
|
|
792
|
-
});
|
|
553
|
+
// Ultra-fast above-the-fold first
|
|
554
|
+
insertHeroAdEarly().catch(() => {});
|
|
793
555
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
waitForContentThenRun();
|
|
556
|
+
// Then normal insertion
|
|
557
|
+
scheduleRun();
|
|
797
558
|
});
|
|
798
559
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
$(window).on('action:posts.loaded.ezoicInfinite', function () {
|
|
805
|
-
ensureObserver();
|
|
806
|
-
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();
|
|
807
564
|
});
|
|
808
565
|
}
|
|
809
566
|
|
|
810
567
|
function bindScroll() {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
var ticking = false;
|
|
815
|
-
window.addEventListener('scroll', function () {
|
|
568
|
+
let ticking = false;
|
|
569
|
+
window.addEventListener('scroll', () => {
|
|
816
570
|
if (ticking) return;
|
|
817
571
|
ticking = true;
|
|
818
|
-
|
|
819
|
-
window.requestAnimationFrame(function () {
|
|
572
|
+
window.requestAnimationFrame(() => {
|
|
820
573
|
ticking = false;
|
|
821
|
-
|
|
822
|
-
enforceNoAdjacentAds();
|
|
823
|
-
|
|
824
|
-
// Debounce scheduleRun (max once every 2s on scroll).
|
|
825
|
-
var now = Date.now();
|
|
826
|
-
if (!state.lastScrollRun || (now - state.lastScrollRun > 2000)) {
|
|
827
|
-
state.lastScrollRun = now;
|
|
828
|
-
scheduleRun('scroll');
|
|
829
|
-
}
|
|
574
|
+
if (!EZOIC_BLOCKED) scheduleRun();
|
|
830
575
|
});
|
|
831
576
|
}, { passive: true });
|
|
832
577
|
}
|
|
833
578
|
|
|
834
|
-
//
|
|
835
|
-
cleanup();
|
|
836
|
-
bind();
|
|
837
|
-
bindScroll();
|
|
838
|
-
ensureObserver();
|
|
579
|
+
// ---------- boot ----------
|
|
839
580
|
|
|
840
581
|
state.pageKey = getPageKey();
|
|
582
|
+
warmUpNetwork();
|
|
583
|
+
patchShowAds();
|
|
584
|
+
ensurePreloadObserver();
|
|
585
|
+
ensureDomObserver();
|
|
586
|
+
|
|
587
|
+
bindNodeBB();
|
|
588
|
+
bindScroll();
|
|
841
589
|
|
|
842
|
-
//
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}, 0);
|
|
590
|
+
// First paint: try hero + run
|
|
591
|
+
EZOIC_BLOCKED = false;
|
|
592
|
+
insertHeroAdEarly().catch(() => {});
|
|
593
|
+
scheduleRun();
|
|
847
594
|
})();
|