nodebb-plugin-ezoic-infinite 1.5.5 → 1.5.7
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 +2 -1
- package/package.json +1 -1
- package/public/client.js +303 -435
- package/public/style.css +7 -21
package/library.js
CHANGED
|
@@ -68,7 +68,7 @@ async function getSettings() {
|
|
|
68
68
|
async function isUserExcluded(uid, excludedGroups) {
|
|
69
69
|
if (!uid || !excludedGroups.length) return false;
|
|
70
70
|
const userGroups = await groups.getUserGroups([uid]);
|
|
71
|
-
return (userGroups[0] || []).some(g => excludedGroups.includes((g && g.name) ? g.name : g));
|
|
71
|
+
return (userGroups[0] || []).some(g => excludedGroups.includes((g && g.name) ? g.name : String(g)));
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
plugin.onSettingsSet = function (data) {
|
|
@@ -111,6 +111,7 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
111
111
|
|
|
112
112
|
res.json({
|
|
113
113
|
excluded,
|
|
114
|
+
excludedGroups: settings.excludedGroups,
|
|
114
115
|
enableBetweenAds: settings.enableBetweenAds,
|
|
115
116
|
showFirstTopicAd: settings.showFirstTopicAd,
|
|
116
117
|
placeholderIds: settings.placeholderIds,
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,63 +1,50 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
// Safety stubs (do NOT stub showAds)
|
|
5
|
-
window._ezaq = window._ezaq || [];
|
|
6
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
7
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
8
|
-
|
|
9
|
-
// NodeBB client context
|
|
10
4
|
const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
11
5
|
|
|
12
6
|
const WRAP_CLASS = 'ezoic-ad';
|
|
13
7
|
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
14
8
|
|
|
15
|
-
// Insert at most N ads per run to keep the UI smooth on infinite scroll
|
|
16
|
-
const MAX_INSERTS_PER_RUN = 3;
|
|
17
|
-
|
|
18
|
-
// Preload before viewport (tune if you want even earlier)
|
|
19
|
-
const PRELOAD_ROOT_MARGIN = '1200px 0px';
|
|
20
|
-
|
|
21
9
|
const SELECTORS = {
|
|
22
10
|
topicItem: 'li[component="category/topic"]',
|
|
23
|
-
postItem: '[component="post"][data-pid]',
|
|
24
11
|
categoryItem: 'li[component="categories/category"]',
|
|
12
|
+
postItem: '[component="post"][data-pid]',
|
|
13
|
+
postContent: '[component="post/content"]',
|
|
25
14
|
};
|
|
26
15
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
// ----------------------------
|
|
17
|
+
// State
|
|
18
|
+
// ----------------------------
|
|
30
19
|
const state = {
|
|
31
20
|
pageKey: null,
|
|
21
|
+
pageToken: 0,
|
|
22
|
+
cfgPromise: null,
|
|
32
23
|
cfg: null,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
poolCategories: [],
|
|
37
|
-
|
|
38
|
-
usedTopics: new Set(),
|
|
39
|
-
usedPosts: new Set(),
|
|
40
|
-
usedCategories: new Set(),
|
|
41
|
-
|
|
42
|
-
// throttle per placeholder id
|
|
43
|
-
lastShowById: new Map(),
|
|
44
|
-
|
|
45
|
-
// observers / schedulers
|
|
46
|
-
domObs: null,
|
|
24
|
+
// throttle per id
|
|
25
|
+
lastShowAt: new Map(),
|
|
26
|
+
// observed placeholders -> ids
|
|
47
27
|
io: null,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// hero
|
|
51
|
-
heroDoneForPage: false,
|
|
28
|
+
mo: null,
|
|
29
|
+
scheduled: false,
|
|
52
30
|
};
|
|
53
31
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
32
|
+
// ----------------------------
|
|
33
|
+
// Small utils
|
|
34
|
+
// ----------------------------
|
|
35
|
+
function getPageKey() {
|
|
36
|
+
try {
|
|
37
|
+
const ax = window.ajaxify;
|
|
38
|
+
if (ax && ax.data) {
|
|
39
|
+
if (ax.data.tid) return `topic:${ax.data.tid}`;
|
|
40
|
+
if (ax.data.cid) return `cid:${ax.data.cid}:${window.location.pathname}`;
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {}
|
|
43
|
+
return window.location.pathname || '';
|
|
44
|
+
}
|
|
58
45
|
|
|
59
46
|
function normalizeBool(v) {
|
|
60
|
-
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
47
|
+
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on' || v === 'yes';
|
|
61
48
|
}
|
|
62
49
|
|
|
63
50
|
function uniqInts(lines) {
|
|
@@ -75,22 +62,7 @@
|
|
|
75
62
|
|
|
76
63
|
function parsePool(raw) {
|
|
77
64
|
if (!raw) return [];
|
|
78
|
-
|
|
79
|
-
.split(/\r?\n/)
|
|
80
|
-
.map(s => s.trim())
|
|
81
|
-
.filter(Boolean);
|
|
82
|
-
return uniqInts(lines);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function getPageKey() {
|
|
86
|
-
try {
|
|
87
|
-
const ax = window.ajaxify;
|
|
88
|
-
if (ax && ax.data) {
|
|
89
|
-
if (ax.data.tid) return `topic:${ax.data.tid}`;
|
|
90
|
-
if (ax.data.cid) return `cid:${ax.data.cid}:${window.location.pathname}`;
|
|
91
|
-
}
|
|
92
|
-
} catch (e) {}
|
|
93
|
-
return window.location.pathname;
|
|
65
|
+
return uniqInts(String(raw).split(/\r?\n/).map(s => s.trim()).filter(Boolean));
|
|
94
66
|
}
|
|
95
67
|
|
|
96
68
|
function getKind() {
|
|
@@ -98,7 +70,6 @@
|
|
|
98
70
|
if (/^\/topic\//.test(p)) return 'topic';
|
|
99
71
|
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
100
72
|
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
101
|
-
|
|
102
73
|
// fallback by DOM
|
|
103
74
|
if (document.querySelector(SELECTORS.categoryItem)) return 'categories';
|
|
104
75
|
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
@@ -118,135 +89,130 @@
|
|
|
118
89
|
const nodes = Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
119
90
|
return nodes.filter((el) => {
|
|
120
91
|
if (!el || !el.isConnected) return false;
|
|
121
|
-
if (!el.querySelector(
|
|
122
|
-
const parentPost = el.parentElement && el.parentElement.closest(
|
|
92
|
+
if (!el.querySelector(SELECTORS.postContent)) return false;
|
|
93
|
+
const parentPost = el.parentElement && el.parentElement.closest(SELECTORS.postItem);
|
|
123
94
|
if (parentPost && parentPost !== el) return false;
|
|
124
95
|
if (el.getAttribute('component') === 'post/parent') return false;
|
|
125
96
|
return true;
|
|
126
97
|
});
|
|
127
98
|
}
|
|
128
99
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
function warmUpNetwork() {
|
|
133
|
-
try {
|
|
134
|
-
const head = document.head || document.getElementsByTagName('head')[0];
|
|
135
|
-
if (!head) return;
|
|
136
|
-
const links = [
|
|
137
|
-
['preconnect', 'https://g.ezoic.net', true],
|
|
138
|
-
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
139
|
-
['preconnect', 'https://go.ezoic.net', true],
|
|
140
|
-
['dns-prefetch', 'https://go.ezoic.net', false],
|
|
141
|
-
];
|
|
142
|
-
for (const [rel, href, cors] of links) {
|
|
143
|
-
const key = `${rel}|${href}`;
|
|
144
|
-
if (_warmLinksDone.has(key)) continue;
|
|
145
|
-
_warmLinksDone.add(key);
|
|
146
|
-
const link = document.createElement('link');
|
|
147
|
-
link.rel = rel;
|
|
148
|
-
link.href = href;
|
|
149
|
-
if (cors) link.crossOrigin = 'anonymous';
|
|
150
|
-
head.appendChild(link);
|
|
151
|
-
}
|
|
152
|
-
} catch (e) {}
|
|
100
|
+
function isPlaceholderPresent(id) {
|
|
101
|
+
const el = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
102
|
+
return !!(el && el.isConnected);
|
|
153
103
|
}
|
|
154
104
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (typeof ez.showAds !== 'function') return;
|
|
163
|
-
|
|
164
|
-
window.__nodebbEzoicPatched = true;
|
|
165
|
-
const orig = ez.showAds;
|
|
166
|
-
|
|
167
|
-
ez.showAds = function (...args) {
|
|
168
|
-
if (EZOIC_BLOCKED) return;
|
|
169
|
-
|
|
170
|
-
let ids = [];
|
|
171
|
-
if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
|
|
172
|
-
else ids = args;
|
|
173
|
-
|
|
174
|
-
const seen = new Set();
|
|
175
|
-
for (const v of ids) {
|
|
176
|
-
const id = parseInt(v, 10);
|
|
177
|
-
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
178
|
-
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
179
|
-
if (!ph || !ph.isConnected) continue;
|
|
180
|
-
seen.add(id);
|
|
181
|
-
try { orig.call(ez, id); } catch (e) {}
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
} catch (e) {}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
applyPatch();
|
|
188
|
-
if (!window.__nodebbEzoicPatched) {
|
|
189
|
-
try {
|
|
190
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
191
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
192
|
-
window.ezstandalone.cmd.push(applyPatch);
|
|
193
|
-
} catch (e) {}
|
|
194
|
-
}
|
|
105
|
+
function schedule(fn) {
|
|
106
|
+
if (state.scheduled) return;
|
|
107
|
+
state.scheduled = true;
|
|
108
|
+
requestAnimationFrame(() => {
|
|
109
|
+
state.scheduled = false;
|
|
110
|
+
try { fn(); } catch (e) {}
|
|
111
|
+
});
|
|
195
112
|
}
|
|
196
113
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
114
|
+
// ----------------------------
|
|
115
|
+
// Ezoic showAds patch (handles arrays, varargs, and filters absent placeholders)
|
|
116
|
+
// ----------------------------
|
|
117
|
+
function patchShowAds() {
|
|
201
118
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
119
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
120
|
+
const ez = window.ezstandalone;
|
|
121
|
+
if (ez.__nodebbEzoicPatched) return;
|
|
122
|
+
|
|
123
|
+
// If showAds isn't ready yet, patch when it appears via cmd (no polling)
|
|
124
|
+
const apply = () => {
|
|
125
|
+
try {
|
|
126
|
+
if (!window.ezstandalone || typeof window.ezstandalone.showAds !== 'function') return;
|
|
127
|
+
const ez2 = window.ezstandalone;
|
|
128
|
+
if (ez2.__nodebbEzoicPatched) return;
|
|
129
|
+
|
|
130
|
+
const orig = ez2.showAds;
|
|
131
|
+
ez2.showAds = function () {
|
|
132
|
+
// Normalize ids from:
|
|
133
|
+
// - showAds([1,2])
|
|
134
|
+
// - showAds(1,2,3)
|
|
135
|
+
// - showAds(1)
|
|
136
|
+
const ids = [];
|
|
137
|
+
if (arguments.length === 1 && Array.isArray(arguments[0])) {
|
|
138
|
+
for (const v of arguments[0]) ids.push(v);
|
|
139
|
+
} else {
|
|
140
|
+
for (let i = 0; i < arguments.length; i++) ids.push(arguments[i]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
for (const v of ids) {
|
|
145
|
+
const id = parseInt(v, 10);
|
|
146
|
+
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
147
|
+
if (!isPlaceholderPresent(id)) continue;
|
|
148
|
+
seen.add(id);
|
|
149
|
+
try { orig.call(ez2, id); } catch (e) {}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
ez2.__nodebbEzoicPatched = true;
|
|
154
|
+
} catch (e) {}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
apply();
|
|
158
|
+
if (!window.ezstandalone.__nodebbEzoicPatchQueued) {
|
|
159
|
+
window.ezstandalone.__nodebbEzoicPatchQueued = true;
|
|
160
|
+
ez.cmd = ez.cmd || [];
|
|
161
|
+
ez.cmd.push(apply);
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {}
|
|
209
164
|
}
|
|
210
165
|
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
166
|
+
function safeCmd(token, fn) {
|
|
167
|
+
try {
|
|
168
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
169
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
170
|
+
window.ezstandalone.cmd.push(function () {
|
|
171
|
+
// Drop stale work after ajaxify navigation
|
|
172
|
+
if (token !== state.pageToken) return;
|
|
173
|
+
if (getPageKey() !== state.pageKey) return;
|
|
174
|
+
try { fn(); } catch (e) {}
|
|
175
|
+
});
|
|
176
|
+
} catch (e) {}
|
|
216
177
|
}
|
|
217
178
|
|
|
218
|
-
|
|
179
|
+
function showAd(id) {
|
|
180
|
+
if (!id) return;
|
|
181
|
+
// throttle to avoid repeated calls during rerenders
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
const last = state.lastShowAt.get(id) || 0;
|
|
184
|
+
if (now - last < 1500) return;
|
|
185
|
+
state.lastShowAt.set(id, now);
|
|
219
186
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
187
|
+
const token = state.pageToken;
|
|
188
|
+
safeCmd(token, () => {
|
|
189
|
+
if (!isPlaceholderPresent(id)) return;
|
|
190
|
+
patchShowAds();
|
|
191
|
+
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
192
|
+
window.ezstandalone.showAds(id);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
227
195
|
}
|
|
228
196
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// preserve common NodeBB list styling
|
|
235
|
-
if (target && target.classList && target.classList.contains('list-group-item')) wrap.classList.add('list-group-item');
|
|
236
|
-
}
|
|
197
|
+
// ----------------------------
|
|
198
|
+
// DOM insertion (HTML-valid: if target is <li>, wrapper is <li>)
|
|
199
|
+
// ----------------------------
|
|
200
|
+
function buildWrap(id, kindClass, afterPos, liLike) {
|
|
201
|
+
const wrap = document.createElement(liLike ? 'li' : 'div');
|
|
237
202
|
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
238
|
-
if (wrap.tagName === 'LI') {
|
|
239
|
-
wrap.setAttribute('role', 'presentation');
|
|
240
|
-
wrap.setAttribute('aria-hidden', 'true');
|
|
241
|
-
}
|
|
242
203
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
204
|
+
wrap.setAttribute('role', 'presentation');
|
|
243
205
|
wrap.style.width = '100%';
|
|
244
206
|
|
|
207
|
+
// Keep list styling if we're inside list-group
|
|
208
|
+
if (liLike && !wrap.classList.contains('list-group-item')) {
|
|
209
|
+
const targetIsListGroup = wrap.parentElement && wrap.parentElement.classList && wrap.parentElement.classList.contains('list-group');
|
|
210
|
+
// can't detect parent yet; we'll keep it minimal
|
|
211
|
+
}
|
|
212
|
+
|
|
245
213
|
const ph = document.createElement('div');
|
|
246
214
|
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
247
|
-
ph.setAttribute('data-ezoic-id', String(id));
|
|
248
215
|
wrap.appendChild(ph);
|
|
249
|
-
|
|
250
216
|
return wrap;
|
|
251
217
|
}
|
|
252
218
|
|
|
@@ -257,353 +223,255 @@
|
|
|
257
223
|
function insertAfter(target, id, kindClass, afterPos) {
|
|
258
224
|
if (!target || !target.insertAdjacentElement) return null;
|
|
259
225
|
if (findWrap(kindClass, afterPos)) return null;
|
|
260
|
-
if (insertingIds.has(id)) return null;
|
|
261
226
|
|
|
262
|
-
|
|
263
|
-
if (
|
|
227
|
+
// avoid duplicates if already exists
|
|
228
|
+
if (isPlaceholderPresent(id)) return null;
|
|
264
229
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
insertingIds.delete(id);
|
|
230
|
+
const liLike = String(target.tagName).toUpperCase() === 'LI';
|
|
231
|
+
const wrap = buildWrap(id, kindClass, afterPos, liLike);
|
|
232
|
+
|
|
233
|
+
// If list-group-item exists on target, mirror it to keep theme markup consistent
|
|
234
|
+
if (liLike && target.classList && target.classList.contains('list-group-item')) {
|
|
235
|
+
wrap.classList.add('list-group-item');
|
|
272
236
|
}
|
|
273
|
-
}
|
|
274
237
|
|
|
275
|
-
|
|
276
|
-
return
|
|
238
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
239
|
+
return wrap;
|
|
277
240
|
}
|
|
278
241
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (
|
|
242
|
+
// ----------------------------
|
|
243
|
+
// Observers: preload + rerun on NodeBB DOM changes
|
|
244
|
+
// ----------------------------
|
|
245
|
+
function ensureIO() {
|
|
246
|
+
if (state.io) return;
|
|
247
|
+
if (!('IntersectionObserver' in window)) return;
|
|
248
|
+
|
|
249
|
+
state.io = new IntersectionObserver((entries) => {
|
|
250
|
+
for (const e of entries) {
|
|
251
|
+
if (!e.isIntersecting) continue;
|
|
252
|
+
const ph = e.target;
|
|
253
|
+
const id = parseInt(String(ph.id).replace(PLACEHOLDER_PREFIX, ''), 10);
|
|
254
|
+
if (Number.isFinite(id)) showAd(id);
|
|
255
|
+
try { state.io.unobserve(ph); } catch (err) {}
|
|
256
|
+
}
|
|
257
|
+
}, { root: null, rootMargin: '1200px 0px', threshold: 0 });
|
|
258
|
+
}
|
|
285
259
|
|
|
260
|
+
function observePlaceholder(id) {
|
|
261
|
+
ensureIO();
|
|
262
|
+
if (!state.io) return;
|
|
286
263
|
const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
287
|
-
if (!ph
|
|
264
|
+
if (!ph) return;
|
|
265
|
+
try { state.io.observe(ph); } catch (e) {}
|
|
266
|
+
}
|
|
288
267
|
|
|
289
|
-
|
|
268
|
+
function ensureMO() {
|
|
269
|
+
if (state.mo) return;
|
|
270
|
+
state.mo = new MutationObserver(() => schedule(runCore));
|
|
271
|
+
try { state.mo.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
272
|
+
}
|
|
290
273
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
274
|
+
// ----------------------------
|
|
275
|
+
// Config / exclusion
|
|
276
|
+
// ----------------------------
|
|
277
|
+
async function fetchConfig() {
|
|
278
|
+
if (state.cfg) return state.cfg;
|
|
279
|
+
if (state.cfgPromise) return state.cfgPromise;
|
|
294
280
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
281
|
+
state.cfgPromise = (async () => {
|
|
282
|
+
try {
|
|
283
|
+
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
284
|
+
if (!res.ok) return null;
|
|
285
|
+
const cfg = await res.json();
|
|
286
|
+
state.cfg = cfg;
|
|
287
|
+
return cfg;
|
|
288
|
+
} catch (e) {
|
|
289
|
+
return null;
|
|
290
|
+
} finally {
|
|
291
|
+
state.cfgPromise = null;
|
|
300
292
|
}
|
|
293
|
+
})();
|
|
301
294
|
|
|
302
|
-
|
|
303
|
-
ez.cmd = ez.cmd || [];
|
|
304
|
-
if (!ph.__ezoicQueued) {
|
|
305
|
-
ph.__ezoicQueued = true;
|
|
306
|
-
ez.cmd.push(() => {
|
|
307
|
-
try {
|
|
308
|
-
if (EZOIC_BLOCKED) return;
|
|
309
|
-
const el = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
310
|
-
if (!el || !el.isConnected) return;
|
|
311
|
-
window.ezstandalone.showAds(id);
|
|
312
|
-
sessionDefinedIds.add(id);
|
|
313
|
-
} catch (e) {}
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
} catch (e) {}
|
|
295
|
+
return state.cfgPromise;
|
|
317
296
|
}
|
|
318
297
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
function ensurePreloadObserver() {
|
|
322
|
-
if (state.io) return state.io;
|
|
298
|
+
function getUserGroupNamesFromAjaxify() {
|
|
323
299
|
try {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
}, { root: null, rootMargin: PRELOAD_ROOT_MARGIN, threshold: 0.01 });
|
|
335
|
-
} catch (e) {
|
|
336
|
-
state.io = null;
|
|
337
|
-
}
|
|
338
|
-
return state.io;
|
|
300
|
+
const ax = window.ajaxify;
|
|
301
|
+
const u = ax && ax.data && (ax.data.user || ax.data.profile || null);
|
|
302
|
+
if (!u) return [];
|
|
303
|
+
// NodeBB varies by route/theme; handle multiple shapes
|
|
304
|
+
const groupsA = u.groups || u.group_names || u.groupNames;
|
|
305
|
+
if (Array.isArray(groupsA)) return groupsA.map(g => (g && g.name) ? g.name : String(g)).filter(Boolean);
|
|
306
|
+
if (typeof groupsA === 'string') return groupsA.split(',').map(s => s.trim()).filter(Boolean);
|
|
307
|
+
} catch (e) {}
|
|
308
|
+
return [];
|
|
339
309
|
}
|
|
340
310
|
|
|
341
|
-
function
|
|
342
|
-
const
|
|
343
|
-
if (!
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// If already above fold, fire immediately
|
|
348
|
-
try {
|
|
349
|
-
const r = ph.getBoundingClientRect();
|
|
350
|
-
if (r.top < window.innerHeight * 1.5 && r.bottom > -200) showAd(id);
|
|
351
|
-
} catch (e) {}
|
|
311
|
+
function parseExcludedGroupsFromCfg(cfg) {
|
|
312
|
+
const v = cfg && (cfg.excludedGroups || cfg.excludedGroupNames || cfg.excluded_groups);
|
|
313
|
+
if (!v) return [];
|
|
314
|
+
if (Array.isArray(v)) return v.map(x => (x && x.name) ? x.name : String(x)).filter(Boolean);
|
|
315
|
+
return String(v).split(',').map(s => s.trim()).filter(Boolean);
|
|
352
316
|
}
|
|
353
317
|
|
|
354
|
-
|
|
318
|
+
function isExcludedClientSide(cfg) {
|
|
319
|
+
// Prefer server decision if present
|
|
320
|
+
if (cfg && cfg.excluded === true) return true;
|
|
321
|
+
|
|
322
|
+
// Extra safety: if cfg contains excluded group names, cross-check client-side.
|
|
323
|
+
const excludedGroups = parseExcludedGroupsFromCfg(cfg);
|
|
324
|
+
if (!excludedGroups.length) return false;
|
|
325
|
+
const userGroups = getUserGroupNamesFromAjaxify();
|
|
326
|
+
if (!userGroups.length) return false;
|
|
355
327
|
|
|
328
|
+
const set = new Set(userGroups);
|
|
329
|
+
return excludedGroups.some(g => set.has(g));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ----------------------------
|
|
333
|
+
// Core injection
|
|
334
|
+
// ----------------------------
|
|
356
335
|
function computeTargets(count, interval, showFirst) {
|
|
357
336
|
const out = [];
|
|
358
337
|
if (count <= 0) return out;
|
|
359
338
|
if (showFirst) out.push(1);
|
|
360
|
-
for (let i =
|
|
361
|
-
if (i % interval === 0) out.push(i);
|
|
362
|
-
}
|
|
339
|
+
for (let i = interval; i <= count; i += interval) out.push(i);
|
|
363
340
|
return Array.from(new Set(out)).sort((a, b) => a - b);
|
|
364
341
|
}
|
|
365
342
|
|
|
366
|
-
function injectBetween(kindClass, items, interval, showFirst, pool
|
|
367
|
-
if (!items.length) return
|
|
343
|
+
function injectBetween(kindClass, items, interval, showFirst, pool) {
|
|
344
|
+
if (!items.length || !pool.length) return [];
|
|
368
345
|
|
|
369
346
|
const targets = computeTargets(items.length, interval, showFirst);
|
|
370
|
-
|
|
371
|
-
|
|
347
|
+
const insertedIds = [];
|
|
372
348
|
for (const afterPos of targets) {
|
|
373
|
-
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
374
|
-
|
|
375
349
|
const el = items[afterPos - 1];
|
|
376
350
|
if (!el || !el.isConnected) continue;
|
|
377
|
-
if (
|
|
351
|
+
if (!pool.length) break;
|
|
378
352
|
if (findWrap(kindClass, afterPos)) continue;
|
|
379
353
|
|
|
380
|
-
const id =
|
|
381
|
-
if (!id) break;
|
|
382
|
-
|
|
383
|
-
usedSet.add(id);
|
|
354
|
+
const id = pool.shift();
|
|
384
355
|
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
385
356
|
if (!wrap) {
|
|
386
|
-
|
|
357
|
+
// push back if couldn't insert
|
|
387
358
|
pool.unshift(id);
|
|
388
359
|
continue;
|
|
389
360
|
}
|
|
390
361
|
|
|
391
|
-
|
|
392
|
-
|
|
362
|
+
insertedIds.push(id);
|
|
363
|
+
|
|
364
|
+
// Above-the-fold: immediate attempt
|
|
365
|
+
const rect = wrap.getBoundingClientRect ? wrap.getBoundingClientRect() : null;
|
|
366
|
+
const vh = window.innerHeight || 800;
|
|
367
|
+
if (rect && rect.top < (vh * 1.5)) {
|
|
368
|
+
showAd(id);
|
|
369
|
+
} else {
|
|
370
|
+
observePlaceholder(id);
|
|
371
|
+
}
|
|
393
372
|
}
|
|
373
|
+
return insertedIds;
|
|
374
|
+
}
|
|
394
375
|
|
|
395
|
-
|
|
376
|
+
function removeAllAds() {
|
|
377
|
+
try { document.querySelectorAll(`.${WRAP_CLASS}`).forEach(n => n.remove()); } catch (e) {}
|
|
396
378
|
}
|
|
397
379
|
|
|
398
|
-
async function
|
|
399
|
-
|
|
400
|
-
const cfg = await fetchConfigOnce();
|
|
401
|
-
if (!cfg || cfg.excluded) return;
|
|
380
|
+
async function runCore() {
|
|
381
|
+
state.pageKey = getPageKey();
|
|
402
382
|
|
|
403
|
-
|
|
383
|
+
patchShowAds();
|
|
404
384
|
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
let pool = null;
|
|
408
|
-
let usedSet = null;
|
|
409
|
-
let kindClass = '';
|
|
385
|
+
const cfg = await fetchConfig();
|
|
386
|
+
if (!cfg) return;
|
|
410
387
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
usedSet = state.usedPosts;
|
|
415
|
-
kindClass = 'ezoic-ad-message';
|
|
416
|
-
} else if (kind === 'categoryTopics' && normalizeBool(cfg.enableBetweenAds)) {
|
|
417
|
-
items = getTopicItems();
|
|
418
|
-
pool = state.poolTopics;
|
|
419
|
-
usedSet = state.usedTopics;
|
|
420
|
-
kindClass = 'ezoic-ad-between';
|
|
421
|
-
} else if (kind === 'categories' && normalizeBool(cfg.enableCategoryAds)) {
|
|
422
|
-
items = getCategoryItems();
|
|
423
|
-
pool = state.poolCategories;
|
|
424
|
-
usedSet = state.usedCategories;
|
|
425
|
-
kindClass = 'ezoic-ad-categories';
|
|
426
|
-
} else {
|
|
388
|
+
// If excluded: ensure we remove any previously injected wrappers
|
|
389
|
+
if (isExcludedClientSide(cfg)) {
|
|
390
|
+
removeAllAds();
|
|
427
391
|
return;
|
|
428
392
|
}
|
|
429
393
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
// Insert after the very first item (above-the-fold)
|
|
433
|
-
const afterPos = 1;
|
|
434
|
-
const el = items[afterPos - 1];
|
|
435
|
-
if (!el || !el.isConnected) return;
|
|
436
|
-
if (isAdjacentAd(el)) return;
|
|
437
|
-
if (findWrap(kindClass, afterPos)) { state.heroDoneForPage = true; return; }
|
|
438
|
-
|
|
439
|
-
const id = pickId(pool);
|
|
440
|
-
if (!id) return;
|
|
394
|
+
const kind = getKind();
|
|
441
395
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
396
|
+
if (kind === 'topic' && normalizeBool(cfg.enableMessageAds)) {
|
|
397
|
+
const pool = parsePool(cfg.messagePlaceholderIds);
|
|
398
|
+
injectBetween(
|
|
399
|
+
'ezoic-ad-message',
|
|
400
|
+
getPostContainers(),
|
|
401
|
+
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
402
|
+
normalizeBool(cfg.showFirstMessageAd),
|
|
403
|
+
pool
|
|
404
|
+
);
|
|
447
405
|
return;
|
|
448
406
|
}
|
|
449
407
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (!cfg || cfg.excluded) return;
|
|
461
|
-
initPools(cfg);
|
|
462
|
-
|
|
463
|
-
const kind = getKind();
|
|
464
|
-
|
|
465
|
-
if (kind === 'topic') {
|
|
466
|
-
if (normalizeBool(cfg.enableMessageAds)) {
|
|
467
|
-
injectBetween(
|
|
468
|
-
'ezoic-ad-message',
|
|
469
|
-
getPostContainers(),
|
|
470
|
-
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
471
|
-
normalizeBool(cfg.showFirstMessageAd),
|
|
472
|
-
state.poolPosts,
|
|
473
|
-
state.usedPosts
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
} else if (kind === 'categoryTopics') {
|
|
477
|
-
if (normalizeBool(cfg.enableBetweenAds)) {
|
|
478
|
-
injectBetween(
|
|
479
|
-
'ezoic-ad-between',
|
|
480
|
-
getTopicItems(),
|
|
481
|
-
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
482
|
-
normalizeBool(cfg.showFirstTopicAd),
|
|
483
|
-
state.poolTopics,
|
|
484
|
-
state.usedTopics
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
} else if (kind === 'categories') {
|
|
488
|
-
if (normalizeBool(cfg.enableCategoryAds)) {
|
|
489
|
-
injectBetween(
|
|
490
|
-
'ezoic-ad-categories',
|
|
491
|
-
getCategoryItems(),
|
|
492
|
-
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
493
|
-
normalizeBool(cfg.showFirstCategoryAd),
|
|
494
|
-
state.poolCategories,
|
|
495
|
-
state.usedCategories
|
|
496
|
-
);
|
|
497
|
-
}
|
|
408
|
+
if (kind === 'categoryTopics' && normalizeBool(cfg.enableBetweenAds)) {
|
|
409
|
+
const pool = parsePool(cfg.placeholderIds);
|
|
410
|
+
injectBetween(
|
|
411
|
+
'ezoic-ad-between',
|
|
412
|
+
getTopicItems(),
|
|
413
|
+
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
414
|
+
normalizeBool(cfg.showFirstTopicAd),
|
|
415
|
+
pool
|
|
416
|
+
);
|
|
417
|
+
return;
|
|
498
418
|
}
|
|
499
|
-
}
|
|
500
419
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
420
|
+
if (kind === 'categories' && normalizeBool(cfg.enableCategoryAds)) {
|
|
421
|
+
const pool = parsePool(cfg.categoryPlaceholderIds);
|
|
422
|
+
injectBetween(
|
|
423
|
+
'ezoic-ad-categories',
|
|
424
|
+
getCategoryItems(),
|
|
425
|
+
Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
|
|
426
|
+
normalizeBool(cfg.showFirstCategoryAd),
|
|
427
|
+
pool
|
|
428
|
+
);
|
|
429
|
+
}
|
|
510
430
|
}
|
|
511
431
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
EZOIC_BLOCKED = true;
|
|
516
|
-
|
|
517
|
-
// remove all wrappers
|
|
518
|
-
try {
|
|
519
|
-
document.querySelectorAll(`.${WRAP_CLASS}`).forEach((el) => {
|
|
520
|
-
try { el.remove(); } catch (e) {}
|
|
521
|
-
});
|
|
522
|
-
} catch (e) {}
|
|
523
|
-
|
|
524
|
-
// reset state
|
|
432
|
+
function cleanupForNav() {
|
|
433
|
+
// New token => any pending safeCmd work becomes stale
|
|
434
|
+
state.pageToken += 1;
|
|
525
435
|
state.cfg = null;
|
|
526
|
-
state.
|
|
527
|
-
state.
|
|
528
|
-
state.poolCategories = [];
|
|
529
|
-
state.usedTopics.clear();
|
|
530
|
-
state.usedPosts.clear();
|
|
531
|
-
state.usedCategories.clear();
|
|
532
|
-
state.lastShowById.clear();
|
|
533
|
-
state.heroDoneForPage = false;
|
|
534
|
-
|
|
535
|
-
sessionDefinedIds.clear();
|
|
536
|
-
|
|
537
|
-
// keep observers alive (MutationObserver will re-trigger after navigation)
|
|
538
|
-
}
|
|
436
|
+
state.cfgPromise = null;
|
|
437
|
+
state.lastShowAt.clear();
|
|
539
438
|
|
|
540
|
-
|
|
541
|
-
if (state.
|
|
542
|
-
state.
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
} catch (e) {}
|
|
439
|
+
// Disconnect observers for old DOM
|
|
440
|
+
try { if (state.io) state.io.disconnect(); } catch (e) {}
|
|
441
|
+
state.io = null;
|
|
442
|
+
try { if (state.mo) state.mo.disconnect(); } catch (e) {}
|
|
443
|
+
state.mo = null;
|
|
444
|
+
|
|
445
|
+
removeAllAds();
|
|
548
446
|
}
|
|
549
447
|
|
|
550
|
-
|
|
448
|
+
// ----------------------------
|
|
449
|
+
// Bind to NodeBB 4.x ajaxify events
|
|
450
|
+
// ----------------------------
|
|
451
|
+
function bind() {
|
|
551
452
|
if (!$) return;
|
|
552
453
|
|
|
553
454
|
$(window).off('.ezoicInfinite');
|
|
554
455
|
|
|
555
|
-
$(window).on('action:ajaxify.start.ezoicInfinite', () =>
|
|
556
|
-
cleanup();
|
|
557
|
-
});
|
|
456
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => cleanupForNav());
|
|
558
457
|
|
|
559
458
|
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
560
459
|
state.pageKey = getPageKey();
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
warmUpNetwork();
|
|
564
|
-
patchShowAds();
|
|
565
|
-
ensurePreloadObserver();
|
|
566
|
-
ensureDomObserver();
|
|
567
|
-
|
|
568
|
-
// Ultra-fast above-the-fold first
|
|
569
|
-
insertHeroAdEarly().catch(() => {});
|
|
570
|
-
|
|
571
|
-
// Then normal insertion
|
|
572
|
-
scheduleRun();
|
|
460
|
+
ensureMO();
|
|
461
|
+
schedule(runCore);
|
|
573
462
|
});
|
|
574
463
|
|
|
575
|
-
// Infinite scroll
|
|
576
|
-
$(window).on('action:posts.loaded.ezoicInfinite
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
464
|
+
// Infinite scroll hooks
|
|
465
|
+
$(window).on('action:posts.loaded.ezoicInfinite', () => schedule(runCore));
|
|
466
|
+
$(window).on('action:topics.loaded.ezoicInfinite', () => schedule(runCore));
|
|
467
|
+
$(window).on('action:category.loaded.ezoicInfinite', () => schedule(runCore));
|
|
468
|
+
$(window).on('action:topic.loaded.ezoicInfinite', () => schedule(runCore));
|
|
580
469
|
}
|
|
581
470
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
ticking = true;
|
|
587
|
-
window.requestAnimationFrame(() => {
|
|
588
|
-
ticking = false;
|
|
589
|
-
if (!EZOIC_BLOCKED) scheduleRun();
|
|
590
|
-
});
|
|
591
|
-
}, { passive: true });
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// ---------- boot ----------
|
|
595
|
-
|
|
471
|
+
// Boot
|
|
472
|
+
cleanupForNav();
|
|
473
|
+
bind();
|
|
474
|
+
ensureMO();
|
|
596
475
|
state.pageKey = getPageKey();
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
ensurePreloadObserver();
|
|
600
|
-
ensureDomObserver();
|
|
601
|
-
|
|
602
|
-
bindNodeBB();
|
|
603
|
-
bindScroll();
|
|
604
|
-
|
|
605
|
-
// First paint: try hero + run
|
|
606
|
-
EZOIC_BLOCKED = false;
|
|
607
|
-
insertHeroAdEarly().catch(() => {});
|
|
608
|
-
scheduleRun();
|
|
609
|
-
})();
|
|
476
|
+
schedule(runCore);
|
|
477
|
+
})();
|
package/public/style.css
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
.ezoic-ad
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.ezoic-ad > [id^="ezoic-pub-ad-placeholder-"] {
|
|
11
|
-
margin: 0 !important;
|
|
12
|
-
padding: 0 !important;
|
|
13
|
-
min-height: 1px; /* keeps placeholder measurable for IO */
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/* Ezoic sometimes wraps in extra spans/divs with margins */
|
|
17
|
-
.ezoic-ad span.ezoic-ad,
|
|
18
|
-
.ezoic-ad .ezoic-ad {
|
|
19
|
-
margin: 0 !important;
|
|
20
|
-
padding: 0 !important;
|
|
21
|
-
}
|
|
1
|
+
.ezoic-ad,
|
|
2
|
+
.ezoic-ad *,
|
|
3
|
+
span.ezoic-ad,
|
|
4
|
+
span[class*="ezoic"] {
|
|
5
|
+
min-height: 0 !important;
|
|
6
|
+
min-width: 0 !important;
|
|
7
|
+
}
|