nodebb-plugin-ezoic-infinite 1.0.5 → 1.0.6
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 +77 -36
- package/package.json +5 -2
- package/plugin.json +4 -8
- package/public/admin.js +22 -52
- package/public/client.js +123 -135
- package/public/templates/admin/plugins/ezoic-infinite.tpl +46 -65
- package/README.md +0 -5
package/library.js
CHANGED
|
@@ -2,58 +2,99 @@
|
|
|
2
2
|
|
|
3
3
|
const meta = require.main.require('./src/meta');
|
|
4
4
|
const groups = require.main.require('./src/groups');
|
|
5
|
+
const db = require.main.require('./src/database');
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const SETTINGS_KEY = 'ezoic-infinite';
|
|
8
|
+
const plugin = {};
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
}
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
}
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
groupNames = await groups.getGroupsFromSet(set, 0, -1);
|
|
21
|
-
if (Array.isArray(groupNames) && groupNames.length) break;
|
|
22
|
-
} catch (e) {}
|
|
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);
|
|
23
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)
|
|
38
|
+
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
39
|
+
placeholderIds: (s.placeholderIds || '').trim(),
|
|
40
|
+
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
24
41
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
groupList = (groupNames || []).map((name) => ({ name }));
|
|
30
|
-
}
|
|
42
|
+
// "Ad message" between replies (looks like a post)
|
|
43
|
+
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
44
|
+
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
45
|
+
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
35
50
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
51
|
+
async function isUserExcluded(uid, excludedGroups) {
|
|
52
|
+
if (!uid || !excludedGroups.length) return false;
|
|
53
|
+
const userGroups = await groups.getUserGroups([uid]);
|
|
54
|
+
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
41
55
|
}
|
|
42
56
|
|
|
43
|
-
|
|
57
|
+
plugin.addAdminNavigation = async (header) => {
|
|
44
58
|
header.plugins = header.plugins || [];
|
|
45
59
|
header.plugins.push({
|
|
46
60
|
route: '/plugins/ezoic-infinite',
|
|
47
|
-
icon: 'fa-
|
|
48
|
-
name: 'Ezoic Infinite'
|
|
61
|
+
icon: 'fa-ad',
|
|
62
|
+
name: 'Ezoic Infinite Ads'
|
|
49
63
|
});
|
|
50
64
|
return header;
|
|
51
65
|
};
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
plugin.init = async ({ router, middleware }) => {
|
|
68
|
+
async function render(req, res) {
|
|
69
|
+
const settings = await getSettings();
|
|
70
|
+
const allGroups = await getAllGroups();
|
|
71
|
+
|
|
72
|
+
res.render('admin/plugins/ezoic-infinite', {
|
|
73
|
+
title: 'Ezoic Infinite Ads',
|
|
74
|
+
...settings,
|
|
75
|
+
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
76
|
+
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
77
|
+
allGroups,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
82
|
+
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
83
|
+
|
|
84
|
+
router.get('/api/plugins/ezoic-infinite/config', middleware.buildHeader, async (req, res) => {
|
|
85
|
+
const settings = await getSettings();
|
|
86
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
87
|
+
|
|
88
|
+
res.json({
|
|
89
|
+
excluded,
|
|
90
|
+
enableBetweenAds: settings.enableBetweenAds,
|
|
91
|
+
placeholderIds: settings.placeholderIds,
|
|
92
|
+
intervalPosts: settings.intervalPosts,
|
|
93
|
+
enableMessageAds: settings.enableMessageAds,
|
|
94
|
+
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
95
|
+
messageIntervalPosts: settings.messageIntervalPosts,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
57
98
|
};
|
|
58
99
|
|
|
59
|
-
module.exports =
|
|
100
|
+
module.exports = plugin;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-ezoic-infinite",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Ezoic ads
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "Ezoic ads with infinite scroll using a pool of placeholder IDs",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"ads",
|
|
12
12
|
"infinite-scroll"
|
|
13
13
|
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
14
17
|
"nbbpm": {
|
|
15
18
|
"compatibility": "^4.0.0"
|
|
16
19
|
}
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "nodebb-plugin-ezoic-infinite",
|
|
3
|
-
"name": "Ezoic Infinite",
|
|
4
|
-
"description": "Ezoic ads
|
|
3
|
+
"name": "NodeBB Ezoic Infinite Ads",
|
|
4
|
+
"description": "Ezoic ads with infinite scroll using a pool of placeholder IDs",
|
|
5
5
|
"library": "./library.js",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
{
|
|
12
12
|
"hook": "filter:admin.header.build",
|
|
13
13
|
"method": "addAdminNavigation"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"hook": "filter:config.get",
|
|
17
|
-
"method": "addConfig"
|
|
18
14
|
}
|
|
19
15
|
],
|
|
20
16
|
"staticDirs": {
|
|
@@ -26,8 +22,8 @@
|
|
|
26
22
|
"scripts": [
|
|
27
23
|
"public/client.js"
|
|
28
24
|
],
|
|
25
|
+
"templates": "public/templates",
|
|
29
26
|
"css": [
|
|
30
27
|
"public/style.css"
|
|
31
|
-
]
|
|
32
|
-
"templates": "public/templates"
|
|
28
|
+
]
|
|
33
29
|
}
|
package/public/admin.js
CHANGED
|
@@ -1,59 +1,29 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* globals ajaxify */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
(function () {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
form.find('[name="enableMessageAds"]').prop('checked', data.enableMessageAds === true || data.enableMessageAds === 'on');
|
|
23
|
-
form.find('[name="messageIntervalPosts"]').val(parseInt(data.messageIntervalPosts, 10) || 3);
|
|
24
|
-
form.find('[name="messagePlaceholderIds"]').val(data.messagePlaceholderIds || '');
|
|
25
|
-
|
|
26
|
-
const selected = (data.excludedGroups || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
27
|
-
form.find('[name="excludedGroups"] option').each(function () {
|
|
28
|
-
$(this).prop('selected', selected.includes($(this).val()));
|
|
5
|
+
function init() {
|
|
6
|
+
const $form = $('.ezoic-infinite-settings');
|
|
7
|
+
if (!$form.length) return;
|
|
8
|
+
|
|
9
|
+
require(['settings', 'alerts'], function (Settings, alerts) {
|
|
10
|
+
Settings.load('ezoic-infinite', $form);
|
|
11
|
+
|
|
12
|
+
$('#save').off('click.ezoicInfinite').on('click.ezoicInfinite', function (e) {
|
|
13
|
+
e.preventDefault();
|
|
14
|
+
|
|
15
|
+
Settings.save('ezoic-infinite', $form, function () {
|
|
16
|
+
// Toast vert (NodeBB core)
|
|
17
|
+
if (alerts && typeof alerts.success === 'function') {
|
|
18
|
+
alerts.success('Enregistré');
|
|
19
|
+
} else if (window.app && typeof window.app.alertSuccess === 'function') {
|
|
20
|
+
window.app.alertSuccess('Enregistré');
|
|
21
|
+
}
|
|
29
22
|
});
|
|
30
23
|
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function save() {
|
|
34
|
-
const form = $('.ezoic-infinite-settings');
|
|
35
|
-
const payload = {
|
|
36
|
-
enableBetweenAds: form.find('[name="enableBetweenAds"]').is(':checked'),
|
|
37
|
-
intervalTopics: parseInt(form.find('[name="intervalTopics"]').val(), 10) || 6,
|
|
38
|
-
placeholderIds: form.find('[name="placeholderIds"]').val() || '',
|
|
39
|
-
|
|
40
|
-
enableMessageAds: form.find('[name="enableMessageAds"]').is(':checked'),
|
|
41
|
-
messageIntervalPosts: parseInt(form.find('[name="messageIntervalPosts"]').val(), 10) || 3,
|
|
42
|
-
messagePlaceholderIds: form.find('[name="messagePlaceholderIds"]').val() || '',
|
|
43
|
-
|
|
44
|
-
excludedGroups: (form.find('[name="excludedGroups"]').val() || []).join(','),
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
socket.emit('admin.settings.set', { hash: namespace, values: payload }, function (err) {
|
|
48
|
-
if (err) {
|
|
49
|
-
app.alertError(err.message || err);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
app.alertSuccess('Paramètres enregistrés');
|
|
53
|
-
});
|
|
54
|
-
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
55
26
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
27
|
+
$(document).ready(init);
|
|
28
|
+
$(window).on('action:ajaxify.end', init);
|
|
59
29
|
})();
|
package/public/client.js
CHANGED
|
@@ -1,45 +1,26 @@
|
|
|
1
|
-
/* global $, ajaxify, app */
|
|
2
1
|
'use strict';
|
|
3
2
|
|
|
3
|
+
/* globals ajaxify */
|
|
4
4
|
(function () {
|
|
5
5
|
if (window.ezoicInfiniteLoaded) return;
|
|
6
6
|
window.ezoicInfiniteLoaded = true;
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
let cachedConfig;
|
|
9
|
+
let lastFetch = 0;
|
|
10
|
+
let debounceTimer;
|
|
11
|
+
|
|
12
|
+
let inFlight = false;
|
|
13
|
+
let rerunRequested = false;
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
// per page state
|
|
13
16
|
let pageKey = null;
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
let
|
|
18
|
+
// separate pools/state
|
|
19
|
+
let usedBetween = new Set(); // between topics list
|
|
20
|
+
let usedMessage = new Set(); // between replies
|
|
17
21
|
let fifoBetween = [];
|
|
18
22
|
let fifoMessage = [];
|
|
19
23
|
|
|
20
|
-
let refreshInFlight = false;
|
|
21
|
-
let refreshQueued = false;
|
|
22
|
-
|
|
23
|
-
function parsePool(text) {
|
|
24
|
-
return String(text || '')
|
|
25
|
-
.split(/\r?\n/)
|
|
26
|
-
.map(s => s.trim())
|
|
27
|
-
.filter(Boolean)
|
|
28
|
-
.map(s => parseInt(s, 10))
|
|
29
|
-
.filter(n => Number.isFinite(n) && n > 0);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function userExcluded() {
|
|
33
|
-
try {
|
|
34
|
-
const raw = (settings && settings.excludedGroups) ? String(settings.excludedGroups) : '';
|
|
35
|
-
if (!raw) return false;
|
|
36
|
-
const excluded = raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
37
|
-
if (!excluded.length) return false;
|
|
38
|
-
const myGroups = (app.user && app.user.groups) ? app.user.groups : [];
|
|
39
|
-
return excluded.some(g => myGroups.includes(g));
|
|
40
|
-
} catch (e) { return false; }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
24
|
function getPageKey() {
|
|
44
25
|
try {
|
|
45
26
|
if (ajaxify && ajaxify.data) {
|
|
@@ -56,11 +37,31 @@
|
|
|
56
37
|
}
|
|
57
38
|
|
|
58
39
|
function isCategoryTopicList() {
|
|
59
|
-
return
|
|
40
|
+
return document.querySelectorAll('li[component="category/topic"]').length > 0 && !isTopicPage();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parsePool(raw) {
|
|
44
|
+
if (!raw) return [];
|
|
45
|
+
// accept newline, comma, space, semicolon
|
|
46
|
+
const arr = String(raw).split(/[\n,;\s]+/)
|
|
47
|
+
.map(x => parseInt(x, 10))
|
|
48
|
+
.filter(n => Number.isFinite(n) && n > 0);
|
|
49
|
+
// unique while preserving order
|
|
50
|
+
return Array.from(new Set(arr));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function fetchConfig() {
|
|
54
|
+
if (cachedConfig && Date.now() - lastFetch < 10000) return cachedConfig;
|
|
55
|
+
const base = (window.config && window.config.relative_path) ? window.config.relative_path : '';
|
|
56
|
+
const res = await fetch(base + '/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
57
|
+
const json = await res.json();
|
|
58
|
+
cachedConfig = json;
|
|
59
|
+
lastFetch = Date.now();
|
|
60
|
+
return json;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
function cleanupForNewPage() {
|
|
63
|
-
|
|
64
|
+
document.querySelectorAll('.ezoic-ad').forEach(el => el.remove());
|
|
64
65
|
usedBetween = new Set();
|
|
65
66
|
usedMessage = new Set();
|
|
66
67
|
fifoBetween = [];
|
|
@@ -85,15 +86,12 @@
|
|
|
85
86
|
const existing = document.getElementById('ezoic-pub-ad-placeholder-' + id);
|
|
86
87
|
if (!existing) return;
|
|
87
88
|
const wrap = existing.closest('.ezoic-ad');
|
|
88
|
-
if (wrap)
|
|
89
|
-
|
|
90
|
-
} else {
|
|
91
|
-
existing.remove();
|
|
92
|
-
}
|
|
89
|
+
if (wrap) wrap.remove();
|
|
90
|
+
else existing.remove();
|
|
93
91
|
destroyPlaceholder(id);
|
|
94
92
|
}
|
|
95
93
|
|
|
96
|
-
function
|
|
94
|
+
function callShowAdsSingle(id) {
|
|
97
95
|
if (!id) return;
|
|
98
96
|
|
|
99
97
|
const now = Date.now();
|
|
@@ -102,30 +100,27 @@
|
|
|
102
100
|
if (now - last < 1200) return;
|
|
103
101
|
window.__ezoicLastSingle[id] = now;
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (run() || tries >= 8) return;
|
|
125
|
-
setTimeout(tick, 800);
|
|
126
|
-
};
|
|
103
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
104
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
105
|
+
|
|
106
|
+
const run = function () {
|
|
107
|
+
try {
|
|
108
|
+
if (typeof window.ezstandalone.showAds === 'function') {
|
|
109
|
+
window.ezstandalone.showAds(id);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {}
|
|
113
|
+
return false;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
window.ezstandalone.cmd.push(function () { run(); });
|
|
117
|
+
|
|
118
|
+
let tries = 0;
|
|
119
|
+
(function tick() {
|
|
120
|
+
tries++;
|
|
121
|
+
if (run() || tries >= 8) return;
|
|
127
122
|
setTimeout(tick, 800);
|
|
128
|
-
}
|
|
123
|
+
})();
|
|
129
124
|
}
|
|
130
125
|
|
|
131
126
|
function pickNextId(pool, usedSet) {
|
|
@@ -133,14 +128,13 @@
|
|
|
133
128
|
return null;
|
|
134
129
|
}
|
|
135
130
|
|
|
136
|
-
function recycle(fifo, usedSet,
|
|
131
|
+
function recycle(fifo, usedSet, selector) {
|
|
137
132
|
fifo.sort((a, b) => a.after - b.after);
|
|
138
133
|
while (fifo.length) {
|
|
139
134
|
const old = fifo.shift();
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
$el.remove();
|
|
135
|
+
const el = document.querySelector(selector(old));
|
|
136
|
+
if (!el) continue;
|
|
137
|
+
el.remove();
|
|
144
138
|
usedSet.delete(old.id);
|
|
145
139
|
destroyPlaceholder(old.id);
|
|
146
140
|
return old.id;
|
|
@@ -148,93 +142,81 @@
|
|
|
148
142
|
return null;
|
|
149
143
|
}
|
|
150
144
|
|
|
151
|
-
function insertAfter(
|
|
145
|
+
function insertAfter(targetEl, id, cls, afterVal) {
|
|
152
146
|
ensureUniquePlaceholder(id);
|
|
153
|
-
const wrap =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
147
|
+
const wrap = document.createElement('div');
|
|
148
|
+
wrap.className = 'ezoic-ad ' + cls;
|
|
149
|
+
wrap.setAttribute('data-ezoic-id', String(id));
|
|
150
|
+
wrap.setAttribute('data-ezoic-after', String(afterVal));
|
|
151
|
+
wrap.innerHTML = '<div class="ezoic-ad-inner"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
|
|
152
|
+
targetEl.insertAdjacentElement('afterend', wrap);
|
|
159
153
|
}
|
|
160
154
|
|
|
161
|
-
function injectBetweenTopics() {
|
|
162
|
-
if (!
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
const pool = parsePool(settings.placeholderIds);
|
|
155
|
+
function injectBetweenTopics(cfg) {
|
|
156
|
+
if (!cfg.enableBetweenAds) return;
|
|
157
|
+
const interval = Math.max(1, parseInt(cfg.intervalPosts, 10) || 6);
|
|
158
|
+
const pool = parsePool(cfg.placeholderIds);
|
|
166
159
|
if (!pool.length) return;
|
|
167
160
|
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
161
|
+
const items = Array.from(document.querySelectorAll('li[component="category/topic"]'));
|
|
162
|
+
if (!items.length) return;
|
|
170
163
|
|
|
171
|
-
|
|
164
|
+
items.forEach((li, idx) => {
|
|
172
165
|
const pos = idx + 1;
|
|
173
166
|
if (pos % interval !== 0) return;
|
|
174
|
-
if (idx ===
|
|
167
|
+
if (idx === items.length - 1) return;
|
|
175
168
|
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
169
|
+
const next = li.nextElementSibling;
|
|
170
|
+
if (next && next.classList && next.classList.contains('ezoic-ad-between')) return;
|
|
178
171
|
|
|
179
172
|
let id = pickNextId(pool, usedBetween);
|
|
180
173
|
if (!id) {
|
|
181
|
-
id = recycle(
|
|
182
|
-
|
|
183
|
-
(old) => $('.ezoic-ad-between[data-ezoic-id="' + old.id + '"][data-ezoic-after="' + old.after + '"]')
|
|
184
|
-
);
|
|
185
|
-
if (!id) return;
|
|
174
|
+
id = recycle(fifoBetween, usedBetween, (old) => '.ezoic-ad-between[data-ezoic-id="' + old.id + '"][data-ezoic-after="' + old.after + '"]');
|
|
175
|
+
if (!id) return; // pool empty and nothing recyclable => stop
|
|
186
176
|
}
|
|
187
177
|
|
|
188
178
|
usedBetween.add(id);
|
|
189
179
|
fifoBetween.push({ id, after: pos });
|
|
190
180
|
|
|
191
|
-
insertAfter(
|
|
192
|
-
|
|
181
|
+
insertAfter(li, id, 'ezoic-ad-between', pos);
|
|
182
|
+
callShowAdsSingle(id);
|
|
193
183
|
});
|
|
194
184
|
}
|
|
195
185
|
|
|
196
|
-
function injectBetweenMessages() {
|
|
197
|
-
if (!
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
const pool = parsePool(settings.messagePlaceholderIds);
|
|
186
|
+
function injectBetweenMessages(cfg) {
|
|
187
|
+
if (!cfg.enableMessageAds) return;
|
|
188
|
+
const interval = Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3);
|
|
189
|
+
const pool = parsePool(cfg.messagePlaceholderIds);
|
|
201
190
|
if (!pool.length) return;
|
|
202
191
|
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
192
|
+
const posts = Array.from(document.querySelectorAll('[component="post"][data-pid]'));
|
|
193
|
+
if (!posts.length) return;
|
|
205
194
|
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
209
|
-
if (idx ===
|
|
195
|
+
posts.forEach((post, idx) => {
|
|
196
|
+
const no = idx + 1;
|
|
197
|
+
if (no % interval !== 0) return;
|
|
198
|
+
if (idx === posts.length - 1) return;
|
|
210
199
|
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
200
|
+
const next = post.nextElementSibling;
|
|
201
|
+
if (next && next.classList && next.classList.contains('ezoic-ad-message')) return;
|
|
213
202
|
|
|
214
203
|
let id = pickNextId(pool, usedMessage);
|
|
215
204
|
if (!id) {
|
|
216
|
-
id = recycle(
|
|
217
|
-
fifoMessage, usedMessage,
|
|
218
|
-
(old) => $('.ezoic-ad-message[data-ezoic-id="' + old.id + '"][data-ezoic-after="' + old.after + '"]')
|
|
219
|
-
);
|
|
205
|
+
id = recycle(fifoMessage, usedMessage, (old) => '.ezoic-ad-message[data-ezoic-id="' + old.id + '"][data-ezoic-after="' + old.after + '"]');
|
|
220
206
|
if (!id) return;
|
|
221
207
|
}
|
|
222
208
|
|
|
223
209
|
usedMessage.add(id);
|
|
224
|
-
fifoMessage.push({ id, after:
|
|
210
|
+
fifoMessage.push({ id, after: no });
|
|
225
211
|
|
|
226
|
-
insertAfter(
|
|
227
|
-
|
|
212
|
+
insertAfter(post, id, 'ezoic-ad-message', no);
|
|
213
|
+
callShowAdsSingle(id);
|
|
228
214
|
});
|
|
229
215
|
}
|
|
230
216
|
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (userExcluded()) return;
|
|
235
|
-
|
|
236
|
-
if (refreshInFlight) { refreshQueued = true; return; }
|
|
237
|
-
refreshInFlight = true;
|
|
217
|
+
async function run() {
|
|
218
|
+
if (inFlight) { rerunRequested = true; return; }
|
|
219
|
+
inFlight = true;
|
|
238
220
|
|
|
239
221
|
try {
|
|
240
222
|
const key = getPageKey();
|
|
@@ -243,29 +225,35 @@
|
|
|
243
225
|
cleanupForNewPage();
|
|
244
226
|
}
|
|
245
227
|
|
|
246
|
-
|
|
247
|
-
|
|
228
|
+
const cfg = await fetchConfig();
|
|
229
|
+
if (!cfg || cfg.excluded) return;
|
|
230
|
+
|
|
231
|
+
if (isTopicPage()) injectBetweenMessages(cfg);
|
|
232
|
+
else if (isCategoryTopicList()) injectBetweenTopics(cfg);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
// silent
|
|
248
235
|
} finally {
|
|
249
|
-
|
|
250
|
-
if (
|
|
236
|
+
inFlight = false;
|
|
237
|
+
if (rerunRequested) {
|
|
238
|
+
rerunRequested = false;
|
|
239
|
+
setTimeout(run, 50);
|
|
240
|
+
}
|
|
251
241
|
}
|
|
252
242
|
}
|
|
253
243
|
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
setTimeout(
|
|
244
|
+
function scheduleRun() {
|
|
245
|
+
clearTimeout(debounceTimer);
|
|
246
|
+
debounceTimer = setTimeout(run, 150);
|
|
257
247
|
}
|
|
258
248
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
$(window).on('action:posts.loaded action:topics.loaded action:topic.loaded action:category.loaded', function () {
|
|
263
|
-
refresh();
|
|
264
|
-
setTimeout(refresh, 600);
|
|
249
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
250
|
+
run();
|
|
251
|
+
setTimeout(run, 1200);
|
|
265
252
|
});
|
|
266
253
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
254
|
+
window.addEventListener('action:ajaxify.end', scheduleRun);
|
|
255
|
+
window.addEventListener('action:posts.loaded', scheduleRun);
|
|
256
|
+
window.addEventListener('action:topic.loaded', scheduleRun);
|
|
257
|
+
window.addEventListener('action:topics.loaded', scheduleRun);
|
|
258
|
+
window.addEventListener('action:category.loaded', scheduleRun);
|
|
271
259
|
})();
|
|
@@ -1,78 +1,59 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<
|
|
3
|
-
<div class="card">
|
|
4
|
-
<div class="card-header">
|
|
5
|
-
<strong>Ezoic Infinite</strong>
|
|
6
|
-
</div>
|
|
7
|
-
<div class="card-body">
|
|
8
|
-
<form class="ezoic-infinite-settings">
|
|
9
|
-
<div class="mb-3">
|
|
10
|
-
<label class="form-label">Groupes exclus</label>
|
|
11
|
-
<select multiple class="form-control" name="excludedGroups" size="10">
|
|
12
|
-
<!-- BEGIN groups -->
|
|
13
|
-
<option value="{groups.name}">{groups.name}</option>
|
|
14
|
-
<!-- END groups -->
|
|
15
|
-
</select>
|
|
16
|
-
<p class="form-text">Aucune publicité ne sera affichée pour ces groupes.</p>
|
|
17
|
-
</div>
|
|
1
|
+
<div class="acp-page-container">
|
|
2
|
+
<h2>Ezoic - Publicités Infinite Scroll Ads</h2>
|
|
18
3
|
|
|
19
|
-
|
|
4
|
+
<form class="ezoic-infinite-settings" role="form">
|
|
5
|
+
<h4 class="mt-3">Pubs entre les posts (bloc simple)</h4>
|
|
20
6
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<label class="form-check-label" for="enableBetweenAds">Activer</label>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<div class="mb-3">
|
|
29
|
-
<label class="form-label">Intervalle (après chaque N topics)</label>
|
|
30
|
-
<input type="number" class="form-control" name="intervalTopics" min="1" step="1">
|
|
31
|
-
</div>
|
|
7
|
+
<div class="form-check mb-3">
|
|
8
|
+
<input class="form-check-input" type="checkbox" id="enableBetweenAds" name="enableBetweenAds" {enableBetweenAds_checked}>
|
|
9
|
+
<label class="form-check-label" for="enableBetweenAds">Activer les pubs entre les posts</label>
|
|
10
|
+
</div>
|
|
32
11
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
12
|
+
<div class="mb-3">
|
|
13
|
+
<label class="form-label" for="placeholderIds">Pool d’IDs Ezoic (entre posts)</label>
|
|
14
|
+
<textarea id="placeholderIds" name="placeholderIds" class="form-control" rows="4">{placeholderIds}</textarea>
|
|
15
|
+
<p class="form-text">Un ID par ligne (ou séparé par virgules/espaces). Le nombre d’IDs = nombre max de pubs simultanées.</p>
|
|
16
|
+
</div>
|
|
37
17
|
|
|
38
|
-
|
|
18
|
+
<div class="mb-3">
|
|
19
|
+
<label class="form-label" for="intervalPosts">Afficher une pub tous les N posts</label>
|
|
20
|
+
<input type="number" id="intervalPosts" name="intervalPosts" class="form-control" value="{intervalPosts}" min="1">
|
|
21
|
+
</div>
|
|
39
22
|
|
|
40
|
-
|
|
23
|
+
<hr/>
|
|
41
24
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<label class="form-check-label" for="enableMessageAds">Activer</label>
|
|
45
|
-
</div>
|
|
25
|
+
<h4 class="mt-3">Pubs “message” entre les réponses</h4>
|
|
26
|
+
<p class="form-text">Insère un bloc qui ressemble à un post, toutes les N réponses (dans une page topic).</p>
|
|
46
27
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
28
|
+
<div class="form-check mb-3">
|
|
29
|
+
<input class="form-check-input" type="checkbox" id="enableMessageAds" name="enableMessageAds" {enableMessageAds_checked}>
|
|
30
|
+
<label class="form-check-label" for="enableMessageAds">Activer les pubs “message”</label>
|
|
31
|
+
</div>
|
|
51
32
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
33
|
+
<div class="mb-3">
|
|
34
|
+
<label class="form-label" for="messagePlaceholderIds">Pool d’IDs Ezoic (message)</label>
|
|
35
|
+
<textarea id="messagePlaceholderIds" name="messagePlaceholderIds" class="form-control" rows="4">{messagePlaceholderIds}</textarea>
|
|
36
|
+
<p class="form-text">Pool séparé recommandé pour éviter la réutilisation d’IDs. IMPORTANT : ne réutilise pas les mêmes IDs dans les deux pools.</p>
|
|
37
|
+
</div>
|
|
56
38
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
</form>
|
|
61
|
-
</div>
|
|
39
|
+
<div class="mb-3">
|
|
40
|
+
<label class="form-label" for="messageIntervalPosts">Afficher un “message pub” tous les N messages</label>
|
|
41
|
+
<input type="number" id="messageIntervalPosts" name="messageIntervalPosts" class="form-control" value="{messageIntervalPosts}" min="1">
|
|
62
42
|
</div>
|
|
63
|
-
</div>
|
|
64
43
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
</
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
44
|
+
<hr/>
|
|
45
|
+
|
|
46
|
+
<h4 class="mt-3">Exclusions</h4>
|
|
47
|
+
<div class="mb-3">
|
|
48
|
+
<label class="form-label" for="excludedGroups">Groupes exclus</label>
|
|
49
|
+
<select id="excludedGroups" name="excludedGroups" class="form-select" multiple>
|
|
50
|
+
<!-- BEGIN allGroups -->
|
|
51
|
+
<option value="{allGroups.name}">{allGroups.name}</option>
|
|
52
|
+
<!-- END allGroups -->
|
|
53
|
+
</select>
|
|
54
|
+
<p class="form-text">Si l’utilisateur appartient à un de ces groupes, aucune pub n’est injectée.</p>
|
|
76
55
|
</div>
|
|
77
|
-
|
|
56
|
+
|
|
57
|
+
<button id="save" class="btn btn-primary">Enregistrer</button>
|
|
58
|
+
</form>
|
|
78
59
|
</div>
|
package/README.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
nodebb-plugin-ezoic-infinite v1.0.5
|
|
2
|
-
|
|
3
|
-
- ACP kept in the same spirit as v0.9.5 (server-rendered groups + admin.settings.get/set)
|
|
4
|
-
- Injection logic matches v0.9.7: showAds called one placeholder at a time, separate pools, FIFO recycling.
|
|
5
|
-
- Frontend settings are read from window.config.ezoicInfinite (filter:config.get), no admin endpoints used on frontend.
|