nodebb-plugin-ezoic-infinite 1.8.19 → 1.8.20
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 +150 -104
- package/package.json +1 -1
- package/public/admin.js +32 -16
- package/public/client.js +34 -105
package/library.js
CHANGED
|
@@ -1,159 +1,205 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const meta
|
|
3
|
+
const meta = require.main.require('./src/meta');
|
|
4
4
|
const groups = require.main.require('./src/groups');
|
|
5
|
-
const db
|
|
5
|
+
const db = require.main.require('./src/database');
|
|
6
6
|
|
|
7
7
|
const SETTINGS_KEY = 'ezoic-infinite';
|
|
8
|
-
const
|
|
8
|
+
const SETTINGS_TTL_MS = 30_000;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const EZOIC_SCRIPTS = `<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>
|
|
11
|
+
<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>
|
|
12
|
+
<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>
|
|
13
|
+
<script>
|
|
14
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
15
|
+
ezstandalone.cmd = ezstandalone.cmd || [];
|
|
16
|
+
</script>`;
|
|
17
|
+
|
|
18
|
+
const DEFAULTS = Object.freeze({
|
|
19
|
+
enableBetweenAds: true,
|
|
20
|
+
showFirstTopicAd: false,
|
|
21
|
+
placeholderIds: '',
|
|
22
|
+
intervalPosts: 6,
|
|
23
|
+
enableCategoryAds: false,
|
|
24
|
+
showFirstCategoryAd: false,
|
|
25
|
+
categoryPlaceholderIds: '',
|
|
26
|
+
intervalCategories: 4,
|
|
27
|
+
enableMessageAds: false,
|
|
28
|
+
showFirstMessageAd: false,
|
|
29
|
+
messagePlaceholderIds: '',
|
|
30
|
+
messageIntervalPosts: 3,
|
|
31
|
+
excludedGroups: [],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const CONFIG_FIELDS = Object.freeze([
|
|
35
|
+
'enableBetweenAds', 'showFirstTopicAd', 'placeholderIds', 'intervalPosts',
|
|
36
|
+
'enableCategoryAds', 'showFirstCategoryAd', 'categoryPlaceholderIds', 'intervalCategories',
|
|
37
|
+
'enableMessageAds', 'showFirstMessageAd', 'messagePlaceholderIds', 'messageIntervalPosts',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const plugin = {
|
|
41
|
+
_settingsCache: null,
|
|
42
|
+
_settingsCacheAt: 0,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function toBool(value, fallback = false) {
|
|
46
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
47
|
+
if (typeof value === 'boolean') return value;
|
|
48
|
+
switch (String(value).trim().toLowerCase()) {
|
|
49
|
+
case '1':
|
|
50
|
+
case 'true':
|
|
51
|
+
case 'on':
|
|
52
|
+
case 'yes':
|
|
53
|
+
return true;
|
|
54
|
+
default:
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toPositiveInt(value, fallback) {
|
|
60
|
+
const parsed = Number.parseInt(value, 10);
|
|
61
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toStringTrim(value) {
|
|
65
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
66
|
+
}
|
|
11
67
|
|
|
12
68
|
function normalizeExcludedGroups(value) {
|
|
13
69
|
if (!value) return [];
|
|
14
|
-
if (Array.isArray(value))
|
|
15
|
-
|
|
16
|
-
const s = String(value).trim();
|
|
17
|
-
if (s.startsWith('[')) {
|
|
18
|
-
try { const parsed = JSON.parse(s); if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean); } catch (_) {}
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return value.map(String).map(v => v.trim()).filter(Boolean);
|
|
19
72
|
}
|
|
20
|
-
|
|
21
|
-
|
|
73
|
+
|
|
74
|
+
const raw = String(value).trim();
|
|
75
|
+
if (!raw) return [];
|
|
76
|
+
|
|
77
|
+
if (raw.startsWith('[')) {
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(raw);
|
|
80
|
+
if (Array.isArray(parsed)) {
|
|
81
|
+
return parsed.map(String).map(v => v.trim()).filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return raw.split(',').map(v => v.trim()).filter(Boolean);
|
|
22
87
|
}
|
|
23
88
|
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
89
|
+
function buildSettings(raw = {}) {
|
|
90
|
+
return {
|
|
91
|
+
enableBetweenAds: toBool(raw.enableBetweenAds, DEFAULTS.enableBetweenAds),
|
|
92
|
+
showFirstTopicAd: toBool(raw.showFirstTopicAd, DEFAULTS.showFirstTopicAd),
|
|
93
|
+
placeholderIds: toStringTrim(raw.placeholderIds),
|
|
94
|
+
intervalPosts: toPositiveInt(raw.intervalPosts, DEFAULTS.intervalPosts),
|
|
95
|
+
enableCategoryAds: toBool(raw.enableCategoryAds, DEFAULTS.enableCategoryAds),
|
|
96
|
+
showFirstCategoryAd: toBool(raw.showFirstCategoryAd, DEFAULTS.showFirstCategoryAd),
|
|
97
|
+
categoryPlaceholderIds: toStringTrim(raw.categoryPlaceholderIds),
|
|
98
|
+
intervalCategories: toPositiveInt(raw.intervalCategories, DEFAULTS.intervalCategories),
|
|
99
|
+
enableMessageAds: toBool(raw.enableMessageAds, DEFAULTS.enableMessageAds),
|
|
100
|
+
showFirstMessageAd: toBool(raw.showFirstMessageAd, DEFAULTS.showFirstMessageAd),
|
|
101
|
+
messagePlaceholderIds: toStringTrim(raw.messagePlaceholderIds),
|
|
102
|
+
messageIntervalPosts: toPositiveInt(raw.messageIntervalPosts, DEFAULTS.messageIntervalPosts),
|
|
103
|
+
excludedGroups: normalizeExcludedGroups(raw.excludedGroups),
|
|
104
|
+
};
|
|
29
105
|
}
|
|
30
106
|
|
|
31
|
-
async function
|
|
107
|
+
async function listNonPrivilegeGroups() {
|
|
32
108
|
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
33
|
-
if (!names || !names.length) {
|
|
109
|
+
if (!Array.isArray(names) || !names.length) {
|
|
34
110
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
35
111
|
}
|
|
36
|
-
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
37
|
-
const data = await groups.getGroupsData(filtered);
|
|
38
|
-
const valid = data.filter(g => g && g.name);
|
|
39
|
-
valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
40
|
-
return valid;
|
|
41
|
-
}
|
|
42
112
|
|
|
43
|
-
|
|
113
|
+
const publicNames = (names || []).filter(name => !groups.isPrivilegeGroup(name));
|
|
114
|
+
const groupData = await groups.getGroupsData(publicNames);
|
|
44
115
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
116
|
+
return (groupData || [])
|
|
117
|
+
.filter(group => group && group.name)
|
|
118
|
+
.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
119
|
+
}
|
|
48
120
|
|
|
49
121
|
async function getSettings() {
|
|
50
122
|
const now = Date.now();
|
|
51
|
-
if (_settingsCache && (now - _settingsCacheAt) <
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
_settingsCache = {
|
|
55
|
-
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
56
|
-
showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
|
|
57
|
-
placeholderIds: (s.placeholderIds || '').trim(),
|
|
58
|
-
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
59
|
-
enableCategoryAds: parseBool(s.enableCategoryAds, false),
|
|
60
|
-
showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
|
|
61
|
-
categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
|
|
62
|
-
intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
|
|
63
|
-
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
64
|
-
showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
|
|
65
|
-
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
66
|
-
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
67
|
-
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
68
|
-
};
|
|
69
|
-
return _settingsCache;
|
|
70
|
-
}
|
|
123
|
+
if (plugin._settingsCache && (now - plugin._settingsCacheAt) < SETTINGS_TTL_MS) {
|
|
124
|
+
return plugin._settingsCache;
|
|
125
|
+
}
|
|
71
126
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
127
|
+
const raw = await meta.settings.get(SETTINGS_KEY);
|
|
128
|
+
const settings = buildSettings(raw);
|
|
129
|
+
|
|
130
|
+
plugin._settingsCache = settings;
|
|
131
|
+
plugin._settingsCacheAt = now;
|
|
132
|
+
return settings;
|
|
76
133
|
}
|
|
77
134
|
|
|
78
|
-
|
|
135
|
+
async function isUserExcluded(uid, excludedGroups) {
|
|
136
|
+
if (!uid || !Array.isArray(excludedGroups) || !excludedGroups.length) return false;
|
|
79
137
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<script>
|
|
84
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
85
|
-
ezstandalone.cmd = ezstandalone.cmd || [];
|
|
86
|
-
</script>`;
|
|
138
|
+
const userGroups = await groups.getUserGroups([uid]);
|
|
139
|
+
const names = (userGroups && userGroups[0]) || [];
|
|
140
|
+
const excludedSet = new Set(excludedGroups);
|
|
87
141
|
|
|
88
|
-
|
|
142
|
+
return names.some(group => group && excludedSet.has(group.name));
|
|
143
|
+
}
|
|
89
144
|
|
|
90
|
-
plugin.onSettingsSet = function (data) {
|
|
91
|
-
if (data && data.hash === SETTINGS_KEY)
|
|
145
|
+
plugin.onSettingsSet = function onSettingsSet(data) {
|
|
146
|
+
if (data && data.hash === SETTINGS_KEY) {
|
|
147
|
+
plugin._settingsCache = null;
|
|
148
|
+
plugin._settingsCacheAt = 0;
|
|
149
|
+
}
|
|
92
150
|
};
|
|
93
151
|
|
|
94
|
-
plugin.addAdminNavigation = async (header)
|
|
152
|
+
plugin.addAdminNavigation = async function addAdminNavigation(header) {
|
|
95
153
|
header.plugins = header.plugins || [];
|
|
96
|
-
header.plugins.push({
|
|
154
|
+
header.plugins.push({
|
|
155
|
+
route: '/plugins/ezoic-infinite',
|
|
156
|
+
icon: 'fa-ad',
|
|
157
|
+
name: 'Ezoic Infinite Ads',
|
|
158
|
+
});
|
|
97
159
|
return header;
|
|
98
160
|
};
|
|
99
161
|
|
|
100
|
-
|
|
101
|
-
* Injecte les scripts Ezoic dans le <head> via templateData.customHTML.
|
|
102
|
-
*
|
|
103
|
-
* NodeBB v4 / thème Harmony : header.tpl contient {{customHTML}} dans le <head>
|
|
104
|
-
* (render.js ligne 232 : templateValues.customHTML = meta.config.customHTML).
|
|
105
|
-
* Le hook filter:middleware.renderHeader reçoit templateData = headerFooterData
|
|
106
|
-
* et est rendu via req.app.renderAsync('header', hookReturn.templateData).
|
|
107
|
-
* On préfixe customHTML pour que nos scripts passent AVANT le customHTML admin,
|
|
108
|
-
* tout en préservant ce dernier.
|
|
109
|
-
*/
|
|
110
|
-
plugin.injectEzoicHead = async (data) => {
|
|
162
|
+
plugin.injectEzoicHead = async function injectEzoicHead(data) {
|
|
111
163
|
try {
|
|
112
164
|
const settings = await getSettings();
|
|
113
|
-
const uid
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// Préfixer : nos scripts d'abord, puis le customHTML existant de l'admin
|
|
117
|
-
data.templateData.customHTML = EZOIC_SCRIPTS + (data.templateData.customHTML || '');
|
|
165
|
+
const uid = data?.req?.uid || 0;
|
|
166
|
+
if (await isUserExcluded(uid, settings.excludedGroups)) {
|
|
167
|
+
return data;
|
|
118
168
|
}
|
|
119
|
-
|
|
169
|
+
|
|
170
|
+
const templateData = data.templateData || (data.templateData = {});
|
|
171
|
+
templateData.customHTML = `${EZOIC_SCRIPTS}${templateData.customHTML || ''}`;
|
|
172
|
+
} catch {}
|
|
173
|
+
|
|
120
174
|
return data;
|
|
121
175
|
};
|
|
122
176
|
|
|
123
|
-
plugin.init = async ({ router, middleware })
|
|
124
|
-
async function
|
|
125
|
-
const settings
|
|
126
|
-
|
|
177
|
+
plugin.init = async function init({ router, middleware }) {
|
|
178
|
+
async function renderAdmin(req, res) {
|
|
179
|
+
const [settings, allGroups] = await Promise.all([
|
|
180
|
+
getSettings(),
|
|
181
|
+
listNonPrivilegeGroups(),
|
|
182
|
+
]);
|
|
183
|
+
|
|
127
184
|
res.render('admin/plugins/ezoic-infinite', {
|
|
128
185
|
title: 'Ezoic Infinite Ads',
|
|
129
186
|
...settings,
|
|
130
187
|
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
131
|
-
enableMessageAds_checked:
|
|
188
|
+
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
132
189
|
allGroups,
|
|
133
190
|
});
|
|
134
191
|
}
|
|
135
192
|
|
|
136
|
-
router.get('/admin/plugins/ezoic-infinite',
|
|
137
|
-
router.get('/api/admin/plugins/ezoic-infinite',
|
|
193
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, renderAdmin);
|
|
194
|
+
router.get('/api/admin/plugins/ezoic-infinite', renderAdmin);
|
|
138
195
|
|
|
139
196
|
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
140
197
|
const settings = await getSettings();
|
|
141
198
|
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
placeholderIds: settings.placeholderIds,
|
|
147
|
-
intervalPosts: settings.intervalPosts,
|
|
148
|
-
enableCategoryAds: settings.enableCategoryAds,
|
|
149
|
-
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
150
|
-
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
151
|
-
intervalCategories: settings.intervalCategories,
|
|
152
|
-
enableMessageAds: settings.enableMessageAds,
|
|
153
|
-
showFirstMessageAd: settings.showFirstMessageAd,
|
|
154
|
-
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
155
|
-
messageIntervalPosts: settings.messageIntervalPosts,
|
|
156
|
-
});
|
|
199
|
+
|
|
200
|
+
const payload = { excluded };
|
|
201
|
+
for (const key of CONFIG_FIELDS) payload[key] = settings[key];
|
|
202
|
+
res.json(payload);
|
|
157
203
|
});
|
|
158
204
|
};
|
|
159
205
|
|
package/package.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -1,28 +1,44 @@
|
|
|
1
1
|
/* globals ajaxify */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
(function () {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
(function initAdminEzoicSettings() {
|
|
5
|
+
const FORM_SELECTOR = '.ezoic-infinite-settings';
|
|
6
|
+
const SAVE_SELECTOR = '#save';
|
|
7
|
+
const SETTINGS_KEY = 'ezoic-infinite';
|
|
8
|
+
const EVENT_NS = '.ezoicInfinite';
|
|
9
|
+
|
|
10
|
+
function showSaved(alerts) {
|
|
11
|
+
if (alerts && typeof alerts.success === 'function') {
|
|
12
|
+
alerts.success('[[admin/settings:saved]]');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (window.app && typeof window.app.alertSuccess === 'function') {
|
|
16
|
+
window.app.alertSuccess('[[admin/settings:saved]]');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
|
|
20
|
+
function bind(Settings, alerts, $form) {
|
|
21
|
+
Settings.load(SETTINGS_KEY, $form);
|
|
11
22
|
|
|
12
|
-
|
|
23
|
+
$(SAVE_SELECTOR)
|
|
24
|
+
.off(`click${EVENT_NS}`)
|
|
25
|
+
.on(`click${EVENT_NS}`, function onSave(e) {
|
|
13
26
|
e.preventDefault();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (alerts && typeof alerts.success === 'function') {
|
|
17
|
-
alerts.success('[[admin/settings:saved]]');
|
|
18
|
-
} else if (window.app && typeof window.app.alertSuccess === 'function') {
|
|
19
|
-
window.app.alertSuccess('[[admin/settings:saved]]');
|
|
20
|
-
}
|
|
27
|
+
Settings.save(SETTINGS_KEY, $form, function onSaved() {
|
|
28
|
+
showSaved(alerts);
|
|
21
29
|
});
|
|
22
30
|
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function boot() {
|
|
34
|
+
const $form = $(FORM_SELECTOR);
|
|
35
|
+
if (!$form.length) return;
|
|
36
|
+
|
|
37
|
+
require(['settings', 'alerts'], function onModules(Settings, alerts) {
|
|
38
|
+
bind(Settings, alerts, $form);
|
|
23
39
|
});
|
|
24
40
|
}
|
|
25
41
|
|
|
26
|
-
$(document).ready(
|
|
27
|
-
$(window).on('action:ajaxify.end',
|
|
42
|
+
$(document).ready(boot);
|
|
43
|
+
$(window).on('action:ajaxify.end', boot);
|
|
28
44
|
})();
|
package/public/client.js
CHANGED
|
@@ -1,70 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js v36
|
|
3
|
-
*
|
|
4
|
-
* Historique des corrections majeures
|
|
5
|
-
* ────────────────────────────────────
|
|
6
|
-
* v18 Ancrage stable par data-pid / data-index au lieu d'ordinalMap fragile.
|
|
7
|
-
*
|
|
8
|
-
* v19 Intervalle global basé sur l'ordinal absolu (data-index), pas sur
|
|
9
|
-
* la position dans le batch courant.
|
|
10
|
-
*
|
|
11
|
-
* v20 Table KIND : anchorAttr / ordinalAttr / baseTag par kindClass.
|
|
12
|
-
* Fix fatal catégories : data-cid au lieu de data-index inexistant.
|
|
13
|
-
* IO fixe (une instance, jamais recréée).
|
|
14
|
-
* Fix TCF locator : MutationObserver recrée l'iframe si ajaxify la retire.
|
|
15
|
-
*
|
|
16
|
-
* v25 Fix scroll-up / virtualisation NodeBB : decluster + grace period.
|
|
17
|
-
*
|
|
18
|
-
* v26 Suppression définitive du recyclage d'id (causait réinjection en haut).
|
|
19
|
-
*
|
|
20
|
-
* v27 pruneOrphans supprimé (faux-orphelins sur virtualisation NodeBB posts).
|
|
21
|
-
*
|
|
22
|
-
* v28 decluster supprimé. Wraps persistants pendant la session.
|
|
23
|
-
*
|
|
24
|
-
* v32 Retour anchorAttr = data-index pour ezoic-ad-between.
|
|
25
|
-
* data-tid peut être absent → clés invalides → wraps empilés.
|
|
26
|
-
* pruneOrphansBetween réactivé uniquement pour topics de catégorie :
|
|
27
|
-
* – NodeBB NE virtualise PAS les topics dans une liste de catégorie,
|
|
28
|
-
* les ancres (data-index) restent en DOM → prune safe et nécessaire
|
|
29
|
-
* pour éviter l'empilement après scroll long.
|
|
30
|
-
* – Toujours désactivé pour les posts : NodeBB virtualise les posts
|
|
31
|
-
* hors-viewport → faux-orphelins → bug réinjection en haut.
|
|
32
|
-
*
|
|
33
|
-
* v34 moveDistantWrap — voir v38.
|
|
34
|
-
*
|
|
35
|
-
* v50 Suppression de bindLoginCheck() : NodeBB fait un rechargement complet
|
|
36
|
-
* après login — filter:middleware.renderHeader re-évalue l'exclusion au
|
|
37
|
-
* rechargement. Redondant depuis le fix normalizeExcludedGroups (v49).
|
|
38
|
-
*
|
|
39
|
-
* v43 Seuil de recyclage abaissé à -vh + unobserve avant recyclage.
|
|
40
|
-
*
|
|
41
|
-
* v42 Seuil -(IO_MARGIN + vh) (trop strict, peu de wraps éligibles).
|
|
42
|
-
*
|
|
43
|
-
* v41 Seuil -1vh (trop permissif sur mobile, ignorait IO_MARGIN).
|
|
44
|
-
*
|
|
45
|
-
* v40 Recyclage slots via destroyPlaceholders+define+displayMore avec délais.
|
|
46
|
-
* Séquence : destroy → 300ms → define → 300ms → displayMore.
|
|
47
|
-
* Testé manuellement : fonctionne. displayMore = API Ezoic infinite scroll.
|
|
48
|
-
*
|
|
49
|
-
* v38 Pool épuisé = fin de quota Ezoic par page-view. ez.refresh() interdit
|
|
50
|
-
* sur la même page que ez.enable() — supprimé. moveDistantWrap supprimé :
|
|
51
|
-
* déplacer un wrap "already defined" ne re-sert aucune pub. Pool épuisé →
|
|
52
|
-
* break propre dans injectBetween. muteConsole : ajout warnings refresh.
|
|
53
|
-
*
|
|
54
|
-
* v36 Optimisations chemin critique (scroll → injectBetween) :
|
|
55
|
-
* – S.wrapByKey Map<anchorKey,wrap> : findWrap() passe de querySelector
|
|
56
|
-
* sur tout le doc à un lookup O(1). Mis à jour dans insertAfter,
|
|
57
|
-
* dropWrap et cleanup.
|
|
58
|
-
* – wrapIsLive allégé : pour les voisins immédiats on vérifie les
|
|
59
|
-
* attributs du nœud lui-même sans querySelector global.
|
|
60
|
-
* – MutationObserver : matches() vérifié avant querySelector() pour
|
|
61
|
-
* court-circuiter les sous-arbres entiers ajoutés par NodeBB.
|
|
62
|
-
*
|
|
63
|
-
* v35 Revue complète prod-ready :
|
|
64
|
-
* – initPools protégé contre ré-initialisation inutile (S.poolsReady).
|
|
65
|
-
* – muteConsole élargit à "No valid placeholders for loadMore".
|
|
66
|
-
* – Commentaires et historique nettoyés.
|
|
67
|
-
*/
|
|
68
1
|
(function nbbEzoicInfinite() {
|
|
69
2
|
'use strict';
|
|
70
3
|
|
|
@@ -99,6 +32,9 @@
|
|
|
99
32
|
topic: 'li[component="category/topic"]',
|
|
100
33
|
category: 'li[component="categories/category"]',
|
|
101
34
|
};
|
|
35
|
+
const WRAP_SEL = `.${WRAP_CLASS}`;
|
|
36
|
+
const FILL_SEL = 'iframe, ins, img, video, [data-google-container-id]';
|
|
37
|
+
const CONTENT_SEL_LIST = [SEL.post, SEL.topic, SEL.category];
|
|
102
38
|
|
|
103
39
|
/**
|
|
104
40
|
* Table KIND — source de vérité par kindClass.
|
|
@@ -157,7 +93,7 @@
|
|
|
157
93
|
const isBlocked = () => ts() < blockedUntil;
|
|
158
94
|
const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
|
|
159
95
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
160
|
-
const isFilled = n => !!(n?.querySelector?.(
|
|
96
|
+
const isFilled = n => !!(n?.querySelector?.(FILL_SEL));
|
|
161
97
|
|
|
162
98
|
function healFalseEmpty(root = document) {
|
|
163
99
|
try {
|
|
@@ -196,7 +132,7 @@
|
|
|
196
132
|
requestAnimationFrame(() => {
|
|
197
133
|
S.sweepQueued = false;
|
|
198
134
|
sweepDeadWraps();
|
|
199
|
-
|
|
135
|
+
healFalseEmpty();
|
|
200
136
|
});
|
|
201
137
|
}
|
|
202
138
|
|
|
@@ -273,7 +209,6 @@ function destroyBeforeReuse(ids) {
|
|
|
273
209
|
return out;
|
|
274
210
|
}
|
|
275
211
|
|
|
276
|
-
|
|
277
212
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
278
213
|
|
|
279
214
|
async function fetchConfig() {
|
|
@@ -430,8 +365,10 @@ function destroyBeforeReuse(ids) {
|
|
|
430
365
|
function sweepDeadWraps() {
|
|
431
366
|
// NodeBB peut retirer des nœuds sans passer par dropWrap() (virtualisation / rerender).
|
|
432
367
|
// On libère alors les IDs/keys fantômes pour éviter l'épuisement du pool.
|
|
433
|
-
|
|
368
|
+
let changed = false;
|
|
369
|
+
for (const [key, wrap] of S.wrapByKey) {
|
|
434
370
|
if (wrap?.isConnected) continue;
|
|
371
|
+
changed = true;
|
|
435
372
|
S.wrapByKey.delete(key);
|
|
436
373
|
const id = parseInt(wrap?.getAttribute?.(A_WRAPID), 10);
|
|
437
374
|
if (Number.isFinite(id)) {
|
|
@@ -441,7 +378,7 @@ function destroyBeforeReuse(ids) {
|
|
|
441
378
|
S.ezActiveIds.delete(id);
|
|
442
379
|
}
|
|
443
380
|
}
|
|
444
|
-
if (S.pending.length) {
|
|
381
|
+
if (changed && S.pending.length) {
|
|
445
382
|
S.pending = S.pending.filter(id => S.pendingSet.has(id));
|
|
446
383
|
}
|
|
447
384
|
}
|
|
@@ -531,7 +468,6 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
531
468
|
return { id, wrap: best };
|
|
532
469
|
}
|
|
533
470
|
|
|
534
|
-
|
|
535
471
|
// ── Wraps DOM — création / suppression ────────────────────────────────────
|
|
536
472
|
|
|
537
473
|
function makeWrap(id, klass, key) {
|
|
@@ -592,7 +528,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
592
528
|
const klass = 'ezoic-ad-between';
|
|
593
529
|
const cfg = KIND[klass];
|
|
594
530
|
|
|
595
|
-
document.querySelectorAll(
|
|
531
|
+
document.querySelectorAll(`${WRAP_SEL}.${klass}`).forEach(w => {
|
|
596
532
|
const created = parseInt(w.getAttribute(A_CREATED) || '0', 10);
|
|
597
533
|
if (ts() - created < MIN_PRUNE_AGE_MS) return; // grâce post-création
|
|
598
534
|
|
|
@@ -751,7 +687,7 @@ function startShowBatch(ids) {
|
|
|
751
687
|
if (!canShowPlaceholderId(id, t)) continue;
|
|
752
688
|
|
|
753
689
|
S.lastShow.set(id, t);
|
|
754
|
-
try { ph.closest?.(
|
|
690
|
+
try { ph.closest?.(WRAP_SEL)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
755
691
|
valid.push(id);
|
|
756
692
|
}
|
|
757
693
|
|
|
@@ -774,7 +710,6 @@ function startShowBatch(ids) {
|
|
|
774
710
|
});
|
|
775
711
|
}
|
|
776
712
|
|
|
777
|
-
|
|
778
713
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
779
714
|
//
|
|
780
715
|
// Intercepte ez.showAds() pour :
|
|
@@ -899,7 +834,7 @@ function startShowBatch(ids) {
|
|
|
899
834
|
|
|
900
835
|
function cleanup() {
|
|
901
836
|
blockedUntil = ts() + 1500;
|
|
902
|
-
mutate(() => document.querySelectorAll(
|
|
837
|
+
mutate(() => document.querySelectorAll(WRAP_SEL).forEach(dropWrap));
|
|
903
838
|
S.cfg = null;
|
|
904
839
|
S.poolsReady = false;
|
|
905
840
|
S.pools = { topics: [], posts: [], categories: [] };
|
|
@@ -924,18 +859,33 @@ function startShowBatch(ids) {
|
|
|
924
859
|
S.lastScrollTs = 0;
|
|
925
860
|
}
|
|
926
861
|
|
|
862
|
+
function nodeMatchesAny(node, selectors) {
|
|
863
|
+
if (!(node instanceof Element)) return false;
|
|
864
|
+
for (const sel of selectors) {
|
|
865
|
+
try { if (node.matches(sel)) return true; } catch (_) {}
|
|
866
|
+
}
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function nodeContainsAny(node, selectors) {
|
|
871
|
+
if (!(node instanceof Element)) return false;
|
|
872
|
+
for (const sel of selectors) {
|
|
873
|
+
try { if (node.querySelector(sel)) return true; } catch (_) {}
|
|
874
|
+
}
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
|
|
927
878
|
// ── MutationObserver ───────────────────────────────────────────────────────
|
|
928
879
|
|
|
929
880
|
function ensureDomObserver() {
|
|
930
881
|
if (S.domObs) return;
|
|
931
|
-
const allSel = [SEL.post, SEL.topic, SEL.category];
|
|
932
882
|
S.domObs = new MutationObserver(muts => {
|
|
933
883
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
934
884
|
for (const m of muts) {
|
|
935
885
|
let sawWrapRemoval = false;
|
|
936
886
|
for (const n of m.removedNodes) {
|
|
937
887
|
if (n.nodeType !== 1) continue;
|
|
938
|
-
if ((n
|
|
888
|
+
if (nodeMatchesAny(n, [WRAP_SEL]) || nodeContainsAny(n, [WRAP_SEL])) {
|
|
939
889
|
sawWrapRemoval = true;
|
|
940
890
|
}
|
|
941
891
|
}
|
|
@@ -944,8 +894,7 @@ function startShowBatch(ids) {
|
|
|
944
894
|
if (n.nodeType !== 1) continue;
|
|
945
895
|
try { healFalseEmpty(n); } catch (_) {}
|
|
946
896
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
947
|
-
if (
|
|
948
|
-
allSel.some(s => { try { return !!n.querySelector(s); } catch(_){return false;} })) {
|
|
897
|
+
if (nodeMatchesAny(n, CONTENT_SEL_LIST) || nodeContainsAny(n, CONTENT_SEL_LIST)) {
|
|
949
898
|
requestBurst(); return;
|
|
950
899
|
}
|
|
951
900
|
}
|
|
@@ -956,27 +905,6 @@ function startShowBatch(ids) {
|
|
|
956
905
|
|
|
957
906
|
// ── Utilitaires ────────────────────────────────────────────────────────────
|
|
958
907
|
|
|
959
|
-
function muteConsole() {
|
|
960
|
-
if (window.__nbbEzMuted) return;
|
|
961
|
-
window.__nbbEzMuted = true;
|
|
962
|
-
const MUTED = [
|
|
963
|
-
'[EzoicAds JS]: Placeholder Id',
|
|
964
|
-
'No valid placeholders for loadMore',
|
|
965
|
-
'cannot call refresh on the same page',
|
|
966
|
-
'no placeholders are currently defined in Refresh',
|
|
967
|
-
'Debugger iframe already exists',
|
|
968
|
-
`with id ${PH_PREFIX}`,
|
|
969
|
-
];
|
|
970
|
-
for (const m of ['log', 'info', 'warn', 'error']) {
|
|
971
|
-
const orig = console[m];
|
|
972
|
-
if (typeof orig !== 'function') continue;
|
|
973
|
-
console[m] = function (...a) {
|
|
974
|
-
if (typeof a[0] === 'string' && MUTED.some(p => a[0].includes(p))) return;
|
|
975
|
-
orig.apply(console, a);
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
908
|
function ensureTcfLocator() {
|
|
981
909
|
try {
|
|
982
910
|
if (!window.__tcfapi && !window.__cmp) return;
|
|
@@ -998,6 +926,7 @@ function startShowBatch(ids) {
|
|
|
998
926
|
function warmNetwork() {
|
|
999
927
|
const head = document.head;
|
|
1000
928
|
if (!head) return;
|
|
929
|
+
const frag = document.createDocumentFragment();
|
|
1001
930
|
for (const [rel, href, cors] of [
|
|
1002
931
|
['preconnect', 'https://g.ezoic.net', true ],
|
|
1003
932
|
['preconnect', 'https://go.ezoic.net', true ],
|
|
@@ -1012,8 +941,9 @@ function startShowBatch(ids) {
|
|
|
1012
941
|
const l = document.createElement('link');
|
|
1013
942
|
l.rel = rel; l.href = href;
|
|
1014
943
|
if (cors) l.crossOrigin = 'anonymous';
|
|
1015
|
-
|
|
944
|
+
frag.appendChild(l);
|
|
1016
945
|
}
|
|
946
|
+
head.appendChild(frag);
|
|
1017
947
|
}
|
|
1018
948
|
|
|
1019
949
|
// ── Bindings ───────────────────────────────────────────────────────────────
|
|
@@ -1027,7 +957,7 @@ function startShowBatch(ids) {
|
|
|
1027
957
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
1028
958
|
S.pageKey = pageKey();
|
|
1029
959
|
blockedUntil = 0;
|
|
1030
|
-
|
|
960
|
+
ensureTcfLocator(); warmNetwork();
|
|
1031
961
|
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
1032
962
|
});
|
|
1033
963
|
|
|
@@ -1075,7 +1005,6 @@ function startShowBatch(ids) {
|
|
|
1075
1005
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
1076
1006
|
|
|
1077
1007
|
S.pageKey = pageKey();
|
|
1078
|
-
muteConsole();
|
|
1079
1008
|
ensureTcfLocator();
|
|
1080
1009
|
warmNetwork();
|
|
1081
1010
|
patchShowAds();
|