nodebb-plugin-ezoic-infinite 1.5.22 → 1.5.24
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 +399 -645
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,93 +1,64 @@
|
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// Full lists (never consumed) + cursors for round-robin reuse
|
|
30
|
+
allTopics: [],
|
|
31
|
+
allPosts: [],
|
|
32
|
+
allCategories: [],
|
|
33
|
+
curTopics: 0,
|
|
34
|
+
curPosts: 0,
|
|
35
|
+
curCategories: 0,
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
usedPosts: new Set(),
|
|
37
|
-
usedCategories: new Set(),
|
|
38
|
-
|
|
39
|
-
// Track wrappers that are still in the DOM to recycle ids once they are far above viewport.
|
|
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(),
|
|
52
|
-
|
|
53
|
-
// Run scheduling / mutation observer.
|
|
54
|
-
scheduled: false,
|
|
55
|
-
timer: null,
|
|
56
|
-
obs: null,
|
|
40
|
+
// observers / schedulers
|
|
41
|
+
domObs: null,
|
|
42
|
+
io: null,
|
|
43
|
+
runQueued: false,
|
|
57
44
|
|
|
58
|
-
//
|
|
59
|
-
|
|
45
|
+
// hero
|
|
46
|
+
heroDoneForPage: false,
|
|
47
|
+
};
|
|
60
48
|
|
|
61
|
-
|
|
62
|
-
canShowAds: false,
|
|
49
|
+
const insertingIds = new Set();
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
poolWaitAttempts: 0,
|
|
66
|
-
awaitItemsAttempts: 0
|
|
67
|
-
};
|
|
51
|
+
// ---------- small utils ----------
|
|
68
52
|
|
|
69
53
|
function normalizeBool(v) {
|
|
70
54
|
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
71
55
|
}
|
|
72
56
|
|
|
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
57
|
function uniqInts(lines) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
for (
|
|
90
|
-
|
|
58
|
+
const out = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
for (const v of lines) {
|
|
61
|
+
const n = parseInt(v, 10);
|
|
91
62
|
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
92
63
|
seen.add(n);
|
|
93
64
|
out.push(n);
|
|
@@ -98,31 +69,31 @@
|
|
|
98
69
|
|
|
99
70
|
function parsePool(raw) {
|
|
100
71
|
if (!raw) return [];
|
|
101
|
-
|
|
72
|
+
const lines = String(raw)
|
|
102
73
|
.split(/\r?\n/)
|
|
103
|
-
.map(
|
|
74
|
+
.map(s => s.trim())
|
|
104
75
|
.filter(Boolean);
|
|
105
76
|
return uniqInts(lines);
|
|
106
77
|
}
|
|
107
78
|
|
|
108
79
|
function getPageKey() {
|
|
109
80
|
try {
|
|
110
|
-
|
|
81
|
+
const ax = window.ajaxify;
|
|
111
82
|
if (ax && ax.data) {
|
|
112
|
-
if (ax.data.tid) return
|
|
113
|
-
if (ax.data.cid) return
|
|
83
|
+
if (ax.data.tid) return `topic:${ax.data.tid}`;
|
|
84
|
+
if (ax.data.cid) return `cid:${ax.data.cid}:${window.location.pathname}`;
|
|
114
85
|
}
|
|
115
86
|
} catch (e) {}
|
|
116
87
|
return window.location.pathname;
|
|
117
88
|
}
|
|
118
89
|
|
|
119
90
|
function getKind() {
|
|
120
|
-
|
|
91
|
+
const p = window.location.pathname || '';
|
|
121
92
|
if (/^\/topic\//.test(p)) return 'topic';
|
|
122
93
|
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
123
94
|
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
124
95
|
|
|
125
|
-
//
|
|
96
|
+
// fallback by DOM
|
|
126
97
|
if (document.querySelector(SELECTORS.categoryItem)) return 'categories';
|
|
127
98
|
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
128
99
|
if (document.querySelector(SELECTORS.topicItem)) return 'categoryTopics';
|
|
@@ -138,710 +109,493 @@
|
|
|
138
109
|
}
|
|
139
110
|
|
|
140
111
|
function getPostContainers() {
|
|
141
|
-
|
|
142
|
-
return nodes.filter(
|
|
112
|
+
const nodes = Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
113
|
+
return nodes.filter((el) => {
|
|
143
114
|
if (!el || !el.isConnected) return false;
|
|
144
115
|
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]');
|
|
116
|
+
const parentPost = el.parentElement && el.parentElement.closest('[component="post"][data-pid]');
|
|
148
117
|
if (parentPost && parentPost !== el) return false;
|
|
149
118
|
if (el.getAttribute('component') === 'post/parent') return false;
|
|
150
|
-
|
|
151
119
|
return true;
|
|
152
120
|
});
|
|
153
121
|
}
|
|
154
122
|
|
|
155
|
-
|
|
156
|
-
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
157
|
-
}
|
|
123
|
+
// ---------- warm-up & patching ----------
|
|
158
124
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
125
|
+
const _warmLinksDone = new Set();
|
|
126
|
+
function warmUpNetwork() {
|
|
127
|
+
try {
|
|
128
|
+
const head = document.head || document.getElementsByTagName('head')[0];
|
|
129
|
+
if (!head) return;
|
|
130
|
+
const links = [
|
|
131
|
+
['preconnect', 'https://g.ezoic.net', true],
|
|
132
|
+
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
133
|
+
['preconnect', 'https://go.ezoic.net', true],
|
|
134
|
+
['dns-prefetch', 'https://go.ezoic.net', false],
|
|
135
|
+
];
|
|
136
|
+
for (const [rel, href, cors] of links) {
|
|
137
|
+
const key = `${rel}|${href}`;
|
|
138
|
+
if (_warmLinksDone.has(key)) continue;
|
|
139
|
+
_warmLinksDone.add(key);
|
|
140
|
+
const link = document.createElement('link');
|
|
141
|
+
link.rel = rel;
|
|
142
|
+
link.href = href;
|
|
143
|
+
if (cors) link.crossOrigin = 'anonymous';
|
|
144
|
+
head.appendChild(link);
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {}
|
|
147
|
+
}
|
|
168
148
|
|
|
169
|
-
|
|
149
|
+
// Patch showAds to avoid warnings when a placeholder disappears (infinite scroll, ajaxify)
|
|
150
|
+
function patchShowAds() {
|
|
151
|
+
const applyPatch = () => {
|
|
170
152
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
153
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
154
|
+
const ez = window.ezstandalone;
|
|
155
|
+
if (window.__nodebbEzoicPatched) return;
|
|
156
|
+
if (typeof ez.showAds !== 'function') return;
|
|
157
|
+
|
|
158
|
+
window.__nodebbEzoicPatched = true;
|
|
159
|
+
const orig = ez.showAds;
|
|
160
|
+
|
|
161
|
+
ez.showAds = function (...args) {
|
|
162
|
+
if (EZOIC_BLOCKED) return;
|
|
163
|
+
|
|
164
|
+
let ids = [];
|
|
165
|
+
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
166
|
+
else ids = args;
|
|
167
|
+
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
for (const v of ids) {
|
|
170
|
+
const id = parseInt(v, 10);
|
|
171
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
172
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
173
|
+
if (!ph || !ph.isConnected) continue;
|
|
174
|
+
seen.add(id);
|
|
175
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
174
178
|
} catch (e) {}
|
|
175
179
|
};
|
|
176
180
|
|
|
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
|
-
}
|
|
181
|
+
applyPatch();
|
|
182
|
+
if (!window.__nodebbEzoicPatched) {
|
|
183
|
+
try {
|
|
184
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
185
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
186
|
+
window.ezstandalone.cmd.push(applyPatch);
|
|
187
|
+
} catch (e) {}
|
|
200
188
|
}
|
|
201
|
-
return null;
|
|
202
189
|
}
|
|
203
190
|
|
|
204
|
-
|
|
205
|
-
if (pool.length) return { id: pool.shift(), recycled: null };
|
|
191
|
+
// ---------- config & pools ----------
|
|
206
192
|
|
|
207
|
-
|
|
208
|
-
if (
|
|
193
|
+
async function fetchConfigOnce() {
|
|
194
|
+
if (state.cfg) return state.cfg;
|
|
195
|
+
try {
|
|
196
|
+
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
197
|
+
if (!res.ok) return null;
|
|
198
|
+
state.cfg = await res.json();
|
|
199
|
+
return state.cfg;
|
|
200
|
+
} catch (e) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
209
204
|
|
|
210
|
-
|
|
205
|
+
function initPools(cfg) {
|
|
206
|
+
if (!cfg) return;
|
|
207
|
+
if (state.allTopics.length === 0) state.allTopics = parsePool(cfg.placeholderIds);
|
|
208
|
+
if (state.allPosts.length === 0) state.allPosts = parsePool(cfg.messagePlaceholderIds);
|
|
209
|
+
if (state.allCategories.length === 0) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
|
|
211
210
|
}
|
|
212
211
|
|
|
213
|
-
|
|
214
|
-
if (!wrap) return null;
|
|
215
|
-
try { wrap.innerHTML = ''; } catch (e) {}
|
|
212
|
+
// ---------- insertion primitives ----------
|
|
216
213
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return
|
|
214
|
+
function isAdjacentAd(target) {
|
|
215
|
+
if (!target) return false;
|
|
216
|
+
const next = target.nextElementSibling;
|
|
217
|
+
if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
|
|
218
|
+
const prev = target.previousElementSibling;
|
|
219
|
+
if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
|
|
220
|
+
return false;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
function buildWrap(id, kindClass, afterPos) {
|
|
224
|
+
const wrap = document.createElement('div');
|
|
225
|
+
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
226
|
+
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
227
|
+
wrap.style.width = '100%';
|
|
227
228
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
const ph = document.createElement('div');
|
|
230
|
+
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
231
|
+
ph.setAttribute('data-ezoic-id', String(id));
|
|
232
|
+
wrap.appendChild(ph);
|
|
232
233
|
|
|
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
234
|
return wrap;
|
|
239
235
|
}
|
|
240
236
|
|
|
241
237
|
function findWrap(kindClass, afterPos) {
|
|
242
|
-
|
|
243
|
-
return document.querySelector('.' + kindClass + '[data-after-pos="' + afterPos + '"]');
|
|
238
|
+
return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
|
|
244
239
|
}
|
|
245
240
|
|
|
246
|
-
function insertAfter(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
241
|
+
function insertAfter(target, id, kindClass, afterPos) {
|
|
242
|
+
if (!target || !target.insertAdjacentElement) return null;
|
|
243
|
+
if (findWrap(kindClass, afterPos)) return null;
|
|
244
|
+
if (insertingIds.has(id)) return null;
|
|
250
245
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
else el.parentNode.appendChild(wrap);
|
|
246
|
+
const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
247
|
+
if (existingPh && existingPh.isConnected) return null;
|
|
254
248
|
|
|
255
|
-
|
|
249
|
+
insertingIds.add(id);
|
|
250
|
+
try {
|
|
251
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
252
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
256
253
|
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.
|
|
254
|
+
} finally {
|
|
255
|
+
insertingIds.delete(id);
|
|
256
|
+
}
|
|
273
257
|
}
|
|
274
258
|
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
259
|
+
function pickIdFromAll(allIds, cursorKey) {
|
|
260
|
+
const n = allIds.length;
|
|
261
|
+
if (!n) return null;
|
|
278
262
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
263
|
+
// Try at most n ids to find one that's not already in the DOM
|
|
264
|
+
for (let tries = 0; tries < n; tries++) {
|
|
265
|
+
const idx = state[cursorKey] % n;
|
|
266
|
+
state[cursorKey] = (state[cursorKey] + 1) % n;
|
|
282
267
|
|
|
283
|
-
|
|
284
|
-
|
|
268
|
+
const id = allIds[idx];
|
|
269
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
270
|
+
if (ph && ph.isConnected) continue;
|
|
285
271
|
|
|
286
|
-
|
|
287
|
-
if (isPlaceholderFilled(wrap)) {
|
|
288
|
-
try { wrap.setAttribute('data-ezoic-filled', '1'); } catch (e) {}
|
|
289
|
-
markFilled(id);
|
|
290
|
-
return;
|
|
272
|
+
return id;
|
|
291
273
|
}
|
|
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) {}
|
|
274
|
+
return null;
|
|
309
275
|
}
|
|
310
276
|
|
|
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;
|
|
321
|
-
}
|
|
277
|
+
function showAd(id) {
|
|
278
|
+
if (!id || EZOIC_BLOCKED) return;
|
|
322
279
|
|
|
323
|
-
|
|
324
|
-
|
|
280
|
+
const now = Date.now();
|
|
281
|
+
const last = state.lastShowById.get(id) || 0;
|
|
282
|
+
if (now - last < 1500) return; // basic throttle
|
|
325
283
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
330
|
-
window.ezstandalone.showAds(ids);
|
|
331
|
-
}
|
|
332
|
-
} catch (e) {}
|
|
333
|
-
};
|
|
284
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
285
|
+
if (!ph || !ph.isConnected) return;
|
|
334
286
|
|
|
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
|
-
}
|
|
340
|
-
|
|
341
|
-
function callShowAdsWhenReady(id) {
|
|
342
|
-
if (!id) return;
|
|
343
|
-
|
|
344
|
-
// Throttle per-id.
|
|
345
|
-
var now = Date.now();
|
|
346
|
-
var last = state.lastShowById.get(id) || 0;
|
|
347
|
-
if (now - last < 1200) return;
|
|
348
|
-
|
|
349
|
-
if (state.pendingById.has(id)) return;
|
|
350
|
-
state.pendingById.add(id);
|
|
351
287
|
state.lastShowById.set(id, now);
|
|
352
288
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
insertingIds.add(id);
|
|
359
|
-
|
|
360
|
-
var attempts = 0;
|
|
361
|
-
|
|
362
|
-
(function waitForPh() {
|
|
363
|
-
attempts++;
|
|
289
|
+
try {
|
|
290
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
291
|
+
const ez = window.ezstandalone;
|
|
364
292
|
|
|
365
|
-
//
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
insertingIds.delete(id);
|
|
293
|
+
// Fast path
|
|
294
|
+
if (typeof ez.showAds === 'function') {
|
|
295
|
+
ez.showAds(id);
|
|
369
296
|
return;
|
|
370
297
|
}
|
|
371
298
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (ph && ph.isConnected) {
|
|
385
|
-
doCall();
|
|
386
|
-
state.pendingById.delete(id);
|
|
387
|
-
insertingIds.delete(id);
|
|
388
|
-
return;
|
|
299
|
+
// Queue once for when Ezoic is ready
|
|
300
|
+
ez.cmd = ez.cmd || [];
|
|
301
|
+
if (!ph.__ezoicQueued) {
|
|
302
|
+
ph.__ezoicQueued = true;
|
|
303
|
+
ez.cmd.push(() => {
|
|
304
|
+
try {
|
|
305
|
+
if (EZOIC_BLOCKED) return;
|
|
306
|
+
const el = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
307
|
+
if (!el || !el.isConnected) return;
|
|
308
|
+
window.ezstandalone.showAds(id);
|
|
309
|
+
} catch (e) {}
|
|
310
|
+
});
|
|
389
311
|
}
|
|
312
|
+
} catch (e) {}
|
|
313
|
+
}
|
|
390
314
|
|
|
391
|
-
|
|
392
|
-
setTimeoutTracked(waitForPh, 50);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
315
|
+
// ---------- preload / above-the-fold ----------
|
|
395
316
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
317
|
+
function ensurePreloadObserver() {
|
|
318
|
+
if (state.io) return state.io;
|
|
319
|
+
try {
|
|
320
|
+
state.io = new IntersectionObserver((entries) => {
|
|
321
|
+
for (const ent of entries) {
|
|
322
|
+
if (!ent.isIntersecting) continue;
|
|
323
|
+
const el = ent.target;
|
|
324
|
+
try { state.io && state.io.unobserve(el); } catch (e) {}
|
|
325
|
+
|
|
326
|
+
const idAttr = el && el.getAttribute && el.getAttribute('data-ezoic-id');
|
|
327
|
+
const id = parseInt(idAttr, 10);
|
|
328
|
+
if (Number.isFinite(id) && id > 0) showAd(id);
|
|
329
|
+
}
|
|
330
|
+
}, { root: null, rootMargin: PRELOAD_ROOT_MARGIN, threshold: 0.01 });
|
|
331
|
+
} catch (e) {
|
|
332
|
+
state.io = null;
|
|
333
|
+
}
|
|
334
|
+
return state.io;
|
|
400
335
|
}
|
|
401
336
|
|
|
402
|
-
function
|
|
403
|
-
|
|
404
|
-
if (!
|
|
405
|
-
|
|
337
|
+
function observePlaceholder(id) {
|
|
338
|
+
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
339
|
+
if (!ph || !ph.isConnected) return;
|
|
340
|
+
const io = ensurePreloadObserver();
|
|
341
|
+
try { io && io.observe(ph); } catch (e) {}
|
|
342
|
+
|
|
343
|
+
// If already above fold, fire immediately
|
|
344
|
+
try {
|
|
345
|
+
const r = ph.getBoundingClientRect();
|
|
346
|
+
if (r.top < window.innerHeight * 1.5 && r.bottom > -200) showAd(id);
|
|
347
|
+
} catch (e) {}
|
|
406
348
|
}
|
|
407
349
|
|
|
350
|
+
// ---------- insertion logic ----------
|
|
351
|
+
|
|
408
352
|
function computeTargets(count, interval, showFirst) {
|
|
409
|
-
|
|
353
|
+
const out = [];
|
|
410
354
|
if (count <= 0) return out;
|
|
411
|
-
|
|
412
355
|
if (showFirst) out.push(1);
|
|
413
|
-
|
|
414
|
-
for (var i = 1; i <= count; i++) {
|
|
356
|
+
for (let i = 1; i <= count; i++) {
|
|
415
357
|
if (i % interval === 0) out.push(i);
|
|
416
358
|
}
|
|
417
|
-
|
|
418
|
-
// Unique + sorted.
|
|
419
|
-
return Array.from(new Set(out)).sort(function (a, b) { return a - b; });
|
|
359
|
+
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
420
360
|
}
|
|
421
361
|
|
|
422
|
-
function injectBetween(kindClass, items, interval, showFirst,
|
|
423
|
-
if (!items
|
|
362
|
+
function injectBetween(kindClass, items, interval, showFirst, allIds, cursorKey) {
|
|
363
|
+
if (!items.length) return 0;
|
|
424
364
|
|
|
425
|
-
|
|
426
|
-
|
|
365
|
+
const targets = computeTargets(items.length, interval, showFirst);
|
|
366
|
+
let inserted = 0;
|
|
427
367
|
|
|
428
|
-
for (
|
|
429
|
-
var afterPos = targets[t];
|
|
368
|
+
for (const afterPos of targets) {
|
|
430
369
|
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
431
370
|
|
|
432
|
-
|
|
371
|
+
const el = items[afterPos - 1];
|
|
433
372
|
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;
|
|
373
|
+
if (isAdjacentAd(el)) continue;
|
|
440
374
|
if (findWrap(kindClass, afterPos)) continue;
|
|
441
375
|
|
|
442
|
-
|
|
443
|
-
var id = pick.id;
|
|
376
|
+
const id = pickIdFromAll(allIds, cursorKey);
|
|
444
377
|
if (!id) break;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (pick.recycled && pick.recycled.wrap) {
|
|
449
|
-
// Recycle: only destroy if Ezoic has actually defined this placeholder before.
|
|
450
|
-
if (sessionDefinedIds.has(id)) destroyPlaceholderIds([id]);
|
|
451
|
-
|
|
452
|
-
// Remove old wrapper.
|
|
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) {}
|
|
456
|
-
|
|
457
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
458
|
-
if (!wrap) continue;
|
|
459
|
-
|
|
460
|
-
// Give Ezoic a moment after DOM insertion.
|
|
461
|
-
setTimeoutTracked(function () { callShowAdsWhenReady(id); }, 700);
|
|
462
|
-
} else {
|
|
463
|
-
usedSet.add(id);
|
|
464
|
-
wrap = insertAfter(el, id, kindClass, afterPos);
|
|
465
|
-
if (!wrap) continue;
|
|
466
|
-
|
|
467
|
-
// Micro-delay to allow layout/DOM settle.
|
|
468
|
-
setTimeoutTracked(function () { callShowAdsWhenReady(id); }, 10);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
liveArr.push({ id: id, wrap: wrap });
|
|
472
|
-
|
|
473
|
-
// Final safety: if adjacency happened due to DOM shifts, rollback.
|
|
474
|
-
var prev = wrap && wrap.previousElementSibling;
|
|
475
|
-
var next = wrap && wrap.nextElementSibling;
|
|
476
|
-
if (wrap && ((prev && prev.classList && prev.classList.contains(WRAP_CLASS)) || (next && next.classList && next.classList.contains(WRAP_CLASS)))) {
|
|
477
|
-
try { wrap.remove(); } catch (e) {}
|
|
478
|
-
|
|
479
|
-
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
480
|
-
try { kindPool.unshift(id); } catch (e) {}
|
|
481
|
-
usedSet.delete(id);
|
|
482
|
-
}
|
|
378
|
+
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
379
|
+
if (!wrap) {
|
|
483
380
|
continue;
|
|
484
381
|
}
|
|
485
382
|
|
|
486
|
-
|
|
383
|
+
observePlaceholder(id);
|
|
384
|
+
inserted += 1;
|
|
487
385
|
}
|
|
488
386
|
|
|
489
387
|
return inserted;
|
|
490
388
|
}
|
|
491
389
|
|
|
492
|
-
function
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
state.obs = null;
|
|
390
|
+
async function insertHeroAdEarly() {
|
|
391
|
+
if (state.heroDoneForPage) return;
|
|
392
|
+
const cfg = await fetchConfigOnce();
|
|
393
|
+
if (!cfg || cfg.excluded) return;
|
|
394
|
+
|
|
395
|
+
initPools(cfg);
|
|
396
|
+
|
|
397
|
+
const kind = getKind();
|
|
398
|
+
let items = [];
|
|
399
|
+
let allIds = [];
|
|
400
|
+
let cursorKey = '';
|
|
401
|
+
let kindClass = '';
|
|
402
|
+
let showFirst = false;
|
|
403
|
+
|
|
404
|
+
if (kind === 'topic' && normalizeBool(cfg.enableMessageAds)) {
|
|
405
|
+
items = getPostContainers();
|
|
406
|
+
allIds = state.allPosts;
|
|
407
|
+
cursorKey = 'curPosts';
|
|
408
|
+
kindClass = 'ezoic-ad-message';
|
|
409
|
+
showFirst = normalizeBool(cfg.showFirstMessageAd);
|
|
410
|
+
} else if (kind === 'categoryTopics' && normalizeBool(cfg.enableBetweenAds)) {
|
|
411
|
+
items = getTopicItems();
|
|
412
|
+
allIds = state.allTopics;
|
|
413
|
+
cursorKey = 'curTopics';
|
|
414
|
+
kindClass = 'ezoic-ad-between';
|
|
415
|
+
showFirst = normalizeBool(cfg.showFirstTopicAd);
|
|
416
|
+
} else if (kind === 'categories' && normalizeBool(cfg.enableCategoryAds)) {
|
|
417
|
+
items = getCategoryItems();
|
|
418
|
+
allIds = state.allCategories;
|
|
419
|
+
cursorKey = 'curCategories';
|
|
420
|
+
kindClass = 'ezoic-ad-categories';
|
|
421
|
+
showFirst = normalizeBool(cfg.showFirstCategoryAd);
|
|
422
|
+
} else {
|
|
423
|
+
return;
|
|
527
424
|
}
|
|
528
425
|
|
|
529
|
-
|
|
530
|
-
|
|
426
|
+
if (!items.length) return;
|
|
427
|
+
if (!showFirst) { state.heroDoneForPage = true; return; }
|
|
531
428
|
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
} catch (e) {}
|
|
539
|
-
|
|
540
|
-
// Reset runtime caches.
|
|
541
|
-
state.pageKey = getPageKey();
|
|
542
|
-
state.cfg = null;
|
|
543
|
-
state.cfgPromise = null;
|
|
429
|
+
// Insert after the very first item (above-the-fold)
|
|
430
|
+
const afterPos = 1;
|
|
431
|
+
const el = items[afterPos - 1];
|
|
432
|
+
if (!el || !el.isConnected) return;
|
|
433
|
+
if (isAdjacentAd(el)) return;
|
|
434
|
+
if (findWrap(kindClass, afterPos)) { state.heroDoneForPage = true; return; }
|
|
544
435
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
state.poolCategories = [];
|
|
548
|
-
|
|
549
|
-
state.usedTopics.clear();
|
|
550
|
-
state.usedPosts.clear();
|
|
551
|
-
state.usedCategories.clear();
|
|
552
|
-
|
|
553
|
-
state.liveTopics = [];
|
|
554
|
-
state.livePosts = [];
|
|
555
|
-
state.liveCategories = [];
|
|
556
|
-
|
|
557
|
-
state.lastShowById.clear();
|
|
558
|
-
state.pendingById.clear();
|
|
559
|
-
insertingIds.clear();
|
|
560
|
-
|
|
561
|
-
state.scheduled = false;
|
|
562
|
-
if (state.timer) {
|
|
563
|
-
try { clearTimeout(state.timer); } catch (e) {}
|
|
564
|
-
state.timer = null;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function ensureObserver() {
|
|
569
|
-
if (state.obs) return;
|
|
570
|
-
try {
|
|
571
|
-
state.obs = new MutationObserver(function () { scheduleRun('mutation'); });
|
|
572
|
-
state.obs.observe(document.body, { childList: true, subtree: true });
|
|
573
|
-
} catch (e) {}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function scheduleRun(/* reason */) {
|
|
577
|
-
if (state.scheduled) return;
|
|
578
|
-
state.scheduled = true;
|
|
436
|
+
const id = pickIdFromAll(allIds, cursorKey);
|
|
437
|
+
if (!id) return;
|
|
579
438
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
439
|
+
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
440
|
+
if (!wrap) {
|
|
441
|
+
return;
|
|
583
442
|
}
|
|
584
443
|
|
|
585
|
-
state.
|
|
586
|
-
|
|
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);
|
|
444
|
+
state.heroDoneForPage = true;
|
|
445
|
+
observePlaceholder(id);
|
|
594
446
|
}
|
|
595
447
|
|
|
596
|
-
function
|
|
597
|
-
|
|
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;
|
|
448
|
+
async function runCore() {
|
|
449
|
+
if (EZOIC_BLOCKED) return;
|
|
602
450
|
|
|
603
|
-
|
|
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
|
-
}
|
|
451
|
+
patchShowAds();
|
|
629
452
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
453
|
+
const cfg = await fetchConfigOnce();
|
|
454
|
+
if (!cfg || cfg.excluded) return;
|
|
455
|
+
initPools(cfg);
|
|
456
|
+
|
|
457
|
+
const kind = getKind();
|
|
458
|
+
|
|
459
|
+
if (kind === 'topic') {
|
|
460
|
+
if (normalizeBool(cfg.enableMessageAds)) {
|
|
461
|
+
injectBetween(
|
|
462
|
+
'ezoic-ad-message',
|
|
463
|
+
getPostContainers(),
|
|
464
|
+
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
465
|
+
normalizeBool(cfg.showFirstMessageAd),
|
|
466
|
+
state.allPosts,
|
|
467
|
+
'curPosts'
|
|
468
|
+
);
|
|
633
469
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
attempts++;
|
|
645
|
-
|
|
646
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
647
|
-
scheduleRun('ezoic-ready');
|
|
648
|
-
waitForContentThenRun();
|
|
649
|
-
return;
|
|
470
|
+
} else if (kind === 'categoryTopics') {
|
|
471
|
+
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
472
|
+
injectBetween(
|
|
473
|
+
'ezoic-ad-between',
|
|
474
|
+
getTopicItems(),
|
|
475
|
+
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
476
|
+
normalizeBool(cfg.showFirstTopicAd),
|
|
477
|
+
state.allTopics,
|
|
478
|
+
'curTopics'
|
|
479
|
+
);
|
|
650
480
|
}
|
|
651
|
-
|
|
652
|
-
if (
|
|
653
|
-
|
|
654
|
-
|
|
481
|
+
} else if (kind === 'categories') {
|
|
482
|
+
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
483
|
+
injectBetween(
|
|
484
|
+
'ezoic-ad-categories',
|
|
485
|
+
getCategoryItems(),
|
|
486
|
+
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
487
|
+
normalizeBool(cfg.showFirstCategoryAd),
|
|
488
|
+
state.allCategories,
|
|
489
|
+
'curCategories'
|
|
490
|
+
);
|
|
655
491
|
}
|
|
656
|
-
|
|
657
|
-
setTimeoutTracked(check, 200);
|
|
658
|
-
})();
|
|
492
|
+
}
|
|
659
493
|
}
|
|
660
494
|
|
|
661
|
-
function
|
|
662
|
-
if (state.
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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;
|
|
495
|
+
function scheduleRun() {
|
|
496
|
+
if (state.runQueued) return;
|
|
497
|
+
state.runQueued = true;
|
|
498
|
+
window.requestAnimationFrame(() => {
|
|
499
|
+
state.runQueued = false;
|
|
500
|
+
const pk = getPageKey();
|
|
501
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
502
|
+
runCore().catch(() => {});
|
|
503
|
+
});
|
|
692
504
|
}
|
|
693
505
|
|
|
694
|
-
|
|
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;
|
|
506
|
+
// ---------- observers / lifecycle ----------
|
|
702
507
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
var kind = getKind();
|
|
706
|
-
var inserted = 0;
|
|
707
|
-
|
|
708
|
-
if (!waitForItemsThenRun(kind)) return;
|
|
508
|
+
function cleanup() {
|
|
509
|
+
EZOIC_BLOCKED = true;
|
|
709
510
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|
-
}
|
|
511
|
+
// remove all wrappers
|
|
512
|
+
try {
|
|
513
|
+
document.querySelectorAll(`.${WRAP_CLASS}`).forEach((el) => {
|
|
514
|
+
try { el.remove(); } catch (e) {}
|
|
515
|
+
});
|
|
516
|
+
} catch (e) {}
|
|
747
517
|
|
|
748
|
-
|
|
518
|
+
// reset state
|
|
519
|
+
state.cfg = null;
|
|
520
|
+
state.allTopics = [];
|
|
521
|
+
state.allPosts = [];
|
|
522
|
+
state.allCategories = [];
|
|
523
|
+
state.curTopics = 0;
|
|
524
|
+
state.curPosts = 0;
|
|
525
|
+
state.curCategories = 0;
|
|
526
|
+
state.lastShowById.clear();
|
|
527
|
+
state.heroDoneForPage = false;
|
|
749
528
|
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
}
|
|
529
|
+
// keep observers alive (MutationObserver will re-trigger after navigation)
|
|
530
|
+
}
|
|
760
531
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
})
|
|
532
|
+
function ensureDomObserver() {
|
|
533
|
+
if (state.domObs) return;
|
|
534
|
+
state.domObs = new MutationObserver(() => {
|
|
535
|
+
if (!EZOIC_BLOCKED) scheduleRun();
|
|
536
|
+
});
|
|
537
|
+
try {
|
|
538
|
+
state.domObs.observe(document.body, { childList: true, subtree: true });
|
|
539
|
+
} catch (e) {}
|
|
766
540
|
}
|
|
767
541
|
|
|
768
|
-
function
|
|
542
|
+
function bindNodeBB() {
|
|
769
543
|
if (!$) return;
|
|
770
544
|
|
|
771
545
|
$(window).off('.ezoicInfinite');
|
|
772
546
|
|
|
773
|
-
$(window).on('action:ajaxify.start.ezoicInfinite',
|
|
547
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => {
|
|
774
548
|
cleanup();
|
|
775
549
|
});
|
|
776
550
|
|
|
777
|
-
$(window).on('action:ajaxify.end.ezoicInfinite',
|
|
551
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
778
552
|
state.pageKey = getPageKey();
|
|
779
|
-
|
|
553
|
+
EZOIC_BLOCKED = false;
|
|
780
554
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}, 300);
|
|
786
|
-
});
|
|
555
|
+
warmUpNetwork();
|
|
556
|
+
patchShowAds();
|
|
557
|
+
ensurePreloadObserver();
|
|
558
|
+
ensureDomObserver();
|
|
787
559
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
ensureObserver();
|
|
791
|
-
waitForContentThenRun();
|
|
792
|
-
});
|
|
560
|
+
// Ultra-fast above-the-fold first
|
|
561
|
+
insertHeroAdEarly().catch(() => {});
|
|
793
562
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
waitForContentThenRun();
|
|
563
|
+
// Then normal insertion
|
|
564
|
+
scheduleRun();
|
|
797
565
|
});
|
|
798
566
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
$(window).on('action:posts.loaded.ezoicInfinite', function () {
|
|
805
|
-
ensureObserver();
|
|
806
|
-
waitForContentThenRun();
|
|
567
|
+
// Infinite scroll / partial updates
|
|
568
|
+
$(window).on('action:posts.loaded.ezoicInfinite action:topics.loaded.ezoicInfinite action:category.loaded.ezoicInfinite action:topic.loaded.ezoicInfinite', () => {
|
|
569
|
+
if (EZOIC_BLOCKED) return;
|
|
570
|
+
scheduleRun();
|
|
807
571
|
});
|
|
808
572
|
}
|
|
809
573
|
|
|
810
574
|
function bindScroll() {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
var ticking = false;
|
|
815
|
-
window.addEventListener('scroll', function () {
|
|
575
|
+
let ticking = false;
|
|
576
|
+
window.addEventListener('scroll', () => {
|
|
816
577
|
if (ticking) return;
|
|
817
578
|
ticking = true;
|
|
818
|
-
|
|
819
|
-
window.requestAnimationFrame(function () {
|
|
579
|
+
window.requestAnimationFrame(() => {
|
|
820
580
|
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
|
-
}
|
|
581
|
+
if (!EZOIC_BLOCKED) scheduleRun();
|
|
830
582
|
});
|
|
831
583
|
}, { passive: true });
|
|
832
584
|
}
|
|
833
585
|
|
|
834
|
-
//
|
|
835
|
-
cleanup();
|
|
836
|
-
bind();
|
|
837
|
-
bindScroll();
|
|
838
|
-
ensureObserver();
|
|
586
|
+
// ---------- boot ----------
|
|
839
587
|
|
|
840
588
|
state.pageKey = getPageKey();
|
|
589
|
+
warmUpNetwork();
|
|
590
|
+
patchShowAds();
|
|
591
|
+
ensurePreloadObserver();
|
|
592
|
+
ensureDomObserver();
|
|
593
|
+
|
|
594
|
+
bindNodeBB();
|
|
595
|
+
bindScroll();
|
|
841
596
|
|
|
842
|
-
//
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}, 0);
|
|
597
|
+
// First paint: try hero + run
|
|
598
|
+
EZOIC_BLOCKED = false;
|
|
599
|
+
insertHeroAdEarly().catch(() => {});
|
|
600
|
+
scheduleRun();
|
|
847
601
|
})();
|