nodebb-plugin-ezoic-infinite 1.4.89 → 1.4.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/README.md +15 -0
- package/public/library.js +114 -0
- package/public/package.json +21 -0
- package/public/plugin.json +29 -0
- package/public/{admin.js → public/admin.js} +3 -2
- package/public/public/client.js +371 -0
- package/public/public/style.css +11 -0
- package/public/client.js +0 -940
- package/public/style.css +0 -46
- /package/public/{templates → public/templates}/admin/plugins/ezoic-infinite.tpl +0 -0
package/package.json
CHANGED
package/public/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# NodeBB Plugin – Ezoic Infinite (Production)
|
|
2
|
+
|
|
3
|
+
This plugin injects Ezoic placeholders between topics and posts on NodeBB 4.x,
|
|
4
|
+
with full support for infinite scroll.
|
|
5
|
+
|
|
6
|
+
## Key guarantees
|
|
7
|
+
- No duplicate ads back-to-back
|
|
8
|
+
- One showAds call per placeholder
|
|
9
|
+
- Fast reveal (MutationObserver on first child)
|
|
10
|
+
- Safe with ajaxify navigation
|
|
11
|
+
- Works with NodeBB 4.x + Harmony
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
- Placeholders must exist and be selected in Ezoic
|
|
15
|
+
- Use separate ID pools for topics vs messages
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const meta = require.main.require('./src/meta');
|
|
4
|
+
const groups = require.main.require('./src/groups');
|
|
5
|
+
const db = require.main.require('./src/database');
|
|
6
|
+
|
|
7
|
+
const SETTINGS_KEY = 'ezoic-infinite';
|
|
8
|
+
const plugin = {};
|
|
9
|
+
|
|
10
|
+
function normalizeExcludedGroups(value) {
|
|
11
|
+
if (!value) return [];
|
|
12
|
+
if (Array.isArray(value)) return value;
|
|
13
|
+
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseBool(v, def = false) {
|
|
17
|
+
if (v === undefined || v === null || v === '') return def;
|
|
18
|
+
if (typeof v === 'boolean') return v;
|
|
19
|
+
const s = String(v).toLowerCase();
|
|
20
|
+
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function getAllGroups() {
|
|
24
|
+
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
25
|
+
if (!names || !names.length) {
|
|
26
|
+
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
27
|
+
}
|
|
28
|
+
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
29
|
+
const data = await groups.getGroupsData(filtered);
|
|
30
|
+
// Sort alphabetically for ACP usability
|
|
31
|
+
data.sort((a, b) => String(a.name).localeCompare(String(b.name), 'fr', { sensitivity: 'base' }));
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
async function getSettings() {
|
|
35
|
+
const s = await meta.settings.get(SETTINGS_KEY);
|
|
36
|
+
return {
|
|
37
|
+
// Between-post ads (simple blocks) in category topic list
|
|
38
|
+
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
39
|
+
showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
|
|
40
|
+
placeholderIds: (s.placeholderIds || '').trim(),
|
|
41
|
+
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
42
|
+
|
|
43
|
+
// Home/categories list ads (between categories on / or /categories)
|
|
44
|
+
enableCategoryAds: parseBool(s.enableCategoryAds, false),
|
|
45
|
+
showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
|
|
46
|
+
categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
|
|
47
|
+
intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
|
|
48
|
+
|
|
49
|
+
// "Ad message" between replies (looks like a post)
|
|
50
|
+
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
51
|
+
showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
|
|
52
|
+
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
53
|
+
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
54
|
+
|
|
55
|
+
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function isUserExcluded(uid, excludedGroups) {
|
|
60
|
+
if (!uid || !excludedGroups.length) return false;
|
|
61
|
+
const userGroups = await groups.getUserGroups([uid]);
|
|
62
|
+
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
plugin.addAdminNavigation = async (header) => {
|
|
66
|
+
header.plugins = header.plugins || [];
|
|
67
|
+
header.plugins.push({
|
|
68
|
+
route: '/plugins/ezoic-infinite',
|
|
69
|
+
icon: 'fa-ad',
|
|
70
|
+
name: 'Ezoic Infinite Ads'
|
|
71
|
+
});
|
|
72
|
+
return header;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
plugin.init = async ({ router, middleware }) => {
|
|
76
|
+
async function render(req, res) {
|
|
77
|
+
const settings = await getSettings();
|
|
78
|
+
const allGroups = await getAllGroups();
|
|
79
|
+
|
|
80
|
+
res.render('admin/plugins/ezoic-infinite', {
|
|
81
|
+
title: 'Ezoic Infinite Ads',
|
|
82
|
+
...settings,
|
|
83
|
+
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
84
|
+
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
85
|
+
allGroups,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
90
|
+
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
91
|
+
|
|
92
|
+
router.get('/api/plugins/ezoic-infinite/config', middleware.buildHeader, async (req, res) => {
|
|
93
|
+
const settings = await getSettings();
|
|
94
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
95
|
+
|
|
96
|
+
res.json({
|
|
97
|
+
excluded,
|
|
98
|
+
enableBetweenAds: settings.enableBetweenAds,
|
|
99
|
+
showFirstTopicAd: settings.showFirstTopicAd,
|
|
100
|
+
placeholderIds: settings.placeholderIds,
|
|
101
|
+
intervalPosts: settings.intervalPosts,
|
|
102
|
+
enableCategoryAds: settings.enableCategoryAds,
|
|
103
|
+
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
104
|
+
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
105
|
+
intervalCategories: settings.intervalCategories,
|
|
106
|
+
enableMessageAds: settings.enableMessageAds,
|
|
107
|
+
showFirstMessageAd: settings.showFirstMessageAd,
|
|
108
|
+
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
109
|
+
messageIntervalPosts: settings.messageIntervalPosts,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
module.exports = plugin;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nodebb-plugin-ezoic-infinite",
|
|
3
|
+
"version": "1.4.8",
|
|
4
|
+
"description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
|
|
5
|
+
"main": "library.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nodebb",
|
|
9
|
+
"nodebb-plugin",
|
|
10
|
+
"ezoic",
|
|
11
|
+
"ads",
|
|
12
|
+
"infinite-scroll"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"nodebb": ">=4.0.0"
|
|
16
|
+
},
|
|
17
|
+
"nbbpm": {
|
|
18
|
+
"compatibility": "^4.0.0"
|
|
19
|
+
},
|
|
20
|
+
"private": false
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nodebb-plugin-ezoic-infinite",
|
|
3
|
+
"name": "NodeBB Ezoic Infinite Ads",
|
|
4
|
+
"description": "Ezoic ads with infinite scroll using a pool of placeholder IDs",
|
|
5
|
+
"library": "./library.js",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"hook": "static:app.load",
|
|
9
|
+
"method": "init"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"hook": "filter:admin.header.build",
|
|
13
|
+
"method": "addAdminNavigation"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"staticDirs": {
|
|
17
|
+
"public": "public"
|
|
18
|
+
},
|
|
19
|
+
"acpScripts": [
|
|
20
|
+
"public/admin.js"
|
|
21
|
+
],
|
|
22
|
+
"scripts": [
|
|
23
|
+
"public/client.js"
|
|
24
|
+
],
|
|
25
|
+
"templates": "public/templates",
|
|
26
|
+
"css": [
|
|
27
|
+
"public/style.css"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
e.preventDefault();
|
|
14
14
|
|
|
15
15
|
Settings.save('ezoic-infinite', $form, function () {
|
|
16
|
+
// Toast vert (NodeBB core)
|
|
16
17
|
if (alerts && typeof alerts.success === 'function') {
|
|
17
|
-
alerts.success('
|
|
18
|
+
alerts.success('Enregistré');
|
|
18
19
|
} else if (window.app && typeof window.app.alertSuccess === 'function') {
|
|
19
|
-
window.app.alertSuccess('
|
|
20
|
+
window.app.alertSuccess('Enregistré');
|
|
20
21
|
}
|
|
21
22
|
});
|
|
22
23
|
});
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
5
|
+
|
|
6
|
+
const SELECTORS = {
|
|
7
|
+
topicItem: 'li[component="category/topic"]',
|
|
8
|
+
postItem: '[component="post"][data-pid]',
|
|
9
|
+
categoryItem: 'li[component="categories/category"]',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const WRAP_CLASS = 'ezoic-ad';
|
|
13
|
+
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
14
|
+
const MAX_INSERTS_PER_RUN = 3;
|
|
15
|
+
|
|
16
|
+
const state = {
|
|
17
|
+
pageKey: null,
|
|
18
|
+
cfg: null,
|
|
19
|
+
cfgPromise: null,
|
|
20
|
+
poolTopics: [],
|
|
21
|
+
poolPosts: [],
|
|
22
|
+
poolCategories: [],
|
|
23
|
+
usedTopics: new Set(),
|
|
24
|
+
usedPosts: new Set(),
|
|
25
|
+
usedCategories: new Set(),
|
|
26
|
+
lastShowById: new Map(),
|
|
27
|
+
canShowAds: false,
|
|
28
|
+
scheduled: false,
|
|
29
|
+
timer: null,
|
|
30
|
+
obs: null,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const sessionDefinedIds = new Set();
|
|
34
|
+
|
|
35
|
+
function normalizeBool(v) {
|
|
36
|
+
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function uniqInts(lines) {
|
|
40
|
+
const out = [], seen = new Set();
|
|
41
|
+
for (const v of lines) {
|
|
42
|
+
const n = parseInt(v, 10);
|
|
43
|
+
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
44
|
+
seen.add(n);
|
|
45
|
+
out.push(n);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parsePool(raw) {
|
|
52
|
+
if (!raw) return [];
|
|
53
|
+
const lines = String(raw).split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
54
|
+
return uniqInts(lines);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getPageKey() {
|
|
58
|
+
try {
|
|
59
|
+
const ax = window.ajaxify;
|
|
60
|
+
if (ax && ax.data) {
|
|
61
|
+
if (ax.data.tid) return `topic:${ax.data.tid}`;
|
|
62
|
+
if (ax.data.cid) return `cid:${ax.data.cid}:${window.location.pathname}`;
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
return window.location.pathname;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getKind() {
|
|
69
|
+
const p = window.location.pathname || '';
|
|
70
|
+
if (/^\/topic\//.test(p)) return 'topic';
|
|
71
|
+
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
72
|
+
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
73
|
+
return 'other';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getTopicItems() {
|
|
77
|
+
return Array.from(document.querySelectorAll(SELECTORS.topicItem));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getPostContainers() {
|
|
81
|
+
return Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getCategoryItems() {
|
|
85
|
+
return Array.from(document.querySelectorAll(SELECTORS.categoryItem));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function fetchConfig() {
|
|
89
|
+
if (state.cfg) return state.cfg;
|
|
90
|
+
if (state.cfgPromise) return state.cfgPromise;
|
|
91
|
+
|
|
92
|
+
state.cfgPromise = (async () => {
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch('/api/ezoic-infinite/config');
|
|
95
|
+
if (!res.ok) return null;
|
|
96
|
+
const cfg = await res.json();
|
|
97
|
+
state.cfg = cfg;
|
|
98
|
+
return cfg;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
|
|
104
|
+
return state.cfgPromise;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function initPools(cfg) {
|
|
108
|
+
if (!state.poolTopics.length) state.poolTopics = parsePool(cfg.poolTopics);
|
|
109
|
+
if (!state.poolPosts.length) state.poolPosts = parsePool(cfg.poolPosts);
|
|
110
|
+
if (!state.poolCategories.length) state.poolCategories = parsePool(cfg.poolCategories);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function destroyPlaceholderIds(ids) {
|
|
114
|
+
if (!ids || !ids.length) return;
|
|
115
|
+
const filtered = ids.filter(id => Number.isFinite(id) && id > 0);
|
|
116
|
+
if (!filtered.length) return;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
120
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
121
|
+
|
|
122
|
+
const call = () => {
|
|
123
|
+
if (typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
124
|
+
window.ezstandalone.destroyPlaceholders(filtered);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
129
|
+
call();
|
|
130
|
+
} else {
|
|
131
|
+
window.ezstandalone.cmd.push(call);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Recyclage: libérer après 100ms
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
filtered.forEach(id => sessionDefinedIds.delete(id));
|
|
137
|
+
}, 100);
|
|
138
|
+
} catch (e) {}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildWrap(id, kindClass, afterPos) {
|
|
142
|
+
const wrap = document.createElement('div');
|
|
143
|
+
wrap.className = `${WRAP_CLASS} ${kindClass}`;
|
|
144
|
+
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
145
|
+
wrap.style.width = '100%';
|
|
146
|
+
|
|
147
|
+
const ph = document.createElement('div');
|
|
148
|
+
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
149
|
+
wrap.appendChild(ph);
|
|
150
|
+
return wrap;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function findWrap(kindClass, afterPos) {
|
|
154
|
+
return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function insertAfter(target, id, kindClass, afterPos) {
|
|
158
|
+
if (!target || !target.insertAdjacentElement) return null;
|
|
159
|
+
if (findWrap(kindClass, afterPos)) return null;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const wrap = buildWrap(id, kindClass, afterPos);
|
|
163
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
164
|
+
return wrap;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function pickId(pool) {
|
|
171
|
+
if (!pool || !pool.length) return null;
|
|
172
|
+
return pool.shift();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function callShowAds(id) {
|
|
176
|
+
if (!id) return;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
180
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
181
|
+
|
|
182
|
+
window.ezstandalone.cmd.push(function() {
|
|
183
|
+
if (typeof window.ezstandalone.showAds === 'function') {
|
|
184
|
+
window.ezstandalone.showAds(id);
|
|
185
|
+
sessionDefinedIds.add(id);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
} catch (e) {}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function injectBetween(kindClass, items, interval, showFirst, pool, usedSet) {
|
|
192
|
+
if (!items || items.length === 0) return 0;
|
|
193
|
+
|
|
194
|
+
let inserted = 0;
|
|
195
|
+
const targets = [];
|
|
196
|
+
|
|
197
|
+
for (let i = 0; i < items.length; i++) {
|
|
198
|
+
const afterPos = i + 1;
|
|
199
|
+
if (afterPos === 1 && !showFirst) continue;
|
|
200
|
+
if (afterPos % interval !== (showFirst ? 1 : 0)) continue;
|
|
201
|
+
targets.push(afterPos);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (const afterPos of targets) {
|
|
205
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
206
|
+
|
|
207
|
+
const el = items[afterPos - 1];
|
|
208
|
+
if (!el || !el.isConnected) continue;
|
|
209
|
+
if (findWrap(kindClass, afterPos)) continue;
|
|
210
|
+
|
|
211
|
+
const id = pickId(pool);
|
|
212
|
+
if (!id) break;
|
|
213
|
+
|
|
214
|
+
const wrap = insertAfter(el, id, kindClass, afterPos);
|
|
215
|
+
if (!wrap) continue;
|
|
216
|
+
|
|
217
|
+
usedSet.add(id);
|
|
218
|
+
callShowAds(id);
|
|
219
|
+
inserted += 1;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return inserted;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function runCore() {
|
|
226
|
+
if (!state.canShowAds) return;
|
|
227
|
+
|
|
228
|
+
const cfg = await fetchConfig();
|
|
229
|
+
if (!cfg || cfg.excluded) return;
|
|
230
|
+
|
|
231
|
+
initPools(cfg);
|
|
232
|
+
|
|
233
|
+
const kind = getKind();
|
|
234
|
+
let inserted = 0;
|
|
235
|
+
|
|
236
|
+
if (kind === 'topic' && normalizeBool(cfg.enableMessageAds)) {
|
|
237
|
+
inserted = injectBetween(
|
|
238
|
+
'ezoic-ad-message',
|
|
239
|
+
getPostContainers(),
|
|
240
|
+
Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
241
|
+
normalizeBool(cfg.showFirstMessageAd),
|
|
242
|
+
state.poolPosts,
|
|
243
|
+
state.usedPosts
|
|
244
|
+
);
|
|
245
|
+
} else if (kind === 'categoryTopics' && normalizeBool(cfg.enableBetweenAds)) {
|
|
246
|
+
inserted = injectBetween(
|
|
247
|
+
'ezoic-ad-between',
|
|
248
|
+
getTopicItems(),
|
|
249
|
+
Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
|
|
250
|
+
normalizeBool(cfg.showFirstTopicAd),
|
|
251
|
+
state.poolTopics,
|
|
252
|
+
state.usedTopics
|
|
253
|
+
);
|
|
254
|
+
} else if (kind === 'categories' && normalizeBool(cfg.enableCategoryAds)) {
|
|
255
|
+
inserted = injectBetween(
|
|
256
|
+
'ezoic-ad-category',
|
|
257
|
+
getCategoryItems(),
|
|
258
|
+
Math.max(1, parseInt(cfg.intervalCategories, 10) || 6),
|
|
259
|
+
normalizeBool(cfg.showFirstCategoryAd),
|
|
260
|
+
state.poolCategories,
|
|
261
|
+
state.usedCategories
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function scheduleRun() {
|
|
267
|
+
if (state.scheduled) return;
|
|
268
|
+
state.scheduled = true;
|
|
269
|
+
|
|
270
|
+
clearTimeout(state.timer);
|
|
271
|
+
state.timer = setTimeout(() => {
|
|
272
|
+
state.scheduled = false;
|
|
273
|
+
const pk = getPageKey();
|
|
274
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
275
|
+
runCore().catch(() => {});
|
|
276
|
+
}, 50);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function cleanup() {
|
|
280
|
+
const allIds = [...state.usedTopics, ...state.usedPosts, ...state.usedCategories];
|
|
281
|
+
if (allIds.length) destroyPlaceholderIds(allIds);
|
|
282
|
+
|
|
283
|
+
document.querySelectorAll('.ezoic-ad').forEach(el => {
|
|
284
|
+
try { el.remove(); } catch (e) {}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
state.pageKey = getPageKey();
|
|
288
|
+
state.cfg = null;
|
|
289
|
+
state.cfgPromise = null;
|
|
290
|
+
state.poolTopics = [];
|
|
291
|
+
state.poolPosts = [];
|
|
292
|
+
state.poolCategories = [];
|
|
293
|
+
state.usedTopics.clear();
|
|
294
|
+
state.usedPosts.clear();
|
|
295
|
+
state.usedCategories.clear();
|
|
296
|
+
state.lastShowById.clear();
|
|
297
|
+
sessionDefinedIds.clear();
|
|
298
|
+
|
|
299
|
+
if (state.obs) {
|
|
300
|
+
state.obs.disconnect();
|
|
301
|
+
state.obs = null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
state.scheduled = false;
|
|
305
|
+
clearTimeout(state.timer);
|
|
306
|
+
state.timer = null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function ensureObserver() {
|
|
310
|
+
if (state.obs) return;
|
|
311
|
+
state.obs = new MutationObserver(() => scheduleRun());
|
|
312
|
+
try {
|
|
313
|
+
state.obs.observe(document.body, { childList: true, subtree: true });
|
|
314
|
+
} catch (e) {}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function waitForContentThenRun() {
|
|
318
|
+
const kind = getKind();
|
|
319
|
+
let selector = SELECTORS.postItem;
|
|
320
|
+
if (kind === 'categoryTopics') selector = SELECTORS.topicItem;
|
|
321
|
+
else if (kind === 'categories') selector = SELECTORS.categoryItem;
|
|
322
|
+
|
|
323
|
+
const check = () => {
|
|
324
|
+
if (document.querySelector(selector)) {
|
|
325
|
+
scheduleRun();
|
|
326
|
+
} else {
|
|
327
|
+
setTimeout(check, 200);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
check();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function bind() {
|
|
334
|
+
if (!$) return;
|
|
335
|
+
|
|
336
|
+
$(window).off('.ezoicInfinite');
|
|
337
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => cleanup());
|
|
338
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
339
|
+
state.pageKey = getPageKey();
|
|
340
|
+
ensureObserver();
|
|
341
|
+
state.canShowAds = true;
|
|
342
|
+
scheduleRun();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
346
|
+
ensureObserver();
|
|
347
|
+
waitForContentThenRun();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
351
|
+
ensureObserver();
|
|
352
|
+
waitForContentThenRun();
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function init() {
|
|
357
|
+
state.pageKey = getPageKey();
|
|
358
|
+
state.canShowAds = true;
|
|
359
|
+
bind();
|
|
360
|
+
ensureObserver();
|
|
361
|
+
waitForContentThenRun();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if ($ && $(document).ready) {
|
|
365
|
+
$(document).ready(init);
|
|
366
|
+
} else if (document.readyState === 'loading') {
|
|
367
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
368
|
+
} else {
|
|
369
|
+
init();
|
|
370
|
+
}
|
|
371
|
+
})();
|